From b6e349b018c5e139ca03a760aa508ec2698c21da Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 15 Jan 2021 13:55:19 +0100 Subject: [PATCH] offer getResolve to externals improve types of externals configuration option support promises in externals --- declarations/WebpackOptions.d.ts | 65 +++++++++++----- lib/ExternalModuleFactoryPlugin.js | 49 +++++++++++- schemas/WebpackOptions.json | 74 ++++++++++++------ test/configCases/externals/resolve/index.js | 5 ++ .../resolve/node_modules/external.js | 0 .../externals/resolve/webpack.config.js | 14 ++++ types.d.ts | 78 +++++++++++++------ 7 files changed, 219 insertions(+), 66 deletions(-) create mode 100644 test/configCases/externals/resolve/index.js create mode 100644 test/configCases/externals/resolve/node_modules/external.js create mode 100644 test/configCases/externals/resolve/webpack.config.js diff --git a/declarations/WebpackOptions.d.ts b/declarations/WebpackOptions.d.ts index 6c332ebc560..727e441588d 100644 --- a/declarations/WebpackOptions.d.ts +++ b/declarations/WebpackOptions.d.ts @@ -150,14 +150,13 @@ export type ExternalItem = | RegExp | string | (ExternalItemObjectKnown & ExternalItemObjectUnknown) - | (( - data: { - context: string; - request: string; - contextInfo: import("../lib/ModuleFactory").ModuleFactoryCreateDataContextInfo; - }, - callback: (err?: Error, result?: string) => void - ) => void); + | ( + | (( + data: ExternalItemFunctionData, + callback: (err?: Error, result?: ExternalItemValue) => void + ) => void) + | ((data: ExternalItemFunctionData) => Promise) + ); /** * Specifies the default type of externals ('amd*', 'umd*', 'system' and 'jsonp' depend on output.libraryTarget set to the same value). */ @@ -642,6 +641,16 @@ export type EntryDynamicNormalized = () => Promise; * The entry point(s) of the compilation. */ export type EntryNormalized = EntryDynamicNormalized | EntryStaticNormalized; +/** + * The dependency used for the external. + */ +export type ExternalItemValue = + | string[] + | boolean + | string + | { + [k: string]: any; + }; /** * Ignore specific warnings. */ @@ -2501,6 +2510,35 @@ export interface EntryStaticNormalized { */ [k: string]: EntryDescriptionNormalized; } +/** + * Data object passed as argument when a function is set for 'externals'. + */ +export interface ExternalItemFunctionData { + /** + * The directory in which the request is placed. + */ + context?: string; + /** + * Contextual information. + */ + contextInfo?: import("../lib/ModuleFactory").ModuleFactoryCreateDataContextInfo; + /** + * Get a resolve function with the current resolver options. + */ + getResolve?: ( + options?: ResolveOptions + ) => + | (( + context: string, + request: string, + callback: (err?: Error, result?: string) => void + ) => void) + | ((context: string, request: string) => Promise); + /** + * The request as written by the user in the require/import expression/statement. + */ + request?: string; +} /** * Parser options for javascript modules. */ @@ -2890,16 +2928,7 @@ export interface ExternalItemObjectKnown { * If an dependency matches exactly a property of the object, the property value is used as dependency. */ export interface ExternalItemObjectUnknown { - /** - * The dependency used for the external. - */ - [k: string]: - | string[] - | boolean - | string - | { - [k: string]: any; - }; + [k: string]: ExternalItemValue; } /** * Specify options for each generator. diff --git a/lib/ExternalModuleFactoryPlugin.js b/lib/ExternalModuleFactoryPlugin.js index 1a80c5fd2c9..0a89a3f5982 100644 --- a/lib/ExternalModuleFactoryPlugin.js +++ b/lib/ExternalModuleFactoryPlugin.js @@ -7,12 +7,13 @@ const util = require("util"); const ExternalModule = require("./ExternalModule"); -const { resolveByProperty } = require("./util/cleverMerge"); +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( @@ -171,14 +172,56 @@ class ExternalModuleFactoryPlugin { cb ); } else { - externals( + const promise = externals( { context, request: dependency.request, - contextInfo + contextInfo, + getResolve: options => (context, request, callback) => { + const dependencyType = dependency.category || ""; + 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") { diff --git a/schemas/WebpackOptions.json b/schemas/WebpackOptions.json index 23e653da2ad..31419faab78 100644 --- a/schemas/WebpackOptions.json +++ b/schemas/WebpackOptions.json @@ -611,28 +611,7 @@ "description": "If an dependency matches exactly a property of the object, the property value is used as dependency.", "type": "object", "additionalProperties": { - "description": "The dependency used for the external.", - "anyOf": [ - { - "type": "array", - "items": { - "description": "A part of the target of the external.", - "type": "string", - "minLength": 1 - } - }, - { - "description": "`true`: The dependency name is used as target of the external.", - "type": "boolean" - }, - { - "description": "The target of the external.", - "type": "string" - }, - { - "type": "object" - } - ] + "$ref": "#/definitions/ExternalItemValue" }, "properties": { "byLayer": { @@ -655,7 +634,56 @@ { "description": "The function is called on each dependency (`function(context, request, callback(err, result))`).", "instanceof": "Function", - "tsType": "((data: { context: string, request: string, contextInfo: import('../lib/ModuleFactory').ModuleFactoryCreateDataContextInfo }, callback: (err?: Error, result?: string) => void) => void)" + "tsType": "(((data: ExternalItemFunctionData, callback: (err?: Error, result?: ExternalItemValue) => void) => void) | ((data: ExternalItemFunctionData) => Promise))" + } + ] + }, + "ExternalItemFunctionData": { + "description": "Data object passed as argument when a function is set for 'externals'.", + "type": "object", + "additionalProperties": false, + "properties": { + "context": { + "description": "The directory in which the request is placed.", + "type": "string" + }, + "contextInfo": { + "description": "Contextual information.", + "type": "object", + "tsType": "import('../lib/ModuleFactory').ModuleFactoryCreateDataContextInfo" + }, + "getResolve": { + "description": "Get a resolve function with the current resolver options.", + "instanceof": "Function", + "tsType": "((options?: ResolveOptions) => ((context: string, request: string, callback: (err?: Error, result?: string) => void) => void) | ((context: string, request: string) => Promise))" + }, + "request": { + "description": "The request as written by the user in the require/import expression/statement.", + "type": "string" + } + } + }, + "ExternalItemValue": { + "description": "The dependency used for the external.", + "anyOf": [ + { + "type": "array", + "items": { + "description": "A part of the target of the external.", + "type": "string", + "minLength": 1 + } + }, + { + "description": "`true`: The dependency name is used as target of the external.", + "type": "boolean" + }, + { + "description": "The target of the external.", + "type": "string" + }, + { + "type": "object" } ] }, diff --git a/test/configCases/externals/resolve/index.js b/test/configCases/externals/resolve/index.js new file mode 100644 index 00000000000..941c59e9b5a --- /dev/null +++ b/test/configCases/externals/resolve/index.js @@ -0,0 +1,5 @@ +it("should allow functions as externals with promise and resolver", function () { + const result = require("external"); + expect(result).toMatch(/^[a-z]:\\|\//i); + expect(result).toMatch(/resolve.node_modules.external\.js$/); +}); diff --git a/test/configCases/externals/resolve/node_modules/external.js b/test/configCases/externals/resolve/node_modules/external.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/externals/resolve/webpack.config.js b/test/configCases/externals/resolve/webpack.config.js new file mode 100644 index 00000000000..fc61b5b07f3 --- /dev/null +++ b/test/configCases/externals/resolve/webpack.config.js @@ -0,0 +1,14 @@ +/** @type {import("../../../../").Configuration} */ +module.exports = { + optimization: { + concatenateModules: true + }, + externals: [ + async ({ context, request, getResolve }) => { + if (request !== "external") return false; + const resolve = getResolve(); + const resolved = await resolve(context, request); + return `var ${JSON.stringify(resolved)}`; + } + ] +}; diff --git a/types.d.ts b/types.d.ts index 7be5a1b0475..b5755362240 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1797,13 +1797,13 @@ declare interface Configuration { | ExternalItem[] | (ExternalItemObjectKnown & ExternalItemObjectUnknown) | (( - data: { - context: string; - request: string; - contextInfo: ModuleFactoryCreateDataContextInfo; - }, - callback: (err?: Error, result?: string) => void - ) => void); + data: ExternalItemFunctionData, + callback: ( + err?: Error, + result?: string | boolean | string[] | { [index: string]: any } + ) => void + ) => void) + | ((data: ExternalItemFunctionData) => Promise); /** * Enable presets of externals for specific targets. @@ -3247,13 +3247,46 @@ type ExternalItem = | RegExp | (ExternalItemObjectKnown & ExternalItemObjectUnknown) | (( - data: { - context: string; - request: string; - contextInfo: ModuleFactoryCreateDataContextInfo; - }, - callback: (err?: Error, result?: string) => void - ) => void); + data: ExternalItemFunctionData, + callback: ( + err?: Error, + result?: string | boolean | string[] | { [index: string]: any } + ) => void + ) => void) + | ((data: ExternalItemFunctionData) => Promise); + +/** + * Data object passed as argument when a function is set for 'externals'. + */ +declare interface ExternalItemFunctionData { + /** + * The directory in which the request is placed. + */ + context?: string; + + /** + * Contextual information. + */ + contextInfo?: ModuleFactoryCreateDataContextInfo; + + /** + * Get a resolve function with the current resolver options. + */ + getResolve?: ( + options?: ResolveOptionsWebpackOptions + ) => + | (( + context: string, + request: string, + callback: (err?: Error, result?: string) => void + ) => void) + | ((context: string, request: string) => Promise); + + /** + * The request as written by the user in the require/import expression/statement. + */ + request?: string; +} /** * If an dependency matches exactly a property of the object, the property value is used as dependency. @@ -3271,8 +3304,9 @@ declare interface ExternalItemObjectKnown { * If an dependency matches exactly a property of the object, the property value is used as dependency. */ declare interface ExternalItemObjectUnknown { - [index: string]: string | boolean | string[] | { [index: string]: any }; + [index: string]: ExternalItemValue; } +type ExternalItemValue = string | boolean | string[] | { [index: string]: any }; declare class ExternalModule extends Module { constructor(request?: any, type?: any, userRequest?: any); request: string | string[] | Record; @@ -3294,13 +3328,13 @@ type Externals = | ExternalItem[] | (ExternalItemObjectKnown & ExternalItemObjectUnknown) | (( - data: { - context: string; - request: string; - contextInfo: ModuleFactoryCreateDataContextInfo; - }, - callback: (err?: Error, result?: string) => void - ) => void); + data: ExternalItemFunctionData, + callback: ( + err?: Error, + result?: string | boolean | string[] | { [index: string]: any } + ) => void + ) => void) + | ((data: ExternalItemFunctionData) => Promise); declare class ExternalsPlugin { constructor(type: undefined | string, externals: Externals); type?: string;