Skip to content

Commit

Permalink
Merge pull request webpack#12429 from webpack/feature/resolve-in-exte…
Browse files Browse the repository at this point in the history
…rnals
  • Loading branch information
sokra authored Jan 15, 2021
2 parents 435b9b8 + b6e349b commit e475ec3
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 66 deletions.
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 @@ -2505,6 +2514,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 @@ -2894,16 +2932,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

0 comments on commit e475ec3

Please sign in to comment.