Skip to content

Commit

Permalink
feat: add babel-macros, fixes #142
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Sep 18, 2019
1 parent e543ffc commit 18d675d
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 32 deletions.
61 changes: 61 additions & 0 deletions __tests__/__snapshots__/macro.spec.ts.snap
@@ -0,0 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`babel macro macros many: many 1`] = `
"
import {imported, useImported} from \\"../macro\\";
const v = imported(() => import('./a'));
const x = () => useImported(() => import('./b'));
↓ ↓ ↓ ↓ ↓ ↓
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
} else {
var newObj = {};
if (obj != null) {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc =
Object.defineProperty && Object.getOwnPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: {};
if (desc.get || desc.set) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
}
newObj.default = obj;
return newObj;
}
}
var importedWrapper = function(marker, realImport) {
if (typeof __deoptimization_sideEffect__ !== \\"undefined\\") {
__deoptimization_sideEffect__(marker, realImport);
}
return realImport;
};
import { imported, useImported } from \\"react-imported-component\\";
const v = imported(() =>
importedWrapper(
\\"imported_-mg71kn_component\\",
Promise.resolve().then(() => _interopRequireWildcard(require(\\"./a\\")))
)
);
const x = () =>
useImported(() =>
importedWrapper(
\\"imported_15vaa6j_component\\",
Promise.resolve().then(() => _interopRequireWildcard(require(\\"./b\\")))
)
);
"
`;
30 changes: 30 additions & 0 deletions __tests__/macro.spec.ts
@@ -0,0 +1,30 @@
const pluginTester = require("babel-plugin-tester");
const plugin = require("babel-plugin-macros");
const prettier = require("prettier");

describe('babel macro', () => {
pluginTester({
plugin,
snapshot: true,
babelOptions: {
filename: __filename,
plugins: ['dynamic-import-node'],
},
formatResult(result: string) {
return prettier.format(result, {trailingComma: "es5"});
},
tests: {
"nothing": "const a = 42;",
"no usage": `import {lazy} from "../macro";`,
"lazy": `
import {lazy} from "../macro";
const v = lazy(() => import('./a'));
`,
"many": `
import {imported, useImported} from "../macro";
const v = imported(() => import('./a'));
const x = () => useImported(() => import('./b'));
`,
},
});
});
7 changes: 7 additions & 0 deletions macro/package.json
@@ -0,0 +1,7 @@
{
"private": true,
"main": "../dist/es5/macro.js",
"jsnext:main": "../dist/es2015/macro.js",
"module": "../dist/es2015/macro.js",
"types": "../dist/es2015/macro.d.ts"
}
4 changes: 4 additions & 0 deletions package.json
Expand Up @@ -51,6 +51,9 @@
"@size-limit/preset-small-lib": "^2.1.1",
"@types/node": "^12.7.2",
"babel-plugin-dynamic-import-node": "^2.3.0",
"babel-plugin-macros": "^2.6.1",
"babel-plugin-tester": "^7.0.1",
"prettier": "^1.18.2",
"react-test-renderer": "^16.6.0",
"sinon": "^5.0.10",
"size-limit": "^2.0.2",
Expand Down Expand Up @@ -89,6 +92,7 @@
"dist",
"boot",
"server",
"macro",
"babel.js"
]
}
77 changes: 45 additions & 32 deletions src/babel.ts
Expand Up @@ -30,7 +30,7 @@ const templateOptions = {
placeholderPattern: /^([A-Z0-9]+)([A-Z0-9_]+)$/,
};

