| | |
| | | |
| | | "use strict"; |
| | | |
| | | const { NormalModule } = require(".."); |
| | | const { getContext } = require("loader-runner"); |
| | | |
| | | const ModuleNotFoundError = require("../ModuleNotFoundError"); |
| | | const NormalModule = require("../NormalModule"); |
| | | const { isAbsolute, join } = require("../util/fs"); |
| | | const { parseResourceWithoutFragment } = require("../util/identifier"); |
| | | |
| | | /** @typedef {import("../Compiler")} Compiler */ |
| | | /** @typedef {import("../NormalModule")} NormalModule */ |
| | | |
| | | /** |
| | | * @template T |
| | | * @typedef {import("../../declarations/LoaderContext").LoaderContext<T>} LoaderContext |
| | | */ |
| | | const DEFAULT_SCHEME = "virtual"; |
| | | |
| | | const PLUGIN_NAME = "VirtualUrlPlugin"; |
| | | const DEFAULT_SCHEME = "virtual"; |
| | | |
| | | /** |
| | | * Defines the compiler type used by this module. |
| | | * @typedef {import("../Compiler")} Compiler |
| | | * @typedef {import("../../declarations/plugins/schemes/VirtualUrlPlugin").VirtualModule} VirtualModuleConfig |
| | | * @typedef {import("../../declarations/plugins/schemes/VirtualUrlPlugin").VirtualModuleContent} VirtualModuleInput |
| | | * @typedef {import("../../declarations/plugins/schemes/VirtualUrlPlugin").VirtualUrlOptions} VirtualUrlOptions |
| | | */ |
| | | |
| | | /** @typedef {(loaderContext: LoaderContext<EXPECTED_ANY>) => Promise<string | Buffer> | string | Buffer} SourceFn */ |
| | | /** @typedef {() => string} VersionFn */ |
| | | |
| | | /** |
| | | * @typedef {object} VirtualModuleConfig |
| | | * @property {string=} type the module type |
| | | * @property {SourceFn} source the source function |
| | | * @property {VersionFn | true | string=} version optional version function or value |
| | | */ |
| | | |
| | | /** |
| | | * @typedef {string | SourceFn | VirtualModuleConfig} VirtualModuleInput |
| | | */ |
| | | |
| | | /** @typedef {{ [key: string]: VirtualModuleInput }} VirtualModules */ |
| | | |
| | | /** |
| | | * Defines the loader context type used by this module. |
| | | * @template T |
| | | * @typedef {import("../../declarations/LoaderContext").LoaderContext<T>} LoaderContext |
| | | */ |
| | | |
| | | /** |
| | | * Normalizes a virtual module definition into a standard format |
| | |
| | | return virtualConfig; |
| | | } |
| | | |
| | | /** @typedef {{ [key: string]: VirtualModuleConfig }} NormalizedModules */ |
| | | |
| | | /** |
| | | * Normalizes all virtual modules with the given scheme |
| | | * @param {VirtualModules} virtualConfigs The virtual modules to normalize |
| | | * @param {string} scheme The URL scheme to use |
| | | * @returns {{ [key: string]: VirtualModuleConfig }} The normalized virtual modules |
| | | * @returns {NormalizedModules} The normalized virtual modules |
| | | */ |
| | | function normalizeModules(virtualConfigs, scheme) { |
| | | return Object.keys(virtualConfigs).reduce((pre, id) => { |
| | | pre[toVid(id, scheme)] = normalizeModule(virtualConfigs[id]); |
| | | return pre; |
| | | }, /** @type {{ [key: string]: VirtualModuleConfig }} */ ({})); |
| | | }, /** @type {NormalizedModules} */ ({})); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | function toVid(id, scheme) { |
| | | return `${scheme}:${id}`; |
| | | } |
| | | |
| | | /** |
| | | * Converts a virtual module id to a module id |
| | | * @param {string} vid The virtual module id |
| | | * @param {string} scheme The URL scheme |
| | | * @returns {string} The module id |
| | | */ |
| | | function fromVid(vid, scheme) { |
| | | return vid.replace(`${scheme}:`, ""); |
| | | } |
| | | |
| | | const VALUE_DEP_VERSION = `webpack/${PLUGIN_NAME}/version`; |
| | |
| | | return `${VALUE_DEP_VERSION}/${toVid(id, scheme)}`; |
| | | } |
| | | |
| | | /** |
| | | * @typedef {object} VirtualUrlPluginOptions |
| | | * @property {VirtualModules} modules - The virtual modules |
| | | * @property {string=} scheme - The URL scheme to use |
| | | */ |
| | | |
| | | class VirtualUrlPlugin { |
| | | /** |
| | | * Creates an instance of VirtualUrlPlugin. |
| | | * @param {VirtualModules} modules The virtual modules |
| | | * @param {string=} scheme The URL scheme to use |
| | | * @param {Omit<VirtualUrlOptions, "modules"> | string=} schemeOrOptions The URL scheme to use |
| | | */ |
| | | constructor(modules, scheme) { |
| | | this.scheme = scheme || DEFAULT_SCHEME; |
| | | this.modules = normalizeModules(modules, this.scheme); |
| | | constructor(modules, schemeOrOptions) { |
| | | /** @type {VirtualUrlOptions} */ |
| | | this.options = { |
| | | modules, |
| | | ...(typeof schemeOrOptions === "string" |
| | | ? { scheme: schemeOrOptions } |
| | | : schemeOrOptions || {}) |
| | | }; |
| | | |
| | | /** @type {string} */ |
| | | this.scheme = this.options.scheme || DEFAULT_SCHEME; |
| | | /** @type {VirtualUrlOptions["context"]} */ |
| | | this.context = this.options.context || "auto"; |
| | | /** @type {NormalizedModules} */ |
| | | this.modules = normalizeModules(this.options.modules, this.scheme); |
| | | } |
| | | |
| | | /** |
| | | * Apply the plugin |
| | | * Applies the plugin by registering its hooks on the compiler. |
| | | * @param {Compiler} compiler the compiler instance |
| | | * @returns {void} |
| | | */ |
| | | apply(compiler) { |
| | | compiler.hooks.validate.tap(PLUGIN_NAME, () => { |
| | | compiler.validate( |
| | | () => require("../../schemas/plugins/schemes/VirtualUrlPlugin.json"), |
| | | this.options, |
| | | { |
| | | name: "Virtual Url Plugin", |
| | | baseDataPath: "options" |
| | | }, |
| | | (options) => |
| | | require("../../schemas/plugins/schemes/VirtualUrlPlugin.check")( |
| | | options |
| | | ) |
| | | ); |
| | | }); |
| | | |
| | | const scheme = this.scheme; |
| | | const cachedParseResourceWithoutFragment = |
| | | parseResourceWithoutFragment.bindCache(compiler.root); |
| | |
| | | compiler.hooks.compilation.tap( |
| | | PLUGIN_NAME, |
| | | (compilation, { normalModuleFactory }) => { |
| | | compilation.hooks.assetPath.tap( |
| | | { name: PLUGIN_NAME, before: "TemplatedPathPlugin" }, |
| | | (path, data) => { |
| | | if (data.filename && this.modules[data.filename]) { |
| | | /** |
| | | * Returns safe path. |
| | | * @param {string} str path |
| | | * @returns {string} safe path |
| | | */ |
| | | const toSafePath = (str) => |
| | | `__${str |
| | | .replace(/:/g, "__") |
| | | .replace(/^[^a-z0-9]+|[^a-z0-9]+$/gi, "") |
| | | .replace(/[^a-z0-9._-]+/gi, "_")}`; |
| | | |
| | | // filename: virtual:logo.svg -> __virtual__logo.svg |
| | | data.filename = toSafePath(data.filename); |
| | | } |
| | | return path; |
| | | } |
| | | ); |
| | | |
| | | normalModuleFactory.hooks.resolveForScheme |
| | | .for(scheme) |
| | | .tap(PLUGIN_NAME, (resourceData) => { |
| | |
| | | resourceData.resource |
| | | ); |
| | | const path = url.path; |
| | | const type = virtualConfig.type; |
| | | const type = virtualConfig.type || ""; |
| | | const context = virtualConfig.context || this.context; |
| | | |
| | | resourceData.path = path + type; |
| | | resourceData.resource = path; |
| | | |
| | | if (context === "auto") { |
| | | const context = getContext(path); |
| | | if (context === path) { |
| | | resourceData.context = compiler.context; |
| | | } else { |
| | | const resolvedContext = fromVid(context, scheme); |
| | | resourceData.context = isAbsolute(resolvedContext) |
| | | ? resolvedContext |
| | | : join( |
| | | /** @type {import("..").InputFileSystem} */ |
| | | (compiler.inputFileSystem), |
| | | compiler.context, |
| | | resolvedContext |
| | | ); |
| | | } |
| | | } else if (context && typeof context === "string") { |
| | | resourceData.context = context; |
| | | } else { |
| | | resourceData.context = compiler.context; |
| | | } |
| | | |
| | | if (virtualConfig.version) { |
| | | const cacheKey = toCacheKey(resourceData.resource, scheme); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Finds virtual module config by id. |
| | | * @param {string} id The module id |
| | | * @returns {VirtualModuleConfig} The virtual module config |
| | | */ |