1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 | const NativeModule = require("module");
|
8 |
|
9 | const {
|
10 | CachedSource,
|
11 | LineToLineMappedSource,
|
12 | OriginalSource,
|
13 | RawSource,
|
14 | SourceMapSource
|
15 | } = require("webpack-sources");
|
16 | const { getContext, runLoaders } = require("loader-runner");
|
17 |
|
18 | const WebpackError = require("./WebpackError");
|
19 | const Module = require("./Module");
|
20 | const ModuleParseError = require("./ModuleParseError");
|
21 | const ModuleBuildError = require("./ModuleBuildError");
|
22 | const ModuleError = require("./ModuleError");
|
23 | const ModuleWarning = require("./ModuleWarning");
|
24 | const createHash = require("./util/createHash");
|
25 | const contextify = require("./util/identifier").contextify;
|
26 |
|
27 |
|
28 |
|
29 | const asString = buf => {
|
30 | if (Buffer.isBuffer(buf)) {
|
31 | return buf.toString("utf-8");
|
32 | }
|
33 | return buf;
|
34 | };
|
35 |
|
36 | const asBuffer = str => {
|
37 | if (!Buffer.isBuffer(str)) {
|
38 | return Buffer.from(str, "utf-8");
|
39 | }
|
40 | return str;
|
41 | };
|
42 |
|
43 | class NonErrorEmittedError extends WebpackError {
|
44 | constructor(error) {
|
45 | super();
|
46 |
|
47 | this.name = "NonErrorEmittedError";
|
48 | this.message = "(Emitted value instead of an instance of Error) " + error;
|
49 |
|
50 | Error.captureStackTrace(this, this.constructor);
|
51 | }
|
52 | }
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | class NormalModule extends Module {
|
61 | constructor({
|
62 | type,
|
63 | request,
|
64 | userRequest,
|
65 | rawRequest,
|
66 | loaders,
|
67 | resource,
|
68 | matchResource,
|
69 | parser,
|
70 | generator,
|
71 | resolveOptions
|
72 | }) {
|
73 | super(type, getContext(resource));
|
74 |
|
75 |
|
76 | this.request = request;
|
77 | this.userRequest = userRequest;
|
78 | this.rawRequest = rawRequest;
|
79 | this.binary = type.startsWith("webassembly");
|
80 | this.parser = parser;
|
81 | this.generator = generator;
|
82 | this.resource = resource;
|
83 | this.matchResource = matchResource;
|
84 | this.loaders = loaders;
|
85 | if (resolveOptions !== undefined) this.resolveOptions = resolveOptions;
|
86 |
|
87 |
|
88 | this.error = null;
|
89 | this._source = null;
|
90 | this._buildHash = "";
|
91 | this.buildTimestamp = undefined;
|
92 |
|
93 | this._cachedSources = new Map();
|
94 |
|
95 |
|
96 |
|
97 | this.useSourceMap = false;
|
98 | this.lineToLine = false;
|
99 |
|
100 |
|
101 | this._lastSuccessfulBuildMeta = {};
|
102 | }
|
103 |
|
104 | identifier() {
|
105 | return this.request;
|
106 | }
|
107 |
|
108 | readableIdentifier(requestShortener) {
|
109 | return requestShortener.shorten(this.userRequest);
|
110 | }
|
111 |
|
112 | libIdent(options) {
|
113 | return contextify(options.context, this.userRequest);
|
114 | }
|
115 |
|
116 | nameForCondition() {
|
117 | const resource = this.matchResource || this.resource;
|
118 | const idx = resource.indexOf("?");
|
119 | if (idx >= 0) return resource.substr(0, idx);
|
120 | return resource;
|
121 | }
|
122 |
|
123 | updateCacheModule(module) {
|
124 | this.type = module.type;
|
125 | this.request = module.request;
|
126 | this.userRequest = module.userRequest;
|
127 | this.rawRequest = module.rawRequest;
|
128 | this.parser = module.parser;
|
129 | this.generator = module.generator;
|
130 | this.resource = module.resource;
|
131 | this.matchResource = module.matchResource;
|
132 | this.loaders = module.loaders;
|
133 | this.resolveOptions = module.resolveOptions;
|
134 | }
|
135 |
|
136 | createSourceForAsset(name, content, sourceMap) {
|
137 | if (!sourceMap) {
|
138 | return new RawSource(content);
|
139 | }
|
140 |
|
141 | if (typeof sourceMap === "string") {
|
142 | return new OriginalSource(content, sourceMap);
|
143 | }
|
144 |
|
145 | return new SourceMapSource(content, name, sourceMap);
|
146 | }
|
147 |
|
148 | createLoaderContext(resolver, options, compilation, fs) {
|
149 | const requestShortener = compilation.runtimeTemplate.requestShortener;
|
150 | const getCurrentLoaderName = () => {
|
151 | const currentLoader = this.getCurrentLoader(loaderContext);
|
152 | if (!currentLoader) return "(not in loader scope)";
|
153 | return requestShortener.shorten(currentLoader.loader);
|
154 | };
|
155 | const loaderContext = {
|
156 | version: 2,
|
157 | emitWarning: warning => {
|
158 | if (!(warning instanceof Error)) {
|
159 | warning = new NonErrorEmittedError(warning);
|
160 | }
|
161 | this.warnings.push(
|
162 | new ModuleWarning(this, warning, {
|
163 | from: getCurrentLoaderName()
|
164 | })
|
165 | );
|
166 | },
|
167 | emitError: error => {
|
168 | if (!(error instanceof Error)) {
|
169 | error = new NonErrorEmittedError(error);
|
170 | }
|
171 | this.errors.push(
|
172 | new ModuleError(this, error, {
|
173 | from: getCurrentLoaderName()
|
174 | })
|
175 | );
|
176 | },
|
177 | getLogger: name => {
|
178 | const currentLoader = this.getCurrentLoader(loaderContext);
|
179 | return compilation.getLogger(() =>
|
180 | [currentLoader && currentLoader.loader, name, this.identifier()]
|
181 | .filter(Boolean)
|
182 | .join("|")
|
183 | );
|
184 | },
|
185 |
|
186 | exec: (code, filename) => {
|
187 |
|
188 | const module = new NativeModule(filename, this);
|
189 |
|
190 | module.paths = NativeModule._nodeModulePaths(this.context);
|
191 | module.filename = filename;
|
192 | module._compile(code, filename);
|
193 | return module.exports;
|
194 | },
|
195 | resolve(context, request, callback) {
|
196 | resolver.resolve({}, context, request, {}, callback);
|
197 | },
|
198 | getResolve(options) {
|
199 | const child = options ? resolver.withOptions(options) : resolver;
|
200 | return (context, request, callback) => {
|
201 | if (callback) {
|
202 | child.resolve({}, context, request, {}, callback);
|
203 | } else {
|
204 | return new Promise((resolve, reject) => {
|
205 | child.resolve({}, context, request, {}, (err, result) => {
|
206 | if (err) reject(err);
|
207 | else resolve(result);
|
208 | });
|
209 | });
|
210 | }
|
211 | };
|
212 | },
|
213 | emitFile: (name, content, sourceMap, assetInfo) => {
|
214 | if (!this.buildInfo.assets) {
|
215 | this.buildInfo.assets = Object.create(null);
|
216 | this.buildInfo.assetsInfo = new Map();
|
217 | }
|
218 | this.buildInfo.assets[name] = this.createSourceForAsset(
|
219 | name,
|
220 | content,
|
221 | sourceMap
|
222 | );
|
223 | this.buildInfo.assetsInfo.set(name, assetInfo);
|
224 | },
|
225 | rootContext: options.context,
|
226 | webpack: true,
|
227 | sourceMap: !!this.useSourceMap,
|
228 | mode: options.mode || "production",
|
229 | _module: this,
|
230 | _compilation: compilation,
|
231 | _compiler: compilation.compiler,
|
232 | fs: fs
|
233 | };
|
234 |
|
235 | compilation.hooks.normalModuleLoader.call(loaderContext, this);
|
236 | if (options.loader) {
|
237 | Object.assign(loaderContext, options.loader);
|
238 | }
|
239 |
|
240 | return loaderContext;
|
241 | }
|
242 |
|
243 | getCurrentLoader(loaderContext, index = loaderContext.loaderIndex) {
|
244 | if (
|
245 | this.loaders &&
|
246 | this.loaders.length &&
|
247 | index < this.loaders.length &&
|
248 | index >= 0 &&
|
249 | this.loaders[index]
|
250 | ) {
|
251 | return this.loaders[index];
|
252 | }
|
253 | return null;
|
254 | }
|
255 |
|
256 | createSource(source, resourceBuffer, sourceMap) {
|
257 |
|
258 | if (!this.identifier) {
|
259 | return new RawSource(source);
|
260 | }
|
261 |
|
262 |
|
263 | const identifier = this.identifier();
|
264 |
|
265 | if (this.lineToLine && resourceBuffer) {
|
266 | return new LineToLineMappedSource(
|
267 | source,
|
268 | identifier,
|
269 | asString(resourceBuffer)
|
270 | );
|
271 | }
|
272 |
|
273 | if (this.useSourceMap && sourceMap) {
|
274 | return new SourceMapSource(source, identifier, sourceMap);
|
275 | }
|
276 |
|
277 | if (Buffer.isBuffer(source)) {
|
278 |
|
279 |
|
280 | return new RawSource(source);
|
281 | }
|
282 |
|
283 | return new OriginalSource(source, identifier);
|
284 | }
|
285 |
|
286 | doBuild(options, compilation, resolver, fs, callback) {
|
287 | const loaderContext = this.createLoaderContext(
|
288 | resolver,
|
289 | options,
|
290 | compilation,
|
291 | fs
|
292 | );
|
293 |
|
294 | runLoaders(
|
295 | {
|
296 | resource: this.resource,
|
297 | loaders: this.loaders,
|
298 | context: loaderContext,
|
299 | readResource: fs.readFile.bind(fs)
|
300 | },
|
301 | (err, result) => {
|
302 | if (result) {
|
303 | this.buildInfo.cacheable = result.cacheable;
|
304 | this.buildInfo.fileDependencies = new Set(result.fileDependencies);
|
305 | this.buildInfo.contextDependencies = new Set(
|
306 | result.contextDependencies
|
307 | );
|
308 | }
|
309 |
|
310 | if (err) {
|
311 | if (!(err instanceof Error)) {
|
312 | err = new NonErrorEmittedError(err);
|
313 | }
|
314 | const currentLoader = this.getCurrentLoader(loaderContext);
|
315 | const error = new ModuleBuildError(this, err, {
|
316 | from:
|
317 | currentLoader &&
|
318 | compilation.runtimeTemplate.requestShortener.shorten(
|
319 | currentLoader.loader
|
320 | )
|
321 | });
|
322 | return callback(error);
|
323 | }
|
324 |
|
325 | const resourceBuffer = result.resourceBuffer;
|
326 | const source = result.result[0];
|
327 | const sourceMap = result.result.length >= 1 ? result.result[1] : null;
|
328 | const extraInfo = result.result.length >= 2 ? result.result[2] : null;
|
329 |
|
330 | if (!Buffer.isBuffer(source) && typeof source !== "string") {
|
331 | const currentLoader = this.getCurrentLoader(loaderContext, 0);
|
332 | const err = new Error(
|
333 | `Final loader (${
|
334 | currentLoader
|
335 | ? compilation.runtimeTemplate.requestShortener.shorten(
|
336 | currentLoader.loader
|
337 | )
|
338 | : "unknown"
|
339 | }) didn't return a Buffer or String`
|
340 | );
|
341 | const error = new ModuleBuildError(this, err);
|
342 | return callback(error);
|
343 | }
|
344 |
|
345 | this._source = this.createSource(
|
346 | this.binary ? asBuffer(source) : asString(source),
|
347 | resourceBuffer,
|
348 | sourceMap
|
349 | );
|
350 | this._ast =
|
351 | typeof extraInfo === "object" &&
|
352 | extraInfo !== null &&
|
353 | extraInfo.webpackAST !== undefined
|
354 | ? extraInfo.webpackAST
|
355 | : null;
|
356 | return callback();
|
357 | }
|
358 | );
|
359 | }
|
360 |
|
361 | markModuleAsErrored(error) {
|
362 |
|
363 | this.buildMeta = Object.assign({}, this._lastSuccessfulBuildMeta);
|
364 | this.error = error;
|
365 | this.errors.push(this.error);
|
366 | this._source = new RawSource(
|
367 | "throw new Error(" + JSON.stringify(this.error.message) + ");"
|
368 | );
|
369 | this._ast = null;
|
370 | }
|
371 |
|
372 | applyNoParseRule(rule, content) {
|
373 |
|
374 | if (typeof rule === "string") {
|
375 | return content.indexOf(rule) === 0;
|
376 | }
|
377 |
|
378 | if (typeof rule === "function") {
|
379 | return rule(content);
|
380 | }
|
381 |
|
382 | return rule.test(content);
|
383 | }
|
384 |
|
385 |
|
386 |
|
387 |
|
388 | shouldPreventParsing(noParseRule, request) {
|
389 |
|
390 |
|
391 | if (!noParseRule) {
|
392 | return false;
|
393 | }
|
394 |
|
395 |
|
396 | if (!Array.isArray(noParseRule)) {
|
397 |
|
398 | return this.applyNoParseRule(noParseRule, request);
|
399 | }
|
400 |
|
401 | for (let i = 0; i < noParseRule.length; i++) {
|
402 | const rule = noParseRule[i];
|
403 |
|
404 |
|
405 | if (this.applyNoParseRule(rule, request)) {
|
406 | return true;
|
407 | }
|
408 | }
|
409 |
|
410 | return false;
|
411 | }
|
412 |
|
413 | _initBuildHash(compilation) {
|
414 | const hash = createHash(compilation.outputOptions.hashFunction);
|
415 | if (this._source) {
|
416 | hash.update("source");
|
417 | this._source.updateHash(hash);
|
418 | }
|
419 | hash.update("meta");
|
420 | hash.update(JSON.stringify(this.buildMeta));
|
421 | this._buildHash = (hash.digest("hex"));
|
422 | }
|
423 |
|
424 | build(options, compilation, resolver, fs, callback) {
|
425 | this.buildTimestamp = Date.now();
|
426 | this.built = true;
|
427 | this._source = null;
|
428 | this._ast = null;
|
429 | this._buildHash = "";
|
430 | this.error = null;
|
431 | this.errors.length = 0;
|
432 | this.warnings.length = 0;
|
433 | this.buildMeta = {};
|
434 | this.buildInfo = {
|
435 | cacheable: false,
|
436 | fileDependencies: new Set(),
|
437 | contextDependencies: new Set(),
|
438 | assets: undefined,
|
439 | assetsInfo: undefined
|
440 | };
|
441 |
|
442 | return this.doBuild(options, compilation, resolver, fs, err => {
|
443 | this._cachedSources.clear();
|
444 |
|
445 |
|
446 | if (err) {
|
447 | this.markModuleAsErrored(err);
|
448 | this._initBuildHash(compilation);
|
449 | return callback();
|
450 | }
|
451 |
|
452 |
|
453 |
|
454 | const noParseRule = options.module && options.module.noParse;
|
455 | if (this.shouldPreventParsing(noParseRule, this.request)) {
|
456 | this._initBuildHash(compilation);
|
457 | return callback();
|
458 | }
|
459 |
|
460 | const handleParseError = e => {
|
461 | const source = this._source.source();
|
462 | const loaders = this.loaders.map(item =>
|
463 | contextify(options.context, item.loader)
|
464 | );
|
465 | const error = new ModuleParseError(this, source, e, loaders);
|
466 | this.markModuleAsErrored(error);
|
467 | this._initBuildHash(compilation);
|
468 | return callback();
|
469 | };
|
470 |
|
471 | const handleParseResult = result => {
|
472 | this._lastSuccessfulBuildMeta = this.buildMeta;
|
473 | this._initBuildHash(compilation);
|
474 | return callback();
|
475 | };
|
476 |
|
477 | try {
|
478 | const result = this.parser.parse(
|
479 | this._ast || this._source.source(),
|
480 | {
|
481 | current: this,
|
482 | module: this,
|
483 | compilation: compilation,
|
484 | options: options
|
485 | },
|
486 | (err, result) => {
|
487 | if (err) {
|
488 | handleParseError(err);
|
489 | } else {
|
490 | handleParseResult(result);
|
491 | }
|
492 | }
|
493 | );
|
494 | if (result !== undefined) {
|
495 |
|
496 | handleParseResult(result);
|
497 | }
|
498 | } catch (e) {
|
499 | handleParseError(e);
|
500 | }
|
501 | });
|
502 | }
|
503 |
|
504 | getHashDigest(dependencyTemplates) {
|
505 |
|
506 | let dtHash = dependencyTemplates.get("hash");
|
507 | return `${this.hash}-${dtHash}`;
|
508 | }
|
509 |
|
510 | source(dependencyTemplates, runtimeTemplate, type = "javascript") {
|
511 | const hashDigest = this.getHashDigest(dependencyTemplates);
|
512 | const cacheEntry = this._cachedSources.get(type);
|
513 | if (cacheEntry !== undefined && cacheEntry.hash === hashDigest) {
|
514 |
|
515 | return cacheEntry.source;
|
516 | }
|
517 |
|
518 | const source = this.generator.generate(
|
519 | this,
|
520 | dependencyTemplates,
|
521 | runtimeTemplate,
|
522 | type
|
523 | );
|
524 |
|
525 | const cachedSource = new CachedSource(source);
|
526 | this._cachedSources.set(type, {
|
527 | source: cachedSource,
|
528 | hash: hashDigest
|
529 | });
|
530 | return cachedSource;
|
531 | }
|
532 |
|
533 | originalSource() {
|
534 | return this._source;
|
535 | }
|
536 |
|
537 | needRebuild(fileTimestamps, contextTimestamps) {
|
538 |
|
539 | if (this.error) return true;
|
540 |
|
541 |
|
542 | if (!this.buildInfo.cacheable) return true;
|
543 |
|
544 |
|
545 |
|
546 |
|
547 | for (const file of this.buildInfo.fileDependencies) {
|
548 | const timestamp = fileTimestamps.get(file);
|
549 | if (!timestamp) return true;
|
550 | if (timestamp >= this.buildTimestamp) return true;
|
551 | }
|
552 | for (const file of this.buildInfo.contextDependencies) {
|
553 | const timestamp = contextTimestamps.get(file);
|
554 | if (!timestamp) return true;
|
555 | if (timestamp >= this.buildTimestamp) return true;
|
556 | }
|
557 |
|
558 | return false;
|
559 | }
|
560 |
|
561 | size() {
|
562 | return this._source ? this._source.size() : -1;
|
563 | }
|
564 |
|
565 | |
566 |
|
567 |
|
568 |
|
569 | updateHash(hash) {
|
570 | hash.update(this._buildHash);
|
571 | super.updateHash(hash);
|
572 | }
|
573 | }
|
574 |
|
575 | module.exports = NormalModule;
|