From 42b8cd9b7bf07522d6a06ad075db8a943f5e5e73 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Thu, 18 Jul 2019 00:25:24 -0700 Subject: [PATCH] major updates for 0.11.0 --- README.md | 111 +------ package-lock.json | 199 ++++++++---- package.json | 6 +- src/index.js | 296 ++++++------------ .../__fixtures__/attributeExpressions/code.js | 3 +- .../attributeExpressions/output.js | 7 +- .../{subTemplates => components}/code.js | 9 +- test/__fixtures__/components/output.js | 89 ++++++ test/__fixtures__/customElements/output.js | 15 +- test/__fixtures__/eventExpressions/output.js | 2 +- test/__fixtures__/flow/code.js | 70 ----- test/__fixtures__/flow/output.js | 158 ---------- test/__fixtures__/fragments/output.js | 99 +----- test/__fixtures__/simpleElements/output.js | 2 +- test/__fixtures__/subTemplates/output.js | 81 ----- test/__fixtures__/textInterpolation/output.js | 12 +- test/plugin.spec.js | 4 +- types/index.d.ts | 19 -- 18 files changed, 390 insertions(+), 792 deletions(-) rename test/__fixtures__/{subTemplates => components}/code.js (85%) create mode 100644 test/__fixtures__/components/output.js delete mode 100644 test/__fixtures__/flow/code.js delete mode 100644 test/__fixtures__/flow/output.js delete mode 100644 test/__fixtures__/subTemplates/output.js diff --git a/README.md b/README.md index 39e4ef4..9df5185 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This package is a JSX compiler built for [DOM Expressions](https://github.com/ry ## Features -This plugin treats all lowercase tags as html elements and mixed cased tags as Custom Functions. This enables breaking up your view into functional components. This library supports Web Component Custom Elements spec. Support for common camelcase event handlers like React, dom safe attributes like class and for, a simple ref property, and parsing of objects for style, and classList properties. +This plugin treats all lowercase tags as html elements and mixed cased tags as Custom Functions. This enables breaking up your view into components. This library supports Web Component Custom Elements spec. Support for common camelcase event handlers like React, dom safe attributes like class and for, a simple ref property, and parsing of objects for style, and classList properties. In general JSX Attribute Expressions are treated as properties by default, with exception of hyphenated(-) ones that will always be set as attributes on the DOM element. Plain string attributes(Non expression, no {}) will be treated as attributes. @@ -39,7 +39,7 @@ import { insert as _$insert } from "dom"; import { wrap as _$wrap } from "dom"; const _tmpl$ = document.createElement("template"); -_tmpl$.innerHTML = ""; +_tmpl$.innerHTML = ``; const view = ({ item }) => function () { @@ -73,7 +73,13 @@ The name of the runtime module to import the methods from. Boolean to indicate whether to enable automatic event delegation on camelCase. ### contextToCustomElements -Boeolean Indicates whether to set current render context on Custom Elements and slots. Useful for seemless Context API with Web Components. +Boolean indicates whether to set current render context on Custom Elements and slots. Useful for seemless Context API with Web Components. + +### alwaysCreateComponents +Always use createComponent method instead of just calling the function. Needed to support class components. + +### builtIns +Array of Component exports from module, that aren't included by default with the library. This plugin will automatically import them if it comes across them in the JSX. ## Special Binding @@ -83,7 +89,7 @@ This binding will assign the variable you pass to it with the DOM element. ### forwardRef -This binding takes a props.ref for Function Components and forwards a Real DOM reference. +This binding takes a function callback and calls it with the ref. Useful for moving refs out Components or doing Custom Bindings. ```jsx const Child = props =>
@@ -100,7 +106,7 @@ These will be treated as event handlers expecting a function. All lowercase are ```jsx ``` This delegation solution works with Web Components and the Shadow DOM as well if the events are composed. That limits the list to custom events and most UA UI events like onClick, onKeyUp, onKeyDown, onDblClick, onInput, onMouseDown, onMouseUp, etc.. @@ -108,15 +114,7 @@ This delegation solution works with Web Components and the Shadow DOM as well if Important: * To allow for casing to work all custom events should follow the all lowercase convention of native events. If you want to use different event convention (or use Level 3 Events "addEventListener") use the events binding. -* Event delegates aren't cleaned up automatically off Document. If you will be completely unmounting the library and wish to remove the handlers from the current page use r.clearDelegatedEvents. - -### $____ - -Custom directives are written with a $ prefix. Their signature is: -```js -function(element, valueAccessor) {} -``` -where valueAccessor is function wrapping the expression. +* Event delegates aren't cleaned up automatically off Document. If you will be completely unmounting the library and wish to remove the handlers from the current page use `clearDelegatedEvents`. ### classList @@ -158,90 +156,9 @@ const MyComp = props => ( Components may have children. This is available as props.children. It will either be the value of a single expression child or it will be a DOM node(Fragment in case of multi-children) which can be rendered. Children are always evaluated lazily upon access (like dynamic properties). -## Control Flow - -Loops and conditionals are handled by a special JSX tag `<$>`. The reason to use a tag instead of just data.map comes from the fact that it isn't just a map function in fine grain. It requires creating nested contexts and memoizing values. Even with custom methods the injection can never be as optimized as giving a special helper and I found I was re-writing pretty much identical code in all implementations. Currently there is support for 6 props on this component 'each', 'when', 'switch', 'provide', 'suspend', and 'portal' where the argument is the list to iterate or the condition. The Child is a function (render prop). For each it passes the item and the index to the function, and for when it passes the evaluated value. - -```jsx - -``` -Often for when there is no need for the argument and it can be skipped if desired using direct descendants. Since this is parsed at compile time there is no concern about the inner code running if the outer condition is not met. - -```jsx -<$ when={ todos.length > 0 }> - {( todos.length )} - - -``` -Sometimes you want mutually exclusive options. Switch will evaluate each child when in succession until it hits the first truthy value. - -```jsx -<$ switch fallback={
Route not Found
}> - <$ when={state.route === 'home'}> - <$ when={state.route === 'profile'}> - <$ when={state.route === 'settings'}> - -``` - -Provide(Experimental) is used with a Context API to allow for hierarchically resolved dependency injection. The value provided will be either passed to an initializer function defined or will be the provided context. Opt in for the specific library runtime. - -```jsx -<$ provide={ ThemeContext } value= { 'dark' }> - - -``` -Suspend(Experimental) works almost the opposite of when where a truthy value will put the child in suspense. It differs from when in that instead of not rendering the child content it attaches it to a foreign document(important to fire connectedCallbacks on Web Components). This is useful if you still want load child components but don't wish to attach them to the current DOM until your are ready and display some fallback content instead. Useful for upstream handling of loading mechanisms when fetching data. It either takes a specific boolean accessor or uses a defined Suspense Context provided by the library. - -```jsx -<$ suspend={ state.loading } fallback={
Loading...
}> - setState({ loading: false })} /> - -``` -Portal(Experimental) renders to a different than the current rendering tree. This is useful for handling modals. By default it will create a div under document.body but the target can be set by passing an argument. To support isolated styles there is an option to useShadow to stick the portal in an isolated ShadowRoot. - -```jsx -<$ portal> - -

Header

-

Lorem ipsum ...

-
- -``` - -Control flow also has some additional options that can be passed as attributes. - -### afterRender -Pass in a function that will be called after each update with the first element and next sibling of the inserted nodes. Useful for postprocessing nodes on mass. Like a batch ref. -Supported by: each, when - -### fallback -If the condition is falsy this fallback content will rendered instead. -Supported by: each, when, suspend - -### useShadow -Uses a Shadow Root for portals. -Supported by: portal - -### value -Sets initialization value for provide control flow. -Supported by: provide - -```jsx -<$ each={ todos } fallback={Loading...}>{ todo => -
{todo.title}
-} -``` +## Fragments -This plugin also supports JSX Fragments with `<>` notation. This is the prefered way to add multi-node roots explicit arrays tend to create more HTML string templates than necessary. +This plugin also supports JSX Fragments with `<>` notation. However they use a Persistent Fragment based on [Document Persistent Fragment](https://github.com/WebReflection/document-persistent-fragment). Look at your specific libraries documentation to learn how to include the polyfill. ## Work in Progress diff --git a/package-lock.json b/package-lock.json index b60603f..b51c411 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "babel-plugin-jsx-dom-expressions", - "version": "0.10.2", + "version": "0.11.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -14,18 +14,18 @@ } }, "@babel/core": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.5.tgz", - "integrity": "sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.0.tgz", + "integrity": "sha512-6Isr4X98pwXqHvtigw71CKgmhL1etZjPs5A67jL/w0TkLM9eqmFR40YrnJvEc1WnMZFsskjsmid8bHZyxKEAnw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.4", - "@babel/helpers": "^7.4.4", - "@babel/parser": "^7.4.5", + "@babel/generator": "^7.5.0", + "@babel/helpers": "^7.5.0", + "@babel/parser": "^7.5.0", "@babel/template": "^7.4.4", - "@babel/traverse": "^7.4.5", - "@babel/types": "^7.4.4", + "@babel/traverse": "^7.5.0", + "@babel/types": "^7.5.0", "convert-source-map": "^1.1.0", "debug": "^4.1.0", "json5": "^2.1.0", @@ -33,6 +33,61 @@ "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.0.tgz", + "integrity": "sha512-1TTVrt7J9rcG5PMjvO7VEG3FrEoEJNHxumRq66GemPmzboLWtIjjcJgk8rokuAS7IiRSpgVSu5Vb9lc99iJkOA==", + "dev": true, + "requires": { + "@babel/types": "^7.5.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/parser": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.0.tgz", + "integrity": "sha512-I5nW8AhGpOXGCCNYGc+p7ExQIBxRFnS2fd/d862bNOKvmoEPjYPcfIjsfdy0ujagYOIYPczKgD9l3FsgTkAzKA==", + "dev": true + }, + "@babel/traverse": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.0.tgz", + "integrity": "sha512-SnA9aLbyOCcnnbQEGwdfBggnc142h/rbqqsXcaATj2hZcegCl903pUD/lfpsNBlBSuWow/YDfRyJuWi2EPR5cg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.5.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.0", + "@babel/types": "^7.5.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "@babel/types": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.0.tgz", + "integrity": "sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", + "dev": true + } } }, "@babel/generator": { @@ -91,14 +146,63 @@ } }, "@babel/helpers": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", - "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.5.1.tgz", + "integrity": "sha512-rVOTDv8sH8kNI72Unenusxw6u+1vEepZgLxeV+jHkhsQlYhzVhzL1EpfoWT7Ub3zpWSv2WV03V853dqsnyoQzA==", "dev": true, "requires": { "@babel/template": "^7.4.4", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/traverse": "^7.5.0", + "@babel/types": "^7.5.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.0.tgz", + "integrity": "sha512-1TTVrt7J9rcG5PMjvO7VEG3FrEoEJNHxumRq66GemPmzboLWtIjjcJgk8rokuAS7IiRSpgVSu5Vb9lc99iJkOA==", + "dev": true, + "requires": { + "@babel/types": "^7.5.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/parser": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.0.tgz", + "integrity": "sha512-I5nW8AhGpOXGCCNYGc+p7ExQIBxRFnS2fd/d862bNOKvmoEPjYPcfIjsfdy0ujagYOIYPczKgD9l3FsgTkAzKA==", + "dev": true + }, + "@babel/traverse": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.0.tgz", + "integrity": "sha512-SnA9aLbyOCcnnbQEGwdfBggnc142h/rbqqsXcaATj2hZcegCl903pUD/lfpsNBlBSuWow/YDfRyJuWi2EPR5cg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.5.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.0", + "@babel/types": "^7.5.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "@babel/types": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.0.tgz", + "integrity": "sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/highlight": { @@ -3340,14 +3444,14 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" }, "lodash.mergewith": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", - "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true }, "lodash.sortby": { @@ -3500,9 +3604,9 @@ "dev": true }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -4186,9 +4290,9 @@ } }, "rollup": { - "version": "1.16.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.16.4.tgz", - "integrity": "sha512-Bht8QXoo2dJc8lUGyEMfnfKCV7hkf1oLzN6L8YdDE2toaaoCe5DxoqYjTyKswWQyiZseViZw9quEdDRz0YXifw==", + "version": "1.16.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.16.6.tgz", + "integrity": "sha512-oM3iKkzPCq9Da95wCnNfS8YlNZjgCD5c/TceKnJIthI9FOeJqnO3PUr/C5Suv9Kjzh0iphKL02PLeja3A5AMIA==", "dev": true, "requires": { "@types/estree": "0.0.39", @@ -4197,9 +4301,9 @@ }, "dependencies": { "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.0.tgz", + "integrity": "sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==", "dev": true } } @@ -4300,9 +4404,9 @@ "dev": true }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -4877,38 +4981,15 @@ } }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, "unset-value": { diff --git a/package.json b/package.json index 5b4a3af..e26af2f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "babel-plugin-jsx-dom-expressions", "description": "A JSX to DOM plugin that wraps expressions for fine grained change detection", - "version": "0.10.2", + "version": "0.11.0", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -18,12 +18,12 @@ "prepublishOnly": "npm run build" }, "devDependencies": { - "@babel/core": "7.4.5", + "@babel/core": "7.5.0", "babel-plugin-tester": "^6.4.0", "coveralls": "3.0.4", "dom-expressions": "~0.10.3", "jest": "^24.8.0", - "rollup": "^1.16.4", + "rollup": "^1.16.6", "rollup-plugin-node-resolve": "^5.2.0" }, "dependencies": { diff --git a/src/index.js b/src/index.js index fef01b4..392ab4c 100644 --- a/src/index.js +++ b/src/index.js @@ -6,8 +6,10 @@ import VoidElements from './VoidElements'; export default (babel) => { const { types: t } = babel; let moduleName = 'dom', - contextToCustomElements = true, - delegateEvents = true; + delegateEvents = true, + builtIns = [], + alwaysCreateComponents = false, + contextToCustomElements = false; function checkParens(jsx, path) { const e = path.hub.file.code.slice(jsx.start+1,jsx.end-1).trim(); @@ -22,7 +24,7 @@ export default (babel) => { } } - function registerTemplate(path, results, isFragment) { + function registerTemplate(path, results) { let decl; if (results.template.length) { const templates = path.scope.getProgramParent().data.templates || (path.scope.getProgramParent().data.templates = []); @@ -36,22 +38,16 @@ export default (babel) => { decl = t.variableDeclarator( results.id, t.callExpression( - isFragment ? t.memberExpression( + t.memberExpression( + t.memberExpression( t.memberExpression(templateId, t.identifier('content')), - t.identifier('cloneNode') - ) : t.memberExpression( - t.memberExpression( - t.memberExpression(templateId, t.identifier('content')), - t.identifier('firstChild') - ), - t.identifier('cloneNode') - ) - , + t.identifier('firstChild') + ), + t.identifier('cloneNode') + ), [t.booleanLiteral(true)] ) ); - } else { - decl = t.variableDeclarator(results.id, t.callExpression(t.memberExpression(t.identifier('document'), t.identifier('createDocumentFragment')), [])); } results.decl.unshift(decl); results.decl = t.variableDeclaration("const", results.decl); @@ -103,23 +99,15 @@ export default (babel) => { )); } - function createPlaceholder(path, results, tempPath, i, nextExprId) { - const exprId = nextExprId || path.scope.generateUidIdentifier("el$"); + function createPlaceholder(path, results, tempPath, i) { + const exprId = path.scope.generateUidIdentifier("el$"); results.template += ``; results.decl.push(t.variableDeclarator(exprId, t.memberExpression(t.identifier(tempPath), t.identifier(i === 0 ? 'firstChild': 'nextSibling')))); return exprId; } - function dynamicSiblings(children, jsxChildren, index) { - if (index + 1 >= children.length) return false; - - // next node expr || boxed by textNodes - if ((children[index + 1].flow || children[index + 1].dynamic) && - ((children[index + 2] && !children[index + 2].id) - || (t.isJSXText(jsxChildren[index]) && t.isJSXText(jsxChildren[index + 2]))) - ) return true; - - return dynamicSiblings(children, jsxChildren, index + 1); + function nextChild(children, index) { + return children[index + 1] && (children[index + 1].id || nextChild(children, index + 1)) } function trimWhitespace(text) { @@ -146,17 +134,35 @@ export default (babel) => { function transformComponentChildren(path, children, opts) { const filteredChildren = filterChildren(children); if (!filteredChildren.length) return; - let child = filteredChildren.length === 1 ? filteredChildren[0] : t.JSXFragment(t.JSXOpeningFragment(), t.JSXClosingFragment(), children); - - child = generateHTMLNode(path, child, opts, {ignoreDynamic: true}); - if (child == null) return; - if (child.id) { - registerTemplate(path, child, filteredChildren.length !== 1); - if (!child.exprs.length && child.decl.declarations.length === 1) - return child.decl.declarations[0].init; - else return t.blockStatement([child.decl, ...child.exprs, t.returnStatement(child.id)]); + let dynamic; + + let transformedChildren = filteredChildren.map(child => { + if (t.isJSXText(child)) { return t.stringLiteral(trimWhitespace(child.value)); } + else { + child = generateHTMLNode(path, child, opts); + if (child.id) { + registerTemplate(path, child); + if (!child.exprs.length && child.decl.declarations.length === 1) + return child.decl.declarations[0].init; + else return t.callExpression(t.arrowFunctionExpression([], t.blockStatement([child.decl, ...child.exprs, t.returnStatement(child.id)])), []); + } + return child.exprs[0]; + } + }); + + if (filteredChildren.length === 1) { + transformedChildren = transformedChildren[0]; + if (t.isJSXExpressionContainer(filteredChildren[0])) + dynamic = checkParens(filteredChildren[0], path); + else { + transformedChildren = t.isCallExpression(transformedChildren) && !transformedChildren.arguments.length ? transformedChildren.callee : t.arrowFunctionExpression([], transformedChildren); + dynamic = true; + } + } else { + transformedChildren = t.arrowFunctionExpression([], t.arrayExpression(transformedChildren)) + dynamic = true; } - return child.exprs[0]; + return [transformedChildren, dynamic] } // reduce unnecessary refs @@ -182,52 +188,18 @@ export default (babel) => { } } - function generateFlow(jsx) { - const flow = {}, - flowOptions = []; - let children = filterChildren(jsx.children), render; - jsx.openingElement.attributes.forEach(attribute => { - const name = attribute.name.name; - if (!flow.type && (name === 'each' || name === 'when' || name === 'suspend' || name === 'portal' || name === 'provide' || name === 'switch')) { - flow.type = name; - flow.condition = attribute.value ? t.arrowFunctionExpression([], attribute.value.expression) : t.nullLiteral(); - } - if (name === 'afterRender' || name === 'useShadow' || name === 'value') - flowOptions.push(t.objectProperty(t.identifier(name), attribute.value.expression)); - if (name === 'fallback') - flowOptions.push(t.objectProperty(t.identifier(name), t.arrowFunctionExpression([], attribute.value.expression))); - }); - flow.options = t.objectExpression(flowOptions); - - if (t.isJSXExpressionContainer(children[0])) render = children[0].expression; - else if (flow.type === 'switch') { - const conditions = []; - children.forEach((child) => { - const {flow: childFlow} = generateFlow(child) - conditions.push(t.objectExpression([ - t.objectProperty(t.identifier('condition'), childFlow.condition), - t.objectProperty(t.identifier('render'), childFlow.render), - t.objectProperty(t.identifier('options'), childFlow.options) - ])); - }); - flow.type = 'switchWhen'; - flow.condition = t.arrayExpression(conditions); - render = t.nullLiteral(); - } else if (children.length > 1) { - children = [t.JSXFragment(t.JSXOpeningFragment(), t.JSXClosingFragment(), jsx.children)]; - } else if (t.isJSXText(children[0])) children[0] = t.stringLiteral(trimWhitespace(children[0].value)); - if (!render) render = t.arrowFunctionExpression([], children[0]); - flow.render = render; - - return { flow, template: '', exprs: [] }; - } - function generateComponent(path, jsx, opts) { let props = [], runningObject = [], exprs, + tagName = getTagName(jsx), dynamicKeys = []; + if (builtIns.indexOf(tagName) > -1) { + registerImportMethod(path, tagName); + tagName = `_$${tagName}`; + } + jsx.openingElement.attributes.forEach(attribute => { if (t.isJSXSpreadAttribute(attribute)) { if (runningObject.length) { @@ -241,9 +213,9 @@ export default (babel) => { props.push( t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('keys')), [attribute.argument]), t.identifier('reduce')), [ t.arrowFunctionExpression([memo, key], - t.assignmentExpression('=', - t.memberExpression(memo, key, true), - t.arrowFunctionExpression([], t.memberExpression(attribute.argument,key, true)) + t.assignmentExpression('=', + t.memberExpression(memo, key, true), + t.arrowFunctionExpression([], t.memberExpression(attribute.argument,key, true)) )), t.objectExpression([]) ]) @@ -259,7 +231,7 @@ export default (babel) => { )); } else if (attribute.name.name === 'forwardRef') { runningObject.push(t.objectProperty(t.identifier('ref'), value.expression)); - } else if (checkParens(value, path)) { + } else if (!t.isFunction(value.expression) && checkParens(value, path)) { dynamicKeys.push(t.stringLiteral(attribute.name.name)); runningObject.push(t.objectProperty(t.identifier(attribute.name.name), t.arrowFunctionExpression([], value.expression))); } else runningObject.push(t.objectProperty(t.identifier(attribute.name.name), value.expression)); @@ -269,32 +241,23 @@ export default (babel) => { }); const childResult = transformComponentChildren(path, jsx.children, opts); - if (childResult) { - if (t.isFunction(childResult)) - runningObject.push(t.objectProperty(t.identifier("children"), childResult)); - else { - dynamicKeys.push(t.stringLiteral('children')); - runningObject.push(t.objectProperty(t.identifier("children"), t.arrowFunctionExpression([], childResult))); - } + if (childResult && childResult[0]) { + childResult[1] && dynamicKeys.push(t.stringLiteral('children')); + runningObject.push(t.objectProperty(t.identifier("children"), childResult[0])); } props.push(t.objectExpression(runningObject)); if (props.length > 1) props = [t.callExpression(t.memberExpression(t.identifier("Object"), t.identifier("assign")), props)]; - if (dynamicKeys.length) { + if (alwaysCreateComponents || dynamicKeys.length) { registerImportMethod(path, 'createComponent'); exprs = [t.callExpression(t.identifier("_$createComponent"), [ - t.identifier(getTagName(jsx)), props[0], t.arrayExpression(dynamicKeys) + t.identifier(tagName), props[0], t.arrayExpression(dynamicKeys) ])]; - } else exprs = [t.callExpression(t.identifier(getTagName(jsx)), props)]; - - // check against static component list - let dynamic = true; - const binding = path.scope.getBinding(getTagName(jsx)), - components = path.scope.getProgramParent().data.components; - if (binding && components && components.has(binding.path)) dynamic = false; - return { exprs, template: '', dynamic } + } else exprs = [t.callExpression(t.identifier(tagName), props)]; + + return { exprs, template: '', component: true } } function transformAttributes(path, jsx, results) { @@ -329,8 +292,6 @@ export default (babel) => { value.expression.properties.forEach(prop => results.exprs.push(t.expressionStatement(t.callExpression(t.memberExpression(elem, t.identifier('addEventListener')), [t.stringLiteral(prop.key.name || prop.key.value), prop.value]))) ); - } else if (key.startsWith('$')) { - results.exprs.unshift(t.expressionStatement(t.callExpression(t.identifier(key.slice(1)), [elem, t.arrowFunctionExpression([], value.expression)]))); } else if (!value || checkParens(value, path)) { results.exprs.push(setAttrExpr(path, elem, key, value.expression)); } else { @@ -338,15 +299,14 @@ export default (babel) => { } } else { results.template += ` ${key}`; - if (value) results.template += `='${value.value}'`; + if (value) results.template += `="${value.value}"`; } }); } function transformChildren(path, jsx, opts, results) { let tempPath = results.id && results.id.name, - i = 0, - nextExprId; + i = 0; const jsxChildren = filterChildren(jsx.children, true), children = jsxChildren.map( (jsxChild, index) => generateHTMLNode(path, jsxChild, opts, {skipId: !results.id || !detectExpressions(jsxChildren, index)}) @@ -365,53 +325,45 @@ export default (babel) => { i++; } else if (child.exprs.length) { registerImportMethod(path, 'insert'); - // jsx parent and only child || next node expr || boxed by textNodes - if ((child.dynamic && children.length === 1 && t.isJSXFragment(jsx)) - || (child.dynamic && children[index + 1] && !children[index + 1].id) - || (t.isJSXText(jsxChildren[index - 1]) && t.isJSXText(jsxChildren[index + 1])) - ) { - let exprId = createPlaceholder(path, results, tempPath, i, nextExprId); + // boxed by textNodes + if (t.isJSXText(jsxChildren[index - 1]) && t.isJSXText(jsxChildren[index + 1])) { + let exprId = createPlaceholder(path, results, tempPath, i); results.exprs.push(t.expressionStatement(t.callExpression(t.identifier("_$insert"), [results.id, child.exprs[0], exprId]))); tempPath = exprId.name; - nextExprId = null; i++; } else if (checkLength(jsxChildren)) { results.exprs.push(t.expressionStatement(t.callExpression(t.identifier("_$insert"), [ results.id, child.exprs[0], - (children[index + 1] && children[index + 1].id) - || dynamicSiblings(children, jsxChildren, index) && (nextExprId || (nextExprId = path.scope.generateUidIdentifier("el$"))) - || t.nullLiteral() + nextChild(children, index) || t.nullLiteral() ]))); } else results.exprs.push(t.expressionStatement(t.callExpression(t.identifier("_$insert"), [results.id, child.exprs[0]]))); - } else if (child.flow) { - registerImportMethod(path, child.flow.type); - // jsx parent and only child || next node expr || boxed by textNodes - if ((children.length === 1 && t.isJSXFragment(jsx)) - || (children[index + 1] && !children[index + 1].id) - || (t.isJSXText(jsxChildren[index - 1]) && t.isJSXText(jsxChildren[index + 1])) - ) { - let exprId = createPlaceholder(path, results, tempPath, i, nextExprId); - results.exprs.push(t.expressionStatement(t.callExpression(t.identifier("_$"+child.flow.type), [results.id, child.flow.condition, child.flow.render, child.flow.options, exprId]))); - tempPath = exprId.name; - nextExprId = null; - i++; - } else if (checkLength(jsxChildren)) { - results.exprs.push(t.expressionStatement(t.callExpression(t.identifier("_$"+child.flow.type), [ - results.id, child.flow.condition, child.flow.render, child.flow.options, - (children[index + 1] && children[index + 1].id) - || dynamicSiblings(children, jsxChildren, index) && (nextExprId || (nextExprId = path.scope.generateUidIdentifier("el$"))) - || t.nullLiteral() - ]))); - } else results.exprs.push(t.expressionStatement(t.callExpression(t.identifier("_$"+child.flow.type), [results.id, child.flow.condition, child.flow.render, child.flow.options]))); } }); } + function transformFragmentChildren(path, jsx, opts, results) { + const jsxChildren = filterChildren(jsx.children, true), + children = jsxChildren.map(child => { + if (t.isJSXText(child)) { return t.stringLiteral(trimWhitespace(child.value)); } + else { + child = generateHTMLNode(path, child, opts); + if (child.id) { + registerTemplate(path, child); + if (!child.exprs.length && child.decl.declarations.length === 1) + return child.decl.declarations[0].init; + else return t.callExpression(t.arrowFunctionExpression([], t.blockStatement([child.decl, ...child.exprs, t.returnStatement(child.id)])), []); + } + return child.exprs[0]; + } + }); + if (children.length === 1) { results.exprs.push(children[0]) } + else results.exprs.push(t.arrayExpression(children)); + } + function generateHTMLNode(path, jsx, opts, info = {}) { if (t.isJSXElement(jsx)) { let tagName = getTagName(jsx), voidTag = VoidElements.indexOf(tagName) > -1; - if (tagName === '$') return generateFlow(jsx); if (tagName !== tagName.toLowerCase()) return generateComponent(path, jsx, opts); let results = { template: `<${tagName}`, decl: [], exprs: [] }; if (!info.skipId) results.id = path.scope.generateUidIdentifier("el$"); @@ -424,16 +376,15 @@ export default (babel) => { t.callExpression(t.identifier('_$currentContext'), []) ))); } + results.template += '>'; if (!voidTag) { - results.template += '>'; transformChildren(path, jsx, opts, results); results.template += ``; - } else results.template += '/>'; + } return results; } else if (t.isJSXFragment(jsx)) { let results = { template: '', decl: [], exprs: [] }; - if (!info.skipId) results.id = path.scope.generateUidIdentifier("el$"); - transformChildren(path, jsx, opts, results); + transformFragmentChildren(path, jsx, opts, results); return results; } else if (t.isJSXText(jsx)) { const text = trimWhitespace(jsx.value); @@ -443,8 +394,8 @@ export default (babel) => { return results; } else if (t.isJSXExpressionContainer(jsx)) { if (t.isJSXEmptyExpression(jsx.expression)) return null; - if (info.ignoreDynamic || !checkParens(jsx, path)) return { exprs: [jsx.expression], template: '' } - return { exprs: [t.arrowFunctionExpression([], jsx.expression)], template: '', dynamic: true } + if (!checkParens(jsx, path)) return { exprs: [jsx.expression], template: '' } + return { exprs: [t.arrowFunctionExpression([], jsx.expression)], template: '' } } } @@ -456,26 +407,9 @@ export default (babel) => { if ('moduleName' in opts) moduleName = opts.moduleName; if ('delegateEvents' in opts) delegateEvents = opts.delegateEvents; if ('contextToCustomElements' in opts) contextToCustomElements = opts.contextToCustomElements; + if ('alwaysCreateComponents' in opts) alwaysCreateComponents = opts.alwaysCreateComponents; + if ('builtIns' in opts) builtIns = opts.builtIns; const result = generateHTMLNode(path, path.node, opts); - if (result.flow) { - const id = path.scope.generateUidIdentifier("el$"), - markerId = path.scope.generateUidIdentifier("el$"); - registerImportMethod(path, result.flow.type); - path.replaceWithMultiple([ - t.variableDeclaration("const", [ - t.variableDeclarator(id, t.callExpression(t.memberExpression(t.identifier('document'), t.identifier('createDocumentFragment')), [])), - t.variableDeclarator(markerId, - t.callExpression(t.memberExpression(id, t.identifier('insertBefore')), [ - t.callExpression(t.memberExpression(t.identifier('document'), t.identifier('createTextNode')), [t.stringLiteral('')]), - t.memberExpression(id, t.identifier('firstChild')) - ]) - ) - ]), - t.expressionStatement(t.callExpression(t.identifier("_$"+result.flow.type), [id, result.flow.condition, result.flow.render, result.flow.options, markerId])), - t.expressionStatement(id) - ]) - return; - } if (result.id) { registerTemplate(path, result); if (!result.exprs.length && result.decl.declarations.length === 1) @@ -487,39 +421,12 @@ export default (babel) => { if ('moduleName' in opts) moduleName = opts.moduleName; if ('delegateEvents' in opts) delegateEvents = opts.delegateEvents; if ('contextToCustomElements' in opts) contextToCustomElements = opts.contextToCustomElements; + if ('alwaysCreateComponents' in opts) alwaysCreateComponents = opts.alwaysCreateComponents; + if ('builtIns' in opts) builtIns = opts.builtIns; const result = generateHTMLNode(path, path.node, opts); - registerTemplate(path, result, true); - if (!result.exprs.length && result.decl.declarations.length === 1) - path.replaceWith(result.decl.declarations[0].init) - else path.replaceWithMultiple([result.decl].concat(result.exprs, t.expressionStatement(result.id))); + path.replaceWith(result.exprs[0]); }, Program: { - enter: (path) => { - path.traverse({ - JSXElement: (path) => { - if (t.isReturnStatement(path.parent) || t.isArrowFunctionExpression(path.parent)) { - let component = path.scope.getFunctionParent().path; - if (!component.node.id) component = component.parentPath; - const id = component.node.id; - if (id && id.name !== id.name.toLowerCase()) { - path.scope.getProgramParent().data.components || (path.scope.getProgramParent().data.components = new Set()); - path.scope.getProgramParent().data.components.add(component); - } - } - }, - JSXFragment: (path) => { - if (t.isReturnStatement(path.parent) || t.isArrowFunctionExpression(path.parent)) { - let component = path.scope.getFunctionParent().path; - if (!component.node.id) component = component.parentPath; - const id = component.node.id; - if (id && id.name !== id.name.toLowerCase()) { - path.scope.getProgramParent().data.components || (path.scope.getProgramParent().data.components = new Set()); - path.scope.getProgramParent().data.components.add(component); - } - } - }, - }); - }, exit: (path) => { if (path.scope.data.events) { registerImportMethod(path, 'delegateEvents'); @@ -531,10 +438,13 @@ export default (babel) => { ); } if (path.scope.data.templates) { - registerImportMethod(path, 'template'); - const declarators = path.scope.data.templates.map(template => - t.variableDeclarator(template.id, t.callExpression(t.identifier('_$template'), [t.stringLiteral(template.template)])) - ) + const declarators = path.scope.data.templates.map(template => { + const tmpl = {cooked: template.template, raw: template.template}; + registerImportMethod(path, 'template'); + return t.variableDeclarator(template.id, t.callExpression(t.identifier('_$template'), [ + t.templateLiteral([t.templateElement(tmpl, true)], []) + ])); + }); path.node.body.unshift(t.variableDeclaration("const", declarators)); } } diff --git a/test/__fixtures__/attributeExpressions/code.js b/test/__fixtures__/attributeExpressions/code.js index 0f9e49a..5a21411 100644 --- a/test/__fixtures__/attributeExpressions/code.js +++ b/test/__fixtures__/attributeExpressions/code.js @@ -2,12 +2,11 @@ const welcoming = 'Welcome'; const selected = true; const color = 'red'; const props = {some: 'stuff', no: 'thing'} -const binding = (el, accessor) => el.custom = accessor(); let link; const template = ( -
+ "); +const _tmpl$ = _$template(``); const welcoming = 'Welcome'; const selected = true; @@ -12,9 +12,6 @@ const props = { some: 'stuff', no: 'thing' }; - -const binding = (el, accessor) => el.custom = accessor(); - let link; const template = function () { @@ -22,8 +19,6 @@ const template = function () { _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild; - custom(_el$, () => binding); - _$classList(_el$, { selected: selected }); diff --git a/test/__fixtures__/subTemplates/code.js b/test/__fixtures__/components/code.js similarity index 85% rename from test/__fixtures__/subTemplates/code.js rename to test/__fixtures__/components/code.js index 7ebac86..9b13ec5 100644 --- a/test/__fixtures__/subTemplates/code.js +++ b/test/__fixtures__/components/code.js @@ -20,9 +20,9 @@ const template = props => { {/* Comment Node */}
{state.content}
- { context => + { context => context - } + }
); } @@ -47,4 +47,9 @@ const template4 = ( const template5 = ( {( dynamicValue )} +) + +// builtIns +const template6 = ( + { item => item } ) \ No newline at end of file diff --git a/test/__fixtures__/components/output.js b/test/__fixtures__/components/output.js new file mode 100644 index 0000000..10257bb --- /dev/null +++ b/test/__fixtures__/components/output.js @@ -0,0 +1,89 @@ +import { template as _$template } from "r-dom"; +import { For as _$For } from "r-dom"; +import { createComponent as _$createComponent } from "r-dom"; +import { insert as _$insert } from "r-dom"; + +const _tmpl$ = _$template(`
Hello
`), + _tmpl$2 = _$template(`
`), + _tmpl$3 = _$template(`
From Parent
`); + +const Child = props => [(() => { + const _el$ = _tmpl$.content.firstChild.cloneNode(true), + _el$2 = _el$.firstChild; + + props.ref && props.ref(_el$); + + _$insert(_el$, props.name, null); + + return _el$; +})(), (() => { + const _el$3 = _tmpl$2.content.firstChild.cloneNode(true); + + _$insert(_el$3, props.children); + + return _el$3; +})()]; + +const Consumer = props => props.children(); + +const someProps = { + some: 'stuff', + more: 'things' +}; + +const template = props => { + let childRef; + return function () { + const _el$4 = _tmpl$2.content.firstChild.cloneNode(true); + + _$insert(_el$4, _$createComponent(Child, Object.assign({ + name: 'John' + }, props, { + ref: r$ => childRef = r$, + children: () => _tmpl$3.content.firstChild.cloneNode(true) + }), ["children"]), null); + + _$insert(_el$4, _$createComponent(Child, Object.assign({ + name: 'Jason' + }, Object.keys(props).reduce((m$, k$) => m$[k$] = () => props[k$], {}), { + ref: props.ref, + children: () => { + const _el$6 = _tmpl$2.content.firstChild.cloneNode(true); + + _$insert(_el$6, state.content); + + return _el$6; + } + }), [...Object.keys(props), "children"]), null); + + _$insert(_el$4, Context.Consumer({ + children: context => context + }), null); + + return _el$4; + }(); +}; + +const template2 = _$createComponent(Child, { + name: 'Jake', + dynamic: () => state.data, + handleClick: clickHandler +}, ["dynamic"]); + +const template3 = _$createComponent(Child, { + children: () => [_tmpl$2.content.firstChild.cloneNode(true), _tmpl$2.content.firstChild.cloneNode(true), _tmpl$2.content.firstChild.cloneNode(true)] +}, ["children"]); + +const template4 = Child({ + children: () => _tmpl$2.content.firstChild.cloneNode(true) +}); + +const template5 = _$createComponent(Child, { + children: () => dynamicValue +}, ["children"]); // builtIns + + +const template6 = _$createComponent(_$For, { + each: () => list, + children: item => item +}, ["each"]); \ No newline at end of file diff --git a/test/__fixtures__/customElements/output.js b/test/__fixtures__/customElements/output.js index 15c08e4..ff887a8 100644 --- a/test/__fixtures__/customElements/output.js +++ b/test/__fixtures__/customElements/output.js @@ -2,9 +2,9 @@ import { template as _$template } from "r-dom"; import { wrap as _$wrap } from "r-dom"; import { currentContext as _$currentContext } from "r-dom"; -const _tmpl$ = _$template(""), - _tmpl$2 = _$template("
Title
"), - _tmpl$3 = _$template(""); +const _tmpl$ = _$template(``), + _tmpl$2 = _$template(`
Title
`), + _tmpl$3 = _$template(``); const template = function () { const _el$ = _tmpl$.content.firstChild.cloneNode(true); @@ -34,10 +34,9 @@ const template3 = function () { return _el$3; }(); -const template4 = function () { - const _el$4 = _tmpl$3.content.cloneNode(true), - _el$5 = _el$4.firstChild; +const template4 = (() => { + const _el$4 = _tmpl$3.content.firstChild.cloneNode(true); - _el$5._context = _$currentContext(); + _el$4._context = _$currentContext(); return _el$4; -}(); \ No newline at end of file +})(); \ No newline at end of file diff --git a/test/__fixtures__/eventExpressions/output.js b/test/__fixtures__/eventExpressions/output.js index d1b2ec0..f8ff6b5 100644 --- a/test/__fixtures__/eventExpressions/output.js +++ b/test/__fixtures__/eventExpressions/output.js @@ -1,7 +1,7 @@ import { template as _$template } from "r-dom"; import { delegateEvents as _$delegateEvents } from "r-dom"; -const _tmpl$ = _$template("
"); +const _tmpl$ = _$template(`
`); const template = function () { const _el$ = _tmpl$.content.firstChild.cloneNode(true), diff --git a/test/__fixtures__/flow/code.js b/test/__fixtures__/flow/code.js deleted file mode 100644 index dc089d0..0000000 --- a/test/__fixtures__/flow/code.js +++ /dev/null @@ -1,70 +0,0 @@ -const list = [ - {id: 1, text: 'Shop for Groceries', completed: true}, - {id: 2, text: 'Go to Work', completed: false} -]; - -const state = { loading: true }; - -let editingId = 1; - -const template = ( - <$ each={list} afterRender={selectWhen(() => editingId, 'editing')}>{ item => - <> -
{( item.text )}
-
- <$ when={item.completed} fallback={
Do it!
}> - Hurray! - -
- <$ when={editingId === item.id}> - Editing: - - - - } -); - -const template2 = ( - <$ suspend={state.loading} fallback={
Loading...
}> -
{state.asyncContent}
- <$ suspend fallback={
Loading...
}> - - - -); - -const template3 = ( - <$ portal useShadow={true}> - -
In a Portal
- -); - -const template4 = ( - <$ provide={ThemeContext} value={'dark'}> - - -) - -const template5 = ( - <$ switch fallback={
Route not Found
}> - <$ when={state.route === 'home'}> - <$ when={state.route === 'profile'}> - <$ when={state.route === 'settings'} afterRender={node => node && node.focus()}> - -) - -const StaticChild = () =>
-const template6 = ( -
- - <$ when={condition}>

Content

-
-) - -const template7 = ( -
- <$ when={condition1}>

Content1

- <$ when={condition2}>

Content2

-
-) \ No newline at end of file diff --git a/test/__fixtures__/flow/output.js b/test/__fixtures__/flow/output.js deleted file mode 100644 index aee2356..0000000 --- a/test/__fixtures__/flow/output.js +++ /dev/null @@ -1,158 +0,0 @@ -import { template as _$template } from "r-dom"; -import { switchWhen as _$switchWhen } from "r-dom"; -import { provide as _$provide } from "r-dom"; -import { portal as _$portal } from "r-dom"; -import { suspend as _$suspend } from "r-dom"; -import { when as _$when } from "r-dom"; -import { insert as _$insert } from "r-dom"; -import { each as _$each } from "r-dom"; - -const _tmpl$ = _$template("
"), - _tmpl$2 = _$template("
Do it!
"), - _tmpl$3 = _$template("Editing: "), - _tmpl$4 = _$template("
"), - _tmpl$5 = _$template("
Loading...
"), - _tmpl$6 = _$template("
In a Portal
"), - _tmpl$7 = _$template("
Route not Found
"), - _tmpl$8 = _$template("

Content

"), - _tmpl$9 = _$template("
"), - _tmpl$10 = _$template("

Content1

"), - _tmpl$11 = _$template("

Content2

"); - -const list = [{ - id: 1, - text: 'Shop for Groceries', - completed: true -}, { - id: 2, - text: 'Go to Work', - completed: false -}]; -const state = { - loading: true -}; -let editingId = 1; - -const template = function () { - const _el$ = document.createDocumentFragment(), - _el$2 = _el$.insertBefore(document.createTextNode(""), _el$.firstChild); - - _$each(_el$, () => list, item => function () { - const _el$3 = _tmpl$.content.cloneNode(true), - _el$4 = _el$3.firstChild, - _el$5 = _el$4.nextSibling; - - _$insert(_el$4, () => item.text); - - _$when(_el$5, () => item.completed, () => "Hurray!", { - fallback: () => _tmpl$2.content.firstChild.cloneNode(true) - }); - - _$when(_el$3, () => editingId === item.id, () => _tmpl$3.content.cloneNode(true), {}, null); - - return _el$3; - }(), { - afterRender: selectWhen(() => editingId, 'editing') - }, _el$2); - - return _el$; -}(); - -const template2 = function () { - const _el$8 = document.createDocumentFragment(), - _el$9 = _el$8.insertBefore(document.createTextNode(""), _el$8.firstChild); - - _$suspend(_el$8, () => state.loading, () => function () { - const _el$10 = _tmpl$4.content.cloneNode(true), - _el$11 = _el$10.firstChild; - - _$insert(_el$11, state.asyncContent); - - _$suspend(_el$10, null, () => AsyncChild({}), { - fallback: () => _tmpl$5.content.firstChild.cloneNode(true) - }, null); - - return _el$10; - }(), { - fallback: () => _tmpl$5.content.firstChild.cloneNode(true) - }, _el$9); - - return _el$8; -}(); - -const template3 = function () { - const _el$14 = document.createDocumentFragment(), - _el$15 = _el$14.insertBefore(document.createTextNode(""), _el$14.firstChild); - - _$portal(_el$14, null, () => function () { - const _el$16 = _tmpl$6.content.cloneNode(true), - _el$17 = _el$16.firstChild; - - _$insert(_el$17, '.isolated { color: red; }'); - - return _el$16; - }(), { - useShadow: true - }, _el$15); - - return _el$14; -}(); - -const template4 = function () { - const _el$18 = document.createDocumentFragment(), - _el$19 = _el$18.insertBefore(document.createTextNode(""), _el$18.firstChild); - - _$provide(_el$18, () => ThemeContext, () => Child({}), { - value: 'dark' - }, _el$19); - - return _el$18; -}(); - -const template5 = function () { - const _el$20 = document.createDocumentFragment(), - _el$21 = _el$20.insertBefore(document.createTextNode(""), _el$20.firstChild); - - _$switchWhen(_el$20, [{ - condition: () => state.route === 'home', - render: () => Home({}), - options: {} - }, { - condition: () => state.route === 'profile', - render: () => Profile({}), - options: {} - }, { - condition: () => state.route === 'settings', - render: () => Settings({}), - options: { - afterRender: node => node && node.focus() - } - }], null, { - fallback: () => _tmpl$7.content.firstChild.cloneNode(true) - }, _el$21); - - return _el$20; -}(); - -const StaticChild = () => _tmpl$4.content.firstChild.cloneNode(true); - -const template6 = function () { - const _el$24 = _tmpl$4.content.firstChild.cloneNode(true); - - _$insert(_el$24, StaticChild({}), null); - - _$when(_el$24, () => condition, () => _tmpl$8.content.firstChild.cloneNode(true), {}, null); - - return _el$24; -}(); - -const template7 = function () { - const _el$26 = _tmpl$9.content.firstChild.cloneNode(true), - _el$27 = _el$26.firstChild; - - _$when(_el$26, () => condition1, () => _tmpl$10.content.firstChild.cloneNode(true), {}, _el$27); - - _$when(_el$26, () => condition2, () => _tmpl$11.content.firstChild.cloneNode(true), {}, null); - - return _el$26; -}(); \ No newline at end of file diff --git a/test/__fixtures__/fragments/output.js b/test/__fixtures__/fragments/output.js index 671a510..1c1af46 100644 --- a/test/__fixtures__/fragments/output.js +++ b/test/__fixtures__/fragments/output.js @@ -1,94 +1,23 @@ import { template as _$template } from "r-dom"; -import { insert as _$insert } from "r-dom"; -const _tmpl$ = _$template(""), - _tmpl$2 = _$template("
First
Last
"), - _tmpl$3 = _$template(""), - _tmpl$4 = _$template("
"); +const _tmpl$ = _$template(``), + _tmpl$2 = _$template(`
First
`), + _tmpl$3 = _$template(`
Last
`), + _tmpl$4 = _$template(`
`); const inserted = 'middle'; const Component = () => _tmpl$.content.firstChild.cloneNode(true); -const multiStatic = _tmpl$2.content.cloneNode(true); +const multiStatic = [_tmpl$2.content.firstChild.cloneNode(true), _tmpl$3.content.firstChild.cloneNode(true)]; +const multiExpression = [_tmpl$2.content.firstChild.cloneNode(true), inserted, _tmpl$3.content.firstChild.cloneNode(true)]; +const singleExpression = inserted; -const multiExpression = function () { - const _el$3 = _tmpl$2.content.cloneNode(true), - _el$4 = _el$3.firstChild, - _el$5 = _el$4.nextSibling; +const singleDynamic = () => inserted; - _$insert(_el$3, inserted, _el$5); - - return _el$3; -}(); - -const singleExpression = function () { - const _el$6 = document.createDocumentFragment(); - - _$insert(_el$6, inserted); - - return _el$6; -}(); - -const singleDynamic = function () { - const _el$7 = _tmpl$3.content.cloneNode(true), - _el$8 = _el$7.firstChild; - - _$insert(_el$7, () => inserted, _el$8); - - return _el$7; -}(); - -const firstStatic = function () { - const _el$9 = _tmpl$4.content.cloneNode(true), - _el$10 = _el$9.firstChild; - - _$insert(_el$9, inserted, _el$10); - - return _el$9; -}(); - -const firstDynamic = function () { - const _el$11 = _tmpl$4.content.cloneNode(true), - _el$12 = _el$11.firstChild; - - _$insert(_el$11, () => inserted, _el$12); - - return _el$11; -}(); - -const firstComponent = function () { - const _el$13 = _tmpl$4.content.cloneNode(true), - _el$14 = _el$13.firstChild; - - _$insert(_el$13, Component({}), _el$14); - - return _el$13; -}(); - -const lastStatic = function () { - const _el$15 = _tmpl$4.content.cloneNode(true), - _el$16 = _el$15.firstChild; - - _$insert(_el$15, inserted, null); - - return _el$15; -}(); - -const lastDynamic = function () { - const _el$17 = _tmpl$4.content.cloneNode(true), - _el$18 = _el$17.firstChild; - - _$insert(_el$17, () => inserted, null); - - return _el$17; -}(); - -const lastComponent = function () { - const _el$19 = _tmpl$4.content.cloneNode(true), - _el$20 = _el$19.firstChild; - - _$insert(_el$19, Component({}), null); - - return _el$19; -}(); \ No newline at end of file +const firstStatic = [inserted, _tmpl$4.content.firstChild.cloneNode(true)]; +const firstDynamic = [() => inserted, _tmpl$4.content.firstChild.cloneNode(true)]; +const firstComponent = [Component({}), _tmpl$4.content.firstChild.cloneNode(true)]; +const lastStatic = [_tmpl$4.content.firstChild.cloneNode(true), inserted]; +const lastDynamic = [_tmpl$4.content.firstChild.cloneNode(true), () => inserted]; +const lastComponent = [_tmpl$4.content.firstChild.cloneNode(true), Component({})]; \ No newline at end of file diff --git a/test/__fixtures__/simpleElements/output.js b/test/__fixtures__/simpleElements/output.js index cea4229..60286b9 100644 --- a/test/__fixtures__/simpleElements/output.js +++ b/test/__fixtures__/simpleElements/output.js @@ -1,6 +1,6 @@ import { template as _$template } from "r-dom"; -const _tmpl$ = _$template("

Welcome

"); +const _tmpl$ = _$template(`

