source: frontend/node_modules/webpack/lib/html/HtmlGenerator.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 12.8 KB
RevLine 
[9af201e]1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3*/
4
5"use strict";
6
7const { RawSource, ReplaceSource } = require("webpack-sources");
8const ConcatenationScope = require("../ConcatenationScope");
9const Generator = require("../Generator");
10const {
11 HTML_TYPE,
12 JAVASCRIPT_TYPE,
13 JAVASCRIPT_TYPES
14} = require("../ModuleSourceTypeConstants");
15const RuntimeGlobals = require("../RuntimeGlobals");
16const CssUrlDependency = require("../dependencies/CssUrlDependency");
17
18/** @typedef {import("webpack-sources").Source} Source */
19/** @typedef {import("../../declarations/WebpackOptions").HtmlGeneratorOptions} HtmlGeneratorOptions */
20/** @typedef {import("../Compilation").DependencyConstructor} DependencyConstructor */
21/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
22/** @typedef {import("../Dependency")} Dependency */
23/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
24/** @typedef {import("../Generator").GenerateContext} GenerateContext */
25/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
26/** @typedef {import("../Module").SourceType} SourceType */
27/** @typedef {import("../Module").SourceTypes} SourceTypes */
28/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
29/** @typedef {import("../ModuleGraph")} ModuleGraph */
30/** @typedef {import("../NormalModule")} NormalModule */
31/** @typedef {import("../util/Hash")} Hash */
32/**
33 * @template T
34 * @typedef {import("../InitFragment")<T>} InitFragment
35 */
36
37/**
38 * @type {ReadonlySet<"javascript" | "html">}
39 */
40const JAVASCRIPT_AND_HTML_TYPES = new Set([JAVASCRIPT_TYPE, HTML_TYPE]);
41
42class HtmlGenerator extends Generator {
43 /**
44 * Creates an instance of HtmlGenerator.
45 * @param {HtmlGeneratorOptions=} options generator options
46 * @param {ModuleGraph=} moduleGraph the module graph; used to detect when an HTML module is reached as a compilation entry so `extract` can default to `true` for it
47 */
48 constructor(options, moduleGraph) {
49 super();
50 this.options = options || {};
51 /** @type {ModuleGraph | undefined} */
52 this._moduleGraph = moduleGraph;
53 }
54
55 /**
56 * Returns the reason this module cannot be concatenated, when one exists.
57 * @param {NormalModule} module module for which the bailout reason should be determined
58 * @param {ConcatenationBailoutReasonContext} context context
59 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
60 */
61 getConcatenationBailoutReason(module, context) {
62 return undefined;
63 }
64
65 /**
66 * Whether this HTML module is reached as a compilation entry. Entry
67 * modules have at least one incoming connection without an
68 * `originModule` (the EntryDependency added by `compilation.addEntry`).
69 * @param {NormalModule} module module
70 * @returns {boolean} true when the module is an entry
71 */
72 _isEntryModule(module) {
73 if (!this._moduleGraph) return false;
74 for (const connection of this._moduleGraph.getIncomingConnections(module)) {
75 if (!connection.originModule) return true;
76 }
77 return false;
78 }
79
80 /**
81 * Whether to emit the extracted `.html` file for this module.
82 * `options.extract === true` always extracts; `false` never; when the
83 * option is left unspecified, extraction defaults to on for HTML modules
84 * used as compilation entries — that's the HTML-as-entry-point use case.
85 * @param {NormalModule} module module
86 * @returns {boolean} true when the `.html` file should be emitted
87 */
88 _shouldExtract(module) {
89 const { extract } = this.options;
90 if (extract === true) return true;
91 if (extract === false) return false;
92 return this._isEntryModule(module);
93 }
94
95 /**
96 * Returns the source types available for this module.
97 * @param {NormalModule} module fresh module
98 * @returns {SourceTypes} available types (do not mutate)
99 */
100 getTypes(module) {
101 if (this._shouldExtract(module)) {
102 return JAVASCRIPT_AND_HTML_TYPES;
103 }
104 return JAVASCRIPT_TYPES;
105 }
106
107 /**
108 * Returns the estimated size for the requested source type.
109 * @param {NormalModule} module the module
110 * @param {SourceType=} type source type
111 * @returns {number} estimate size of the module
112 */
113 getSize(module, type) {
114 const originalSource = module.originalSource();
115 if (!originalSource) return 0;
116 if (type === HTML_TYPE) return originalSource.size();
117 return originalSource.size() + 10;
118 }
119
120 /**
121 * Processes the provided module.
122 * @param {NormalModule} module the current module
123 * @param {Dependency} dependency the dependency to generate
124 * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
125 * @param {ReplaceSource} source the current replace source which can be modified
126 * @param {GenerateContext} generateContext the render context
127 * @returns {void}
128 */
129 sourceDependency(module, dependency, initFragments, source, generateContext) {
130 const constructor =
131 /** @type {DependencyConstructor} */
132 (dependency.constructor);
133 const template = generateContext.dependencyTemplates.get(constructor);
134 if (!template) {
135 throw new Error(
136 `No template for dependency: ${dependency.constructor.name}`
137 );
138 }
139
140 /** @type {DependencyTemplateContext} */
141 /** @type {InitFragment<GenerateContext>[] | undefined} */
142 let chunkInitFragments;
143 /** @type {DependencyTemplateContext} */
144 const templateContext = {
145 runtimeTemplate: generateContext.runtimeTemplate,
146 dependencyTemplates: generateContext.dependencyTemplates,
147 moduleGraph: generateContext.moduleGraph,
148 chunkGraph: generateContext.chunkGraph,
149 module,
150 runtime: generateContext.runtime,
151 runtimeRequirements: generateContext.runtimeRequirements,
152 concatenationScope: generateContext.concatenationScope,
153 codeGenerationResults:
154 /** @type {CodeGenerationResults} */
155 (generateContext.codeGenerationResults),
156 initFragments,
157 get chunkInitFragments() {
158 if (!chunkInitFragments) {
159 const data =
160 /** @type {NonNullable<GenerateContext["getData"]>} */
161 (generateContext.getData)();
162 chunkInitFragments = data.get("chunkInitFragments");
163 if (!chunkInitFragments) {
164 chunkInitFragments = [];
165 data.set("chunkInitFragments", chunkInitFragments);
166 }
167 }
168
169 return chunkInitFragments;
170 }
171 };
172
173 template.apply(dependency, source, templateContext);
174 }
175
176 /**
177 * Processes the provided dependencies block.
178 * @param {NormalModule} module the module to generate
179 * @param {import("../DependenciesBlock")} block the dependencies block which will be processed
180 * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
181 * @param {ReplaceSource} source the current replace source which can be modified
182 * @param {GenerateContext} generateContext the generateContext
183 * @returns {void}
184 */
185 sourceBlock(module, block, initFragments, source, generateContext) {
186 for (const dependency of block.dependencies) {
187 this.sourceDependency(
188 module,
189 dependency,
190 initFragments,
191 source,
192 generateContext
193 );
194 }
195
196 for (const childBlock of block.blocks) {
197 this.sourceBlock(
198 module,
199 childBlock,
200 initFragments,
201 source,
202 generateContext
203 );
204 }
205 }
206
207 /**
208 * Processes the provided module.
209 * @param {NormalModule} module the module to generate
210 * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
211 * @param {ReplaceSource} source the current replace source which can be modified
212 * @param {GenerateContext} generateContext the generateContext
213 * @returns {void}
214 */
215 sourceModule(module, initFragments, source, generateContext) {
216 for (const dependency of module.dependencies) {
217 this.sourceDependency(
218 module,
219 dependency,
220 initFragments,
221 source,
222 generateContext
223 );
224 }
225
226 if (module.presentationalDependencies !== undefined) {
227 for (const dependency of module.presentationalDependencies) {
228 this.sourceDependency(
229 module,
230 dependency,
231 initFragments,
232 source,
233 generateContext
234 );
235 }
236 }
237
238 for (const childBlock of module.blocks) {
239 this.sourceBlock(
240 module,
241 childBlock,
242 initFragments,
243 source,
244 generateContext
245 );
246 }
247 }
248
249 /**
250 * Run all HTML dependency templates against the original module source and
251 * return the rewritten HTML. When `undoPath` is a string, `[webpack/auto]`
252 * placeholders left in by asset/url dependencies are resolved to that
253 * undo path (use `""` to make URLs root-relative). When `undoPath` is
254 * `undefined`, the placeholders are preserved so the caller (typically
255 * `HtmlModulesPlugin#renderManifest`, which only knows the final
256 * `.html` filename after code generation) can resolve them itself.
257 * @param {NormalModule} module the module to render
258 * @param {GenerateContext} generateContext the generate context
259 * @param {string=} undoPath value to substitute for `[webpack/auto]` placeholders
260 * @returns {string} the rewritten HTML
261 */
262 _renderHtml(module, generateContext, undoPath) {
263 const originalSource = /** @type {Source} */ (module.originalSource());
264 const source = new ReplaceSource(originalSource);
265 /** @type {InitFragment<GenerateContext>[]} */
266 const initFragments = [];
267
268 this.sourceModule(module, initFragments, source, generateContext);
269
270 if (undoPath === undefined) {
271 return /** @type {string} */ (source.source());
272 }
273
274 const moduleSourceContent = source.source();
275 const generatedSource = new ReplaceSource(source);
276
277 const autoPlaceholder = CssUrlDependency.PUBLIC_PATH_AUTO;
278 const autoPlaceholderLen = autoPlaceholder.length;
279 for (
280 let idx = moduleSourceContent.indexOf(autoPlaceholder);
281 idx !== -1;
282 idx = moduleSourceContent.indexOf(
283 autoPlaceholder,
284 idx + autoPlaceholderLen
285 )
286 ) {
287 generatedSource.replace(idx, idx + autoPlaceholderLen - 1, undoPath);
288 }
289
290 // TODO handle `[fullhash]`
291
292 return /** @type {string} */ (generatedSource.source());
293 }
294
295 /**
296 * Generates generated code for this runtime module.
297 * @param {NormalModule} module module for which the code should be generated
298 * @param {GenerateContext} generateContext context for generate
299 * @returns {Source | null} generated code
300 */
301 generate(module, generateContext) {
302 const originalSource = module.originalSource();
303
304 if (!originalSource) {
305 return new RawSource("");
306 }
307
308 if (generateContext.type === HTML_TYPE) {
309 // Preserve `[webpack/auto]` placeholders here — the plugin's
310 // `renderManifest` hook knows the final `.html` filename and
311 // resolves them to an undo path relative to that location.
312 return new RawSource(
313 this._renderHtml(module, generateContext, undefined)
314 );
315 }
316
317 // JS export: the rewritten HTML is a string the consumer reads at
318 // runtime, so resolve placeholders to root-relative URLs.
319 const generated = this._renderHtml(module, generateContext, "");
320
321 /** @type {string} */
322 let sourceContent;
323 if (generateContext.concatenationScope) {
324 generateContext.concatenationScope.registerNamespaceExport(
325 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
326 );
327 sourceContent = `${generateContext.runtimeTemplate.renderConst()} ${
328 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
329 } = ${JSON.stringify(generated)};`;
330 } else {
331 generateContext.runtimeRequirements.add(RuntimeGlobals.module);
332 sourceContent = `${module.moduleArgument}.exports = ${JSON.stringify(
333 generated
334 )};`;
335 }
336
337 return new RawSource(sourceContent);
338 }
339
340 /**
341 * Generates fallback output for the provided error condition.
342 * @param {Error} error the error
343 * @param {NormalModule} module module for which the code should be generated
344 * @param {GenerateContext} generateContext context for generate
345 * @returns {Source | null} generated code
346 */
347 generateError(error, module, generateContext) {
348 if (generateContext.type === HTML_TYPE) {
349 // The error message can contain arbitrary text (file paths, user
350 // input, dep request strings). Strip `<`, `>`, and `--` runs so a
351 // crafted message can't close the comment with `-->` (or open a
352 // fake nested comment) and inject HTML into the extracted page.
353 const safe = String(error.message)
354 .replace(/[<>]/g, "")
355 .replace(/-{2,}/g, (m) => `${"-".repeat(m.length - 1)} `);
356 return new RawSource(`<!-- webpack error: ${safe} -->`);
357 }
358 return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
359 }
360
361 /**
362 * Updates the hash with the data contributed by this instance.
363 * @param {Hash} hash hash that will be modified
364 * @param {UpdateHashContext} updateHashContext context for updating hash
365 */
366 updateHash(hash, updateHashContext) {
367 hash.update("html");
368 // Hash the *effective* extraction state, not just the raw option,
369 // so the module hash flips when a module becomes (or stops being)
370 // a compilation entry under the `extract: undefined` default — the
371 // generator's source-type set changes with it, so any cached
372 // HTML-type codegen result must be invalidated.
373 if (this._shouldExtract(updateHashContext.module)) {
374 hash.update("extract");
375 }
376 }
377}
378
379module.exports = HtmlGenerator;
Note: See TracBrowser for help on using the repository browser.