| 1 | "use strict";
|
|---|
| 2 |
|
|---|
| 3 | const path = require("path");
|
|---|
| 4 | const schema = require("./loader-options.json");
|
|---|
| 5 | const {
|
|---|
| 6 | ABSOLUTE_PUBLIC_PATH,
|
|---|
| 7 | AUTO_PUBLIC_PATH,
|
|---|
| 8 | BASE_URI,
|
|---|
| 9 | SINGLE_DOT_PATH_SEGMENT,
|
|---|
| 10 | evalModuleCode,
|
|---|
| 11 | findModuleById,
|
|---|
| 12 | stringifyLocal,
|
|---|
| 13 | stringifyRequest
|
|---|
| 14 | } = require("./utils");
|
|---|
| 15 | const MiniCssExtractPlugin = require("./index");
|
|---|
| 16 |
|
|---|
| 17 | /** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
|
|---|
| 18 | /** @typedef {import("webpack").Compiler} Compiler */
|
|---|
| 19 | /** @typedef {import("webpack").Compilation} Compilation */
|
|---|
| 20 | /** @typedef {import("webpack").Chunk} Chunk */
|
|---|
| 21 | /** @typedef {import("webpack").Module} Module */
|
|---|
| 22 | /** @typedef {import("webpack").sources.Source} Source */
|
|---|
| 23 | /** @typedef {import("webpack").AssetInfo} AssetInfo */
|
|---|
| 24 | /** @typedef {import("webpack").NormalModule} NormalModule */
|
|---|
| 25 | /** @typedef {import("./index.js").LoaderOptions} LoaderOptions */
|
|---|
| 26 |
|
|---|
| 27 | // eslint-disable-next-line jsdoc/reject-function-type
|
|---|
| 28 | /** @typedef {{ [key: string]: string | Function }} Locals */
|
|---|
| 29 |
|
|---|
| 30 | // eslint-disable-next-line jsdoc/reject-any-type
|
|---|
| 31 | /** @typedef {any} EXPECTED_ANY */
|
|---|
| 32 |
|
|---|
| 33 | /**
|
|---|
| 34 | * @typedef {object} Dependency
|
|---|
| 35 | * @property {string} identifier identifier
|
|---|
| 36 | * @property {string | null} context context
|
|---|
| 37 | * @property {Buffer} content content
|
|---|
| 38 | * @property {string=} media media
|
|---|
| 39 | * @property {string=} supports supports
|
|---|
| 40 | * @property {string=} layer layer
|
|---|
| 41 | * @property {Buffer=} sourceMap source map
|
|---|
| 42 | */
|
|---|
| 43 |
|
|---|
| 44 | /**
|
|---|
| 45 | * @param {string} code code
|
|---|
| 46 | * @param {{ loaderContext: import("webpack").LoaderContext<LoaderOptions>, options: LoaderOptions, locals: Locals | undefined }} context context
|
|---|
| 47 | * @returns {string} code and HMR code
|
|---|
| 48 | */
|
|---|
| 49 | function hotLoader(code, context) {
|
|---|
| 50 | const localsJsonString = JSON.stringify(JSON.stringify(context.locals));
|
|---|
| 51 | return `${code}
|
|---|
| 52 | if(module.hot) {
|
|---|
| 53 | (function() {
|
|---|
| 54 | var localsJsonString = ${localsJsonString};
|
|---|
| 55 | // ${Date.now()}
|
|---|
| 56 | var cssReload = require(${stringifyRequest(context.loaderContext, path.join(__dirname, "hmr/hotModuleReplacement.js"))})(module.id, ${JSON.stringify(context.options)});
|
|---|
| 57 | // only invalidate when locals change
|
|---|
| 58 | if (
|
|---|
| 59 | module.hot.data &&
|
|---|
| 60 | module.hot.data.value &&
|
|---|
| 61 | module.hot.data.value !== localsJsonString
|
|---|
| 62 | ) {
|
|---|
| 63 | module.hot.invalidate();
|
|---|
| 64 | } else {
|
|---|
| 65 | module.hot.accept();
|
|---|
| 66 | }
|
|---|
| 67 | module.hot.dispose(function(data) {
|
|---|
| 68 | data.value = localsJsonString;
|
|---|
| 69 | cssReload();
|
|---|
| 70 | });
|
|---|
| 71 | })();
|
|---|
| 72 | }
|
|---|
| 73 | `;
|
|---|
| 74 | }
|
|---|
| 75 |
|
|---|
| 76 | /**
|
|---|
| 77 | * @this {import("webpack").LoaderContext<LoaderOptions>}
|
|---|
| 78 | * @param {string} request request
|
|---|
| 79 | */
|
|---|
| 80 | function pitch(request) {
|
|---|
| 81 | if (this._compiler && this._compiler.options && this._compiler.options.experiments && this._compiler.options.experiments.css && this._module && (this._module.type === "css" || this._module.type === "css/auto" || this._module.type === "css/global" || this._module.type === "css/module")) {
|
|---|
| 82 | this.emitWarning(new Error('You can\'t use `experiments.css` (`experiments.futureDefaults` enable built-in CSS support by default) and `mini-css-extract-plugin` together, please set `experiments.css` to `false` or set `{ type: "javascript/auto" }` for rules with `mini-css-extract-plugin` in your webpack config (now `mini-css-extract-plugin` does nothing).'));
|
|---|
| 83 | return;
|
|---|
| 84 | }
|
|---|
| 85 | const options = this.getOptions(/** @type {Schema} */schema);
|
|---|
| 86 | const emit = typeof options.emit !== "undefined" ? options.emit : true;
|
|---|
| 87 | const callback = this.async();
|
|---|
| 88 | const optionsFromPlugin =
|
|---|
| 89 | // @ts-expect-error
|
|---|
| 90 | this[MiniCssExtractPlugin.pluginSymbol];
|
|---|
| 91 | if (!optionsFromPlugin) {
|
|---|
| 92 | callback(new Error("You forgot to add 'mini-css-extract-plugin' plugin (i.e. `{ plugins: [new MiniCssExtractPlugin()] }`), please read https://github.com/webpack/mini-css-extract-plugin#getting-started"));
|
|---|
| 93 | return;
|
|---|
| 94 | }
|
|---|
| 95 | const {
|
|---|
| 96 | webpack
|
|---|
| 97 | } = /** @type {Compiler} */this._compiler;
|
|---|
| 98 |
|
|---|
| 99 | /**
|
|---|
| 100 | * @param {EXPECTED_ANY} originalExports original exports
|
|---|
| 101 | * @param {Compilation=} compilation compilation
|
|---|
| 102 | * @param {{ [name: string]: Source }=} assets assets
|
|---|
| 103 | * @param {Map<string, AssetInfo>=} assetsInfo assets info
|
|---|
| 104 | * @returns {void}
|
|---|
| 105 | */
|
|---|
| 106 | const handleExports = (originalExports, compilation, assets, assetsInfo) => {
|
|---|
| 107 | /** @type {Locals | undefined} */
|
|---|
| 108 | let locals;
|
|---|
| 109 | let namedExport;
|
|---|
| 110 | const esModule = typeof options.esModule !== "undefined" ? options.esModule : true;
|
|---|
| 111 |
|
|---|
| 112 | /**
|
|---|
| 113 | * @param {Dependency[] | [null, object][]} dependencies dependencies
|
|---|
| 114 | */
|
|---|
| 115 | const addDependencies = dependencies => {
|
|---|
| 116 | // eslint-disable-next-line no-eq-null, eqeqeq
|
|---|
| 117 | if (!Array.isArray(dependencies) && dependencies != null) {
|
|---|
| 118 | throw new Error(`Exported value was not extracted as an array: ${JSON.stringify(dependencies)}`);
|
|---|
| 119 | }
|
|---|
| 120 | const identifierCountMap = new Map();
|
|---|
| 121 | let lastDep;
|
|---|
| 122 | for (const dependency of dependencies) {
|
|---|
| 123 | if (!(/** @type {Dependency} */dependency.identifier) || !emit) {
|
|---|
| 124 | continue;
|
|---|
| 125 | }
|
|---|
| 126 | const count = identifierCountMap.get(/** @type {Dependency} */dependency.identifier) || 0;
|
|---|
| 127 | const CssDependency = MiniCssExtractPlugin.getCssDependency(webpack);
|
|---|
| 128 |
|
|---|
| 129 | /** @type {NormalModule} */
|
|---|
| 130 | this._module.addDependency(lastDep = new CssDependency(/** @type {Dependency} */
|
|---|
| 131 | dependency, /** @type {Dependency} */
|
|---|
| 132 | dependency.context, count));
|
|---|
| 133 | identifierCountMap.set(/** @type {Dependency} */
|
|---|
| 134 | dependency.identifier, count + 1);
|
|---|
| 135 | }
|
|---|
| 136 | if (lastDep && assets) {
|
|---|
| 137 | lastDep.assets = assets;
|
|---|
| 138 | lastDep.assetsInfo = assetsInfo;
|
|---|
| 139 | }
|
|---|
| 140 | };
|
|---|
| 141 | try {
|
|---|
| 142 | const exports = originalExports.__esModule ? originalExports.default : originalExports;
|
|---|
| 143 | namedExport = originalExports.__esModule && (!originalExports.default || !("locals" in originalExports.default));
|
|---|
| 144 | if (namedExport) {
|
|---|
| 145 | for (const key of Object.keys(originalExports)) {
|
|---|
| 146 | if (key !== "default") {
|
|---|
| 147 | if (!locals) {
|
|---|
| 148 | locals = {};
|
|---|
| 149 | }
|
|---|
| 150 |
|
|---|
| 151 | /** @type {Locals} */
|
|---|
| 152 | locals[key] = originalExports[key];
|
|---|
| 153 | }
|
|---|
| 154 | }
|
|---|
| 155 | } else {
|
|---|
| 156 | locals = exports && exports.locals;
|
|---|
| 157 | }
|
|---|
| 158 |
|
|---|
| 159 | /** @type {Dependency[] | [null, Record<string, string>][]} */
|
|---|
| 160 | let dependencies;
|
|---|
| 161 | if (!Array.isArray(exports)) {
|
|---|
| 162 | dependencies = [[null, exports]];
|
|---|
| 163 | } else {
|
|---|
| 164 | dependencies = exports.map(([id, content, media, sourceMap, supports, layer]) => {
|
|---|
| 165 | let identifier = id;
|
|---|
| 166 | let context;
|
|---|
| 167 | if (compilation) {
|
|---|
| 168 | const module = /** @type {Module} */
|
|---|
| 169 | findModuleById(compilation, id);
|
|---|
| 170 | identifier = module.identifier();
|
|---|
| 171 | ({
|
|---|
| 172 | context
|
|---|
| 173 | } = module);
|
|---|
| 174 | } else {
|
|---|
| 175 | // TODO check if this context is used somewhere
|
|---|
| 176 | context = this.rootContext;
|
|---|
| 177 | }
|
|---|
| 178 | return {
|
|---|
| 179 | identifier,
|
|---|
| 180 | context,
|
|---|
| 181 | content: Buffer.from(content),
|
|---|
| 182 | media,
|
|---|
| 183 | supports,
|
|---|
| 184 | layer,
|
|---|
| 185 | sourceMap: sourceMap ? Buffer.from(JSON.stringify(sourceMap)) : undefined
|
|---|
| 186 | };
|
|---|
| 187 | });
|
|---|
| 188 | }
|
|---|
| 189 | addDependencies(dependencies);
|
|---|
| 190 | } catch (err) {
|
|---|
| 191 | callback(/** @type {Error} */err);
|
|---|
| 192 | return;
|
|---|
| 193 | }
|
|---|
| 194 | const result = function makeResult() {
|
|---|
| 195 | const defaultExport = typeof options.defaultExport !== "undefined" ? options.defaultExport : false;
|
|---|
| 196 | if (locals) {
|
|---|
| 197 | if (namedExport) {
|
|---|
| 198 | const identifiers = [...function* generateIdentifiers() {
|
|---|
| 199 | let identifierId = 0;
|
|---|
| 200 | for (const key of Object.keys(locals)) {
|
|---|
| 201 | identifierId += 1;
|
|---|
| 202 | yield [`_${identifierId.toString(16)}`, key];
|
|---|
| 203 | }
|
|---|
| 204 | }()];
|
|---|
| 205 | const localsString = identifiers.map(([id, key]) => `\nvar ${id} = ${stringifyLocal(/** @type {Locals} */locals[key])};`).join("");
|
|---|
| 206 | const exportsString = `export { ${identifiers.map(([id, key]) => `${id} as ${JSON.stringify(key)}`).join(", ")} }`;
|
|---|
| 207 | return defaultExport ? `${localsString}\n${exportsString}\nexport default { ${identifiers.map(([id, key]) => `${JSON.stringify(key)}: ${id}`).join(", ")} }\n` : `${localsString}\n${exportsString}\n`;
|
|---|
| 208 | }
|
|---|
| 209 | return `\n${esModule ? "export default" : "module.exports = "} ${JSON.stringify(locals)};`;
|
|---|
| 210 | } else if (esModule) {
|
|---|
| 211 | return defaultExport ? "\nexport {};export default {};" : "\nexport {};";
|
|---|
| 212 | }
|
|---|
| 213 | return "";
|
|---|
| 214 | }();
|
|---|
| 215 | let resultSource = `// extracted by ${MiniCssExtractPlugin.pluginName}`;
|
|---|
| 216 |
|
|---|
| 217 | // only attempt hotreloading if the css is actually used for something other than hash values
|
|---|
| 218 | resultSource += this.hot && emit ? hotLoader(result, {
|
|---|
| 219 | loaderContext: this,
|
|---|
| 220 | options,
|
|---|
| 221 | locals
|
|---|
| 222 | }) : result;
|
|---|
| 223 | callback(null, resultSource);
|
|---|
| 224 | };
|
|---|
| 225 | let {
|
|---|
| 226 | publicPath
|
|---|
| 227 | } = /** @type {Compilation} */
|
|---|
| 228 | this._compilation.outputOptions;
|
|---|
| 229 | if (typeof options.publicPath === "string") {
|
|---|
| 230 | publicPath = options.publicPath;
|
|---|
| 231 | } else if (typeof options.publicPath === "function") {
|
|---|
| 232 | publicPath = options.publicPath(this.resourcePath, this.rootContext);
|
|---|
| 233 | }
|
|---|
| 234 | if (publicPath === "auto") {
|
|---|
| 235 | publicPath = AUTO_PUBLIC_PATH;
|
|---|
| 236 | }
|
|---|
| 237 | if (typeof optionsFromPlugin.experimentalUseImportModule === "undefined" && typeof this.importModule === "function" || optionsFromPlugin.experimentalUseImportModule) {
|
|---|
| 238 | if (!this.importModule) {
|
|---|
| 239 | callback(new Error("You are using 'experimentalUseImportModule' but 'this.importModule' is not available in loader context. You need to have at least webpack 5.33.2."));
|
|---|
| 240 | return;
|
|---|
| 241 | }
|
|---|
| 242 | let publicPathForExtract;
|
|---|
| 243 | if (typeof publicPath === "string") {
|
|---|
| 244 | const isAbsolutePublicPath = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/.test(publicPath);
|
|---|
| 245 | publicPathForExtract = isAbsolutePublicPath ? publicPath : `${ABSOLUTE_PUBLIC_PATH}${publicPath.replace(/\./g, SINGLE_DOT_PATH_SEGMENT)}`;
|
|---|
| 246 | } else {
|
|---|
| 247 | publicPathForExtract = publicPath;
|
|---|
| 248 | }
|
|---|
| 249 | this.importModule(`${this._module && this._module.matchResource ? this._module.matchResource : this.resourcePath}.webpack[javascript/auto]!=!!!${request}`, {
|
|---|
| 250 | layer: options.layer,
|
|---|
| 251 | publicPath: (/** @type {string} */publicPathForExtract),
|
|---|
| 252 | baseUri: `${BASE_URI}/`
|
|---|
| 253 | },
|
|---|
| 254 | /**
|
|---|
| 255 | * @param {Error | null | undefined} error error
|
|---|
| 256 | * @param {object} exports exports
|
|---|
| 257 | */
|
|---|
| 258 | (error, exports) => {
|
|---|
| 259 | if (error) {
|
|---|
| 260 | callback(error);
|
|---|
| 261 | return;
|
|---|
| 262 | }
|
|---|
| 263 | handleExports(exports);
|
|---|
| 264 | });
|
|---|
| 265 | return;
|
|---|
| 266 | }
|
|---|
| 267 | const loaders = this.loaders.slice(this.loaderIndex + 1);
|
|---|
| 268 | this.addDependency(this.resourcePath);
|
|---|
| 269 | const childFilename = "*";
|
|---|
| 270 | const outputOptions = {
|
|---|
| 271 | filename: childFilename,
|
|---|
| 272 | publicPath
|
|---|
| 273 | };
|
|---|
| 274 | const childCompiler = /** @type {Compilation} */
|
|---|
| 275 | this._compilation.createChildCompiler(`${MiniCssExtractPlugin.pluginName} ${request}`, outputOptions);
|
|---|
| 276 |
|
|---|
| 277 | // The templates are compiled and executed by NodeJS - similar to server side rendering
|
|---|
| 278 | // Unfortunately this causes issues as some loaders require an absolute URL to support ES Modules
|
|---|
| 279 | // The following config enables relative URL support for the child compiler
|
|---|
| 280 | childCompiler.options.module = {
|
|---|
| 281 | ...childCompiler.options.module
|
|---|
| 282 | };
|
|---|
| 283 | childCompiler.options.module.parser = {
|
|---|
| 284 | ...childCompiler.options.module.parser
|
|---|
| 285 | };
|
|---|
| 286 | childCompiler.options.module.parser.javascript = {
|
|---|
| 287 | ...childCompiler.options.module.parser.javascript,
|
|---|
| 288 | url: "relative"
|
|---|
| 289 | };
|
|---|
| 290 | const {
|
|---|
| 291 | NodeTemplatePlugin
|
|---|
| 292 | } = webpack.node;
|
|---|
| 293 | const {
|
|---|
| 294 | NodeTargetPlugin
|
|---|
| 295 | } = webpack.node;
|
|---|
| 296 | new NodeTemplatePlugin().apply(childCompiler);
|
|---|
| 297 | new NodeTargetPlugin().apply(childCompiler);
|
|---|
| 298 | const {
|
|---|
| 299 | EntryOptionPlugin
|
|---|
| 300 | } = webpack;
|
|---|
| 301 | const {
|
|---|
| 302 | library: {
|
|---|
| 303 | EnableLibraryPlugin
|
|---|
| 304 | }
|
|---|
| 305 | } = webpack;
|
|---|
| 306 | new EnableLibraryPlugin("commonjs2").apply(childCompiler);
|
|---|
| 307 | EntryOptionPlugin.applyEntryOption(childCompiler, this.context, {
|
|---|
| 308 | child: {
|
|---|
| 309 | library: {
|
|---|
| 310 | type: "commonjs2"
|
|---|
| 311 | },
|
|---|
| 312 | import: [`!!${request}`]
|
|---|
| 313 | }
|
|---|
| 314 | });
|
|---|
| 315 | const {
|
|---|
| 316 | LimitChunkCountPlugin
|
|---|
| 317 | } = webpack.optimize;
|
|---|
| 318 | new LimitChunkCountPlugin({
|
|---|
| 319 | maxChunks: 1
|
|---|
| 320 | }).apply(childCompiler);
|
|---|
| 321 | const {
|
|---|
| 322 | NormalModule
|
|---|
| 323 | } = webpack;
|
|---|
| 324 | childCompiler.hooks.thisCompilation.tap(`${MiniCssExtractPlugin.pluginName} loader`,
|
|---|
| 325 | /**
|
|---|
| 326 | * @param {Compilation} compilation compilation
|
|---|
| 327 | */
|
|---|
| 328 | compilation => {
|
|---|
| 329 | const normalModuleHook = NormalModule.getCompilationHooks(compilation).loader;
|
|---|
| 330 | normalModuleHook.tap(`${MiniCssExtractPlugin.pluginName} loader`, (loaderContext, module) => {
|
|---|
| 331 | if (module.request === request) {
|
|---|
| 332 | module.loaders = loaders.map(loader => ({
|
|---|
| 333 | type: null,
|
|---|
| 334 | loader: loader.path,
|
|---|
| 335 | options: loader.options,
|
|---|
| 336 | ident: loader.ident
|
|---|
| 337 | }));
|
|---|
| 338 | }
|
|---|
| 339 | });
|
|---|
| 340 | });
|
|---|
| 341 |
|
|---|
| 342 | /** @type {string | Buffer} */
|
|---|
| 343 | let source;
|
|---|
| 344 | childCompiler.hooks.compilation.tap(MiniCssExtractPlugin.pluginName,
|
|---|
| 345 | /**
|
|---|
| 346 | * @param {Compilation} compilation compilation
|
|---|
| 347 | */
|
|---|
| 348 | compilation => {
|
|---|
| 349 | compilation.hooks.processAssets.tap(MiniCssExtractPlugin.pluginName, () => {
|
|---|
| 350 | source = compilation.assets[childFilename] && compilation.assets[childFilename].source();
|
|---|
| 351 |
|
|---|
| 352 | // Remove all chunk assets
|
|---|
| 353 | for (const chunk of compilation.chunks) {
|
|---|
| 354 | for (const file of chunk.files) {
|
|---|
| 355 | compilation.deleteAsset(file);
|
|---|
| 356 | }
|
|---|
| 357 | }
|
|---|
| 358 | });
|
|---|
| 359 | });
|
|---|
| 360 | childCompiler.runAsChild((error, entries, compilation_) => {
|
|---|
| 361 | if (error) {
|
|---|
| 362 | callback(error);
|
|---|
| 363 | return;
|
|---|
| 364 | }
|
|---|
| 365 | const compilation = /** @type {Compilation} */compilation_;
|
|---|
| 366 | if (compilation.errors.length > 0) {
|
|---|
| 367 | callback(compilation.errors[0]);
|
|---|
| 368 | return;
|
|---|
| 369 | }
|
|---|
| 370 |
|
|---|
| 371 | /** @type {{ [name: string]: Source }} */
|
|---|
| 372 | const assets = Object.create(null);
|
|---|
| 373 | /** @type {Map<string, AssetInfo>} */
|
|---|
| 374 | const assetsInfo = new Map();
|
|---|
| 375 | for (const asset of compilation.getAssets()) {
|
|---|
| 376 | assets[asset.name] = asset.source;
|
|---|
| 377 | assetsInfo.set(asset.name, asset.info);
|
|---|
| 378 | }
|
|---|
| 379 | for (const dep of compilation.fileDependencies) {
|
|---|
| 380 | this.addDependency(dep);
|
|---|
| 381 | }
|
|---|
| 382 | for (const dep of compilation.contextDependencies) {
|
|---|
| 383 | this.addContextDependency(dep);
|
|---|
| 384 | }
|
|---|
| 385 | if (!source) {
|
|---|
| 386 | callback(new Error("Didn't get a result from child compiler"));
|
|---|
| 387 | return;
|
|---|
| 388 | }
|
|---|
| 389 | let originalExports;
|
|---|
| 390 | try {
|
|---|
| 391 | originalExports = evalModuleCode(this, source, request);
|
|---|
| 392 | } catch (err) {
|
|---|
| 393 | callback(/** @type {Error} */err);
|
|---|
| 394 | return;
|
|---|
| 395 | }
|
|---|
| 396 | handleExports(originalExports, compilation, assets, assetsInfo);
|
|---|
| 397 | });
|
|---|
| 398 | }
|
|---|
| 399 |
|
|---|
| 400 | /**
|
|---|
| 401 | * @this {import("webpack").LoaderContext<LoaderOptions>}
|
|---|
| 402 | * @param {string} content content
|
|---|
| 403 | * @returns {string | undefined} the original content
|
|---|
| 404 | */
|
|---|
| 405 | function loader(content) {
|
|---|
| 406 | if (this._compiler && this._compiler.options && this._compiler.options.experiments && this._compiler.options.experiments.css && this._module && (this._module.type === "css" || this._module.type === "css/auto" || this._module.type === "css/global" || this._module.type === "css/module")) {
|
|---|
| 407 | return content;
|
|---|
| 408 | }
|
|---|
| 409 | }
|
|---|
| 410 | module.exports = loader;
|
|---|
| 411 | module.exports.hotLoader = hotLoader;
|
|---|
| 412 | module.exports.pitch = pitch; |
|---|