/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");
/** @import Chunk from "../Chunk" */
/** @import ChunkGraph from "../ChunkGraph" */
/** @import Compilation from "../Compilation" */
class AutoPublicPathRuntimeModule extends RuntimeModule {
constructor() {
super("publicPath", RuntimeModule.STAGE_BASIC);
}
/**
* Generates runtime code for this runtime module.
* @returns {string | null} runtime code
*/
generate() {
const compilation = /** @type {Compilation} */ (this.compilation);
// The virtual build-time chunk has no script url and sits at the output
// root, so urls resolve against the `importModule` baseUri.
if (/** @type {ChunkGraph} */ (this.chunkGraph).buildTimeExecution) {
return `${RuntimeGlobals.publicPath} = "";`;
}
const { scriptType, importMetaName, environment } =
compilation.outputOptions;
const chunk = /** @type {Chunk} */ (this.chunk);
const undoPath = compilation.runtimeTemplate.chunkRootOutputDir(
chunk,
false
);
const global = environment.globalThis
? "globalThis"
: RuntimeGlobals.global;
const entryOptions = chunk.getEntryOptions();
// A worklet chunk is always loaded as a module via `addModule`, so it can
// read `import.meta.url` directly instead of the worker-scope detection.
const fromImportMeta =
scriptType === "module" || Boolean(entryOptions && entryOptions.worklet);
const runtimeTemplate = compilation.runtimeTemplate;
const cst = runtimeTemplate.renderConst();
const lt = runtimeTemplate.renderLet();
return Template.asString([
`${lt} scriptUrl;`,
fromImportMeta
? `if (typeof ${importMetaName}.url === "string") scriptUrl = ${importMetaName}.url`
: Template.asString([
`if (${global}.importScripts) scriptUrl = ${global}.location + "";`,
`${cst} document = ${global}.document;`,
"if (!scriptUrl && document) {",
Template.indent([
// Technically we could use `document.currentScript instanceof window.HTMLScriptElement`,
// but an attacker could try to inject ``
// and use `
`
`if (${runtimeTemplate.optionalChaining(
"document.currentScript",
"tagName.toUpperCase() === 'SCRIPT'"
)})`,
Template.indent("scriptUrl = document.currentScript.src;"),
"if (!scriptUrl) {",
Template.indent([
`${cst} scripts = document.getElementsByTagName("script");`,
"if(scripts.length) {",
Template.indent([
`${lt} i = scripts.length - 1;`,
"while (i > -1 && (!scriptUrl || !/^https?:/.test(scriptUrl))) scriptUrl = scripts[i--].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");',
// One pass for the `blob:` prefix and the query/fragment: both stop at the
// first `?` or `#`, so stripping either first leaves the same url.
'scriptUrl = scriptUrl.replace(/^blob:|[?#].*$/g, "").replace(/\\/[^/]+$/, "/");',
!undoPath
? `${RuntimeGlobals.publicPath} = scriptUrl;`
: `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify(
undoPath
)};`
]);
}
}
module.exports = AutoPublicPathRuntimeModule;