Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

offer getResolve to externals #12429

Merged
merged 1 commit into from
Jan 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 47 additions & 18 deletions declarations/WebpackOptions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExternalItemValue>)
);
/**
* Specifies the default type of externals ('amd*', 'umd*', 'system' and 'jsonp' depend on output.libraryTarget set to the same value).
*/
Expand Down Expand Up @@ -642,6 +641,16 @@ export type EntryDynamicNormalized = () => Promise<EntryStaticNormalized>;
* 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.
*/
Expand Down Expand Up @@ -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<string>);
/**
* The request as written by the user in the require/import expression/statement.
*/
request?: string;
}
/**
* Parser options for javascript modules.
*/
Expand Down Expand Up @@ -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.
Expand Down
49 changes: 46 additions & 3 deletions lib/ExternalModuleFactoryPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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") {
Expand Down
74 changes: 51 additions & 23 deletions schemas/WebpackOptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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<ExternalItemValue>))"
}
]
},
"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<string>))"
},
"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"
}
]
},
Expand Down
5 changes: 5 additions & 0 deletions test/configCases/externals/resolve/index.js
Original file line number Diff line number Diff line change
@@ -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$/);
});
Empty file.
14 changes: 14 additions & 0 deletions test/configCases/externals/resolve/webpack.config.js
Original file line number Diff line number Diff line change
@@ -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)}`;
}
]
};
78 changes: 56 additions & 22 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExternalItemValue>);

/**
* Enable presets of externals for specific targets.
Expand Down Expand Up @@ -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<ExternalItemValue>);

/**
* 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<string>);

/**
* 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.
Expand All @@ -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<string, string | string[]>;
Expand All @@ -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<ExternalItemValue>);
declare class ExternalsPlugin {
constructor(type: undefined | string, externals: Externals);
type?: string;
Expand Down