webpack/lib/NormalModuleFactory.js

321 lines
9.5 KiB
JavaScript
Raw Normal View History

2013-01-31 01:49:25 +08:00
/*
2017-04-04 05:28:08 +08:00
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
2017-04-05 21:06:23 +08:00
const asyncLib = require("async");
const Tapable = require("tapable-old");
2017-04-04 05:28:08 +08:00
const NormalModule = require("./NormalModule");
const RawModule = require("./RawModule");
const RuleSet = require("./RuleSet");
const cachedMerge = require("./util/cachedMerge");
const EMPTY_RESOLVE_OPTIONS = {};
2017-11-08 18:32:05 +08:00
const loaderToIdent = data => {
if(!data.options)
return data.loader;
if(typeof data.options === "string")
return data.loader + "?" + data.options;
if(typeof data.options !== "object")
throw new Error("loader options must be string or object");
if(data.ident)
return data.loader + "??" + data.ident;
return data.loader + "?" + JSON.stringify(data.options);
2017-11-08 18:32:05 +08:00
};
2013-01-31 01:49:25 +08:00
2017-11-08 18:32:05 +08:00
const identToLoaderRequest = resultString => {
2017-04-04 16:26:16 +08:00
const idx = resultString.indexOf("?");
2017-04-04 05:28:08 +08:00
let options;
if(idx >= 0) {
options = resultString.substr(idx + 1);
resultString = resultString.substr(0, idx);
return {
loader: resultString,
2017-04-04 05:28:08 +08:00
options
};
} else {
return {
loader: resultString
2017-01-11 17:51:58 +08:00
};
}
2017-11-08 18:32:05 +08:00
};
2017-04-04 05:28:08 +08:00
class NormalModuleFactory extends Tapable {
constructor(context, resolverFactory, options) {
2017-04-04 05:28:08 +08:00
super();
this.resolverFactory = resolverFactory;
this.ruleSet = new RuleSet(options.defaultRules.concat(options.rules));
2017-04-04 05:28:08 +08:00
this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache);
this.context = context || "";
this.parserCache = {};
this.plugin("factory", () => (result, callback) => {
let resolver = this.applyPluginsWaterfall0("resolver", null);
// Ignored
if(!resolver) return callback();
resolver(result, (err, data) => {
if(err) return callback(err);
// Ignored
if(!data) return callback();
// direct module
if(typeof data.source === "function")
return callback(null, data);
2016-02-10 02:32:50 +08:00
this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => {
if(err) return callback(err);
2015-05-17 00:27:59 +08:00
// Ignored
if(!result) return callback();
2015-05-17 00:27:59 +08:00
2017-08-08 15:29:46 +08:00
let createdModule = this.applyPluginsBailResult1("create-module", result);
if(!createdModule) {
2017-02-21 00:09:20 +08:00
if(!result.request) {
return callback(new Error("Empty dependency (no request)"));
}
2017-04-04 05:28:08 +08:00
createdModule = new NormalModule(
2017-10-30 20:56:57 +08:00
result.type,
result.request,
result.userRequest,
result.rawRequest,
result.loaders,
result.resource,
result.parser,
result.resolveOptions
2017-04-04 05:28:08 +08:00
);
}
2017-08-08 15:40:17 +08:00
createdModule = this.applyPluginsWaterfall1("module", createdModule, result);
return callback(null, createdModule);
});
});
});
this.plugin("resolver", () => (data, callback) => {
const contextInfo = data.contextInfo;
const context = data.context;
const request = data.request;
const noAutoLoaders = /^-?!/.test(request);
const noPrePostAutoLoaders = /^!!/.test(request);
const noPostAutoLoaders = /^-!/.test(request);
let elements = request.replace(/^-?!+/, "").replace(/!!+/g, "!").split("!");
let resource = elements.pop();
elements = elements.map(identToLoaderRequest);
const loaderResolver = this.getResolver("loader");
const normalResolver = this.getResolver("normal", data.resolveOptions);
asyncLib.parallel([
callback => this.resolveRequestArray(contextInfo, context, elements, loaderResolver, callback),
callback => {
if(resource === "" || resource[0] === "?") {
return callback(null, {
resource
});
}
2015-07-13 06:20:09 +08:00
normalResolver.resolve(contextInfo, context, resource, (err, resource, resourceResolveData) => {
if(err) return callback(err);
callback(null, {
resourceResolveData,
resource
});
});
}
], (err, results) => {
if(err) return callback(err);
let loaders = results[0];
const resourceResolveData = results[1].resourceResolveData;
resource = results[1].resource;
// translate option idents
try {
loaders.forEach(item => {
if(typeof item.options === "string" && /^\?/.test(item.options)) {
item.options = this.ruleSet.findOptionsByIdent(item.options.substr(1));
2017-04-04 05:28:08 +08:00
}
});
} catch(e) {
return callback(e);
}
if(resource === false) {
// ignored
return callback(null,
new RawModule(
"/* (ignored) */",
`ignored ${context} ${request}`,
`${request} (ignored)`
)
);
}
const userRequest = loaders.map(loaderToIdent).concat([resource]).join("!");
let resourcePath = resource;
let resourceQuery = "";
const queryIndex = resourcePath.indexOf("?");
if(queryIndex >= 0) {
resourceQuery = resourcePath.substr(queryIndex);
resourcePath = resourcePath.substr(0, queryIndex);
}
const result = this.ruleSet.exec({
resource: resourcePath,
resourceQuery,
issuer: contextInfo.issuer,
compiler: contextInfo.compiler
});
const settings = {};
const useLoadersPost = [];
const useLoaders = [];
const useLoadersPre = [];
result.forEach(r => {
if(r.type === "use") {
if(r.enforce === "post" && !noPostAutoLoaders && !noPrePostAutoLoaders)
useLoadersPost.push(r.value);
else if(r.enforce === "pre" && !noPrePostAutoLoaders)
useLoadersPre.push(r.value);
else if(!r.enforce && !noAutoLoaders && !noPrePostAutoLoaders)
useLoaders.push(r.value);
} else if(typeof r.value === "object" && r.value !== null && typeof settings[r.type] === "object" && settings[r.type] !== null) {
settings[r.type] = cachedMerge(settings[r.type], r.value);
} else {
settings[r.type] = r.value;
}
});
asyncLib.parallel([
this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPost, loaderResolver),
this.resolveRequestArray.bind(this, contextInfo, this.context, useLoaders, loaderResolver),
this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPre, loaderResolver)
], (err, results) => {
if(err) return callback(err);
loaders = results[0].concat(loaders, results[1], results[2]);
process.nextTick(() => {
const type = settings.type;
const resolveOptions = settings.resolve;
callback(null, {
context: context,
request: loaders.map(loaderToIdent).concat([resource]).join("!"),
dependencies: data.dependencies,
userRequest,
rawRequest: request,
loaders,
resource,
resourceResolveData,
2017-10-30 20:56:57 +08:00
type,
parser: this.getParser(type, settings.parser),
resolveOptions
2017-04-04 05:28:08 +08:00
});
});
});
});
2017-04-04 05:28:08 +08:00
});
}
2013-01-31 01:49:25 +08:00
2017-04-04 05:28:08 +08:00
create(data, callback) {
2017-04-04 16:26:16 +08:00
const dependencies = data.dependencies;
const cacheEntry = dependencies[0].__NormalModuleFactoryCache;
2017-04-04 05:28:08 +08:00
if(cacheEntry) return callback(null, cacheEntry);
2017-04-04 16:26:16 +08:00
const context = data.context || this.context;
const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
2017-04-04 16:26:16 +08:00
const request = dependencies[0].request;
const contextInfo = data.contextInfo || {};
2017-04-04 05:28:08 +08:00
this.applyPluginsAsyncWaterfall("before-resolve", {
contextInfo,
resolveOptions,
2017-04-04 05:28:08 +08:00
context,
request,
dependencies
}, (err, result) => {
if(err) return callback(err);
2017-04-04 05:28:08 +08:00
// Ignored
if(!result) return callback();
2017-04-04 16:26:16 +08:00
const factory = this.applyPluginsWaterfall0("factory", null);
2017-04-04 05:28:08 +08:00
// Ignored
if(!factory) return callback();
2017-04-04 05:28:08 +08:00
factory(result, (err, module) => {
if(err) return callback(err);
if(module && this.cachePredicate(module)) {
dependencies.forEach(d => d.__NormalModuleFactoryCache = module);
}
callback(null, module);
});
});
}
2017-04-04 05:28:08 +08:00
resolveRequestArray(contextInfo, context, array, resolver, callback) {
if(array.length === 0) return callback(null, []);
2017-04-05 21:06:23 +08:00
asyncLib.map(array, (item, callback) => {
2017-04-04 05:28:08 +08:00
resolver.resolve(contextInfo, context, item.loader, (err, result) => {
if(err && /^[^/]*$/.test(item.loader) && !/-loader$/.test(item.loader)) {
return resolver.resolve(contextInfo, context, item.loader + "-loader", err2 => {
if(!err2) {
err.message = err.message + "\n" +
"BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +
` You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +
" see https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed";
}
callback(err);
});
}
if(err) return callback(err);
2017-04-04 16:26:16 +08:00
const optionsOnly = item.options ? {
2017-04-04 05:28:08 +08:00
options: item.options
} : undefined;
return callback(null, Object.assign({}, item, identToLoaderRequest(result), optionsOnly));
});
}, callback);
}
2017-10-30 20:56:57 +08:00
getParser(type, parserOptions) {
let ident = type;
2017-04-04 05:28:08 +08:00
if(parserOptions) {
if(parserOptions.ident)
2017-10-30 20:56:57 +08:00
ident = `${type}|${parserOptions.ident}`;
2017-04-04 05:28:08 +08:00
else
2017-10-30 20:56:57 +08:00
ident = JSON.stringify([type, parserOptions]);
2017-04-04 05:28:08 +08:00
}
2017-04-04 16:26:16 +08:00
const parser = this.parserCache[ident];
2017-04-04 05:28:08 +08:00
if(parser)
return parser;
2017-10-30 20:56:57 +08:00
return this.parserCache[ident] = this.createParser(type, parserOptions);
2017-04-04 05:28:08 +08:00
}
2017-10-30 20:56:57 +08:00
createParser(type, parserOptions) {
parserOptions = parserOptions || {};
const parser = this.applyPluginsBailResult1(`create-parser ${type}`, parserOptions);
if(!parser) {
throw new Error(`No parser registered for ${type}`);
2017-10-30 20:56:57 +08:00
}
if(type === "javascript/auto") {
// TODO: remove backward compat in webpack 5
this.applyPlugins2("parser", parser, parserOptions);
2017-10-30 20:56:57 +08:00
}
this.applyPlugins2(`parser ${type}`, parser, parserOptions);
return parser;
2017-04-04 05:28:08 +08:00
}
getResolver(type, resolveOptions) {
return this.resolverFactory.get(type, resolveOptions || EMPTY_RESOLVE_OPTIONS);
}
2017-04-04 05:28:08 +08:00
}
module.exports = NormalModuleFactory;