Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 95 additions & 37 deletions packages/babel-plugin/src/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -580,12 +580,12 @@ exports[`plugin loadable.lib should be transpiled too 1`] = `
});"
`;

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 \\"ModA\\";
return \\"foo-bar\\";
},

isReady(props) {
Expand All @@ -602,9 +602,9 @@ exports[`plugin simple import in a complex promise should work 1`] = `
return false;
},

importAsync: () => timeout(import(
/* webpackChunkName: \\"ModA\\" */
'./ModA'), 2000),
importAsync: () => import(
/* webpackChunkName: \\"foo-bar\\" */
'../foo/bar'),

requireAsync(props) {
const key = this.resolve(props);
Expand All @@ -627,21 +627,21 @@ exports[`plugin simple import in a complex promise should work 1`] = `

resolve() {
if (require.resolveWeak) {
return require.resolveWeak(\\"./ModA\\");
return require.resolveWeak(\\"../foo/bar\\");
}

return eval('require.resolve')(\\"./ModA\\");
return eval('require.resolve')(\\"../foo/bar\\");
}

});"
`;

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-bar\\";
return \`foo\`.replace(/[^a-zA-Z0-9_!§$()=\\\\-^°]+/g, \\"-\\");
},

isReady(props) {
Expand All @@ -659,8 +659,8 @@ exports[`plugin simple import should transform path into "chunk-friendly" name 1
},

importAsync: () => import(
/* webpackChunkName: \\"foo-bar\\" */
'../foo/bar'),
/* webpackChunkName: \\"foo\\" */
\`./foo*\`),

requireAsync(props) {
const key = this.resolve(props);
Expand All @@ -683,21 +683,21 @@ exports[`plugin simple import should transform path into "chunk-friendly" name 1

