mirror of https://github.com/webpack/webpack.git
feat: url assets
This commit is contained in:
parent
3e4c2ef87a
commit
ddc83b0d70
|
|
@ -1011,6 +1011,10 @@ export interface RuleSetRule {
|
|||
* Match the child compiler name.
|
||||
*/
|
||||
compiler?: RuleSetConditionOrConditions;
|
||||
/**
|
||||
* Match dependency type.
|
||||
*/
|
||||
dependency?: string;
|
||||
/**
|
||||
* Match values of properties in the description file (usually package.json).
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Ivan Kopeykin @vankop
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const BaseURIRuntimeModule = require("./BaseURIRuntimeModule");
|
||||
const RuntimeGlobals = require("./RuntimeGlobals");
|
||||
|
||||
/** @typedef {import("../declarations/WebpackOptions").Target} Target */
|
||||
/** @typedef {import("./Compiler")} Compiler */
|
||||
|
||||
class BaseURIPlugin {
|
||||
/**
|
||||
* @param {Target} target target
|
||||
*/
|
||||
constructor(target) {
|
||||
switch (target) {
|
||||
case "webworker":
|
||||
this.environment = /** @type {"webworker"} */ ("webworker");
|
||||
break;
|
||||
case "node":
|
||||
case "async-node":
|
||||
case "node-webkit":
|
||||
case "electron-main":
|
||||
case "electron-preload":
|
||||
this.environment = /** @type {"node"} */ ("node");
|
||||
break;
|
||||
default:
|
||||
this.environment = /** @type {"web"} */ ("web");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Compiler} compiler compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.compilation.tap("BaseURIPlugin", compilation => {
|
||||
compilation.hooks.runtimeRequirementInTree
|
||||
.for(RuntimeGlobals.baseURI)
|
||||
.tap("BaseURIPlugin", (chunk, set) => {
|
||||
compilation.addRuntimeModule(
|
||||
chunk,
|
||||
new BaseURIRuntimeModule(this.environment)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseURIPlugin;
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Ivan Kopeykin @vankop
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const RuntimeGlobals = require("./RuntimeGlobals");
|
||||
const RuntimeModule = require("./RuntimeModule");
|
||||
const Template = require("./Template");
|
||||
|
||||
class BaseURIRuntimeModule extends RuntimeModule {
|
||||
/**
|
||||
* @param {"node"|"web"|"webworker"} environment environment
|
||||
*/
|
||||
constructor(environment) {
|
||||
super("baseURI");
|
||||
this.env = environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} runtime code
|
||||
*/
|
||||
generate() {
|
||||
switch (this.env) {
|
||||
case "web":
|
||||
return Template.asString([
|
||||
`${RuntimeGlobals.baseURI} = document.baseURI;`
|
||||
]);
|
||||
case "webworker":
|
||||
return Template.asString([
|
||||
`${RuntimeGlobals.baseURI} = self.location;`
|
||||
]);
|
||||
case "node":
|
||||
return Template.asString([
|
||||
`${RuntimeGlobals.baseURI} = require('url').pathToFileURL(__filename);`
|
||||
]);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseURIRuntimeModule;
|
||||
|
|
@ -132,6 +132,7 @@ const dependencyCache = new WeakMap();
|
|||
const ruleSetCompiler = new RuleSetCompiler([
|
||||
new BasicMatcherRulePlugin("test", "resource"),
|
||||
new BasicMatcherRulePlugin("mimetype"),
|
||||
new BasicMatcherRulePlugin("dependency"),
|
||||
new BasicMatcherRulePlugin("include", "resource"),
|
||||
new BasicMatcherRulePlugin("exclude", "resource", true),
|
||||
new BasicMatcherRulePlugin("resource"),
|
||||
|
|
|
|||
|
|
@ -278,3 +278,8 @@ exports.hasOwnProperty = "__webpack_require__.o";
|
|||
* the System.register context object
|
||||
*/
|
||||
exports.systemContext = "__webpack_require__.y";
|
||||
|
||||
/**
|
||||
* the baseURI of current document
|
||||
*/
|
||||
exports.baseURI = "__webpack_require__.b";
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ const TemplatedPathPlugin = require("./TemplatedPathPlugin");
|
|||
const UseStrictPlugin = require("./UseStrictPlugin");
|
||||
const WarnCaseSensitiveModulesPlugin = require("./WarnCaseSensitiveModulesPlugin");
|
||||
|
||||
const BaseURIPlugin = require("./BaseURIPlugin");
|
||||
const URLPlugin = require("./dependencies/URLPlugin");
|
||||
const DataUriPlugin = require("./schemes/DataUriPlugin");
|
||||
const FileUriPlugin = require("./schemes/FileUriPlugin");
|
||||
|
||||
|
|
@ -313,6 +315,8 @@ class WebpackOptionsApply extends OptionsApply {
|
|||
: true
|
||||
}).apply(compiler);
|
||||
|
||||
new BaseURIPlugin(options.target).apply(compiler);
|
||||
new URLPlugin().apply(compiler);
|
||||
new DataUriPlugin().apply(compiler);
|
||||
new FileUriPlugin().apply(compiler);
|
||||
|
||||
|
|
|
|||
|
|
@ -356,6 +356,13 @@ const applyModuleDefaults = (
|
|||
mimetype: "application/node",
|
||||
type: "javascript/auto"
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
dependency: "url",
|
||||
resolve: {
|
||||
conditionNames: ["esm", "..."]
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.json$/i,
|
||||
type: "json"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Ivan Kopeykin @vankop
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const RuntimeGlobals = require("../RuntimeGlobals");
|
||||
const ModuleDependency = require("./ModuleDependency");
|
||||
|
||||
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
|
||||
/** @typedef {import("../ChunkGraph")} ChunkGraph */
|
||||
/** @typedef {import("../Dependency")} Dependency */
|
||||
/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
|
||||
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
|
||||
/** @typedef {import("../ModuleGraph")} ModuleGraph */
|
||||
/** @typedef {import("../util/Hash")} Hash */
|
||||
|
||||
class URLDependency extends ModuleDependency {
|
||||
/**
|
||||
* @param {string} request request
|
||||
* @param {[number, number]} range range
|
||||
*/
|
||||
constructor(request, range) {
|
||||
super(request);
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return "new URL()";
|
||||
}
|
||||
|
||||
get category() {
|
||||
return "url";
|
||||
}
|
||||
}
|
||||
|
||||
URLDependency.Template = class URLDependencyTemplate extends ModuleDependency.Template {
|
||||
/**
|
||||
* @param {Dependency} dependency the dependency for which the template should be applied
|
||||
* @param {ReplaceSource} source the current replace source which can be modified
|
||||
* @param {DependencyTemplateContext} templateContext the context object
|
||||
* @returns {void}
|
||||
*/
|
||||
apply(dependency, source, templateContext) {
|
||||
const {
|
||||
chunkGraph,
|
||||
moduleGraph,
|
||||
runtimeRequirements,
|
||||
runtimeTemplate,
|
||||
runtime
|
||||
} = templateContext;
|
||||
const dep = /** @type {URLDependency} */ (dependency);
|
||||
|
||||
const connection = moduleGraph.getConnection(dep);
|
||||
if (connection && !connection.isActive(runtime)) return;
|
||||
|
||||
runtimeRequirements.add(RuntimeGlobals.baseURI);
|
||||
runtimeRequirements.add(RuntimeGlobals.require);
|
||||
|
||||
source.replace(
|
||||
dep.range[0],
|
||||
dep.range[1] - 1,
|
||||
`new URL(/* asset import */ ${runtimeTemplate.moduleRaw({
|
||||
chunkGraph,
|
||||
module: moduleGraph.getModule(dep),
|
||||
request: dep.request,
|
||||
runtimeRequirements,
|
||||
weak: false
|
||||
})}, ${RuntimeGlobals.baseURI})`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = URLDependency;
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Ivan Kopeykin @vankop
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const URLDependency = require("./URLDependency");
|
||||
|
||||
/** @typedef {import("estree").NewExpression} NewExpressionNode */
|
||||
/** @typedef {import("../Compiler")} Compiler */
|
||||
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
|
||||
|
||||
class URLPlugin {
|
||||
/**
|
||||
* @param {Compiler} compiler compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.compilation.tap(
|
||||
"URLPlugin",
|
||||
(compilation, { normalModuleFactory }) => {
|
||||
compilation.dependencyFactories.set(URLDependency, normalModuleFactory);
|
||||
compilation.dependencyTemplates.set(
|
||||
URLDependency,
|
||||
new URLDependency.Template()
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {JavascriptParser} parser parser
|
||||
*/
|
||||
const parserCallback = parser => {
|
||||
parser.hooks.new.for("URL").tap("URLPlugin", _expr => {
|
||||
const expr = /** @type {NewExpressionNode} */ (_expr);
|
||||
|
||||
if (expr.arguments.length !== 2) return;
|
||||
|
||||
const [arg1, arg2] = expr.arguments;
|
||||
|
||||
if (
|
||||
arg2.type !== "MemberExpression" ||
|
||||
arg1.type === "SpreadElement"
|
||||
)
|
||||
return;
|
||||
|
||||
const chain = parser.extractMemberExpressionChain(arg2);
|
||||
|
||||
if (
|
||||
chain.members.length !== 1 ||
|
||||
chain.object.type !== "MetaProperty" ||
|
||||
chain.object.property.name !== "meta" ||
|
||||
chain.members[0] !== "url"
|
||||
)
|
||||
return;
|
||||
|
||||
const request = parser.evaluateExpression(arg1).asString();
|
||||
|
||||
if (!request) return;
|
||||
|
||||
const dep = new URLDependency(request, expr.range);
|
||||
dep.loc = expr.loc;
|
||||
parser.state.module.addDependency(dep);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
normalModuleFactory.hooks.parser
|
||||
.for("javascript/auto")
|
||||
.tap("URLPlugin", parserCallback);
|
||||
|
||||
normalModuleFactory.hooks.parser
|
||||
.for("javascript/esm")
|
||||
.tap("URLPlugin", parserCallback);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = URLPlugin;
|
||||
|
|
@ -2551,6 +2551,10 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"dependency": {
|
||||
"description": "Match dependency type.",
|
||||
"type": "string"
|
||||
},
|
||||
"descriptionData": {
|
||||
"description": "Match values of properties in the description file (usually package.json).",
|
||||
"type": "object",
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
a {}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
const currentDir = require("url").pathToFileURL(__dirname);
|
||||
|
||||
it("should handle import.meta.url in URL()", () => {
|
||||
const {href} = new URL("./index.css", import.meta.url);
|
||||
|
||||
expect(href).toBe(currentDir + "/public/index.css");
|
||||
});
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/** @type {import("../../../../").Configuration} */
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
target: "node",
|
||||
devtool: false,
|
||||
output: {
|
||||
assetModuleFilename: "[name][ext]",
|
||||
publicPath: "public/"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
]
|
||||
},
|
||||
experiments: {
|
||||
asset: true
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
a {}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
const currentDir = require("url").pathToFileURL(__dirname);
|
||||
|
||||
it("should handle import.meta.url in URL()", () => {
|
||||
const {href} = new URL("./index.css", import.meta.url);
|
||||
|
||||
expect(href).toBe(currentDir + "/index.css");
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/** @type {import("../../../../").Configuration} */
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
target: "node",
|
||||
devtool: false,
|
||||
output: {
|
||||
assetModuleFilename: "[name][ext]"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
]
|
||||
},
|
||||
experiments: {
|
||||
asset: true
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
a {}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
it("should handle import.meta.url in URL()", () => {
|
||||
const {href} = new URL("./index.css", import.meta.url);
|
||||
|
||||
expect(href).toBe("file:///index.css");
|
||||
});
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/** @type {import("../../../../").Configuration} */
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
target: "node",
|
||||
devtool: false,
|
||||
output: {
|
||||
assetModuleFilename: "[name][ext]",
|
||||
publicPath: "/"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
]
|
||||
},
|
||||
experiments: {
|
||||
asset: true
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
a {}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
it("should handle import.meta.url in URL()", () => {
|
||||
const {href} = new URL("./index.css", import.meta.url);
|
||||
|
||||
expect(href).toBe("https://test.cases/path/index.css");
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
let _URL = require("url").URL;
|
||||
|
||||
module.exports = {
|
||||
moduleScope(scope) {
|
||||
scope.URL = function URL(a, b) {
|
||||
return new _URL(a, b);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/** @type {import("../../../../").Configuration} */
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
target: "web",
|
||||
devtool: false,
|
||||
output: {
|
||||
assetModuleFilename: "[name][ext]"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
]
|
||||
},
|
||||
experiments: {
|
||||
asset: true
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
a {}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
it("should handle import.meta.url in URL()", () => {
|
||||
const {href} = new URL("./index.css", import.meta.url);
|
||||
|
||||
expect(href).toBe("https://test.cases/path2/index.css");
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
let _URL = require("url").URL;
|
||||
|
||||
module.exports = {
|
||||
moduleScope(scope) {
|
||||
scope.URL = function URL(a, b) {
|
||||
return new _URL(a, b);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/** @type {import("../../../../").Configuration} */
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
target: "web",
|
||||
devtool: false,
|
||||
output: {
|
||||
assetModuleFilename: "[name][ext]",
|
||||
publicPath: "/path2/"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
]
|
||||
},
|
||||
experiments: {
|
||||
asset: true
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
a {}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
it("should handle import.meta.url in URL()", () => {
|
||||
const {href} = new URL("./index.css", import.meta.url);
|
||||
|
||||
expect(href).toBe("https://test.cases/path/index.css");
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
let _URL = require("url").URL;
|
||||
|
||||
module.exports = {
|
||||
moduleScope(scope) {
|
||||
scope.URL = function URL(a, b) {
|
||||
return new _URL(a, b);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/** @type {import("../../../../").Configuration} */
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
target: "webworker",
|
||||
devtool: false,
|
||||
output: {
|
||||
assetModuleFilename: "[name][ext]"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
]
|
||||
},
|
||||
experiments: {
|
||||
asset: true
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
a {}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
it("should handle import.meta.url in URL()", () => {
|
||||
const {href} = new URL("./index.css", import.meta.url);
|
||||
|
||||
expect(href).toBe("https://test.cases/index.css");
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
let _URL = require("url").URL;
|
||||
|
||||
module.exports = {
|
||||
moduleScope(scope) {
|
||||
scope.URL = function URL(a, b) {
|
||||
return new _URL(a, b);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/** @type {import("../../../../").Configuration} */
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
target: "webworker",
|
||||
devtool: false,
|
||||
output: {
|
||||
assetModuleFilename: "[name][ext]",
|
||||
publicPath: "/"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
]
|
||||
},
|
||||
experiments: {
|
||||
asset: true
|
||||
}
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
module.exports = class FakeDocument {
|
||||
constructor() {
|
||||
this.head = this.createElement("head");
|
||||
this.baseURI = "https://test.cases/path/index.html";
|
||||
this._elementsByTagName = new Map([["head", [this.head]]]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6948,6 +6948,11 @@ declare interface RuleSetRule {
|
|||
*/
|
||||
compiler?: RuleSetCondition;
|
||||
|
||||
/**
|
||||
* Match dependency type.
|
||||
*/
|
||||
dependency?: string;
|
||||
|
||||
/**
|
||||
* Match values of properties in the description file (usually package.json).
|
||||
*/
|
||||
|
|
@ -8990,6 +8995,7 @@ declare namespace exports {
|
|||
export let system: string;
|
||||
export let hasOwnProperty: string;
|
||||
export let systemContext: string;
|
||||
export let baseURI: string;
|
||||
}
|
||||
export const UsageState: Readonly<{
|
||||
Unused: 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue