UNPKG

42.1 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const parseJson = require("json-parse-better-errors");
9const { getContext, runLoaders } = require("loader-runner");
10const querystring = require("querystring");
11const { HookMap, SyncHook, AsyncSeriesBailHook } = require("tapable");
12const {
13 CachedSource,
14 OriginalSource,
15 RawSource,
16 SourceMapSource
17} = require("webpack-sources");
18const Compilation = require("./Compilation");
19const HookWebpackError = require("./HookWebpackError");
20const Module = require("./Module");
21const ModuleBuildError = require("./ModuleBuildError");
22const ModuleError = require("./ModuleError");
23const ModuleGraphConnection = require("./ModuleGraphConnection");
24const ModuleParseError = require("./ModuleParseError");
25const ModuleWarning = require("./ModuleWarning");
26const RuntimeGlobals = require("./RuntimeGlobals");
27const UnhandledSchemeError = require("./UnhandledSchemeError");
28const WebpackError = require("./WebpackError");
29const formatLocation = require("./formatLocation");
30const LazySet = require("./util/LazySet");
31const { isSubset } = require("./util/SetHelpers");
32const { getScheme } = require("./util/URLAbsoluteSpecifier");
33const {
34 compareLocations,
35 concatComparators,
36 compareSelect,
37 keepOriginalOrder
38} = require("./util/comparators");
39const createHash = require("./util/createHash");
40const { createFakeHook } = require("./util/deprecation");
41const { join } = require("./util/fs");
42const {
43 contextify,
44 absolutify,
45 makePathsRelative
46} = require("./util/identifier");
47const makeSerializable = require("./util/makeSerializable");
48const memoize = require("./util/memoize");
49
50/** @typedef {import("webpack-sources").Source} Source */
51/** @typedef {import("../declarations/LoaderContext").NormalModuleLoaderContext} NormalModuleLoaderContext */
52/** @typedef {import("../declarations/WebpackOptions").Mode} Mode */
53/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */
54/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
55/** @typedef {import("./ChunkGraph")} ChunkGraph */
56/** @typedef {import("./Compiler")} Compiler */
57/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
58/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
59/** @typedef {import("./Generator")} Generator */
60/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */
61/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
62/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
63/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */
64/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */
65/** @typedef {import("./ModuleGraph")} ModuleGraph */
66/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */
67/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */
68/** @typedef {import("./Parser")} Parser */
69/** @typedef {import("./RequestShortener")} RequestShortener */
70/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
71/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
72/** @typedef {import("./logging/Logger").Logger} WebpackLogger */
73/** @typedef {import("./util/Hash")} Hash */
74/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
75/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
76
77/**
78 * @typedef {Object} SourceMap
79 * @property {number} version
80 * @property {string[]} sources
81 * @property {string} mappings
82 * @property {string=} file
83 * @property {string=} sourceRoot
84 * @property {string[]=} sourcesContent
85 * @property {string[]=} names
86 */
87
88const getInvalidDependenciesModuleWarning = memoize(() =>
89 require("./InvalidDependenciesModuleWarning")
90);
91const getValidate = memoize(() => require("schema-utils").validate);
92
93const ABSOLUTE_PATH_REGEX = /^([a-zA-Z]:\\|\\\\|\/)/;
94
95/**
96 * @typedef {Object} LoaderItem
97 * @property {string} loader
98 * @property {any} options
99 * @property {string?} ident
100 * @property {string?} type
101 */
102
103/**
104 * @param {string} context absolute context path
105 * @param {string} source a source path
106 * @param {Object=} associatedObjectForCache an object to which the cache will be attached
107 * @returns {string} new source path
108 */
109const contextifySourceUrl = (context, source, associatedObjectForCache) => {
110 if (source.startsWith("webpack://")) return source;
111 return `webpack://${makePathsRelative(
112 context,
113 source,
114 associatedObjectForCache
115 )}`;
116};
117
118/**
119 * @param {string} context absolute context path
120 * @param {SourceMap} sourceMap a source map
121 * @param {Object=} associatedObjectForCache an object to which the cache will be attached
122 * @returns {SourceMap} new source map
123 */
124const contextifySourceMap = (context, sourceMap, associatedObjectForCache) => {
125 if (!Array.isArray(sourceMap.sources)) return sourceMap;
126 const { sourceRoot } = sourceMap;
127 /** @type {function(string): string} */
128 const mapper = !sourceRoot
129 ? source => source
130 : sourceRoot.endsWith("/")
131 ? source =>
132 source.startsWith("/")
133 ? `${sourceRoot.slice(0, -1)}${source}`
134 : `${sourceRoot}${source}`
135 : source =>
136 source.startsWith("/")
137 ? `${sourceRoot}${source}`
138 : `${sourceRoot}/${source}`;
139 const newSources = sourceMap.sources.map(source =>
140 contextifySourceUrl(context, mapper(source), associatedObjectForCache)
141 );
142 return {
143 ...sourceMap,
144 file: "x",
145 sourceRoot: undefined,
146 sources: newSources
147 };
148};
149
150/**
151 * @param {string | Buffer} input the input
152 * @returns {string} the converted string
153 */
154const asString = input => {
155 if (Buffer.isBuffer(input)) {
156 return input.toString("utf-8");
157 }
158 return input;
159};
160
161/**
162 * @param {string | Buffer} input the input
163 * @returns {Buffer} the converted buffer
164 */
165const asBuffer = input => {
166 if (!Buffer.isBuffer(input)) {
167 return Buffer.from(input, "utf-8");
168 }
169 return input;
170};
171
172class NonErrorEmittedError extends WebpackError {
173 constructor(error) {
174 super();
175
176 this.name = "NonErrorEmittedError";
177 this.message = "(Emitted value instead of an instance of Error) " + error;
178 }
179}
180
181makeSerializable(
182 NonErrorEmittedError,
183 "webpack/lib/NormalModule",
184 "NonErrorEmittedError"
185);
186
187/**
188 * @typedef {Object} NormalModuleCompilationHooks
189 * @property {SyncHook<[object, NormalModule]>} loader
190 * @property {SyncHook<[LoaderItem[], NormalModule, object]>} beforeLoaders
191 * @property {SyncHook<[NormalModule]>} beforeParse
192 * @property {SyncHook<[NormalModule]>} beforeSnapshot
193 * @property {HookMap<AsyncSeriesBailHook<[string, NormalModule], string | Buffer>>} readResourceForScheme
194 * @property {HookMap<AsyncSeriesBailHook<[object], string | Buffer>>} readResource
195 * @property {AsyncSeriesBailHook<[NormalModule, NeedBuildContext], boolean>} needBuild
196 */
197
198/**
199 * @typedef {Object} NormalModuleCreateData
200 * @property {string=} layer an optional layer in which the module is
201 * @property {string} type module type
202 * @property {string} request request string
203 * @property {string} userRequest request intended by user (without loaders from config)
204 * @property {string} rawRequest request without resolving
205 * @property {LoaderItem[]} loaders list of loaders
206 * @property {string} resource path + query of the real resource
207 * @property {Record<string, any>=} resourceResolveData resource resolve data
208 * @property {string} context context directory for resolving
209 * @property {string=} matchResource path + query of the matched resource (virtual)
210 * @property {Parser} parser the parser used
211 * @property {Record<string, any>=} parserOptions the options of the parser used
212 * @property {Generator} generator the generator used
213 * @property {Record<string, any>=} generatorOptions the options of the generator used
214 * @property {ResolveOptions=} resolveOptions options used for resolving requests from this module
215 */
216
217/** @type {WeakMap<Compilation, NormalModuleCompilationHooks>} */
218const compilationHooksMap = new WeakMap();
219
220class NormalModule extends Module {
221 /**
222 * @param {Compilation} compilation the compilation
223 * @returns {NormalModuleCompilationHooks} the attached hooks
224 */
225 static getCompilationHooks(compilation) {
226 if (!(compilation instanceof Compilation)) {
227 throw new TypeError(
228 "The 'compilation' argument must be an instance of Compilation"
229 );
230 }
231 let hooks = compilationHooksMap.get(compilation);
232 if (hooks === undefined) {
233 hooks = {
234 loader: new SyncHook(["loaderContext", "module"]),
235 beforeLoaders: new SyncHook(["loaders", "module", "loaderContext"]),
236 beforeParse: new SyncHook(["module"]),
237 beforeSnapshot: new SyncHook(["module"]),
238 // TODO webpack 6 deprecate
239 readResourceForScheme: new HookMap(scheme => {
240 const hook = hooks.readResource.for(scheme);
241 return createFakeHook(
242 /** @type {AsyncSeriesBailHook<[string, NormalModule], string | Buffer>} */ ({
243 tap: (options, fn) =>
244 hook.tap(options, loaderContext =>
245 fn(loaderContext.resource, loaderContext._module)
246 ),
247 tapAsync: (options, fn) =>
248 hook.tapAsync(options, (loaderContext, callback) =>
249 fn(loaderContext.resource, loaderContext._module, callback)
250 ),
251 tapPromise: (options, fn) =>
252 hook.tapPromise(options, loaderContext =>
253 fn(loaderContext.resource, loaderContext._module)
254 )
255 })
256 );
257 }),
258 readResource: new HookMap(
259 () => new AsyncSeriesBailHook(["loaderContext"])
260 ),
261 needBuild: new AsyncSeriesBailHook(["module", "context"])
262 };
263 compilationHooksMap.set(compilation, hooks);
264 }
265 return hooks;
266 }
267
268 /**
269 * @param {NormalModuleCreateData} options options object
270 */
271 constructor({
272 layer,
273 type,
274 request,
275 userRequest,
276 rawRequest,
277 loaders,
278 resource,
279 resourceResolveData,
280 context,
281 matchResource,
282 parser,
283 parserOptions,
284 generator,
285 generatorOptions,
286 resolveOptions
287 }) {
288 super(type, context || getContext(resource), layer);
289
290 // Info from Factory
291 /** @type {string} */
292 this.request = request;
293 /** @type {string} */
294 this.userRequest = userRequest;
295 /** @type {string} */
296 this.rawRequest = rawRequest;
297 /** @type {boolean} */
298 this.binary = /^(asset|webassembly)\b/.test(type);
299 /** @type {Parser} */
300 this.parser = parser;
301 this.parserOptions = parserOptions;
302 /** @type {Generator} */
303 this.generator = generator;
304 this.generatorOptions = generatorOptions;
305 /** @type {string} */
306 this.resource = resource;
307 this.resourceResolveData = resourceResolveData;
308 /** @type {string | undefined} */
309 this.matchResource = matchResource;
310 /** @type {LoaderItem[]} */
311 this.loaders = loaders;
312 if (resolveOptions !== undefined) {
313 // already declared in super class
314 this.resolveOptions = resolveOptions;
315 }
316
317 // Info from Build
318 /** @type {(WebpackError | null)=} */
319 this.error = null;
320 /** @private @type {Source=} */
321 this._source = null;
322 /** @private @type {Map<string, number> | undefined} **/
323 this._sourceSizes = undefined;
324 /** @private @type {Set<string>} */
325 this._sourceTypes = undefined;
326
327 // Cache
328 this._lastSuccessfulBuildMeta = {};
329 this._forceBuild = true;
330 this._isEvaluatingSideEffects = false;
331 /** @type {WeakSet<ModuleGraph> | undefined} */
332 this._addedSideEffectsBailout = undefined;
333 }
334
335 /**
336 * @returns {string} a unique identifier of the module
337 */
338 identifier() {
339 if (this.layer === null) {
340 if (this.type === "javascript/auto") {
341 return this.request;
342 } else {
343 return `${this.type}|${this.request}`;
344 }
345 } else {
346 return `${this.type}|${this.request}|${this.layer}`;
347 }
348 }
349
350 /**
351 * @param {RequestShortener} requestShortener the request shortener
352 * @returns {string} a user readable identifier of the module
353 */
354 readableIdentifier(requestShortener) {
355 return requestShortener.shorten(this.userRequest);
356 }
357
358 /**
359 * @param {LibIdentOptions} options options
360 * @returns {string | null} an identifier for library inclusion
361 */
362 libIdent(options) {
363 let ident = contextify(
364 options.context,
365 this.userRequest,
366 options.associatedObjectForCache
367 );
368 if (this.layer) ident = `(${this.layer})/${ident}`;
369 return ident;
370 }
371
372 /**
373 * @returns {string | null} absolute path which should be used for condition matching (usually the resource path)
374 */
375 nameForCondition() {
376 const resource = this.matchResource || this.resource;
377 const idx = resource.indexOf("?");
378 if (idx >= 0) return resource.slice(0, idx);
379 return resource;
380 }
381
382 /**
383 * Assuming this module is in the cache. Update the (cached) module with
384 * the fresh module from the factory. Usually updates internal references
385 * and properties.
386 * @param {Module} module fresh module
387 * @returns {void}
388 */
389 updateCacheModule(module) {
390 super.updateCacheModule(module);
391 const m = /** @type {NormalModule} */ (module);
392 this.binary = m.binary;
393 this.request = m.request;
394 this.userRequest = m.userRequest;
395 this.rawRequest = m.rawRequest;
396 this.parser = m.parser;
397 this.parserOptions = m.parserOptions;
398 this.generator = m.generator;
399 this.generatorOptions = m.generatorOptions;
400 this.resource = m.resource;
401 this.resourceResolveData = m.resourceResolveData;
402 this.context = m.context;
403 this.matchResource = m.matchResource;
404 this.loaders = m.loaders;
405 }
406
407 /**
408 * Assuming this module is in the cache. Remove internal references to allow freeing some memory.
409 */
410 cleanupForCache() {
411 // Make sure to cache types and sizes before cleanup when this module has been built
412 // They are accessed by the stats and we don't want them to crash after cleanup
413 // TODO reconsider this for webpack 6
414 if (this.buildInfo) {
415 if (this._sourceTypes === undefined) this.getSourceTypes();
416 for (const type of this._sourceTypes) {
417 this.size(type);
418 }
419 }
420 super.cleanupForCache();
421 this.parser = undefined;
422 this.parserOptions = undefined;
423 this.generator = undefined;
424 this.generatorOptions = undefined;
425 }
426
427 /**
428 * Module should be unsafe cached. Get data that's needed for that.
429 * This data will be passed to restoreFromUnsafeCache later.
430 * @returns {object} cached data
431 */
432 getUnsafeCacheData() {
433 const data = super.getUnsafeCacheData();
434 data.parserOptions = this.parserOptions;
435 data.generatorOptions = this.generatorOptions;
436 return data;
437 }
438
439 restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory) {
440 this._restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory);
441 }
442
443 /**
444 * restore unsafe cache data
445 * @param {object} unsafeCacheData data from getUnsafeCacheData
446 * @param {NormalModuleFactory} normalModuleFactory the normal module factory handling the unsafe caching
447 */
448 _restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory) {
449 super._restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory);
450 this.parserOptions = unsafeCacheData.parserOptions;
451 this.parser = normalModuleFactory.getParser(this.type, this.parserOptions);
452 this.generatorOptions = unsafeCacheData.generatorOptions;
453 this.generator = normalModuleFactory.getGenerator(
454 this.type,
455 this.generatorOptions
456 );
457 // we assume the generator behaves identically and keep cached sourceTypes/Sizes
458 }
459
460 /**
461 * @param {string} context the compilation context
462 * @param {string} name the asset name
463 * @param {string} content the content
464 * @param {string | TODO} sourceMap an optional source map
465 * @param {Object=} associatedObjectForCache object for caching
466 * @returns {Source} the created source
467 */
468 createSourceForAsset(
469 context,
470 name,
471 content,
472 sourceMap,
473 associatedObjectForCache
474 ) {
475 if (sourceMap) {
476 if (
477 typeof sourceMap === "string" &&
478 (this.useSourceMap || this.useSimpleSourceMap)
479 ) {
480 return new OriginalSource(
481 content,
482 contextifySourceUrl(context, sourceMap, associatedObjectForCache)
483 );
484 }
485
486 if (this.useSourceMap) {
487 return new SourceMapSource(
488 content,
489 name,
490 contextifySourceMap(context, sourceMap, associatedObjectForCache)
491 );
492 }
493 }
494
495 return new RawSource(content);
496 }
497
498 /**
499 * @param {ResolverWithOptions} resolver a resolver
500 * @param {WebpackOptions} options webpack options
501 * @param {Compilation} compilation the compilation
502 * @param {InputFileSystem} fs file system from reading
503 * @param {NormalModuleCompilationHooks} hooks the hooks
504 * @returns {NormalModuleLoaderContext} loader context
505 */
506 _createLoaderContext(resolver, options, compilation, fs, hooks) {
507 const { requestShortener } = compilation.runtimeTemplate;
508 const getCurrentLoaderName = () => {
509 const currentLoader = this.getCurrentLoader(loaderContext);
510 if (!currentLoader) return "(not in loader scope)";
511 return requestShortener.shorten(currentLoader.loader);
512 };
513 const getResolveContext = () => {
514 return {
515 fileDependencies: {
516 add: d => loaderContext.addDependency(d)
517 },
518 contextDependencies: {
519 add: d => loaderContext.addContextDependency(d)
520 },
521 missingDependencies: {
522 add: d => loaderContext.addMissingDependency(d)
523 }
524 };
525 };
526 const getAbsolutify = memoize(() =>
527 absolutify.bindCache(compilation.compiler.root)
528 );
529 const getAbsolutifyInContext = memoize(() =>
530 absolutify.bindContextCache(this.context, compilation.compiler.root)
531 );
532 const getContextify = memoize(() =>
533 contextify.bindCache(compilation.compiler.root)
534 );
535 const getContextifyInContext = memoize(() =>
536 contextify.bindContextCache(this.context, compilation.compiler.root)
537 );
538 const utils = {
539 absolutify: (context, request) => {
540 return context === this.context
541 ? getAbsolutifyInContext()(request)
542 : getAbsolutify()(context, request);
543 },
544 contextify: (context, request) => {
545 return context === this.context
546 ? getContextifyInContext()(request)
547 : getContextify()(context, request);
548 },
549 createHash: type => {
550 return createHash(type || compilation.outputOptions.hashFunction);
551 }
552 };
553 const loaderContext = {
554 version: 2,
555 getOptions: schema => {
556 const loader = this.getCurrentLoader(loaderContext);
557
558 let { options } = loader;
559
560 if (typeof options === "string") {
561 if (options.startsWith("{") && options.endsWith("}")) {
562 try {
563 options = parseJson(options);
564 } catch (e) {
565 throw new Error(`Cannot parse string options: ${e.message}`);
566 }
567 } else {
568 options = querystring.parse(options, "&", "=", {
569 maxKeys: 0
570 });
571 }
572 }
573
574 if (options === null || options === undefined) {
575 options = {};
576 }
577
578 if (schema) {
579 let name = "Loader";
580 let baseDataPath = "options";
581 let match;
582 if (schema.title && (match = /^(.+) (.+)$/.exec(schema.title))) {
583 [, name, baseDataPath] = match;
584 }
585 getValidate()(schema, options, {
586 name,
587 baseDataPath
588 });
589 }
590
591 return options;
592 },
593 emitWarning: warning => {
594 if (!(warning instanceof Error)) {
595 warning = new NonErrorEmittedError(warning);
596 }
597 this.addWarning(
598 new ModuleWarning(warning, {
599 from: getCurrentLoaderName()
600 })
601 );
602 },
603 emitError: error => {
604 if (!(error instanceof Error)) {
605 error = new NonErrorEmittedError(error);
606 }
607 this.addError(
608 new ModuleError(error, {
609 from: getCurrentLoaderName()
610 })
611 );
612 },
613 getLogger: name => {
614 const currentLoader = this.getCurrentLoader(loaderContext);
615 return compilation.getLogger(() =>
616 [currentLoader && currentLoader.loader, name, this.identifier()]
617 .filter(Boolean)
618 .join("|")
619 );
620 },
621 resolve(context, request, callback) {
622 resolver.resolve({}, context, request, getResolveContext(), callback);
623 },
624 getResolve(options) {
625 const child = options ? resolver.withOptions(options) : resolver;
626 return (context, request, callback) => {
627 if (callback) {
628 child.resolve({}, context, request, getResolveContext(), callback);
629 } else {
630 return new Promise((resolve, reject) => {
631 child.resolve(
632 {},
633 context,
634 request,
635 getResolveContext(),
636 (err, result) => {
637 if (err) reject(err);
638 else resolve(result);
639 }
640 );
641 });
642 }
643 };
644 },
645 emitFile: (name, content, sourceMap, assetInfo) => {
646 if (!this.buildInfo.assets) {
647 this.buildInfo.assets = Object.create(null);
648 this.buildInfo.assetsInfo = new Map();
649 }
650 this.buildInfo.assets[name] = this.createSourceForAsset(
651 options.context,
652 name,
653 content,
654 sourceMap,
655 compilation.compiler.root
656 );
657 this.buildInfo.assetsInfo.set(name, assetInfo);
658 },
659 addBuildDependency: dep => {
660 if (this.buildInfo.buildDependencies === undefined) {
661 this.buildInfo.buildDependencies = new LazySet();
662 }
663 this.buildInfo.buildDependencies.add(dep);
664 },
665 utils,
666 rootContext: options.context,
667 webpack: true,
668 sourceMap: !!this.useSourceMap,
669 mode: options.mode || "production",
670 _module: this,
671 _compilation: compilation,
672 _compiler: compilation.compiler,
673 fs: fs
674 };
675
676 Object.assign(loaderContext, options.loader);
677
678 hooks.loader.call(loaderContext, this);
679
680 return loaderContext;
681 }
682
683 getCurrentLoader(loaderContext, index = loaderContext.loaderIndex) {
684 if (
685 this.loaders &&
686 this.loaders.length &&
687 index < this.loaders.length &&
688 index >= 0 &&
689 this.loaders[index]
690 ) {
691 return this.loaders[index];
692 }
693 return null;
694 }
695
696 /**
697 * @param {string} context the compilation context
698 * @param {string | Buffer} content the content
699 * @param {string | TODO} sourceMap an optional source map
700 * @param {Object=} associatedObjectForCache object for caching
701 * @returns {Source} the created source
702 */
703 createSource(context, content, sourceMap, associatedObjectForCache) {
704 if (Buffer.isBuffer(content)) {
705 return new RawSource(content);
706 }
707
708 // if there is no identifier return raw source
709 if (!this.identifier) {
710 return new RawSource(content);
711 }
712
713 // from here on we assume we have an identifier
714 const identifier = this.identifier();
715
716 if (this.useSourceMap && sourceMap) {
717 return new SourceMapSource(
718 content,
719 contextifySourceUrl(context, identifier, associatedObjectForCache),
720 contextifySourceMap(context, sourceMap, associatedObjectForCache)
721 );
722 }
723
724 if (this.useSourceMap || this.useSimpleSourceMap) {
725 return new OriginalSource(
726 content,
727 contextifySourceUrl(context, identifier, associatedObjectForCache)
728 );
729 }
730
731 return new RawSource(content);
732 }
733
734 /**
735 * @param {WebpackOptions} options webpack options
736 * @param {Compilation} compilation the compilation
737 * @param {ResolverWithOptions} resolver the resolver
738 * @param {InputFileSystem} fs the file system
739 * @param {NormalModuleCompilationHooks} hooks the hooks
740 * @param {function((WebpackError | null)=): void} callback callback function
741 * @returns {void}
742 */
743 _doBuild(options, compilation, resolver, fs, hooks, callback) {
744 const loaderContext = this._createLoaderContext(
745 resolver,
746 options,
747 compilation,
748 fs,
749 hooks
750 );
751
752 const processResult = (err, result) => {
753 if (err) {
754 if (!(err instanceof Error)) {
755 err = new NonErrorEmittedError(err);
756 }
757 const currentLoader = this.getCurrentLoader(loaderContext);
758 const error = new ModuleBuildError(err, {
759 from:
760 currentLoader &&
761 compilation.runtimeTemplate.requestShortener.shorten(
762 currentLoader.loader
763 )
764 });
765 return callback(error);
766 }
767
768 const source = result[0];
769 const sourceMap = result.length >= 1 ? result[1] : null;
770 const extraInfo = result.length >= 2 ? result[2] : null;
771
772 if (!Buffer.isBuffer(source) && typeof source !== "string") {
773 const currentLoader = this.getCurrentLoader(loaderContext, 0);
774 const err = new Error(
775 `Final loader (${
776 currentLoader
777 ? compilation.runtimeTemplate.requestShortener.shorten(
778 currentLoader.loader
779 )
780 : "unknown"
781 }) didn't return a Buffer or String`
782 );
783 const error = new ModuleBuildError(err);
784 return callback(error);
785 }
786
787 this._source = this.createSource(
788 options.context,
789 this.binary ? asBuffer(source) : asString(source),
790 sourceMap,
791 compilation.compiler.root
792 );
793 if (this._sourceSizes !== undefined) this._sourceSizes.clear();
794 this._ast =
795 typeof extraInfo === "object" &&
796 extraInfo !== null &&
797 extraInfo.webpackAST !== undefined
798 ? extraInfo.webpackAST
799 : null;
800 return callback();
801 };
802
803 this.buildInfo.fileDependencies = new LazySet();
804 this.buildInfo.contextDependencies = new LazySet();
805 this.buildInfo.missingDependencies = new LazySet();
806 this.buildInfo.cacheable = true;
807
808 try {
809 hooks.beforeLoaders.call(this.loaders, this, loaderContext);
810 } catch (err) {
811 processResult(err);
812 return;
813 }
814
815 if (this.loaders.length > 0) {
816 this.buildInfo.buildDependencies = new LazySet();
817 }
818
819 runLoaders(
820 {
821 resource: this.resource,
822 loaders: this.loaders,
823 context: loaderContext,
824 processResource: (loaderContext, resourcePath, callback) => {
825 const resource = loaderContext.resource;
826 const scheme = getScheme(resource);
827 hooks.readResource
828 .for(scheme)
829 .callAsync(loaderContext, (err, result) => {
830 if (err) return callback(err);
831 if (typeof result !== "string" && !result) {
832 return callback(new UnhandledSchemeError(scheme, resource));
833 }
834 return callback(null, result);
835 });
836 }
837 },
838 (err, result) => {
839 // Cleanup loaderContext to avoid leaking memory in ICs
840 loaderContext._compilation =
841 loaderContext._compiler =
842 loaderContext._module =
843 loaderContext.fs =
844 undefined;
845
846 if (!result) {
847 this.buildInfo.cacheable = false;
848 return processResult(
849 err || new Error("No result from loader-runner processing"),
850 null
851 );
852 }
853 this.buildInfo.fileDependencies.addAll(result.fileDependencies);
854 this.buildInfo.contextDependencies.addAll(result.contextDependencies);
855 this.buildInfo.missingDependencies.addAll(result.missingDependencies);
856 for (const loader of this.loaders) {
857 this.buildInfo.buildDependencies.add(loader.loader);
858 }
859 this.buildInfo.cacheable = this.buildInfo.cacheable && result.cacheable;
860 processResult(err, result.result);
861 }
862 );
863 }
864
865 /**
866 * @param {WebpackError} error the error
867 * @returns {void}
868 */
869 markModuleAsErrored(error) {
870 // Restore build meta from successful build to keep importing state
871 this.buildMeta = { ...this._lastSuccessfulBuildMeta };
872 this.error = error;
873 this.addError(error);
874 }
875
876 applyNoParseRule(rule, content) {
877 // must start with "rule" if rule is a string
878 if (typeof rule === "string") {
879 return content.startsWith(rule);
880 }
881
882 if (typeof rule === "function") {
883 return rule(content);
884 }
885 // we assume rule is a regexp
886 return rule.test(content);
887 }
888
889 // check if module should not be parsed
890 // returns "true" if the module should !not! be parsed
891 // returns "false" if the module !must! be parsed
892 shouldPreventParsing(noParseRule, request) {
893 // if no noParseRule exists, return false
894 // the module !must! be parsed.
895 if (!noParseRule) {
896 return false;
897 }
898
899 // we only have one rule to check
900 if (!Array.isArray(noParseRule)) {
901 // returns "true" if the module is !not! to be parsed
902 return this.applyNoParseRule(noParseRule, request);
903 }
904
905 for (let i = 0; i < noParseRule.length; i++) {
906 const rule = noParseRule[i];
907 // early exit on first truthy match
908 // this module is !not! to be parsed
909 if (this.applyNoParseRule(rule, request)) {
910 return true;
911 }
912 }
913 // no match found, so this module !should! be parsed
914 return false;
915 }
916
917 _initBuildHash(compilation) {
918 const hash = createHash(compilation.outputOptions.hashFunction);
919 if (this._source) {
920 hash.update("source");
921 this._source.updateHash(hash);
922 }
923 hash.update("meta");
924 hash.update(JSON.stringify(this.buildMeta));
925 this.buildInfo.hash = /** @type {string} */ (hash.digest("hex"));
926 }
927
928 /**
929 * @param {WebpackOptions} options webpack options
930 * @param {Compilation} compilation the compilation
931 * @param {ResolverWithOptions} resolver the resolver
932 * @param {InputFileSystem} fs the file system
933 * @param {function(WebpackError=): void} callback callback function
934 * @returns {void}
935 */
936 build(options, compilation, resolver, fs, callback) {
937 this._forceBuild = false;
938 this._source = null;
939 if (this._sourceSizes !== undefined) this._sourceSizes.clear();
940 this._sourceTypes = undefined;
941 this._ast = null;
942 this.error = null;
943 this.clearWarningsAndErrors();
944 this.clearDependenciesAndBlocks();
945 this.buildMeta = {};
946 this.buildInfo = {
947 cacheable: false,
948 parsed: true,
949 fileDependencies: undefined,
950 contextDependencies: undefined,
951 missingDependencies: undefined,
952 buildDependencies: undefined,
953 valueDependencies: undefined,
954 hash: undefined,
955 assets: undefined,
956 assetsInfo: undefined
957 };
958
959 const startTime = compilation.compiler.fsStartTime || Date.now();
960
961 const hooks = NormalModule.getCompilationHooks(compilation);
962
963 return this._doBuild(options, compilation, resolver, fs, hooks, err => {
964 // if we have an error mark module as failed and exit
965 if (err) {
966 this.markModuleAsErrored(err);
967 this._initBuildHash(compilation);
968 return callback();
969 }
970
971 const handleParseError = e => {
972 const source = this._source.source();
973 const loaders = this.loaders.map(item =>
974 contextify(options.context, item.loader, compilation.compiler.root)
975 );
976 const error = new ModuleParseError(source, e, loaders, this.type);
977 this.markModuleAsErrored(error);
978 this._initBuildHash(compilation);
979 return callback();
980 };
981
982 const handleParseResult = result => {
983 this.dependencies.sort(
984 concatComparators(
985 compareSelect(a => a.loc, compareLocations),
986 keepOriginalOrder(this.dependencies)
987 )
988 );
989 this._initBuildHash(compilation);
990 this._lastSuccessfulBuildMeta = this.buildMeta;
991 return handleBuildDone();
992 };
993
994 const handleBuildDone = () => {
995 try {
996 hooks.beforeSnapshot.call(this);
997 } catch (err) {
998 this.markModuleAsErrored(err);
999 return callback();
1000 }
1001
1002 const snapshotOptions = compilation.options.snapshot.module;
1003 if (!this.buildInfo.cacheable || !snapshotOptions) {
1004 return callback();
1005 }
1006 // add warning for all non-absolute paths in fileDependencies, etc
1007 // This makes it easier to find problems with watching and/or caching
1008 let nonAbsoluteDependencies = undefined;
1009 const checkDependencies = deps => {
1010 for (const dep of deps) {
1011 if (!ABSOLUTE_PATH_REGEX.test(dep)) {
1012 if (nonAbsoluteDependencies === undefined)
1013 nonAbsoluteDependencies = new Set();
1014 nonAbsoluteDependencies.add(dep);
1015 deps.delete(dep);
1016 try {
1017 const depWithoutGlob = dep.replace(/[\\/]?\*.*$/, "");
1018 const absolute = join(
1019 compilation.fileSystemInfo.fs,
1020 this.context,
1021 depWithoutGlob
1022 );
1023 if (absolute !== dep && ABSOLUTE_PATH_REGEX.test(absolute)) {
1024 (depWithoutGlob !== dep
1025 ? this.buildInfo.contextDependencies
1026 : deps
1027 ).add(absolute);
1028 }
1029 } catch (e) {
1030 // ignore
1031 }
1032 }
1033 }
1034 };
1035 checkDependencies(this.buildInfo.fileDependencies);
1036 checkDependencies(this.buildInfo.missingDependencies);
1037 checkDependencies(this.buildInfo.contextDependencies);
1038 if (nonAbsoluteDependencies !== undefined) {
1039 const InvalidDependenciesModuleWarning =
1040 getInvalidDependenciesModuleWarning();
1041 this.addWarning(
1042 new InvalidDependenciesModuleWarning(this, nonAbsoluteDependencies)
1043 );
1044 }
1045 // convert file/context/missingDependencies into filesystem snapshot
1046 compilation.fileSystemInfo.createSnapshot(
1047 startTime,
1048 this.buildInfo.fileDependencies,
1049 this.buildInfo.contextDependencies,
1050 this.buildInfo.missingDependencies,
1051 snapshotOptions,
1052 (err, snapshot) => {
1053 if (err) {
1054 this.markModuleAsErrored(err);
1055 return;
1056 }
1057 this.buildInfo.fileDependencies = undefined;
1058 this.buildInfo.contextDependencies = undefined;
1059 this.buildInfo.missingDependencies = undefined;
1060 this.buildInfo.snapshot = snapshot;
1061 return callback();
1062 }
1063 );
1064 };
1065
1066 try {
1067 hooks.beforeParse.call(this);
1068 } catch (err) {
1069 this.markModuleAsErrored(err);
1070 this._initBuildHash(compilation);
1071 return callback();
1072 }
1073
1074 // check if this module should !not! be parsed.
1075 // if so, exit here;
1076 const noParseRule = options.module && options.module.noParse;
1077 if (this.shouldPreventParsing(noParseRule, this.request)) {
1078 // We assume that we need module and exports
1079 this.buildInfo.parsed = false;
1080 this._initBuildHash(compilation);
1081 return handleBuildDone();
1082 }
1083
1084 let result;
1085 try {
1086 const source = this._source.source();
1087 result = this.parser.parse(this._ast || source, {
1088 source,
1089 current: this,
1090 module: this,
1091 compilation: compilation,
1092 options: options
1093 });
1094 } catch (e) {
1095 handleParseError(e);
1096 return;
1097 }
1098 handleParseResult(result);
1099 });
1100 }
1101
1102 /**
1103 * @param {ConcatenationBailoutReasonContext} context context
1104 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
1105 */
1106 getConcatenationBailoutReason(context) {
1107 return this.generator.getConcatenationBailoutReason(this, context);
1108 }
1109
1110 /**
1111 * @param {ModuleGraph} moduleGraph the module graph
1112 * @returns {ConnectionState} how this module should be connected to referencing modules when consumed for side-effects only
1113 */
1114 getSideEffectsConnectionState(moduleGraph) {
1115 if (this.factoryMeta !== undefined) {
1116 if (this.factoryMeta.sideEffectFree) return false;
1117 if (this.factoryMeta.sideEffectFree === false) return true;
1118 }
1119 if (this.buildMeta !== undefined && this.buildMeta.sideEffectFree) {
1120 if (this._isEvaluatingSideEffects)
1121 return ModuleGraphConnection.CIRCULAR_CONNECTION;
1122 this._isEvaluatingSideEffects = true;
1123 /** @type {ConnectionState} */
1124 let current = false;
1125 for (const dep of this.dependencies) {
1126 const state = dep.getModuleEvaluationSideEffectsState(moduleGraph);
1127 if (state === true) {
1128 if (
1129 this._addedSideEffectsBailout === undefined
1130 ? ((this._addedSideEffectsBailout = new WeakSet()), true)
1131 : !this._addedSideEffectsBailout.has(moduleGraph)
1132 ) {
1133 this._addedSideEffectsBailout.add(moduleGraph);
1134 moduleGraph
1135 .getOptimizationBailout(this)
1136 .push(
1137 () =>
1138 `Dependency (${
1139 dep.type
1140 }) with side effects at ${formatLocation(dep.loc)}`
1141 );
1142 }
1143 this._isEvaluatingSideEffects = false;
1144 return true;
1145 } else if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) {
1146 current = ModuleGraphConnection.addConnectionStates(current, state);
1147 }
1148 }
1149 this._isEvaluatingSideEffects = false;
1150 // When caching is implemented here, make sure to not cache when
1151 // at least one circular connection was in the loop above
1152 return current;
1153 } else {
1154 return true;
1155 }
1156 }
1157
1158 /**
1159 * @returns {Set<string>} types available (do not mutate)
1160 */
1161 getSourceTypes() {
1162 if (this._sourceTypes === undefined) {
1163 this._sourceTypes = this.generator.getTypes(this);
1164 }
1165 return this._sourceTypes;
1166 }
1167
1168 /**
1169 * @param {CodeGenerationContext} context context for code generation
1170 * @returns {CodeGenerationResult} result
1171 */
1172 codeGeneration({
1173 dependencyTemplates,
1174 runtimeTemplate,
1175 moduleGraph,
1176 chunkGraph,
1177 runtime,
1178 concatenationScope,
1179 codeGenerationResults,
1180 sourceTypes
1181 }) {
1182 /** @type {Set<string>} */
1183 const runtimeRequirements = new Set();
1184
1185 if (!this.buildInfo.parsed) {
1186 runtimeRequirements.add(RuntimeGlobals.module);
1187 runtimeRequirements.add(RuntimeGlobals.exports);
1188 runtimeRequirements.add(RuntimeGlobals.thisAsExports);
1189 }
1190
1191 /** @type {Map<string, any>} */
1192 let data;
1193 const getData = () => {
1194 if (data === undefined) data = new Map();
1195 return data;
1196 };
1197
1198 const sources = new Map();
1199 for (const type of sourceTypes || chunkGraph.getModuleSourceTypes(this)) {
1200 const source = this.error
1201 ? new RawSource(
1202 "throw new Error(" + JSON.stringify(this.error.message) + ");"
1203 )
1204 : this.generator.generate(this, {
1205 dependencyTemplates,
1206 runtimeTemplate,
1207 moduleGraph,
1208 chunkGraph,
1209 runtimeRequirements,
1210 runtime,
1211 concatenationScope,
1212 codeGenerationResults,
1213 getData,
1214 type
1215 });
1216
1217 if (source) {
1218 sources.set(type, new CachedSource(source));
1219 }
1220 }
1221
1222 /** @type {CodeGenerationResult} */
1223 const resultEntry = {
1224 sources,
1225 runtimeRequirements,
1226 data
1227 };
1228 return resultEntry;
1229 }
1230
1231 /**
1232 * @returns {Source | null} the original source for the module before webpack transformation
1233 */
1234 originalSource() {
1235 return this._source;
1236 }
1237
1238 /**
1239 * @returns {void}
1240 */
1241 invalidateBuild() {
1242 this._forceBuild = true;
1243 }
1244
1245 /**
1246 * @param {NeedBuildContext} context context info
1247 * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild
1248 * @returns {void}
1249 */
1250 needBuild(context, callback) {
1251 const { fileSystemInfo, compilation, valueCacheVersions } = context;
1252 // build if enforced
1253 if (this._forceBuild) return callback(null, true);
1254
1255 // always try to build in case of an error
1256 if (this.error) return callback(null, true);
1257
1258 // always build when module is not cacheable
1259 if (!this.buildInfo.cacheable) return callback(null, true);
1260
1261 // build when there is no snapshot to check
1262 if (!this.buildInfo.snapshot) return callback(null, true);
1263
1264 // build when valueDependencies have changed
1265 /** @type {Map<string, string | Set<string>>} */
1266 const valueDependencies = this.buildInfo.valueDependencies;
1267 if (valueDependencies) {
1268 if (!valueCacheVersions) return callback(null, true);
1269 for (const [key, value] of valueDependencies) {
1270 if (value === undefined) return callback(null, true);
1271 const current = valueCacheVersions.get(key);
1272 if (
1273 value !== current &&
1274 (typeof value === "string" ||
1275 typeof current === "string" ||
1276 current === undefined ||
1277 !isSubset(value, current))
1278 ) {
1279 return callback(null, true);
1280 }
1281 }
1282 }
1283
1284 // check snapshot for validity
1285 fileSystemInfo.checkSnapshotValid(this.buildInfo.snapshot, (err, valid) => {
1286 if (err) return callback(err);
1287 if (!valid) return callback(null, true);
1288 const hooks = NormalModule.getCompilationHooks(compilation);
1289 hooks.needBuild.callAsync(this, context, (err, needBuild) => {
1290 if (err) {
1291 return callback(
1292 HookWebpackError.makeWebpackError(
1293 err,
1294 "NormalModule.getCompilationHooks().needBuild"
1295 )
1296 );
1297 }
1298 callback(null, !!needBuild);
1299 });
1300 });
1301 }
1302
1303 /**
1304 * @param {string=} type the source type for which the size should be estimated
1305 * @returns {number} the estimated size of the module (must be non-zero)
1306 */
1307 size(type) {
1308 const cachedSize =
1309 this._sourceSizes === undefined ? undefined : this._sourceSizes.get(type);
1310 if (cachedSize !== undefined) {
1311 return cachedSize;
1312 }
1313 const size = Math.max(1, this.generator.getSize(this, type));
1314 if (this._sourceSizes === undefined) {
1315 this._sourceSizes = new Map();
1316 }
1317 this._sourceSizes.set(type, size);
1318 return size;
1319 }
1320
1321 /**
1322 * @param {LazySet<string>} fileDependencies set where file dependencies are added to
1323 * @param {LazySet<string>} contextDependencies set where context dependencies are added to
1324 * @param {LazySet<string>} missingDependencies set where missing dependencies are added to
1325 * @param {LazySet<string>} buildDependencies set where build dependencies are added to
1326 */
1327 addCacheDependencies(
1328 fileDependencies,
1329 contextDependencies,
1330 missingDependencies,
1331 buildDependencies
1332 ) {
1333 const { snapshot, buildDependencies: buildDeps } = this.buildInfo;
1334 if (snapshot) {
1335 fileDependencies.addAll(snapshot.getFileIterable());
1336 contextDependencies.addAll(snapshot.getContextIterable());
1337 missingDependencies.addAll(snapshot.getMissingIterable());
1338 } else {
1339 const {
1340 fileDependencies: fileDeps,
1341 contextDependencies: contextDeps,
1342 missingDependencies: missingDeps
1343 } = this.buildInfo;
1344 if (fileDeps !== undefined) fileDependencies.addAll(fileDeps);
1345 if (contextDeps !== undefined) contextDependencies.addAll(contextDeps);
1346 if (missingDeps !== undefined) missingDependencies.addAll(missingDeps);
1347 }
1348 if (buildDeps !== undefined) {
1349 buildDependencies.addAll(buildDeps);
1350 }
1351 }
1352
1353 /**
1354 * @param {Hash} hash the hash used to track dependencies
1355 * @param {UpdateHashContext} context context
1356 * @returns {void}
1357 */
1358 updateHash(hash, context) {
1359 hash.update(this.buildInfo.hash);
1360 this.generator.updateHash(hash, {
1361 module: this,
1362 ...context
1363 });
1364 super.updateHash(hash, context);
1365 }
1366
1367 serialize(context) {
1368 const { write } = context;
1369 // deserialize
1370 write(this._source);
1371 write(this.error);
1372 write(this._lastSuccessfulBuildMeta);
1373 write(this._forceBuild);
1374 super.serialize(context);
1375 }
1376
1377 static deserialize(context) {
1378 const obj = new NormalModule({
1379 // will be deserialized by Module
1380 layer: null,
1381 type: "",
1382 // will be filled by updateCacheModule
1383 resource: "",
1384 context: "",
1385 request: null,
1386 userRequest: null,
1387 rawRequest: null,
1388 loaders: null,
1389 matchResource: null,
1390 parser: null,
1391 parserOptions: null,
1392 generator: null,
1393 generatorOptions: null,
1394 resolveOptions: null
1395 });
1396 obj.deserialize(context);
1397 return obj;
1398 }
1399
1400 deserialize(context) {
1401 const { read } = context;
1402 this._source = read();
1403 this.error = read();
1404 this._lastSuccessfulBuildMeta = read();
1405 this._forceBuild = read();
1406 super.deserialize(context);
1407 }
1408}
1409
1410makeSerializable(NormalModule, "webpack/lib/NormalModule");
1411
1412module.exports = NormalModule;