123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
-
- const path = require("path");
- const asyncLib = require("neo-async");
- const {
- Tapable,
- AsyncSeriesWaterfallHook,
- SyncWaterfallHook,
- SyncBailHook,
- SyncHook,
- HookMap
- } = require("tapable");
- const NormalModule = require("./NormalModule");
- const RawModule = require("./RawModule");
- const RuleSet = require("./RuleSet");
- const { cachedCleverMerge } = require("./util/cleverMerge");
-
- const EMPTY_RESOLVE_OPTIONS = {};
-
- const MATCH_RESOURCE_REGEX = /^([^!]+)!=!/;
-
- 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);
- };
-
- const identToLoaderRequest = resultString => {
- const idx = resultString.indexOf("?");
- if (idx >= 0) {
- const loader = resultString.substr(0, idx);
- const options = resultString.substr(idx + 1);
- return {
- loader,
- options
- };
- } else {
- return {
- loader: resultString,
- options: undefined
- };
- }
- };
-
- const dependencyCache = new WeakMap();
-
- class NormalModuleFactory extends Tapable {
- constructor(context, resolverFactory, options) {
- super();
- this.hooks = {
- resolver: new SyncWaterfallHook(["resolver"]),
- factory: new SyncWaterfallHook(["factory"]),
- beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
- afterResolve: new AsyncSeriesWaterfallHook(["data"]),
- createModule: new SyncBailHook(["data"]),
- module: new SyncWaterfallHook(["module", "data"]),
- createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
- parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
- createGenerator: new HookMap(
- () => new SyncBailHook(["generatorOptions"])
- ),
- generator: new HookMap(
- () => new SyncHook(["generator", "generatorOptions"])
- )
- };
- this._pluginCompat.tap("NormalModuleFactory", options => {
- switch (options.name) {
- case "before-resolve":
- case "after-resolve":
- options.async = true;
- break;
- case "parser":
- this.hooks.parser
- .for("javascript/auto")
- .tap(options.fn.name || "unnamed compat plugin", options.fn);
- return true;
- }
- let match;
- match = /^parser (.+)$/.exec(options.name);
- if (match) {
- this.hooks.parser
- .for(match[1])
- .tap(
- options.fn.name || "unnamed compat plugin",
- options.fn.bind(this)
- );
- return true;
- }
- match = /^create-parser (.+)$/.exec(options.name);
- if (match) {
- this.hooks.createParser
- .for(match[1])
- .tap(
- options.fn.name || "unnamed compat plugin",
- options.fn.bind(this)
- );
- return true;
- }
- });
- this.resolverFactory = resolverFactory;
- this.ruleSet = new RuleSet(options.defaultRules.concat(options.rules));
- this.cachePredicate =
- typeof options.unsafeCache === "function"
- ? options.unsafeCache
- : Boolean.bind(null, options.unsafeCache);
- this.context = context || "";
- this.parserCache = Object.create(null);
- this.generatorCache = Object.create(null);
- this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
- let resolver = this.hooks.resolver.call(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);
-
- this.hooks.afterResolve.callAsync(data, (err, result) => {
- if (err) return callback(err);
-
- // Ignored
- if (!result) return callback();
-
- let createdModule = this.hooks.createModule.call(result);
- if (!createdModule) {
- if (!result.request) {
- return callback(new Error("Empty dependency (no request)"));
- }
-
- createdModule = new NormalModule(result);
- }
-
- createdModule = this.hooks.module.call(createdModule, result);
-
- return callback(null, createdModule);
- });
- });
- });
- this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {
- const contextInfo = data.contextInfo;
- const context = data.context;
- const request = data.request;
-
- const loaderResolver = this.getResolver("loader");
- const normalResolver = this.getResolver("normal", data.resolveOptions);
-
- let matchResource = undefined;
- let requestWithoutMatchResource = request;
- const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);
- if (matchResourceMatch) {
- matchResource = matchResourceMatch[1];
- if (/^\.\.?\//.test(matchResource)) {
- matchResource = path.join(context, matchResource);
- }
- requestWithoutMatchResource = request.substr(
- matchResourceMatch[0].length
- );
- }
-
- const noPreAutoLoaders = requestWithoutMatchResource.startsWith("-!");
- const noAutoLoaders =
- noPreAutoLoaders || requestWithoutMatchResource.startsWith("!");
- const noPrePostAutoLoaders = requestWithoutMatchResource.startsWith("!!");
- let elements = requestWithoutMatchResource
- .replace(/^-?!+/, "")
- .replace(/!!+/g, "!")
- .split("!");
- let resource = elements.pop();
- elements = elements.map(identToLoaderRequest);
-
- asyncLib.parallel(
- [
- callback =>
- this.resolveRequestArray(
- contextInfo,
- context,
- elements,
- loaderResolver,
- callback
- ),
- callback => {
- if (resource === "" || resource[0] === "?") {
- return callback(null, {
- resource
- });
- }
-
- 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 {
- for (const item of loaders) {
- if (typeof item.options === "string" && item.options[0] === "?") {
- const ident = item.options.substr(1);
- item.options = this.ruleSet.findOptionsByIdent(ident);
- item.ident = ident;
- }
- }
- } catch (e) {
- return callback(e);
- }
-
- if (resource === false) {
- // ignored
- return callback(
- null,
- new RawModule(
- "/* (ignored) */",
- `ignored ${context} ${request}`,
- `${request} (ignored)`
- )
- );
- }
-
- const userRequest =
- (matchResource !== undefined ? `${matchResource}!=!` : "") +
- loaders
- .map(loaderToIdent)
- .concat([resource])
- .join("!");
-
- let resourcePath =
- matchResource !== undefined ? matchResource : 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,
- realResource:
- matchResource !== undefined
- ? resource.replace(/\?.*/, "")
- : resourcePath,
- resourceQuery,
- issuer: contextInfo.issuer,
- compiler: contextInfo.compiler
- });
- const settings = {};
- const useLoadersPost = [];
- const useLoaders = [];
- const useLoadersPre = [];
- for (const r of result) {
- if (r.type === "use") {
- if (r.enforce === "post" && !noPrePostAutoLoaders) {
- useLoadersPost.push(r.value);
- } else if (
- r.enforce === "pre" &&
- !noPreAutoLoaders &&
- !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] = cachedCleverMerge(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);
- if (matchResource === undefined) {
- loaders = results[0].concat(loaders, results[1], results[2]);
- } else {
- loaders = results[0].concat(results[1], loaders, 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,
- matchResource,
- resourceResolveData,
- settings,
- type,
- parser: this.getParser(type, settings.parser),
- generator: this.getGenerator(type, settings.generator),
- resolveOptions
- });
- });
- }
- );
- }
- );
- });
- }
-
- create(data, callback) {
- const dependencies = data.dependencies;
- const cacheEntry = dependencyCache.get(dependencies[0]);
- if (cacheEntry) return callback(null, cacheEntry);
- const context = data.context || this.context;
- const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
- const request = dependencies[0].request;
- const contextInfo = data.contextInfo || {};
- this.hooks.beforeResolve.callAsync(
- {
- contextInfo,
- resolveOptions,
- context,
- request,
- dependencies
- },
- (err, result) => {
- if (err) return callback(err);
-
- // Ignored
- if (!result) return callback();
-
- const factory = this.hooks.factory.call(null);
-
- // Ignored
- if (!factory) return callback();
-
- factory(result, (err, module) => {
- if (err) return callback(err);
-
- if (module && this.cachePredicate(module)) {
- for (const d of dependencies) {
- dependencyCache.set(d, module);
- }
- }
-
- callback(null, module);
- });
- }
- );
- }
-
- resolveRequestArray(contextInfo, context, array, resolver, callback) {
- if (array.length === 0) return callback(null, []);
- asyncLib.map(
- array,
- (item, callback) => {
- 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/migrate/3/#automatic-loader-module-name-extension-removed";
- }
- callback(err);
- }
- );
- }
- if (err) return callback(err);
-
- const optionsOnly = item.options
- ? {
- options: item.options
- }
- : undefined;
- return callback(
- null,
- Object.assign({}, item, identToLoaderRequest(result), optionsOnly)
- );
- }
- );
- },
- callback
- );
- }
-
- getParser(type, parserOptions) {
- let ident = type;
- if (parserOptions) {
- if (parserOptions.ident) {
- ident = `${type}|${parserOptions.ident}`;
- } else {
- ident = JSON.stringify([type, parserOptions]);
- }
- }
- if (ident in this.parserCache) {
- return this.parserCache[ident];
- }
- return (this.parserCache[ident] = this.createParser(type, parserOptions));
- }
-
- createParser(type, parserOptions = {}) {
- const parser = this.hooks.createParser.for(type).call(parserOptions);
- if (!parser) {
- throw new Error(`No parser registered for ${type}`);
- }
- this.hooks.parser.for(type).call(parser, parserOptions);
- return parser;
- }
-
- getGenerator(type, generatorOptions) {
- let ident = type;
- if (generatorOptions) {
- if (generatorOptions.ident) {
- ident = `${type}|${generatorOptions.ident}`;
- } else {
- ident = JSON.stringify([type, generatorOptions]);
- }
- }
- if (ident in this.generatorCache) {
- return this.generatorCache[ident];
- }
- return (this.generatorCache[ident] = this.createGenerator(
- type,
- generatorOptions
- ));
- }
-
- createGenerator(type, generatorOptions = {}) {
- const generator = this.hooks.createGenerator
- .for(type)
- .call(generatorOptions);
- if (!generator) {
- throw new Error(`No generator registered for ${type}`);
- }
- this.hooks.generator.for(type).call(generator, generatorOptions);
- return generator;
- }
-
- getResolver(type, resolveOptions) {
- return this.resolverFactory.get(
- type,
- resolveOptions || EMPTY_RESOLVE_OPTIONS
- );
- }
- }
-
- module.exports = NormalModuleFactory;
|