webpack/lib/sharing/ConsumeSharedPlugin.js

229 lines
7.0 KiB
JavaScript
Raw Normal View History

2020-05-26 05:14:19 +08:00
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const validateOptions = require("schema-utils");
const schema = require("../../schemas/plugins/sharing/ConsumeSharedPlugin.json");
const ModuleNotFoundError = require("../ModuleNotFoundError");
const RuntimeGlobals = require("../RuntimeGlobals");
const { parseOptions } = require("../container/options");
const LazySet = require("../util/LazySet");
const ConsumeFallbackDependency = require("./ConsumeFallbackDependency");
const ConsumeSharedModule = require("./ConsumeSharedModule");
const ConsumeSharedRuntimeModule = require("./ConsumeSharedRuntimeModule");
2020-05-26 06:46:09 +08:00
const ProvidedDependency = require("./ProvidedDependency");
2020-05-26 05:14:19 +08:00
const { parseRequiredVersion } = require("./utils");
/** @typedef {import("enhanced-resolve").ResolveContext} ResolveContext */
/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumeSharedPluginOptions} ConsumeSharedPluginOptions */
/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumesConfig} ConsumesConfig */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("./ConsumeSharedModule").ConsumeOptions} ConsumeOptions */
const PLUGIN_NAME = "ConsumeSharedPlugin";
class ConsumeSharedPlugin {
/**
* @param {ConsumeSharedPluginOptions} options options
*/
constructor(options) {
if (typeof options !== "string") {
validateOptions(schema, options, { name: "Consumes Shared Plugin" });
}
/** @type {[string, ConsumeOptions][]} */
this._consumes = parseOptions(
options.consumes,
(item, key) => {
if (Array.isArray(item)) throw new Error("Unexpected array in options");
/** @type {ConsumeOptions} */
let result;
2020-05-26 05:14:19 +08:00
if (item === key) {
// item is a request/key
result = {
2020-05-26 05:14:19 +08:00
import: key,
shareScope: options.shareScope || "default",
shareKey: key,
requiredVersion: undefined,
strictVersion: false,
eager: false
2020-05-26 05:14:19 +08:00
};
} else {
// key is a request/key
// item is a version
result = {
2020-05-26 05:14:19 +08:00
import: key,
shareScope: options.shareScope || "default",
shareKey: key,
requiredVersion: parseRequiredVersion(item),
strictVersion: true,
eager: false
2020-05-26 05:14:19 +08:00
};
}
return result;
2020-05-26 05:14:19 +08:00
},
(item, key) => ({
import: item.import === false ? undefined : item.import || key,
shareScope: item.shareScope || options.shareScope || "default",
shareKey: item.shareKey || key,
requiredVersion:
typeof item.requiredVersion === "string"
? parseRequiredVersion(item.requiredVersion)
: item.requiredVersion,
strictVersion:
item.requiredVersion &&
(typeof item.strictVersion === "boolean"
? item.strictVersion
: item.import !== false),
eager: !!item.eager
2020-05-26 05:14:19 +08:00
})
);
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.thisCompilation.tap(
PLUGIN_NAME,
(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(
ConsumeFallbackDependency,
normalModuleFactory
);
/** @type {Map<string, ConsumeOptions>} */
const resolvedConsumes = new Map();
/** @type {Map<string, ConsumeOptions>} */
const unresolvedConsumes = new Map();
/** @type {Map<string, ConsumeOptions>} */
const prefixConsumes = new Map();
const resolveContext = {
/** @type {LazySet<string>} */
fileDependencies: new LazySet(),
/** @type {LazySet<string>} */
contextDependencies: new LazySet(),
/** @type {LazySet<string>} */
missingDependencies: new LazySet()
};
const resolver = compilation.resolverFactory.get("normal");
/**
* @param {string} request imported request
* @param {ConsumeOptions} options options
* @returns {Promise<void>} promise
*/
const resolveConsume = (request, options) => {
if (/^\.\.?(\/|$)/.test(request)) {
// relative request
return new Promise(resolve => {
resolver.resolve(
{},
compiler.context,
request,
resolveContext,
(err, result) => {
if (err) {
compilation.errors.push(
new ModuleNotFoundError(null, err, {
name: `consumed shared module ${request}`
})
);
return resolve();
}
resolvedConsumes.set(result, options);
resolve();
}
);
});
} else if (/^(\/|[A-Za-z]:\\|\\\\)/.test(request)) {
// absolute path
resolvedConsumes.set(request, options);
} else if (request.endsWith("/")) {
// module request prefix
prefixConsumes.set(request, options);
} else {
// module request
unresolvedConsumes.set(request, options);
}
};
const promise = Promise.all(
this._consumes.map(([name, config]) => resolveConsume(name, config))
).then(() => {
compilation.contextDependencies.addAll(
resolveContext.contextDependencies
);
compilation.fileDependencies.addAll(resolveContext.fileDependencies);
compilation.missingDependencies.addAll(
resolveContext.missingDependencies
);
});
normalModuleFactory.hooks.factorize.tapPromise(
PLUGIN_NAME,
({ context, request, dependencies }) =>
// wait for resolving to be complete
promise.then(() => {
2020-05-26 06:46:09 +08:00
if (
dependencies[0] instanceof ConsumeFallbackDependency ||
dependencies[0] instanceof ProvidedDependency
) {
2020-05-26 05:14:19 +08:00
return;
}
const match = unresolvedConsumes.get(request);
if (match !== undefined) {
return new ConsumeSharedModule(context, match);
}
for (const [prefix, options] of prefixConsumes) {
if (request.startsWith(prefix)) {
const remainder = request.slice(prefix.length);
return new ConsumeSharedModule(context, {
...options,
import: options.import
? options.import + remainder
: undefined,
shareKey: options.shareKey + remainder
});
}
}
})
);
normalModuleFactory.hooks.module.tap(
PLUGIN_NAME,
(module, createData, { context, request, dependencies }) => {
2020-05-26 06:46:09 +08:00
if (
dependencies[0] instanceof ConsumeFallbackDependency ||
dependencies[0] instanceof ProvidedDependency
) {
2020-05-26 05:14:19 +08:00
return;
}
const options = resolvedConsumes.get(createData.resource);
if (options !== undefined) {
return new ConsumeSharedModule(context, options);
}
}
);
compilation.hooks.additionalTreeRuntimeRequirements.tap(
PLUGIN_NAME,
(chunk, set) => {
set.add(RuntimeGlobals.module);
set.add(RuntimeGlobals.moduleFactoriesAddOnly);
set.add(RuntimeGlobals.shareScopeMap);
set.add(RuntimeGlobals.initializeSharing);
2020-05-26 05:14:19 +08:00
set.add(RuntimeGlobals.hasOwnProperty);
compilation.addRuntimeModule(
chunk,
new ConsumeSharedRuntimeModule(set)
);
}
);
}
);
}
}
module.exports = ConsumeSharedPlugin;