Initial version to monorepo-setup to start experimenting with, featuring a fork of @babel JSX-transformation, and a crude VDOM>DOM rendering-step. The Custom-Elements part is just a placeholder at this point but ready to be populated with base-classes and decorators.
This commit is contained in:
commit
8ae591810a
41
package.json
Normal file
41
package.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "csx-ce-monorepo",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "latest",
|
||||||
|
"@babel/core": "^7.6.2",
|
||||||
|
"@babel/plugin-proposal-class-properties": "latest",
|
||||||
|
"@babel/plugin-proposal-decorators": "latest",
|
||||||
|
"@babel/plugin-proposal-export-default-from": "latest",
|
||||||
|
"@babel/plugin-proposal-export-namespace-from": "latest",
|
||||||
|
"@babel/plugin-proposal-nullish-coalescing-operator": "latest",
|
||||||
|
"@babel/plugin-proposal-optional-chaining": "latest",
|
||||||
|
"@babel/plugin-proposal-private-methods": "latest",
|
||||||
|
"@babel/plugin-transform-react-jsx": "latest",
|
||||||
|
"@babel/preset-env": "latest",
|
||||||
|
"jsdoc": "latest",
|
||||||
|
"sass": "latest",
|
||||||
|
"rollup": "latest",
|
||||||
|
"rollup-plugin-babel": "latest",
|
||||||
|
"rollup-plugin-node-resolve": "latest",
|
||||||
|
"rollup-plugin-commonjs": "latest",
|
||||||
|
"rollup-plugin-terser": "latest",
|
||||||
|
"rollup-plugin-copy": "latest",
|
||||||
|
"rollup-plugin-sass": "latest",
|
||||||
|
"serve": "latest",
|
||||||
|
"npm-run-all": "latest"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "npm-run-all -p watch start:test",
|
||||||
|
"build": "npm-run-all -s build:babel-transform-csx build:csx-custom-elements build:test",
|
||||||
|
"watch": "npm-run-all -p watch:babel-transform-csx watch:csx-custom-elements watch:test",
|
||||||
|
"start:test": "serve public",
|
||||||
|
"build:test": "rollup -c",
|
||||||
|
"watch:test": "rollup -c -w",
|
||||||
|
"build:babel-transform-csx": "cd ./packages/babel-plugin-transform-csx-jsx && npm run build",
|
||||||
|
"build:csx-custom-elements": "cd ./packages/csx-custom-elements && npm run build",
|
||||||
|
"watch:babel-transform-csx": "cd ./packages/babel-plugin-transform-csx-jsx && npm run watch",
|
||||||
|
"watch:csx-custom-elements": "cd ./packages/csx-custom-elements && npm run watch"
|
||||||
|
}
|
||||||
|
}
|
||||||
34
packages/babel-plugin-transform-csx-jsx/package.json
Normal file
34
packages/babel-plugin-transform-csx-jsx/package.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "babel-plugin-transform-csx-jsx",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"devDependencies": {
|
||||||
|
"esutils": "^2.0.3",
|
||||||
|
"@babel/cli": "7.6.4",
|
||||||
|
"@babel/plugin-proposal-class-properties": "latest",
|
||||||
|
"@babel/plugin-proposal-decorators": "latest",
|
||||||
|
"@babel/plugin-proposal-export-default-from": "latest",
|
||||||
|
"@babel/plugin-proposal-export-namespace-from": "latest",
|
||||||
|
"@babel/plugin-proposal-nullish-coalescing-operator": "latest",
|
||||||
|
"@babel/plugin-proposal-optional-chaining": "latest",
|
||||||
|
"@babel/plugin-proposal-private-methods": "latest",
|
||||||
|
"@babel/preset-env": "latest",
|
||||||
|
"@babel/helper-builder-react-jsx": "7.3.0",
|
||||||
|
"@babel/helper-plugin-utils": "7.0.0",
|
||||||
|
"@babel/plugin-syntax-jsx": "7.2.0",
|
||||||
|
"jsdoc": "latest",
|
||||||
|
"rollup": "latest",
|
||||||
|
"rollup-plugin-babel": "latest",
|
||||||
|
"rollup-plugin-node-resolve": "latest",
|
||||||
|
"rollup-plugin-commonjs": "latest",
|
||||||
|
"rollup-plugin-terser": "latest",
|
||||||
|
"rollup-plugin-json": "latest",
|
||||||
|
"npm-run-all": "latest"
|
||||||
|
},
|
||||||
|
"dependencies":{
|
||||||
|
"@babel/core": "7.6.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup -c",
|
||||||
|
"watch": "rollup -c -w"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
packages/babel-plugin-transform-csx-jsx/rollup.config.js
Normal file
29
packages/babel-plugin-transform-csx-jsx/rollup.config.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import babel from 'rollup-plugin-babel';
|
||||||
|
import resolve from 'rollup-plugin-node-resolve';
|
||||||
|
import commonjs from 'rollup-plugin-commonjs';
|
||||||
|
import { terser } from 'rollup-plugin-terser';
|
||||||
|
import json from "rollup-plugin-json";
|
||||||
|
|
||||||
|
// `npm run build` -> `production` is true
|
||||||
|
// `npm run dev` -> `production` is false
|
||||||
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/index.js',
|
||||||
|
output: {
|
||||||
|
file: 'dist/index.js',
|
||||||
|
format: 'cjs', // immediately-invoked function expression — suitable for <script> tags
|
||||||
|
sourcemap: true
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
json(), // Add json support (sadly in rollup we have to do this explicity, despite the NodeJS algorithm supporitng this by defulat
|
||||||
|
babel(), // babel (the reason we're doing all of this, babel transpiling)
|
||||||
|
resolve({// node_modules (again we have to add support in rollup for something that is NodeJS default)
|
||||||
|
dedupe: [ '@babel/core', "@babel/helper-plugin-utils", "@babel/plugin-syntax-jsx", "@babel/helper-builder-react-jsx" , "@babel/types"]
|
||||||
|
}),
|
||||||
|
commonjs({ // CJS-modules (require-style bundle support, again something rollup doesnt handle by default)
|
||||||
|
}),
|
||||||
|
production && terser() // minify, but only in production
|
||||||
|
],
|
||||||
|
external: ['@babel/core','@babel/traverse', "@babel/types" ]
|
||||||
|
};
|
||||||
269
packages/babel-plugin-transform-csx-jsx/src/helper.js
Normal file
269
packages/babel-plugin-transform-csx-jsx/src/helper.js
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
// This is based of off @babel/helper-builder-react-jsx package
|
||||||
|
|
||||||
|
import esutils from "esutils";
|
||||||
|
import * as t from "@babel/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ElementState
|
||||||
|
* @property {Object} tagExpr - tag node
|
||||||
|
* @property {string} [tagName] - raw string tag name
|
||||||
|
* @property {Array.<Object>} args - array of call arguments
|
||||||
|
* @property {Object} [call] - optional call property that can be set to override the call expression returned
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param opts
|
||||||
|
*/
|
||||||
|
export default function(opts) {
|
||||||
|
const visitor = {};
|
||||||
|
|
||||||
|
visitor.JSXNamespacedName = function(path) {
|
||||||
|
if (opts.throwIfNamespace) {
|
||||||
|
throw path.buildCodeFrameError(
|
||||||
|
`Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
|
||||||
|
You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
visitor.JSXSpreadChild = function(path) {
|
||||||
|
throw path.buildCodeFrameError(
|
||||||
|
"Spread children are not supported in React.",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
visitor.JSXElement = {
|
||||||
|
exit(path, file) {
|
||||||
|
const callExpr = buildElementCall(path, file);
|
||||||
|
if (callExpr) {
|
||||||
|
path.replaceWith(t.inherits(callExpr, path.node));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
visitor.JSXFragment = {
|
||||||
|
exit(path, file) {
|
||||||
|
if (opts.compat) {
|
||||||
|
throw path.buildCodeFrameError(
|
||||||
|
"Fragment tags are only supported in React 16 and up.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const callExpr = buildFragmentCall(path, file);
|
||||||
|
if (callExpr) {
|
||||||
|
path.replaceWith(t.inherits(callExpr, path.node));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return visitor;
|
||||||
|
|
||||||
|
function convertJSXIdentifier(node, parent) {
|
||||||
|
if (t.isJSXIdentifier(node)) {
|
||||||
|
if (node.name === "this" && t.isReferenced(node, parent)) {
|
||||||
|
return t.thisExpression();
|
||||||
|
} else if (esutils.keyword.isIdentifierNameES6(node.name)) {
|
||||||
|
node.type = "Identifier";
|
||||||
|
} else {
|
||||||
|
return t.stringLiteral(node.name);
|
||||||
|
}
|
||||||
|
} else if (t.isJSXMemberExpression(node)) {
|
||||||
|
return t.memberExpression(
|
||||||
|
convertJSXIdentifier(node.object, node),
|
||||||
|
convertJSXIdentifier(node.property, node),
|
||||||
|
);
|
||||||
|
} else if (t.isJSXNamespacedName(node)) {
|
||||||
|
/**
|
||||||
|
* If there is flag "throwIfNamespace"
|
||||||
|
* print XMLNamespace like string literal
|
||||||
|
*/
|
||||||
|
return t.stringLiteral(`${node.namespace.name}:${node.name.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertAttributeValue(node) {
|
||||||
|
if (t.isJSXExpressionContainer(node)) {
|
||||||
|
return node.expression;
|
||||||
|
} else {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertAttribute(node) {
|
||||||
|
const value = convertAttributeValue(node.value || t.booleanLiteral(true));
|
||||||
|
|
||||||
|
if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
|
||||||
|
value.value = value.value.replace(/\n\s+/g, " ");
|
||||||
|
|
||||||
|
// "raw" JSXText should not be used from a StringLiteral because it needs to be escaped.
|
||||||
|
if (value.extra && value.extra.raw) {
|
||||||
|
delete value.extra.raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.isJSXNamespacedName(node.name)) {
|
||||||
|
node.name = t.stringLiteral(
|
||||||
|
node.name.namespace.name + ":" + node.name.name.name,
|
||||||
|
);
|
||||||
|
} else if (esutils.keyword.isIdentifierNameES6(node.name.name)) {
|
||||||
|
node.name.type = "Identifier";
|
||||||
|
} else {
|
||||||
|
node.name = t.stringLiteral(node.name.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.inherits(t.objectProperty(node.name, value), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildElementCall(path, file) {
|
||||||
|
if (opts.filter && !opts.filter(path.node, file)) return;
|
||||||
|
|
||||||
|
const openingPath = path.get("openingElement");
|
||||||
|
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
||||||
|
|
||||||
|
const tagExpr = convertJSXIdentifier(
|
||||||
|
openingPath.node.name,
|
||||||
|
openingPath.node,
|
||||||
|
);
|
||||||
|
const args = [];
|
||||||
|
|
||||||
|
let tagName;
|
||||||
|
if (t.isIdentifier(tagExpr)) {
|
||||||
|
tagName = tagExpr.name;
|
||||||
|
} else if (t.isLiteral(tagExpr)) {
|
||||||
|
tagName = tagExpr.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ElementState}
|
||||||
|
*/
|
||||||
|
const state = {
|
||||||
|
tagExpr: tagExpr,
|
||||||
|
tagName: tagName,
|
||||||
|
args: args,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (opts.pre) {
|
||||||
|
opts.pre(state, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
let attribs = openingPath.node.attributes;
|
||||||
|
if (attribs.length) {
|
||||||
|
attribs = buildOpeningElementAttributes(attribs, file);
|
||||||
|
} else {
|
||||||
|
attribs = t.nullLiteral();
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(attribs, ...path.node.children);
|
||||||
|
|
||||||
|
if (opts.post) {
|
||||||
|
opts.post(state, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS IS THE LINE THAT USED TO BE HERE: (it maps JSX to a call of whatever the pragma-options is
|
||||||
|
// return state.call || t.callExpression(state.callee, args);
|
||||||
|
// THIS IS THE REPLACEMENT: (it maps JSX to a VNode-object)
|
||||||
|
if(state.call) return state.call;
|
||||||
|
else{
|
||||||
|
let vnodeExpr = t.objectExpression([
|
||||||
|
t.objectProperty(t.identifier('type'), args[0]),
|
||||||
|
t.objectProperty(t.identifier('props'), args[1]),
|
||||||
|
t.objectProperty(t.identifier('children'), t.arrayExpression(args.slice(2)))
|
||||||
|
]);
|
||||||
|
if(state.callee) return t.callExpression(state.callee, [vnodeExpr]);
|
||||||
|
else return vnodeExpr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushProps(_props, objs) {
|
||||||
|
if (!_props.length) return _props;
|
||||||
|
|
||||||
|
objs.push(t.objectExpression(_props));
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The logic for this is quite terse. It's because we need to
|
||||||
|
* support spread elements. We loop over all attributes,
|
||||||
|
* breaking on spreads, we then push a new object containing
|
||||||
|
* all prior attributes to an array for later processing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function buildOpeningElementAttributes(attribs, file) {
|
||||||
|
let _props = [];
|
||||||
|
const objs = [];
|
||||||
|
|
||||||
|
const useBuiltIns = file.opts.useBuiltIns || false;
|
||||||
|
if (typeof useBuiltIns !== "boolean") {
|
||||||
|
throw new Error(
|
||||||
|
"transform-react-jsx currently only accepts a boolean option for " +
|
||||||
|
"useBuiltIns (defaults to false)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (attribs.length) {
|
||||||
|
const prop = attribs.shift();
|
||||||
|
if (t.isJSXSpreadAttribute(prop)) {
|
||||||
|
_props = pushProps(_props, objs);
|
||||||
|
objs.push(prop.argument);
|
||||||
|
} else {
|
||||||
|
_props.push(convertAttribute(prop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pushProps(_props, objs);
|
||||||
|
|
||||||
|
if (objs.length === 1) {
|
||||||
|
// only one object
|
||||||
|
attribs = objs[0];
|
||||||
|
} else {
|
||||||
|
// looks like we have multiple objects
|
||||||
|
if (!t.isObjectExpression(objs[0])) {
|
||||||
|
objs.unshift(t.objectExpression([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const helper = useBuiltIns
|
||||||
|
? t.memberExpression(t.identifier("Object"), t.identifier("assign"))
|
||||||
|
: file.addHelper("extends");
|
||||||
|
|
||||||
|
// spread it
|
||||||
|
attribs = t.callExpression(helper, objs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return attribs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFragmentCall(path, file) {
|
||||||
|
if (opts.filter && !opts.filter(path.node, file)) return;
|
||||||
|
|
||||||
|
const openingPath = path.get("openingElement");
|
||||||
|
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
||||||
|
|
||||||
|
const args = [];
|
||||||
|
const tagName = null;
|
||||||
|
const tagExpr = file.get("jsxFragIdentifier")();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ElementState}
|
||||||
|
*/
|
||||||
|
const state= {
|
||||||
|
tagExpr: tagExpr,
|
||||||
|
tagName: tagName,
|
||||||
|
args: args,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (opts.pre) {
|
||||||
|
opts.pre(state, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no attributes are allowed with <> syntax
|
||||||
|
args.push(t.nullLiteral(), ...path.node.children);
|
||||||
|
|
||||||
|
if (opts.post) {
|
||||||
|
opts.post(state, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
file.set("usedFragment", true);
|
||||||
|
return state.call || t.callExpression(state.callee, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
112
packages/babel-plugin-transform-csx-jsx/src/index.js
Normal file
112
packages/babel-plugin-transform-csx-jsx/src/index.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// At the time of writing this is a copy of react-jsx transformer plugin..
|
||||||
|
|
||||||
|
import { declare } from "@babel/helper-plugin-utils";
|
||||||
|
import jsx from "@babel/plugin-syntax-jsx";
|
||||||
|
import { types as t } from "@babel/core";
|
||||||
|
import helper from "./helper";
|
||||||
|
|
||||||
|
export default declare((api, options) => {
|
||||||
|
api.assertVersion(7);
|
||||||
|
|
||||||
|
const THROW_IF_NAMESPACE =
|
||||||
|
options.throwIfNamespace === undefined ? true : !!options.throwIfNamespace;
|
||||||
|
|
||||||
|
const PRAGMA_DEFAULT = options.pragma;
|
||||||
|
const PRAGMA_FRAG_DEFAULT = options.pragmaFrag;
|
||||||
|
|
||||||
|
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
|
||||||
|
const JSX_FRAG_ANNOTATION_REGEX = /\*?\s*@jsxFrag\s+([^\s]+)/;
|
||||||
|
|
||||||
|
// returns a closure that returns an identifier or memberExpression node
|
||||||
|
// based on the given id
|
||||||
|
const createIdentifierParser = (id) => () => {
|
||||||
|
return id
|
||||||
|
.split(".")
|
||||||
|
.map(name => t.identifier(name))
|
||||||
|
.reduce((object, property) => t.memberExpression(object, property));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Was testing with this but it made more sense ot keep in in helper.js for now
|
||||||
|
// const createVNodeExpr = ({type, props, children})=>{
|
||||||
|
// return t.objectExpression([
|
||||||
|
// t.objectProperty(t.identifier('type'), type),
|
||||||
|
// t.objectProperty(t.identifier('props'), props),
|
||||||
|
// t.objectProperty(t.identifier('children'), children),
|
||||||
|
// ]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const visitor = helper({
|
||||||
|
pre(state) {
|
||||||
|
const tagName = state.tagName;
|
||||||
|
const args = state.args;
|
||||||
|
if (t.react.isCompatTag(tagName)) {
|
||||||
|
args.push(t.stringLiteral(tagName));
|
||||||
|
} else {
|
||||||
|
args.push(state.tagExpr);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
post(state, pass) {
|
||||||
|
let jsxIdent = pass.get("jsxIdentifier");
|
||||||
|
state.callee = jsxIdent? jsxIdent() : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
throwIfNamespace: THROW_IF_NAMESPACE,
|
||||||
|
});
|
||||||
|
|
||||||
|
visitor.Program = {
|
||||||
|
enter(path, state) {
|
||||||
|
const { file } = state;
|
||||||
|
|
||||||
|
let pragma = PRAGMA_DEFAULT;
|
||||||
|
let pragmaFrag = PRAGMA_FRAG_DEFAULT;
|
||||||
|
let pragmaSet = !!options.pragma;
|
||||||
|
let pragmaFragSet = !!options.pragmaFrag;
|
||||||
|
|
||||||
|
if (file.ast.comments) {
|
||||||
|
for (const comment of (file.ast.comments)) {
|
||||||
|
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
|
||||||
|
if (jsxMatches) {
|
||||||
|
pragma = jsxMatches[1];
|
||||||
|
pragmaSet = true;
|
||||||
|
}
|
||||||
|
const jsxFragMatches = JSX_FRAG_ANNOTATION_REGEX.exec(comment.value);
|
||||||
|
if (jsxFragMatches) {
|
||||||
|
pragmaFrag = jsxFragMatches[1];
|
||||||
|
pragmaFragSet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pragma) state.set("jsxIdentifier", createIdentifierParser(pragma));
|
||||||
|
if(pragmaFrag) state.set("jsxFragIdentifier", createIdentifierParser(pragmaFrag));
|
||||||
|
state.set("usedFragment", false);
|
||||||
|
state.set("pragmaSet", pragmaSet);
|
||||||
|
state.set("pragmaFragSet", pragmaFragSet);
|
||||||
|
},
|
||||||
|
exit(path, state) {
|
||||||
|
if (
|
||||||
|
state.get("pragmaSet") &&
|
||||||
|
state.get("usedFragment") &&
|
||||||
|
!state.get("pragmaFragSet")
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"transform-react-jsx: pragma has been set but " +
|
||||||
|
"pragmafrag has not been set",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
visitor.JSXAttribute = function(path) {
|
||||||
|
if (t.isJSXElement(path.node.value)) {
|
||||||
|
path.node.value = t.jsxExpressionContainer(path.node.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "transform-react-jsx",
|
||||||
|
inherits: jsx,
|
||||||
|
visitor,
|
||||||
|
};
|
||||||
|
});
|
||||||
2865
packages/babel-plugin-transform-csx-jsx/yarn.lock
Normal file
2865
packages/babel-plugin-transform-csx-jsx/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
35
packages/csx-custom-elements/package.json
Normal file
35
packages/csx-custom-elements/package.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "csx-ce",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "latest",
|
||||||
|
"@babel/core": "^7.6.2",
|
||||||
|
"@babel/plugin-proposal-class-properties": "latest",
|
||||||
|
"@babel/plugin-proposal-decorators": "latest",
|
||||||
|
"@babel/plugin-proposal-export-default-from": "latest",
|
||||||
|
"@babel/plugin-proposal-export-namespace-from": "latest",
|
||||||
|
"@babel/plugin-proposal-nullish-coalescing-operator": "latest",
|
||||||
|
"@babel/plugin-proposal-optional-chaining": "latest",
|
||||||
|
"@babel/plugin-proposal-private-methods": "latest",
|
||||||
|
"@babel/preset-env": "latest",
|
||||||
|
"jsdoc": "latest",
|
||||||
|
"rollup": "latest",
|
||||||
|
"rollup-plugin-babel": "latest",
|
||||||
|
"rollup-plugin-node-resolve": "latest",
|
||||||
|
"rollup-plugin-commonjs": "latest",
|
||||||
|
"rollup-plugin-terser": "latest",
|
||||||
|
"rollup-plugin-json": "latest",
|
||||||
|
"npm-run-all": "latest"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "npm-run-all -p build-cjs build-es6",
|
||||||
|
"watch": "npm-run-all -p watch-cjs watch-es6",
|
||||||
|
"build-cjs": "rollup -c",
|
||||||
|
"watch-cjs": "rollup -c -w",
|
||||||
|
"build-es6": "npx babel ./src --out-dir=lib --source-maps",
|
||||||
|
"watch-es6": "npx babel ./src --out-dir=lib --source-maps -w"
|
||||||
|
},
|
||||||
|
"module": "./lib/index.js",
|
||||||
|
"main": "./dist/index.js"
|
||||||
|
}
|
||||||
30
packages/csx-custom-elements/rollup.config.js
Normal file
30
packages/csx-custom-elements/rollup.config.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import babel from 'rollup-plugin-babel';
|
||||||
|
import resolve from 'rollup-plugin-node-resolve';
|
||||||
|
import commonjs from 'rollup-plugin-commonjs';
|
||||||
|
import { terser } from 'rollup-plugin-terser';
|
||||||
|
import json from "rollup-plugin-json";
|
||||||
|
|
||||||
|
// `npm run build` -> `production` is true
|
||||||
|
// `npm run dev` -> `production` is false
|
||||||
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/index.js',
|
||||||
|
output: {
|
||||||
|
file: 'dist/index.js',
|
||||||
|
format: 'cjs', // immediately-invoked function expression — suitable for <script> tags
|
||||||
|
sourcemap: true
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
json(), // Add json support (sadly in rollup we have to do this explicity, despite the NodeJS algorithm supporitng this by defulat
|
||||||
|
babel(), // babel (the reason we're doing all of this, babel transpiling)
|
||||||
|
resolve({// node_modules (again we have to add support in rollup for something that is NodeJS default)
|
||||||
|
}),
|
||||||
|
commonjs({ // CJS-modules (require-style bundle support, again something rollup doesnt handle by default)
|
||||||
|
}),
|
||||||
|
production && terser() // minify, but only in production
|
||||||
|
],
|
||||||
|
external: [
|
||||||
|
|
||||||
|
]
|
||||||
|
};
|
||||||
13
packages/csx-custom-elements/src/.babelrc
Normal file
13
packages/csx-custom-elements/src/.babelrc
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
[ "@babel/plugin-proposal-decorators", { "legacy": true }],
|
||||||
|
[ "@babel/plugin-proposal-class-properties", { "loose": true } ],
|
||||||
|
[ "@babel/plugin-proposal-private-methods", {"loose": true } ],
|
||||||
|
[ "@babel/plugin-proposal-optional-chaining" ],
|
||||||
|
[ "@babel/plugin-proposal-nullish-coalescing-operator" ],
|
||||||
|
[ "@babel/plugin-proposal-export-namespace-from" ],
|
||||||
|
[ "@babel/plugin-proposal-export-default-from" ]
|
||||||
|
]
|
||||||
|
}
|
||||||
1
packages/csx-custom-elements/src/custom-element/index.js
Normal file
1
packages/csx-custom-elements/src/custom-element/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './custom-element'
|
||||||
2
packages/csx-custom-elements/src/index.js
Normal file
2
packages/csx-custom-elements/src/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './vdom';
|
||||||
|
export * from './custom-element';
|
||||||
18
packages/csx-custom-elements/src/vdom/constants.js
Normal file
18
packages/csx-custom-elements/src/vdom/constants.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
export const VNODE_EXCLUDE = {
|
||||||
|
[null]: true,
|
||||||
|
[undefined]: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keys of a Element to be set directly rather than using setAttribute
|
||||||
|
export const VNODEPROP_DIRECT = {
|
||||||
|
['checked']: true
|
||||||
|
};
|
||||||
|
export const VNODEPROP_EXCLUDE_DIRECT = {
|
||||||
|
['style']: true,
|
||||||
|
['class']: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VNODEPROP_IGNORE = {
|
||||||
|
['key']: true,
|
||||||
|
};
|
||||||
2
packages/csx-custom-elements/src/vdom/index.js
Normal file
2
packages/csx-custom-elements/src/vdom/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./vnode";
|
||||||
|
export * from "./render";
|
||||||
91
packages/csx-custom-elements/src/vdom/render.js
Normal file
91
packages/csx-custom-elements/src/vdom/render.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import './vnode';
|
||||||
|
import {
|
||||||
|
VNODE_EXCLUDE,
|
||||||
|
VNODEPROP_DIRECT, VNODEPROP_EXCLUDE_DIRECT,
|
||||||
|
VNODEPROP_IGNORE,
|
||||||
|
} from "./constants";
|
||||||
|
|
||||||
|
// This is copied from the blog sample right now. it should process jsx but it aint what it needs to be
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} RenderOptions
|
||||||
|
* @category VDOM
|
||||||
|
* @property {Element} host - A host element to update to the specified VDOM
|
||||||
|
* TODO: Other options clearly...
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exists as a very basic example/test for JSX-to-DOM
|
||||||
|
* @param {VNode} vnode
|
||||||
|
* @param {RenderOptions} opts
|
||||||
|
* @return {Element}
|
||||||
|
*/
|
||||||
|
export function render(vnode, opts = {}) {
|
||||||
|
// Replace how this works into a queue instead of a recursive call, also consider changing JSX to support (changed)="..." notation
|
||||||
|
let {
|
||||||
|
/**
|
||||||
|
* @type {Element}
|
||||||
|
*/
|
||||||
|
host
|
||||||
|
} = opts;
|
||||||
|
|
||||||
|
if(VNODE_EXCLUDE[vnode]) return undefined;
|
||||||
|
|
||||||
|
console.log(vnode);
|
||||||
|
|
||||||
|
if(vnode instanceof Object){
|
||||||
|
// Type
|
||||||
|
if(!host) host = document.createElement(vnode.type);
|
||||||
|
|
||||||
|
// Props
|
||||||
|
if (vnode.props) {
|
||||||
|
if (vnode.props.style && typeof (vnode.props.style) === 'object') {
|
||||||
|
for (let styleKey in vnode.props.style) {
|
||||||
|
host.style[ styleKey ] = vnode.props.style[ styleKey ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let key in vnode.props) {
|
||||||
|
let val = vnode.props[key];
|
||||||
|
if(VNODEPROP_IGNORE[key]){
|
||||||
|
// NO-OP
|
||||||
|
}else if(VNODEPROP_DIRECT[key]){
|
||||||
|
host[key] = val;
|
||||||
|
}else{
|
||||||
|
if(!VNODEPROP_EXCLUDE_DIRECT[key] && !key.indexOf('-')){
|
||||||
|
host[key] = val;
|
||||||
|
}
|
||||||
|
if (val === false) {
|
||||||
|
host.removeAttribute(key);
|
||||||
|
} else if (val === true) {
|
||||||
|
host.setAttribute(key, "");
|
||||||
|
} else{
|
||||||
|
host.setAttribute(key, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Children
|
||||||
|
if (vnode.children) {
|
||||||
|
let children = vnode.children instanceof Array? vnode.children : [vnode.children];
|
||||||
|
|
||||||
|
for(let child of children){
|
||||||
|
let el = child instanceof Element? child : render(child, {
|
||||||
|
...opts,
|
||||||
|
host: undefined
|
||||||
|
});
|
||||||
|
if(el!==undefined){
|
||||||
|
host.appendChild(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO figure out how to handle events (its not that easy to create (click)={this.onClick} or something, that is not supporter by the @babel/parser and we'd have to fork it..
|
||||||
|
// TODO ref-prop (should it only return once all child els are created and appended to the child?!)
|
||||||
|
// TODO innerHTML, innerText and other tags/props that are trickyer then just mapping value to attribute
|
||||||
|
}else{
|
||||||
|
if(!host) host = document.createTextNode(vnode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
44
packages/csx-custom-elements/src/vdom/vnode.js
Normal file
44
packages/csx-custom-elements/src/vdom/vnode.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Type of a node, this is usally the string-tag, but when a CustomElement was created using the @CustomElement annotation the class itself may be used
|
||||||
|
* @typedef {string|null|Component} VNodeType
|
||||||
|
* @category VDOM
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties of a node
|
||||||
|
* @typedef {Object.<string, any>} VNodeProps
|
||||||
|
* @category VDOM
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Children of a node
|
||||||
|
* @typedef {VNode|Element|Array.<VNode|Element>} VNodeChildren
|
||||||
|
* @category VDOM
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef VNode
|
||||||
|
* @interface
|
||||||
|
* @category VDOM
|
||||||
|
* @property {VNodeType} type - TagName or CustomElement of the html-element
|
||||||
|
* @property {VNodeProps} props - Properties to set on the element
|
||||||
|
* @property {VNodeChildren} children - Children of the element
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exists as a very basic example/test for JSX-to-DOM.
|
||||||
|
*
|
||||||
|
* The custom babel-plugin-transform-csx-jsx removes the need for this, only use asVNode when using with the default
|
||||||
|
* transform-react plugin of babel
|
||||||
|
*
|
||||||
|
* @param {VNodeType} type
|
||||||
|
* @param {VNodeProps} props
|
||||||
|
* @param {VNodeChildren} children
|
||||||
|
* @return {VNode}
|
||||||
|
*/
|
||||||
|
export function asVNode(type, props, ...children) {
|
||||||
|
let vnode = {type, props, children};
|
||||||
|
console.log(vnode);
|
||||||
|
return vnode;
|
||||||
|
}
|
||||||
2850
packages/csx-custom-elements/yarn.lock
Normal file
2850
packages/csx-custom-elements/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
32
rollup.config.js
Normal file
32
rollup.config.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import babel from 'rollup-plugin-babel';
|
||||||
|
import resolve from 'rollup-plugin-node-resolve';
|
||||||
|
import commonjs from 'rollup-plugin-commonjs';
|
||||||
|
import { terser } from 'rollup-plugin-terser';
|
||||||
|
import copy from "rollup-plugin-copy";
|
||||||
|
import sass from "rollup-plugin-sass";
|
||||||
|
|
||||||
|
// `npm run build` -> `production` is true
|
||||||
|
// `npm run dev` -> `production` is false
|
||||||
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'test/index.jsx',
|
||||||
|
output: {
|
||||||
|
file: 'public/index.js',
|
||||||
|
format: 'iife', // immediately-invoked function expression — suitable for <script> tags
|
||||||
|
sourcemap: true
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
sass(),
|
||||||
|
babel(), // babel
|
||||||
|
resolve(), // node_modules
|
||||||
|
commonjs(), // CJS-modules
|
||||||
|
production && terser(), // minify, but only in production
|
||||||
|
copy({
|
||||||
|
targets: [
|
||||||
|
{ src: 'test/index.html', dest: 'public' }
|
||||||
|
],
|
||||||
|
copyOnce: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
23
test/.babelrc
Normal file
23
test/.babelrc
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
["@babel/preset-env", {
|
||||||
|
"targets": {
|
||||||
|
"node": "current"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
[ "@babel/plugin-proposal-decorators", { "legacy": true }],
|
||||||
|
[ "@babel/plugin-proposal-class-properties", { "loose": true } ],
|
||||||
|
[ "@babel/plugin-proposal-private-methods", {"loose": true } ],
|
||||||
|
[ "@babel/plugin-proposal-optional-chaining" ],
|
||||||
|
[ "@babel/plugin-proposal-nullish-coalescing-operator" ],
|
||||||
|
[ "@babel/plugin-proposal-export-namespace-from" ],
|
||||||
|
[ "@babel/plugin-proposal-export-default-from" ],
|
||||||
|
[ "../packages/babel-plugin-transform-csx-jsx/dist", {
|
||||||
|
//"pragma": "render",
|
||||||
|
//"pragmaFrag": "render",
|
||||||
|
"throwIfNamespace": false
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
10
test/index.html
Normal file
10
test/index.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Cerxes - CustomElements</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="text/javascript" src="index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
28
test/index.jsx
Normal file
28
test/index.jsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import {render} from "../packages/csx-custom-elements/lib";
|
||||||
|
import style from "./index.scss";
|
||||||
|
|
||||||
|
document.body.appendChild(render(<style>{style}</style>));
|
||||||
|
document.body.appendChild(render(<div class="center-me" iCanDoUpperCaseAttrs={ "yes" }>
|
||||||
|
<h1>I am a title!</h1>
|
||||||
|
</div>));
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Findings:
|
||||||
|
* - JSX does not allow dot-notation in attributes: language error
|
||||||
|
* - Current code lower-cases attributes that result: to be investigated further... is this a limitation of setAttribute?
|
||||||
|
* - React uses on<EventName> to capture events and the IDE auto-suggests using this (can we generalize this approach for customEvents?)
|
||||||
|
* - Nothing stops us from using
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continuation suggestionss:
|
||||||
|
* - ref={...} does not work yet
|
||||||
|
* - style-attribute untested
|
||||||
|
* - Want a way to toggle classes: <Host class={{'bq-checkbox': true, 'checked': this.isChecked}}> could do
|
||||||
|
* - Need to support update an existing DOM-tree to a VNode-tree
|
||||||
|
* - Need to support the key-attribute for lists (linking with previous to have an idea how to update DOM-tree efficiently, are we going atomico/react/prect style diffing with a Virtual-DOM?)
|
||||||
|
* - <Host> and <ShadowDom> special handlers
|
||||||
|
* - Supporting fragments <>...</>?
|
||||||
|
* - Try working towards a simple ToDo-MVC application
|
||||||
|
*/
|
||||||
19
test/index.scss
Normal file
19
test/index.scss
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
html{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-me{
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user