Skip to content

Commit 0b55a54

Browse files
authored
Merge pull request #195 from theKashey/improve-prefetching
refine babel/webpack integration
2 parents 4f6a78d + 15047a2 commit 0b55a54

25 files changed

+429
-292
lines changed

.size-limit.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ module.exports = [
22
{
33
path: ['dist/es2015/entrypoints/index.js', 'dist/es2015/entrypoints/boot.js'],
44
ignore: ['tslib'],
5-
limit: '3.6 KB',
5+
limit: '3.7 KB',
66
},
77
{
88
path: 'dist/es2015/entrypoints/index.js',
99
ignore: ['tslib'],
10-
limit: '3.3 KB',
10+
limit: '3.5 KB',
1111
},
1212
{
1313
path: 'dist/es2015/entrypoints/boot.js',
1414
ignore: ['tslib'],
15-
limit: '1.8 KB',
15+
limit: '1.9 KB',
1616
},
1717
];

.size.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
{
33
"name": "dist/es2015/entrypoints/index.js, dist/es2015/entrypoints/boot.js",
44
"passed": true,
5-
"size": 3601
5+
"size": 3704
66
},
77
{
88
"name": "dist/es2015/entrypoints/index.js",
99
"passed": true,
10-
"size": 3316
10+
"size": 3414
1111
},
1212
{
1313
"name": "dist/es2015/entrypoints/boot.js",
1414
"passed": true,
15-
"size": 1775
15+
"size": 1863
1616
}
1717
]

__tests__/__fixtures__/babel/webpack/actual.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import imported from 'react-imported-component';
33

44
const PreloadComponent = imported(() => import('./PreloadThis'));
55
const PrefetchChunkComponent = imported(() => import('./ChunkThis'));
6-
const AsyncComponent0 = imported(() => import(/* webpackChunkName:namedChunk */'./MyComponent'));
6+
const AsyncComponent0 = imported(() => import(/* webpackChunkName: "namedChunk" */'./MyComponent'));
77

88
const AsyncComponent1 = imported(() => import('./MyComponent'));
99

__tests__/__fixtures__/babel/webpack/expected.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const PrefetchChunkComponent = imported(() => importedWrapper("imported_fspdct_c
1111
/* webpackPrefetch: true */
1212
'./ChunkThis')));
1313
const AsyncComponent0 = imported(() => importedWrapper("imported_18g2v0c_component", import(
14-
/* webpackChunkName:namedChunk */
14+
/* webpackChunkName: "namedChunk" */
1515
'./MyComponent')));
1616
const AsyncComponent1 = imported(() => importedWrapper("imported_18g2v0c_component", import('./MyComponent')));
1717
const AsyncComponent2 = imported(async () => await importedWrapper("imported_18g2v0c_component", import('./MyComponent')));

__tests__/rehydrate.spec.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as React from 'react';
44
import * as ReactDOM from 'react-dom/server';
55
import { act } from 'react-dom/test-utils';
66

