react-build-reload 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/bin/react-build-reload.js +262 -0
- package/dist/components/BuildReloadWatcher.d.ts +3 -0
- package/dist/components/BuildReloadWatcher.d.ts.map +1 -0
- package/dist/hooks/useBuildReload.d.ts +3 -0
- package/dist/hooks/useBuildReload.d.ts.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +238 -0
- package/dist/types/index.d.ts +49 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/chunkErrors.d.ts +4 -0
- package/dist/utils/chunkErrors.d.ts.map +1 -0
- package/dist/utils/compareBuildId.d.ts +2 -0
- package/dist/utils/compareBuildId.d.ts.map +1 -0
- package/dist/utils/fetchBuildInfo.d.ts +6 -0
- package/dist/utils/fetchBuildInfo.d.ts.map +1 -0
- package/dist/utils/reloadPage.d.ts +2 -0
- package/dist/utils/reloadPage.d.ts.map +1 -0
- package/docs/api-reference.md +87 -0
- package/docs/getting-started.md +97 -0
- package/docs/roadmap.md +26 -0
- package/docs/usage-examples.md +85 -0
- package/docs/version-file.md +63 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Satyam shah
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# react-build-reload
|
|
2
|
+
|
|
3
|
+
React library for detecting when a new frontend build is available and helping the app refresh safely.
|
|
4
|
+
|
|
5
|
+
## Website
|
|
6
|
+
|
|
7
|
+
Homepage: [react-refresh-website.satyamshah.workers.dev](https://react-refresh-website.satyamshah.workers.dev)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install react-build-reload
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
React is a peer dependency. The consuming app must provide React and React DOM.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
Generate the version file before building your app:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx react-build-reload generate
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This creates `public/build-version.json`.
|
|
26
|
+
|
|
27
|
+
Add the watcher near the root of your app:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { BuildReloadWatcher } from "react-build-reload";
|
|
31
|
+
|
|
32
|
+
export function App() {
|
|
33
|
+
return (
|
|
34
|
+
<>
|
|
35
|
+
<BuildReloadWatcher
|
|
36
|
+
versionUrl="/build-version.json"
|
|
37
|
+
checkInterval={60_000}
|
|
38
|
+
reloadMode="prompt"
|
|
39
|
+
/>
|
|
40
|
+
<MainApp />
|
|
41
|
+
</>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The generated version file looks like this:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"buildId": "abc123-20260530123000",
|
|
51
|
+
"version": "1.0.0",
|
|
52
|
+
"builtAt": "2026-05-30T12:30:00.000Z",
|
|
53
|
+
"environment": "production"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
When `buildId` changes, the library can show a prompt, reload automatically, or call your callback.
|
|
58
|
+
|
|
59
|
+
## Defaults
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<BuildReloadWatcher
|
|
63
|
+
versionUrl="/build-version.json"
|
|
64
|
+
checkInterval={60000}
|
|
65
|
+
reloadMode="prompt"
|
|
66
|
+
reloadOnChunkError={true}
|
|
67
|
+
/>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Hook Usage
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { useBuildReload } from "react-build-reload";
|
|
74
|
+
|
|
75
|
+
function CustomReloadNotice() {
|
|
76
|
+
const { isNewBuildAvailable, reloadApp } = useBuildReload({
|
|
77
|
+
reloadMode: "manual"
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!isNewBuildAvailable) return null;
|
|
81
|
+
|
|
82
|
+
return <button onClick={reloadApp}>Refresh now</button>;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Documentation
|
|
87
|
+
|
|
88
|
+
- [Getting started](docs/getting-started.md)
|
|
89
|
+
- [API reference](docs/api-reference.md)
|
|
90
|
+
- [Version file](docs/version-file.md)
|
|
91
|
+
- [Usage examples](docs/usage-examples.md)
|
|
92
|
+
- [Roadmap](docs/roadmap.md)
|
|
93
|
+
|
|
94
|
+
## Scope
|
|
95
|
+
|
|
96
|
+
This package handles runtime build checks in React apps and includes a small CLI to create `build-version.json`. It does not provide Vite/Webpack/Next.js plugins or manage service workers.
|
|
97
|
+
|
|
98
|
+
## Author
|
|
99
|
+
|
|
100
|
+
Created by [Satyam Shah](https://github.com/satyam-shah-gt).
|
|
101
|
+
|
|
102
|
+
## Free to Use
|
|
103
|
+
|
|
104
|
+
`react-build-reload` is free to use under the MIT license.
|
|
105
|
+
|
|
106
|
+
## No Warranty
|
|
107
|
+
|
|
108
|
+
This package is provided as-is, without any warranty. The author is not liable for any damages, data loss, downtime, or other issues caused by using this package. Review and test it in your own environment before using it in production.
|
|
109
|
+
|
|
110
|
+
## Contributing
|
|
111
|
+
|
|
112
|
+
Contributions are welcome. Please open an issue or pull request on the repository:
|
|
113
|
+
|
|
114
|
+
[github.com/satyam-shah-gt/react-build-reload](https://github.com/satyam-shah-gt/react-build-reload)
|
|
115
|
+
|
|
116
|
+
Before submitting changes, run:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm run typecheck
|
|
120
|
+
npm test
|
|
121
|
+
npm run build
|
|
122
|
+
```
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
realpathSync,
|
|
8
|
+
writeFileSync
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { dirname, resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_OUT = "public/build-version.json";
|
|
14
|
+
|
|
15
|
+
function printHelp() {
|
|
16
|
+
console.log(`react-build-reload
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
react-build-reload generate [options]
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--out <path> Output file path. Default: ${DEFAULT_OUT}
|
|
23
|
+
--build-id <id> Build ID to write. Defaults to env/git/timestamp data.
|
|
24
|
+
--version <version> App version. Defaults to package.json version when found.
|
|
25
|
+
--environment <name> Environment name. Defaults to NODE_ENV or production.
|
|
26
|
+
--no-git Skip git metadata lookup.
|
|
27
|
+
--help Show help.
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
react-build-reload generate --out public/build-version.json
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseArgs(args) {
|
|
35
|
+
const options = {
|
|
36
|
+
command: "generate",
|
|
37
|
+
out: DEFAULT_OUT,
|
|
38
|
+
buildId: undefined,
|
|
39
|
+
version: undefined,
|
|
40
|
+
environment: undefined,
|
|
41
|
+
includeGit: true,
|
|
42
|
+
help: false
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const pending = [...args];
|
|
46
|
+
|
|
47
|
+
if (pending[0] && !pending[0].startsWith("-")) {
|
|
48
|
+
options.command = pending.shift();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (let index = 0; index < pending.length; index += 1) {
|
|
52
|
+
const arg = pending[index];
|
|
53
|
+
|
|
54
|
+
switch (arg) {
|
|
55
|
+
case "--out":
|
|
56
|
+
options.out = readRequiredValue(pending, ++index, "--out");
|
|
57
|
+
break;
|
|
58
|
+
case "--build-id":
|
|
59
|
+
options.buildId = readRequiredValue(pending, ++index, "--build-id");
|
|
60
|
+
break;
|
|
61
|
+
case "--version":
|
|
62
|
+
options.version = readRequiredValue(pending, ++index, "--version");
|
|
63
|
+
break;
|
|
64
|
+
case "--environment":
|
|
65
|
+
options.environment = readRequiredValue(pending, ++index, "--environment");
|
|
66
|
+
break;
|
|
67
|
+
case "--no-git":
|
|
68
|
+
options.includeGit = false;
|
|
69
|
+
break;
|
|
70
|
+
case "--help":
|
|
71
|
+
case "-h":
|
|
72
|
+
options.help = true;
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return options;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readRequiredValue(args, index, optionName) {
|
|
83
|
+
const value = args[index];
|
|
84
|
+
|
|
85
|
+
if (!value || value.startsWith("-")) {
|
|
86
|
+
throw new Error(`${optionName} requires a value.`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readPackageJson(cwd) {
|
|
93
|
+
const packagePath = resolve(cwd, "package.json");
|
|
94
|
+
|
|
95
|
+
if (!existsSync(packagePath)) {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(readFileSync(packagePath, "utf8"));
|
|
101
|
+
} catch {
|
|
102
|
+
throw new Error(`Could not parse ${packagePath}.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function readGitValue(cwd, args) {
|
|
107
|
+
try {
|
|
108
|
+
return execFileSync("git", args, {
|
|
109
|
+
cwd,
|
|
110
|
+
encoding: "utf8",
|
|
111
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
112
|
+
}).trim();
|
|
113
|
+
} catch {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readGitInfo(cwd) {
|
|
119
|
+
const commit = readGitValue(cwd, ["rev-parse", "HEAD"]);
|
|
120
|
+
const shortCommit = readGitValue(cwd, ["rev-parse", "--short", "HEAD"]);
|
|
121
|
+
const branch = readGitValue(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
122
|
+
const status = readGitValue(cwd, ["status", "--porcelain"]);
|
|
123
|
+
|
|
124
|
+
if (!commit && !shortCommit && !branch) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
commit,
|
|
130
|
+
shortCommit,
|
|
131
|
+
branch,
|
|
132
|
+
dirty: Boolean(status)
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function readEnvBuildId(env) {
|
|
137
|
+
return (
|
|
138
|
+
env.BUILD_ID ||
|
|
139
|
+
env.VITE_BUILD_ID ||
|
|
140
|
+
env.REACT_APP_BUILD_ID ||
|
|
141
|
+
env.GITHUB_SHA ||
|
|
142
|
+
env.VERCEL_GIT_COMMIT_SHA ||
|
|
143
|
+
env.CF_PAGES_COMMIT_SHA ||
|
|
144
|
+
env.COMMIT_SHA
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function createBuildId({ explicitBuildId, env, gitInfo, builtAt }) {
|
|
149
|
+
if (explicitBuildId?.trim()) {
|
|
150
|
+
return explicitBuildId.trim();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const envBuildId = readEnvBuildId(env);
|
|
154
|
+
|
|
155
|
+
if (envBuildId?.trim()) {
|
|
156
|
+
return envBuildId.trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const timestamp = builtAt.replace(/\D/g, "").slice(0, 14);
|
|
160
|
+
|
|
161
|
+
if (gitInfo?.shortCommit) {
|
|
162
|
+
return `${gitInfo.shortCommit}-${timestamp}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return `build-${timestamp}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createBuildInfo(options, cwd = process.cwd(), env = process.env) {
|
|
169
|
+
const packageJson = readPackageJson(cwd);
|
|
170
|
+
const builtAt = new Date().toISOString();
|
|
171
|
+
const git = options.includeGit ? readGitInfo(cwd) : undefined;
|
|
172
|
+
const buildId = createBuildId({
|
|
173
|
+
explicitBuildId: options.buildId,
|
|
174
|
+
env,
|
|
175
|
+
gitInfo: git,
|
|
176
|
+
builtAt
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (!buildId.trim()) {
|
|
180
|
+
throw new Error("Generated buildId is empty.");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const buildInfo = {
|
|
184
|
+
buildId,
|
|
185
|
+
version: options.version || packageJson.version || "0.0.0",
|
|
186
|
+
builtAt,
|
|
187
|
+
environment: options.environment || env.NODE_ENV || "production"
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
if (packageJson.name) {
|
|
191
|
+
buildInfo.name = packageJson.name;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (git) {
|
|
195
|
+
buildInfo.git = git;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return buildInfo;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function writeBuildInfo(buildInfo, outPath, cwd = process.cwd()) {
|
|
202
|
+
const absoluteOutPath = resolve(cwd, outPath);
|
|
203
|
+
|
|
204
|
+
mkdirSync(dirname(absoluteOutPath), { recursive: true });
|
|
205
|
+
writeFileSync(absoluteOutPath, `${JSON.stringify(buildInfo, null, 2)}\n`, "utf8");
|
|
206
|
+
|
|
207
|
+
return absoluteOutPath;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function run(args = process.argv.slice(2), cwd = process.cwd(), env = process.env) {
|
|
211
|
+
const options = parseArgs(args);
|
|
212
|
+
|
|
213
|
+
if (options.help) {
|
|
214
|
+
printHelp();
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (options.command !== "generate") {
|
|
219
|
+
throw new Error(`Unknown command: ${options.command}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const buildInfo = createBuildInfo(options, cwd, env);
|
|
223
|
+
const outPath = writeBuildInfo(buildInfo, options.out, cwd);
|
|
224
|
+
|
|
225
|
+
console.log(`Created ${outPath}`);
|
|
226
|
+
console.log(`buildId: ${buildInfo.buildId}`);
|
|
227
|
+
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isDirectCliInvocation(argvPath, currentFilePath) {
|
|
232
|
+
if (!argvPath) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
return realpathSync(argvPath) === currentFilePath;
|
|
238
|
+
} catch {
|
|
239
|
+
return resolve(argvPath) === currentFilePath;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
244
|
+
|
|
245
|
+
if (isDirectCliInvocation(process.argv[1], currentFilePath)) {
|
|
246
|
+
try {
|
|
247
|
+
process.exitCode = run();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
250
|
+
process.exitCode = 1;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export {
|
|
255
|
+
createBuildId,
|
|
256
|
+
createBuildInfo,
|
|
257
|
+
parseArgs,
|
|
258
|
+
readGitInfo,
|
|
259
|
+
run,
|
|
260
|
+
writeBuildInfo,
|
|
261
|
+
isDirectCliInvocation
|
|
262
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { BuildReloadWatcherProps } from "../types";
|
|
2
|
+
export declare function BuildReloadWatcher({ promptMessage, refreshButtonLabel, dismissButtonLabel, promptPosition, showDismissButton, reloadOnChunkError, ...options }: BuildReloadWatcherProps): import("react/jsx-runtime").JSX.Element | null;
|
|
3
|
+
//# sourceMappingURL=BuildReloadWatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BuildReloadWatcher.d.ts","sourceRoot":"","sources":["../../src/components/BuildReloadWatcher.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAoDxD,wBAAgB,kBAAkB,CAAC,EACjC,aAAgE,EAChE,kBAA8B,EAC9B,kBAA8B,EAC9B,cAAyB,EACzB,iBAAwB,EACxB,kBAAyB,EACzB,GAAG,OAAO,EACX,EAAE,uBAAuB,kDA6CzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBuildReload.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuildReload.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAIV,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,UAAU,CAAC;AAMlB,wBAAgB,cAAc,CAC5B,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAiJtB"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react"),t=require("react/jsx-runtime");var n=[/loading chunk \d* failed/i,/chunkloaderror/i,/failed to fetch dynamically imported module/i,/importing a module script failed/i,/error loading dynamically imported module/i,/unable to preload css/i],r=`react-build-reload:chunk-error-reloaded`;function i(e){if(typeof e==`string`)return e;if(e instanceof Error)return`${e.name} ${e.message}`;if(typeof e==`object`&&e){let t=e;return[t.message,t.reason,t.type].filter(Boolean).map(String).join(` `)}return``}function a(e){let t=i(e);return n.some(e=>e.test(t))}function o(e,t){if(typeof window>`u`)return()=>void 0;let n=n=>{if(!a(n))return;let o=n instanceof Error?n:Error(i(n)||`Chunk loading failed.`);t?.(o),sessionStorage.getItem(r)!==`true`&&(sessionStorage.setItem(r,`true`),e())},o=e=>{n(e.error??e.message)},s=e=>{n(e.reason)};return window.addEventListener(`error`,o),window.addEventListener(`unhandledrejection`,s),()=>{window.removeEventListener(`error`,o),window.removeEventListener(`unhandledrejection`,s)}}var s=class extends Error{constructor(e){super(e),this.name=`BuildReloadError`}},c=`/build-version.json`;function l(e,t=Date.now()){let n=/^[a-z][a-z\d+\-.]*:/i.test(e),r=typeof window>`u`?`http://localhost`:window.location.href,i=new URL(e,r);return i.searchParams.set(`t`,String(t)),n?i.toString():`${i.pathname}${i.search}${i.hash}`}function u(e){return typeof e==`object`&&!!e&&`buildId`in e&&typeof e.buildId==`string`&&e.buildId.trim().length>0}async function d(e=c,t){let n;try{n=await fetch(l(e),{cache:`no-store`,signal:t})}catch(e){throw e instanceof Error?e:new s(`Failed to fetch build information.`)}if(!n.ok)throw new s(`Failed to fetch build information: ${n.status} ${n.statusText}`.trim());let r;try{r=await n.json()}catch{throw new s(`Build information response is not valid JSON.`)}if(!u(r))throw new s(`Build information must include a non-empty buildId.`);return r}function f(e,t){return!e||!t?!1:e!==t}function p(){typeof window>`u`||window.location.reload()}var m=6e4,h=`prompt`,g=0;function _(t={}){let{versionUrl:n=c,currentBuildId:r,checkInterval:i=m,reloadMode:a=h,reloadDelay:o=g,enabled:s=!0,onNewBuild:l,onError:u}=t,[_,v]=(0,e.useState)(r??null),[y,b]=(0,e.useState)(null),[x,S]=(0,e.useState)(!1),[C,w]=(0,e.useState)(null),T=(0,e.useRef)(r??null),E=(0,e.useRef)(null),D=(0,e.useRef)(null),O=(0,e.useRef)(null),k=(0,e.useRef)(l),A=(0,e.useRef)(u);(0,e.useEffect)(()=>{k.current=l},[l]),(0,e.useEffect)(()=>{A.current=u},[u]),(0,e.useEffect)(()=>{r!==void 0&&(T.current=r,v(r))},[r]);let j=(0,e.useCallback)(()=>{p()},[]),M=(0,e.useCallback)(e=>{E.current!==e.latestBuildId&&(E.current=e.latestBuildId,S(!0),k.current?.(e),a===`auto`&&(D.current=setTimeout(j,Math.max(0,o))))},[j,o,a]),N=(0,e.useCallback)(async()=>{if(!s)return;O.current?.abort();let e=new AbortController;O.current=e;try{let t=await d(n,e.signal);b(t),w(null);let r=T.current;if(!r){T.current=t.buildId,v(t.buildId);return}f(r,t.buildId)&&M({currentBuildId:r,latestBuildId:t.buildId,latestBuildInfo:t})}catch(e){if(e instanceof DOMException&&e.name===`AbortError`)return;let t=e instanceof Error?e:Error(`Build reload check failed.`);w(t),A.current?.(t)}},[s,M,n]);(0,e.useEffect)(()=>{if(!s)return;N();let e=window.setInterval(()=>{N()},Math.max(1e3,i));return()=>{window.clearInterval(e),O.current?.abort(),D.current&&clearTimeout(D.current)}},[i,N,s]);let P=(0,e.useCallback)(()=>{S(!1)},[]);return{isNewBuildAvailable:x,currentBuildId:_,latestBuildId:y?.buildId??null,latestBuildInfo:y,error:C,reloadApp:j,checkNow:N,dismissPrompt:P}}var v={position:`fixed`,left:`16px`,right:`16px`,zIndex:2147483647,display:`flex`,alignItems:`center`,justifyContent:`space-between`,gap:`12px`,padding:`12px 14px`,borderRadius:`8px`,background:`#111827`,color:`#ffffff`,boxShadow:`0 12px 28px rgba(17, 24, 39, 0.24)`,fontFamily:`Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`,fontSize:`14px`,lineHeight:1.4},y={display:`flex`,alignItems:`center`,gap:`8px`,flexShrink:0},b={border:0,borderRadius:`6px`,padding:`7px 10px`,cursor:`pointer`,font:`inherit`,fontWeight:600},x={...b,background:`#ffffff`,color:`#111827`},S={...b,background:`transparent`,color:`#d1d5db`};function C({promptMessage:n=`A new version is available. Refresh to update.`,refreshButtonLabel:r=`Refresh`,dismissButtonLabel:i=`Dismiss`,promptPosition:a=`bottom`,showDismissButton:s=!0,reloadOnChunkError:c=!0,...l}){let{isNewBuildAvailable:u,reloadApp:d,dismissPrompt:f}=_(l);if((0,e.useEffect)(()=>{if(c)return o(d,l.onError)},[d,c,l.onError]),l.reloadMode===`manual`||!u)return null;let p=a===`top`?{top:`16px`}:{bottom:`16px`};return(0,t.jsxs)(`div`,{role:`status`,"aria-live":`polite`,style:{...v,...p},children:[(0,t.jsx)(`span`,{children:n}),(0,t.jsxs)(`div`,{style:y,children:[s?(0,t.jsx)(`button`,{type:`button`,style:S,onClick:f,children:i}):null,(0,t.jsx)(`button`,{type:`button`,style:x,onClick:d,children:r})]})]})}exports.BuildReloadWatcher=C,exports.DEFAULT_VERSION_URL=c,exports.createCacheSafeUrl=l,exports.fetchBuildInfo=d,exports.hasBuildChanged=f,exports.installChunkErrorReload=o,exports.isChunkLoadError=a,exports.useBuildReload=_;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { BuildReloadWatcher } from "./components/BuildReloadWatcher";
|
|
2
|
+
export { useBuildReload } from "./hooks/useBuildReload";
|
|
3
|
+
export { createCacheSafeUrl, fetchBuildInfo, DEFAULT_VERSION_URL } from "./utils/fetchBuildInfo";
|
|
4
|
+
export { hasBuildChanged } from "./utils/compareBuildId";
|
|
5
|
+
export { isChunkLoadError, installChunkErrorReload } from "./utils/chunkErrors";
|
|
6
|
+
export type { BuildInfo, BuildReloadConfig, BuildReloadWatcherProps, NewBuildPayload, PromptPosition, ReloadMode, UseBuildReloadOptions, UseBuildReloadResult } from "./types";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAChF,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,cAAc,EACd,UAAU,EACV,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { useCallback as e, useEffect as t, useRef as n, useState as r } from "react";
|
|
2
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
3
|
+
//#region src/utils/chunkErrors.ts
|
|
4
|
+
var o = [
|
|
5
|
+
/loading chunk \d* failed/i,
|
|
6
|
+
/chunkloaderror/i,
|
|
7
|
+
/failed to fetch dynamically imported module/i,
|
|
8
|
+
/importing a module script failed/i,
|
|
9
|
+
/error loading dynamically imported module/i,
|
|
10
|
+
/unable to preload css/i
|
|
11
|
+
], s = "react-build-reload:chunk-error-reloaded";
|
|
12
|
+
function c(e) {
|
|
13
|
+
if (typeof e == "string") return e;
|
|
14
|
+
if (e instanceof Error) return `${e.name} ${e.message}`;
|
|
15
|
+
if (typeof e == "object" && e) {
|
|
16
|
+
let t = e;
|
|
17
|
+
return [
|
|
18
|
+
t.message,
|
|
19
|
+
t.reason,
|
|
20
|
+
t.type
|
|
21
|
+
].filter(Boolean).map(String).join(" ");
|
|
22
|
+
}
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
function l(e) {
|
|
26
|
+
let t = c(e);
|
|
27
|
+
return o.some((e) => e.test(t));
|
|
28
|
+
}
|
|
29
|
+
function u(e, t) {
|
|
30
|
+
if (typeof window > "u") return () => void 0;
|
|
31
|
+
let n = (n) => {
|
|
32
|
+
if (!l(n)) return;
|
|
33
|
+
let r = n instanceof Error ? n : Error(c(n) || "Chunk loading failed.");
|
|
34
|
+
t?.(r), sessionStorage.getItem(s) !== "true" && (sessionStorage.setItem(s, "true"), e());
|
|
35
|
+
}, r = (e) => {
|
|
36
|
+
n(e.error ?? e.message);
|
|
37
|
+
}, i = (e) => {
|
|
38
|
+
n(e.reason);
|
|
39
|
+
};
|
|
40
|
+
return window.addEventListener("error", r), window.addEventListener("unhandledrejection", i), () => {
|
|
41
|
+
window.removeEventListener("error", r), window.removeEventListener("unhandledrejection", i);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/types/index.ts
|
|
46
|
+
var d = class extends Error {
|
|
47
|
+
constructor(e) {
|
|
48
|
+
super(e), this.name = "BuildReloadError";
|
|
49
|
+
}
|
|
50
|
+
}, f = "/build-version.json";
|
|
51
|
+
function p(e, t = Date.now()) {
|
|
52
|
+
let n = /^[a-z][a-z\d+\-.]*:/i.test(e), r = typeof window > "u" ? "http://localhost" : window.location.href, i = new URL(e, r);
|
|
53
|
+
return i.searchParams.set("t", String(t)), n ? i.toString() : `${i.pathname}${i.search}${i.hash}`;
|
|
54
|
+
}
|
|
55
|
+
function m(e) {
|
|
56
|
+
return typeof e == "object" && !!e && "buildId" in e && typeof e.buildId == "string" && e.buildId.trim().length > 0;
|
|
57
|
+
}
|
|
58
|
+
async function h(e = f, t) {
|
|
59
|
+
let n;
|
|
60
|
+
try {
|
|
61
|
+
n = await fetch(p(e), {
|
|
62
|
+
cache: "no-store",
|
|
63
|
+
signal: t
|
|
64
|
+
});
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw e instanceof Error ? e : new d("Failed to fetch build information.");
|
|
67
|
+
}
|
|
68
|
+
if (!n.ok) throw new d(`Failed to fetch build information: ${n.status} ${n.statusText}`.trim());
|
|
69
|
+
let r;
|
|
70
|
+
try {
|
|
71
|
+
r = await n.json();
|
|
72
|
+
} catch {
|
|
73
|
+
throw new d("Build information response is not valid JSON.");
|
|
74
|
+
}
|
|
75
|
+
if (!m(r)) throw new d("Build information must include a non-empty buildId.");
|
|
76
|
+
return r;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/utils/compareBuildId.ts
|
|
80
|
+
function g(e, t) {
|
|
81
|
+
return !e || !t ? !1 : e !== t;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/utils/reloadPage.ts
|
|
85
|
+
function _() {
|
|
86
|
+
typeof window > "u" || window.location.reload();
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/hooks/useBuildReload.ts
|
|
90
|
+
var v = 6e4, y = "prompt", b = 0;
|
|
91
|
+
function x(i = {}) {
|
|
92
|
+
let { versionUrl: a = f, currentBuildId: o, checkInterval: s = v, reloadMode: c = y, reloadDelay: l = b, enabled: u = !0, onNewBuild: d, onError: p } = i, [m, x] = r(o ?? null), [S, C] = r(null), [w, T] = r(!1), [E, D] = r(null), O = n(o ?? null), k = n(null), A = n(null), j = n(null), M = n(d), N = n(p);
|
|
93
|
+
t(() => {
|
|
94
|
+
M.current = d;
|
|
95
|
+
}, [d]), t(() => {
|
|
96
|
+
N.current = p;
|
|
97
|
+
}, [p]), t(() => {
|
|
98
|
+
o !== void 0 && (O.current = o, x(o));
|
|
99
|
+
}, [o]);
|
|
100
|
+
let P = e(() => {
|
|
101
|
+
_();
|
|
102
|
+
}, []), F = e((e) => {
|
|
103
|
+
k.current !== e.latestBuildId && (k.current = e.latestBuildId, T(!0), M.current?.(e), c === "auto" && (A.current = setTimeout(P, Math.max(0, l))));
|
|
104
|
+
}, [
|
|
105
|
+
P,
|
|
106
|
+
l,
|
|
107
|
+
c
|
|
108
|
+
]), I = e(async () => {
|
|
109
|
+
if (!u) return;
|
|
110
|
+
j.current?.abort();
|
|
111
|
+
let e = new AbortController();
|
|
112
|
+
j.current = e;
|
|
113
|
+
try {
|
|
114
|
+
let t = await h(a, e.signal);
|
|
115
|
+
C(t), D(null);
|
|
116
|
+
let n = O.current;
|
|
117
|
+
if (!n) {
|
|
118
|
+
O.current = t.buildId, x(t.buildId);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
g(n, t.buildId) && F({
|
|
122
|
+
currentBuildId: n,
|
|
123
|
+
latestBuildId: t.buildId,
|
|
124
|
+
latestBuildInfo: t
|
|
125
|
+
});
|
|
126
|
+
} catch (e) {
|
|
127
|
+
if (e instanceof DOMException && e.name === "AbortError") return;
|
|
128
|
+
let t = e instanceof Error ? e : /* @__PURE__ */ Error("Build reload check failed.");
|
|
129
|
+
D(t), N.current?.(t);
|
|
130
|
+
}
|
|
131
|
+
}, [
|
|
132
|
+
u,
|
|
133
|
+
F,
|
|
134
|
+
a
|
|
135
|
+
]);
|
|
136
|
+
t(() => {
|
|
137
|
+
if (!u) return;
|
|
138
|
+
I();
|
|
139
|
+
let e = window.setInterval(() => {
|
|
140
|
+
I();
|
|
141
|
+
}, Math.max(1e3, s));
|
|
142
|
+
return () => {
|
|
143
|
+
window.clearInterval(e), j.current?.abort(), A.current && clearTimeout(A.current);
|
|
144
|
+
};
|
|
145
|
+
}, [
|
|
146
|
+
s,
|
|
147
|
+
I,
|
|
148
|
+
u
|
|
149
|
+
]);
|
|
150
|
+
let L = e(() => {
|
|
151
|
+
T(!1);
|
|
152
|
+
}, []);
|
|
153
|
+
return {
|
|
154
|
+
isNewBuildAvailable: w,
|
|
155
|
+
currentBuildId: m,
|
|
156
|
+
latestBuildId: S?.buildId ?? null,
|
|
157
|
+
latestBuildInfo: S,
|
|
158
|
+
error: E,
|
|
159
|
+
reloadApp: P,
|
|
160
|
+
checkNow: I,
|
|
161
|
+
dismissPrompt: L
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/components/BuildReloadWatcher.tsx
|
|
166
|
+
var S = {
|
|
167
|
+
position: "fixed",
|
|
168
|
+
left: "16px",
|
|
169
|
+
right: "16px",
|
|
170
|
+
zIndex: 2147483647,
|
|
171
|
+
display: "flex",
|
|
172
|
+
alignItems: "center",
|
|
173
|
+
justifyContent: "space-between",
|
|
174
|
+
gap: "12px",
|
|
175
|
+
padding: "12px 14px",
|
|
176
|
+
borderRadius: "8px",
|
|
177
|
+
background: "#111827",
|
|
178
|
+
color: "#ffffff",
|
|
179
|
+
boxShadow: "0 12px 28px rgba(17, 24, 39, 0.24)",
|
|
180
|
+
fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif",
|
|
181
|
+
fontSize: "14px",
|
|
182
|
+
lineHeight: 1.4
|
|
183
|
+
}, C = {
|
|
184
|
+
display: "flex",
|
|
185
|
+
alignItems: "center",
|
|
186
|
+
gap: "8px",
|
|
187
|
+
flexShrink: 0
|
|
188
|
+
}, w = {
|
|
189
|
+
border: 0,
|
|
190
|
+
borderRadius: "6px",
|
|
191
|
+
padding: "7px 10px",
|
|
192
|
+
cursor: "pointer",
|
|
193
|
+
font: "inherit",
|
|
194
|
+
fontWeight: 600
|
|
195
|
+
}, T = {
|
|
196
|
+
...w,
|
|
197
|
+
background: "#ffffff",
|
|
198
|
+
color: "#111827"
|
|
199
|
+
}, E = {
|
|
200
|
+
...w,
|
|
201
|
+
background: "transparent",
|
|
202
|
+
color: "#d1d5db"
|
|
203
|
+
};
|
|
204
|
+
function D({ promptMessage: e = "A new version is available. Refresh to update.", refreshButtonLabel: n = "Refresh", dismissButtonLabel: r = "Dismiss", promptPosition: o = "bottom", showDismissButton: s = !0, reloadOnChunkError: c = !0, ...l }) {
|
|
205
|
+
let { isNewBuildAvailable: d, reloadApp: f, dismissPrompt: p } = x(l);
|
|
206
|
+
if (t(() => {
|
|
207
|
+
if (c) return u(f, l.onError);
|
|
208
|
+
}, [
|
|
209
|
+
f,
|
|
210
|
+
c,
|
|
211
|
+
l.onError
|
|
212
|
+
]), l.reloadMode === "manual" || !d) return null;
|
|
213
|
+
let m = o === "top" ? { top: "16px" } : { bottom: "16px" };
|
|
214
|
+
return /* @__PURE__ */ a("div", {
|
|
215
|
+
role: "status",
|
|
216
|
+
"aria-live": "polite",
|
|
217
|
+
style: {
|
|
218
|
+
...S,
|
|
219
|
+
...m
|
|
220
|
+
},
|
|
221
|
+
children: [/* @__PURE__ */ i("span", { children: e }), /* @__PURE__ */ a("div", {
|
|
222
|
+
style: C,
|
|
223
|
+
children: [s ? /* @__PURE__ */ i("button", {
|
|
224
|
+
type: "button",
|
|
225
|
+
style: E,
|
|
226
|
+
onClick: p,
|
|
227
|
+
children: r
|
|
228
|
+
}) : null, /* @__PURE__ */ i("button", {
|
|
229
|
+
type: "button",
|
|
230
|
+
style: T,
|
|
231
|
+
onClick: f,
|
|
232
|
+
children: n
|
|
233
|
+
})]
|
|
234
|
+
})]
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
export { D as BuildReloadWatcher, f as DEFAULT_VERSION_URL, p as createCacheSafeUrl, h as fetchBuildInfo, g as hasBuildChanged, u as installChunkErrorReload, l as isChunkLoadError, x as useBuildReload };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type ReloadMode = "prompt" | "auto" | "manual";
|
|
3
|
+
export type PromptPosition = "top" | "bottom";
|
|
4
|
+
export interface BuildInfo {
|
|
5
|
+
buildId: string;
|
|
6
|
+
version?: string;
|
|
7
|
+
builtAt?: string;
|
|
8
|
+
environment?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface NewBuildPayload {
|
|
12
|
+
currentBuildId: string;
|
|
13
|
+
latestBuildId: string;
|
|
14
|
+
latestBuildInfo: BuildInfo;
|
|
15
|
+
}
|
|
16
|
+
export interface BuildReloadConfig {
|
|
17
|
+
versionUrl?: string;
|
|
18
|
+
currentBuildId?: string;
|
|
19
|
+
checkInterval?: number;
|
|
20
|
+
reloadMode?: ReloadMode;
|
|
21
|
+
reloadDelay?: number;
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
reloadOnChunkError?: boolean;
|
|
24
|
+
onNewBuild?: (payload: NewBuildPayload) => void;
|
|
25
|
+
onError?: (error: Error) => void;
|
|
26
|
+
}
|
|
27
|
+
export interface UseBuildReloadOptions extends BuildReloadConfig {
|
|
28
|
+
}
|
|
29
|
+
export interface UseBuildReloadResult {
|
|
30
|
+
isNewBuildAvailable: boolean;
|
|
31
|
+
currentBuildId: string | null;
|
|
32
|
+
latestBuildId: string | null;
|
|
33
|
+
latestBuildInfo: BuildInfo | null;
|
|
34
|
+
error: Error | null;
|
|
35
|
+
reloadApp: () => void;
|
|
36
|
+
checkNow: () => Promise<void>;
|
|
37
|
+
dismissPrompt: () => void;
|
|
38
|
+
}
|
|
39
|
+
export interface BuildReloadWatcherProps extends BuildReloadConfig {
|
|
40
|
+
promptMessage?: ReactNode;
|
|
41
|
+
refreshButtonLabel?: string;
|
|
42
|
+
dismissButtonLabel?: string;
|
|
43
|
+
promptPosition?: PromptPosition;
|
|
44
|
+
showDismissButton?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare class BuildReloadError extends Error {
|
|
47
|
+
constructor(message: string);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEtD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,CAAC;AAE9C,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;CAAG;AAEnE,MAAM,WAAW,oBAAoB;IACnC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,SAAS,GAAG,IAAI,CAAC;IAClC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAwB,SAAQ,iBAAiB;IAChE,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function isChunkLoadError(error: unknown): boolean;
|
|
2
|
+
export declare function clearChunkReloadMarker(): void;
|
|
3
|
+
export declare function installChunkErrorReload(reloadApp: () => void, onError?: (error: Error) => void): () => void;
|
|
4
|
+
//# sourceMappingURL=chunkErrors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunkErrors.d.ts","sourceRoot":"","sources":["../../src/utils/chunkErrors.ts"],"names":[],"mappings":"AA+BA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGxD;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAM7C;AAED,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,IAAI,EACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAC/B,MAAM,IAAI,CAyCZ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compareBuildId.d.ts","sourceRoot":"","sources":["../../src/utils/compareBuildId.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC,OAAO,CAMT"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type BuildInfo } from "../types";
|
|
2
|
+
export declare const DEFAULT_VERSION_URL = "/build-version.json";
|
|
3
|
+
export declare const CACHE_BUST_PARAM = "t";
|
|
4
|
+
export declare function createCacheSafeUrl(versionUrl: string, now?: number): string;
|
|
5
|
+
export declare function fetchBuildInfo(versionUrl?: string, signal?: AbortSignal): Promise<BuildInfo>;
|
|
6
|
+
//# sourceMappingURL=fetchBuildInfo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetchBuildInfo.d.ts","sourceRoot":"","sources":["../../src/utils/fetchBuildInfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAE5D,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AACzD,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,MAAM,CAa/E;AAYD,wBAAsB,cAAc,CAClC,UAAU,SAAsB,EAChC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,SAAS,CAAC,CAmCpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reloadPage.d.ts","sourceRoot":"","sources":["../../src/utils/reloadPage.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,IAAI,IAAI,CAMjC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## `BuildReloadWatcher`
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
<BuildReloadWatcher />
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Component that starts polling, handles chunk load errors, and renders the default prompt UI when needed.
|
|
10
|
+
|
|
11
|
+
### Props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Default | Description |
|
|
14
|
+
| --- | --- | --- | --- |
|
|
15
|
+
| `versionUrl` | `string` | `"/build-version.json"` | URL that returns the latest build info. |
|
|
16
|
+
| `currentBuildId` | `string` | first fetched `buildId` | Current running app build ID. |
|
|
17
|
+
| `checkInterval` | `number` | `60000` | Poll interval in milliseconds. Minimum runtime interval is clamped to 1000 ms. |
|
|
18
|
+
| `reloadMode` | `"prompt" \| "auto" \| "manual"` | `"prompt"` | What happens when a new build is detected. |
|
|
19
|
+
| `reloadDelay` | `number` | `0` | Delay before automatic reload in `auto` mode. |
|
|
20
|
+
| `enabled` | `boolean` | `true` | Enables or disables polling. |
|
|
21
|
+
| `reloadOnChunkError` | `boolean` | `true` | Reload once when common dynamic import/chunk failures occur. |
|
|
22
|
+
| `onNewBuild` | `(payload) => void` | `undefined` | Called once when a new build is detected. |
|
|
23
|
+
| `onError` | `(error) => void` | `undefined` | Called when fetching or parsing build info fails. |
|
|
24
|
+
| `promptMessage` | `ReactNode` | default message | Prompt text or node. |
|
|
25
|
+
| `refreshButtonLabel` | `string` | `"Refresh"` | Refresh button label. |
|
|
26
|
+
| `dismissButtonLabel` | `string` | `"Dismiss"` | Dismiss button label. |
|
|
27
|
+
| `promptPosition` | `"top" \| "bottom"` | `"bottom"` | Default prompt position. |
|
|
28
|
+
| `showDismissButton` | `boolean` | `true` | Whether the prompt includes a dismiss button. |
|
|
29
|
+
|
|
30
|
+
## `useBuildReload`
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
const state = useBuildReload(options);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Hook for custom UI or manual integration.
|
|
37
|
+
|
|
38
|
+
### Return Value
|
|
39
|
+
|
|
40
|
+
| Field | Type | Description |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `isNewBuildAvailable` | `boolean` | True after a different latest `buildId` is detected. |
|
|
43
|
+
| `currentBuildId` | `string \| null` | Running app build ID. |
|
|
44
|
+
| `latestBuildId` | `string \| null` | Latest fetched build ID. |
|
|
45
|
+
| `latestBuildInfo` | `BuildInfo \| null` | Full latest response object. |
|
|
46
|
+
| `error` | `Error \| null` | Last non-abort error from version checking. |
|
|
47
|
+
| `reloadApp` | `() => void` | Reloads the current page. |
|
|
48
|
+
| `checkNow` | `() => Promise<void>` | Runs an immediate version check. |
|
|
49
|
+
| `dismissPrompt` | `() => void` | Clears the prompt state locally. |
|
|
50
|
+
|
|
51
|
+
## Types
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
type ReloadMode = "prompt" | "auto" | "manual";
|
|
55
|
+
|
|
56
|
+
interface BuildInfo {
|
|
57
|
+
buildId: string;
|
|
58
|
+
version?: string;
|
|
59
|
+
builtAt?: string;
|
|
60
|
+
environment?: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface NewBuildPayload {
|
|
65
|
+
currentBuildId: string;
|
|
66
|
+
latestBuildId: string;
|
|
67
|
+
latestBuildInfo: BuildInfo;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## CLI
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
react-build-reload generate [options]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Creates a JSON build metadata file for the runtime watcher.
|
|
78
|
+
|
|
79
|
+
| Option | Default | Description |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| `--out <path>` | `public/build-version.json` | Output file path. Parent folders are created automatically. |
|
|
82
|
+
| `--build-id <id>` | env/git/timestamp | Build ID to write. |
|
|
83
|
+
| `--version <version>` | app `package.json` version or `0.0.0` | Version value to write. |
|
|
84
|
+
| `--environment <name>` | `NODE_ENV` or `production` | Environment value to write. |
|
|
85
|
+
| `--no-git` | disabled | Skips git metadata lookup. |
|
|
86
|
+
|
|
87
|
+
Generated files include `buildId`, `version`, `builtAt`, `environment`, and `name` when available. Git metadata is included when git is available and `--no-git` is not used.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
`react-build-reload` watches a build version endpoint and detects when the running app is older than the deployed app.
|
|
4
|
+
|
|
5
|
+
## 1. Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install react-build-reload
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 2. Generate a Version File
|
|
12
|
+
|
|
13
|
+
Run this in your app before building:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx react-build-reload generate
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This creates:
|
|
20
|
+
|
|
21
|
+
```txt
|
|
22
|
+
public/build-version.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Example output:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"buildId": "abc123-20260530123000",
|
|
30
|
+
"version": "1.0.0",
|
|
31
|
+
"builtAt": "2026-05-30T12:30:00.000Z",
|
|
32
|
+
"environment": "production",
|
|
33
|
+
"name": "my-app"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The default URL is:
|
|
38
|
+
|
|
39
|
+
```txt
|
|
40
|
+
/build-version.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The generator uses an explicit build ID, common CI environment variables, git metadata, or a timestamp fallback. The runtime library only requires `buildId`.
|
|
44
|
+
|
|
45
|
+
Add it to your app build scripts:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"scripts": {
|
|
50
|
+
"prebuild": "react-build-reload generate",
|
|
51
|
+
"build": "vite build"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 3. Add the Watcher
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { BuildReloadWatcher } from "react-build-reload";
|
|
60
|
+
|
|
61
|
+
function App() {
|
|
62
|
+
return (
|
|
63
|
+
<>
|
|
64
|
+
<BuildReloadWatcher />
|
|
65
|
+
<MainApp />
|
|
66
|
+
</>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
By default, the watcher checks every 60 seconds and shows a refresh prompt when a new build is detected.
|
|
72
|
+
|
|
73
|
+
## 4. Use Your Own Endpoint
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
<BuildReloadWatcher versionUrl="/api/version" />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The endpoint should return JSON with a non-empty `buildId`.
|
|
80
|
+
|
|
81
|
+
## 5. Disable in Development
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<BuildReloadWatcher enabled={import.meta.env.PROD} />
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This keeps local development from repeatedly checking a file that may not exist.
|
|
88
|
+
|
|
89
|
+
## 6. Generator Options
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
react-build-reload generate --out public/build-version.json
|
|
93
|
+
react-build-reload generate --build-id "$GITHUB_SHA"
|
|
94
|
+
react-build-reload generate --environment staging
|
|
95
|
+
react-build-reload generate --version 2.0.0
|
|
96
|
+
react-build-reload generate --no-git
|
|
97
|
+
```
|
package/docs/roadmap.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
These items are intentionally outside the MVP.
|
|
4
|
+
|
|
5
|
+
## Future Features
|
|
6
|
+
|
|
7
|
+
- Multi-tab synchronization with `BroadcastChannel` or storage events.
|
|
8
|
+
- Custom prompt render prop or component slot.
|
|
9
|
+
- Toast notification integrations.
|
|
10
|
+
- Next.js-specific guidance and compatibility helpers.
|
|
11
|
+
- Service worker update coordination.
|
|
12
|
+
- Vite or Webpack plugin wrappers around the existing generator.
|
|
13
|
+
- Deployment metadata display.
|
|
14
|
+
- Environment-specific behavior.
|
|
15
|
+
- Pause checks while users are typing.
|
|
16
|
+
- Check immediately when a tab becomes active again.
|
|
17
|
+
|
|
18
|
+
## Non-MVP Boundaries
|
|
19
|
+
|
|
20
|
+
The MVP remains focused on React runtime behavior:
|
|
21
|
+
|
|
22
|
+
- Poll a version URL.
|
|
23
|
+
- Compare build IDs.
|
|
24
|
+
- Prompt, reload, or call a callback.
|
|
25
|
+
- Recover once from chunk load failures.
|
|
26
|
+
- Avoid breaking the app when version checks fail.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Usage Examples
|
|
2
|
+
|
|
3
|
+
## Generate Before Vite Build
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"scripts": {
|
|
8
|
+
"prebuild": "react-build-reload generate",
|
|
9
|
+
"build": "vite build"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Vite copies `public/build-version.json` to `/build-version.json` during build.
|
|
15
|
+
|
|
16
|
+
## Generate with CI Build ID
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
react-build-reload generate --build-id "$GITHUB_SHA" --environment production
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Prompt Mode
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
<BuildReloadWatcher reloadMode="prompt" />
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Shows a small refresh prompt when a new build is detected. This is the default and safest mode for user-facing apps.
|
|
29
|
+
|
|
30
|
+
## Auto Reload
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
<BuildReloadWatcher reloadMode="auto" reloadDelay={3000} />
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Reloads after a new build is detected. Use this for dashboards, internal tools, or monitoring screens where showing the latest version is more important than preserving unsaved input.
|
|
37
|
+
|
|
38
|
+
## Manual Callback
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<BuildReloadWatcher
|
|
42
|
+
reloadMode="manual"
|
|
43
|
+
onNewBuild={({ latestBuildId }) => {
|
|
44
|
+
showToast(`New build available: ${latestBuildId}`);
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use this when the application already has its own modal, toast, or unsaved-work flow.
|
|
50
|
+
|
|
51
|
+
## Custom Version URL
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<BuildReloadWatcher versionUrl="/api/version" />
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The endpoint must return JSON with `buildId`.
|
|
58
|
+
|
|
59
|
+
## Disabled in Development
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<BuildReloadWatcher enabled={import.meta.env.PROD} />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This avoids noisy local errors when `/build-version.json` is not available during development.
|
|
66
|
+
|
|
67
|
+
## Hook with Custom UI
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
function ReloadToast() {
|
|
71
|
+
const { isNewBuildAvailable, reloadApp, dismissPrompt } = useBuildReload({
|
|
72
|
+
reloadMode: "manual"
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!isNewBuildAvailable) return null;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div>
|
|
79
|
+
<span>A new version is available.</span>
|
|
80
|
+
<button onClick={dismissPrompt}>Later</button>
|
|
81
|
+
<button onClick={reloadApp}>Refresh</button>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Version File
|
|
2
|
+
|
|
3
|
+
The library needs a URL that returns the latest deployed build ID. You can create this file with the included CLI.
|
|
4
|
+
|
|
5
|
+
## Generate the File
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
react-build-reload generate
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Default output:
|
|
12
|
+
|
|
13
|
+
```txt
|
|
14
|
+
public/build-version.json
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Recommended app setup:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"scripts": {
|
|
22
|
+
"prebuild": "react-build-reload generate",
|
|
23
|
+
"build": "vite build"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Required Shape
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"buildId": "abc123"
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`buildId` must be a non-empty string. It should change on every frontend deployment.
|
|
37
|
+
|
|
38
|
+
## Optional Metadata
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"buildId": "abc123",
|
|
43
|
+
"version": "1.0.0",
|
|
44
|
+
"builtAt": "2026-05-30T12:30:00Z",
|
|
45
|
+
"environment": "production"
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Optional fields are preserved in `latestBuildInfo` and passed to `onNewBuild`.
|
|
50
|
+
|
|
51
|
+
## Cache Safety
|
|
52
|
+
|
|
53
|
+
Every version check adds a timestamp query param:
|
|
54
|
+
|
|
55
|
+
```txt
|
|
56
|
+
/build-version.json?t=123456789
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The request also uses `cache: "no-store"`. This reduces the chance of comparing against stale browser-cached metadata.
|
|
60
|
+
|
|
61
|
+
## Deployment Responsibility
|
|
62
|
+
|
|
63
|
+
The consuming app owns when the file is generated and deployed. The included CLI writes the file, but your app build or CI pipeline must run it before publishing assets.
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-build-reload",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "React library for detecting new frontend builds and reloading safely.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Satyam Shah",
|
|
8
|
+
"url": "https://github.com/satyam-shah-gt"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://react-refresh-website.satyamshah.workers.dev",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/satyam-shah-gt/react-build-reload"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/satyam-shah-gt/react-build-reload/issues"
|
|
17
|
+
},
|
|
18
|
+
"main": "./dist/index.cjs",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"bin": {
|
|
22
|
+
"react-build-reload": "./bin/react-build-reload.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin",
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"docs/getting-started.md",
|
|
29
|
+
"docs/api-reference.md",
|
|
30
|
+
"docs/version-file.md",
|
|
31
|
+
"docs/usage-examples.md",
|
|
32
|
+
"docs/roadmap.md"
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js",
|
|
38
|
+
"require": "./dist/index.cjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"sideEffects": false,
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "vite build && tsc -p tsconfig.build.json",
|
|
44
|
+
"generate:version": "node ./bin/react-build-reload.js generate",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"typecheck": "tsc --noEmit"
|
|
48
|
+
},
|
|
49
|
+
"keywords": [
|
|
50
|
+
"react",
|
|
51
|
+
"reload",
|
|
52
|
+
"deployment",
|
|
53
|
+
"build-version",
|
|
54
|
+
"frontend"
|
|
55
|
+
],
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"react": ">=18.0.0",
|
|
59
|
+
"react-dom": ">=18.0.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
63
|
+
"@testing-library/react": "^16.3.0",
|
|
64
|
+
"@testing-library/user-event": "^14.6.1",
|
|
65
|
+
"@types/node": "^24.12.3",
|
|
66
|
+
"@types/react": "^19.2.14",
|
|
67
|
+
"@types/react-dom": "^19.2.3",
|
|
68
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
69
|
+
"jsdom": "^26.1.0",
|
|
70
|
+
"typescript": "~6.0.2",
|
|
71
|
+
"vite": "^8.0.12",
|
|
72
|
+
"vitest": "^4.1.7"
|
|
73
|
+
}
|
|
74
|
+
}
|