/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const util = require("util"); const ExternalModule = require("./ExternalModule"); const { resolveByProperty, cachedSetProperty } = require("./util/cleverMerge"); /** @typedef {import("../declarations/WebpackOptions").Externals} Externals */ /** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ const UNSPECIFIED_EXTERNAL_TYPE_REGEXP = /^[a-z0-9-]+ /; const EMPTY_RESOLVE_OPTIONS = {}; // TODO webpack 6 remove this const callDeprecatedExternals = util.deprecate( (externalsFunction, context, request, cb) => { externalsFunction.call(null, context, request, cb); }, "The externals-function should be defined like ({context, request}, cb) => { ... }", "DEP_WEBPACK_EXTERNALS_FUNCTION_PARAMETERS" ); const cache = new WeakMap(); const resolveLayer = (obj, layer) => { let map = cache.get(obj); if (map === undefined) { map = new Map(); cache.set(obj, map); } else { const cacheEntry = map.get(layer); if (cacheEntry !== undefined) return cacheEntry; } const result = resolveByProperty(obj, "byLayer", layer); map.set(layer, result); return result; }; class ExternalModuleFactoryPlugin { /** * @param {string | undefined} type default external type * @param {Externals} externals externals config */ constructor(type, externals) { this.type = type; this.externals = externals; } /** * @param {NormalModuleFactory} normalModuleFactory the normal module factory * @returns {void} */ apply(normalModuleFactory) { const globalType = this.type; normalModuleFactory.hooks.factorize.tapAsync( "ExternalModuleFactoryPlugin", (data, callback) => { const context = data.context; const contextInfo = data.contextInfo; const dependency = data.dependencies[0]; /** * @param {string|string[]|boolean|Record} value the external config * @param {string|undefined} type type of external * @param {function(Error=, ExternalModule=): void} callback callback * @returns {void} */ const handleExternal = (value, type, callback) => { if (value === false) { // Not externals, fallback to original factory return callback(); } /** @type {string | string[] | Record} */ let externalConfig; if (value === true) { externalConfig = dependency.request; } else { externalConfig = value; } // When no explicit type is specified, extract it from the externalConfig if (type === undefined) { if ( typeof externalConfig === "string" && UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig) ) { const idx = externalConfig.indexOf(" "); type = externalConfig.substr(0, idx); externalConfig = externalConfig.substr(idx + 1); } else if ( Array.isArray(externalConfig) && externalConfig.length > 0 && UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig[0]) ) { const firstItem = externalConfig[0]; const idx = firstItem.indexOf(" "); type = firstItem.substr(0, idx); externalConfig = [ firstItem.substr(idx + 1), ...externalConfig.slice(1) ]; } } callback( null, new ExternalModule( externalConfig, type || globalType, dependency.request ) ); }; /** * @param {Externals} externals externals config * @param {function(Error=, ExternalModule=): void} callback callback * @returns {void} */ const handleExternals = (externals, callback) => { if (typeof externals === "string") { if (externals === dependency.request) { return handleExternal(dependency.request, undefined, callback); } } else if (Array.isArray(externals)) { let i = 0; const next = () => { let asyncFlag; const handleExternalsAndCallback = (err, module) => { if (err) return callback(err); if (!module) { if (asyncFlag) { asyncFlag = false; return; } return next(); } callback(null, module); }; do { asyncFlag = true; if (i >= externals.length) return callback(); handleExternals(externals[i++], handleExternalsAndCallback); } while (!asyncFlag); asyncFlag = false; }; next(); return; } else if (externals instanceof RegExp) { if (externals.test(dependency.request)) { return handleExternal(dependency.request, undefined, callback); } } else if (typeof externals === "function") { const cb = (err, value, type) => { if (err) return callback(err); if (value !== undefined) { handleExternal(value, type, callback); } else { callback(); } }; if (externals.length === 3) { // TODO webpack 6 remove this callDeprecatedExternals( externals, context, dependency.request, cb ); } else { const dependencyType = dependency.category || ""; const promise = externals( { context, request: dependency.request, dependencyType, contextInfo, getResolve: options => (context, request, callback) => { const resolveContext = { fileDependencies: data.fileDependencies, missingDependencies: data.missingDependencies, contextDependencies: data.contextDependencies }; let resolver = normalModuleFactory.getResolver( "normal", dependencyType ? cachedSetProperty( data.resolveOptions || EMPTY_RESOLVE_OPTIONS, "dependencyType", dependencyType ) : data.resolveOptions ); if (options) resolver = resolver.withOptions(options); if (callback) { resolver.resolve( {}, context, request, resolveContext, callback ); } else { return new Promise((resolve, reject) => { resolver.resolve( {}, context, request, resolveContext, (err, result) => { if (err) reject(err); else resolve(result); } ); }); } } }, cb ); if (promise && promise.then) promise.then(r => cb(null, r), cb); } return; } else if (typeof externals === "object") { const resolvedExternals = resolveLayer( externals, contextInfo.issuerLayer ); if ( Object.prototype.hasOwnProperty.call( resolvedExternals, dependency.request ) ) { return handleExternal( resolvedExternals[dependency.request], undefined, callback ); } } callback(); }; handleExternals(this.externals, callback); } ); } } module.exports = ExternalModuleFactoryPlugin;