Merge pull request #11258 from gkjohnson/dynamically-resolve-public-path

Dynamically resolve public path by default
This commit is contained in:
Tobias Koppers 2020-09-18 19:36:06 +02:00 committed by GitHub
commit f502828e99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 681 additions and 412 deletions

View File

@ -466,6 +466,10 @@ export type Iife = boolean;
* The name of the native import() function (can be exchanged for a polyfill).
*/
export type ImportFunctionName = string;
/**
* The name of the native import.meta object (can be exchanged for a polyfill).
*/
export type ImportMetaName = string;
/**
* Make the output files a library, exporting the exports of the entry point.
*/
@ -486,6 +490,7 @@ export type Pathinfo = boolean;
* The `publicPath` specifies the public URL address of the output files when referenced in a browser.
*/
export type PublicPath =
| "auto"
| string
| ((
pathData: import("../lib/Compilation").PathData,
@ -1759,6 +1764,10 @@ export interface Output {
* The name of the native import() function (can be exchanged for a polyfill).
*/
importFunctionName?: ImportFunctionName;
/**
* The name of the native import.meta object (can be exchanged for a polyfill).
*/
importMetaName?: ImportMetaName;
/**
* Make the output files a library, exporting the exports of the entry point.
*/
@ -2426,6 +2435,10 @@ export interface OutputNormalized {
* The name of the native import() function (can be exchanged for a polyfill).
*/
importFunctionName?: ImportFunctionName;
/**
* The name of the native import.meta object (can be exchanged for a polyfill).
*/
importMetaName?: ImportMetaName;
/**
* Options for library.
*/

View File

@ -8,6 +8,7 @@
const RuntimeGlobals = require("./RuntimeGlobals");
const RuntimeRequirementsDependency = require("./dependencies/RuntimeRequirementsDependency");
const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
const AutoPublicPathRuntimeModule = require("./runtime/AutoPublicPathRuntimeModule");
const CompatGetDefaultExportRuntimeModule = require("./runtime/CompatGetDefaultExportRuntimeModule");
const CompatRuntimeModule = require("./runtime/CompatRuntimeModule");
const CreateFakeNamespaceObjectRuntimeModule = require("./runtime/CreateFakeNamespaceObjectRuntimeModule");
@ -157,16 +158,26 @@ class RuntimePlugin {
});
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.publicPath)
.tap("RuntimePlugin", chunk => {
const module = new PublicPathRuntimeModule();
const publicPath = compilation.outputOptions.publicPath;
if (
typeof publicPath !== "string" ||
/\[(full)?hash\]/.test(publicPath)
) {
module.fullHash = true;
.tap("RuntimePlugin", (chunk, set) => {
const { outputOptions } = compilation;
const { publicPath, scriptType } = outputOptions;
if (publicPath === "auto") {
const module = new AutoPublicPathRuntimeModule();
if (scriptType !== "module") set.add(RuntimeGlobals.global);
compilation.addRuntimeModule(chunk, module);
} else {
const module = new PublicPathRuntimeModule();
if (
typeof publicPath !== "string" ||
/\[(full)?hash\]/.test(publicPath)
) {
module.fullHash = true;
}
compilation.addRuntimeModule(chunk, module);
}
compilation.addRuntimeModule(chunk, module);
return true;
});
compilation.hooks.runtimeRequirementInTree

View File

@ -91,6 +91,11 @@ class RuntimeTemplate {
return this.outputOptions.environment.module;
}
supportTemplateLiteral() {
// TODO
return false;
}
returningFunction(returnValue, args = "") {
return this.supportsArrowFunction()
? `(${args}) => ${returnValue}`

View File

@ -537,6 +537,7 @@ const applyOutputDefaults = (
F(output, "module", () => !!outputModule);
F(output, "iife", () => !output.module);
D(output, "importFunctionName", "import");
D(output, "importMetaName", "import.meta");
F(output, "chunkFilename", () => {
const filename = output.filename;
if (typeof filename !== "function") {
@ -553,7 +554,6 @@ const applyOutputDefaults = (
});
D(output, "assetModuleFilename", "[hash][ext][query]");
D(output, "webassemblyModuleFilename", "[hash].module.wasm");
D(output, "publicPath", "");
D(output, "compareBeforeEmit", true);
D(output, "charset", true);
F(output, "hotUpdateGlobal", () =>
@ -639,6 +639,13 @@ const applyOutputDefaults = (
D(output, "hotUpdateMainFilename", "[fullhash].hot-update.json");
D(output, "crossOriginLoading", false);
F(output, "scriptType", () => (output.module ? "module" : false));
D(
output,
"publicPath",
(tp && (tp.document || tp.importScripts)) || output.scriptType === "module"
? "auto"
: ""
);
D(output, "chunkLoadTimeout", 120000);
D(output, "hashFunction", "md4");
D(output, "hashDigest", "hex");

View File

@ -252,6 +252,7 @@ const getNormalizedWebpackOptions = config => {
hotUpdateMainFilename: output.hotUpdateMainFilename,
iife: output.iife,
importFunctionName: output.importFunctionName,
importMetaName: output.importMetaName,
scriptType: output.scriptType,
library: libraryBase && {
type:

View File

@ -0,0 +1,68 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");
const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
const { getUndoPath } = require("../util/identifier");
class AutoPublicPathRuntimeModule extends RuntimeModule {
constructor() {
super("publicPath", 5);
}
/**
* @returns {string} runtime code
*/
generate() {
const { compilation } = this;
const { scriptType, importMetaName } = compilation.outputOptions;
const chunkName = compilation.getPath(
JavascriptModulesPlugin.getChunkFilenameTemplate(
this.chunk,
compilation.outputOptions
),
{
chunk: this.chunk,
contentHashType: "javascript"
}
);
const undoPath = getUndoPath(chunkName, false);
return Template.asString([
"var scriptUrl;",
scriptType === "module"
? `if (typeof ${importMetaName}.url === "string") scriptUrl = ${importMetaName}.url`
: Template.asString([
`if (${RuntimeGlobals.global}.importScripts) scriptUrl = ${RuntimeGlobals.global}.location + "";`,
`var document = ${RuntimeGlobals.global}.document;`,
"if (!scriptUrl && document) {",
Template.indent([
`if (document.currentScript)`,
Template.indent(`scriptUrl = document.currentScript.src`),
"if (!scriptUrl) {",
Template.indent([
'var scripts = document.getElementsByTagName("script");',
"if(scripts.length) scriptUrl = scripts[scripts.length - 1].src"
]),
"}"
]),
"}"
]),
"// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration",
'// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.',
'if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");',
'scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\\?.*$/, "").replace(/\\/[^\\/]+$/, "/");',
!undoPath
? `${RuntimeGlobals.publicPath} = scriptUrl;`
: `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify(
undoPath
)};`
]);
}
}
module.exports = AutoPublicPathRuntimeModule;

View File

@ -7,24 +7,22 @@
const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
/** @typedef {import("../Compilation")} Compilation */
class PublicPathRuntimeModule extends RuntimeModule {
constructor() {
super("publicPath");
super("publicPath", 5);
}
/**
* @returns {string} runtime code
*/
generate() {
const { compilation } = this;
const { publicPath } = compilation.outputOptions;
return `${RuntimeGlobals.publicPath} = ${JSON.stringify(
this.compilation.getPath(
this.compilation.outputOptions.publicPath || "",
{
hash: this.compilation.hash || "XXXX"
}
)
this.compilation.getPath(publicPath || "", {
hash: this.compilation.hash || "XXXX"
})
)};`;
}
}

View File

@ -780,6 +780,10 @@
"description": "The name of the native import() function (can be exchanged for a polyfill).",
"type": "string"
},
"ImportMetaName": {
"description": "The name of the native import.meta object (can be exchanged for a polyfill).",
"type": "string"
},
"InfrastructureLogging": {
"description": "Options for infrastructure level logging.",
"type": "object",
@ -1868,6 +1872,9 @@
"importFunctionName": {
"$ref": "#/definitions/ImportFunctionName"
},
"importMetaName": {
"$ref": "#/definitions/ImportMetaName"
},
"library": {
"$ref": "#/definitions/Library"
},
@ -2032,6 +2039,9 @@
"importFunctionName": {
"$ref": "#/definitions/ImportFunctionName"
},
"importMetaName": {
"$ref": "#/definitions/ImportMetaName"
},
"library": {
"$ref": "#/definitions/LibraryOptions"
},
@ -2147,6 +2157,9 @@
"PublicPath": {
"description": "The `publicPath` specifies the public URL address of the output files when referenced in a browser.",
"anyOf": [
{
"enum": ["auto"]
},
{
"type": "string"
},

View File

@ -9,6 +9,7 @@ const checkArrayExpectation = require("./checkArrayExpectation");
const createLazyTestEnv = require("./helpers/createLazyTestEnv");
const deprecationTracking = require("./helpers/deprecationTracking");
const FakeDocument = require("./helpers/FakeDocument");
const CurrentScript = require("./helpers/CurrentScript");
const webpack = require("..");
const prepareOptions = require("./helpers/prepareOptions");
@ -222,12 +223,13 @@ const describeCases = config => {
const bundlePath = testConfig.findBundle(i, optionsArr[i]);
if (bundlePath) {
filesCount++;
const document = new FakeDocument();
const globalContext = {
console: console,
expect: expect,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
document: new FakeDocument(),
document,
location: {
href: "https://test.cases/path/index.html",
origin: "https://test.cases",
@ -243,6 +245,7 @@ const describeCases = config => {
if (Array.isArray(module) || /^\.\.?\//.test(module)) {
let content;
let p;
let subPath = "";
if (Array.isArray(module)) {
p = path.join(currentDirectory, ".array-require.js");
content = `module.exports = (${module
@ -253,6 +256,26 @@ const describeCases = config => {
} else {
p = path.join(currentDirectory, module);
content = fs.readFileSync(p, "utf-8");
const lastSlash = module.lastIndexOf("/");
let firstSlash = module.indexOf("/");
if (lastSlash !== -1 && firstSlash !== lastSlash) {
if (firstSlash !== -1) {
let next = module.indexOf("/", firstSlash + 1);
let dir = module.slice(firstSlash + 1, next);
while (dir === ".") {
firstSlash = next;
next = module.indexOf("/", firstSlash + 1);
dir = module.slice(firstSlash + 1, next);
}
}
subPath = module.slice(
firstSlash + 1,
lastSlash + 1
);
}
}
if (p in requireCache) {
return requireCache[p].exports;
@ -262,6 +285,9 @@ const describeCases = config => {
};
requireCache[p] = m;
let runInNewContext = false;
let oldCurrentScript = document.currentScript;
document.currentScript = new CurrentScript(subPath);
const moduleScope = {
require: _require.bind(
null,
@ -357,6 +383,10 @@ const describeCases = config => {
? vm.runInNewContext(code, globalContext, p)
: vm.runInThisContext(code, p);
fn.call(m.exports, moduleScope);
//restore state
document.currentScript = oldCurrentScript;
return m.exports;
} else if (
testConfig.modules &&

View File

@ -303,11 +303,12 @@ describe("Defaults", () => {
"hotUpdateMainFilename": "[fullhash].hot-update.json",
"iife": true,
"importFunctionName": "import",
"importMetaName": "import.meta",
"library": undefined,
"module": false,
"path": "<cwd>/dist",
"pathinfo": false,
"publicPath": "",
"publicPath": "auto",
"scriptType": false,
"sourceMapFilename": "[file].map[query]",
"sourcePrefix": undefined,
@ -972,6 +973,9 @@ describe("Defaults", () => {
- "globalObject": "self",
+ "globalObject": "global",
@@ ... @@
- "publicPath": "auto",
+ "publicPath": "",
@@ ... @@
- "wasmLoading": "fetch",
+ "wasmLoading": "async-node",
@@ ... @@
@ -1096,6 +1100,9 @@ describe("Defaults", () => {
- "globalObject": "self",
+ "globalObject": "global",
@@ ... @@
- "publicPath": "auto",
+ "publicPath": "",
@@ ... @@
- "wasmLoading": "fetch",
+ "wasmLoading": "async-node",
@@ ... @@
@ -1202,6 +1209,9 @@ describe("Defaults", () => {
- "globalObject": "self",
+ "globalObject": "global",
@@ ... @@
- "publicPath": "auto",
+ "publicPath": "",
@@ ... @@
- "wasmLoading": "fetch",
+ "wasmLoading": "async-node",
@@ ... @@

View File

@ -66,6 +66,8 @@ const describeCases = config => {
options.output.chunkFilename = "[name].chunk.[fullhash].js";
if (options.output.pathinfo === undefined)
options.output.pathinfo = true;
if (options.output.publicPath === undefined)
options.output.publicPath = "";
if (options.output.library === undefined)
options.output.library = { type: "commonjs2" };
if (!options.optimization) options.optimization = {};

View File

@ -173,10 +173,10 @@ describe("Stats", () => {
"assets": Array [
Object {
"name": "entryB.js",
"size": 2185,
"size": 2698,
},
],
"assetsSize": 2185,
"assetsSize": 2698,
"auxiliaryAssets": undefined,
"auxiliaryAssetsSize": 0,
"childAssets": undefined,
@ -220,10 +220,10 @@ describe("Stats", () => {
"filteredRelated": undefined,
"info": Object {
"minimized": true,
"size": 2185,
"size": 2698,
},
"name": "entryB.js",
"size": 2185,
"size": 2698,
"type": "asset",
},
Object {

View File

@ -496,7 +496,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.output has an unknown property 'ecmaVersion'. These properties are valid:
object { assetModuleFilename?, auxiliaryComment?, charset?, chunkFilename?, chunkFormat?, chunkLoadTimeout?, chunkLoading?, chunkLoadingGlobal?, compareBeforeEmit?, crossOriginLoading?, devtoolFallbackModuleFilenameTemplate?, devtoolModuleFilenameTemplate?, devtoolNamespace?, enabledChunkLoadingTypes?, enabledLibraryTypes?, enabledWasmLoadingTypes?, environment?, filename?, globalObject?, hashDigest?, hashDigestLength?, hashFunction?, hashSalt?, hotUpdateChunkFilename?, hotUpdateGlobal?, hotUpdateMainFilename?, iife?, importFunctionName?, library?, libraryExport?, libraryTarget?, module?, path?, pathinfo?, publicPath?, scriptType?, sourceMapFilename?, sourcePrefix?, strictModuleExceptionHandling?, umdNamedDefine?, uniqueName?, wasmLoading?, webassemblyModuleFilename?, workerChunkLoading?, workerWasmLoading? }
object { assetModuleFilename?, auxiliaryComment?, charset?, chunkFilename?, chunkFormat?, chunkLoadTimeout?, chunkLoading?, chunkLoadingGlobal?, compareBeforeEmit?, crossOriginLoading?, devtoolFallbackModuleFilenameTemplate?, devtoolModuleFilenameTemplate?, devtoolNamespace?, enabledChunkLoadingTypes?, enabledLibraryTypes?, enabledWasmLoadingTypes?, environment?, filename?, globalObject?, hashDigest?, hashDigestLength?, hashFunction?, hashSalt?, hotUpdateChunkFilename?, hotUpdateGlobal?, hotUpdateMainFilename?, iife?, importFunctionName?, importMetaName?, library?, libraryExport?, libraryTarget?, module?, path?, pathinfo?, publicPath?, scriptType?, sourceMapFilename?, sourcePrefix?, strictModuleExceptionHandling?, umdNamedDefine?, uniqueName?, wasmLoading?, webassemblyModuleFilename?, workerChunkLoading?, workerWasmLoading? }
-> Options affecting the output of the compilation. \`output\` options tell webpack how to write the compiled files to disk.
Did you mean output.environment?"
`)

View File

@ -2445,6 +2445,19 @@ Object {
"multiple": false,
"simpleType": "string",
},
"output-import-meta-name": Object {
"configs": Array [
Object {
"description": "The name of the native import.meta object (can be exchanged for a polyfill).",
"multiple": false,
"path": "output.importMetaName",
"type": "string",
},
],
"description": "The name of the native import.meta object (can be exchanged for a polyfill).",
"multiple": false,
"simpleType": "string",
},
"output-library": Object {
"configs": Array [
Object {
@ -2783,6 +2796,15 @@ Object {
},
"output-public-path": Object {
"configs": Array [
Object {
"description": "The \`publicPath\` specifies the public URL address of the output files when referenced in a browser.",
"multiple": false,
"path": "output.publicPath",
"type": "enum",
"values": Array [
"auto",
],
},
Object {
"description": "The \`publicPath\` specifies the public URL address of the output files when referenced in a browser.",
"multiple": false,

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ module.exports = {
devtool: false,
output: {
filename: "deep/path/[name].js",
assetModuleFilename: "[path][name][ext]"
assetModuleFilename: "[path][name][ext]",
publicPath: ""
}
};

View File

@ -0,0 +1,5 @@
import asset from "./asset.jpg";
it("should define public path", () => {
expect(asset).toBe("asset.jpg");
});

View File

@ -0,0 +1,16 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "none",
target: "node",
output: {
assetModuleFilename: "[name][ext]"
},
module: {
rules: [
{
test: /\.jpg$/,
type: "asset/resource"
}
]
}
};

View File

@ -0,0 +1,5 @@
import asset from "./asset.jpg";
it("should define public path", () => {
expect(asset).toBe("http://test.co/path/asset.jpg");
});

View File

@ -0,0 +1,10 @@
module.exports = {
findBundle: function() {
return [
"./index.mjs"
];
},
moduleScope(scope) {
scope.pseudoImport = { meta: { url: "http://test.co/path/index.js" } };
}
};

View File

@ -0,0 +1,19 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "none",
target: "electron-renderer",
output: {
assetModuleFilename: "[name][ext]",
importMetaName: "pseudoImport.meta",
scriptType: "module",
filename: "index.mjs"
},
module: {
rules: [
{
test: /\.jpg$/,
type: "asset/resource"
}
]
}
};

View File

@ -0,0 +1,5 @@
import asset from "./asset.jpg";
it("should define public path", () => {
expect(asset).toBe("https://test.cases/path/inner1/inner2/../../asset.jpg");
});

View File

@ -0,0 +1,5 @@
import asset from "./asset.jpg";
it("should define public path", () => {
expect(asset).toBe("https://test.cases/path/asset.jpg");
});

View File

@ -0,0 +1,8 @@
module.exports = {
findBundle: function() {
return [
"./inner1/inner2/a.js",
"./b.js"
];
}
};

View File

@ -0,0 +1,25 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "none",
target: "web",
entry() {
return {
a: "./a",
b: "./b"
};
},
output: {
filename: data => {
return data.chunk.name === "a" ? `inner1/inner2/[name].js` : "[name].js";
},
assetModuleFilename: "[name][ext]"
},
module: {
rules: [
{
test: /\.jpg$/,
type: "asset/resource"
}
]
}
};

View File

@ -0,0 +1,8 @@
class CurrentScript {
constructor(path = "", type = "text/javascript") {
this.src = `https://test.cases/path/${path}index.js`;
this.type = type;
}
}
module.exports = CurrentScript;

View File

@ -10,6 +10,9 @@ module.exports = {
}
]
},
output: {
publicPath: ""
},
target: "web",
node: {
__dirname: false

11
types.d.ts vendored
View File

@ -6036,6 +6036,11 @@ declare interface Output {
*/
importFunctionName?: string;
/**
* The name of the native import.meta object (can be exchanged for a polyfill).
*/
importMetaName?: string;
/**
* Make the output files a library, exporting the exports of the entry point.
*/
@ -6280,6 +6285,11 @@ declare interface OutputNormalized {
*/
importFunctionName?: string;
/**
* The name of the native import.meta object (can be exchanged for a polyfill).
*/
importMetaName?: string;
/**
* Options for library.
*/
@ -7785,6 +7795,7 @@ declare abstract class RuntimeTemplate {
supportsBigIntLiteral(): boolean;
supportsDynamicImport(): boolean;
supportsEcmaScriptModuleSyntax(): boolean;
supportTemplateLiteral(): boolean;
returningFunction(returnValue?: any, args?: string): string;
basicFunction(args?: any, body?: any): string;
destructureArray(items?: any, value?: any): string;