mirror of https://github.com/webpack/webpack.git
refactor scheme handling into plugins
This commit is contained in:
parent
a11922fea3
commit
7dad1c1c30
|
|
@ -5,14 +5,11 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const http = require("http");
|
||||
const https = require("https");
|
||||
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 { URL, fileURLToPath } = require("url");
|
||||
const { HookMap, SyncHook, AsyncSeriesBailHook } = require("tapable");
|
||||
const {
|
||||
CachedSource,
|
||||
OriginalSource,
|
||||
|
|
@ -26,9 +23,8 @@ const ModuleError = require("./ModuleError");
|
|||
const ModuleParseError = require("./ModuleParseError");
|
||||
const ModuleWarning = require("./ModuleWarning");
|
||||
const RuntimeGlobals = require("./RuntimeGlobals");
|
||||
const UnsupportedSchemeError = require("./UnsupportedSchemeError");
|
||||
const UnhandledSchemeError = require("./UnhandledSchemeError");
|
||||
const WebpackError = require("./WebpackError");
|
||||
const { decodeDataURI } = require("./util/DataURI");
|
||||
const { getScheme } = require("./util/URLAbsoluteSpecifier");
|
||||
const {
|
||||
compareLocations,
|
||||
|
|
@ -130,88 +126,6 @@ const asBuffer = input => {
|
|||
return input;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {InputFileSystem} fs fs
|
||||
* @returns {(resource: string, cb: (err: Error|null, data?: string|Buffer) => void) => void} read function
|
||||
*/
|
||||
const readResourceFn = fs => {
|
||||
return (resource, callback) => {
|
||||
const scheme = getScheme(resource);
|
||||
if (scheme) {
|
||||
switch (scheme) {
|
||||
case "data":
|
||||
return process.nextTick(() => {
|
||||
callback(null, decodeDataURI(resource));
|
||||
});
|
||||
case "file":
|
||||
return fs.readFile(fileURLToPath(new URL(resource)), callback);
|
||||
case "http":
|
||||
return http.get(new URL(resource), res => {
|
||||
if (res.statusCode !== 200) {
|
||||
res.destroy();
|
||||
return callback(
|
||||
new ModuleBuildError(resource, {
|
||||
from: `http request status code = ${res.statusCode}`
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const bufferArr = [];
|
||||
|
||||
res.on("data", chunk => {
|
||||
bufferArr.push(chunk);
|
||||
});
|
||||
|
||||
res.on("end", () => {
|
||||
if (!res.complete) {
|
||||
return callback(
|
||||
new ModuleBuildError(resource, {
|
||||
from: "http request was terminated"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
callback(null, Buffer.concat(bufferArr));
|
||||
});
|
||||
});
|
||||
case "https":
|
||||
return https.get(new URL(resource), res => {
|
||||
if (res.statusCode !== 200) {
|
||||
res.destroy();
|
||||
return callback(
|
||||
new ModuleBuildError(resource, {
|
||||
from: `https request status code = ${res.statusCode}`
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const bufferArr = [];
|
||||
|
||||
res.on("data", chunk => {
|
||||
bufferArr.push(chunk);
|
||||
});
|
||||
|
||||
res.on("end", () => {
|
||||
if (!res.complete) {
|
||||
return callback(
|
||||
new ModuleBuildError(resource, {
|
||||
from: "https request was terminated"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
callback(null, Buffer.concat(bufferArr));
|
||||
});
|
||||
});
|
||||
default:
|
||||
callback(new UnsupportedSchemeError(scheme, resource));
|
||||
}
|
||||
}
|
||||
|
||||
fs.readFile(resource, callback);
|
||||
};
|
||||
};
|
||||
|
||||
class NonErrorEmittedError extends WebpackError {
|
||||
constructor(error) {
|
||||
super();
|
||||
|
|
@ -232,6 +146,7 @@ makeSerializable(
|
|||
/**
|
||||
* @typedef {Object} NormalModuleCompilationHooks
|
||||
* @property {SyncHook<[object, NormalModule]>} loader
|
||||
* @property {HookMap<AsyncSeriesBailHook<[string, NormalModule], string | Buffer>>} readResourceForScheme
|
||||
*/
|
||||
|
||||
/** @type {WeakMap<Compilation, NormalModuleCompilationHooks>} */
|
||||
|
|
@ -251,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);
|
||||
}
|
||||
|
|
@ -269,7 +187,6 @@ class NormalModule extends Module {
|
|||
* @param {string | undefined} options.matchResource path + query of the matched resource (virtual)
|
||||
* @param {Parser} options.parser the parser used
|
||||
* @param {Generator} options.generator the generator used
|
||||
* @param {string=} options.scheme scheme
|
||||
* @param {Object} options.resolveOptions options used for resolving requests from this module
|
||||
*/
|
||||
constructor({
|
||||
|
|
@ -282,8 +199,7 @@ class NormalModule extends Module {
|
|||
matchResource,
|
||||
parser,
|
||||
generator,
|
||||
resolveOptions,
|
||||
scheme
|
||||
resolveOptions
|
||||
}) {
|
||||
super(type, getContext(resource));
|
||||
|
||||
|
|
@ -304,8 +220,6 @@ class NormalModule extends Module {
|
|||
this.resource = resource;
|
||||
/** @type {string | undefined} */
|
||||
this.matchResource = matchResource;
|
||||
/** @type {string | undefined} */
|
||||
this.scheme = scheme;
|
||||
/** @type {LoaderItem[]} */
|
||||
this.loaders = loaders;
|
||||
if (resolveOptions !== undefined) {
|
||||
|
|
@ -380,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;
|
||||
|
|
@ -388,7 +303,6 @@ class NormalModule extends Module {
|
|||
this.resource = m.resource;
|
||||
this.matchResource = m.matchResource;
|
||||
this.loaders = m.loaders;
|
||||
this.scheme = m.scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -700,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) {
|
||||
|
|
@ -994,9 +923,6 @@ class NormalModule extends Module {
|
|||
// always try to build in case of an error
|
||||
if (this.error) return callback(null, true);
|
||||
|
||||
// always build when module resource is an absolute url, except data url
|
||||
if (this.scheme && this.scheme !== "data") return callback(null, true);
|
||||
|
||||
// always build when module is not cacheable
|
||||
if (!this.buildInfo.cacheable) return callback(null, true);
|
||||
|
||||
|
|
@ -1039,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);
|
||||
|
|
@ -1054,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,
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@ const BasicMatcherRulePlugin = require("./rules/BasicMatcherRulePlugin");
|
|||
const RuleSetCompiler = require("./rules/RuleSetCompiler");
|
||||
const UseEffectRulePlugin = require("./rules/UseEffectRulePlugin");
|
||||
const LazySet = require("./util/LazySet");
|
||||
const { getScheme, getMimetype } = require("./util/URLAbsoluteSpecifier");
|
||||
const { getScheme } = require("./util/URLAbsoluteSpecifier");
|
||||
const { cachedCleverMerge, cachedSetProperty } = require("./util/cleverMerge");
|
||||
const { join } = require("./util/fs");
|
||||
const { parsePathQueryFragment } = 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 = /^([^!]+)!=!/;
|
||||
|
|
@ -148,6 +159,10 @@ class NormalModuleFactory extends ModuleFactory {
|
|||
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>} */
|
||||
|
|
@ -266,24 +281,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 +300,10 @@ class NormalModuleFactory extends ModuleFactory {
|
|||
matchResource = join(this.fs, context, matchResource);
|
||||
}
|
||||
}
|
||||
matchResourceData = {
|
||||
resource: matchResource,
|
||||
...parsePathQueryFragment(matchResource)
|
||||
};
|
||||
requestWithoutMatchResource = request.substr(
|
||||
matchResourceMatch[0].length
|
||||
);
|
||||
|
|
@ -319,11 +328,11 @@ class NormalModuleFactory extends ModuleFactory {
|
|||
contextDependencies
|
||||
};
|
||||
|
||||
/** @type {string | false} */
|
||||
let resource;
|
||||
/** @type {ResourceDataWithData} */
|
||||
let resourceData;
|
||||
/** @type {string | undefined} */
|
||||
const scheme = getScheme(unresolvedResource);
|
||||
let resourceResolveData;
|
||||
|
||||
let loaders;
|
||||
|
||||
const continueCallback = needCalls(2, err => {
|
||||
|
|
@ -354,7 +363,7 @@ class NormalModuleFactory extends ModuleFactory {
|
|||
return callback(e);
|
||||
}
|
||||
|
||||
if (resource === false) {
|
||||
if (!resourceData) {
|
||||
// ignored
|
||||
return callback(
|
||||
null,
|
||||
|
|
@ -367,26 +376,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: scheme ? getMimetype(scheme, resource) : "",
|
||||
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 +427,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 +438,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),
|
||||
|
|
@ -501,19 +507,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 (scheme) {
|
||||
resource = unresolvedResource;
|
||||
// resource without scheme and without path
|
||||
else if (/^($|\?|#)/.test(unresolvedResource)) {
|
||||
resourceData = {
|
||||
resource: unresolvedResource,
|
||||
data: {},
|
||||
...parsePathQueryFragment(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,
|
||||
|
|
@ -521,8 +553,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,
|
||||
...parsePathQueryFragment(resolvedResource)
|
||||
};
|
||||
}
|
||||
continueCallback();
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
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 UnsupportedSchemeError extends WebpackError {
|
||||
/**
|
||||
* @param {string} scheme scheme
|
||||
* @param {string} specifier specifier
|
||||
*/
|
||||
constructor(scheme, specifier) {
|
||||
super(
|
||||
`Unsupported scheme ${JSON.stringify(
|
||||
scheme
|
||||
)} provided, specifier: ${specifier}.`
|
||||
);
|
||||
this.name = "UnsupportedSchemeError";
|
||||
}
|
||||
}
|
||||
|
||||
makeSerializable(
|
||||
UnsupportedSchemeError,
|
||||
"webpack/lib/UnsupportedSchemeError",
|
||||
"UnsupportedSchemeError"
|
||||
);
|
||||
|
||||
module.exports = UnsupportedSchemeError;
|
||||
|
|
@ -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");
|
||||
|
|
@ -325,6 +328,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,
|
||||
|
|
|
|||
|
|
@ -426,5 +426,13 @@ module.exports = mergeExports(fn, {
|
|||
get serialization() {
|
||||
return require("./util/serialization");
|
||||
}
|
||||
},
|
||||
|
||||
experiments: {
|
||||
schemes: {
|
||||
get HttpUriPlugin() {
|
||||
return require("./schemes/HttpUriPlugin");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Tobias Koppers @sokra
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { URL } = require("url");
|
||||
const NormalModule = require("../NormalModule");
|
||||
const memorize = require("../util/memorize");
|
||||
|
||||
/** @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 }) => {
|
||||
const resolveHandler = 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);
|
||||
};
|
||||
const readHandler = (scheme, getBuiltin) => (
|
||||
resource,
|
||||
module,
|
||||
callback
|
||||
) => {
|
||||
return getBuiltin().get(new URL(resource), res => {
|
||||
if (res.statusCode !== 200) {
|
||||
res.destroy();
|
||||
return callback(
|
||||
new Error(`${scheme} request status code = ${res.statusCode}`)
|
||||
);
|
||||
}
|
||||
|
||||
const bufferArr = [];
|
||||
|
||||
res.on("data", chunk => {
|
||||
bufferArr.push(chunk);
|
||||
});
|
||||
|
||||
res.on("end", () => {
|
||||
if (!res.complete) {
|
||||
return callback(new Error(`${scheme} request was terminated`));
|
||||
}
|
||||
|
||||
callback(null, Buffer.concat(bufferArr));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
normalModuleFactory.hooks.resolveForScheme
|
||||
.for("http")
|
||||
.tap("HttpUriPlugin", resolveHandler);
|
||||
normalModuleFactory.hooks.resolveForScheme
|
||||
.for("https")
|
||||
.tap("HttpUriPlugin", resolveHandler);
|
||||
NormalModule.getCompilationHooks(compilation)
|
||||
.readResourceForScheme.for("http")
|
||||
.tapAsync(
|
||||
"HttpUriPlugin",
|
||||
readHandler(
|
||||
"http",
|
||||
memorize(() => require("http"))
|
||||
)
|
||||
);
|
||||
NormalModule.getCompilationHooks(compilation)
|
||||
.readResourceForScheme.for("https")
|
||||
.tapAsync(
|
||||
"HttpUriPlugin",
|
||||
readHandler(
|
||||
"https",
|
||||
memorize(() => require("https"))
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HttpUriPlugin;
|
||||
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { getMimetype: getDataUrlMimetype } = require("./DataURI");
|
||||
|
||||
/** @typedef {import("./fs").InputFileSystem} InputFileSystem */
|
||||
/** @typedef {(error: Error|null, result?: Buffer) => void} ErrorFirstCallback */
|
||||
|
||||
|
|
@ -85,20 +83,5 @@ function getProtocol(specifier) {
|
|||
return scheme === undefined ? undefined : scheme + ":";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} scheme protocol
|
||||
* @param {string} specifier specifier
|
||||
* @returns {string} mimetype if exists
|
||||
*/
|
||||
function getMimetype(scheme, specifier) {
|
||||
switch (scheme) {
|
||||
case "data":
|
||||
return getDataUrlMimetype(specifier);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
exports.getScheme = getScheme;
|
||||
exports.getMimetype = getMimetype;
|
||||
exports.getProtocol = getProtocol;
|
||||
|
|
|
|||
|
|
@ -243,3 +243,19 @@ const _absolutify = (context, request) => {
|
|||
|
||||
const absolutify = makeCacheable(_absolutify);
|
||||
exports.absolutify = absolutify;
|
||||
|
||||
const PATH_QUERY_FRAGMENT_REGEXP = /^([^?#]*)(\?[^#]*)?(#.*)?$/;
|
||||
|
||||
/**
|
||||
* @param {string} str the path with query and fragment
|
||||
* @returns {{ path: string, query: string, fragment: string }} parsed parts
|
||||
*/
|
||||
const parsePathQueryFragment = str => {
|
||||
const match = PATH_QUERY_FRAGMENT_REGEXP.exec(str);
|
||||
return {
|
||||
path: match[1],
|
||||
query: match[2] || "",
|
||||
fragment: match[3] || ""
|
||||
};
|
||||
};
|
||||
exports.parsePathQueryFragment = parsePathQueryFragment;
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ module.exports = {
|
|||
require("../sharing/ProvideForSharedDependency"),
|
||||
UnsupportedFeatureWarning: () => require("../UnsupportedFeatureWarning"),
|
||||
"util/LazySet": () => require("../util/LazySet"),
|
||||
UnsupportedSchemeError: () => require("../UnsupportedSchemeError"),
|
||||
UnhandledSchemeError: () => require("../UnhandledSchemeError"),
|
||||
WebpackError: () => require("../WebpackError"),
|
||||
|
||||
"util/registerExternalSerializer": () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import cssContent from "http://localhost:9990/index.css";
|
||||
import cssContent from "http://localhost:9990/index.css?query#fragment";
|
||||
|
||||
it("http url request should be supported", () => {
|
||||
expect(cssContent).toBe("a {}.webpack{}");
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
const { HttpUriPlugin } = require("../../../../").experiments.schemes;
|
||||
const ServerPlugin = require("./server");
|
||||
|
||||
/** @type {import("../../../../").Configuration} */
|
||||
|
|
@ -11,5 +12,5 @@ module.exports = {
|
|||
}
|
||||
]
|
||||
},
|
||||
plugins: [new ServerPlugin(9990)]
|
||||
plugins: [new ServerPlugin(9990), new HttpUriPlugin()]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
const { HttpUriPlugin } = require("../../../../").experiments.schemes;
|
||||
|
||||
/** @type {import("../../../../").Configuration} */
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
|
|
@ -8,5 +10,6 @@ module.exports = {
|
|||
loader: "./loaders/md-loader"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
plugins: [new HttpUriPlugin()]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2858,6 +2858,14 @@ 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 IgnorePlugin {
|
||||
constructor(options: IgnorePluginOptions);
|
||||
options: IgnorePluginOptions;
|
||||
|
|
@ -4440,10 +4448,6 @@ declare class NormalModule extends Module {
|
|||
* the generator used
|
||||
*/
|
||||
generator: Generator;
|
||||
/**
|
||||
* scheme
|
||||
*/
|
||||
scheme?: string;
|
||||
/**
|
||||
* options used for resolving requests from this module
|
||||
*/
|
||||
|
|
@ -4457,7 +4461,6 @@ declare class NormalModule extends Module {
|
|||
generator: Generator;
|
||||
resource: string;
|
||||
matchResource: string;
|
||||
scheme: string;
|
||||
loaders: LoaderItem[];
|
||||
error: WebpackError;
|
||||
createSourceForAsset(
|
||||
|
|
@ -4497,10 +4500,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>;
|
||||
|
|
@ -8617,6 +8638,11 @@ declare namespace exports {
|
|||
export { MEASURE_START_OPERATION, MEASURE_END_OPERATION };
|
||||
}
|
||||
}
|
||||
export namespace experiments {
|
||||
export namespace schemes {
|
||||
export { HttpUriPlugin };
|
||||
}
|
||||
}
|
||||
export type WebpackPluginFunction = (
|
||||
this: Compiler,
|
||||
compiler: Compiler
|
||||
|
|
|
|||
Loading…
Reference in New Issue