Welcome

`); const template = function () { const _el$ = _tmpl$.content.firstChild.cloneNode(true), diff --git a/test/__fixtures__/subTemplates/output.js b/test/__fixtures__/subTemplates/output.js deleted file mode 100644 index ad4bef2..0000000 --- a/test/__fixtures__/subTemplates/output.js +++ /dev/null @@ -1,81 +0,0 @@ -import { template as _$template } from "r-dom"; -import { createComponent as _$createComponent } from "r-dom"; -import { insert as _$insert } from "r-dom"; - -const _tmpl$ = _$template("
Hello
"), - _tmpl$2 = _$template("
From Parent
"), - _tmpl$3 = _$template("
"), - _tmpl$4 = _$template("
"); - -const Child = props => function () { - const _el$ = _tmpl$.content.cloneNode(true), - _el$2 = _el$.firstChild, - _el$3 = _el$2.firstChild, - _el$4 = _el$2.nextSibling; - - props.ref && props.ref(_el$2); - - _$insert(_el$2, props.name, null); - - _$insert(_el$4, props.children); - - return _el$; -}(); - -const Consumer = props => props.children(); - -const someProps = { - some: 'stuff', - more: 'things' -}; - -const template = props => { - let childRef; - return function () { - const _el$5 = _tmpl$3.content.firstChild.cloneNode(true); - - _$insert(_el$5, _$createComponent(Child, Object.assign({ - name: 'John' - }, props, { - ref: r$ => childRef = r$, - children: () => _tmpl$2.content.firstChild.cloneNode(true) - }), ["children"]), null); - - _$insert(_el$5, _$createComponent(Child, Object.assign({ - name: 'Jason' - }, Object.keys(props).reduce((m$, k$) => m$[k$] = () => props[k$], {}), { - ref: props.ref, - children: () => { - const _el$7 = _tmpl$3.content.firstChild.cloneNode(true); - - _$insert(_el$7, state.content); - - return _el$7; - } - }), [...Object.keys(props), "children"]), null); - - _$insert(_el$5, Consumer({ - children: context => context - }), null); - - return _el$5; - }(); -}; - -const template2 = _$createComponent(Child, { - name: 'Jake', - dynamic: () => state.data, - handleClick: clickHandler -}, ["dynamic"]); - -const template3 = _$createComponent(Child, { - children: () => _tmpl$4.content.cloneNode(true) -}, ["children"]); - -const template4 = Child({ - children: () => _tmpl$3.content.firstChild.cloneNode(true) -}); - -const template5 = _$createComponent(Child, { - children: () => dynamicValue -}, ["children"]); \ No newline at end of file diff --git a/test/__fixtures__/textInterpolation/output.js b/test/__fixtures__/textInterpolation/output.js index fd976c5..77c8ec4 100644 --- a/test/__fixtures__/textInterpolation/output.js +++ b/test/__fixtures__/textInterpolation/output.js @@ -1,12 +1,12 @@ import { template as _$template } from "r-dom"; import { insert as _$insert } from "r-dom"; -const _tmpl$ = _$template("Hello "), - _tmpl$2 = _$template(" John"), - _tmpl$3 = _$template("Hello John"), - _tmpl$4 = _$template(" "), - _tmpl$5 = _$template(" "), - _tmpl$6 = _$template("Hello"); +const _tmpl$ = _$template(`Hello `), + _tmpl$2 = _$template(` John`), + _tmpl$3 = _$template(`Hello John`), + _tmpl$4 = _$template(` `), + _tmpl$5 = _$template(` `), + _tmpl$6 = _$template(`Hello`); const name = 'Jake', greeting = 'Welcome'; diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 760d532..dd0e016 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -6,8 +6,10 @@ pluginTester({ plugin, pluginOptions: { moduleName: 'r-dom', + builtIns: ['For'], delegateEvents: true, - contextToCustomElements: true, + alwaysCreateComponents: false, + contextToCustomElements: true }, title: 'Convert JSX', fixtures: path.join(__dirname, '__fixtures__'), diff --git a/types/index.d.ts b/types/index.d.ts index 89741a0..abf8c49 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -6,25 +6,6 @@ declare global { * @see https://github.com/ryansolid/babel-plugin-jsx-dom-expressions */ - // This may interfere with JQuery but no real solutions here - // Open issue with TypeScript: https://github.com/microsoft/TypeScript/issues/31606 - const $: (attr: { - children?: {} - each?: unknown[] - when?: boolean - switch?: void | boolean - provide?: { - id: symbol - initFn: () => any - } - suspend?: boolean - portal?: Node - fallback?: unknown - useShadow?: boolean - value?: any - afterRender?: (start: Node | null, end?: Node | null) => void - }) => any - namespace JSX { interface Element extends HTMLElement {}