UNPKG

2.92 kBJavaScriptView Raw
1const { SourceMapConsumer, SourceMapGenerator, SourceNode } = require('source-map');
2const { Template } = require('webpack');
3
4/**
5 * Generates an identity source map from a source file.
6 * @param {string} source The content of the source file.
7 * @param {string} resourcePath The name of the source file.
8 * @returns {import('source-map').RawSourceMap} The identity source map.
9 */
10function getIdentitySourceMap(source, resourcePath) {
11 const sourceMap = new SourceMapGenerator();
12 sourceMap.setSourceContent(resourcePath, source);
13
14 source.split('\n').forEach((line, index) => {
15 sourceMap.addMapping({
16 source: resourcePath,
17 original: {
18 line: index + 1,
19 column: 0,
20 },
21 generated: {
22 line: index + 1,
23 column: 0,
24 },
25 });
26 });
27
28 return sourceMap.toJSON();
29}
30
31/**
32 * Gets a runtime template from provided function.
33 * @param {function(): void} fn A function containing the runtime template.
34 * @returns {string} The "sanitized" runtime template.
35 */
36function getTemplate(fn) {
37 return Template.getFunctionContent(fn).trim().replace(/^ {2}/gm, '');
38}
39
40const RefreshSetupRuntime = getTemplate(require('./RefreshSetup.runtime'));
41const RefreshModuleRuntime = getTemplate(require('./RefreshModule.runtime'));
42
43/**
44 * A simple Webpack loader to inject react-refresh HMR code into modules.
45 *
46 * [Reference for Loader API](https://webpack.js.org/api/loaders/)
47 * @this {import('webpack').loader.LoaderContext}
48 * @param {string} source The original module source code.
49 * @param {import('source-map').RawSourceMap} [inputSourceMap] The source map of the module.
50 * @param {*} [meta] The loader metadata passed in.
51 * @returns {void}
52 */
53function ReactRefreshLoader(source, inputSourceMap, meta) {
54 const callback = this.async();
55
56 /**
57 * @this {import('webpack').loader.LoaderContext}
58 * @param {string} source
59 * @param {import('source-map').RawSourceMap} [inputSourceMap]
60 * @returns {Promise<[string, import('source-map').RawSourceMap]>}
61 */
62 async function _loader(source, inputSourceMap) {
63 if (this.sourceMap) {
64 let originalSourceMap = inputSourceMap;
65 if (!originalSourceMap) {
66 originalSourceMap = getIdentitySourceMap(source, this.resourcePath);
67 }
68
69 const node = SourceNode.fromStringWithSourceMap(
70 source,
71 await new SourceMapConsumer(originalSourceMap)
72 );
73
74 node.prepend([RefreshSetupRuntime, '\n\n']);
75 node.add(['\n\n', RefreshModuleRuntime]);
76
77 const { code, map } = node.toStringWithSourceMap();
78 return [code, map.toJSON()];
79 } else {
80 return [[RefreshSetupRuntime, source, RefreshModuleRuntime].join('\n\n'), inputSourceMap];
81 }
82 }
83
84 _loader.call(this, source, inputSourceMap).then(
85 ([code, map]) => {
86 callback(null, code, map, meta);
87 },
88 (error) => {
89 callback(error);
90 }
91 );
92}
93
94module.exports = ReactRefreshLoader;