resolve() {
if (require.resolveWeak) {
return require.resolveWeak(\\"../foo/bar\\");
return require.resolveWeak(\`./foo*\`);
}

return eval('require.resolve')(\\"../foo/bar\\");
return eval('require.resolve')(\`./foo*\`);
}

});"
`;

exports[`plugin simple import should work with * in name 1`] = `
exports[`plugin simple import should work with + concatenation 1`] = `
"loadable({
resolved: {},

chunkName() {
return \`foo\`.replace(/[^a-zA-Z0-9_!§$()=\\\\-^°]+/g, \\"-\\");
return \\"\\";
},

isReady(props) {
Expand All @@ -715,8 +715,8 @@ exports[`plugin simple import should work with * in name 1`] = `
},

importAsync: () => import(
/* webpackChunkName: \\"foo\\" */
\`./foo*\`),
/* webpackChunkName: \\"\\" */
'./Mod' + 'A'),

requireAsync(props) {
const key = this.resolve(props);
Expand All @@ -739,21 +739,21 @@ exports[`plugin simple import should work with * in name 1`] = `

resolve() {
if (require.resolveWeak) {
return require.resolveWeak(\`./foo*\`);
return require.resolveWeak('./Mod' + 'A');
}

return eval('require.resolve')(\`./foo*\`);
return eval('require.resolve')('./Mod' + 'A');
}

});"
`;

exports[`plugin simple import should work with + concatenation 1`] = `
exports[`plugin simple import should work with template literal 1`] = `
"loadable({
resolved: {},

chunkName() {
return \\"\\";
return \`ModA\`.replace(/[^a-zA-Z0-9_!§$()=\\\\-^°]+/g, \\"-\\");
},

isReady(props) {
Expand All @@ -771,8 +771,8 @@ exports[`plugin simple import should work with + concatenation 1`] = `
},

importAsync: () => import(
/* webpackChunkName: \\"\\" */
'./Mod' + 'A'),
/* webpackChunkName: \\"ModA\\" */
\`./ModA\`),

requireAsync(props) {
const key = this.resolve(props);
Expand All @@ -795,21 +795,21 @@ exports[`plugin simple import should work with + concatenation 1`] = `

resolve() {
if (require.resolveWeak) {
return require.resolveWeak('./Mod' + 'A');
return require.resolveWeak(\`./ModA\`);
}

return eval('require.resolve')('./Mod' + 'A');
return eval('require.resolve')(\`./ModA\`);
}

});"
`;

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 \`ModA\`.replace(/[^a-zA-Z0-9_!§$()=\\\\-^°]+/g, \\"-\\");
return \\"ChunkA\\";
},

isReady(props) {
Expand All @@ -827,8 +827,8 @@ exports[`plugin simple import should work with template literal 1`] = `
},

importAsync: () => import(
/* webpackChunkName: \\"ModA\\" */
\`./ModA\`),
/* webpackChunkName: \\"ChunkA\\" */
'./ModA'),

requireAsync(props) {
const key = this.resolve(props);
Expand All @@ -851,16 +851,16 @@ exports[`plugin simple import should work with template literal 1`] = `

resolve() {
if (require.resolveWeak) {
return require.resolveWeak(\`./ModA\`);
return require.resolveWeak(\\"./ModA\\");
}

return eval('require.resolve')(\`./ModA\`);
return eval('require.resolve')(\\"./ModA\\");
}

});"
`;

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: {},

Expand All @@ -883,7 +883,7 @@ exports[`plugin simple import with "webpackChunkName" comment should use it 1`]
},

importAsync: () => import(
/* webpackChunkName: \\"ChunkA\\" */
/* webpackPrefetch: true, webpackChunkName: \\"ChunkA\\" */
'./ModA'),

requireAsync(props) {
Expand Down Expand Up @@ -916,12 +916,12 @@ 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`] = `
exports[`plugin simple import with arrow function with body 1`] = `
"loadable({
resolved: {},

chunkName() {
return \\"ChunkA\\";
return \\"ModA\\";
},

isReady(props) {
Expand All @@ -938,8 +938,66 @@ exports[`plugin simple import with "webpackChunkName" comment should use it even
return false;
},

importAsync: () => import(
/* webpackPrefetch: true, webpackChunkName: \\"ChunkA\\" */
importAsync: () => {
return 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);

if (typeof __webpack_require__ !== 'undefined') {
return __webpack_require__(id);
}

return eval('module.require')(id);
},

resolve() {
if (require.resolveWeak) {
return require.resolveWeak(\\"./ModA\\");
}

return eval('require.resolve')(\\"./ModA\\");
}

});"
`;

exports[`plugin simple import with async arrow function 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__[key];
}

return false;
},

importAsync: async () => import(
/* webpackChunkName: \\"ModA\\" */
'./ModA'),

requireAsync(props) {
Expand Down
57 changes: 54 additions & 3 deletions packages/babel-plugin/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,49 @@ const loadablePlugin = api => {
return imports
}

function isImportCall(path) {
if (path.type === 'CallExpression') {
const { callee } = path;
if (callee.type === 'Import') {
return true;
}
}
return false
}

function isFunctionBodyWhichReturnsImportCall(path) {
if (path.type === 'BlockStatement') {
const { body: methodBody } = path;
if (methodBody.length === 1) {
const [statement] = methodBody;
if (statement.type === 'ReturnStatement') {
const { argument: returnExpression } = statement;
if (isImportCall(returnExpression)) {
return true;
}
}
}
}
return false;
}

function isFunctionAndOnlyReturnsImport(importCreator) {
if (importCreator.type === 'ArrowFunctionExpression') {
const { body } = importCreator;
if (isImportCall(body) || isFunctionBodyWhichReturnsImportCall(body)) {
return true;
}
}

if (['ObjectMethod', 'FunctionExpression'].indexOf(importCreator.type) !== -1) {
const { body } = importCreator;
if (isFunctionBodyWhichReturnsImportCall(body)) {
return true;
}
}
return false;
}

const propertyFactories = properties.map(init => init(api))

function isValidIdentifier(path) {
Expand Down Expand Up @@ -72,10 +115,18 @@ const loadablePlugin = api => {
}

function transformImport(path) {
const callPaths = collectImportCallPaths(path)
const importCreator = path.node.type === 'CallExpression'
? path.node.arguments[0] // loadable((...) => import(...)) or loadable.lib
: path.node; // /* #__LOADABLE__ */ () => import(...)

// Ignore loadable function that does not have any "import" call
if (callPaths.length === 0) return
if (!isFunctionAndOnlyReturnsImport(importCreator)) {
throw new Error(
'The first argument to `loadable()` must be a function with a single statement that returns a call to `import()`' +
'See https://loadable-components.com/docs/api-loadable-component/#loadfn for more information',
);
}

const callPaths = collectImportCallPaths(path)

// Multiple imports call is not supported
if (callPaths.length > 1) {
Expand Down
Loading