From 30243482be917e89515d057e2368e7278e34696c Mon Sep 17 00:00:00 2001 From: Anton Korzunov Date: Mon, 2 Dec 2019 21:37:38 +1100 Subject: [PATCH] fix: fix isReady problem (#445) Closes #400 --- .../src/__snapshots__/index.test.js.snap | 378 ++++++++++++++++-- packages/babel-plugin/src/index.js | 4 + .../src/properties/importAsync.js | 13 + .../babel-plugin/src/properties/isReady.js | 7 +- .../src/properties/requireAsync.js | 26 +- packages/babel-plugin/src/properties/state.js | 8 + packages/component/.size-snapshot.json | 10 +- 7 files changed, 394 insertions(+), 52 deletions(-) create mode 100644 packages/babel-plugin/src/properties/importAsync.js create mode 100644 packages/babel-plugin/src/properties/state.js diff --git a/packages/babel-plugin/src/__snapshots__/index.test.js.snap b/packages/babel-plugin/src/__snapshots__/index.test.js.snap index d094b5ed..21b5aa42 100644 --- a/packages/babel-plugin/src/__snapshots__/index.test.js.snap +++ b/packages/babel-plugin/src/__snapshots__/index.test.js.snap @@ -4,22 +4,39 @@ exports[`plugin Magic comment should remove only needed comments 1`] = ` "const load = /* IMPORTANT! */ { + resolved: {}, + chunkName() { return \\"moment\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => import( + importAsync: () => import( /* webpackChunkName: \\"moment\\" */ 'moment'), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -43,22 +60,39 @@ exports[`plugin Magic comment should remove only needed comments 1`] = ` exports[`plugin Magic comment should transpile arrow functions 1`] = ` "const load = { + resolved: {}, + chunkName() { return \\"moment\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => import( + importAsync: () => import( /* webpackChunkName: \\"moment\\" */ 'moment'), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -82,24 +116,41 @@ exports[`plugin Magic comment should transpile arrow functions 1`] = ` exports[`plugin Magic comment should transpile function expression 1`] = ` "const load = { + resolved: {}, + chunkName() { return \\"moment\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: function () { + importAsync: function () { return import( /* webpackChunkName: \\"moment\\" */ 'moment'); }, + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -124,24 +175,41 @@ exports[`plugin Magic comment should transpile function expression 1`] = ` exports[`plugin Magic comment should transpile shortand properties 1`] = ` "const obj = { load: { + resolved: {}, + chunkName() { return \\"moment\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => { + importAsync: () => { return import( /* webpackChunkName: \\"moment\\" */ 'moment'); }, + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -166,6 +234,8 @@ exports[`plugin Magic comment should transpile shortand properties 1`] = ` exports[`plugin aggressive import should work with destructuration 1`] = ` "loadable({ + resolved: {}, + chunkName({ foo }) { @@ -173,19 +243,34 @@ exports[`plugin aggressive import should work with destructuration 1`] = ` }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: ({ + importAsync: ({ foo }) => import( /* webpackChunkName: \\"[request]\\" */ \`./\${foo}\`), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -211,22 +296,39 @@ exports[`plugin aggressive import should work with destructuration 1`] = ` exports[`plugin aggressive import with "webpackChunkName" should replace it 1`] = ` "loadable({ + resolved: {}, + chunkName(props) { return \`\${props.foo}\`.replace(/[^a-zA-Z0-9_!§$()=\\\\-^°]+/g, \\"-\\"); }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: props => import( + importAsync: props => import( /* webpackChunkName: \\"[request]\\" */ \`./\${props.foo}\`), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -250,22 +352,39 @@ exports[`plugin aggressive import with "webpackChunkName" should replace it 1`] exports[`plugin aggressive import without "webpackChunkName" should support complex request 1`] = ` "loadable({ + resolved: {}, + chunkName(props) { return \`dir-\${props.foo}-test\`.replace(/[^a-zA-Z0-9_!§$()=\\\\-^°]+/g, \\"-\\"); }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: props => import( + importAsync: props => import( /* webpackChunkName: \\"dir-[request]\\" */ \`./dir/\${props.foo}/test\`), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -289,6 +408,8 @@ exports[`plugin aggressive import without "webpackChunkName" should support comp exports[`plugin aggressive import without "webpackChunkName" should support destructuring 1`] = ` "loadable({ + resolved: {}, + chunkName({ foo }) { @@ -296,19 +417,34 @@ exports[`plugin aggressive import without "webpackChunkName" should support dest }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: ({ + importAsync: ({ foo }) => import( /* webpackChunkName: \\"dir-[request]\\" */ \`./dir/\${foo}/test\`), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -334,22 +470,39 @@ exports[`plugin aggressive import without "webpackChunkName" should support dest exports[`plugin aggressive import without "webpackChunkName" should support simple request 1`] = ` "loadable({ + resolved: {}, + chunkName(props) { return \`\${props.foo}\`.replace(/[^a-zA-Z0-9_!§$()=\\\\-^°]+/g, \\"-\\"); }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: props => import( + importAsync: props => import( /* webpackChunkName: \\"[request]\\" */ \`./\${props.foo}\`), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -373,22 +526,39 @@ exports[`plugin aggressive import without "webpackChunkName" should support simp exports[`plugin loadable.lib should be transpiled too 1`] = ` "loadable.lib({ + resolved: {}, + chunkName() { return \\"moment\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => import( + importAsync: () => import( /* webpackChunkName: \\"moment\\" */ 'moment'), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -412,22 +582,39 @@ exports[`plugin loadable.lib should be transpiled too 1`] = ` exports[`plugin simple import in a complex promise should work 1`] = ` "loadable({ + resolved: {}, + chunkName() { return \\"ModA\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => timeout(import( + importAsync: () => timeout(import( /* webpackChunkName: \\"ModA\\" */ './ModA'), 2000), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -451,22 +638,39 @@ exports[`plugin simple import in a complex promise should work 1`] = ` exports[`plugin simple import should transform path into "chunk-friendly" name 1`] = ` "loadable({ + resolved: {}, + chunkName() { return \\"foo-bar\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => import( + importAsync: () => import( /* webpackChunkName: \\"foo-bar\\" */ '../foo/bar'), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -490,22 +694,39 @@ exports[`plugin simple import should transform path into "chunk-friendly" name 1 exports[`plugin simple import should work with * in name 1`] = ` "loadable({ + resolved: {}, + chunkName() { return \`foo\`.replace(/[^a-zA-Z0-9_!§$()=\\\\-^°]+/g, \\"-\\"); }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => import( + importAsync: () => import( /* webpackChunkName: \\"foo\\" */ \`./foo*\`), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -529,22 +750,39 @@ exports[`plugin simple import should work with * in name 1`] = ` exports[`plugin simple import should work with + concatenation 1`] = ` "loadable({ + resolved: {}, + chunkName() { return \\"\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => import( + importAsync: () => import( /* webpackChunkName: \\"\\" */ './Mod' + 'A'), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -568,22 +806,39 @@ exports[`plugin simple import should work with + concatenation 1`] = ` exports[`plugin simple import should work with template literal 1`] = ` "loadable({ + resolved: {}, + chunkName() { return \`ModA\`.replace(/[^a-zA-Z0-9_!§$()=\\\\-^°]+/g, \\"-\\"); }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => import( + importAsync: () => import( /* webpackChunkName: \\"ModA\\" */ \`./ModA\`), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -607,22 +862,39 @@ exports[`plugin simple import should work with template literal 1`] = ` exports[`plugin simple import with "webpackChunkName" comment should use it 1`] = ` "loadable({ + resolved: {}, + chunkName() { return \\"ChunkA\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => import( + importAsync: () => import( /* webpackChunkName: \\"ChunkA\\" */ './ModA'), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -646,22 +918,39 @@ exports[`plugin simple import with "webpackChunkName" comment should use it 1`] exports[`plugin simple import with "webpackChunkName" comment should use it even if comment is separated by "," 1`] = ` "loadable({ + resolved: {}, + chunkName() { return \\"ChunkA\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => import( + importAsync: () => import( /* webpackPrefetch: true, webpackChunkName: \\"ChunkA\\" */ './ModA'), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); @@ -685,22 +974,39 @@ exports[`plugin simple import with "webpackChunkName" comment should use it even exports[`plugin simple import without "webpackChunkName" comment should add it 1`] = ` "loadable({ + resolved: {}, + chunkName() { return \\"ModA\\"; }, isReady(props) { + const key = this.resolve(props); + + if (this.resolved[key] === false) { + return false; + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!__webpack_modules__[this.resolve(props)]; + return !!__webpack_modules__[key]; } return false; }, - requireAsync: () => import( + importAsync: () => import( /* webpackChunkName: \\"ModA\\" */ './ModA'), + requireAsync(props) { + const key = this.resolve(props); + this.resolved[key] = false; + return this.importAsync(props).then(resolved => { + this.resolved[key] = true; + return resolved; + }); + }, + requireSync(props) { const id = this.resolve(props); diff --git a/packages/babel-plugin/src/index.js b/packages/babel-plugin/src/index.js index ba8a7652..2f7c0e0b 100644 --- a/packages/babel-plugin/src/index.js +++ b/packages/babel-plugin/src/index.js @@ -1,13 +1,17 @@ import syntaxDynamicImport from '@babel/plugin-syntax-dynamic-import' import chunkNameProperty from './properties/chunkName' import isReadyProperty from './properties/isReady' +import importAsyncProperty from './properties/importAsync' import requireAsyncProperty from './properties/requireAsync' import requireSyncProperty from './properties/requireSync' import resolveProperty from './properties/resolve' +import stateProperty from './properties/state'; const properties = [ + stateProperty, chunkNameProperty, isReadyProperty, + importAsyncProperty, requireAsyncProperty, requireSyncProperty, resolveProperty, diff --git a/packages/babel-plugin/src/properties/importAsync.js b/packages/babel-plugin/src/properties/importAsync.js new file mode 100644 index 00000000..7e14ef92 --- /dev/null +++ b/packages/babel-plugin/src/properties/importAsync.js @@ -0,0 +1,13 @@ +export default function requireAsyncProperty({ types: t }) { + function getFunc(funcPath) { + if (funcPath.isObjectMethod()) { + const { params, body, async } = funcPath.node + return t.arrowFunctionExpression(params, body, async) + } + + return funcPath.node + } + + return ({ funcPath }) => + t.objectProperty(t.identifier('importAsync'), getFunc(funcPath)) +} diff --git a/packages/babel-plugin/src/properties/isReady.js b/packages/babel-plugin/src/properties/isReady.js index 62bfeac6..102d510d 100644 --- a/packages/babel-plugin/src/properties/isReady.js +++ b/packages/babel-plugin/src/properties/isReady.js @@ -1,7 +1,12 @@ export default function isReadyProperty({ types: t, template }) { const statements = template.ast(` + const key=this.resolve(props) + if (this.resolved[key] === false) { + return false + } + if (typeof __webpack_modules__ !== 'undefined') { - return !!(__webpack_modules__[this.resolve(props)]) + return !!(__webpack_modules__[key]) } return false diff --git a/packages/babel-plugin/src/properties/requireAsync.js b/packages/babel-plugin/src/properties/requireAsync.js index e0b27a79..e884530e 100644 --- a/packages/babel-plugin/src/properties/requireAsync.js +++ b/packages/babel-plugin/src/properties/requireAsync.js @@ -1,13 +1,19 @@ -export default function requireAsyncProperty({ types: t }) { - function getFunc(funcPath) { - if (funcPath.isObjectMethod()) { - const { params, body, async } = funcPath.node - return t.arrowFunctionExpression(params, body, async) - } +export default function requireAsyncProperty({types: t, template}) { - return funcPath.node - } + const tracking = template.ast(` + const key = this.resolve(props) + this.resolved[key] = false + return this.importAsync(props).then(resolved => { + this.resolved[key] = true + return resolved; + }); + `); - return ({ funcPath }) => - t.objectProperty(t.identifier('requireAsync'), getFunc(funcPath)) + return () => + t.objectMethod( + 'method', + t.identifier('requireAsync'), + [t.identifier('props')], + t.blockStatement(tracking), + ) } diff --git a/packages/babel-plugin/src/properties/state.js b/packages/babel-plugin/src/properties/state.js new file mode 100644 index 00000000..d14cb060 --- /dev/null +++ b/packages/babel-plugin/src/properties/state.js @@ -0,0 +1,8 @@ +export default function requireAsyncProperty({types: t}) { + + return () => + t.objectProperty( + t.identifier('resolved'), + t.objectExpression([]) + ) +} diff --git a/packages/component/.size-snapshot.json b/packages/component/.size-snapshot.json index 10dda587..a7e7fa44 100644 --- a/packages/component/.size-snapshot.json +++ b/packages/component/.size-snapshot.json @@ -1,20 +1,20 @@ { "dist/loadable.cjs.js": { "bundled": 12664, - "minified": 6090, - "gzipped": 2184 + "minified": 6110, + "gzipped": 2195 }, "dist/loadable.esm.js": { "bundled": 12281, - "minified": 5781, - "gzipped": 2119, + "minified": 5801, + "gzipped": 2126, "treeshaked": { "rollup": { "code": 259, "import_statements": 259 }, "webpack": { - "code": 5054 + "code": 5068 } } }