source: frontend/node_modules/@pmmmwh/react-refresh-webpack-plugin/lib/index.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: 14.4 KB
Line 
1const { validate: validateOptions } = require('schema-utils');
2const { getRefreshGlobalScope, getWebpackVersion } = require('./globals');
3const {
4 getAdditionalEntries,
5 getIntegrationEntry,
6 getRefreshGlobal,
7 getSocketIntegration,
8 injectRefreshEntry,
9 injectRefreshLoader,
10 makeRefreshRuntimeModule,
11 normalizeOptions,
12} = require('./utils');
13const schema = require('./options.json');
14
15class ReactRefreshPlugin {
16 /**
17 * @param {import('./types').ReactRefreshPluginOptions} [options] Options for react-refresh-plugin.
18 */
19 constructor(options = {}) {
20 validateOptions(schema, options, {
21 name: 'React Refresh Plugin',
22 baseDataPath: 'options',
23 });
24
25 /**
26 * @readonly
27 * @type {import('./types').NormalizedPluginOptions}
28 */
29 this.options = normalizeOptions(options);
30 }
31
32 /**
33 * Applies the plugin.
34 * @param {import('webpack').Compiler} compiler A webpack compiler object.
35 * @returns {void}
36 */
37 apply(compiler) {
38 // Skip processing in non-development mode, but allow manual force-enabling
39 if (
40 // Webpack do not set process.env.NODE_ENV, so we need to check for mode.
41 // Ref: https://github.com/webpack/webpack/issues/7074
42 (compiler.options.mode !== 'development' ||
43 // We also check for production process.env.NODE_ENV,
44 // in case it was set and mode is non-development (e.g. 'none')
45 (process.env.NODE_ENV && process.env.NODE_ENV === 'production')) &&
46 !this.options.forceEnable
47 ) {
48 return;
49 }
50
51 const webpackVersion = getWebpackVersion(compiler);
52 const logger = compiler.getInfrastructureLogger(this.constructor.name);
53
54 // Get Webpack imports from compiler instance (if available) -
55 // this allow mono-repos to use different versions of Webpack without conflicts.
56 const webpack = compiler.webpack || require('webpack');
57 const {
58 DefinePlugin,
59 EntryDependency,
60 EntryPlugin,
61 ModuleFilenameHelpers,
62 NormalModule,
63 ProvidePlugin,
64 RuntimeGlobals,
65 Template,
66 } = webpack;
67
68 // Inject react-refresh context to all Webpack entry points.
69 // This should create `EntryDependency` objects when available,
70 // and fallback to patching the `entry` object for legacy workflows.
71 const addEntries = getAdditionalEntries({
72 devServer: compiler.options.devServer,
73 options: this.options,
74 });
75 if (EntryPlugin) {
76 // Prepended entries does not care about injection order,
77 // so we can utilise EntryPlugin for simpler logic.
78 addEntries.prependEntries.forEach((entry) => {
79 new EntryPlugin(compiler.context, entry, { name: undefined }).apply(compiler);
80 });
81
82 const integrationEntry = getIntegrationEntry(this.options.overlay.sockIntegration);
83 const socketEntryData = [];
84 compiler.hooks.make.tap(
85 { name: this.constructor.name, stage: Number.POSITIVE_INFINITY },
86 (compilation) => {
87 // Exhaustively search all entries for `integrationEntry`.
88 // If found, mark those entries and the index of `integrationEntry`.
89 for (const [name, entryData] of compilation.entries.entries()) {
90 const index = entryData.dependencies.findIndex(
91 (dep) => dep.request && dep.request.includes(integrationEntry)
92 );
93 if (index !== -1) {
94 socketEntryData.push({ name, index });
95 }
96 }
97 }
98 );
99
100 // Overlay entries need to be injected AFTER integration's entry,
101 // so we will loop through everything in `finishMake` instead of `make`.
102 // This ensures we can traverse all entry points and inject stuff with the correct order.
103 addEntries.overlayEntries.forEach((entry, idx, arr) => {
104 compiler.hooks.finishMake.tapPromise(
105 { name: this.constructor.name, stage: Number.MIN_SAFE_INTEGER + (arr.length - idx - 1) },
106 (compilation) => {
107 // Only hook into the current compiler
108 if (compilation.compiler !== compiler) {
109 return Promise.resolve();
110 }
111
112 const injectData = socketEntryData.length ? socketEntryData : [{ name: undefined }];
113 return Promise.all(
114 injectData.map(({ name, index }) => {
115 return new Promise((resolve, reject) => {
116 const options = { name };
117 const dep = EntryPlugin.createDependency(entry, options);
118 compilation.addEntry(compiler.context, dep, options, (err) => {
119 if (err) return reject(err);
120
121 // If the entry is not a global one,
122 // and we have registered the index for integration entry,
123 // we will reorder all entry dependencies to our desired order.
124 // That is, to have additional entries DIRECTLY behind integration entry.
125 if (name && typeof index !== 'undefined') {
126 const entryData = compilation.entries.get(name);
127 entryData.dependencies.splice(
128 index + 1,
129 0,
130 entryData.dependencies.splice(entryData.dependencies.length - 1, 1)[0]
131 );
132 }
133
134 resolve();
135 });
136 });
137 })
138 ).then(() => {});
139 }
140 );
141 });
142 } else {
143 compiler.options.entry = injectRefreshEntry(compiler.options.entry, addEntries);
144 }
145
146 // Inject necessary modules and variables to bundle's global scope
147 const refreshGlobal = getRefreshGlobalScope(RuntimeGlobals || {});
148 /** @type {Record<string, string | boolean>}*/
149 const definedModules = {
150 // Mapping of react-refresh globals to Webpack runtime globals
151 $RefreshReg$: `${refreshGlobal}.register`,
152 $RefreshSig$: `${refreshGlobal}.signature`,
153 'typeof $RefreshReg$': 'function',
154 'typeof $RefreshSig$': 'function',
155
156 // Library mode
157 __react_refresh_library__: JSON.stringify(
158 Template.toIdentifier(
159 this.options.library ||
160 compiler.options.output.uniqueName ||
161 compiler.options.output.library
162 )
163 ),
164 };
165 /** @type {Record<string, string>} */
166 const providedModules = {
167 __react_refresh_utils__: require.resolve('./runtime/RefreshUtils'),
168 };
169
170 if (this.options.overlay === false) {
171 // Stub errorOverlay module so their calls can be erased
172 definedModules.__react_refresh_error_overlay__ = false;
173 definedModules.__react_refresh_polyfill_url__ = false;
174 definedModules.__react_refresh_socket__ = false;
175 } else {
176 definedModules.__react_refresh_polyfill_url__ = this.options.overlay.useURLPolyfill || false;
177
178 if (this.options.overlay.module) {
179 providedModules.__react_refresh_error_overlay__ = require.resolve(
180 this.options.overlay.module
181 );
182 }
183 if (this.options.overlay.sockIntegration) {
184 providedModules.__react_refresh_socket__ = getSocketIntegration(
185 this.options.overlay.sockIntegration
186 );
187 }
188 }
189
190 new DefinePlugin(definedModules).apply(compiler);
191 new ProvidePlugin(providedModules).apply(compiler);
192
193 const match = ModuleFilenameHelpers.matchObject.bind(undefined, this.options);
194 let loggedHotWarning = false;
195 compiler.hooks.compilation.tap(
196 this.constructor.name,
197 (compilation, { normalModuleFactory }) => {
198 // Only hook into the current compiler
199 if (compilation.compiler !== compiler) {
200 return;
201 }
202
203 // Tap into version-specific compilation hooks
204 switch (webpackVersion) {
205 case 4: {
206 const outputOptions = compilation.mainTemplate.outputOptions;
207 compilation.mainTemplate.hooks.require.tap(
208 this.constructor.name,
209 // Constructs the module template for react-refresh
210 (source, chunk, hash) => {
211 // Check for the output filename
212 // This is to ensure we are processing a JS-related chunk
213 let filename = outputOptions.filename;
214 if (typeof filename === 'function') {
215 // Only usage of the `chunk` property is documented by Webpack.
216 // However, some internal Webpack plugins uses other properties,
217 // so we also pass them through to be on the safe side.
218 filename = filename({
219 contentHashType: 'javascript',
220 chunk,
221 hash,
222 });
223 }
224
225 // Check whether the current compilation is outputting to JS,
226 // since other plugins can trigger compilations for other file types too.
227 // If we apply the transform to them, their compilation will break fatally.
228 // One prominent example of this is the HTMLWebpackPlugin.
229 // If filename is falsy, something is terribly wrong and there's nothing we can do.
230 if (!filename || !filename.includes('.js')) {
231 return source;
232 }
233
234 // Split template source code into lines for easier processing
235 const lines = source.split('\n');
236 // Webpack generates this line when the MainTemplate is called
237 const moduleInitializationLineNumber = lines.findIndex((line) =>
238 line.includes('modules[moduleId].call(')
239 );
240 // Unable to find call to module execution -
241 // this happens if the current module does not call MainTemplate.
242 // In this case, we will return the original source and won't mess with it.
243 if (moduleInitializationLineNumber === -1) {
244 return source;
245 }
246
247 const moduleInterceptor = Template.asString([
248 `${refreshGlobal}.setup(moduleId);`,
249 'try {',
250 Template.indent(lines[moduleInitializationLineNumber]),
251 '} finally {',
252 Template.indent(`${refreshGlobal}.cleanup(moduleId);`),
253 '}',
254 ]);
255
256 return Template.asString([
257 ...lines.slice(0, moduleInitializationLineNumber),
258 '',
259 outputOptions.strictModuleExceptionHandling
260 ? Template.indent(moduleInterceptor)
261 : moduleInterceptor,
262 '',
263 ...lines.slice(moduleInitializationLineNumber + 1, lines.length),
264 ]);
265 }
266 );
267
268 compilation.mainTemplate.hooks.requireExtensions.tap(
269 this.constructor.name,
270 // Setup react-refresh globals as extensions to Webpack's require function
271 (source) => {
272 return Template.asString([source, '', getRefreshGlobal(Template)]);
273 }
274 );
275
276 normalModuleFactory.hooks.afterResolve.tap(
277 this.constructor.name,
278 // Add react-refresh loader to process files that matches specified criteria
279 (data) => {
280 return injectRefreshLoader(data, {
281 match,
282 options: { const: false, esModule: false },
283 });
284 }
285 );
286
287 compilation.hooks.normalModuleLoader.tap(
288 // `Number.POSITIVE_INFINITY` ensures this check will run only after all other taps
289 { name: this.constructor.name, stage: Number.POSITIVE_INFINITY },
290 // Check for existence of the HMR runtime -
291 // it is the foundation to this plugin working correctly
292 (context) => {
293 if (!context.hot && !loggedHotWarning) {
294 logger.warn(
295 [
296 'Hot Module Replacement (HMR) is not enabled!',
297 'React Refresh requires HMR to function properly.',
298 ].join(' ')
299 );
300 loggedHotWarning = true;
301 }
302 }
303 );
304
305 break;
306 }
307 case 5: {
308 // Set factory for EntryDependency which is used to initialise the module
309 compilation.dependencyFactories.set(EntryDependency, normalModuleFactory);
310
311 const ReactRefreshRuntimeModule = makeRefreshRuntimeModule(webpack);
312 compilation.hooks.additionalTreeRuntimeRequirements.tap(
313 this.constructor.name,
314 // Setup react-refresh globals with a Webpack runtime module
315 (chunk, runtimeRequirements) => {
316 runtimeRequirements.add(RuntimeGlobals.interceptModuleExecution);
317 runtimeRequirements.add(RuntimeGlobals.moduleCache);
318 runtimeRequirements.add(refreshGlobal);
319 compilation.addRuntimeModule(chunk, new ReactRefreshRuntimeModule());
320 }
321 );
322
323 normalModuleFactory.hooks.afterResolve.tap(
324 this.constructor.name,
325 // Add react-refresh loader to process files that matches specified criteria
326 (resolveData) => {
327 injectRefreshLoader(resolveData.createData, {
328 match,
329 options: {
330 const: compilation.runtimeTemplate.supportsConst(),
331 esModule: this.options.esModule,
332 },
333 });
334 }
335 );
336
337 NormalModule.getCompilationHooks(compilation).loader.tap(
338 // `Infinity` ensures this check will run only after all other taps
339 { name: this.constructor.name, stage: Infinity },
340 // Check for existence of the HMR runtime -
341 // it is the foundation to this plugin working correctly
342 (context) => {
343 if (!context.hot && !loggedHotWarning) {
344 logger.warn(
345 [
346 'Hot Module Replacement (HMR) is not enabled!',
347 'React Refresh requires HMR to function properly.',
348 ].join(' ')
349 );
350 loggedHotWarning = true;
351 }
352 }
353 );
354
355 break;
356 }
357 default: {
358 // Do nothing - this should be an impossible case
359 }
360 }
361 }
362 );
363 }
364}
365
366module.exports.ReactRefreshPlugin = ReactRefreshPlugin;
367module.exports = ReactRefreshPlugin;
Note: See TracBrowser for help on using the repository browser.