| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Tobias Koppers @sokra
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | const ContextElementDependency = require("./dependencies/ContextElementDependency");
|
|---|
| 9 | const { join } = require("./util/fs");
|
|---|
| 10 |
|
|---|
| 11 | /** @typedef {import("./Compiler")} Compiler */
|
|---|
| 12 | /** @typedef {import("./ContextModule").ContextModuleOptions} ContextModuleOptions */
|
|---|
| 13 | /** @typedef {import("./ContextModuleFactory").BeforeContextResolveData} BeforeContextResolveData */
|
|---|
| 14 | /** @typedef {import("./ContextModuleFactory").AfterContextResolveData} AfterContextResolveData */
|
|---|
| 15 | /** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
|
|---|
| 16 |
|
|---|
| 17 | /** @typedef {Record<string, string>} NewContentCreateContextMap */
|
|---|
| 18 |
|
|---|
| 19 | const PLUGIN_NAME = "ContextReplacementPlugin";
|
|---|
| 20 |
|
|---|
| 21 | class ContextReplacementPlugin {
|
|---|
| 22 | /**
|
|---|
| 23 | * Creates an instance of ContextReplacementPlugin.
|
|---|
| 24 | * @param {RegExp} resourceRegExp A regular expression that determines which files will be selected
|
|---|
| 25 | * @param {(string | ((context: BeforeContextResolveData | AfterContextResolveData) => void) | RegExp | boolean)=} newContentResource A new resource to replace the match
|
|---|
| 26 | * @param {(boolean | NewContentCreateContextMap | RegExp)=} newContentRecursive If true, all subdirectories are searched for matches
|
|---|
| 27 | * @param {RegExp=} newContentRegExp A regular expression that determines which files will be selected
|
|---|
| 28 | */
|
|---|
| 29 | constructor(
|
|---|
| 30 | resourceRegExp,
|
|---|
| 31 | newContentResource,
|
|---|
| 32 | newContentRecursive,
|
|---|
| 33 | newContentRegExp
|
|---|
| 34 | ) {
|
|---|
| 35 | this.resourceRegExp = resourceRegExp;
|
|---|
| 36 |
|
|---|
| 37 | // new webpack.ContextReplacementPlugin(/selector/, (context) => { /* Logic */ });
|
|---|
| 38 | if (typeof newContentResource === "function") {
|
|---|
| 39 | this.newContentCallback = newContentResource;
|
|---|
| 40 | }
|
|---|
| 41 | // new ContextReplacementPlugin(/selector/, './folder', { './request': './request' });
|
|---|
| 42 | else if (
|
|---|
| 43 | typeof newContentResource === "string" &&
|
|---|
| 44 | typeof newContentRecursive === "object"
|
|---|
| 45 | ) {
|
|---|
| 46 | this.newContentResource = newContentResource;
|
|---|
| 47 | /**
|
|---|
| 48 | * Stores new content create context map.
|
|---|
| 49 | * @param {InputFileSystem} fs input file system
|
|---|
| 50 | * @param {(err: null | Error, newContentRecursive: NewContentCreateContextMap) => void} callback callback
|
|---|
| 51 | */
|
|---|
| 52 | this.newContentCreateContextMap = (fs, callback) => {
|
|---|
| 53 | callback(
|
|---|
| 54 | null,
|
|---|
| 55 | /** @type {NewContentCreateContextMap} */ (newContentRecursive)
|
|---|
| 56 | );
|
|---|
| 57 | };
|
|---|
| 58 | }
|
|---|
| 59 | // new ContextReplacementPlugin(/selector/, './folder', (context) => { /* Logic */ });
|
|---|
| 60 | else if (
|
|---|
| 61 | typeof newContentResource === "string" &&
|
|---|
| 62 | typeof newContentRecursive === "function"
|
|---|
| 63 | ) {
|
|---|
| 64 | this.newContentResource = newContentResource;
|
|---|
| 65 | this.newContentCreateContextMap = newContentRecursive;
|
|---|
| 66 | } else {
|
|---|
| 67 | // new webpack.ContextReplacementPlugin(/selector/, false, /reg-exp/);
|
|---|
| 68 | if (typeof newContentResource !== "string") {
|
|---|
| 69 | newContentRegExp = /** @type {RegExp} */ (newContentRecursive);
|
|---|
| 70 | newContentRecursive = /** @type {boolean} */ (newContentResource);
|
|---|
| 71 | newContentResource = undefined;
|
|---|
| 72 | }
|
|---|
| 73 | // new webpack.ContextReplacementPlugin(/selector/, /de|fr|hu/);
|
|---|
| 74 | if (typeof newContentRecursive !== "boolean") {
|
|---|
| 75 | newContentRegExp = /** @type {RegExp} */ (newContentRecursive);
|
|---|
| 76 | newContentRecursive = undefined;
|
|---|
| 77 | }
|
|---|
| 78 | // new webpack.ContextReplacementPlugin(/selector/, './folder', false, /selector/);
|
|---|
| 79 | this.newContentResource =
|
|---|
| 80 | /** @type {string | undefined} */
|
|---|
| 81 | (newContentResource);
|
|---|
| 82 | this.newContentRecursive =
|
|---|
| 83 | /** @type {boolean | undefined} */
|
|---|
| 84 | (newContentRecursive);
|
|---|
| 85 | this.newContentRegExp =
|
|---|
| 86 | /** @type {RegExp | undefined} */
|
|---|
| 87 | (newContentRegExp);
|
|---|
| 88 | }
|
|---|
| 89 | }
|
|---|
| 90 |
|
|---|
| 91 | /**
|
|---|
| 92 | * Applies the plugin by registering its hooks on the compiler.
|
|---|
| 93 | * @param {Compiler} compiler the compiler instance
|
|---|
| 94 | * @returns {void}
|
|---|
| 95 | */
|
|---|
| 96 | apply(compiler) {
|
|---|
| 97 | const resourceRegExp = this.resourceRegExp;
|
|---|
| 98 | const newContentCallback = this.newContentCallback;
|
|---|
| 99 | const newContentResource = this.newContentResource;
|
|---|
| 100 | const newContentRecursive = this.newContentRecursive;
|
|---|
| 101 | const newContentRegExp = this.newContentRegExp;
|
|---|
| 102 | const newContentCreateContextMap = this.newContentCreateContextMap;
|
|---|
| 103 |
|
|---|
| 104 | compiler.hooks.contextModuleFactory.tap(PLUGIN_NAME, (cmf) => {
|
|---|
| 105 | cmf.hooks.beforeResolve.tap(PLUGIN_NAME, (result) => {
|
|---|
| 106 | if (!result) return;
|
|---|
| 107 | if (resourceRegExp.test(result.request)) {
|
|---|
| 108 | if (newContentResource !== undefined) {
|
|---|
| 109 | result.request = newContentResource;
|
|---|
| 110 | }
|
|---|
| 111 | if (newContentRecursive !== undefined) {
|
|---|
| 112 | result.recursive = newContentRecursive;
|
|---|
| 113 | }
|
|---|
| 114 | if (newContentRegExp !== undefined) {
|
|---|
| 115 | result.regExp = newContentRegExp;
|
|---|
| 116 | }
|
|---|
| 117 | if (typeof newContentCallback === "function") {
|
|---|
| 118 | newContentCallback(result);
|
|---|
| 119 | } else {
|
|---|
| 120 | for (const d of result.dependencies) {
|
|---|
| 121 | if (d.critical) d.critical = false;
|
|---|
| 122 | }
|
|---|
| 123 | }
|
|---|
| 124 | }
|
|---|
| 125 | return result;
|
|---|
| 126 | });
|
|---|
| 127 | cmf.hooks.afterResolve.tap(PLUGIN_NAME, (result) => {
|
|---|
| 128 | if (!result) return;
|
|---|
| 129 | const isMatchResourceRegExp = () => {
|
|---|
| 130 | if (Array.isArray(result.resource)) {
|
|---|
| 131 | return result.resource.some((item) => resourceRegExp.test(item));
|
|---|
| 132 | }
|
|---|
| 133 |
|
|---|
| 134 | return resourceRegExp.test(result.resource);
|
|---|
| 135 | };
|
|---|
| 136 | if (isMatchResourceRegExp()) {
|
|---|
| 137 | if (newContentResource !== undefined) {
|
|---|
| 138 | if (
|
|---|
| 139 | newContentResource.startsWith("/") ||
|
|---|
| 140 | (newContentResource.length > 1 && newContentResource[1] === ":")
|
|---|
| 141 | ) {
|
|---|
| 142 | result.resource = newContentResource;
|
|---|
| 143 | } else {
|
|---|
| 144 | const rootPath =
|
|---|
| 145 | typeof result.resource === "string"
|
|---|
| 146 | ? result.resource
|
|---|
| 147 | : /** @type {string} */
|
|---|
| 148 | (result.resource.find((item) => resourceRegExp.test(item)));
|
|---|
| 149 | result.resource = join(
|
|---|
| 150 | /** @type {InputFileSystem} */
|
|---|
| 151 | (compiler.inputFileSystem),
|
|---|
| 152 | rootPath,
|
|---|
| 153 | newContentResource
|
|---|
| 154 | );
|
|---|
| 155 | }
|
|---|
| 156 | }
|
|---|
| 157 | if (newContentRecursive !== undefined) {
|
|---|
| 158 | result.recursive = newContentRecursive;
|
|---|
| 159 | }
|
|---|
| 160 | if (newContentRegExp !== undefined) {
|
|---|
| 161 | result.regExp = newContentRegExp;
|
|---|
| 162 | }
|
|---|
| 163 | if (typeof newContentCreateContextMap === "function") {
|
|---|
| 164 | result.resolveDependencies =
|
|---|
| 165 | createResolveDependenciesFromContextMap(
|
|---|
| 166 | newContentCreateContextMap
|
|---|
| 167 | );
|
|---|
| 168 | }
|
|---|
| 169 | if (typeof newContentCallback === "function") {
|
|---|
| 170 | const origResource = result.resource;
|
|---|
| 171 | newContentCallback(result);
|
|---|
| 172 | if (result.resource !== origResource) {
|
|---|
| 173 | const newResource = Array.isArray(result.resource)
|
|---|
| 174 | ? result.resource
|
|---|
| 175 | : [result.resource];
|
|---|
| 176 |
|
|---|
| 177 | for (let i = 0; i < newResource.length; i++) {
|
|---|
| 178 | if (
|
|---|
| 179 | !newResource[i].startsWith("/") &&
|
|---|
| 180 | (newResource[i].length <= 1 || newResource[i][1] !== ":")
|
|---|
| 181 | ) {
|
|---|
| 182 | // When the function changed it to an relative path
|
|---|
| 183 | newResource[i] = join(
|
|---|
| 184 | /** @type {InputFileSystem} */
|
|---|
| 185 | (compiler.inputFileSystem),
|
|---|
| 186 | origResource[i],
|
|---|
| 187 | newResource[i]
|
|---|
| 188 | );
|
|---|
| 189 | }
|
|---|
| 190 | }
|
|---|
| 191 |
|
|---|
| 192 | result.resource = newResource;
|
|---|
| 193 | }
|
|---|
| 194 | } else {
|
|---|
| 195 | for (const d of result.dependencies) {
|
|---|
| 196 | if (d.critical) d.critical = false;
|
|---|
| 197 | }
|
|---|
| 198 | }
|
|---|
| 199 | }
|
|---|
| 200 | return result;
|
|---|
| 201 | });
|
|---|
| 202 | });
|
|---|
| 203 | }
|
|---|
| 204 | }
|
|---|
| 205 |
|
|---|
| 206 | /**
|
|---|
| 207 | * Creates a resolve dependencies from context map.
|
|---|
| 208 | * @param {(fs: InputFileSystem, callback: (err: null | Error, map: NewContentCreateContextMap) => void) => void} createContextMap create context map function
|
|---|
| 209 | * @returns {(fs: InputFileSystem, options: ContextModuleOptions, callback: (err: null | Error, dependencies?: ContextElementDependency[]) => void) => void} resolve resolve dependencies from context map function
|
|---|
| 210 | */
|
|---|
| 211 | const createResolveDependenciesFromContextMap =
|
|---|
| 212 | (createContextMap) => (fs, options, callback) => {
|
|---|
| 213 | createContextMap(fs, (err, map) => {
|
|---|
| 214 | if (err) return callback(err);
|
|---|
| 215 | const dependencies = Object.keys(map).map(
|
|---|
| 216 | (key) =>
|
|---|
| 217 | new ContextElementDependency(
|
|---|
| 218 | map[key] + options.resourceQuery + options.resourceFragment,
|
|---|
| 219 | key,
|
|---|
| 220 | options.typePrefix,
|
|---|
| 221 | /** @type {string} */
|
|---|
| 222 | (options.category),
|
|---|
| 223 | options.referencedExports
|
|---|
| 224 | )
|
|---|
| 225 | );
|
|---|
| 226 | callback(null, dependencies);
|
|---|
| 227 | });
|
|---|
| 228 | };
|
|---|
| 229 |
|
|---|
| 230 | module.exports = ContextReplacementPlugin;
|
|---|