UNPKG

5.52 kBJavaScriptView Raw
1"use strict";
2
3const types = require('@babel/types');
4
5const template = require('@babel/template').default;
6
7const traverse = require('@babel/traverse').default;
8
9const urlJoin = require('../utils/urlJoin');
10
11const isURL = require('../utils/is-url');
12
13const nodeBuiltins = require('node-libs-browser');
14
15const requireTemplate = template('require("_bundle_loader")');
16const argTemplate = template('require.resolve(MODULE)');
17const serviceWorkerPattern = ['navigator', 'serviceWorker', 'register'];
18module.exports = {
19 ImportDeclaration(node, asset) {
20 asset.isES6Module = true;
21 addDependency(asset, node.source);
22 },
23
24 ExportNamedDeclaration(node, asset) {
25 asset.isES6Module = true;
26
27 if (node.source) {
28 addDependency(asset, node.source);
29 }
30 },
31
32 ExportAllDeclaration(node, asset) {
33 asset.isES6Module = true;
34 addDependency(asset, node.source);
35 },
36
37 ExportDefaultDeclaration(node, asset) {
38 asset.isES6Module = true;
39 },
40
41 CallExpression(node, asset, ancestors) {
42 let callee = node.callee,
43 args = node.arguments;
44 let isRequire = types.isIdentifier(callee) && callee.name === 'require' && args.length === 1 && types.isStringLiteral(args[0]) && !hasBinding(ancestors, 'require') && !isInFalsyBranch(ancestors);
45
46 if (isRequire) {
47 let optional = ancestors.some(a => types.isTryStatement(a)) || undefined;
48 addDependency(asset, args[0], {
49 optional
50 });
51 return;
52 }
53
54 let isDynamicImport = callee.type === 'Import' && args.length === 1 && types.isStringLiteral(args[0]);
55
56 if (isDynamicImport) {
57 if (isURL(args[0].value)) return;
58 asset.addDependency('_bundle_loader');
59 addDependency(asset, args[0], {
60 dynamic: true
61 });
62 node.callee = requireTemplate().expression;
63 node.arguments[0] = argTemplate({
64 MODULE: args[0]
65 }).expression;
66 asset.isAstDirty = true;
67 return;
68 }
69
70 const isRegisterServiceWorker = types.isStringLiteral(args[0]) && types.matchesPattern(callee, serviceWorkerPattern);
71
72 if (isRegisterServiceWorker) {
73 // Treat service workers as an entry point so filenames remain consistent across builds.
74 // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#avoid_changing_the_url_of_your_service_worker_script
75 addURLDependency(asset, args[0], {
76 entry: true,
77 isolated: true
78 });
79 return;
80 }
81 },
82
83 NewExpression(node, asset) {
84 const callee = node.callee,
85 args = node.arguments;
86 const isWebWorker = callee.type === 'Identifier' && (callee.name === 'Worker' || callee.name === 'SharedWorker') && args.length === 1 && types.isStringLiteral(args[0]);
87
88 if (isWebWorker) {
89 addURLDependency(asset, args[0], {
90 isolated: true
91 });
92 return;
93 }
94 }
95
96};
97
98function hasBinding(node, name) {
99 if (Array.isArray(node)) {
100 return node.some(ancestor => hasBinding(ancestor, name));
101 } else if (types.isProgram(node) || types.isBlockStatement(node) || types.isBlock(node)) {
102 return node.body.some(statement => hasBinding(statement, name));
103 } else if (types.isFunctionDeclaration(node) || types.isFunctionExpression(node) || types.isArrowFunctionExpression(node)) {
104 return node.id && node.id.name === name || node.params.some(param => types.isIdentifier(param) && param.name === name);
105 } else if (types.isVariableDeclaration(node)) {
106 return node.declarations.some(declaration => declaration.id.name === name);
107 }
108
109 return false;
110}
111
112function isInFalsyBranch(ancestors) {
113 // Check if any ancestors are if statements
114 return ancestors.some((node, index) => {
115 if (types.isIfStatement(node)) {
116 let res = evaluateExpression(node.test);
117
118 if (res && res.confident) {
119 // If the test is truthy, exclude the dep if it is in the alternate branch.
120 // If the test if falsy, exclude the dep if it is in the consequent branch.
121 let child = ancestors[index + 1];
122 return res.value ? child === node.alternate : child === node.consequent;
123 }
124 }
125 });
126}
127
128function evaluateExpression(node) {
129 // Wrap the node in a standalone program so we can traverse it
130 node = types.file(types.program([types.expressionStatement(node)])); // Find the first expression and evaluate it.
131
132 let res = null;
133 traverse(node, {
134 Expression(path) {
135 res = path.evaluate();
136 path.stop();
137 }
138
139 });
140 return res;
141}
142
143function addDependency(asset, node, opts = {}) {
144 // Don't bundle node builtins
145 if (asset.options.target === 'node' && node.value in nodeBuiltins) {
146 return;
147 } // If this came from an inline <script> tag, throw an error.
148 // TODO: run JSPackager on inline script tags.
149
150
151 let inlineHTML = asset.options.rendition && asset.options.rendition.inlineHTML;
152
153 if (inlineHTML) {
154 let err = new Error('Imports and requires are not supported inside inline <script> tags yet.');
155 err.loc = node.loc && node.loc.start;
156 throw err;
157 }
158
159 if (!asset.options.bundleNodeModules) {
160 const isRelativeImport = /^[/~.]/.test(node.value);
161 if (!isRelativeImport) return;
162 }
163
164 opts.loc = node.loc && node.loc.start;
165 asset.addDependency(node.value, opts);
166}
167
168function addURLDependency(asset, node, opts = {}) {
169 opts.loc = node.loc && node.loc.start;
170 let assetPath = asset.addURLDependency(node.value, opts);
171
172 if (!isURL(assetPath)) {
173 assetPath = urlJoin(asset.options.publicURL, assetPath);
174 }
175
176 node.value = assetPath;
177 asset.isAstDirty = true;
178}
\No newline at end of file