-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
126 lines (101 loc) · 4.99 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
const {declare} = require(`@babel/helper-plugin-utils`);
const pluginTransformParameters = require(`@babel/plugin-transform-parameters`).default;
const {wrapInterop} = require(`@babel/helper-module-transforms`);
const template = require(`@babel/template`).default;
const DYNAMIC_IMPORT = template.expression(`await import(SOURCE)`, {
allowAwaitOutsideFunction: true,
plugins: [`dynamicImport`],
});
const CONST_DECLARATION = template.statement(`const NAME = INIT`);
const PROPERTY_ACCESS = template.expression(`SOURCE.NAME`);
module.exports = declare((api, options) => {
const {types: t} = api;
const pluginTransformParametersInstance = pluginTransformParameters(api, {});
function findAsyncPath(path) {
let candidate = null
for (let current = path.getFunctionParent(); current !== null; current = current.getFunctionParent())
if (current.node.async)
candidate = current;
return candidate;
}
function isInParameterList(path) {
const parentFunction = path.getFunctionParent();
return !path.isDescendant(parentFunction.get(`body`));
}
let programPath;
return {
visitor: {
Program(path) {
programPath = path;
},
ImportDeclaration(path) {
const allRemaps = new Map();
for (const specifier of path.get(`specifiers`)) {
const importedIdentifierName = specifier.node.local.name;
const {referencePaths} = path.scope.getBinding(importedIdentifierName);
const referencesWithTheirBlocks = referencePaths.map(referencePath => {
return [referencePath, findAsyncPath(referencePath)];
});
// If even one of the references is used in a strictly
// synchronous block, there's no point in lazy-loading
// the file
if (referencesWithTheirBlocks.some(([, asyncPath]) => asyncPath === null))
return;
for (const [referencePath, asyncPath] of referencesWithTheirBlocks) {
let remaps = allRemaps.get(asyncPath);
if (typeof remaps === `undefined`)
allRemaps.set(asyncPath, remaps = []);
remaps.push([specifier, referencePath]);
}
}
if (allRemaps.size === 0)
return;
for (const [asyncPath, remaps] of allRemaps) {
if (asyncPath.node.type === `ArrowFunctionExpression` && asyncPath.node.body.type !== `BlockStatement`)
asyncPath.node.body = t.blockStatement([t.returnStatement(asyncPath.node.body)]);
const idRef = path.scope.generateUidIdentifierBasedOnNode(path.node.id);
let downgradeParameters = false;
const dynamicImport = DYNAMIC_IMPORT({
SOURCE: path.node.source,
});
for (const [specifierPath, referencePath] of remaps) {
if (isInParameterList(referencePath)) {
source = dynamicImport;
downgradeParameters = true;
}
let dereference;
switch (specifierPath.node.type) {
case `ImportSpecifier`: {
dereference = PROPERTY_ACCESS({
SOURCE: idRef,
NAME: specifierPath.node.imported,
});
} break;
case `ImportDefaultSpecifier`: {
dereference = PROPERTY_ACCESS({
SOURCE: wrapInterop(programPath, idRef, "default"),
NAME: t.identifier(`default`),
});
} break;
case `ImportNamespaceSpecifier`: {
dereference = wrapInterop(programPath, idRef, "namespace");
} break;
default: {
throw new Error(`Unsupported import specifier type "${specifierPath.node.type}"`);
} break;
}
referencePath.replaceWith(dereference);
}
if (downgradeParameters) {
pluginTransformParametersInstance.visitor.Function(asyncPath);
}
asyncPath.get(`body`).unshiftContainer(`body`, CONST_DECLARATION({
NAME: idRef,
INIT: dynamicImport,
}));
}
path.remove();
},
},
};
});