improve buildHttp

allow multiple configurations to write to the same lockfile
add allowedUris for allowlisting
add schema validation
This commit is contained in:
Tobias Koppers 2021-10-18 19:23:29 +02:00
parent 8e283053c8
commit ae52a74802
16 changed files with 600 additions and 250 deletions

View File

@ -153,6 +153,10 @@ export type WasmLoadingType =
* An entry point without name.
*/
export type EntryUnnamed = EntryItem;
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
export type Experiments = ExperimentsCommon & ExperimentsExtra;
/**
* Specify dependencies that shouldn't be resolved by webpack, but should become dependencies of the resulting bundle. The kind of the dependency depends on `output.libraryTarget`.
*/
@ -693,6 +697,11 @@ export type EntryDynamicNormalized = () => Promise<EntryStaticNormalized>;
* The entry point(s) of the compilation.
*/
export type EntryNormalized = EntryDynamicNormalized | EntryStaticNormalized;
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
export type ExperimentsNormalized = ExperimentsCommon &
ExperimentsNormalizedExtra;
/**
* The dependency used for the external.
*/
@ -703,6 +712,18 @@ export type ExternalItemValue =
| {
[k: string]: any;
};
/**
* List of allowed URIs for building http resources.
*/
export type HttpUriAllowedUris = HttpUriOptionsAllowedUris;
/**
* List of allowed URIs (resp. the beginning of them).
*/
export type HttpUriOptionsAllowedUris = (
| RegExp
| string
| ((uri: string) => boolean)
)[];
/**
* Ignore specific warnings.
*/
@ -1098,104 +1119,6 @@ export interface LibraryCustomUmdObject {
*/
root?: string[] | string;
}
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
export interface Experiments {
/**
* Allow module type 'asset' to generate assets.
*/
asset?: boolean;
/**
* Support WebAssembly as asynchronous EcmaScript Module.
*/
asyncWebAssembly?: boolean;
/**
* Build http(s): urls using a lockfile and resource content cache.
*/
buildHttp?: boolean | HttpUriOptions;
/**
* Enable additional in memory caching of modules that are unchanged and reference only unchanged modules.
*/
cacheUnaffected?: boolean;
/**
* Apply defaults of next major version.
*/
futureDefaults?: boolean;
/**
* Enable module and chunk layers.
*/
layers?: boolean;
/**
* Compile entrypoints and import()s only when they are accessed.
*/
lazyCompilation?:
| boolean
| {
/**
* A custom backend.
*/
backend?:
| ((
compiler: import("../lib/Compiler"),
client: string,
callback: (err?: Error, api?: any) => void
) => void)
| ((
compiler: import("../lib/Compiler"),
client: string
) => Promise<any>);
/**
* A custom client.
*/
client?: string;
/**
* Enable/disable lazy compilation for entries.
*/
entries?: boolean;
/**
* Enable/disable lazy compilation for import() modules.
*/
imports?: boolean;
/**
* Specify which entrypoints or import()ed modules should be lazily compiled. This is matched with the imported module and not the entrypoint name.
*/
test?: RegExp | string | ((module: import("../lib/Module")) => boolean);
};
/**
* Allow output javascript files as module source type.
*/
outputModule?: boolean;
/**
* Support WebAssembly as synchronous EcmaScript Module (outdated).
*/
syncWebAssembly?: boolean;
/**
* Allow using top-level-await in EcmaScript Modules.
*/
topLevelAwait?: boolean;
}
/**
* Options for building http resources.
*/
export interface HttpUriOptions {
/**
* Location where resource content is stored for lockfile entries. It's also possible to disable storing by passing false.
*/
cacheLocation?: false | string;
/**
* When set, anything that would lead to a modification of the lockfile or any resource content, will result in an error.
*/
frozen?: boolean;
/**
* Location of the lockfile.
*/
lockfileLocation?: string;
/**
* When set, resources of existing lockfile entries will be fetched and entries will be upgraded when resource content has changed.
*/
upgrade?: boolean;
}
/**
* Enable presets of externals for specific targets.
*/
@ -2793,6 +2716,43 @@ export interface EntryStaticNormalized {
*/
[k: string]: EntryDescriptionNormalized;
}
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
export interface ExperimentsCommon {
/**
* Allow module type 'asset' to generate assets.
*/
asset?: boolean;
/**
* Support WebAssembly as asynchronous EcmaScript Module.
*/
asyncWebAssembly?: boolean;
/**
* Enable additional in memory caching of modules that are unchanged and reference only unchanged modules.
*/
cacheUnaffected?: boolean;
/**
* Apply defaults of next major version.
*/
futureDefaults?: boolean;
/**
* Enable module and chunk layers.
*/
layers?: boolean;
/**
* Allow output javascript files as module source type.
*/
outputModule?: boolean;
/**
* Support WebAssembly as synchronous EcmaScript Module (outdated).
*/
syncWebAssembly?: boolean;
/**
* Allow using top-level-await in EcmaScript Modules.
*/
topLevelAwait?: boolean;
}
/**
* Data object passed as argument when a function is set for 'externals'.
*/
@ -2826,6 +2786,31 @@ export interface ExternalItemFunctionData {
*/
request?: string;
}
/**
* Options for building http resources.
*/
export interface HttpUriOptions {
/**
* List of allowed URIs (resp. the beginning of them).
*/
allowedUris: HttpUriOptionsAllowedUris;
/**
* Location where resource content is stored for lockfile entries. It's also possible to disable storing by passing false.
*/
cacheLocation?: false | string;
/**
* When set, anything that would lead to a modification of the lockfile or any resource content, will result in an error.
*/
frozen?: boolean;
/**
* Location of the lockfile.
*/
lockfileLocation?: string;
/**
* When set, resources of existing lockfile entries will be fetched and entries will be upgraded when resource content has changed.
*/
upgrade?: boolean;
}
/**
* Parser options for javascript modules.
*/
@ -2940,6 +2925,37 @@ export interface JavascriptParserOptions {
wrappedContextRegExp?: RegExp;
[k: string]: any;
}
/**
* Options for compiling entrypoints and import()s only when they are accessed.
*/
export interface LazyCompilationOptions {
/**
* A custom backend.
*/
backend?:
| ((
compiler: import("../lib/Compiler"),
client: string,
callback: (err?: Error, api?: any) => void
) => void)
| ((compiler: import("../lib/Compiler"), client: string) => Promise<any>);
/**
* A custom client.
*/
client?: string;
/**
* Enable/disable lazy compilation for entries.
*/
entries?: boolean;
/**
* Enable/disable lazy compilation for import() modules.
*/
imports?: boolean;
/**
* Specify which entrypoints or import()ed modules should be lazily compiled. This is matched with the imported module and not the entrypoint name.
*/
test?: RegExp | string | ((module: import("../lib/Module")) => boolean);
}
/**
* Options affecting the normal modules (`NormalModuleFactory`).
*/
@ -3193,7 +3209,7 @@ export interface WebpackOptionsNormalized {
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
experiments: Experiments;
experiments: ExperimentsNormalized;
/**
* Specify dependencies that shouldn't be resolved by webpack, but should become dependencies of the resulting bundle. The kind of the dependency depends on `output.libraryTarget`.
*/
@ -3295,6 +3311,32 @@ export interface WebpackOptionsNormalized {
*/
watchOptions: WatchOptions;
}
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
export interface ExperimentsExtra {
/**
* Build http(s): urls using a lockfile and resource content cache.
*/
buildHttp?: HttpUriAllowedUris | HttpUriOptions;
/**
* Compile entrypoints and import()s only when they are accessed.
*/
lazyCompilation?: boolean | LazyCompilationOptions;
}
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
export interface ExperimentsNormalizedExtra {
/**
* Build http(s): urls using a lockfile and resource content cache.
*/
buildHttp?: HttpUriOptions;
/**
* Compile entrypoints and import()s only when they are accessed.
*/
lazyCompilation?: LazyCompilationOptions;
}
/**
* If an dependency matches exactly a property of the object, the property value is used as dependency.
*/

View File

@ -5,11 +5,23 @@
*/
export type HttpUriPluginOptions = HttpUriOptions;
/**
* List of allowed URIs (resp. the beginning of them).
*/
export type HttpUriOptionsAllowedUris = (
| RegExp
| string
| ((uri: string) => boolean)
)[];
/**
* Options for building http resources.
*/
export interface HttpUriOptions {
/**
* List of allowed URIs (resp. the beginning of them).
*/
allowedUris: HttpUriOptionsAllowedUris;
/**
* Location where resource content is stored for lockfile entries. It's also possible to disable storing by passing false.
*/

View File

@ -4,6 +4,11 @@ module.exports = {
// loggingDebug: /HttpUriPlugin/
// },
experiments: {
buildHttp: true
buildHttp: [
"https://cdn.esm.sh/",
"https://cdn.skypack.dev/",
"https://jspm.dev/",
/^https:\/\/unpkg\.com\/.+\?module$/
]
}
};

View File

@ -279,8 +279,6 @@ class WebpackOptionsApply extends OptionsApply {
if (options.experiments.buildHttp) {
const HttpUriPlugin = require("./schemes/HttpUriPlugin");
const httpOptions = options.experiments.buildHttp;
if (httpOptions === true)
throw new Error("Unexpected due to normalization");
new HttpUriPlugin(httpOptions).apply(compiler);
}

View File

@ -19,6 +19,7 @@ const {
/** @typedef {import("../../declarations/WebpackOptions").EntryDescription} EntryDescription */
/** @typedef {import("../../declarations/WebpackOptions").EntryNormalized} Entry */
/** @typedef {import("../../declarations/WebpackOptions").Experiments} Experiments */
/** @typedef {import("../../declarations/WebpackOptions").ExperimentsNormalized} ExperimentsNormalized */
/** @typedef {import("../../declarations/WebpackOptions").ExternalsPresets} ExternalsPresets */
/** @typedef {import("../../declarations/WebpackOptions").ExternalsType} ExternalsType */
/** @typedef {import("../../declarations/WebpackOptions").InfrastructureLogging} InfrastructureLogging */
@ -257,7 +258,7 @@ const applyWebpackOptionsDefaults = options => {
};
/**
* @param {Experiments} experiments options
* @param {ExperimentsNormalized} experiments options
* @param {Object} options options
* @param {boolean} options.production is production
* @param {boolean} options.development is development mode
@ -270,14 +271,14 @@ const applyExperimentsDefaults = (experiments, { production, development }) => {
D(experiments, "outputModule", false);
D(experiments, "asset", false);
D(experiments, "layers", false);
D(experiments, "lazyCompilation", false);
D(experiments, "buildHttp", false);
D(experiments, "lazyCompilation", undefined);
D(experiments, "buildHttp", undefined);
D(experiments, "futureDefaults", false);
D(experiments, "cacheUnaffected", experiments.futureDefaults);
if (typeof experiments.buildHttp === "object") {
D(experiments.buildHttp, "frozen", production);
D(experiments.buildHttp, "upgrade", development);
D(experiments.buildHttp, "upgrade", false);
}
};

View File

@ -174,7 +174,12 @@ const getNormalizedWebpackOptions = config => {
experiments: nestedConfig(config.experiments, experiments => ({
...experiments,
buildHttp: optionalNestedConfig(experiments.buildHttp, options =>
options === true ? {} : options
Array.isArray(options) ? { allowedUris: options } : options
),
lazyCompilation: optionalNestedConfig(
experiments.lazyCompilation,
options =>
options === true ? {} : options === false ? undefined : options
)
})),
externals: config.externals,

View File

@ -5,12 +5,13 @@
"use strict";
const { resolve, extname, dirname } = require("path");
const { extname, basename } = require("path");
const { URL } = require("url");
const { createGunzip, createBrotliDecompress, createInflate } = require("zlib");
const NormalModule = require("../NormalModule");
const createSchemaValidation = require("../util/create-schema-validation");
const createHash = require("../util/createHash");
const { mkdirp } = require("../util/fs");
const { mkdirp, dirname, join } = require("../util/fs");
const memoize = require("../util/memoize");
/** @typedef {import("../../declarations/plugins/schemes/HttpUriPlugin").HttpUriPluginOptions} HttpUriPluginOptions */
@ -19,6 +20,18 @@ const memoize = require("../util/memoize");
const getHttp = memoize(() => require("http"));
const getHttps = memoize(() => require("https"));
/** @type {(() => void)[] | undefined} */
let inProgressWrite = undefined;
const validate = createSchemaValidation(
require("../../schemas/plugins/schemes/HttpUriPlugin.check.js"),
() => require("../../schemas/plugins/schemes/HttpUriPlugin.json"),
{
name: "Http Uri Plugin",
baseDataPath: "options"
}
);
const toSafePath = str =>
str
.replace(/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g, "")
@ -104,7 +117,6 @@ class Lockfile {
this.version = 1;
/** @type {Map<string, LockfileEntry | "ignore" | "no-cache">} */
this.entries = new Map();
this.outdated = false;
}
static parse(content) {
@ -251,25 +263,17 @@ const cachedWithKey = (fn, forceFn = fn) => {
return resultFn;
};
/**
* @typedef {Object} HttpUriPluginAdvancedOptions
* @property {string | typeof import("../util/Hash")=} hashFunction
* @property {string=} hashDigest
* @property {number=} hashDigestLength
*/
class HttpUriPlugin {
/**
* @param {HttpUriPluginOptions & HttpUriPluginAdvancedOptions} options options
* @param {HttpUriPluginOptions} options options
*/
constructor(options = {}) {
constructor(options) {
validate(options);
this._lockfileLocation = options.lockfileLocation;
this._cacheLocation = options.cacheLocation;
this._upgrade = options.upgrade;
this._frozen = options.frozen;
this._hashFunction = options.hashFunction;
this._hashDigest = options.hashDigest;
this._hashDigestLength = options.hashDigestLength;
this._allowedUris = options.allowedUris;
}
/**
@ -299,7 +303,8 @@ class HttpUriPlugin {
const logger = compilation.getLogger("webpack.HttpUriPlugin");
const lockfileLocation =
this._lockfileLocation ||
resolve(
join(
intermediateFs,
compiler.context,
compiler.name
? `${toSafePath(compiler.name)}.webpack.lock`
@ -311,11 +316,10 @@ class HttpUriPlugin {
: lockfileLocation + ".data";
const upgrade = this._upgrade || false;
const frozen = this._frozen || false;
const hashFunction = this._hashFunction || "sha512";
const hashDigest =
this._hashDigest || compilation.outputOptions.hashDigest;
const hashDigestLength =
this._hashDigestLength || compilation.outputOptions.hashDigestLength;
const hashFunction = "sha512";
const hashDigest = "hex";
const hashDigestLength = 20;
const allowedUris = this._allowedUris;
let warnedAboutEol = false;
@ -400,11 +404,13 @@ class HttpUriPlugin {
}
);
let outdatedLockfile = undefined;
/** @type {Map<string, LockfileEntry | "ignore" | "no-cache"> | undefined} */
let lockfileUpdates = undefined;
const storeLockEntry = (lockfile, url, entry) => {
const oldEntry = lockfile.entries.get(url);
if (lockfileUpdates === undefined) lockfileUpdates = new Map();
lockfileUpdates.set(url, entry);
lockfile.entries.set(url, entry);
outdatedLockfile = lockfile;
if (!oldEntry) {
logger.log(`${url} added to lockfile`);
} else if (typeof oldEntry === "string") {
@ -440,8 +446,8 @@ class HttpUriPlugin {
if (!cacheLocation || !result.content)
return callback(null, result);
const key = getCacheKey(result.entry.resolved);
const filePath = resolve(cacheLocation, key);
mkdirp(intermediateFs, dirname(filePath), err => {
const filePath = join(intermediateFs, cacheLocation, key);
mkdirp(intermediateFs, dirname(intermediateFs, filePath), err => {
if (err) return callback(err);
intermediateFs.writeFile(filePath, result.content, err => {
if (err) return callback(err);
@ -671,6 +677,19 @@ class HttpUriPlugin {
(url, callback) => fetchContentRaw(url, undefined, callback)
);
const isAllowed = uri => {
for (const allowed of allowedUris) {
if (typeof allowed === "string") {
if (uri.startsWith(allowed)) return true;
} else if (typeof allowed === "function") {
if (allowed(uri)) return true;
} else {
if (allowed.test(uri)) return true;
}
}
return false;
};
const getInfo = cachedWithKey(
/**
* @param {string} url the url
@ -678,6 +697,15 @@ class HttpUriPlugin {
* @returns {void}
*/
(url, callback) => {
if (!isAllowed(url)) {
return callback(
new Error(
`${url} doesn't match the allowedUris policy. These URIs are allowed:\n${allowedUris
.map(uri => ` - ${uri}`)
.join("\n")}`
)
);
}
getLockfile((err, lockfile) => {
if (err) return callback(err);
const entryOrString = lockfile.entries.get(url);
@ -789,7 +817,7 @@ Remove this line from the lockfile to force upgrading.`
// When there is a lockfile cache
// we read the content from there
const key = getCacheKey(entry.resolved);
const filePath = resolve(cacheLocation, key);
const filePath = join(intermediateFs, cacheLocation, key);
fs.readFile(filePath, (err, result) => {
const content = /** @type {Buffer} */ (result);
if (err) {
@ -934,6 +962,7 @@ Run build with un-frozen lockfile to automatically fix lockfile.`
.tapAsync("HttpUriPlugin", (resource, module, callback) => {
return getInfo(resource, (err, result) => {
if (err) return callback(err);
module.buildInfo.resourceIntegrity = result.entry.integrity;
callback(null, result.content);
});
});
@ -963,12 +992,59 @@ Run build with un-frozen lockfile to automatically fix lockfile.`
compilation.hooks.finishModules.tapAsync(
"HttpUriPlugin",
(modules, callback) => {
if (!outdatedLockfile) return callback();
intermediateFs.writeFile(
lockfileLocation,
outdatedLockfile.toString(),
callback
if (!lockfileUpdates) return callback();
const ext = extname(lockfileLocation);
const tempFile = join(
intermediateFs,
dirname(intermediateFs, lockfileLocation),
`.${basename(lockfileLocation, ext)}.${
(Math.random() * 10000) | 0
}${ext}`
);
const writeDone = () => {
const nextOperation = inProgressWrite.shift();
if (nextOperation) {
nextOperation();
} else {
inProgressWrite = undefined;
}
};
const runWrite = () => {
intermediateFs.readFile(lockfileLocation, (err, buffer) => {
if (err && err.code !== "ENOENT") {
writeDone();
return callback(err);
}
const lockfile = buffer
? Lockfile.parse(buffer.toString("utf-8"))
: new Lockfile();
for (const [key, value] of lockfileUpdates) {
lockfile.entries.set(key, value);
}
intermediateFs.writeFile(tempFile, lockfile.toString(), err => {
if (err) {
writeDone();
return intermediateFs.unlink(tempFile, () => callback(err));
}
intermediateFs.rename(tempFile, lockfileLocation, err => {
if (err) {
writeDone();
return intermediateFs.unlink(tempFile, () =>
callback(err)
);
}
writeDone();
callback();
});
});
});
};
if (inProgressWrite) {
inProgressWrite.push(runWrite);
} else {
inProgressWrite = [];
}
}
);
}

View File

@ -83,6 +83,8 @@ const path = require("path");
* @returns {Watcher} a watcher
*/
// TODO webpack 6 make optional methods required
/**
* @typedef {Object} OutputFileSystem
* @property {function(string, Buffer|string, Callback): void} writeFile

File diff suppressed because one or more lines are too long

View File

@ -678,6 +678,7 @@
"Experiments": {
"description": "Enables/Disables experiments (experimental features with relax SemVer compatibility).",
"type": "object",
"implements": ["#/definitions/ExperimentsCommon"],
"additionalProperties": false,
"properties": {
"asset": {
@ -692,7 +693,7 @@
"description": "Build http(s): urls using a lockfile and resource content cache.",
"anyOf": [
{
"type": "boolean"
"$ref": "#/definitions/HttpUriAllowedUris"
},
{
"$ref": "#/definitions/HttpUriOptions"
@ -718,43 +719,102 @@
"type": "boolean"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"backend": {
"description": "A custom backend.",
"instanceof": "Function",
"tsType": "(((compiler: import('../lib/Compiler'), client: string, callback: (err?: Error, api?: any) => void) => void) | ((compiler: import('../lib/Compiler'), client: string) => Promise<any>))"
},
"client": {
"description": "A custom client.",
"type": "string"
},
"entries": {
"description": "Enable/disable lazy compilation for entries.",
"type": "boolean"
},
"imports": {
"description": "Enable/disable lazy compilation for import() modules.",
"type": "boolean"
},
"test": {
"description": "Specify which entrypoints or import()ed modules should be lazily compiled. This is matched with the imported module and not the entrypoint name.",
"anyOf": [
{
"instanceof": "RegExp",
"tsType": "RegExp"
},
{
"type": "string"
},
{
"instanceof": "Function",
"tsType": "((module: import('../lib/Module')) => boolean)"
}
]
}
}
"$ref": "#/definitions/LazyCompilationOptions"
}
]
},
"outputModule": {
"description": "Allow output javascript files as module source type.",
"type": "boolean"
},
"syncWebAssembly": {
"description": "Support WebAssembly as synchronous EcmaScript Module (outdated).",
"type": "boolean"
},
"topLevelAwait": {
"description": "Allow using top-level-await in EcmaScript Modules.",
"type": "boolean"
}
}
},
"ExperimentsCommon": {
"description": "Enables/Disables experiments (experimental features with relax SemVer compatibility).",
"type": "object",
"additionalProperties": false,
"properties": {
"asset": {
"description": "Allow module type 'asset' to generate assets.",
"type": "boolean"
},
"asyncWebAssembly": {
"description": "Support WebAssembly as asynchronous EcmaScript Module.",
"type": "boolean"
},
"cacheUnaffected": {
"description": "Enable additional in memory caching of modules that are unchanged and reference only unchanged modules.",
"type": "boolean"
},
"futureDefaults": {
"description": "Apply defaults of next major version.",
"type": "boolean"
},
"layers": {
"description": "Enable module and chunk layers.",
"type": "boolean"
},
"outputModule": {
"description": "Allow output javascript files as module source type.",
"type": "boolean"
},
"syncWebAssembly": {
"description": "Support WebAssembly as synchronous EcmaScript Module (outdated).",
"type": "boolean"
},
"topLevelAwait": {
"description": "Allow using top-level-await in EcmaScript Modules.",
"type": "boolean"
}
}
},
"ExperimentsNormalized": {
"description": "Enables/Disables experiments (experimental features with relax SemVer compatibility).",
"type": "object",
"implements": ["#/definitions/ExperimentsCommon"],
"additionalProperties": false,
"properties": {
"asset": {
"description": "Allow module type 'asset' to generate assets.",
"type": "boolean"
},
"asyncWebAssembly": {
"description": "Support WebAssembly as asynchronous EcmaScript Module.",
"type": "boolean"
},
"buildHttp": {
"description": "Build http(s): urls using a lockfile and resource content cache.",
"oneOf": [
{
"$ref": "#/definitions/HttpUriOptions"
}
]
},
"cacheUnaffected": {
"description": "Enable additional in memory caching of modules that are unchanged and reference only unchanged modules.",
"type": "boolean"
},
"futureDefaults": {
"description": "Apply defaults of next major version.",
"type": "boolean"
},
"layers": {
"description": "Enable module and chunk layers.",
"type": "boolean"
},
"lazyCompilation": {
"description": "Compile entrypoints and import()s only when they are accessed.",
"oneOf": [
{
"$ref": "#/definitions/LazyCompilationOptions"
}
]
},
@ -1223,11 +1283,25 @@
"type": "string",
"absolutePath": false
},
"HttpUriAllowedUris": {
"description": "List of allowed URIs for building http resources.",
"cli": {
"exclude": true
},
"oneOf": [
{
"$ref": "#/definitions/HttpUriOptionsAllowedUris"
}
]
},
"HttpUriOptions": {
"description": "Options for building http resources.",
"type": "object",
"additionalProperties": false,
"properties": {
"allowedUris": {
"$ref": "#/definitions/HttpUriOptionsAllowedUris"
},
"cacheLocation": {
"description": "Location where resource content is stored for lockfile entries. It's also possible to disable storing by passing false.",
"anyOf": [
@ -1253,6 +1327,31 @@
"description": "When set, resources of existing lockfile entries will be fetched and entries will be upgraded when resource content has changed.",
"type": "boolean"
}
},
"required": ["allowedUris"]
},
"HttpUriOptionsAllowedUris": {
"description": "List of allowed URIs (resp. the beginning of them).",
"type": "array",
"items": {
"description": "List of allowed URIs (resp. the beginning of them).",
"anyOf": [
{
"description": "Allowed URI pattern.",
"instanceof": "RegExp",
"tsType": "RegExp"
},
{
"description": "Allowed URI (resp. the beginning of it).",
"type": "string",
"pattern": "^https?://"
},
{
"description": "Allowed URI filter function.",
"instanceof": "Function",
"tsType": "((uri: string) => boolean)"
}
]
}
},
"IgnoreWarnings": {
@ -1516,6 +1615,46 @@
}
]
},
"LazyCompilationOptions": {
"description": "Options for compiling entrypoints and import()s only when they are accessed.",
"type": "object",
"additionalProperties": false,
"properties": {
"backend": {
"description": "A custom backend.",
"instanceof": "Function",
"tsType": "(((compiler: import('../lib/Compiler'), client: string, callback: (err?: Error, api?: any) => void) => void) | ((compiler: import('../lib/Compiler'), client: string) => Promise<any>))"
},
"client": {
"description": "A custom client.",
"type": "string"
},
"entries": {
"description": "Enable/disable lazy compilation for entries.",
"type": "boolean"
},
"imports": {
"description": "Enable/disable lazy compilation for import() modules.",
"type": "boolean"
},
"test": {
"description": "Specify which entrypoints or import()ed modules should be lazily compiled. This is matched with the imported module and not the entrypoint name.",
"anyOf": [
{
"instanceof": "RegExp",
"tsType": "RegExp"
},
{
"type": "string"
},
{
"instanceof": "Function",
"tsType": "((module: import('../lib/Module')) => boolean)"
}
]
}
}
},
"Library": {
"description": "Make the output files a library, exporting the exports of the entry point.",
"anyOf": [
@ -4703,7 +4842,7 @@
"$ref": "#/definitions/EntryNormalized"
},
"experiments": {
"$ref": "#/definitions/Experiments"
"$ref": "#/definitions/ExperimentsNormalized"
},
"externals": {
"$ref": "#/definitions/Externals"

View File

@ -3,4 +3,4 @@
* DO NOT MODIFY BY HAND.
* Run `yarn special-lint-fix` to update
*/
const o=/^(?:[A-Za-z]:[\\/]|\\\\|\/)/;function t(e,{instancePath:n="",parentData:s,parentDataProperty:l,rootData:a=e}={}){let i=null,r=0;const c=r;let p=!1,u=null;const f=r;if(r==r)if(e&&"object"==typeof e&&!Array.isArray(e)){const t=r;for(const o in e)if("cacheLocation"!==o&&"frozen"!==o&&"lockfileLocation"!==o&&"upgrade"!==o){const t={params:{additionalProperty:o}};null===i?i=[t]:i.push(t),r++;break}if(t===r){if(void 0!==e.cacheLocation){let t=e.cacheLocation;const n=r,s=r;let l=!1;const a=r;if(!1!==t){const o={params:{}};null===i?i=[o]:i.push(o),r++}var h=a===r;if(l=l||h,!l){const e=r;if(r===e)if("string"==typeof t){if(t.includes("!")||!0!==o.test(t)){const o={params:{}};null===i?i=[o]:i.push(o),r++}}else{const o={params:{type:"string"}};null===i?i=[o]:i.push(o),r++}h=e===r,l=l||h}if(l)r=s,null!==i&&(s?i.length=s:i=null);else{const o={params:{}};null===i?i=[o]:i.push(o),r++}var d=n===r}else d=!0;if(d){if(void 0!==e.frozen){const o=r;if("boolean"!=typeof e.frozen){const o={params:{type:"boolean"}};null===i?i=[o]:i.push(o),r++}d=o===r}else d=!0;if(d){if(void 0!==e.lockfileLocation){let t=e.lockfileLocation;const n=r;if(r===n)if("string"==typeof t){if(t.includes("!")||!0!==o.test(t)){const o={params:{}};null===i?i=[o]:i.push(o),r++}}else{const o={params:{type:"string"}};null===i?i=[o]:i.push(o),r++}d=n===r}else d=!0;if(d)if(void 0!==e.upgrade){const o=r;if("boolean"!=typeof e.upgrade){const o={params:{type:"boolean"}};null===i?i=[o]:i.push(o),r++}d=o===r}else d=!0}}}}else{const o={params:{type:"object"}};null===i?i=[o]:i.push(o),r++}if(f===r&&(p=!0,u=0),!p){const o={params:{passingSchemas:u}};return null===i?i=[o]:i.push(o),r++,t.errors=i,!1}return r=c,null!==i&&(c?i.length=c:i=null),t.errors=i,0===r}module.exports=t,module.exports.default=t;
const r=/^(?:[A-Za-z]:[\\/]|\\\\|\/)/;module.exports=n,module.exports.default=n;const t=new RegExp("^https?://","u");function e(n,{instancePath:o="",parentData:s,parentDataProperty:a,rootData:l=n}={}){let i=null,p=0;if(0===p){if(!n||"object"!=typeof n||Array.isArray(n))return e.errors=[{params:{type:"object"}}],!1;{let o;if(void 0===n.allowedUris&&(o="allowedUris"))return e.errors=[{params:{missingProperty:o}}],!1;{const o=p;for(const r in n)if("allowedUris"!==r&&"cacheLocation"!==r&&"frozen"!==r&&"lockfileLocation"!==r&&"upgrade"!==r)return e.errors=[{params:{additionalProperty:r}}],!1;if(o===p){if(void 0!==n.allowedUris){let r=n.allowedUris;const o=p;if(p==p){if(!Array.isArray(r))return e.errors=[{params:{type:"array"}}],!1;{const n=r.length;for(let o=0;o<n;o++){let n=r[o];const s=p,a=p;let l=!1;const f=p;if(!(n instanceof RegExp)){const r={params:{}};null===i?i=[r]:i.push(r),p++}var c=f===p;if(l=l||c,!l){const r=p;if(p===r)if("string"==typeof n){if(!t.test(n)){const r={params:{pattern:"^https?://"}};null===i?i=[r]:i.push(r),p++}}else{const r={params:{type:"string"}};null===i?i=[r]:i.push(r),p++}if(c=r===p,l=l||c,!l){const r=p;if(!(n instanceof Function)){const r={params:{}};null===i?i=[r]:i.push(r),p++}c=r===p,l=l||c}}if(!l){const r={params:{}};return null===i?i=[r]:i.push(r),p++,e.errors=i,!1}if(p=a,null!==i&&(a?i.length=a:i=null),s!==p)break}}}var f=o===p}else f=!0;if(f){if(void 0!==n.cacheLocation){let t=n.cacheLocation;const o=p,s=p;let a=!1;const l=p;if(!1!==t){const r={params:{}};null===i?i=[r]:i.push(r),p++}var u=l===p;if(a=a||u,!a){const e=p;if(p===e)if("string"==typeof t){if(t.includes("!")||!0!==r.test(t)){const r={params:{}};null===i?i=[r]:i.push(r),p++}}else{const r={params:{type:"string"}};null===i?i=[r]:i.push(r),p++}u=e===p,a=a||u}if(!a){const r={params:{}};return null===i?i=[r]:i.push(r),p++,e.errors=i,!1}p=s,null!==i&&(s?i.length=s:i=null),f=o===p}else f=!0;if(f){if(void 0!==n.frozen){const r=p;if("boolean"!=typeof n.frozen)return e.errors=[{params:{type:"boolean"}}],!1;f=r===p}else f=!0;if(f){if(void 0!==n.lockfileLocation){let t=n.lockfileLocation;const o=p;if(p===o){if("string"!=typeof t)return e.errors=[{params:{type:"string"}}],!1;if(t.includes("!")||!0!==r.test(t))return e.errors=[{params:{}}],!1}f=o===p}else f=!0;if(f)if(void 0!==n.upgrade){const r=p;if("boolean"!=typeof n.upgrade)return e.errors=[{params:{type:"boolean"}}],!1;f=r===p}else f=!0}}}}}}}return e.errors=i,0===p}function n(r,{instancePath:t="",parentData:o,parentDataProperty:s,rootData:a=r}={}){let l=null,i=0;const p=i;let c=!1,f=null;const u=i;if(e(r,{instancePath:t,parentData:o,parentDataProperty:s,rootData:a})||(l=null===l?e.errors:l.concat(e.errors),i=l.length),u===i&&(c=!0,f=0),!c){const r={params:{passingSchemas:f}};return null===l?l=[r]:l.push(r),i++,n.errors=l,!1}return i=p,null!==l&&(p?l.length=p:l=null),n.errors=l,0===i}

View File

@ -5,6 +5,9 @@
"type": "object",
"additionalProperties": false,
"properties": {
"allowedUris": {
"$ref": "#/definitions/HttpUriOptionsAllowedUris"
},
"cacheLocation": {
"description": "Location where resource content is stored for lockfile entries. It's also possible to disable storing by passing false.",
"anyOf": [
@ -30,6 +33,31 @@
"description": "When set, resources of existing lockfile entries will be fetched and entries will be upgraded when resource content has changed.",
"type": "boolean"
}
},
"required": ["allowedUris"]
},
"HttpUriOptionsAllowedUris": {
"description": "List of allowed URIs (resp. the beginning of them).",
"type": "array",
"items": {
"description": "List of allowed URIs (resp. the beginning of them).",
"anyOf": [
{
"description": "Allowed URI pattern.",
"instanceof": "RegExp",
"tsType": "RegExp"
},
{
"description": "Allowed URI (resp. the beginning of it).",
"type": "string",
"pattern": "^https?://"
},
{
"description": "Allowed URI filter function.",
"instanceof": "Function",
"tsType": "((uri: string) => boolean)"
}
]
}
}
},

View File

@ -93,11 +93,11 @@ Object {
"experiments": Object {
"asset": false,
"asyncWebAssembly": false,
"buildHttp": false,
"buildHttp": undefined,
"cacheUnaffected": false,
"futureDefaults": false,
"layers": false,
"lazyCompilation": false,
"lazyCompilation": undefined,
"outputModule": false,
"syncWebAssembly": false,
"topLevelAwait": false,

View File

@ -488,16 +488,35 @@ Object {
"multiple": false,
"simpleType": "boolean",
},
"experiments-build-http": Object {
"experiments-build-http-allowed-uris": Object {
"configs": Array [
Object {
"description": "Build http(s): urls using a lockfile and resource content cache.",
"multiple": false,
"path": "experiments.buildHttp",
"type": "boolean",
"description": "Allowed URI pattern.",
"multiple": true,
"path": "experiments.buildHttp.allowedUris[]",
"type": "RegExp",
},
Object {
"description": "Allowed URI (resp. the beginning of it).",
"multiple": true,
"path": "experiments.buildHttp.allowedUris[]",
"type": "string",
},
],
"description": "Build http(s): urls using a lockfile and resource content cache.",
"description": "Allowed URI pattern. Allowed URI (resp. the beginning of it).",
"multiple": true,
"simpleType": "string",
},
"experiments-build-http-allowed-uris-reset": Object {
"configs": Array [
Object {
"description": "Clear all items provided in 'experiments.buildHttp.allowedUris' configuration. List of allowed URIs (resp. the beginning of them).",
"multiple": false,
"path": "experiments.buildHttp.allowedUris",
"type": "reset",
},
],
"description": "Clear all items provided in 'experiments.buildHttp.allowedUris' configuration. List of allowed URIs (resp. the beginning of them).",
"multiple": false,
"simpleType": "boolean",
},

View File

@ -29,6 +29,10 @@ const base = {
};
const frozen = true;
const allowedUris = [
"http://localhost:9990/",
"https://raw.githubusercontent.com/"
];
module.exports = [
{
@ -37,6 +41,7 @@ module.exports = [
plugins: [
serverPlugin,
new HttpUriPlugin({
allowedUris,
upgrade: true,
frozen
})
@ -48,7 +53,8 @@ module.exports = [
plugins: [
serverPlugin,
new HttpUriPlugin({
upgrade: true,
allowedUris,
upgrade: false,
frozen: false
})
]
@ -59,6 +65,7 @@ module.exports = [
plugins: [
serverPlugin,
new HttpUriPlugin({
allowedUris,
upgrade: false,
frozen
})
@ -70,6 +77,7 @@ module.exports = [
plugins: [
serverPlugin,
new HttpUriPlugin({
allowedUris,
cacheLocation: false,
frozen
})
@ -82,6 +90,7 @@ module.exports = [
plugins: [
serverPlugin,
new HttpUriPlugin({
allowedUris,
upgrade: true,
frozen: true
})

138
types.d.ts vendored
View File

@ -3289,11 +3289,12 @@ declare interface ExecuteModuleResult {
missingDependencies: LazySet<string>;
buildDependencies: LazySet<string>;
}
type Experiments = ExperimentsCommon & ExperimentsExtra;
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
declare interface Experiments {
declare interface ExperimentsCommon {
/**
* Allow module type 'asset' to generate assets.
*/
@ -3304,11 +3305,6 @@ declare interface Experiments {
*/
asyncWebAssembly?: boolean;
/**
* Build http(s): urls using a lockfile and resource content cache.
*/
buildHttp?: boolean | HttpUriOptions;
/**
* Enable additional in memory caching of modules that are unchanged and reference only unchanged modules.
*/
@ -3324,40 +3320,6 @@ declare interface Experiments {
*/
layers?: boolean;
/**
* Compile entrypoints and import()s only when they are accessed.
*/
lazyCompilation?:
| boolean
| {
/**
* A custom backend.
*/
backend?:
| ((
compiler: Compiler,
client: string,
callback: (err?: Error, api?: any) => void
) => void)
| ((compiler: Compiler, client: string) => Promise<any>);
/**
* A custom client.
*/
client?: string;
/**
* Enable/disable lazy compilation for entries.
*/
entries?: boolean;
/**
* Enable/disable lazy compilation for import() modules.
*/
imports?: boolean;
/**
* Specify which entrypoints or import()ed modules should be lazily compiled. This is matched with the imported module and not the entrypoint name.
*/
test?: string | RegExp | ((module: Module) => boolean);
};
/**
* Allow output javascript files as module source type.
*/
@ -3373,6 +3335,37 @@ declare interface Experiments {
*/
topLevelAwait?: boolean;
}
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
declare interface ExperimentsExtra {
/**
* Build http(s): urls using a lockfile and resource content cache.
*/
buildHttp?: HttpUriOptions | (string | RegExp | ((uri: string) => boolean))[];
/**
* Compile entrypoints and import()s only when they are accessed.
*/
lazyCompilation?: boolean | LazyCompilationOptions;
}
type ExperimentsNormalized = ExperimentsCommon & ExperimentsNormalizedExtra;
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
declare interface ExperimentsNormalizedExtra {
/**
* Build http(s): urls using a lockfile and resource content cache.
*/
buildHttp?: HttpUriOptions;
/**
* Compile entrypoints and import()s only when they are accessed.
*/
lazyCompilation?: LazyCompilationOptions;
}
declare abstract class ExportInfo {
name: string;
@ -4382,6 +4375,11 @@ declare class HotUpdateChunk extends Chunk {
* Options for building http resources.
*/
declare interface HttpUriOptions {
/**
* List of allowed URIs (resp. the beginning of them).
*/
allowedUris: (string | RegExp | ((uri: string) => boolean))[];
/**
* Location where resource content is stored for lockfile entries. It's also possible to disable storing by passing false.
*/
@ -4403,27 +4401,7 @@ declare interface HttpUriOptions {
upgrade?: boolean;
}
declare class HttpUriPlugin {
constructor(options?: {
/**
* Location where resource content is stored for lockfile entries. It's also possible to disable storing by passing false.
*/
cacheLocation?: string | false;
/**
* When set, anything that would lead to a modification of the lockfile or any resource content, will result in an error.
*/
frozen?: boolean;
/**
* Location of the lockfile.
*/
lockfileLocation?: string;
/**
* When set, resources of existing lockfile entries will be fetched and entries will be upgraded when resource content has changed.
*/
upgrade?: boolean;
hashFunction?: string | typeof Hash;
hashDigest?: string;
hashDigestLength?: number;
});
constructor(options: HttpUriOptions);
/**
* Apply the plugin
@ -5814,6 +5792,42 @@ declare interface KnownStatsProfile {
factory: number;
dependencies: number;
}
/**
* Options for compiling entrypoints and import()s only when they are accessed.
*/
declare interface LazyCompilationOptions {
/**
* A custom backend.
*/
backend?:
| ((
compiler: Compiler,
client: string,
callback: (err?: Error, api?: any) => void
) => void)
| ((compiler: Compiler, client: string) => Promise<any>);
/**
* A custom client.
*/
client?: string;
/**
* Enable/disable lazy compilation for entries.
*/
entries?: boolean;
/**
* Enable/disable lazy compilation for import() modules.
*/
imports?: boolean;
/**
* Specify which entrypoints or import()ed modules should be lazily compiled. This is matched with the imported module and not the entrypoint name.
*/
test?: string | RegExp | ((module: Module) => boolean);
}
declare class LazySet<T> {
constructor(iterable?: Iterable<T>);
readonly size: number;
@ -11837,7 +11851,7 @@ declare interface WebpackOptionsNormalized {
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
*/
experiments: Experiments;
experiments: ExperimentsNormalized;
/**
* Specify dependencies that shouldn't be resolved by webpack, but should become dependencies of the resulting bundle. The kind of the dependency depends on `output.libraryTarget`.