Skip to content

Commit 183f1de

Browse files
authored
fix: add safety check in prefresh.js to prevent runtime errors (#34)
1 parent 752baf1 commit 183f1de

7 files changed

Lines changed: 295 additions & 0 deletions

File tree

client/prefresh.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ function isSafeExport(key) {
2323
}
2424

2525
function registerExports(moduleExports, moduleId) {
26+
if (!self['__PREFRESH__']) return;
27+
2628
self['__PREFRESH__'].register(moduleExports, moduleId + ' %exports%');
2729
if (moduleExports == null || typeof moduleExports !== 'object') return;
2830

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Case basic: Step 0
2+
3+
## Changed Files
4+
5+
6+
## Asset Files
7+
- Bundle: bundle.js
8+
- Bundle: worker_js.chunk.CURRENT_HASH.js
9+
10+
## Manifest
11+
12+
13+
## Update
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Case basic: Step 1
2+
3+
## Changed Files
4+
- app.jsx
5+
6+
## Asset Files
7+
- Bundle: bundle.js
8+
- Bundle: worker_js.chunk.CURRENT_HASH.js
9+
- Manifest: [runtime of worker_js].LAST_HASH.hot-update.json, size: 33
10+
- Manifest: main.LAST_HASH.hot-update.json, size: 28
11+
- Update: main.LAST_HASH.hot-update.js, size: 3832
12+
- Update: worker_js.LAST_HASH.hot-update.js, size: 187
13+
14+
## Manifest
15+
16+
### [runtime of worker_js].LAST_HASH.hot-update.json
17+
18+
```json
19+
{"c":["worker_js"],"r":[],"m":[]}
20+
```
21+
22+
23+
24+
### main.LAST_HASH.hot-update.json
25+
26+
```json
27+
{"c":["main"],"r":[],"m":[]}
28+
```
29+
30+
31+
## Update
32+
33+
34+
### main.LAST_HASH.hot-update.js
35+
36+
#### Changed Modules
37+
- ./app.jsx
38+
39+
#### Changed Runtime Modules
40+
- webpack/runtime/get_full_hash
41+
42+
#### Changed Content
43+
```js
44+
"use strict";
45+
self["webpackHotUpdate"]("main", {
46+
"./app.jsx":
47+
/*!*****************!*\
48+
!*** ./app.jsx ***!
49+
\*****************/
50+
(function (module, __webpack_exports__, __webpack_require__) {
51+
__webpack_require__.r(__webpack_exports__);
52+
__webpack_require__.d(__webpack_exports__, {
53+
App: () => (App)
54+
});
55+
/* ESM import */var react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react/jsx-dev-runtime */ "../../../../node_modules/<PNPM_INNER>/preact/compat/jsx-dev-runtime.js");
56+
/* module decorator */ module = __webpack_require__.hmd(module);
57+
/* provided dependency */ var __prefresh_utils__ = __webpack_require__(/*! ../../../../client/prefresh.js */ "../../../../client/prefresh.js")["default"];
58+
59+
function App() {
60+
return /*#__PURE__*/ (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxDEV)("div", {
61+
children: /*#__PURE__*/ (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxDEV)("span", {
62+
children: "content 2"
63+
}, void 0, false, {
64+
fileName: "<ROOT>/test/hotCases/with-worker/basic/app.jsx",
65+
lineNumber: 2,
66+
columnNumber: 16
67+
}, this)
68+
}, void 0, false, {
69+
fileName: "<ROOT>/test/hotCases/with-worker/basic/app.jsx",
70+
lineNumber: 2,
71+
columnNumber: 11
72+
}, this);
73+
}
74+
_c = App;
75+
var _c;
76+
$RefreshReg$(_c, "App");
77+
78+
/**
79+
* The following code is modified based on
80+
* //https://github.com/preactjs/prefresh/blob/018f5cc907629b82ffb201c32e948efe4b40098a/packages/webpack/src/loader/runtime.js
81+
*
82+
* MIT Licensed
83+
* Author JoviDeCroock
84+
* Copyright (c) 2021-Present Preact Team
85+
* https://github.com/preactjs/prefresh/blob/018f5cc907629b82ffb201c32e948efe4b40098a/LICENSE
86+
*/
87+
88+
const isPrefreshComponent = __prefresh_utils__.shouldBind(module);
89+
90+
// `@vanilla-extract/webpack` does some custom preprocessing where
91+
// `module.hot` is partially replaced. This leads to our injected
92+
// code being executed although it shouldn't be:
93+
//
94+
// Intermediate result:
95+
//
96+
// if (true) { // <- inlined by intermediate compile step
97+
// const previousHotModuleExports = module.hot.data && ...
98+
// // Crash happens here ---^
99+
//
100+
// It crashes at that line because some intermediate compiler isn't
101+
// running in hot mode, but the overall guard condition was compiled
102+
// down to being truthy. By moving `module.hot` outside of the
103+
// condition of the if-statement, it will be left as is.
104+
const moduleHot = module.hot;
105+
106+
if (moduleHot) {
107+
const currentExports = __prefresh_utils__.getExports(module);
108+
const previousHotModuleExports =
109+
moduleHot.data && moduleHot.data.moduleExports;
110+
111+
__prefresh_utils__.registerExports(currentExports, module.id);
112+
113+
if (isPrefreshComponent) {
114+
if (previousHotModuleExports) {
115+
try {
116+
__prefresh_utils__.flush();
117+
if (
118+
typeof __prefresh_errors__ !== 'undefined' &&
119+
__prefresh_errors__ &&
120+
__prefresh_errors__.clearRuntimeErrors
121+
) {
122+
__prefresh_errors__.clearRuntimeErrors();
123+
}
124+
} catch (e) {
125+
// Only available in newer webpack versions.
126+
if (moduleHot.invalidate) {
127+
moduleHot.invalidate();
128+
} else {
129+
self.location.reload();
130+
}
131+
}
132+
}
133+
134+
moduleHot.dispose(data => {
135+
data.moduleExports = __prefresh_utils__.getExports(module);
136+
});
137+
138+
moduleHot.accept(function errorRecovery() {
139+
if (
140+
typeof __prefresh_errors__ !== 'undefined' &&
141+
__prefresh_errors__ &&
142+
__prefresh_errors__.handleRuntimeError
143+
) {
144+
__prefresh_errors__.handleRuntimeError(error);
145+
}
146+
147+
__webpack_require__.c[module.id].hot.accept(errorRecovery);
148+
});
149+
}
150+
}
151+
152+
}),
153+
154+
},function(__webpack_require__) {
155+
// webpack/runtime/get_full_hash
156+
(() => {
157+
__webpack_require__.h = () => ("CURRENT_HASH")
158+
})();
159+
160+
}
161+
);
162+
```
163+
164+
165+
166+
### worker_js.LAST_HASH.hot-update.js
167+
168+
#### Changed Modules
169+
170+
171+
#### Changed Runtime Modules
172+
- webpack/runtime/get_full_hash
173+
174+
#### Changed Content
175+
```js
176+
"use strict";
177+
self["webpackHotUpdate"]("worker_js", {},function(__webpack_require__) {
178+
// webpack/runtime/get_full_hash
179+
(() => {
180+
__webpack_require__.h = () => ("CURRENT_HASH")
181+
})();
182+
183+
}
184+
);
185+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function App() {
2+
return (<div><span>content 1</span></div>);
3+
}
4+
---
5+
export function App() {
6+
return (<div><span>content 2</span></div>);
7+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom/client';
3+
import update from '../../update';
4+
import { App } from './app';
5+
6+
const container = document.createElement('div');
7+
container.id = 'root';
8+
document.body.appendChild(container);
9+
const root = ReactDOM.createRoot(container);
10+
root.render(
11+
<div>
12+
<App />
13+
</div>,
14+
);
15+
16+
new Worker(new URL('./worker.js', import.meta.url));
17+
18+
it('should rerender jsx', (done) => {
19+
expect(container.querySelector('span').textContent).toBe('content 1');
20+
NEXT(
21+
update(done, true, () => {
22+
expect(container.querySelector('span').textContent).toBe('content 2');
23+
done();
24+
}),
25+
);
26+
});
27+
28+
if (module.hot) {
29+
module.hot.accept('./app');
30+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const ReactRefreshRspackPlugin = require('../../../..');
2+
3+
/** @type {import('@rspack/core').Configuration} */
4+
module.exports = {
5+
context: __dirname,
6+
entry: './index.jsx',
7+
resolve: {
8+
extensions: ['...', '.ts', '.tsx', '.jsx'],
9+
alias: {
10+
react: 'preact/compat',
11+
'react-dom/test-utils': 'preact/test-utils',
12+
'react-dom': 'preact/compat', // Must be below test-utils
13+
'react/jsx-runtime': 'preact/jsx-runtime',
14+
},
15+
},
16+
module: {
17+
rules: [
18+
{
19+
test: /\.jsx?$/,
20+
exclude: [/node_modules/],
21+
use: {
22+
loader: 'builtin:swc-loader',
23+
options: {
24+
jsc: {
25+
experimental: {
26+
plugins: [
27+
[
28+
'@swc/plugin-prefresh', // enable prefresh specific transformation
29+
{},
30+
],
31+
],
32+
},
33+
parser: {
34+
syntax: 'ecmascript',
35+
jsx: true,
36+
},
37+
externalHelpers: true,
38+
preserveAllComments: false,
39+
transform: {
40+
react: {
41+
runtime: 'automatic',
42+
pragma: 'h',
43+
pragmaFrag: 'Fragment',
44+
throwIfNamespace: true,
45+
useBuiltins: false,
46+
refresh: true, // enable react hooks hmr compatiblity
47+
},
48+
},
49+
},
50+
},
51+
},
52+
type: 'javascript/auto',
53+
},
54+
],
55+
},
56+
plugins: [new ReactRefreshRspackPlugin()],
57+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// empty

0 commit comments

Comments
 (0)