UNPKG

12.5 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 SINGLE_DOT_PATH_SEGMENT,
11 stringifyRequest
12} = require("./utils");
13
14const schema = require("./loader-options.json");
15
16const MiniCssExtractPlugin = require("./index");
17/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
18
19/** @typedef {import("webpack").Compiler} Compiler */
20
21/** @typedef {import("webpack").Compilation} Compilation */
22
23/** @typedef {import("webpack").Chunk} Chunk */
24
25/** @typedef {import("webpack").Module} Module */
26
27/** @typedef {import("webpack").sources.Source} Source */
28
29/** @typedef {import("webpack").AssetInfo} AssetInfo */
30
31/** @typedef {import("webpack").NormalModule} NormalModule */
32
33/** @typedef {import("./index.js").LoaderOptions} LoaderOptions */
34
35/** @typedef {any} TODO */
36
37/**
38 * @typedef {Object} Dependency
39 * @property {string} identifier
40 * @property {string | null} context
41 * @property {Buffer} content
42 * @property {string} media
43 * @property {string} [supports]
44 * @property {string} [supports]
45 * @property {Buffer} [sourceMap]
46 */
47
48/**
49 * @param {string} content
50 * @param {TODO} context
51 * @returns {string}
52 */
53
54
55function hotLoader(content, context) {
56 const accept = context.locals ? "" : "module.hot.accept(undefined, cssReload);";
57 return `${content}
58 if(module.hot) {
59 // ${Date.now()}
60 var cssReload = require(${stringifyRequest(context.context, path.join(__dirname, "hmr/hotModuleReplacement.js"))})(module.id, ${JSON.stringify({ ...context.options,
61 locals: !!context.locals
62 })});
63 module.hot.dispose(cssReload);
64 ${accept}
65 }
66 `;
67}
68/**
69 * @this {import("webpack").LoaderContext<LoaderOptions>}
70 * @param {string} request
71 */
72
73
74function pitch(request) {
75 // @ts-ignore
76 const options = this.getOptions(
77 /** @type {Schema} */
78 schema);
79 const callback = this.async();
80 const optionsFromPlugin =
81 /** @type {TODO} */
82 this[MiniCssExtractPlugin.pluginSymbol];
83
84 if (!optionsFromPlugin) {
85 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"));
86 return;
87 }
88
89 const {
90 webpack
91 } =
92 /** @type {Compiler} */
93 this._compiler;
94 /**
95 * @param {TODO} originalExports
96 * @param {Compilation} [compilation]
97 * @param {{ [name: string]: Source }} [assets]
98 * @param {Map<string, AssetInfo>} [assetsInfo]
99 * @returns {void}
100 */
101
102 const handleExports = (originalExports, compilation, assets, assetsInfo) => {
103 /** @type {{[key: string]: string } | undefined} */
104 let locals;
105 let namedExport;
106 const esModule = typeof options.esModule !== "undefined" ? options.esModule : true;
107 /**
108 * @param {Dependency[] | [null, object][]} dependencies
109 */
110
111 const addDependencies = dependencies => {
112 if (!Array.isArray(dependencies) && dependencies != null) {
113 throw new Error(`Exported value was not extracted as an array: ${JSON.stringify(dependencies)}`);
114 }
115
116 const identifierCountMap = new Map();
117 const emit = typeof options.emit !== "undefined" ? options.emit : true;
118 let lastDep;
119
120 for (const dependency of dependencies) {
121 if (!
122 /** @type {Dependency} */
123 dependency.identifier || !emit) {
124 // eslint-disable-next-line no-continue
125 continue;
126 }
127
128 const count = identifierCountMap.get(
129 /** @type {Dependency} */
130 dependency.identifier) || 0; // @ts-ignore
131
132 const CssDependency = MiniCssExtractPlugin.getCssDependency(webpack);
133 /** @type {NormalModule} */
134
135 this._module.addDependency( // @ts-ignore
136 lastDep = new CssDependency(
137 /** @type {Dependency} */
138 dependency,
139 /** @type {Dependency} */
140 dependency.context, count));
141
142 identifierCountMap.set(
143 /** @type {Dependency} */
144 dependency.identifier, count + 1);
145 }
146
147 if (lastDep && assets) {
148 lastDep.assets = assets;
149 lastDep.assetsInfo = assetsInfo;
150 }
151 };
152
153 try {
154 // eslint-disable-next-line no-underscore-dangle
155 const exports = originalExports.__esModule ? originalExports.default : originalExports;
156 namedExport = // eslint-disable-next-line no-underscore-dangle
157 originalExports.__esModule && (!originalExports.default || !("locals" in originalExports.default));
158
159 if (namedExport) {
160 Object.keys(originalExports).forEach(key => {
161 if (key !== "default") {
162 if (!locals) {
163 locals = {};
164 }
165
166 locals[key] = originalExports[key];
167 }
168 });
169 } else {
170 locals = exports && exports.locals;
171 }
172 /** @type {Dependency[] | [null, object][]} */
173
174
175 let dependencies;
176
177 if (!Array.isArray(exports)) {
178 dependencies = [[null, exports]];
179 } else {
180 dependencies = exports.map(([id, content, media, sourceMap, supports, layer]) => {
181 let identifier = id;
182 let context;
183
184 if (compilation) {
185 const module =
186 /** @type {Module} */
187 findModuleById(compilation, id);
188 identifier = module.identifier();
189 ({
190 context
191 } = module);
192 } else {
193 // TODO check if this context is used somewhere
194 context = this.rootContext;
195 }
196
197 return {
198 identifier,
199 context,
200 content: Buffer.from(content),
201 media,
202 supports,
203 layer,
204 sourceMap: sourceMap ? Buffer.from(JSON.stringify(sourceMap)) : // eslint-disable-next-line no-undefined
205 undefined
206 };
207 });
208 }
209
210 addDependencies(dependencies);
211 } catch (e) {
212 callback(
213 /** @type {Error} */
214 e);
215 return;
216 }
217
218 const result = locals ? namedExport ? Object.keys(locals).map(key => `\nexport var ${key} = ${JSON.stringify(
219 /** @type {{[key: string]: string }} */
220 locals[key])};`).join("") : `\n${esModule ? "export default" : "module.exports ="} ${JSON.stringify(locals)};` : esModule ? `\nexport {};` : "";
221 let resultSource = `// extracted by ${MiniCssExtractPlugin.pluginName}`;
222 resultSource += this.hot ? hotLoader(result, {
223 context: this.context,
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 },
268 /**
269 * @param {Error | null | undefined} error
270 * @param {object} exports
271 */
272 (error, exports) => {
273 if (error) {
274 callback(error);
275 return;
276 }
277
278 handleExports(exports);
279 });
280 return;
281 }
282
283 const loaders = this.loaders.slice(this.loaderIndex + 1);
284 this.addDependency(this.resourcePath);
285 const childFilename = "*";
286 const outputOptions = {
287 filename: childFilename,
288 publicPath
289 };
290
291 const childCompiler =
292 /** @type {Compilation} */
293 this._compilation.createChildCompiler(`${MiniCssExtractPlugin.pluginName} ${request}`, outputOptions); // The templates are compiled and executed by NodeJS - similar to server side rendering
294 // Unfortunately this causes issues as some loaders require an absolute URL to support ES Modules
295 // The following config enables relative URL support for the child compiler
296
297
298 childCompiler.options.module = { ...childCompiler.options.module
299 };
300 childCompiler.options.module.parser = { ...childCompiler.options.module.parser
301 };
302 childCompiler.options.module.parser.javascript = { ...childCompiler.options.module.parser.javascript,
303 url: "relative"
304 };
305 const {
306 NodeTemplatePlugin
307 } = webpack.node;
308 const {
309 NodeTargetPlugin
310 } = webpack.node;
311 new NodeTemplatePlugin(outputOptions).apply(childCompiler);
312 new NodeTargetPlugin().apply(childCompiler);
313 const {
314 EntryOptionPlugin
315 } = webpack;
316 const {
317 library: {
318 EnableLibraryPlugin
319 }
320 } = webpack;
321 new EnableLibraryPlugin("commonjs2").apply(childCompiler);
322 EntryOptionPlugin.applyEntryOption(childCompiler, this.context, {
323 child: {
324 library: {
325 type: "commonjs2"
326 },
327 import: [`!!${request}`]
328 }
329 });
330 const {
331 LimitChunkCountPlugin
332 } = webpack.optimize;
333 new LimitChunkCountPlugin({
334 maxChunks: 1
335 }).apply(childCompiler);
336 const {
337 NormalModule
338 } = webpack;
339 childCompiler.hooks.thisCompilation.tap(`${MiniCssExtractPlugin.pluginName} loader`,
340 /**
341 * @param {Compilation} compilation
342 */
343 compilation => {
344 const normalModuleHook = NormalModule.getCompilationHooks(compilation).loader;
345 normalModuleHook.tap(`${MiniCssExtractPlugin.pluginName} loader`, (loaderContext, module) => {
346 if (module.request === request) {
347 // eslint-disable-next-line no-param-reassign
348 module.loaders = loaders.map(loader => {
349 return {
350 type: null,
351 // @ts-ignore
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