Skip to content

Commit

Permalink
feat(babel-plugin): transform code annotated with magic comment
Browse files Browse the repository at this point in the history
Transform imports inside code annotated with `/* #__LOADABLE__ * /`.

Fixes #192
  • Loading branch information
tagoro9 authored and gregberge committed Feb 5, 2019
1 parent ef11e11 commit 4f832dc
Show file tree
Hide file tree
Showing 5 changed files with 396 additions and 24 deletions.
164 changes: 164 additions & 0 deletions packages/babel-plugin/src/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,169 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`plugin Magic comment should remove only needed comments 1`] = `
"const load =
/* IMPORTANT! */
{
chunkName() {
return \\"moment\\";
},
isReady(props) {
if (typeof __webpack_modules__ !== 'undefined') {
return !!__webpack_modules__[this.resolve(props)];
}
return false;
},
requireAsync: () => import(
/* webpackChunkName: \\"moment\\" */
'moment'),
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(\\"moment\\");
}
return require('path').resolve(__dirname, \\"moment\\");
}
};"
`;

exports[`plugin Magic comment should transpile arrow functions 1`] = `
"const load = {
chunkName() {
return \\"moment\\";
},
isReady(props) {
if (typeof __webpack_modules__ !== 'undefined') {
return !!__webpack_modules__[this.resolve(props)];
}
return false;
},
requireAsync: () => import(
/* webpackChunkName: \\"moment\\" */
'moment'),
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(\\"moment\\");
}
return require('path').resolve(__dirname, \\"moment\\");
}
};"
`;

exports[`plugin Magic comment should transpile function expression 1`] = `
"const load = {
chunkName() {
return \\"moment\\";
},
isReady(props) {
if (typeof __webpack_modules__ !== 'undefined') {
return !!__webpack_modules__[this.resolve(props)];
}
return false;
},
requireAsync: function () {
return import(
/* webpackChunkName: \\"moment\\" */
'moment');
},
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(\\"moment\\");
}
return require('path').resolve(__dirname, \\"moment\\");
}
};"
`;

exports[`plugin Magic comment should transpile shortand properties 1`] = `
"const obj = {
load: {
chunkName() {
return \\"moment\\";
},
isReady(props) {
if (typeof __webpack_modules__ !== 'undefined') {
return !!__webpack_modules__[this.resolve(props)];
}
return false;
},
requireAsync: () => {
return import(
/* webpackChunkName: \\"moment\\" */
'moment');
},
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(\\"moment\\");
}
return require('path').resolve(__dirname, \\"moment\\");
}
}
};"
`;

exports[`plugin aggressive import should work with destructuration 1`] = `
"loadable({
chunkName({
Expand Down
88 changes: 65 additions & 23 deletions packages/babel-plugin/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const properties = [
resolveProperty,
]

const LOADABLE_COMMENT = '#__LOADABLE__'

const loadablePlugin = api => {
const { types: t } = api

Expand Down Expand Up @@ -42,34 +44,74 @@ const loadablePlugin = api => {
)
}

return {
inherits: syntaxDynamicImport,
visitor: {
CallExpression(path) {
if (!isValidIdentifier(path)) return
function hasLoadableComment(path) {
const comments = path.get('leadingComments')
const comment = comments.find(
({ node }) =>
node && node.value && String(node.value).includes(LOADABLE_COMMENT),
)
if (!comment) return false
comment.remove()
return true
}

function getFuncPath(path) {
const funcPath = path.isCallExpression() ? path.get('arguments.0') : path
if (
!funcPath.isFunctionExpression() &&
!funcPath.isArrowFunctionExpression() &&
!funcPath.isObjectMethod()
) {
return null
}
return funcPath
}

function transformImport(path) {
const callPaths = collectImportCallPaths(path)

const callPaths = collectImportCallPaths(path)
// Ignore loadable function that does not have any "import" call
if (callPaths.length === 0) return

// Ignore loadable function that does not have any "import" call
if (callPaths.length === 0) return
// Multiple imports call is not supported
if (callPaths.length > 1) {
throw new Error(
'loadable: multiple import calls inside `loadable()` function are not supported.',
)
}

// Multiple imports call is not supported
if (callPaths.length > 1) {
throw new Error(
'loadable: multiple import calls inside `loadable()` function are not supported.',
)
}
const [callPath] = callPaths

const [callPath] = callPaths
const funcPath = path.get('arguments.0')
const funcPath = getFuncPath(path)
if (!funcPath) return

funcPath.replaceWith(
t.objectExpression(
propertyFactories.map(getProperty =>
getProperty({ path, callPath, funcPath }),
),
),
)
funcPath.node.params = funcPath.node.params || []

const object = t.objectExpression(
propertyFactories.map(getProperty =>
getProperty({ path, callPath, funcPath }),
),
)

if (funcPath.isObjectMethod()) {
funcPath.replaceWith(
t.objectProperty(funcPath.node.key, object, funcPath.node.computed),
)
} else {
funcPath.replaceWith(object)
}
}

return {
inherits: syntaxDynamicImport,
visitor: {
CallExpression(path) {
if (!isValidIdentifier(path)) return
transformImport(path)
},
'ArrowFunctionExpression|FunctionExpression|ObjectMethod': path => {
if (!hasLoadableComment(path)) return
transformImport(path)
},
},
}
Expand Down
40 changes: 40 additions & 0 deletions packages/babel-plugin/src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,44 @@ describe('plugin', () => {
expect(result).toMatchSnapshot()
})
})

describe('Magic comment', () => {
it('should transpile shortand properties', () => {
const result = testPlugin(`
const obj = {
/* #__LOADABLE__ */
load() {
return import('moment')
}
}
`)

expect(result).toMatchSnapshot()
})

it('should transpile arrow functions', () => {
const result = testPlugin(`
const load = /* #__LOADABLE__ */ () => import('moment')
`)

expect(result).toMatchSnapshot()
})

it('should transpile function expression', () => {
const result = testPlugin(`
const load = /* #__LOADABLE__ */ function () {
return import('moment')
}
`)
expect(result).toMatchSnapshot()
})

it('should remove only needed comments', () => {
const result = testPlugin(`
const load = /* #__LOADABLE__ */ /* IMPORTANT! */ () => import('moment')
`)

expect(result).toMatchSnapshot()
})
})
})
11 changes: 10 additions & 1 deletion packages/babel-plugin/src/properties/requireAsync.js
Original file line number Diff line number Diff line change
@@ -1,4 +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('requireAsync'), funcPath.node)
t.objectProperty(t.identifier('requireAsync'), getFunc(funcPath))
}
Loading

0 comments on commit 4f832dc

Please sign in to comment.