export default function ({types: t, template}: any) {
export const createTransformer = ({types: t, template}: any) => {
const headerTemplate = template(`var importedWrapper = function(marker, realImport) {
if (typeof __deoptimization_sideEffect__ !== 'undefined') {
__deoptimization_sideEffect__(marker, realImport);
Expand All @@ -47,47 +47,60 @@ export default function ({types: t, template}: any) {
const visitedNodes = new Map();

return {
inherits: syntax,

visitor: {
// using program to replace imports before "dynamic-import-node"
// see: https://jamie.build/babel-plugin-ordering.html
Program: {
enter(programPath: any, {file}: any) {
programPath.traverse({
Import({parentPath}:any) {
if (visitedNodes.has(parentPath.node)) {
return;
}
traverse(programPath: any, file: any) {
programPath.traverse({
Import({parentPath}: any) {
if (visitedNodes.has(parentPath.node)) {
return;
}

const localFile = file.opts.filename;
const newImport = parentPath.node;
const importName = parentPath.get('arguments')[0].node.value;

if (!importName) {
return;
}
const requiredFileHash = encipherImport(resolveImport(importName, localFile));

let replace = null;

replace = importRegistration({
MARK: t.stringLiteral(`imported_${requiredFileHash}_component`),
IMPORT: newImport
});

const localFile = file.opts.filename;
const newImport = parentPath.node;
const importName = parentPath.get('arguments')[0].node.value;
hasImports.add(localFile);
visitedNodes.set(newImport, true);

if (!importName) {
return;
}
const requiredFileHash = encipherImport(resolveImport(importName, localFile));
parentPath.replaceWith(replace);
}
});
},

let replace = null;
finish(node: any) {
node.body.unshift(headerTemplate());
},

replace = importRegistration({
MARK: t.stringLiteral(`imported_${requiredFileHash}_component`),
IMPORT: newImport
});
hasImports,
}
};

hasImports.add(localFile);
visitedNodes.set(newImport, true);
export default function (babel: any) {
const transformer = createTransformer(babel);
return {
inherits: syntax,

parentPath.replaceWith(replace);
}
});
visitor: {
Program: {
enter(programPath: any, {file}: any) {
transformer.traverse(programPath, file);
},

exit({node}: any, {file}: any) {
if (!hasImports.has(file.opts.filename)) return;
if (!transformer.hasImports.has(file.opts.filename)) return;

node.body.unshift(headerTemplate());
transformer.finish(node);
}
},
}
Expand Down
5 changes: 5 additions & 0 deletions src/index.ts
Expand Up @@ -15,6 +15,8 @@ import {remapImports} from './helpers';
import {useImported, useLoadable, useLazy} from './useImported';
import {loadByChunkname} from './loadByChunkName';

import {addPreloader} from "./preloaders";

export {
printDrainHydrateMarks,
drainHydrateMarks,
Expand All @@ -32,12 +34,15 @@ export {
ImportedStream,
setConfiguration,

imported,
lazy,
LazyBoundary,
remapImports,

useLoadable,
useImported,
useLazy,

addPreloader,
}
export default imported;
89 changes: 89 additions & 0 deletions src/macro.ts
@@ -0,0 +1,89 @@
// @ts-ignore
import {createMacro} from "babel-plugin-macros";

import {createTransformer} from "./babel"

function getMacroType(tagName: string) {
switch (tagName) {
case "imported":
case "lazy":
case "useImported":
return true;

default:
return false;
}
}

function macro({references, state, babel}: any) {
const {types: t} = babel;

const imports: string[] = [];
const transformer = createTransformer(babel);

Object
.keys(references)
.forEach((tagName: string) => {
if (getMacroType(tagName)) {
imports.push(tagName);
const tags = references[tagName];
tags.forEach((tag: any) => {
let expression = tag.parentPath;

if (t.isCallExpression(expression)) {
transformer.traverse(expression, state.file)
}
});
}
});

if (addReactImports(babel, state, imports)) {
transformer.finish(state.file.path.node);
}
}

function addReactImports(babel: any, state: any, imports: string[]) {
if (!imports.length) return false;

const {types: t} = babel;

const importedImport = state.file.path.node.body.find(
(importNode: any) =>
t.isImportDeclaration(importNode) &&
importNode.source.value === "react-imported-component"
);

// Handle adding the import or altering the existing import
if (importedImport) {
imports.forEach(name => {
if (
!importedImport.specifiers.find(
(specifier: any) => specifier.imported && specifier.imported.name === name
)
) {
importedImport.specifiers.push(
t.importSpecifier(t.identifier(name), t.identifier(name))
)
}
})
} else {
state.file.path.node.body.unshift(
t.importDeclaration(
imports.map(name =>
t.importSpecifier(t.identifier(name), t.identifier(name))
),
t.stringLiteral("react-imported-component")
)
)
}

return true;
}

const lazy: typeof import('./HOC').lazy = null as any;
const imported: typeof import('./HOC').default = null as any;
const useImported: typeof import('./useImported').useImported = null as any;

export {lazy, imported, useImported};

export default createMacro(macro)

0 comments on commit 18d675d

Please sign in to comment.