webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
104 lines (94 loc) • 3.72 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
;
const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");
const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
const { getUndoPath } = require("../util/identifier");
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compilation")} 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);
const { scriptType, importMetaName, path, environment } =
compilation.outputOptions;
const chunk = /** @type {Chunk} */ (this.chunk);
const chunkName = compilation.getPath(
JavascriptModulesPlugin.getChunkFilenameTemplate(
chunk,
compilation.outputOptions
),
{
chunk,
contentHashType: "javascript"
}
);
const undoPath = getUndoPath(
chunkName,
/** @type {string} */ (path),
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 `<script>HTMLScriptElement = HTMLImageElement</script>`
// and use `<img name="currentScript" src="https://attacker.controlled.server/"></img>`
`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 || !/^http(s?):/.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");',
'scriptUrl = scriptUrl.replace(/^blob:/, "").replace(/#.*$/, "").replace(/\\?.*$/, "").replace(/\\/[^\\/]+$/, "/");',
!undoPath
? `${RuntimeGlobals.publicPath} = scriptUrl;`
: `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify(
undoPath
)};`
]);
}
}
module.exports = AutoPublicPathRuntimeModule;