Merge pull request #11095 from vankop/support-url-requests

Support url requests
This commit is contained in:
Tobias Koppers 2020-07-06 20:03:52 +02:00 committed by GitHub
commit 1bb02df61e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1096 additions and 299 deletions

View File

@ -1059,6 +1059,10 @@ export interface RuleSetRule {
* Match the resource path of the module.
*/
resource?: RuleSetConditionOrConditionsAbsolute;
/**
* Match the resource fragment of the module.
*/
resourceFragment?: RuleSetConditionOrConditions;
/**
* Match the resource query of the module.
*/

View File

@ -875,7 +875,8 @@ class Compiler {
context: this.options.context,
fs: this.inputFileSystem,
resolverFactory: this.resolverFactory,
options: this.options.module || {}
options: this.options.module || {},
associatedObjectForCache: this.root
});
this.hooks.normalModuleFactory.call(normalModuleFactory);
return normalModuleFactory;

View File

@ -8,14 +8,10 @@
const CachedConstDependency = require("./dependencies/CachedConstDependency");
const ConstDependency = require("./dependencies/ConstDependency");
const { evaluateToString } = require("./javascript/JavascriptParserHelpers");
const { parseResource } = require("./util/identifier");
/** @typedef {import("./Compiler")} Compiler */
const getQuery = request => {
const i = request.indexOf("?");
return i !== -1 ? request.substr(i) : "";
};
const collectDeclaration = (declarations, pattern) => {
const stack = [pattern];
while (stack.length > 0) {
@ -117,6 +113,7 @@ class ConstPlugin {
* @returns {void}
*/
apply(compiler) {
const cacheParseResource = parseResource.bindCache(compiler.root);
compiler.hooks.compilation.tap(
"ConstPlugin",
(compilation, { normalModuleFactory }) => {
@ -327,9 +324,9 @@ class ConstPlugin {
.tap("ConstPlugin", expr => {
if (parser.scope.isAsmJs) return;
if (!parser.state.module) return;
return evaluateToString(getQuery(parser.state.module.resource))(
expr
);
return evaluateToString(
cacheParseResource(parser.state.module.resource).query
)(expr);
});
parser.hooks.expression
.for("__resourceQuery")
@ -337,7 +334,9 @@ class ConstPlugin {
if (parser.scope.isAsmJs) return;
if (!parser.state.module) return;
const dep = new CachedConstDependency(
JSON.stringify(getQuery(parser.state.module.resource)),
JSON.stringify(
cacheParseResource(parser.state.module.resource).query
),
expr.range,
"__resourceQuery"
);
@ -345,6 +344,32 @@ class ConstPlugin {
parser.state.module.addPresentationalDependency(dep);
return true;
});
parser.hooks.evaluateIdentifier
.for("__resourceFragment")
.tap("ConstPlugin", expr => {
if (parser.scope.isAsmJs) return;
if (!parser.state.module) return;
return evaluateToString(
cacheParseResource(parser.state.module.resource).fragment
)(expr);
});
parser.hooks.expression
.for("__resourceFragment")
.tap("ConstPlugin", expr => {
if (parser.scope.isAsmJs) return;
if (!parser.state.module) return;
const dep = new CachedConstDependency(
JSON.stringify(
cacheParseResource(parser.state.module.resource).fragment
),
expr.range,
"__resourceFragment"
);
dep.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep);
return true;
});
};
normalModuleFactory.hooks.parser

View File

@ -19,7 +19,7 @@ const {
keepOriginalOrder
} = require("./util/comparators");
const { compareModulesById } = require("./util/comparators");
const { contextify } = require("./util/identifier");
const { contextify, parseResource } = require("./util/identifier");
const makeSerializable = require("./util/makeSerializable");
/** @typedef {import("webpack-sources").Source} Source */
@ -60,6 +60,7 @@ const makeSerializable = require("./util/makeSerializable");
* @typedef {Object} ContextModuleOptionsExtras
* @property {string} resource
* @property {string=} resourceQuery
* @property {string=} resourceFragment
* @property {TODO} resolveOptions
*/
@ -86,21 +87,10 @@ class ContextModule extends Module {
* @param {ContextModuleOptions} options options object
*/
constructor(resolveDependencies, options) {
let resource;
let resourceQuery;
if (options.resourceQuery) {
resource = options.resource;
resourceQuery = options.resourceQuery;
} else {
const queryIdx = options.resource.indexOf("?");
if (queryIdx >= 0) {
resource = options.resource.substr(0, queryIdx);
resourceQuery = options.resource.substr(queryIdx);
} else {
resource = options.resource;
resourceQuery = "";
}
}
const parsed = parseResource(options.resource);
const resource = parsed.path;
const resourceQuery = options.resourceQuery || parsed.query;
const resourceFragment = options.resourceFragment || parsed.fragment;
super("javascript/dynamic", resource);
@ -108,8 +98,9 @@ class ContextModule extends Module {
this.resolveDependencies = resolveDependencies;
/** @type {Omit<ContextModuleOptions, "resolveOptions">} */
this.options = {
resource: resource,
resourceQuery: resourceQuery,
resource,
resourceQuery,
resourceFragment,
mode: options.mode,
recursive: options.recursive,
addon: options.addon,
@ -169,6 +160,9 @@ class ContextModule extends Module {
if (this.options.resourceQuery) {
identifier += `|${this.options.resourceQuery}`;
}
if (this.options.resourceFragment) {
identifier += `|${this.options.resourceFragment}`;
}
if (this.options.mode) {
identifier += `|${this.options.mode}`;
}

View File

@ -232,6 +232,7 @@ module.exports = class ContextModuleFactory extends ModuleFactory {
const {
resource,
resourceQuery,
resourceFragment,
recursive,
regExp,
include,
@ -286,7 +287,7 @@ module.exports = class ContextModuleFactory extends ModuleFactory {
.filter(obj => regExp.test(obj.request))
.map(obj => {
const dep = new ContextElementDependency(
obj.request + resourceQuery,
obj.request + resourceQuery + resourceFragment,
obj.request,
category,
referencedExports

View File

@ -141,7 +141,7 @@ const createResolveDependenciesFromContextMap = createContextMap => {
if (err) return callback(err);
const dependencies = Object.keys(map).map(key => {
return new ContextElementDependency(
map[key] + options.resourceQuery,
map[key] + options.resourceQuery + options.resourceFragment,
key,
options.category,
options.referencedExports

View File

@ -9,7 +9,7 @@ const parseJson = require("json-parse-better-errors");
const { getContext, runLoaders } = require("loader-runner");
const querystring = require("querystring");
const validateOptions = require("schema-utils");
const { SyncHook } = require("tapable");
const { HookMap, SyncHook, AsyncSeriesBailHook } = require("tapable");
const {
CachedSource,
OriginalSource,
@ -23,8 +23,9 @@ const ModuleError = require("./ModuleError");
const ModuleParseError = require("./ModuleParseError");
const ModuleWarning = require("./ModuleWarning");
const RuntimeGlobals = require("./RuntimeGlobals");
const UnhandledSchemeError = require("./UnhandledSchemeError");
const WebpackError = require("./WebpackError");
const { decodeDataURI } = require("./util/DataURI");
const { getScheme } = require("./util/URLAbsoluteSpecifier");
const {
compareLocations,
concatComparators,
@ -125,21 +126,6 @@ const asBuffer = input => {
return input;
};
const readResourceFn = fs => {
return (resource, callback) => {
const decodedData = decodeDataURI(resource);
if (decodedData) {
process.nextTick(() => {
callback(null, decodedData);
});
return;
}
fs.readFile(resource, callback);
};
};
class NonErrorEmittedError extends WebpackError {
constructor(error) {
super();
@ -160,6 +146,7 @@ makeSerializable(
/**
* @typedef {Object} NormalModuleCompilationHooks
* @property {SyncHook<[object, NormalModule]>} loader
* @property {HookMap<AsyncSeriesBailHook<[string, NormalModule], string | Buffer>>} readResourceForScheme
*/
/** @type {WeakMap<Compilation, NormalModuleCompilationHooks>} */
@ -179,7 +166,10 @@ class NormalModule extends Module {
let hooks = compilationHooksMap.get(compilation);
if (hooks === undefined) {
hooks = {
loader: new SyncHook(["loaderContext", "module"])
loader: new SyncHook(["loaderContext", "module"]),
readResourceForScheme: new HookMap(
() => new AsyncSeriesBailHook(["resource", "module"])
)
};
compilationHooksMap.set(compilation, hooks);
}
@ -304,6 +294,7 @@ class NormalModule extends Module {
updateCacheModule(module) {
super.updateCacheModule(module);
const m = /** @type {NormalModule} */ (module);
this.binary = m.binary;
this.request = m.request;
this.userRequest = m.userRequest;
this.rawRequest = m.rawRequest;
@ -623,7 +614,22 @@ class NormalModule extends Module {
resource: this.resource,
loaders: this.loaders,
context: loaderContext,
readResource: readResourceFn(fs)
readResource: (resource, callback) => {
const scheme = getScheme(resource);
if (scheme) {
NormalModule.getCompilationHooks(compilation)
.readResourceForScheme.for(scheme)
.callAsync(resource, this, (err, result) => {
if (err) return callback(err);
if (typeof result !== "string" && !result) {
return callback(new UnhandledSchemeError(scheme, resource));
}
return callback(null, result);
});
} else {
fs.readFile(resource, callback);
}
}
},
(err, result) => {
if (!result) {
@ -959,9 +965,6 @@ class NormalModule extends Module {
serialize(context) {
const { write } = context;
// constructor
write(this.type);
write(this.resource);
// deserialize
write(this._source);
write(this._sourceSizes);
@ -974,11 +977,10 @@ class NormalModule extends Module {
}
static deserialize(context) {
const { read } = context;
const obj = new NormalModule({
type: read(),
resource: read(),
// will be filled by updateCacheModule
type: "",
resource: "",
request: null,
userRequest: null,
rawRequest: null,

View File

@ -21,10 +21,11 @@ const BasicEffectRulePlugin = require("./rules/BasicEffectRulePlugin");
const BasicMatcherRulePlugin = require("./rules/BasicMatcherRulePlugin");
const RuleSetCompiler = require("./rules/RuleSetCompiler");
const UseEffectRulePlugin = require("./rules/UseEffectRulePlugin");
const { getMimetype } = require("./util/DataURI");
const LazySet = require("./util/LazySet");
const { getScheme } = require("./util/URLAbsoluteSpecifier");
const { cachedCleverMerge, cachedSetProperty } = require("./util/cleverMerge");
const { join } = require("./util/fs");
const { parseResource } = require("./util/identifier");
/** @typedef {import("../declarations/WebpackOptions").ModuleOptions} ModuleOptions */
/** @typedef {import("./Generator")} Generator */
@ -48,6 +49,16 @@ const { join } = require("./util/fs");
* @property {boolean} cacheable allow to use the unsafe cache
*/
/**
* @typedef {Object} ResourceData
* @property {string} resource
* @property {string} path
* @property {string} query
* @property {string} fragment
*/
/** @typedef {ResourceData & { data: Record<string, any> }} ResourceDataWithData */
const EMPTY_RESOLVE_OPTIONS = {};
const MATCH_RESOURCE_REGEX = /^([^!]+)!=!/;
@ -124,6 +135,7 @@ const ruleSetCompiler = new RuleSetCompiler([
new BasicMatcherRulePlugin("exclude", "resource", true),
new BasicMatcherRulePlugin("resource"),
new BasicMatcherRulePlugin("resourceQuery"),
new BasicMatcherRulePlugin("resourceFragment"),
new BasicMatcherRulePlugin("realResource"),
new BasicMatcherRulePlugin("issuer"),
new BasicMatcherRulePlugin("compiler"),
@ -142,12 +154,23 @@ class NormalModuleFactory extends ModuleFactory {
* @param {InputFileSystem} param.fs file system
* @param {ResolverFactory} param.resolverFactory resolverFactory
* @param {ModuleOptions} param.options options
* @param {Object=} param.associatedObjectForCache an object to which the cache will be attached
*/
constructor({ context, fs, resolverFactory, options }) {
constructor({
context,
fs,
resolverFactory,
options,
associatedObjectForCache
}) {
super();
this.hooks = Object.freeze({
/** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
resolve: new AsyncSeriesBailHook(["resolveData"]),
/** @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>} */
resolveForScheme: new HookMap(
() => new AsyncSeriesBailHook(["resourceData", "resolveData"])
),
/** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
factorize: new AsyncSeriesBailHook(["resolveData"]),
/** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
@ -191,6 +214,11 @@ class NormalModuleFactory extends ModuleFactory {
* @type {Map<string, WeakMap<Object, Generator>>}
*/
this.generatorCache = new Map();
const cacheParseResource = parseResource.bindCache(
associatedObjectForCache
);
this.hooks.factorize.tapAsync(
{
name: "NormalModuleFactory",
@ -266,24 +294,14 @@ class NormalModuleFactory extends ModuleFactory {
} = data;
const loaderResolver = this.getResolver("loader");
const normalResolver = this.getResolver(
"normal",
dependencies.length > 0
? cachedSetProperty(
resolveOptions || EMPTY_RESOLVE_OPTIONS,
"dependencyType",
dependencies[0].category
)
: resolveOptions
);
/** @type {string} */
let matchResource = undefined;
/** @type {ResourceData | undefined} */
let matchResourceData = undefined;
/** @type {string} */
let requestWithoutMatchResource = request;
const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);
if (matchResourceMatch) {
matchResource = matchResourceMatch[1];
let matchResource = matchResourceMatch[1];
if (matchResource.charCodeAt(0) === 46) {
// 46 === ".", 47 === "/"
const secondChar = matchResource.charCodeAt(1);
@ -295,6 +313,10 @@ class NormalModuleFactory extends ModuleFactory {
matchResource = join(this.fs, context, matchResource);
}
}
matchResourceData = {
resource: matchResource,
...cacheParseResource(matchResource)
};
requestWithoutMatchResource = request.substr(
matchResourceMatch[0].length
);
@ -319,11 +341,11 @@ class NormalModuleFactory extends ModuleFactory {
contextDependencies
};
let dataURIMimetype = getMimetype(unresolvedResource);
/** @type {ResourceDataWithData} */
let resourceData;
/** @type {string | undefined} */
const scheme = getScheme(unresolvedResource);
/** @type {string | false} */
let resource;
let resourceResolveData;
let loaders;
const continueCallback = needCalls(2, err => {
@ -354,7 +376,7 @@ class NormalModuleFactory extends ModuleFactory {
return callback(e);
}
if (resource === false) {
if (!resourceData) {
// ignored
return callback(
null,
@ -367,26 +389,18 @@ class NormalModuleFactory extends ModuleFactory {
}
const userRequest =
(matchResource !== undefined ? `${matchResource}!=!` : "") +
stringifyLoadersAndResource(loaders, resource);
let resourcePath =
matchResource !== undefined ? matchResource : resource;
let resourceQuery = "";
const queryIndex = resourcePath.indexOf("?");
if (queryIndex >= 0) {
resourceQuery = resourcePath.substr(queryIndex);
resourcePath = resourcePath.substr(0, queryIndex);
}
(matchResourceData !== undefined
? `${matchResourceData.resource}!=!`
: "") +
stringifyLoadersAndResource(loaders, resourceData.resource);
const resourceDataForRules = matchResourceData || resourceData;
const result = this.ruleSet.exec({
resource: resourcePath,
realResource:
matchResource !== undefined
? resource.replace(/\?.*/, "")
: resourcePath,
resourceQuery,
mimetype: dataURIMimetype,
resource: resourceDataForRules.path,
realResource: resourceData.path,
resourceQuery: resourceDataForRules.query,
resourceFragment: resourceDataForRules.fragment,
mimetype: matchResourceData ? "" : resourceData.data.mimetype || "",
issuer: contextInfo.issuer,
compiler: contextInfo.compiler
});
@ -426,7 +440,7 @@ class NormalModuleFactory extends ModuleFactory {
return callback(err);
}
const allLoaders = postLoaders;
if (matchResource === undefined) {
if (matchResourceData === undefined) {
for (const loader of loaders) allLoaders.push(loader);
for (const loader of normalLoaders) allLoaders.push(loader);
} else {
@ -437,13 +451,18 @@ class NormalModuleFactory extends ModuleFactory {
const type = settings.type;
const resolveOptions = settings.resolve;
Object.assign(data.createData, {
request: stringifyLoadersAndResource(allLoaders, resource),
request: stringifyLoadersAndResource(
allLoaders,
resourceData.resource
),
userRequest,
rawRequest: request,
loaders: allLoaders,
resource,
matchResource,
resourceResolveData,
resource: resourceData.resource,
matchResource: matchResourceData
? matchResourceData.resource
: undefined,
resourceResolveData: resourceData.data,
settings,
type,
parser: this.getParser(type, settings.parser),
@ -500,19 +519,45 @@ class NormalModuleFactory extends ModuleFactory {
}
);
if (
unresolvedResource === "" ||
unresolvedResource.charCodeAt(0) === 63
) {
// 63 === "?"
resource = unresolvedResource;
return continueCallback();
// resource with scheme
if (scheme) {
resourceData = {
resource: unresolvedResource,
data: {},
path: undefined,
query: undefined,
fragment: undefined
};
this.hooks.resolveForScheme
.for(scheme)
.callAsync(resourceData, data, err => {
if (err) return continueCallback(err);
continueCallback();
});
}
if (dataURIMimetype) {
resource = unresolvedResource;
// resource without scheme and without path
else if (/^($|\?|#)/.test(unresolvedResource)) {
resourceData = {
resource: unresolvedResource,
data: {},
...cacheParseResource(unresolvedResource)
};
continueCallback();
} else {
}
// resource without scheme and with path
else {
const normalResolver = this.getResolver(
"normal",
dependencies.length > 0
? cachedSetProperty(
resolveOptions || EMPTY_RESOLVE_OPTIONS,
"dependencyType",
dependencies[0].category
)
: resolveOptions
);
normalResolver.resolve(
contextInfo,
context,
@ -520,8 +565,13 @@ class NormalModuleFactory extends ModuleFactory {
resolveContext,
(err, resolvedResource, resolvedResourceResolveData) => {
if (err) return continueCallback(err);
resource = resolvedResource;
resourceResolveData = resolvedResourceResolveData;
if (resolvedResource !== false) {
resourceData = {
resource: resolvedResource,
data: resolvedResourceResolveData,
...cacheParseResource(resolvedResource)
};
}
continueCallback();
}
);

View File

@ -0,0 +1,33 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const WebpackError = require("./WebpackError");
const makeSerializable = require("./util/makeSerializable");
class UnhandledSchemeError extends WebpackError {
/**
* @param {string} scheme scheme
* @param {string} resource resource
*/
constructor(scheme, resource) {
super(
`Reading from "${resource}" is not handled by plugins (Unhandled scheme).` +
'\nWebpack supports "data:" and "file:" URIs by default.' +
`\nYou may need an additional plugin to handle "${scheme}:" URIs.`
);
this.file = resource;
this.name = "UnhandledSchemeError";
}
}
makeSerializable(
UnhandledSchemeError,
"webpack/lib/UnhandledSchemeError",
"UnhandledSchemeError"
);
module.exports = UnhandledSchemeError;

View File

@ -26,6 +26,9 @@ const TemplatedPathPlugin = require("./TemplatedPathPlugin");
const UseStrictPlugin = require("./UseStrictPlugin");
const WarnCaseSensitiveModulesPlugin = require("./WarnCaseSensitiveModulesPlugin");
const DataUriPlugin = require("./schemes/DataUriPlugin");
const FileUriPlugin = require("./schemes/FileUriPlugin");
const ResolverCachePlugin = require("./cache/ResolverCachePlugin");
const CommonJsPlugin = require("./dependencies/CommonJsPlugin");
@ -309,6 +312,9 @@ class WebpackOptionsApply extends OptionsApply {
: true
}).apply(compiler);
new DataUriPlugin().apply(compiler);
new FileUriPlugin().apply(compiler);
new CompatibilityPlugin().apply(compiler);
new HarmonyModulesPlugin({
module: options.module,

View File

@ -5,6 +5,8 @@
"use strict";
const { parseResource } = require("../util/identifier");
/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
/** @typedef {import("./ContextDependency")} ContextDependency */
@ -32,19 +34,6 @@ const splitContextFromPrefix = prefix => {
};
};
const splitQueryFromPostfix = postfix => {
const idx = postfix.indexOf("?");
let query = "";
if (idx >= 0) {
query = postfix.substr(idx);
postfix = postfix.substr(0, idx);
}
return {
postfix,
query
};
};
// TODO Use Omit<> type for contextOptions when typescript >= 3.5
/** @typedef {Partial<Pick<ContextDependencyOptions, Exclude<keyof ContextDependencyOptions, "resource"|"recursive"|"regExp">>>} PartialContextDependencyOptions */
@ -70,7 +59,10 @@ exports.create = (Dep, range, param, expr, options, contextOptions, parser) => {
const valueRange = param.range;
const { context, prefix } = splitContextFromPrefix(prefixRaw);
const { postfix, query } = splitQueryFromPostfix(postfixRaw);
const { path: postfix, query, fragment } = parseResource(
postfixRaw,
parser
);
// When there are more than two quasis, the generated RegExp can be more precise
// We join the quasis with the expression regexp
@ -81,20 +73,21 @@ exports.create = (Dep, range, param, expr, options, contextOptions, parser) => {
.map(q => quoteMeta(q.string) + options.wrappedContextRegExp.source)
.join("");
// Example: `./context/pre${e}inner${e}inner2${e}post?query`
// Example: `./context/pre${e}inner${e}inner2${e}post?query#frag`
// context: "./context"
// prefix: "./pre"
// innerQuasis: [BEE("inner"), BEE("inner2")]
// (BEE = BasicEvaluatedExpression)
// postfix: "post"
// query: "?query"
// fragment: "#frag"
// regExp: /^\.\/pre.*inner.*inner2.*post$/
const regExp = new RegExp(
`^${quoteMeta(prefix)}${innerRegExp}${quoteMeta(postfix)}$`
);
const dep = new Dep(
{
request: context + query,
request: context + query + fragment,
recursive: options.wrappedContextRecursive,
regExp,
mode: "sync",
@ -165,7 +158,10 @@ exports.create = (Dep, range, param, expr, options, contextOptions, parser) => {
param.postfix && param.postfix.isString() ? param.postfix.range : null;
const valueRange = param.range;
const { context, prefix } = splitContextFromPrefix(prefixRaw);
const { postfix, query } = splitQueryFromPostfix(postfixRaw);
const { path: postfix, query, fragment } = parseResource(
postfixRaw,
parser
);
const regExp = new RegExp(
`^${quoteMeta(prefix)}${options.wrappedContextRegExp.source}${quoteMeta(
postfix
@ -173,7 +169,7 @@ exports.create = (Dep, range, param, expr, options, contextOptions, parser) => {
);
const dep = new Dep(
{
request: context + query,
request: context + query + fragment,
recursive: options.wrappedContextRecursive,
regExp,
mode: "sync",

View File

@ -426,5 +426,16 @@ module.exports = mergeExports(fn, {
get serialization() {
return require("./util/serialization");
}
},
experiments: {
schemes: {
get HttpUriPlugin() {
return require("./schemes/HttpUriPlugin");
},
get HttpsUriPlugin() {
return require("./schemes/HttpsUriPlugin");
}
}
}
});

View File

@ -0,0 +1,36 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const NormalModule = require("../NormalModule");
const { getMimetype, decodeDataURI } = require("../util/DataURI");
/** @typedef {import("../Compiler")} Compiler */
class DataUriPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
"DataUriPlugin",
(compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.resolveForScheme
.for("data")
.tap("DataUriPlugin", resourceData => {
resourceData.data.mimetype = getMimetype(resourceData.resource);
});
NormalModule.getCompilationHooks(compilation)
.readResourceForScheme.for("data")
.tap("DataUriPlugin", resource => decodeDataURI(resource));
}
);
}
}
module.exports = DataUriPlugin;

View File

@ -0,0 +1,40 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { URL, fileURLToPath } = require("url");
/** @typedef {import("../Compiler")} Compiler */
class FileUriPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
"FileUriPlugin",
(compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.resolveForScheme
.for("file")
.tap("FileUriPlugin", resourceData => {
const url = new URL(resourceData.resource);
const path = fileURLToPath(url);
const query = url.search;
const fragment = url.hash;
resourceData.path = path;
resourceData.query = query;
resourceData.fragment = fragment;
resourceData.resource = path + query + fragment;
return true;
});
}
);
}
}
module.exports = FileUriPlugin;

View File

@ -0,0 +1,63 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { URL } = require("url");
const NormalModule = require("../NormalModule");
/** @typedef {import("../Compiler")} Compiler */
class HttpUriPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
"HttpUriPlugin",
(compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.resolveForScheme
.for("http")
.tap("HttpUriPlugin", resourceData => {
const url = new URL(resourceData.resource);
resourceData.path = url.origin + url.pathname;
resourceData.query = url.search;
resourceData.fragment = url.hash;
return /** @type {true} */ (true);
});
NormalModule.getCompilationHooks(compilation)
.readResourceForScheme.for("http")
.tapAsync("HttpUriPlugin", (resource, module, callback) => {
return require("http").get(new URL(resource), res => {
if (res.statusCode !== 200) {
res.destroy();
return callback(
new Error(`http request status code = ${res.statusCode}`)
);
}
const bufferArr = [];
res.on("data", chunk => {
bufferArr.push(chunk);
});
res.on("end", () => {
if (!res.complete) {
return callback(new Error("http request was terminated"));
}
callback(null, Buffer.concat(bufferArr));
});
});
});
}
);
}
}
module.exports = HttpUriPlugin;

View File

@ -0,0 +1,63 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { URL } = require("url");
const NormalModule = require("../NormalModule");
/** @typedef {import("../Compiler")} Compiler */
class HttpsUriPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
"HttpsUriPlugin",
(compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.resolveForScheme
.for("https")
.tap("HttpsUriPlugin", resourceData => {
const url = new URL(resourceData.resource);
resourceData.path = url.origin + url.pathname;
resourceData.query = url.search;
resourceData.fragment = url.hash;
return /** @type {true} */ (true);
});
NormalModule.getCompilationHooks(compilation)
.readResourceForScheme.for("https")
.tapAsync("HttpsUriPlugin", (resource, module, callback) => {
return require("https").get(new URL(resource), res => {
if (res.statusCode !== 200) {
res.destroy();
return callback(
new Error(`https request status code = ${res.statusCode}`)
);
}
const bufferArr = [];
res.on("data", chunk => {
bufferArr.push(chunk);
});
res.on("end", () => {
if (!res.complete) {
return callback(new Error("https request was terminated"));
}
callback(null, Buffer.concat(bufferArr));
});
});
});
}
);
}
}
module.exports = HttpsUriPlugin;

View File

@ -6,7 +6,7 @@
// data URL scheme: "data:text/javascript;charset=utf-8;base64,some-string"
// http://www.ietf.org/rfc/rfc2397.txt
const URIRegEx = /^data:([^;,]+)?((?:;(?:[^;,]+))*?)(;base64)?,(.*)$/;
const URIRegEx = /^data:([^;,]+)?((?:;(?:[^;,]+))*?)(;base64)?,(.*)$/i;
const decodeDataURI = uri => {
const match = URIRegEx.exec(uri);

View File

@ -0,0 +1,87 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
/** @typedef {import("./fs").InputFileSystem} InputFileSystem */
/** @typedef {(error: Error|null, result?: Buffer) => void} ErrorFirstCallback */
const backSlashCharCode = "\\".charCodeAt(0);
const slashCharCode = "/".charCodeAt(0);
const aLowerCaseCharCode = "a".charCodeAt(0);
const zLowerCaseCharCode = "z".charCodeAt(0);
const aUpperCaseCharCode = "A".charCodeAt(0);
const zUpperCaseCharCode = "Z".charCodeAt(0);
const _0CharCode = "0".charCodeAt(0);
const _9CharCode = "9".charCodeAt(0);
const plusCharCode = "+".charCodeAt(0);
const hyphenCharCode = "-".charCodeAt(0);
const colonCharCode = ":".charCodeAt(0);
const hashCharCode = "#".charCodeAt(0);
const queryCharCode = "?".charCodeAt(0);
/**
* Get scheme if specifier is an absolute URL specifier
* e.g. Absolute specifiers like 'file:///user/webpack/index.js'
* https://tools.ietf.org/html/rfc3986#section-3.1
* @param {string} specifier specifier
* @returns {string|undefined} scheme if absolute URL specifier provided
*/
function getScheme(specifier) {
const start = specifier.charCodeAt(0);
// First char maybe only a letter
if (
(start < aLowerCaseCharCode || start > zLowerCaseCharCode) &&
(start < aUpperCaseCharCode || start > zUpperCaseCharCode)
) {
return undefined;
}
let i = 1;
let ch = specifier.charCodeAt(i);
while (
(ch >= aLowerCaseCharCode && ch <= zLowerCaseCharCode) ||
(ch >= aUpperCaseCharCode && ch <= zUpperCaseCharCode) ||
(ch >= _0CharCode && ch <= _9CharCode) ||
ch === plusCharCode ||
ch === hyphenCharCode
) {
if (++i === specifier.length) return undefined;
ch = specifier.charCodeAt(i);
}
// Scheme must end with colon
if (ch !== colonCharCode) return undefined;
// Check for Windows absolute path
// https://url.spec.whatwg.org/#url-miscellaneous
if (i === 1) {
const nextChar = i + 1 < specifier.length ? specifier.charCodeAt(i + 1) : 0;
if (
nextChar === 0 ||
nextChar === backSlashCharCode ||
nextChar === slashCharCode ||
nextChar === hashCharCode ||
nextChar === queryCharCode
) {
return undefined;
}
}
return specifier.slice(0, i).toLowerCase();
}
/**
* @param {string} specifier specifier
* @returns {string|null} protocol if absolute URL specifier provided
*/
function getProtocol(specifier) {
const scheme = getScheme(specifier);
return scheme === undefined ? undefined : scheme + ":";
}
exports.getScheme = getScheme;
exports.getProtocol = getProtocol;

View File

@ -243,3 +243,62 @@ const _absolutify = (context, request) => {
const absolutify = makeCacheable(_absolutify);
exports.absolutify = absolutify;
const PATH_QUERY_FRAGMENT_REGEXP = /^([^?#]*)(\?[^#]*)?(#.*)?$/;
/** @typedef {{ resource: string, path: string, query: string, fragment: string }} ParsedResource */
/**
* @param {string} str the path with query and fragment
* @returns {ParsedResource} parsed parts
*/
const _parseResource = str => {
const match = PATH_QUERY_FRAGMENT_REGEXP.exec(str);
return {
resource: str,
path: match[1],
query: match[2] || "",
fragment: match[3] || ""
};
};
exports.parseResource = (realFn => {
/** @type {WeakMap<object, Map<string, ParsedResource>>} */
const cache = new WeakMap();
const getCache = associatedObjectForCache => {
const entry = cache.get(associatedObjectForCache);
if (entry !== undefined) return entry;
/** @type {Map<string, ParsedResource>} */
const map = new Map();
cache.set(associatedObjectForCache, map);
return map;
};
/**
* @param {string} str the path with query and fragment
* @param {Object=} associatedObjectForCache an object to which the cache will be attached
* @returns {ParsedResource} parsed parts
*/
const fn = (str, associatedObjectForCache) => {
if (!associatedObjectForCache) return realFn(str);
const cache = getCache(associatedObjectForCache);
const entry = cache.get(str);
if (entry !== undefined) return entry;
const result = realFn(str);
cache.set(str, result);
return result;
};
fn.bindCache = associatedObjectForCache => {
const cache = getCache(associatedObjectForCache);
return str => {
const entry = cache.get(str);
if (entry !== undefined) return entry;
const result = realFn(str);
cache.set(str, result);
return result;
};
};
return fn;
})(_parseResource);

View File

@ -170,6 +170,7 @@ module.exports = {
require("../sharing/ProvideForSharedDependency"),
UnsupportedFeatureWarning: () => require("../UnsupportedFeatureWarning"),
"util/LazySet": () => require("../util/LazySet"),
UnhandledSchemeError: () => require("../UnhandledSchemeError"),
WebpackError: () => require("../WebpackError"),
"util/registerExternalSerializer": () => {

View File

@ -18,7 +18,7 @@
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.15",
"json-parse-better-errors": "^1.0.2",
"loader-runner": "^3.1.0",
"loader-runner": "^4.0.0",
"mime-types": "^2.1.26",
"neo-async": "^2.6.1",
"pkg-dir": "^4.2.0",

View File

@ -2563,6 +2563,14 @@
}
]
},
"resourceFragment": {
"description": "Match the resource fragment of the module.",
"oneOf": [
{
"$ref": "#/definitions/RuleSetConditionOrConditions"
}
]
},
"resourceQuery": {
"description": "Match the resource query of the module.",
"oneOf": [

View File

@ -0,0 +1,85 @@
const { getScheme, getProtocol } = require("../lib/util/URLAbsoluteSpecifier");
/**
* @type {Array<{specifier: string, expected: string|undefined}>}
*/
const samples = [
{
specifier: "@babel/core",
expected: undefined
},
{
specifier: "webpack",
expected: undefined
},
{
specifier: "1webpack:///c:/windows/dir",
expected: undefined
},
{
specifier: "webpack:///c:/windows/dir",
expected: "webpack"
},
{
specifier: "WEBPACK2020:///c:/windows/dir",
expected: "webpack2020"
},
{
specifier: "my-data:image/jpg;base64",
expected: "my-data"
},
{
specifier: "My+Data:image/jpg;base64",
expected: "my+data"
},
{
specifier: "mY+dATA:image/jpg;base64",
expected: "my+data"
},
{
specifier: "my-data/next:image/",
expected: undefined
},
{
specifier: "my-data\\next:image/",
expected: undefined
},
{
specifier: "D:\\path\\file.js",
expected: undefined
},
{
specifier: "d:/path/file.js",
expected: undefined
},
{
specifier: "z:#foo",
expected: undefined
},
{
specifier: "Z:?query",
expected: undefined
},
{
specifier: "C:",
expected: undefined
}
];
describe("getScheme", () => {
samples.forEach(({ specifier, expected }, i) => {
it(`should handle ${specifier}`, () => {
expect(getScheme(specifier)).toBe(expected);
});
});
});
describe("getProtocol", () => {
samples.forEach(({ specifier, expected }, i) => {
it(`should handle ${specifier}`, () => {
expect(getProtocol(specifier)).toBe(
expected ? expected + ":" : undefined
);
});
});
});

View File

@ -60,10 +60,10 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.entry['bundle'] should be an non-empty array.
-> All modules are loaded upon startup. The last one is exported."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.entry['bundle'] should be an non-empty array.
-> All modules are loaded upon startup. The last one is exported."
`)
);
createTestCase(
@ -103,10 +103,10 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.entry should not contain the item 'abc' twice.
-> All modules are loaded upon startup. The last one is exported."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.entry should not contain the item 'abc' twice.
-> All modules are loaded upon startup. The last one is exported."
`)
);
createTestCase(
@ -119,16 +119,16 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.entry[0] should be a non-empty string.
-> A module that is loaded upon startup. Only the last one is exported.
- configuration.output.filename should be one of these:
non-empty string | function
-> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The \`output.path\` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
Details:
* configuration.output.filename should be a non-empty string.
* configuration.output.filename should be an instance of function."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.entry[0] should be a non-empty string.
-> A module that is loaded upon startup. Only the last one is exported.
- configuration.output.filename should be one of these:
non-empty string | function
-> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The \`output.path\` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
Details:
* configuration.output.filename should be a non-empty string.
* configuration.output.filename should be an instance of function."
`)
);
createTestCase(
@ -146,16 +146,16 @@ describe("Validation", () => {
],
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration[0].entry[0] should be a non-empty string.
-> A module that is loaded upon startup. Only the last one is exported.
- configuration[1].output.filename should be one of these:
non-empty string | function
-> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The \`output.path\` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
Details:
* configuration[1].output.filename should be a non-empty string.
* configuration[1].output.filename should be an instance of function."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration[0].entry[0] should be a non-empty string.
-> A module that is loaded upon startup. Only the last one is exported.
- configuration[1].output.filename should be one of these:
non-empty string | function
-> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The \`output.path\` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
Details:
* configuration[1].output.filename should be a non-empty string.
* configuration[1].output.filename should be an instance of function."
`)
);
createTestCase(
@ -181,7 +181,7 @@ describe("Validation", () => {
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.module.rules[0].oneOf[0] has an unknown property 'passer'. These properties are valid:
object { compiler?, enforce?, exclude?, generator?, include?, issuer?, loader?, mimetype?, oneOf?, options?, parser?, realResource?, resolve?, resource?, resourceQuery?, rules?, sideEffects?, test?, type?, use? }
object { compiler?, enforce?, exclude?, generator?, include?, issuer?, loader?, mimetype?, oneOf?, options?, parser?, realResource?, resolve?, resource?, resourceFragment?, resourceQuery?, rules?, sideEffects?, test?, type?, use? }
-> A rule description with conditions and effects for modules."
`)
);
@ -312,17 +312,17 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.plugins[0] should be one of these:
object { apply, } | function
-> Plugin of type object or instanceof Function.
Details:
* configuration.plugins[0] should be an object:
object { apply, }
-> Plugin instance.
* configuration.plugins[0] should be an instance of function.
-> Function acting as plugin."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.plugins[0] should be one of these:
object { apply, } | function
-> Plugin of type object or instanceof Function.
Details:
* configuration.plugins[0] should be an object:
object { apply, }
-> Plugin instance.
* configuration.plugins[0] should be an instance of function.
-> Function acting as plugin."
`)
);
createTestCase(
@ -333,17 +333,17 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.plugins[0] should be one of these:
object { apply, } | function
-> Plugin of type object or instanceof Function.
Details:
* configuration.plugins[0] should be an object:
object { apply, }
-> Plugin instance.
* configuration.plugins[0] should be an instance of function.
-> Function acting as plugin."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.plugins[0] should be one of these:
object { apply, } | function
-> Plugin of type object or instanceof Function.
Details:
* configuration.plugins[0] should be an object:
object { apply, }
-> Plugin instance.
* configuration.plugins[0] should be an instance of function.
-> Function acting as plugin."
`)
);
createTestCase(
@ -354,17 +354,17 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.plugins[0] should be one of these:
object { apply, } | function
-> Plugin of type object or instanceof Function.
Details:
* configuration.plugins[0] should be an object:
object { apply, }
-> Plugin instance.
* configuration.plugins[0] should be an instance of function.
-> Function acting as plugin."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.plugins[0] should be one of these:
object { apply, } | function
-> Plugin of type object or instanceof Function.
Details:
* configuration.plugins[0] should be an object:
object { apply, }
-> Plugin instance.
* configuration.plugins[0] should be an instance of function.
-> Function acting as plugin."
`)
);
createTestCase(
@ -375,17 +375,17 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.plugins[0] should be one of these:
object { apply, } | function
-> Plugin of type object or instanceof Function.
Details:
* configuration.plugins[0] should be an object:
object { apply, }
-> Plugin instance.
* configuration.plugins[0] should be an instance of function.
-> Function acting as plugin."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.plugins[0] should be one of these:
object { apply, } | function
-> Plugin of type object or instanceof Function.
Details:
* configuration.plugins[0] should be an object:
object { apply, }
-> Plugin instance.
* configuration.plugins[0] should be an instance of function.
-> Function acting as plugin."
`)
);
createTestCase(
@ -452,18 +452,18 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.optimization.splitChunks.cacheGroups should not be object { test, }.
-> Using the cacheGroup shorthand syntax with a cache group named 'test' is a potential config error
Did you intent to define a cache group with a test instead?
cacheGroups: {
<name>: {
test: ...
}
}.
object { <key>: false | RegExp | string | function | object { automaticNameDelimiter?, chunks?, enforce?, filename?, idHint?, maxAsyncRequests?, maxAsyncSize?, maxInitialRequests?, maxInitialSize?, maxSize?, minChunks?, minRemainingSize?, minSize?, name?, priority?, reuseExistingChunk?, test?, type? } }
-> Assign modules to a cache group (modules from different cache groups are tried to keep in separate chunks, default categories: 'default', 'defaultVendors')."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.optimization.splitChunks.cacheGroups should not be object { test, }.
-> Using the cacheGroup shorthand syntax with a cache group named 'test' is a potential config error
Did you intent to define a cache group with a test instead?
cacheGroups: {
<name>: {
test: ...
}
}.
object { <key>: false | RegExp | string | function | object { automaticNameDelimiter?, chunks?, enforce?, filename?, idHint?, maxAsyncRequests?, maxAsyncSize?, maxInitialRequests?, maxInitialSize?, maxSize?, minChunks?, minRemainingSize?, minSize?, name?, priority?, reuseExistingChunk?, test?, type? } }
-> Assign modules to a cache group (modules from different cache groups are tried to keep in separate chunks, default categories: 'default', 'defaultVendors')."
`)
);
createTestCase(
@ -494,14 +494,14 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.output.ecmaVersion should be one of these:
2009 | number (should be >= 5 and <= 11) | number (should be >= 2015 and <= 2020)
-> The maximum EcmaScript version of the webpack generated code (doesn't include input source code from modules).
Details:
* configuration.output.ecmaVersion should be >= 5 and <= 11.
* configuration.output.ecmaVersion should be >= 2015 and <= 2020."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.output.ecmaVersion should be one of these:
2009 | number (should be >= 5 and <= 11) | number (should be >= 2015 and <= 2020)
-> The maximum EcmaScript version of the webpack generated code (doesn't include input source code from modules).
Details:
* configuration.output.ecmaVersion should be >= 5 and <= 11.
* configuration.output.ecmaVersion should be >= 2015 and <= 2020."
`)
);
createTestCase(
@ -511,14 +511,14 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.output.ecmaVersion should be one of these:
2009 | number (should be >= 5 and <= 11) | number (should be >= 2015 and <= 2020)
-> The maximum EcmaScript version of the webpack generated code (doesn't include input source code from modules).
Details:
* configuration.output.ecmaVersion should be >= 5 and <= 11.
* configuration.output.ecmaVersion should be >= 2015 and <= 2020."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.output.ecmaVersion should be one of these:
2009 | number (should be >= 5 and <= 11) | number (should be >= 2015 and <= 2020)
-> The maximum EcmaScript version of the webpack generated code (doesn't include input source code from modules).
Details:
* configuration.output.ecmaVersion should be >= 5 and <= 11.
* configuration.output.ecmaVersion should be >= 2015 and <= 2020."
`)
);
createTestCase(
@ -528,14 +528,14 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.output.ecmaVersion should be one of these:
2009 | number (should be >= 5 and <= 11) | number (should be >= 2015 and <= 2020)
-> The maximum EcmaScript version of the webpack generated code (doesn't include input source code from modules).
Details:
* configuration.output.ecmaVersion should be >= 5 and <= 11.
* configuration.output.ecmaVersion should be >= 2015 and <= 2020."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.output.ecmaVersion should be one of these:
2009 | number (should be >= 5 and <= 11) | number (should be >= 2015 and <= 2020)
-> The maximum EcmaScript version of the webpack generated code (doesn't include input source code from modules).
Details:
* configuration.output.ecmaVersion should be >= 5 and <= 11.
* configuration.output.ecmaVersion should be >= 2015 and <= 2020."
`)
);
createTestCase(
@ -545,14 +545,14 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.output.ecmaVersion should be one of these:
2009 | number (should be >= 5 and <= 11) | number (should be >= 2015 and <= 2020)
-> The maximum EcmaScript version of the webpack generated code (doesn't include input source code from modules).
Details:
* configuration.output.ecmaVersion should be >= 5 and <= 11.
* configuration.output.ecmaVersion should be >= 2015 and <= 2020."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.output.ecmaVersion should be one of these:
2009 | number (should be >= 5 and <= 11) | number (should be >= 2015 and <= 2020)
-> The maximum EcmaScript version of the webpack generated code (doesn't include input source code from modules).
Details:
* configuration.output.ecmaVersion should be >= 5 and <= 11.
* configuration.output.ecmaVersion should be >= 2015 and <= 2020."
`)
);
createTestCase(
@ -590,11 +590,11 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.watchOptions should be an object:
object { aggregateTimeout?, ignored?, poll?, stdin? }
-> Options for the watcher."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.watchOptions should be an object:
object { aggregateTimeout?, ignored?, poll?, stdin? }
-> Options for the watcher."
`)
);
createTestCase(
@ -619,12 +619,12 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration has an unknown property 'rules'. These properties are valid:
object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, entry?, experiments?, externals?, externalsType?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, stats?, target?, watch?, watchOptions? }
-> Options object as provided by the user.
Did you mean module.rules?"
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration has an unknown property 'rules'. These properties are valid:
object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, entry?, experiments?, externals?, externalsType?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, stats?, target?, watch?, watchOptions? }
-> Options object as provided by the user.
Did you mean module.rules?"
`)
);
createTestCase(
"optimization.splitChunks",
@ -633,12 +633,12 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration has an unknown property 'splitChunks'. These properties are valid:
object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, entry?, experiments?, externals?, externalsType?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, stats?, target?, watch?, watchOptions? }
-> Options object as provided by the user.
Did you mean optimization.splitChunks?"
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration has an unknown property 'splitChunks'. These properties are valid:
object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, entry?, experiments?, externals?, externalsType?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, stats?, target?, watch?, watchOptions? }
-> Options object as provided by the user.
Did you mean optimization.splitChunks?"
`)
);
createTestCase(
"module.noParse",
@ -647,12 +647,12 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration has an unknown property 'noParse'. These properties are valid:
object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, entry?, experiments?, externals?, externalsType?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, stats?, target?, watch?, watchOptions? }
-> Options object as provided by the user.
Did you mean module.noParse?"
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration has an unknown property 'noParse'. These properties are valid:
object { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, entry?, experiments?, externals?, externalsType?, infrastructureLogging?, loader?, mode?, module?, name?, node?, optimization?, output?, parallelism?, performance?, plugins?, profile?, recordsInputPath?, recordsOutputPath?, recordsPath?, resolve?, resolveLoader?, stats?, target?, watch?, watchOptions? }
-> Options object as provided by the user.
Did you mean module.noParse?"
`)
);
createTestCase(
"opimization.moduleIds",
@ -663,12 +663,12 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.optimization has an unknown property 'hashedModuleIds'. These properties are valid:
object { checkWasmTypes?, chunkIds?, concatenateModules?, flagIncludedChunks?, innerGraph?, mangleExports?, mangleWasmImports?, mergeDuplicateChunks?, minimize?, minimizer?, moduleIds?, noEmitOnErrors?, nodeEnv?, portableRecords?, providedExports?, removeAvailableModules?, removeEmptyChunks?, runtimeChunk?, sideEffects?, splitChunks?, usedExports? }
-> Enables/Disables integrated optimizations.
Did you mean optimization.moduleIds: \\"hashed\\" (BREAKING CHANGE since webpack 5)?"
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.optimization has an unknown property 'hashedModuleIds'. These properties are valid:
object { checkWasmTypes?, chunkIds?, concatenateModules?, flagIncludedChunks?, innerGraph?, mangleExports?, mangleWasmImports?, mergeDuplicateChunks?, minimize?, minimizer?, moduleIds?, noEmitOnErrors?, nodeEnv?, portableRecords?, providedExports?, removeAvailableModules?, removeEmptyChunks?, runtimeChunk?, sideEffects?, splitChunks?, usedExports? }
-> Enables/Disables integrated optimizations.
Did you mean optimization.moduleIds: \\"hashed\\" (BREAKING CHANGE since webpack 5)?"
`)
);
createTestCase(
"optimization.chunkIds",
@ -679,12 +679,12 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.optimization has an unknown property 'namedChunks'. These properties are valid:
object { checkWasmTypes?, chunkIds?, concatenateModules?, flagIncludedChunks?, innerGraph?, mangleExports?, mangleWasmImports?, mergeDuplicateChunks?, minimize?, minimizer?, moduleIds?, noEmitOnErrors?, nodeEnv?, portableRecords?, providedExports?, removeAvailableModules?, removeEmptyChunks?, runtimeChunk?, sideEffects?, splitChunks?, usedExports? }
-> Enables/Disables integrated optimizations.
Did you mean optimization.chunkIds: \\"named\\" (BREAKING CHANGE since webpack 5)?"
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.optimization has an unknown property 'namedChunks'. These properties are valid:
object { checkWasmTypes?, chunkIds?, concatenateModules?, flagIncludedChunks?, innerGraph?, mangleExports?, mangleWasmImports?, mergeDuplicateChunks?, minimize?, minimizer?, moduleIds?, noEmitOnErrors?, nodeEnv?, portableRecords?, providedExports?, removeAvailableModules?, removeEmptyChunks?, runtimeChunk?, sideEffects?, splitChunks?, usedExports? }
-> Enables/Disables integrated optimizations.
Did you mean optimization.chunkIds: \\"named\\" (BREAKING CHANGE since webpack 5)?"
`)
);
createTestCase(
"optimization.chunk/moduleIds",
@ -695,12 +695,12 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.optimization has an unknown property 'occurrenceOrder'. These properties are valid:
object { checkWasmTypes?, chunkIds?, concatenateModules?, flagIncludedChunks?, innerGraph?, mangleExports?, mangleWasmImports?, mergeDuplicateChunks?, minimize?, minimizer?, moduleIds?, noEmitOnErrors?, nodeEnv?, portableRecords?, providedExports?, removeAvailableModules?, removeEmptyChunks?, runtimeChunk?, sideEffects?, splitChunks?, usedExports? }
-> Enables/Disables integrated optimizations.
Did you mean optimization.chunkIds: \\"size\\" and optimization.moduleIds: \\"size\\" (BREAKING CHANGE since webpack 5)?"
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.optimization has an unknown property 'occurrenceOrder'. These properties are valid:
object { checkWasmTypes?, chunkIds?, concatenateModules?, flagIncludedChunks?, innerGraph?, mangleExports?, mangleWasmImports?, mergeDuplicateChunks?, minimize?, minimizer?, moduleIds?, noEmitOnErrors?, nodeEnv?, portableRecords?, providedExports?, removeAvailableModules?, removeEmptyChunks?, runtimeChunk?, sideEffects?, splitChunks?, usedExports? }
-> Enables/Disables integrated optimizations.
Did you mean optimization.chunkIds: \\"size\\" and optimization.moduleIds: \\"size\\" (BREAKING CHANGE since webpack 5)?"
`)
);
createTestCase(
"optimization.idHint",
@ -713,11 +713,11 @@ describe("Validation", () => {
},
msg =>
expect(msg).toMatchInlineSnapshot(`
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.optimization.splitChunks has an unknown property 'automaticNamePrefix'. These properties are valid:
object { automaticNameDelimiter?, cacheGroups?, chunks?, fallbackCacheGroup?, filename?, hidePathInfo?, maxAsyncRequests?, maxAsyncSize?, maxInitialRequests?, maxInitialSize?, maxSize?, minChunks?, minRemainingSize?, minSize?, name? }
-> Options object for splitting chunks into smaller chunks."
`)
"Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.optimization.splitChunks has an unknown property 'automaticNamePrefix'. These properties are valid:
object { automaticNameDelimiter?, cacheGroups?, chunks?, fallbackCacheGroup?, filename?, hidePathInfo?, maxAsyncRequests?, maxAsyncSize?, maxInitialRequests?, maxInitialSize?, maxSize?, minChunks?, minRemainingSize?, minSize?, name? }
-> Options object for splitting chunks into smaller chunks."
`)
);
});
});

View File

@ -854,6 +854,25 @@ Object {
"multiple": true,
"simpleType": "string",
},
"module-rules-resource-fragment": Object {
"configs": Array [
Object {
"description": "Match the resource fragment of the module.",
"multiple": true,
"path": "module.rules[].resourceFragment",
"type": "RegExp",
},
Object {
"description": "Match the resource fragment of the module.",
"multiple": true,
"path": "module.rules[].resourceFragment",
"type": "string",
},
],
"description": "Match the resource fragment of the module.",
"multiple": true,
"simpleType": "string",
},
"module-rules-resource-query": Object {
"configs": Array [
Object {

View File

@ -38,3 +38,14 @@ it("should evaluate __dirname and __resourceQuery with replace and substr", func
var result = require("./resourceQuery/index?" + __dirname);
expect(result).toEqual("?resourceQuery");
});
it("should evaluate __dirname and __resourceFragment with replace and substr", function() {
var result = require("./resourceFragment/index#" + __dirname);
expect(result).toEqual("#resourceFragment");
});
it("should allow resourceFragment in context", function() {
var fn = x => require(`./resourceFragment/${x}#..`);
expect(fn("index")).toEqual("#resourceFragment");
expect(fn("returnRF")).toBe("#..")
});

View File

@ -0,0 +1,3 @@
module.exports = require((
__resourceFragment.substr(1) + "/resourceFragment/returnRF#XXXFragment"
).replace(/XXX/g, "resource"));

View File

@ -0,0 +1 @@
module.exports = __resourceFragment;

View File

@ -17,13 +17,13 @@ it("should import js module from base64 data-uri", function() {
});
it("should require coffee module from base64 data-uri", function() {
const mod = require('coffee-loader!data:text/javascript;charset=utf-8;base64,bW9kdWxlLmV4cG9ydHMgPQogIG51bWJlcjogNDIKICBmbjogKCkgLT4gIkhlbGxvIHdvcmxkIg==');
const mod = require('coffee-loader!Data:text/javascript;charset=utf-8;base64,bW9kdWxlLmV4cG9ydHMgPQogIG51bWJlcjogNDIKICBmbjogKCkgLT4gIkhlbGxvIHdvcmxkIg==');
expect(mod.number).toBe(42);
expect(mod.fn()).toBe("Hello world");
});
it("should require json module from base64 data-uri", function() {
const mod = require('data:application/json;charset=utf-8;base64,ewogICJpdCI6ICJ3b3JrcyIsCiAgIm51bWJlciI6IDQyCn0K');
const mod = require('DATA:application/json;charset=utf-8;base64,ewogICJpdCI6ICJ3b3JrcyIsCiAgIm51bWJlciI6IDQyCn0K');
expect(mod.it).toBe("works");
expect(mod.number).toBe(42);
})

View File

@ -0,0 +1 @@
temp

View File

@ -0,0 +1,7 @@
import {val1, val2} from "./temp/index.js";
import expected from "./src with spaces/module";
it("file url request should be supported", () => {
expect(val1).toBe(expected);
expect(val2).toBe(expected);
});

View File

@ -0,0 +1 @@
export default "default"

View File

@ -0,0 +1,38 @@
const fs = require("fs");
const path = require("path");
const { pathToFileURL } = require("url");
const file = path.resolve(
"./test/configCases/asset-modules/file-url",
"./temp/index.js"
);
fs.mkdirSync("./test/configCases/asset-modules/file-url/temp", {
recursive: true
});
fs.writeFileSync(
file,
`import v1 from ${JSON.stringify(
pathToFileURL(
path.resolve(
"./test/configCases/asset-modules/file-url/src with spaces/module.js"
)
)
)};
import v2 from ${JSON.stringify(
"file://localhost" +
pathToFileURL(
path.resolve(
"./test/configCases/asset-modules/file-url/src with spaces/module.js"
)
)
.toString()
.slice("file://".length)
)};
export const val1 = v1;
export const val2 = v2;`
);
/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "development"
};

View File

@ -0,0 +1,5 @@
import cssContent from "http://localhost:9990/index.css?query#fragment";
it("http url request should be supported", () => {
expect(cssContent).toBe("a {}.webpack{}");
});

View File

@ -0,0 +1 @@
module.exports = content => `export default ${JSON.stringify(content + ".webpack{}")}`;

View File

@ -0,0 +1 @@
a {}

View File

@ -0,0 +1,62 @@
const http = require("http");
const fs = require("fs");
/**
* @param {number} port port
* @returns {Promise<import("http").Server>} server instance
*/
function createServer(port) {
const file = fs.readFileSync("./test/configCases/asset-modules/http-url/server/index.css").toString().trim();
const server = http.createServer((req, res) => {
if (req.url !== "/index.css") {
res.statusCode = 404;
res.end();
} else {
res.end(file);
}
});
return new Promise((resolve, reject) => {
server.listen(port, (err) => {
if (err) {
reject(err);
} else {
resolve(server);
}
});
});
}
class ServerPlugin {
/**
* @param {number} port
*/
constructor(port) {
this.port = port;
}
/**
* @param {import("../../../../../").Compiler} compiler
*/
apply(compiler) {
const serverPromise = createServer(this.port);
serverPromise
.then(server => server.unref());
compiler.hooks.done.tapAsync("ServerPlugin", (stats, callback) => {
serverPromise
.then(server => server.close(callback))
.catch(callback)
});
compiler.hooks.beforeRun.tapAsync("ServerPlugin", (compiler, callback) => {
serverPromise
.then(() => callback())
.catch(callback)
});
}
}
module.exports = ServerPlugin;

View File

@ -0,0 +1,16 @@
const { HttpUriPlugin } = require("../../../../").experiments.schemes;
const ServerPlugin = require("./server");
/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.css$/,
loader: "./loaders/css-loader"
}
]
},
plugins: [new ServerPlugin(9990), new HttpUriPlugin()]
};

View File

@ -0,0 +1,5 @@
import codeOfConduct from "https://raw.githubusercontent.com/webpack/webpack/master/CODE_OF_CONDUCT.md";
it("https url request should be supported", () => {
expect(codeOfConduct.includes("CODE_OF_CONDUCT")).toBeTruthy();
});

View File

@ -0,0 +1 @@
module.exports = content => `export default ${JSON.stringify(content)}`;

View File

@ -0,0 +1,15 @@
const { HttpsUriPlugin } = require("../../../../").experiments.schemes;
/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.md$/,
loader: "./loaders/md-loader"
}
]
},
plugins: [new HttpsUriPlugin()]
};

45
types.d.ts vendored
View File

@ -1725,6 +1725,7 @@ declare abstract class ContextModuleFactory extends ModuleFactory {
referencedExports?: string[][];
resource: string;
resourceQuery?: string;
resourceFragment?: string;
resolveOptions: any;
},
callback: (err?: Error, dependencies?: ContextElementDependency[]) => any
@ -2858,6 +2859,22 @@ declare class HotModuleReplacementPlugin {
apply(compiler: Compiler): void;
static getParserHooks(parser: JavascriptParser): HMRJavascriptParserHooks;
}
declare class HttpUriPlugin {
constructor();
/**
* Apply the plugin
*/
apply(compiler: Compiler): void;
}
declare class HttpsUriPlugin {
constructor();
/**
* Apply the plugin
*/
apply(compiler: Compiler): void;
}
declare class IgnorePlugin {
constructor(options: IgnorePluginOptions);
options: IgnorePluginOptions;
@ -4498,10 +4515,28 @@ declare class NormalModule extends Module {
}
declare interface NormalModuleCompilationHooks {
loader: SyncHook<[any, NormalModule], void>;
readResourceForScheme: HookMap<
AsyncSeriesBailHook<[string, NormalModule], string | Buffer>
>;
}
declare abstract class NormalModuleFactory extends ModuleFactory {
hooks: Readonly<{
resolve: AsyncSeriesBailHook<[ResolveData], any>;
resolveForScheme: HookMap<
AsyncSeriesBailHook<
[
{
resource: string;
path: string;
query: string;
fragment: string;
data: Record<string, any>;
},
ResolveData
],
true | void
>
>;
factorize: AsyncSeriesBailHook<[ResolveData], any>;
beforeResolve: AsyncSeriesBailHook<[ResolveData], any>;
afterResolve: AsyncSeriesBailHook<[ResolveData], any>;
@ -6549,6 +6584,11 @@ declare interface RuleSetRule {
*/
resource?: RuleSetConditionAbsolute;
/**
* Match the resource fragment of the module.
*/
resourceFragment?: RuleSetCondition;
/**
* Match the resource query of the module.
*/
@ -8623,6 +8663,11 @@ declare namespace exports {
export { MEASURE_START_OPERATION, MEASURE_END_OPERATION };
}
}
export namespace experiments {
export namespace schemes {
export { HttpUriPlugin, HttpsUriPlugin };
}
}
export type WebpackPluginFunction = (
this: Compiler,
compiler: Compiler

View File

@ -4465,10 +4465,10 @@ load-json-file@^4.0.0:
pify "^3.0.0"
strip-bom "^3.0.0"
loader-runner@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-3.1.0.tgz#e9440e5875f2ad2f968489cd2c7b59a4f2847fcb"
integrity sha512-wE/bOCdTKMR2rm7Xxh+eirDOmN7Vx7hntWgiTayuFPtF8MgsFDo49SP8kkYz8IVlEBTOtR7P+XI7bE1xjo/IkA==
loader-runner@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.0.0.tgz#02abcfd9fe6ff7a5aeb3547464746c4dc6ba333d"
integrity sha512-Rqf48ufrr48gFjnaqss04QesoXB7VenbpFFIV/0yOKGnpbejrVlOPqTsoX42FG5goXM5Ixekcs4DqDzHOX2z7Q==
loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
version "1.4.0"