source: imaps-frontend/node_modules/html-webpack-plugin/lib/child-compiler.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 2 weeks ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 8.6 KB
RevLine 
[79a0317]1// @ts-check
2"use strict";
3
4/**
5 * @file
6 * This file uses webpack to compile a template with a child compiler.
7 *
8 * [TEMPLATE] -> [JAVASCRIPT]
9 *
10 */
11
12/** @typedef {import("webpack").Chunk} Chunk */
13/** @typedef {import("webpack").sources.Source} Source */
14/** @typedef {{hash: string, entry: Chunk, content: string, assets: {[name: string]: { source: Source, info: import("webpack").AssetInfo }}}} ChildCompilationTemplateResult */
15
16/**
17 * The HtmlWebpackChildCompiler is a helper to allow reusing one childCompiler
18 * for multiple HtmlWebpackPlugin instances to improve the compilation performance.
19 */
20class HtmlWebpackChildCompiler {
21 /**
22 *
23 * @param {string[]} templates
24 */
25 constructor(templates) {
26 /**
27 * @type {string[]} templateIds
28 * The template array will allow us to keep track which input generated which output
29 */
30 this.templates = templates;
31 /** @type {Promise<{[templatePath: string]: ChildCompilationTemplateResult}>} */
32 this.compilationPromise; // eslint-disable-line
33 /** @type {number | undefined} */
34 this.compilationStartedTimestamp; // eslint-disable-line
35 /** @type {number | undefined} */
36 this.compilationEndedTimestamp; // eslint-disable-line
37 /**
38 * All file dependencies of the child compiler
39 * @type {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}}
40 */
41 this.fileDependencies = {
42 fileDependencies: [],
43 contextDependencies: [],
44 missingDependencies: [],
45 };
46 }
47
48 /**
49 * Returns true if the childCompiler is currently compiling
50 *
51 * @returns {boolean}
52 */
53 isCompiling() {
54 return !this.didCompile() && this.compilationStartedTimestamp !== undefined;
55 }
56
57 /**
58 * Returns true if the childCompiler is done compiling
59 *
60 * @returns {boolean}
61 */
62 didCompile() {
63 return this.compilationEndedTimestamp !== undefined;
64 }
65
66 /**
67 * This function will start the template compilation
68 * once it is started no more templates can be added
69 *
70 * @param {import('webpack').Compilation} mainCompilation
71 * @returns {Promise<{[templatePath: string]: ChildCompilationTemplateResult}>}
72 */
73 compileTemplates(mainCompilation) {
74 const webpack = mainCompilation.compiler.webpack;
75 const Compilation = webpack.Compilation;
76
77 const NodeTemplatePlugin = webpack.node.NodeTemplatePlugin;
78 const NodeTargetPlugin = webpack.node.NodeTargetPlugin;
79 const LoaderTargetPlugin = webpack.LoaderTargetPlugin;
80 const EntryPlugin = webpack.EntryPlugin;
81
82 // To prevent multiple compilations for the same template
83 // the compilation is cached in a promise.
84 // If it already exists return
85 if (this.compilationPromise) {
86 return this.compilationPromise;
87 }
88
89 const outputOptions = {
90 filename: "__child-[name]",
91 publicPath: "",
92 library: {
93 type: "var",
94 name: "HTML_WEBPACK_PLUGIN_RESULT",
95 },
96 scriptType: /** @type {'text/javascript'} */ ("text/javascript"),
97 iife: true,
98 };
99 const compilerName = "HtmlWebpackCompiler";
100 // Create an additional child compiler which takes the template
101 // and turns it into an Node.JS html factory.
102 // This allows us to use loaders during the compilation
103 const childCompiler = mainCompilation.createChildCompiler(
104 compilerName,
105 outputOptions,
106 [
107 // Compile the template to nodejs javascript
108 new NodeTargetPlugin(),
109 new NodeTemplatePlugin(),
110 new LoaderTargetPlugin("node"),
111 new webpack.library.EnableLibraryPlugin("var"),
112 ],
113 );
114 // The file path context which webpack uses to resolve all relative files to
115 childCompiler.context = mainCompilation.compiler.context;
116
117 // Generate output file names
118 const temporaryTemplateNames = this.templates.map(
119 (template, index) => `__child-HtmlWebpackPlugin_${index}-${template}`,
120 );
121
122 // Add all templates
123 this.templates.forEach((template, index) => {
124 new EntryPlugin(
125 childCompiler.context,
126 "data:text/javascript,__webpack_public_path__ = __webpack_base_uri__ = htmlWebpackPluginPublicPath;",
127 `HtmlWebpackPlugin_${index}-${template}`,
128 ).apply(childCompiler);
129 new EntryPlugin(
130 childCompiler.context,
131 template,
132 `HtmlWebpackPlugin_${index}-${template}`,
133 ).apply(childCompiler);
134 });
135
136 // The templates are compiled and executed by NodeJS - similar to server side rendering
137 // Unfortunately this causes issues as some loaders require an absolute URL to support ES Modules
138 // The following config enables relative URL support for the child compiler
139 childCompiler.options.module = { ...childCompiler.options.module };
140 childCompiler.options.module.parser = {
141 ...childCompiler.options.module.parser,
142 };
143 childCompiler.options.module.parser.javascript = {
144 ...childCompiler.options.module.parser.javascript,
145 url: "relative",
146 };
147
148 this.compilationStartedTimestamp = new Date().getTime();
149 /** @type {Promise<{[templatePath: string]: ChildCompilationTemplateResult}>} */
150 this.compilationPromise = new Promise((resolve, reject) => {
151 /** @type {Source[]} */
152 const extractedAssets = [];
153
154 childCompiler.hooks.thisCompilation.tap(
155 "HtmlWebpackPlugin",
156 (compilation) => {
157 compilation.hooks.processAssets.tap(
158 {
159 name: "HtmlWebpackPlugin",
160 stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
161 },
162 (assets) => {
163 temporaryTemplateNames.forEach((temporaryTemplateName) => {
164 if (assets[temporaryTemplateName]) {
165 extractedAssets.push(assets[temporaryTemplateName]);
166
167 compilation.deleteAsset(temporaryTemplateName);
168 }
169 });
170 },
171 );
172 },
173 );
174
175 childCompiler.runAsChild((err, entries, childCompilation) => {
176 // Extract templates
177 // TODO fine a better way to store entries and results, to avoid duplicate chunks and assets
178 const compiledTemplates = entries
179 ? extractedAssets.map((asset) => asset.source())
180 : [];
181
182 // Extract file dependencies
183 if (entries && childCompilation) {
184 this.fileDependencies = {
185 fileDependencies: Array.from(childCompilation.fileDependencies),
186 contextDependencies: Array.from(
187 childCompilation.contextDependencies,
188 ),
189 missingDependencies: Array.from(
190 childCompilation.missingDependencies,
191 ),
192 };
193 }
194
195 // Reject the promise if the childCompilation contains error
196 if (
197 childCompilation &&
198 childCompilation.errors &&
199 childCompilation.errors.length
200 ) {
201 const errorDetailsArray = [];
202 for (const error of childCompilation.errors) {
203 let message = error.message;
204 if (error.stack) {
205 message += "\n" + error.stack;
206 }
207 errorDetailsArray.push(message);
208 }
209 const errorDetails = errorDetailsArray.join("\n");
210
211 reject(new Error("Child compilation failed:\n" + errorDetails));
212
213 return;
214 }
215
216 // Reject if the error object contains errors
217 if (err) {
218 reject(err);
219 return;
220 }
221
222 if (!childCompilation || !entries) {
223 reject(new Error("Empty child compilation"));
224 return;
225 }
226
227 /**
228 * @type {{[templatePath: string]: ChildCompilationTemplateResult}}
229 */
230 const result = {};
231
232 /** @type {{[name: string]: { source: Source, info: import("webpack").AssetInfo }}} */
233 const assets = {};
234
235 for (const asset of childCompilation.getAssets()) {
236 assets[asset.name] = { source: asset.source, info: asset.info };
237 }
238
239 compiledTemplates.forEach((templateSource, entryIndex) => {
240 // The compiledTemplates are generated from the entries added in
241 // the addTemplate function.
242 // Therefore, the array index of this.templates should be the as entryIndex.
243 result[this.templates[entryIndex]] = {
244 // TODO, can we have Buffer here?
245 content: /** @type {string} */ (templateSource),
246 hash: childCompilation.hash || "XXXX",
247 entry: entries[entryIndex],
248 assets,
249 };
250 });
251
252 this.compilationEndedTimestamp = new Date().getTime();
253
254 resolve(result);
255 });
256 });
257
258 return this.compilationPromise;
259 }
260}
261
262module.exports = {
263 HtmlWebpackChildCompiler,
264};
Note: See TracBrowser for help on using the repository browser.