7-
import { settings } from '../src/config';
7+
import { setConfiguration } from '../src/configuration/config';
88
import { done as whenDone, toLoadable } from '../src/loadable/loadable';
99
import { createLoadableStream, drainHydrateMarks, rehydrateMarks } from '../src/loadable/marks';
1010
import { ImportedComponent } from '../src/ui/Component';
@@ -22,10 +22,10 @@ describe('SSR Component', () => {
2222

2323
describe('client-rehydrate', () => {
2424
beforeEach(() => {
25-
settings.SSR = false;
25+
setConfiguration({ SSR: false });
2626
});
2727
afterEach(() => {
28-
settings.SSR = true;
28+
setConfiguration({ SSR: true });
2929
});
3030

3131
it('SSR green case', async () => {
@@ -110,11 +110,9 @@ describe('SSR Component', () => {
110110

111111
describe('server-rehydrate', () => {
112112
beforeEach(() => {
113-
settings.SSR = true;
114-
});
115-
afterEach(() => {
116-
settings.SSR = true;
113+
setConfiguration({ SSR: false });
117114
});
115+
118116
it('green case', async () => {
119117
const renderSpy2 = jest.fn().mockImplementation(A => <div>{A && <A />}</div>);
120118
const Component = () => <div>loaded!</div>;

__tests__/signatures.spec.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,21 @@ describe('importMatch', () => {
9797
getFunctionSignature('()=>$(`imported_-f5674t_component`,x.e(3).then(x.bind(null,`xxx`,7)))')
9898
);
9999
expect(getFunctionSignature('()=>$(`imported_-f5674t_component`,n.e(3).then(n.bind(null,`xxx`,7)))')).toEqual(
100-
'()=>$(`imported_-f5674t_component`,-we(3).-wbind(null,`xxx`,7)))'
100+
'()=>$(`imported_-f5674t_component`,-we().-wbind(null,`xxx`,7)))'
101+
);
102+
});
103+
104+
it('maps internal and external signatures', () => {
105+
// internal is with Promise.resolve
106+
// extenal is with webpack_require.e
107+
expect(
108+
getFunctionSignature(
109+
'() => importedWrapper("imported_-1135avo_component", __webpack_require__.e(/*! import() */ 12).then(__webpack_require__.bind(null, /*! universal/components/SERP */ "./universal/components/SERP/index.js")))'
110+
)
111+
).toBe(
112+
getFunctionSignature(
113+
'() => importedWrapper("imported_-1135avo_component", Promise.resolve(/*! import() */).then(__webpack_require__.bind(null, /*! universal/components/SERP */ "./universal/components/SERP/index.js")))'
114+
)
101115
);
102116
});
103117

@@ -106,7 +120,15 @@ describe('importMatch', () => {
106120
getFunctionSignature('()=>s("imported_-is59m_component",n.e(41).then(n.bind(null,"./Promo.jsx")))')
107121
);
108122
expect(getFunctionSignature('()=>P("imported_-is59m_component",t.e(41).then(t.bind(null,"./Promo.jsx")))')).toEqual(
109-
'()=>$(`imported_-is59m_component`,-we(41).-wbind(null,`./Promo.jsx`)))'
123+
'()=>$(`imported_-is59m_component`,-we().-wbind(null,`./Promo.jsx`)))'
124+
);
125+
});
126+
127+
it('fallback check: same signature, different function', () => {
128+
expect(
129+
getFunctionSignature('()=>P("imported_-one_component",t.e(41).then(t.bind(null,"./Promo.jsx")))')
130+
).not.toEqual(
131+
getFunctionSignature('()=>s("imported_-another_component",n.e(41).then(n.bind(null,"./Promo.jsx")))')
110132
);
111133
});
112134
});

__tests__/utils.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ describe('scanForImports', () => {
106106
(a, b) => a + b,
107107
imports,
108108
imported => imported.indexOf('c.js') < 0,
109-
(imported, _, givenChunkName) => (imported.indexOf('a.js') > 0 ? `test-${givenChunkName}-test` : 'bundle-b')
109+
(imported, _, options) => (imported.indexOf('a.js') > 0 ? `test-${options.chunkName}-test` : 'bundle-b')
110110
);
111111
expect(Object.values(imports)).toEqual([
112112
`[() => import(/* webpackChunkName: \"chunk-a\" */'${rel}/a.js'), 'test-chunk-a-test', '${rel}/a.js', false] /* from .a */`,

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-imported-component",
3-
"version": "6.3.5",
3+
"version": "6.3.9",
44
"description": "I will import your component, and help to handle it",
55
"main": "dist/es5/entrypoints/index.js",
66
"jsnext:main": "dist/es2015/entrypoints/index.js",
@@ -62,7 +62,6 @@
6262
"@babel/runtime": "^7.3.1",
6363
"@size-limit/preset-small-lib": "^4.5.1",
6464
"@theuiteam/lib-builder": "0.0.8",
65-
"@types/detect-node": "^2.0.0",
6665
"@types/enzyme": "^3.10.3",
6766
"@types/node": "^12.12.6",
6867
"@types/react-dom": "^16.9.4",
@@ -78,7 +77,7 @@
7877
"dependencies": {
7978
"babel-plugin-macros": "^2.6.1",
8079
"crc-32": "^1.2.0",
81-
"detect-node": "^2.0.4",
80+
"detect-node-es": "^1.0.0",
8281
"scan-directory": "^2.0.0",
8382
"tslib": "^1.10.0"
8483
},

src/babel/babel.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// @ts-ignore
2+
import * as crc32 from 'crc-32';
3+
import { existsSync } from 'fs';
4+
import { dirname, join, relative, resolve } from 'path';
5+
6+
import { ImportedConfiguration } from '../configuration/configuration';
7+
import { processComment } from './magic-comments';
8+
9+
export const encipherImport = (str: string) => {
10+
return crc32.str(str).toString(32);
11+
};
12+
13+
// Babel v7 compat
14+
let syntax: any;
15+
try {
16+
syntax = require('babel-plugin-syntax-dynamic-import');
17+
} catch (err) {
18+
try {
19+
syntax = require('@babel/plugin-syntax-dynamic-import');
20+
} catch (e) {
21+
throw new Error(
22+
'react-imported-component babel plugin is requiring `babel-plugin-syntax-dynamic-import` or `@babel/plugin-syntax-dynamic-import` to work. Please add this dependency.'
23+
);
24+
}
25+
}
26+
syntax = syntax.default || syntax;
27+
28+
const resolveImport = (importName: string, file = '') => {
29+
if (importName.charAt(0) === '.') {
30+
return relative(process.cwd(), resolve(dirname(file), importName));
31+
}
32+
return importName;
33+
};
34+
35+
const templateOptions = {
36+
placeholderPattern: /^([A-Z0-9]+)([A-Z0-9_]+)$/,
37+
};
38+
39+
function getImportArg(callPath: any) {
40+
return callPath.get('arguments.0');
41+
}
42+
43+
function getComments(callPath: any) {
44+
return callPath.has('leadingComments') ? callPath.get('leadingComments') : [];
45+
}
46+
47+
// load configuration
48+
const configurationFile = join(process.cwd(), '.imported.js');
49+
const defaultConfiguration: ImportedConfiguration = (existsSync(configurationFile)
50+
? require(configurationFile)
51+
: {}) as ImportedConfiguration;
52+
53+
export const createTransformer = (
54+
{ types: t, template }: any,
55+
excludeMacro = false,
56+
configuration = defaultConfiguration
57+
) => {
58+
const headerTemplate = template(
59+
`var importedWrapper = require('react-imported-component/wrapper');`,
60+
templateOptions
61+
);
62+
63+
const importRegistration = template('importedWrapper(MARK, IMPORT)', templateOptions);
64+
65+
const hasImports = new Set<string>();
66+
const visitedNodes = new Map();
67+
68+
return {
69+
traverse(programPath: any, fileName: string) {
70+
let isBootstrapFile = false;
71+
72+
programPath.traverse({
73+
ImportDeclaration(path: any) {
74+
if (excludeMacro) {
75+
return;
76+
}
77+
78+
const source = path.node.source.value;
79+
if (source === 'react-imported-component/macro') {
80+
const { specifiers } = path.node;
81+
path.remove();
82+
const assignName = 'assignImportedComponents';
83+
if (specifiers.length === 1 && specifiers[0].imported.name === assignName) {
84+
isBootstrapFile = true;
85+
programPath.node.body.unshift(
86+
t.importDeclaration(
87+
[t.importSpecifier(t.identifier(assignName), t.identifier(assignName))],
88+
t.stringLiteral('react-imported-component/boot')
89+
)
90+
);
91+
} else {
92+
programPath.node.body.unshift(
93+
t.importDeclaration(
94+
specifiers.map((spec: any) =>
95+
t.importSpecifier(t.identifier(spec.imported.name), t.identifier(spec.imported.name))
96+
),
97+
t.stringLiteral('react-imported-component')
98+
)
99+
);
100+
}
101+
}
102+
},
103+
Import({ parentPath }: any) {
104+
if (visitedNodes.has(parentPath.node)) {
105+
return;
106+
}
107+
108+
const newImport = parentPath.node;
109+
const rawImport = getImportArg(parentPath);
110+
const importName = rawImport.node.value;
111+
const rawComments = getComments(rawImport);
112+
const comments = rawComments.map((parent: any) => parent.node.value);
113+
114+
const newComments = processComment(configuration, comments, importName, fileName, {
115+
isBootstrapFile,
116+
});
117+
118+
if (newComments !== comments) {
119+
rawComments.forEach((comment: any) => comment.remove());
120+
newComments.forEach((comment: string) => {
121+
rawImport.addComment('leading', ` ${comment} `);
122+
});
123+
}
124+
125+
if (!importName) {
126+
return;
127+
}
128+
const requiredFileHash = encipherImport(resolveImport(importName, fileName));
129+
130+
let replace = null;
131+
132+
replace = importRegistration({
133+
MARK: t.stringLiteral(`imported_${requiredFileHash}_component`),
134+
IMPORT: newImport,
135+
});
136+
137+
hasImports.add(fileName);
138+
visitedNodes.set(newImport, true);
139+
140+
parentPath.replaceWith(replace);
141+
},
142+
});
143+
},
144+
145+
finish(node: any, filename: string) {
146+
if (!hasImports.has(filename)) {
147+
return;
148+
}
149+
node.body.unshift(headerTemplate());
150+
},
151+
152+
hasImports,
153+
};
154+
};
155+
156+
export const babelPlugin = (babel: any, options: ImportedConfiguration = {}) => {
157+
const transformer = createTransformer(babel, false, {
158+
...defaultConfiguration,
159+
...options,
160+
});
161+
162+
return {
163+
inherits: syntax,
164+
165+
visitor: {
166+
Program: {
167+
enter(programPath: any, { file }: any) {
168+
transformer.traverse(programPath, file.opts.filename);
169+
},
170+
171+
exit({ node }: any, { file }: any) {
172+
transformer.finish(node, file.opts.filename);
173+
},
174+
},
175+
},
176+
};
177+
};

0 commit comments

Comments
 (0)