UNPKG

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