summaryrefslogtreecommitdiffstats
path: root/node_modules/webpack/lib/NormalModule.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/webpack/lib/NormalModule.js')
-rw-r--r--node_modules/webpack/lib/NormalModule.js582
1 files changed, 582 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/NormalModule.js b/node_modules/webpack/lib/NormalModule.js
new file mode 100644
index 0000000..76aa1bd
--- /dev/null
+++ b/node_modules/webpack/lib/NormalModule.js
@@ -0,0 +1,582 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const NativeModule = require("module");
+
+const {
+ CachedSource,
+ LineToLineMappedSource,
+ OriginalSource,
+ RawSource,
+ SourceMapSource
+} = require("webpack-sources");
+const { getContext, runLoaders } = require("loader-runner");
+
+const WebpackError = require("./WebpackError");
+const Module = require("./Module");
+const ModuleParseError = require("./ModuleParseError");
+const ModuleBuildError = require("./ModuleBuildError");
+const ModuleError = require("./ModuleError");
+const ModuleWarning = require("./ModuleWarning");
+const createHash = require("./util/createHash");
+const contextify = require("./util/identifier").contextify;
+
+/** @typedef {import("./util/createHash").Hash} Hash */
+
+const asString = buf => {
+ if (Buffer.isBuffer(buf)) {
+ return buf.toString("utf-8");
+ }
+ return buf;
+};
+
+const asBuffer = str => {
+ if (!Buffer.isBuffer(str)) {
+ return Buffer.from(str, "utf-8");
+ }
+ return str;
+};
+
+class NonErrorEmittedError extends WebpackError {
+ constructor(error) {
+ super();
+
+ this.name = "NonErrorEmittedError";
+ this.message = "(Emitted value instead of an instance of Error) " + error;
+
+ Error.captureStackTrace(this, this.constructor);
+ }
+}
+
+/**
+ * @typedef {Object} CachedSourceEntry
+ * @property {TODO} source the generated source
+ * @property {string} hash the hash value
+ */
+
+class NormalModule extends Module {
+ constructor({
+ type,
+ request,
+ userRequest,
+ rawRequest,
+ loaders,
+ resource,
+ matchResource,
+ parser,
+ generator,
+ resolveOptions
+ }) {
+ super(type, getContext(resource));
+
+ // Info from Factory
+ this.request = request;
+ this.userRequest = userRequest;
+ this.rawRequest = rawRequest;
+ this.binary = type.startsWith("webassembly");
+ this.parser = parser;
+ this.generator = generator;
+ this.resource = resource;
+ this.matchResource = matchResource;
+ this.loaders = loaders;
+ if (resolveOptions !== undefined) this.resolveOptions = resolveOptions;
+
+ // Info from Build
+ this.error = null;
+ this._source = null;
+ this._sourceSize = null;
+ this._buildHash = "";
+ this.buildTimestamp = undefined;
+ /** @private @type {Map<string, CachedSourceEntry>} */
+ this._cachedSources = new Map();
+
+ // Options for the NormalModule set by plugins
+ // TODO refactor this -> options object filled from Factory
+ this.useSourceMap = false;
+ this.lineToLine = false;
+
+ // Cache
+ this._lastSuccessfulBuildMeta = {};
+ }
+
+ identifier() {
+ return this.request;
+ }
+
+ readableIdentifier(requestShortener) {
+ return requestShortener.shorten(this.userRequest);
+ }
+
+ libIdent(options) {
+ return contextify(options.context, this.userRequest);
+ }
+
+ nameForCondition() {
+ const resource = this.matchResource || this.resource;
+ const idx = resource.indexOf("?");
+ if (idx >= 0) return resource.substr(0, idx);
+ return resource;
+ }
+
+ updateCacheModule(module) {
+ this.type = module.type;
+ this.request = module.request;
+ this.userRequest = module.userRequest;
+ this.rawRequest = module.rawRequest;
+ this.parser = module.parser;
+ this.generator = module.generator;
+ this.resource = module.resource;
+ this.matchResource = module.matchResource;
+ this.loaders = module.loaders;
+ this.resolveOptions = module.resolveOptions;
+ }
+
+ createSourceForAsset(name, content, sourceMap) {
+ if (!sourceMap) {
+ return new RawSource(content);
+ }
+
+ if (typeof sourceMap === "string") {
+ return new OriginalSource(content, sourceMap);
+ }
+
+ return new SourceMapSource(content, name, sourceMap);
+ }
+
+ createLoaderContext(resolver, options, compilation, fs) {
+ const requestShortener = compilation.runtimeTemplate.requestShortener;
+ const getCurrentLoaderName = () => {
+ const currentLoader = this.getCurrentLoader(loaderContext);
+ if (!currentLoader) return "(not in loader scope)";
+ return requestShortener.shorten(currentLoader.loader);
+ };
+ const loaderContext = {
+ version: 2,
+ emitWarning: warning => {
+ if (!(warning instanceof Error)) {
+ warning = new NonErrorEmittedError(warning);
+ }
+ this.warnings.push(
+ new ModuleWarning(this, warning, {
+ from: getCurrentLoaderName()
+ })
+ );
+ },
+ emitError: error => {
+ if (!(error instanceof Error)) {
+ error = new NonErrorEmittedError(error);
+ }
+ this.errors.push(
+ new ModuleError(this, error, {
+ from: getCurrentLoaderName()
+ })
+ );
+ },
+ getLogger: name => {
+ const currentLoader = this.getCurrentLoader(loaderContext);
+ return compilation.getLogger(() =>
+ [currentLoader && currentLoader.loader, name, this.identifier()]
+ .filter(Boolean)
+ .join("|")
+ );
+ },
+ // TODO remove in webpack 5
+ exec: (code, filename) => {
+ // @ts-ignore Argument of type 'this' is not assignable to parameter of type 'Module'.
+ const module = new NativeModule(filename, this);
+ // @ts-ignore _nodeModulePaths is deprecated and undocumented Node.js API
+ module.paths = NativeModule._nodeModulePaths(this.context);
+ module.filename = filename;
+ module._compile(code, filename);
+ return module.exports;
+ },
+ resolve(context, request, callback) {
+ resolver.resolve({}, context, request, {}, callback);
+ },
+ getResolve(options) {
+ const child = options ? resolver.withOptions(options) : resolver;
+ return (context, request, callback) => {
+ if (callback) {
+ child.resolve({}, context, request, {}, callback);
+ } else {
+ return new Promise((resolve, reject) => {
+ child.resolve({}, context, request, {}, (err, result) => {
+ if (err) reject(err);
+ else resolve(result);
+ });
+ });
+ }
+ };
+ },
+ emitFile: (name, content, sourceMap, assetInfo) => {
+ if (!this.buildInfo.assets) {
+ this.buildInfo.assets = Object.create(null);
+ this.buildInfo.assetsInfo = new Map();
+ }
+ this.buildInfo.assets[name] = this.createSourceForAsset(
+ name,
+ content,
+ sourceMap
+ );
+ this.buildInfo.assetsInfo.set(name, assetInfo);
+ },
+ rootContext: options.context,
+ webpack: true,
+ sourceMap: !!this.useSourceMap,
+ mode: options.mode || "production",
+ _module: this,
+ _compilation: compilation,
+ _compiler: compilation.compiler,
+ fs: fs
+ };
+
+ compilation.hooks.normalModuleLoader.call(loaderContext, this);
+ if (options.loader) {
+ Object.assign(loaderContext, options.loader);
+ }
+
+ return loaderContext;
+ }
+
+ getCurrentLoader(loaderContext, index = loaderContext.loaderIndex) {
+ if (
+ this.loaders &&
+ this.loaders.length &&
+ index < this.loaders.length &&
+ index >= 0 &&
+ this.loaders[index]
+ ) {
+ return this.loaders[index];
+ }
+ return null;
+ }
+
+ createSource(source, resourceBuffer, sourceMap) {
+ // if there is no identifier return raw source
+ if (!this.identifier) {
+ return new RawSource(source);
+ }
+
+ // from here on we assume we have an identifier
+ const identifier = this.identifier();
+
+ if (this.lineToLine && resourceBuffer) {
+ return new LineToLineMappedSource(
+ source,
+ identifier,
+ asString(resourceBuffer)
+ );
+ }
+
+ if (this.useSourceMap && sourceMap) {
+ return new SourceMapSource(source, identifier, sourceMap);
+ }
+
+ if (Buffer.isBuffer(source)) {
+ // @ts-ignore
+ // TODO We need to fix @types/webpack-sources to allow RawSource to take a Buffer | string
+ return new RawSource(source);
+ }
+
+ return new OriginalSource(source, identifier);
+ }
+
+ doBuild(options, compilation, resolver, fs, callback) {
+ const loaderContext = this.createLoaderContext(
+ resolver,
+ options,
+ compilation,
+ fs
+ );
+
+ runLoaders(
+ {
+ resource: this.resource,
+ loaders: this.loaders,
+ context: loaderContext,
+ readResource: fs.readFile.bind(fs)
+ },
+ (err, result) => {
+ if (result) {
+ this.buildInfo.cacheable = result.cacheable;
+ this.buildInfo.fileDependencies = new Set(result.fileDependencies);
+ this.buildInfo.contextDependencies = new Set(
+ result.contextDependencies
+ );
+ }
+
+ if (err) {
+ if (!(err instanceof Error)) {
+ err = new NonErrorEmittedError(err);
+ }
+ const currentLoader = this.getCurrentLoader(loaderContext);
+ const error = new ModuleBuildError(this, err, {
+ from:
+ currentLoader &&
+ compilation.runtimeTemplate.requestShortener.shorten(
+ currentLoader.loader
+ )
+ });
+ return callback(error);
+ }
+
+ const resourceBuffer = result.resourceBuffer;
+ const source = result.result[0];
+ const sourceMap = result.result.length >= 1 ? result.result[1] : null;
+ const extraInfo = result.result.length >= 2 ? result.result[2] : null;
+
+ if (!Buffer.isBuffer(source) && typeof source !== "string") {
+ const currentLoader = this.getCurrentLoader(loaderContext, 0);
+ const err = new Error(
+ `Final loader (${
+ currentLoader
+ ? compilation.runtimeTemplate.requestShortener.shorten(
+ currentLoader.loader
+ )
+ : "unknown"
+ }) didn't return a Buffer or String`
+ );
+ const error = new ModuleBuildError(this, err);
+ return callback(error);
+ }
+
+ this._source = this.createSource(
+ this.binary ? asBuffer(source) : asString(source),
+ resourceBuffer,
+ sourceMap
+ );
+ this._sourceSize = null;
+ this._ast =
+ typeof extraInfo === "object" &&
+ extraInfo !== null &&
+ extraInfo.webpackAST !== undefined
+ ? extraInfo.webpackAST
+ : null;
+ return callback();
+ }
+ );
+ }
+
+ markModuleAsErrored(error) {
+ // Restore build meta from successful build to keep importing state
+ this.buildMeta = Object.assign({}, this._lastSuccessfulBuildMeta);
+ this.error = error;
+ this.errors.push(this.error);
+ this._source = new RawSource(
+ "throw new Error(" + JSON.stringify(this.error.message) + ");"
+ );
+ this._sourceSize = null;
+ this._ast = null;
+ }
+
+ applyNoParseRule(rule, content) {
+ // must start with "rule" if rule is a string
+ if (typeof rule === "string") {
+ return content.indexOf(rule) === 0;
+ }
+
+ if (typeof rule === "function") {
+ return rule(content);
+ }
+ // we assume rule is a regexp
+ return rule.test(content);
+ }
+
+ // check if module should not be parsed
+ // returns "true" if the module should !not! be parsed
+ // returns "false" if the module !must! be parsed
+ shouldPreventParsing(noParseRule, request) {
+ // if no noParseRule exists, return false
+ // the module !must! be parsed.
+ if (!noParseRule) {
+ return false;
+ }
+
+ // we only have one rule to check
+ if (!Array.isArray(noParseRule)) {
+ // returns "true" if the module is !not! to be parsed
+ return this.applyNoParseRule(noParseRule, request);
+ }
+
+ for (let i = 0; i < noParseRule.length; i++) {
+ const rule = noParseRule[i];
+ // early exit on first truthy match
+ // this module is !not! to be parsed
+ if (this.applyNoParseRule(rule, request)) {
+ return true;
+ }
+ }
+ // no match found, so this module !should! be parsed
+ return false;
+ }
+
+ _initBuildHash(compilation) {
+ const hash = createHash(compilation.outputOptions.hashFunction);
+ if (this._source) {
+ hash.update("source");
+ this._source.updateHash(hash);
+ }
+ hash.update("meta");
+ hash.update(JSON.stringify(this.buildMeta));
+ this._buildHash = /** @type {string} */ (hash.digest("hex"));
+ }
+
+ build(options, compilation, resolver, fs, callback) {
+ this.buildTimestamp = Date.now();
+ this.built = true;
+ this._source = null;
+ this._sourceSize = null;
+ this._ast = null;
+ this._buildHash = "";
+ this.error = null;
+ this.errors.length = 0;
+ this.warnings.length = 0;
+ this.buildMeta = {};
+ this.buildInfo = {
+ cacheable: false,
+ fileDependencies: new Set(),
+ contextDependencies: new Set(),
+ assets: undefined,
+ assetsInfo: undefined
+ };
+
+ return this.doBuild(options, compilation, resolver, fs, err => {
+ this._cachedSources.clear();
+
+ // if we have an error mark module as failed and exit
+ if (err) {
+ this.markModuleAsErrored(err);
+ this._initBuildHash(compilation);
+ return callback();
+ }
+
+ // check if this module should !not! be parsed.
+ // if so, exit here;
+ const noParseRule = options.module && options.module.noParse;
+ if (this.shouldPreventParsing(noParseRule, this.request)) {
+ this._initBuildHash(compilation);
+ return callback();
+ }
+
+ const handleParseError = e => {
+ const source = this._source.source();
+ const loaders = this.loaders.map(item =>
+ contextify(options.context, item.loader)
+ );
+ const error = new ModuleParseError(this, source, e, loaders);
+ this.markModuleAsErrored(error);
+ this._initBuildHash(compilation);
+ return callback();
+ };
+
+ const handleParseResult = result => {
+ this._lastSuccessfulBuildMeta = this.buildMeta;
+ this._initBuildHash(compilation);
+ return callback();
+ };
+
+ try {
+ const result = this.parser.parse(
+ this._ast || this._source.source(),
+ {
+ current: this,
+ module: this,
+ compilation: compilation,
+ options: options
+ },
+ (err, result) => {
+ if (err) {
+ handleParseError(err);
+ } else {
+ handleParseResult(result);
+ }
+ }
+ );
+ if (result !== undefined) {
+ // parse is sync
+ handleParseResult(result);
+ }
+ } catch (e) {
+ handleParseError(e);
+ }
+ });
+ }
+
+ getHashDigest(dependencyTemplates) {
+ // TODO webpack 5 refactor
+ let dtHash = dependencyTemplates.get("hash");
+ return `${this.hash}-${dtHash}`;
+ }
+
+ source(dependencyTemplates, runtimeTemplate, type = "javascript") {
+ const hashDigest = this.getHashDigest(dependencyTemplates);
+ const cacheEntry = this._cachedSources.get(type);
+ if (cacheEntry !== undefined && cacheEntry.hash === hashDigest) {
+ // We can reuse the cached source
+ return cacheEntry.source;
+ }
+
+ const source = this.generator.generate(
+ this,
+ dependencyTemplates,
+ runtimeTemplate,
+ type
+ );
+
+ const cachedSource = new CachedSource(source);
+ this._cachedSources.set(type, {
+ source: cachedSource,
+ hash: hashDigest
+ });
+ return cachedSource;
+ }
+
+ originalSource() {
+ return this._source;
+ }
+
+ needRebuild(fileTimestamps, contextTimestamps) {
+ // always try to rebuild in case of an error
+ if (this.error) return true;
+
+ // always rebuild when module is not cacheable
+ if (!this.buildInfo.cacheable) return true;
+
+ // Check timestamps of all dependencies
+ // Missing timestamp -> need rebuild
+ // Timestamp bigger than buildTimestamp -> need rebuild
+ for (const file of this.buildInfo.fileDependencies) {
+ const timestamp = fileTimestamps.get(file);
+ if (!timestamp) return true;
+ if (timestamp >= this.buildTimestamp) return true;
+ }
+ for (const file of this.buildInfo.contextDependencies) {
+ const timestamp = contextTimestamps.get(file);
+ if (!timestamp) return true;
+ if (timestamp >= this.buildTimestamp) return true;
+ }
+ // elsewise -> no rebuild needed
+ return false;
+ }
+
+ size() {
+ if (this._sourceSize === null) {
+ this._sourceSize = this._source ? this._source.size() : -1;
+ }
+ return this._sourceSize;
+ }
+
+ /**
+ * @param {Hash} hash the hash used to track dependencies
+ * @returns {void}
+ */
+ updateHash(hash) {
+ hash.update(this._buildHash);
+ super.updateHash(hash);
+ }
+}
+
+module.exports = NormalModule;