UNPKG

6.4 kBJavaScriptView Raw
1const traverse = require('@babel/traverse').default;
2const codeFrame = require('@babel/code-frame').codeFrameColumns;
3const collectDependencies = require('../visitors/dependencies');
4const walk = require('babylon-walk');
5const Asset = require('../Asset');
6const babelParser = require('@babel/parser');
7const insertGlobals = require('../visitors/globals');
8const fsVisitor = require('../visitors/fs');
9const envVisitor = require('../visitors/env');
10const processVisitor = require('../visitors/process');
11const babel = require('../transforms/babel/transform');
12const babel7 = require('../transforms/babel/babel7');
13const generate = require('@babel/generator').default;
14const terser = require('../transforms/terser');
15const SourceMap = require('../SourceMap');
16const hoist = require('../scope-hoisting/hoist');
17const loadSourceMap = require('../utils/loadSourceMap');
18const isAccessedVarChanged = require('../utils/isAccessedVarChanged');
19
20const IMPORT_RE = /\b(?:import\b|export\b|require\s*\()/;
21const ENV_RE = /\b(?:process\.env)\b/;
22const BROWSER_RE = /\b(?:process\.browser)\b/;
23const GLOBAL_RE = /\b(?:process|__dirname|__filename|global|Buffer|define)\b/;
24const FS_RE = /\breadFileSync\b/;
25const SW_RE = /\bnavigator\s*\.\s*serviceWorker\s*\.\s*register\s*\(/;
26const WORKER_RE = /\bnew\s*(?:Shared)?Worker\s*\(/;
27
28class JSAsset extends Asset {
29 constructor(name, options) {
30 super(name, options);
31 this.type = 'js';
32 this.globals = new Map();
33 this.isAstDirty = false;
34 this.isES6Module = false;
35 this.outputCode = null;
36 this.cacheData.env = {};
37 this.rendition = options.rendition;
38 this.sourceMap = this.rendition ? this.rendition.map : null;
39 }
40
41 shouldInvalidate(cacheData) {
42 return isAccessedVarChanged(cacheData);
43 }
44
45 mightHaveDependencies() {
46 return (
47 this.isAstDirty ||
48 !/\.js$/.test(this.name) ||
49 IMPORT_RE.test(this.contents) ||
50 GLOBAL_RE.test(this.contents) ||
51 SW_RE.test(this.contents) ||
52 WORKER_RE.test(this.contents)
53 );
54 }
55
56 async parse(code) {
57 return babelParser.parse(code, {
58 filename: this.name,
59 allowReturnOutsideFunction: true,
60 strictMode: false,
61 sourceType: 'module',
62 plugins: ['exportDefaultFrom', 'exportNamespaceFrom', 'dynamicImport']
63 });
64 }
65
66 traverse(visitor) {
67 return traverse(this.ast, visitor, null, this);
68 }
69
70 traverseFast(visitor) {
71 return walk.simple(this.ast, visitor, this);
72 }
73
74 collectDependencies() {
75 walk.ancestor(this.ast, collectDependencies, this);
76 }
77
78 async pretransform() {
79 if (this.options.sourceMaps && !this.sourceMap) {
80 this.sourceMap = await loadSourceMap(this);
81 }
82
83 await babel(this);
84
85 // Inline environment variables
86 if (this.options.target === 'browser' && ENV_RE.test(this.contents)) {
87 await this.parseIfNeeded();
88 this.traverseFast(envVisitor);
89 }
90
91 // Inline process.browser
92 if (this.options.target === 'browser' && BROWSER_RE.test(this.contents)) {
93 await this.parseIfNeeded();
94 this.traverse(processVisitor);
95 this.isAstDirty = true;
96 }
97 }
98
99 async transform() {
100 if (this.options.target === 'browser') {
101 if (this.dependencies.has('fs') && FS_RE.test(this.contents)) {
102 // Check if we should ignore fs calls
103 // See https://github.com/defunctzombie/node-browser-resolve#skip
104 let pkg = await this.getPackage();
105 let ignore = pkg && pkg.browser && pkg.browser.fs === false;
106
107 if (!ignore) {
108 await this.parseIfNeeded();
109 this.traverse(fsVisitor);
110 }
111 }
112
113 if (GLOBAL_RE.test(this.contents)) {
114 await this.parseIfNeeded();
115 walk.ancestor(this.ast, insertGlobals, this);
116 }
117 }
118
119 if (this.options.scopeHoist) {
120 await this.parseIfNeeded();
121 await this.getPackage();
122
123 this.traverse(hoist);
124 this.isAstDirty = true;
125 } else {
126 if (this.isES6Module) {
127 await babel7(this, {
128 internal: true,
129 config: {
130 plugins: [require('@babel/plugin-transform-modules-commonjs')]
131 }
132 });
133 }
134 }
135
136 if (this.options.minify) {
137 await terser(this);
138 }
139 }
140
141 async generate() {
142 let code;
143 if (this.isAstDirty) {
144 let opts = {
145 sourceMaps: this.options.sourceMaps,
146 sourceFileName: this.relativeName
147 };
148
149 let generated = generate(this.ast, opts, this.contents);
150
151 if (this.options.sourceMaps && generated.rawMappings) {
152 let rawMap = new SourceMap(generated.rawMappings, {
153 [this.relativeName]: this.contents
154 });
155
156 // Check if we already have a source map (e.g. from TypeScript or CoffeeScript)
157 // In that case, we need to map the original source map to the babel generated one.
158 if (this.sourceMap) {
159 this.sourceMap = await new SourceMap().extendSourceMap(
160 this.sourceMap,
161 rawMap
162 );
163 } else {
164 this.sourceMap = rawMap;
165 }
166 }
167
168 code = generated.code;
169 } else {
170 code = this.outputCode != null ? this.outputCode : this.contents;
171 }
172
173 if (this.options.sourceMaps && !this.sourceMap) {
174 this.sourceMap = new SourceMap().generateEmptyMap(
175 this.relativeName,
176 this.contents
177 );
178 }
179
180 if (this.globals.size > 0) {
181 code = Array.from(this.globals.values()).join('\n') + '\n' + code;
182 if (this.options.sourceMaps) {
183 if (!(this.sourceMap instanceof SourceMap)) {
184 this.sourceMap = await new SourceMap().addMap(this.sourceMap);
185 }
186
187 this.sourceMap.offset(this.globals.size);
188 }
189 }
190
191 return [
192 {
193 type: 'js',
194 value: code,
195 map: this.sourceMap
196 }
197 ];
198 }
199
200 generateErrorMessage(err) {
201 const loc = err.loc;
202 if (loc) {
203 // Babel 7 adds its own code frame on the error message itself
204 // We need to remove it and pass it separately.
205 if (err.message.startsWith(this.name)) {
206 err.message = err.message
207 .slice(this.name.length + 1, err.message.indexOf('\n'))
208 .trim();
209 }
210
211 err.codeFrame = codeFrame(this.contents, {start: loc});
212 err.highlightedCodeFrame = codeFrame(
213 this.contents,
214 {start: loc},
215 {highlightCode: true}
216 );
217 }
218
219 return err;
220 }
221}
222
223module.exports = JSAsset;