更新前端静态网页获取方式,放弃使用后端获取api

This commit is contained in:
2025-09-09 10:47:51 +08:00
parent 6889ca37e5
commit 44a4f1bae1
25558 changed files with 2463152 additions and 153 deletions

View File

@@ -0,0 +1,328 @@
## 0.5.16 (31 Mar 2025)
### Fixes
- Fixed out of order cleanup when using top-level await (#898)
## 0.5.15 (3 Jun 2024)
### Fixes
- Fixed wrong import in error overlay for `ansi-html` (#853)
## 0.5.14 (1 Jun 2024)
### Fixes
- Moved to `ansi-html` `v0.0.9` and `schema-utils` `v4.x` (#848)
### Internal
- Run tests on latest versions of Node.js 18, 20 and 22 (#848)
- Bumped `jest` to v29 and some other development dependencies (#848)
- Removed `yalc` (#849)
## 0.5.13 (28 Apr 2024)
### Fixes
- Fixed module system inferring (ESM vs CJS) to start from the point of each file (#771)
## 0.5.12 (27 Apr 2024)
### Fixes
- Fixed incorrect `sockProtocol` override (#835)
- Relaxed peer dependency requirement on `webpack-dev-server` to allow v5.x (#837)
## 0.5.11 (15 Aug 2023)
### Features
- Added support to exclude dynamically generated modules from other loaders (#769)
### Fixes
- Fixed unnecessary memory leaks due to `prevExports` (#766)
- Relaxed peer dependency requirement on `type-fest` to allow v4.x (#767)
- Fixed module type resolution when there is difference across contexts (#768)
## 0.5.10 (24 Nov 2022)
### Fixes
- Bumped `loader-utils` to fix security vulnerability (#700)
## 0.5.9 (10 Nov 2022)
### Fixes
- Bumped `loader-utils` to fix security vulnerability (#685)
## 0.5.8 (9 Oct 2022)
### Fixes
- Fixed performance issue regarding `require.resolve` in loader injection (#669)
- Bumped `core-js-pure` to not depend on deprecated versions (#674)
## 0.5.7 (23 May 2022)
### Fixes
- Removed debug `console.log` statement (#631)
### Internal
- Run tests on Node.js 18 (#631)
## 0.5.6 (10 May 2022)
### Fixes
- Fixed faulty `this` type import in loader (#624)
- Made current script detection more robust for edge cases (#630)
### Internal
- Swapped to new `ReactDOM.createRoot` API in examples (#626)
## 0.5.5 (4 April 2022)
### Fixes
- Handle unknown `moduleId` for dynamically generated modules (#547)
- Handle WDS `auto` value on `port` (#574)
- Fixed `react-refresh@0.12.0` compatibility (#576)
- Fixed crash when parsing compile errors in overlay (#577)
- Respect virtual modules when injecting loader (#593)
- Allow `port` to be missing for WDS, also some general refactoring (#623)
### Internal
- A couple documentation changes in README (#575, 8c39623, #597)
- Bumped dependencies for testing infrastructure (#526, #564, #567, #581, #588, #591, #594, #616)
## 0.5.4 (22 December 2021)
### Fixes
- Skip loader injection for files referenced as assets (#545)
- Changed failures of `exports` capturing to warn instead of throw (#546)
## 0.5.3 (28 November 2021)
### Fixes
- Updated overlay for unsafe area in Safari (#528)
- Fixed performance in large projects due to memory leak in loader (#537)
## 0.5.2 (19 November 2021)
### Features
- Added support for WDS v4 `client.webSocketURL` (#529)
### Fixes
- Fixed lost module context due to interceptor by always using regular functions (#531)
- Relaxed peer dependency requirement on `react-refresh` (#534)
## 0.5.1 (15 September 2021)
### Fixes
- Relaxed peer dependency requirement on `type-fest` to allow v2.x (#507, #508)
### Internal
- Fixed typos in README (#509)
## 0.5.0 (14 September 2021)
### BREAKING
- While most of the public API did not change,
we've re-written a large chunk of the runtime code to support a wider range of use cases.
This is likely to provide more stability, but if `0.4.x` works in your setup but `0.5.x` doesn't,
please file us an issue - we would love to address it!
- The `disableRefreshCheck` option have been removed (#285).
It has long been effect-less and deprecated since `0.3.x`.
- The `overlay.useLegacyWDSSockets` have been removed (#498).
It is aimed to support WDS below `3.6.0` (published in June 2019),
but looking at current usage and download stats,
we've decided it is best to drop support for the old socket format moving forward.
- Handling of port `0` have been removed (#337).
- `html-entities` have been bumped to `2.x` (#321).
- `react-refresh` have been bumped to `0.10.0` (#353).
### Features
- Added WDS v4 support with new socket defaults through Webpack config (#241, #286, #392, #413, #479)
- Added the `overlay.sockProtocol` option (#242)
- Added monorepo compatibility via the the `library` option (#273)
- Rewritten URL handling using WHATWG `URL` APIs with automatic pony-filling (#278, #332, #378)
- Rewritten Webpack 5 compatibility using new APIs and hooks (#319, #372, #434, #483)
- Rewritten refresh runtime to be fully module system aware (#337, #461, #482, #492)
- Rewritten Webpack 4 and 5 checks using feature detection on compiler (#415)
- Added support for `experiments.topLevelAwait` (#435, #447, #493)
- Added retry logic when socket initialisation fails (#446)
### Fixes
- Relaxed peer dependency requirement on `type-fest` (#257, c02018a, #484)
- Relaxed requirement on the `overlay` option to accept relative paths (#284)
- Patched unstable initialisation of global scope across module boundaries (#290, #369, #464, #505)
- Patched quote escaping in injected runtime code (#306)
- Invalidate updates outside of Refresh boundary for consistency (#307)
- Properly throw when an ambiguous entrypoint is received while using Webpack 4 (#320)
- Fixed overlay script source detection for WDS when no `src` is found (#331)
- Fixed possible Stack Overflow through self-referencing (#370, #380)
- Relaxed errors on HMR not found to not crash JS parsing (#371)
- Ensure overlay code won't run if disabled (#374)
- Relaxed peer dependency requirement on `@types/webpack` (#414)
- Fixed compiler error overlay crashes when messages are empty (#462)
- Swapped `ansi-html` to `ansi-html-community` to fix ReDoS vulnerability (#501)
### Internal
- More stable testing infrastructure (#234)
- Run tests by default on Webpack 5 (#440)
- Rewrite documentation and fix outstanding issues (#283, #291, #311, #376, #480, #497, #499)
- Added documentation on community plugins: `react-refresh-typescript` and `swc` (#248, fbe1f27, #450)
## 0.4.3 (2 November 2020)
### Fixes
- Fixed Yarn 2 PnP compatibility with absolute `react-refresh/runtime` imports (#230)
- Fixed Webpack 5 compatibility by requiring `__webpack_require__` (#233)
- Fixed IE 11 compatibility in socket code (4033e6af)
- Relaxed peer dependency requirement for `react-refresh` to allow `0.9.x` (747c19ba)
## 0.4.2 (3 September 2020)
### Fixes
- Patched loader to use with Node.js global `fetch` polyfills (#193)
- Patched default `include` and `exclude` options to be case-insensitive (#194)
## 0.4.1 (28 July 2020)
### Fixes
- Fixed accidental use of testing alias `webpack.next` in published plugin code (#167)
## 0.4.0 (28 July 2020)
### BREAKING
- Minimum required Node.js version have been bumped to 10 as Node.js 8 is EOL now.
- Minimum required Webpack version is now `v4.43.0` or later as we adopted the new `module.hot.invalidate` API (#89).
The new API enabled us to bail out of the HMR loop less frequently and provide a better experience.
If you really cannot upgrade, you can stay on `0.3.3` for the time being.
- While most of our public API did not change, this release is closer to a rewrite than a refactor.
A lot of files have moved to provide easier access for advanced users and frameworks (#122).
You can check the difference in the PR to see what have moved and where they are now.
- The `useLegacyWDSSockets` option is now scoped under the `overlay` option (#153).
### Features
- Adopted the `module.hot.invalidate()` API, which means we will now bail out less often (#89)
- Attach runtime on Webpack's global scope instead of `window`, making the plugin platform-agnostic (#102)
- Added stable support for **Webpack 5** and beta support for **Module Federation** (#123, #132, #164)
- Socket integration URL detection via `document.currentScript` (#133)
- Relaxed requirements for "required" `overlay` options to receive `false` as value (#154)
- Prefixed all errors thrown by the plugin (#161)
- Eliminated use of soon-to-be-deprecated `lodash.debounce` package (#163)
### Fixes
- Fixed circular references for `__react_refresh_error_overlay__` and `__react_refresh_utils` (#116)
- Fixed IE11 compatibility (#106, #121)
- Rearranged directories to provide more ergonomic imports (#122)
- Fixed issues with Babel/ESLint/Flow regarding loader ordering and runtime cleanup (#129, #140)
- Correctly detecting the HMR plugin (#130, #160)
- Fixed unwanted injection of error overlay in non-browser environment (#146)
- Scoped the `useLegacyWDSSockets` options under `overlay` to reflect its true use (#153)
- Fixed non-preserved relative ordering of Webpack entries (#165)
### Internal
- Full HMR test suite - we are confident the plugin works! (#93, #96)
- Unit tests for all plugin-related Node.js code (#127)
## 0.3.3 (29 May 2020)
### Fixes
- Removed unrecoverable React errors check and its corresponding bail out logic on hot dispose (#104)
## 0.3.2 (22 May 2020)
### Fixes
- Fixed error in overlay when stack trace is unavailable (#91)
- Fixed IE11 compatibility (#98)
## 0.3.1 (11 May 2020)
### Fixes
- Relaxed peer dependency requirements for `webpack-plugin-serve`
## 0.3.0 (10 May 2020)
### BREAKING
- Deprecated the `disableRefreshCheck` flag (#60)
### Features
- Added custom error overlay support (#44)
- Added example project to use TypeScript without usual Babel settings (#46)
- Added custom socket parameters for WDS (#52)
- Added TypeScript definition files (#65)
- Added stricter options validation rules (#62)
- Added option to configure socket runtime to support more hot integrations (#64)
- Added support for `webpack-plugin-serve` (#74)
### Fixes
- Fixed non-dismissible overlay for build warnings (#57)
- Fixed electron compatibility (#58)
- Fixed optional peer dependencies to be truly optional (#59)
- Fixed compatibility issues caused by `node-url` (#61)
- Removed check for `react` import for compatibility (#69)
## 0.2.0 (2 March 2020)
### Features
- Added `webpack-hot-middleware` support (#23)
### Fixes
- Fixed dependency on a global `this` variable to better support web workers (#29)
## 0.1.3 (19 December 2019)
### Fixes
- Fixed runtime template injection when the `runtimeChunks` optimization is used in Webpack (#26)
## 0.1.2 (18 December 2019)
### Fixes
- Fixed caching of Webpack loader to significantly improve performance (#22)
## 0.1.1 (13 December 2019)
### Fixes
- Fixed usage of WDS SockJS fallback (#17)
## 0.1.0 (7 December 2019)
- Initial public release

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Michael Mok
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,348 @@
# React Refresh Webpack Plugin
[actions]: https://github.com/pmmmwh/react-refresh-webpack-plugin/actions/workflows/ci.yml
[actions:badge]: https://img.shields.io/github/actions/workflow/status/pmmmwh/react-refresh-webpack-plugin/ci.yml?branch=main
[license:badge]: https://img.shields.io/github/license/pmmmwh/react-refresh-webpack-plugin
[npm:latest]: https://www.npmjs.com/package/@pmmmwh/react-refresh-webpack-plugin/v/latest
[npm:latest:badge]: https://img.shields.io/npm/v/@pmmmwh/react-refresh-webpack-plugin/latest
[npm:next]: https://www.npmjs.com/package/@pmmmwh/react-refresh-webpack-plugin/v/next
[npm:next:badge]: https://img.shields.io/npm/v/@pmmmwh/react-refresh-webpack-plugin/next
[![GitHub Actions][actions:badge]][actions]
[![License][license:badge]](./LICENSE)
[![Latest Version][npm:latest:badge]][npm:latest]
[![Next Version][npm:next:badge]][npm:next]
An **EXPERIMENTAL** Webpack plugin to enable "Fast Refresh" (also known as _Hot Reloading_) for React components.
> This plugin is not 100% stable.
> We're hoping to land a v1 release soon - please help us by reporting any issues you've encountered!
## Getting Started
### Prerequisites
Ensure that you are using at least the minimum supported versions of this plugin's peer dependencies -
older versions unfortunately do not contain code to orchestrate "Fast Refresh",
and thus cannot be made compatible.
We recommend using the following versions:
| Dependency | Version |
| --------------- | ---------------------------- |
| `react` | `16.13.0`+, `17.x` or `18.x` |
| `react-dom` | `16.13.0`+, `17.x` or `18.x` |
| `react-refresh` | `0.10.0`+ |
| `webpack` | `4.46.0`+ or `5.2.0`+ |
<details>
<summary>Minimum requirements</summary>
<br />
| Dependency | Version |
| --------------- | -------- |
| `react` | `16.9.0` |
| `react-dom` | `16.9.0` |
| `react-refresh` | `0.10.0` |
| `webpack` | `4.43.0` |
</details>
<details>
<summary>Using custom renderers (e.g. <code>react-three-fiber</code>, <code>react-pdf</code>, <code>ink</code>)</summary>
<br />
To ensure full support of "Fast Refresh" with components rendered by custom renderers,
you should ensure the renderer you're using depends on a recent version of `react-reconciler`.
We recommend version `0.25.0` or above, but any versions above `0.22.0` should work.
If the renderer is not compatible, please file them an issue instead.
</details>
### Installation
With all prerequisites met, you can install this plugin using your package manager of choice:
```sh
# if you prefer npm
npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
# if you prefer yarn
yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
# if you prefer pnpm
pnpm add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
```
The `react-refresh` package (from the React team) is a required peer dependency of this plugin.
We recommend using version `0.10.0` or above.
<details>
<summary>Support for TypeScript</summary>
<br />
TypeScript support is available out-of-the-box for those who use `webpack.config.ts`.
Our exported types however depends on `type-fest`, so you'll have to add it as a `devDependency`:
```sh
# if you prefer npm
npm install -D type-fest
# if you prefer yarn
yarn add -D type-fest
# if you prefer pnpm
pnpm add -D type-fest
```
> **:memo: Note**:
>
> `type-fest@4.x` only supports Node.js v16 or above,
> `type-fest@3.x` only supports Node.js v14.16 or above,
> and `type-fest@2.x` only supports Node.js v12.20 or above.
> If you're using an older version of Node.js, please install `type-fest@1.x`.
</details>
### Usage
For most setups, we recommend integrating with `babel-loader`.
It covers the most use cases and is officially supported by the React team.
The example below will assume you're using `webpack-dev-server`.
If you haven't done so, set up your development Webpack configuration for Hot Module Replacement (HMR).
```js
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
devServer: {
hot: true,
},
};
```
<details>
<summary>Using <code>webpack-hot-middleware</code></summary>
<br />
```js
const webpack = require('webpack');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
plugins: [isDevelopment && new webpack.HotModuleReplacementPlugin()].filter(Boolean),
};
```
</details>
<details>
<summary>Using <code>webpack-plugin-serve</code></summary>
<br />
```js
const { WebpackPluginServe } = require('webpack-plugin-serve');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
plugins: [isDevelopment && new WebpackPluginServe()].filter(Boolean),
};
```
</details>
Then, add the `react-refresh/babel` plugin to your Babel configuration and this plugin to your Webpack configuration.
```js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
plugins: [isDevelopment && require.resolve('react-refresh/babel')].filter(Boolean),
},
},
],
},
],
},
plugins: [isDevelopment && new ReactRefreshWebpackPlugin()].filter(Boolean),
};
```
> **:memo: Note**:
>
> Ensure both the Babel transform (`react-refresh/babel`) and this plugin are enabled only in `development` mode!
<details>
<summary>Using <code>ts-loader</code></summary>
<br />
> **:warning: Warning**:
> This is an un-official integration maintained by the community.
Install [`react-refresh-typescript`](https://github.com/Jack-Works/react-refresh-transformer/tree/main/typescript).
Ensure your TypeScript version is at least 4.0.
```sh
# if you prefer npm
npm install -D react-refresh-typescript
# if you prefer yarn
yarn add -D react-refresh-typescript
# if you prefer pnpm
pnpm add -D react-refresh-typescript
```
Then, instead of wiring up `react-refresh/babel` via `babel-loader`,
you can wire-up `react-refresh-typescript` with `ts-loader`:
```js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const ReactRefreshTypeScript = require('react-refresh-typescript');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('ts-loader'),
options: {
getCustomTransformers: () => ({
before: [isDevelopment && ReactRefreshTypeScript()].filter(Boolean),
}),
transpileOnly: isDevelopment,
},
},
],
},
],
},
plugins: [isDevelopment && new ReactRefreshWebpackPlugin()].filter(Boolean),
};
```
> It is recommended to run `ts-loader` with `transpileOnly` is set to `true`.
> You can use `ForkTsCheckerWebpackPlugin` as an alternative if you need typechecking during development.
</details>
<details>
<summary>Using <code>swc-loader</code></summary>
<br />
> **:warning: Warning**:
> This is an un-official integration maintained by the community.
Ensure your `@swc/core` version is at least `1.2.86`.
It is also recommended to use `swc-loader` version `0.1.13` or above.
Then, instead of wiring up `react-refresh/babel` via `babel-loader`,
you can wire-up `swc-loader` and use the `refresh` transform:
```js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('swc-loader'),
options: {
jsc: {
transform: {
react: {
development: isDevelopment,
refresh: isDevelopment,
},
},
},
},
},
],
},
],
},
plugins: [isDevelopment && new ReactRefreshWebpackPlugin()].filter(Boolean),
};
```
> Starting from version `0.1.13`, `swc-loader` will set the `development` option based on Webpack's `mode` option.
> `swc` won't enable fast refresh when `development` is `false`.
</details>
For more information on how to set up "Fast Refresh" with different integrations,
please check out [our examples](examples).
### Overlay Integration
This plugin integrates with the most common Webpack HMR solutions to surface errors during development -
in the form of an error overlay.
By default, `webpack-dev-server` is used,
but you can set the [`overlay.sockIntegration`](docs/API.md#sockintegration) option to match what you're using.
The supported versions are as follows:
| Dependency | Version |
| ------------------------ | -------------------------- |
| `webpack-dev-server` | `3.6.0`+ or `4.x` or `5.x` |
| `webpack-hot-middleware` | `2.x` |
| `webpack-plugin-serve` | `0.x` or `1.x` |
## API
Please refer to [the API docs](docs/API.md) for all available options.
## FAQs and Troubleshooting
Please refer to [the Troubleshooting guide](docs/TROUBLESHOOTING.md) for FAQs and resolutions to common issues.
## License
This project is licensed under the terms of the [MIT License](/LICENSE).
## Special Thanks
<a href="https://jb.gg/OpenSource?from=ReactRefreshWebpackPlugin" target="_blank">
<img
alt="JetBrains Logo"
src="https://user-images.githubusercontent.com/9338255/132110580-61d3dba5-f5c7-4479-bd8e-39cd65b42fc5.png"
width="120"
/>
</a>

View File

@@ -0,0 +1,100 @@
/* global __react_refresh_error_overlay__, __react_refresh_socket__, __resourceQuery */
const events = require('./utils/errorEventHandlers.js');
const formatWebpackErrors = require('./utils/formatWebpackErrors.js');
const runWithPatchedUrl = require('./utils/patchUrl.js');
const runWithRetry = require('./utils/retry.js');
// Setup error states
let isHotReload = false;
let hasRuntimeErrors = false;
/**
* Try dismissing the compile error overlay.
* This will also reset runtime error records (if any),
* because we have new source to evaluate.
* @returns {void}
*/
function tryDismissErrorOverlay() {
__react_refresh_error_overlay__.clearCompileError();
__react_refresh_error_overlay__.clearRuntimeErrors(!hasRuntimeErrors);
hasRuntimeErrors = false;
}
/**
* A function called after a compile success signal is received from Webpack.
* @returns {void}
*/
function handleCompileSuccess() {
isHotReload = true;
if (isHotReload) {
tryDismissErrorOverlay();
}
}
/**
* A function called after a compile errored signal is received from Webpack.
* @param {string[]} errors
* @returns {void}
*/
function handleCompileErrors(errors) {
isHotReload = true;
const formattedErrors = formatWebpackErrors(errors);
// Only show the first error
__react_refresh_error_overlay__.showCompileError(formattedErrors[0]);
}
/**
* Handles compilation messages from Webpack.
* Integrates with a compile error overlay.
* @param {*} message A Webpack HMR message sent via WebSockets.
* @returns {void}
*/
function compileMessageHandler(message) {
switch (message.type) {
case 'ok':
case 'still-ok':
case 'warnings': {
// TODO: Implement handling for warnings
handleCompileSuccess();
break;
}
case 'errors': {
handleCompileErrors(message.data);
break;
}
default: {
// Do nothing.
}
}
}
if (process.env.NODE_ENV !== 'production') {
if (typeof window !== 'undefined') {
runWithPatchedUrl(function setupOverlay() {
// Only register if no other overlay have been registered
if (!window.__reactRefreshOverlayInjected && __react_refresh_socket__) {
// Registers handlers for compile errors with retry -
// This is to prevent mismatching injection order causing errors to be thrown
runWithRetry(function initSocket() {
__react_refresh_socket__.init(compileMessageHandler, __resourceQuery);
}, 3);
// Registers handlers for runtime errors
events.handleError(function handleError(error) {
hasRuntimeErrors = true;
__react_refresh_error_overlay__.handleRuntimeError(error);
});
events.handleUnhandledRejection(function handleUnhandledPromiseRejection(error) {
hasRuntimeErrors = true;
__react_refresh_error_overlay__.handleRuntimeError(error);
});
// Mark overlay as injected to prevent double-injection
window.__reactRefreshOverlayInjected = true;
}
});
}
}

View File

@@ -0,0 +1,23 @@
/* global __react_refresh_library__ */
const safeThis = require('core-js-pure/features/global-this');
const RefreshRuntime = require('react-refresh/runtime');
if (process.env.NODE_ENV !== 'production') {
if (typeof safeThis !== 'undefined') {
var $RefreshInjected$ = '__reactRefreshInjected';
// Namespace the injected flag (if necessary) for monorepo compatibility
if (typeof __react_refresh_library__ !== 'undefined' && __react_refresh_library__) {
$RefreshInjected$ += '_' + __react_refresh_library__;
}
// Only inject the runtime if it hasn't been injected
if (!safeThis[$RefreshInjected$]) {
// Inject refresh runtime into global scope
RefreshRuntime.injectIntoGlobalHook(safeThis);
// Mark the runtime as injected to prevent double-injection
safeThis[$RefreshInjected$] = true;
}
}
}

View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View File

@@ -0,0 +1,102 @@
/**
* @callback EventCallback
* @param {string | Error | null} context
* @returns {void}
*/
/**
* @callback EventHandler
* @param {Event} event
* @returns {void}
*/
/**
* A function that creates an event handler for the `error` event.
* @param {EventCallback} callback A function called to handle the error context.
* @returns {EventHandler} A handler for the `error` event.
*/
function createErrorHandler(callback) {
return function errorHandler(event) {
if (!event || !event.error) {
return callback(null);
}
if (event.error instanceof Error) {
return callback(event.error);
}
// A non-error was thrown, we don't have a trace. :(
// Look in your browser's devtools for more information
return callback(new Error(event.error));
};
}
/**
* A function that creates an event handler for the `unhandledrejection` event.
* @param {EventCallback} callback A function called to handle the error context.
* @returns {EventHandler} A handler for the `unhandledrejection` event.
*/
function createRejectionHandler(callback) {
return function rejectionHandler(event) {
if (!event || !event.reason) {
return callback(new Error('Unknown'));
}
if (event.reason instanceof Error) {
return callback(event.reason);
}
// A non-error was rejected, we don't have a trace :(
// Look in your browser's devtools for more information
return callback(new Error(event.reason));
};
}
/**
* Creates a handler that registers an EventListener on window for a valid type
* and calls a callback when the event fires.
* @param {string} eventType A valid DOM event type.
* @param {function(EventCallback): EventHandler} createHandler A function that creates an event handler.
* @returns {register} A function that registers the EventListener given a callback.
*/
function createWindowEventHandler(eventType, createHandler) {
/**
* @type {EventHandler | null} A cached event handler function.
*/
let eventHandler = null;
/**
* Unregisters an EventListener if it has been registered.
* @returns {void}
*/
function unregister() {
if (eventHandler === null) {
return;
}
window.removeEventListener(eventType, eventHandler);
eventHandler = null;
}
/**
* Registers an EventListener if it hasn't been registered.
* @param {EventCallback} callback A function called after the event handler to handle its context.
* @returns {unregister | void} A function to unregister the registered EventListener if registration is performed.
*/
function register(callback) {
if (eventHandler !== null) {
return;
}
eventHandler = createHandler(callback);
window.addEventListener(eventType, eventHandler);
return unregister;
}
return register;
}
const handleError = createWindowEventHandler('error', createErrorHandler);
const handleUnhandledRejection = createWindowEventHandler(
'unhandledrejection',
createRejectionHandler
);
module.exports = {
handleError: handleError,
handleUnhandledRejection: handleUnhandledRejection,
};

View File

@@ -0,0 +1,96 @@
/**
* @typedef {Object} WebpackErrorObj
* @property {string} moduleIdentifier
* @property {string} moduleName
* @property {string} message
*/
const friendlySyntaxErrorLabel = 'Syntax error:';
/**
* Checks if the error message is for a syntax error.
* @param {string} message The raw Webpack error message.
* @returns {boolean} Whether the error message is for a syntax error.
*/
function isLikelyASyntaxError(message) {
return message.indexOf(friendlySyntaxErrorLabel) !== -1;
}
/**
* Cleans up Webpack error messages.
*
* This implementation is based on the one from [create-react-app](https://github.com/facebook/create-react-app/blob/edc671eeea6b7d26ac3f1eb2050e50f75cf9ad5d/packages/react-dev-utils/formatWebpackMessages.js).
* @param {string} message The raw Webpack error message.
* @returns {string} The formatted Webpack error message.
*/
function formatMessage(message) {
let lines = message.split('\n');
// Strip Webpack-added headers off errors/warnings
// https://github.com/webpack/webpack/blob/master/lib/ModuleError.js
lines = lines.filter(function (line) {
return !/Module [A-z ]+\(from/.test(line);
});
// Remove leading newline
if (lines.length > 2 && lines[1].trim() === '') {
lines.splice(1, 1);
}
// Remove duplicated newlines
lines = lines.filter(function (line, index, arr) {
return index === 0 || line.trim() !== '' || line.trim() !== arr[index - 1].trim();
});
// Clean up the file name
lines[0] = lines[0].replace(/^(.*) \d+:\d+-\d+$/, '$1');
// Cleans up verbose "module not found" messages for files and packages.
if (lines[1] && lines[1].indexOf('Module not found: ') === 0) {
lines = [
lines[0],
lines[1]
.replace('Error: ', '')
.replace('Module not found: Cannot find file:', 'Cannot find file:'),
];
}
message = lines.join('\n');
// Clean up syntax errors
message = message.replace('SyntaxError:', friendlySyntaxErrorLabel);
// Internal stacks are generally useless, so we strip them -
// except the stacks containing `webpack:`,
// because they're normally from user code generated by webpack.
message = message.replace(/^\s*at\s((?!webpack:).)*:\d+:\d+[\s)]*(\n|$)/gm, ''); // at ... ...:x:y
message = message.replace(/^\s*at\s((?!webpack:).)*<anonymous>[\s)]*(\n|$)/gm, ''); // at ... <anonymous>
message = message.replace(/^\s*at\s<anonymous>(\n|$)/gm, ''); // at <anonymous>
return message.trim();
}
/**
* Formats Webpack error messages into a more readable format.
* @param {Array<string | WebpackErrorObj>} errors An array of Webpack error messages.
* @returns {string[]} The formatted Webpack error messages.
*/
function formatWebpackErrors(errors) {
let formattedErrors = errors.map(function (errorObjOrMessage) {
// Webpack 5 compilation errors are in the form of descriptor objects,
// so we have to join pieces to get the format we want.
if (typeof errorObjOrMessage === 'object') {
return formatMessage([errorObjOrMessage.moduleName, errorObjOrMessage.message].join('\n'));
}
// Webpack 4 compilation errors are strings
return formatMessage(errorObjOrMessage);
});
if (formattedErrors.some(isLikelyASyntaxError)) {
// If there are any syntax errors, show just them.
formattedErrors = formattedErrors.filter(isLikelyASyntaxError);
}
return formattedErrors;
}
module.exports = formatWebpackErrors;

View File

@@ -0,0 +1,40 @@
/* global __react_refresh_polyfill_url__ */
/**
* @typedef {Object} UrlAPIs
* @property {typeof URL} URL
* @property {typeof URLSearchParams} URLSearchParams
*/
/**
* Runs a callback with patched the DOM URL APIs.
* @param {function(UrlAPIs): void} callback The code to run with patched URL globals.
* @returns {void}
*/
function runWithPatchedUrl(callback) {
var __originalURL;
var __originalURLSearchParams;
// Polyfill the DOM URL and URLSearchParams constructors
if (__react_refresh_polyfill_url__ || !window.URL) {
__originalURL = window.URL;
window.URL = require('core-js-pure/web/url');
}
if (__react_refresh_polyfill_url__ || !window.URLSearchParams) {
__originalURLSearchParams = window.URLSearchParams;
window.URLSearchParams = require('core-js-pure/web/url-search-params');
}
// Pass in URL APIs in case they are needed
callback({ URL: window.URL, URLSearchParams: window.URLSearchParams });
// Restore polyfill-ed APIs to their original state
if (__originalURL) {
window.URL = __originalURL;
}
if (__originalURLSearchParams) {
window.URLSearchParams = __originalURLSearchParams;
}
}
module.exports = runWithPatchedUrl;

View File

@@ -0,0 +1,23 @@
function runWithRetry(callback, maxRetries) {
function executeWithRetryAndTimeout(currentCount) {
try {
if (currentCount > maxRetries - 1) {
console.warn('[React Refresh] Failed to set up the socket connection.');
return;
}
callback();
} catch (err) {
setTimeout(
function () {
executeWithRetryAndTimeout(currentCount + 1);
},
Math.pow(10, currentCount)
);
}
}
executeWithRetryAndTimeout(0);
}
module.exports = runWithRetry;

View File

@@ -0,0 +1,108 @@
// This is a patch for mozilla/source-map#349 -
// internally, it uses the existence of the `fetch` global to toggle browser behaviours.
// That check, however, will break when `fetch` polyfills are used for SSR setups.
// We "reset" the polyfill here to ensure it won't interfere with source-map generation.
const originalFetch = global.fetch;
delete global.fetch;
const { getOptions } = require('loader-utils');
const { validate: validateOptions } = require('schema-utils');
const { SourceMapConsumer, SourceNode } = require('source-map');
const {
getIdentitySourceMap,
getModuleSystem,
getRefreshModuleRuntime,
normalizeOptions,
} = require('./utils');
const schema = require('./options.json');
const RefreshRuntimePath = require
.resolve('react-refresh')
.replace(/\\/g, '/')
.replace(/'/g, "\\'");
/**
* A simple Webpack loader to inject react-refresh HMR code into modules.
*
* [Reference for Loader API](https://webpack.js.org/api/loaders/)
* @this {import('webpack').LoaderContext<import('./types').ReactRefreshLoaderOptions>}
* @param {string} source The original module source code.
* @param {import('source-map').RawSourceMap} [inputSourceMap] The source map of the module.
* @param {*} [meta] The loader metadata passed in.
* @returns {void}
*/
function ReactRefreshLoader(source, inputSourceMap, meta) {
let options = getOptions(this);
validateOptions(schema, options, {
baseDataPath: 'options',
name: 'React Refresh Loader',
});
options = normalizeOptions(options);
const callback = this.async();
const { ModuleFilenameHelpers, Template } = this._compiler.webpack || require('webpack');
const RefreshSetupRuntimes = {
cjs: Template.asString(
`__webpack_require__.$Refresh$.runtime = require('${RefreshRuntimePath}');`
),
esm: Template.asString([
`import * as __react_refresh_runtime__ from '${RefreshRuntimePath}';`,
`__webpack_require__.$Refresh$.runtime = __react_refresh_runtime__;`,
]),
};
/**
* @this {import('webpack').LoaderContext<import('./types').ReactRefreshLoaderOptions>}
* @param {string} source
* @param {import('source-map').RawSourceMap} [inputSourceMap]
* @returns {Promise<[string, import('source-map').RawSourceMap]>}
*/
async function _loader(source, inputSourceMap) {
/** @type {'esm' | 'cjs'} */
const moduleSystem = await getModuleSystem.call(this, ModuleFilenameHelpers, options);
const RefreshSetupRuntime = RefreshSetupRuntimes[moduleSystem];
const RefreshModuleRuntime = getRefreshModuleRuntime(Template, {
const: options.const,
moduleSystem,
});
if (this.sourceMap) {
let originalSourceMap = inputSourceMap;
if (!originalSourceMap) {
originalSourceMap = getIdentitySourceMap(source, this.resourcePath);
}
return SourceMapConsumer.with(originalSourceMap, undefined, (consumer) => {
const node = SourceNode.fromStringWithSourceMap(source, consumer);
node.prepend([RefreshSetupRuntime, '\n\n']);
node.add(['\n\n', RefreshModuleRuntime]);
const { code, map } = node.toStringWithSourceMap();
return [code, map.toJSON()];
});
} else {
return [[RefreshSetupRuntime, source, RefreshModuleRuntime].join('\n\n'), inputSourceMap];
}
}
_loader.call(this, source, inputSourceMap).then(
([code, map]) => {
callback(null, code, map, meta);
},
(error) => {
callback(error);
}
);
}
module.exports = ReactRefreshLoader;
// Restore the original value of the `fetch` global, if it exists
if (originalFetch) {
global.fetch = originalFetch;
}

View File

@@ -0,0 +1,37 @@
{
"additionalProperties": false,
"type": "object",
"definitions": {
"MatchCondition": {
"anyOf": [{ "instanceof": "RegExp", "tsType": "RegExp" }, { "$ref": "#/definitions/Path" }]
},
"MatchConditions": {
"type": "array",
"items": { "$ref": "#/definitions/MatchCondition" },
"minItems": 1
},
"Path": { "type": "string" },
"ESModuleOptions": {
"additionalProperties": false,
"type": "object",
"properties": {
"exclude": {
"anyOf": [
{ "$ref": "#/definitions/MatchCondition" },
{ "$ref": "#/definitions/MatchConditions" }
]
},
"include": {
"anyOf": [
{ "$ref": "#/definitions/MatchCondition" },
{ "$ref": "#/definitions/MatchConditions" }
]
}
}
}
},
"properties": {
"const": { "type": "boolean" },
"esModule": { "anyOf": [{ "type": "boolean" }, { "$ref": "#/definitions/ESModuleOptions" }] }
}
}

View File

@@ -0,0 +1,17 @@
/**
* @typedef {Object} ESModuleOptions
* @property {string | RegExp | Array<string | RegExp>} [exclude] Files to explicitly exclude from flagged as ES Modules.
* @property {string | RegExp | Array<string | RegExp>} [include] Files to explicitly include for flagged as ES Modules.
*/
/**
* @typedef {Object} ReactRefreshLoaderOptions
* @property {boolean} [const] Enables usage of ES6 `const` and `let` in generated runtime code.
* @property {boolean | ESModuleOptions} [esModule] Enables strict ES Modules compatible runtime.
*/
/**
* @typedef {import('type-fest').SetRequired<ReactRefreshLoaderOptions, 'const'>} NormalizedLoaderOptions
*/
module.exports = {};

View File

@@ -0,0 +1,30 @@
const { SourceMapGenerator } = require('source-map');
/**
* Generates an identity source map from a source file.
* @param {string} source The content of the source file.
* @param {string} resourcePath The name of the source file.
* @returns {import('source-map').RawSourceMap} The identity source map.
*/
function getIdentitySourceMap(source, resourcePath) {
const sourceMap = new SourceMapGenerator();
sourceMap.setSourceContent(resourcePath, source);
source.split('\n').forEach((line, index) => {
sourceMap.addMapping({
source: resourcePath,
original: {
line: index + 1,
column: 0,
},
generated: {
line: index + 1,
column: 0,
},
});
});
return sourceMap.toJSON();
}
module.exports = getIdentitySourceMap;

View File

@@ -0,0 +1,128 @@
const { promises: fsPromises } = require('fs');
const path = require('path');
/** @type {Map<string, string | undefined>} */
let packageJsonTypeMap = new Map();
/**
* Infers the current active module system from loader context and options.
* @this {import('webpack').loader.LoaderContext}
* @param {import('webpack').ModuleFilenameHelpers} ModuleFilenameHelpers Webpack's module filename helpers.
* @param {import('../types').NormalizedLoaderOptions} options The normalized loader options.
* @return {Promise<'esm' | 'cjs'>} The inferred module system.
*/
async function getModuleSystem(ModuleFilenameHelpers, options) {
// Check loader options -
// if `esModule` is set we don't have to do extra guess work.
switch (typeof options.esModule) {
case 'boolean': {
return options.esModule ? 'esm' : 'cjs';
}
case 'object': {
if (
options.esModule.include &&
ModuleFilenameHelpers.matchPart(this.resourcePath, options.esModule.include)
) {
return 'esm';
}
if (
options.esModule.exclude &&
ModuleFilenameHelpers.matchPart(this.resourcePath, options.esModule.exclude)
) {
return 'cjs';
}
break;
}
default: // Do nothing
}
// Check current resource's extension
if (/\.mjs$/.test(this.resourcePath)) return 'esm';
if (/\.cjs$/.test(this.resourcePath)) return 'cjs';
if (typeof this.addMissingDependency !== 'function') {
// This is Webpack 4 which does not support `import.meta`.
// We assume `.js` files are CommonJS because the output cannot be ESM anyway.
return 'cjs';
}
// We will assume CommonJS if we cannot determine otherwise
let packageJsonType = '';
// We begin our search for relevant `package.json` files,
// at the directory of the resource being loaded.
// These paths should already be resolved,
// but we resolve them again to ensure we are dealing with an aboslute path.
const resourceContext = path.dirname(this.resourcePath);
let searchPath = resourceContext;
let previousSearchPath = '';
// We start our search just above the root context of the webpack compilation
const stopPath = path.dirname(this.rootContext);
// If the module context is a resolved symlink outside the `rootContext` path,
// then we will never find the `stopPath` - so we also halt when we hit the root.
// Note that there is a potential that the wrong `package.json` is found in some pathalogical cases,
// such as a folder that is conceptually a package + does not have an ancestor `package.json`,
// but there exists a `package.json` higher up.
// This might happen if you have a folder of utility JS files that you symlink but did not organize as a package.
// We consider this an unsupported edge case for now.
while (searchPath !== stopPath && searchPath !== previousSearchPath) {
// If we have already determined the `package.json` type for this path we can stop searching.
// We do however still need to cache the found value,
// from the `resourcePath` folder up to the matching `searchPath`,
// to avoid retracing these steps when processing sibling resources.
if (packageJsonTypeMap.has(searchPath)) {
packageJsonType = packageJsonTypeMap.get(searchPath);
let currentPath = resourceContext;
while (currentPath !== searchPath) {
// We set the found type at least level from `resourcePath` folder up to the matching `searchPath`
packageJsonTypeMap.set(currentPath, packageJsonType);
currentPath = path.dirname(currentPath);
}
break;
}
let packageJsonPath = path.join(searchPath, 'package.json');
try {
const packageSource = await fsPromises.readFile(packageJsonPath, 'utf-8');
try {
const packageObject = JSON.parse(packageSource);
// Any package.json is sufficient as long as it can be parsed.
// If it does not explicitly have a `type: "module"` it will be assumed to be CommonJS.
packageJsonType = typeof packageObject.type === 'string' ? packageObject.type : '';
packageJsonTypeMap.set(searchPath, packageJsonType);
// We set the type in the cache for all paths from the `resourcePath` folder,
// up to the matching `searchPath` to avoid retracing these steps when processing sibling resources.
let currentPath = resourceContext;
while (currentPath !== searchPath) {
packageJsonTypeMap.set(currentPath, packageJsonType);
currentPath = path.dirname(currentPath);
}
} catch (e) {
// `package.json` exists but could not be parsed.
// We track it as a dependency so we can reload if this file changes.
}
this.addDependency(packageJsonPath);
break;
} catch (e) {
// `package.json` does not exist.
// We track it as a missing dependency so we can reload if this file is added.
this.addMissingDependency(packageJsonPath);
}
// Try again at the next level up
previousSearchPath = searchPath;
searchPath = path.dirname(searchPath);
}
// Check `package.json` for the `type` field -
// fallback to use `cjs` for anything ambiguous.
return packageJsonType === 'module' ? 'esm' : 'cjs';
}
module.exports = getModuleSystem;

View File

@@ -0,0 +1,63 @@
/**
* @typedef ModuleRuntimeOptions {Object}
* @property {boolean} const Use ES6 `const` and `let` in generated runtime code.
* @property {'cjs' | 'esm'} moduleSystem The module system to be used.
*/
/**
* Generates code appended to each JS-like module for react-refresh capabilities.
*
* `__react_refresh_utils__` will be replaced with actual utils during source parsing by `webpack.ProvidePlugin`.
*
* [Reference for Runtime Injection](https://github.com/webpack/webpack/blob/b07d3b67d2252f08e4bb65d354a11c9b69f8b434/lib/HotModuleReplacementPlugin.js#L419)
* [Reference for HMR Error Recovery](https://github.com/webpack/webpack/issues/418#issuecomment-490296365)
*
* @param {import('webpack').Template} Webpack's templating helpers.
* @param {ModuleRuntimeOptions} options The refresh module runtime options.
* @returns {string} The refresh module runtime template.
*/
function getRefreshModuleRuntime(Template, options) {
const constDeclaration = options.const ? 'const' : 'var';
const letDeclaration = options.const ? 'let' : 'var';
const webpackHot = options.moduleSystem === 'esm' ? 'import.meta.webpackHot' : 'module.hot';
return Template.asString([
`${constDeclaration} $ReactRefreshModuleId$ = __webpack_require__.$Refresh$.moduleId;`,
`${constDeclaration} $ReactRefreshCurrentExports$ = __react_refresh_utils__.getModuleExports(`,
Template.indent('$ReactRefreshModuleId$'),
');',
'',
'function $ReactRefreshModuleRuntime$(exports) {',
Template.indent([
`if (${webpackHot}) {`,
Template.indent([
`${letDeclaration} errorOverlay;`,
"if (typeof __react_refresh_error_overlay__ !== 'undefined') {",
Template.indent('errorOverlay = __react_refresh_error_overlay__;'),
'}',
`${letDeclaration} testMode;`,
"if (typeof __react_refresh_test__ !== 'undefined') {",
Template.indent('testMode = __react_refresh_test__;'),
'}',
'return __react_refresh_utils__.executeRuntime(',
Template.indent([
'exports,',
'$ReactRefreshModuleId$,',
`${webpackHot},`,
'errorOverlay,',
'testMode',
]),
');',
]),
'}',
]),
'}',
'',
"if (typeof Promise !== 'undefined' && $ReactRefreshCurrentExports$ instanceof Promise) {",
Template.indent('$ReactRefreshCurrentExports$.then($ReactRefreshModuleRuntime$);'),
'} else {',
Template.indent('$ReactRefreshModuleRuntime$($ReactRefreshCurrentExports$);'),
'}',
]);
}
module.exports = getRefreshModuleRuntime;

View File

@@ -0,0 +1,11 @@
const getIdentitySourceMap = require('./getIdentitySourceMap');
const getModuleSystem = require('./getModuleSystem');
const getRefreshModuleRuntime = require('./getRefreshModuleRuntime');
const normalizeOptions = require('./normalizeOptions');
module.exports = {
getIdentitySourceMap,
getModuleSystem,
getRefreshModuleRuntime,
normalizeOptions,
};

View File

@@ -0,0 +1,25 @@
const { d, n } = require('../../options');
/**
* Normalizes the options for the loader.
* @param {import('../types').ReactRefreshLoaderOptions} options Non-normalized loader options.
* @returns {import('../types').NormalizedLoaderOptions} Normalized loader options.
*/
const normalizeOptions = (options) => {
d(options, 'const', false);
n(options, 'esModule', (esModule) => {
if (typeof esModule === 'boolean' || typeof esModule === 'undefined') {
return esModule;
}
d(esModule, 'include');
d(esModule, 'exclude');
return esModule;
});
return options;
};
module.exports = normalizeOptions;

View File

@@ -0,0 +1,32 @@
/**
* Sets a constant default value for the property when it is undefined.
* @template T
* @template {keyof T} Property
* @param {T} object An object.
* @param {Property} property A property of the provided object.
* @param {T[Property]} [defaultValue] The default value to set for the property.
* @returns {T[Property]} The defaulted property value.
*/
const d = (object, property, defaultValue) => {
if (typeof object[property] === 'undefined' && typeof defaultValue !== 'undefined') {
object[property] = defaultValue;
}
return object[property];
};
/**
* Resolves the value for a nested object option.
* @template T
* @template {keyof T} Property
* @template Result
* @param {T} object An object.
* @param {Property} property A property of the provided object.
* @param {function(T | undefined): Result} fn The handler to resolve the property's value.
* @returns {Result} The resolved option value.
*/
const n = (object, property, fn) => {
object[property] = fn(object[property]);
return object[property];
};
module.exports = { d, n };

Binary file not shown.

View File

@@ -0,0 +1,58 @@
const ansiHTML = require('ansi-html');
const entities = require('html-entities');
const theme = require('../theme.js');
const utils = require('../utils.js');
ansiHTML.setColors(theme);
/**
* @typedef {Object} CompileErrorTraceProps
* @property {string} errorMessage
*/
/**
* A formatter that turns Webpack compile error messages into highlighted HTML source traces.
* @param {Document} document
* @param {HTMLElement} root
* @param {CompileErrorTraceProps} props
* @returns {void}
*/
function CompileErrorTrace(document, root, props) {
const errorParts = props.errorMessage.split('\n');
if (errorParts.length) {
if (errorParts[0]) {
errorParts[0] = utils.formatFilename(errorParts[0]);
}
const errorMessage = errorParts.splice(1, 1)[0];
if (errorMessage) {
// Strip filename from the error message
errorParts.unshift(errorMessage.replace(/^(.*:)\s.*:(\s.*)$/, '$1$2'));
}
}
const stackContainer = document.createElement('pre');
stackContainer.innerHTML = entities.decode(
ansiHTML(entities.encode(errorParts.join('\n'), { level: 'html5', mode: 'nonAscii' })),
{ level: 'html5' }
);
stackContainer.style.fontFamily = [
'"Operator Mono SSm"',
'"Operator Mono"',
'"Fira Code Retina"',
'"Fira Code"',
'"FiraCode-Retina"',
'"Andale Mono"',
'"Lucida Console"',
'Menlo',
'Consolas',
'Monaco',
'monospace',
].join(', ');
stackContainer.style.margin = '0';
stackContainer.style.whiteSpace = 'pre-wrap';
root.appendChild(stackContainer);
}
module.exports = CompileErrorTrace;

View File

@@ -0,0 +1,58 @@
const Spacer = require('./Spacer.js');
const theme = require('../theme.js');
/**
* @typedef {Object} PageHeaderProps
* @property {string} [message]
* @property {string} title
* @property {string} [topOffset]
*/
/**
* The header of the overlay.
* @param {Document} document
* @param {HTMLElement} root
* @param {PageHeaderProps} props
* @returns {void}
*/
function PageHeader(document, root, props) {
const pageHeaderContainer = document.createElement('div');
pageHeaderContainer.style.background = '#' + theme.dimgrey;
pageHeaderContainer.style.boxShadow = '0 1px 4px rgba(0, 0, 0, 0.3)';
pageHeaderContainer.style.color = '#' + theme.white;
pageHeaderContainer.style.left = '0';
pageHeaderContainer.style.right = '0';
pageHeaderContainer.style.padding = '1rem 1.5rem';
pageHeaderContainer.style.paddingLeft = 'max(1.5rem, env(safe-area-inset-left))';
pageHeaderContainer.style.paddingRight = 'max(1.5rem, env(safe-area-inset-right))';
pageHeaderContainer.style.position = 'fixed';
pageHeaderContainer.style.top = props.topOffset || '0';
const title = document.createElement('h3');
title.innerText = props.title;
title.style.color = '#' + theme.red;
title.style.fontSize = '1.125rem';
title.style.lineHeight = '1.3';
title.style.margin = '0';
pageHeaderContainer.appendChild(title);
if (props.message) {
title.style.margin = '0 0 0.5rem';
const message = document.createElement('span');
message.innerText = props.message;
message.style.color = '#' + theme.white;
message.style.wordBreak = 'break-word';
pageHeaderContainer.appendChild(message);
}
root.appendChild(pageHeaderContainer);
// This has to run after appending elements to root
// because we need to actual mounted height.
Spacer(document, root, {
space: pageHeaderContainer.offsetHeight.toString(10),
});
}
module.exports = PageHeader;

View File

@@ -0,0 +1,93 @@
const Spacer = require('./Spacer.js');
const theme = require('../theme.js');
/**
* @typedef {Object} RuntimeErrorFooterProps
* @property {string} [initialFocus]
* @property {boolean} multiple
* @property {function(MouseEvent): void} onClickCloseButton
* @property {function(MouseEvent): void} onClickNextButton
* @property {function(MouseEvent): void} onClickPrevButton
*/
/**
* A fixed footer that handles pagination of runtime errors.
* @param {Document} document
* @param {HTMLElement} root
* @param {RuntimeErrorFooterProps} props
* @returns {void}
*/
function RuntimeErrorFooter(document, root, props) {
const footer = document.createElement('div');
footer.style.backgroundColor = '#' + theme.dimgrey;
footer.style.bottom = '0';
footer.style.boxShadow = '0 -1px 4px rgba(0, 0, 0, 0.3)';
footer.style.height = '2.5rem';
footer.style.left = '0';
footer.style.right = '0';
footer.style.lineHeight = '2.5rem';
footer.style.paddingBottom = '0';
footer.style.paddingBottom = 'env(safe-area-inset-bottom)';
footer.style.position = 'fixed';
footer.style.textAlign = 'center';
footer.style.zIndex = '2';
const BUTTON_CONFIGS = {
prev: {
id: 'prev',
label: '◀&ensp;Prev',
onClick: props.onClickPrevButton,
},
close: {
id: 'close',
label: '×&ensp;Close',
onClick: props.onClickCloseButton,
},
next: {
id: 'next',
label: 'Next&ensp;▶',
onClick: props.onClickNextButton,
},
};
let buttons = [BUTTON_CONFIGS.close];
if (props.multiple) {
buttons = [BUTTON_CONFIGS.prev, BUTTON_CONFIGS.close, BUTTON_CONFIGS.next];
}
/** @type {HTMLButtonElement | undefined} */
let initialFocusButton;
for (let i = 0; i < buttons.length; i += 1) {
const buttonConfig = buttons[i];
const button = document.createElement('button');
button.id = buttonConfig.id;
button.innerHTML = buttonConfig.label;
button.tabIndex = 1;
button.style.backgroundColor = '#' + theme.dimgrey;
button.style.border = 'none';
button.style.color = '#' + theme.white;
button.style.cursor = 'pointer';
button.style.fontSize = 'inherit';
button.style.height = '100%';
button.style.padding = '0.5rem 0.75rem';
button.style.width = (100 / buttons.length).toString(10) + '%';
button.addEventListener('click', buttonConfig.onClick);
if (buttonConfig.id === props.initialFocus) {
initialFocusButton = button;
}
footer.appendChild(button);
}
root.appendChild(footer);
Spacer(document, root, { space: '2.5rem' });
if (initialFocusButton) {
initialFocusButton.focus();
}
}
module.exports = RuntimeErrorFooter;

View File

@@ -0,0 +1,37 @@
const Spacer = require('./Spacer.js');
const theme = require('../theme.js');
/**
* @typedef {Object} RuntimeErrorHeaderProps
* @property {number} currentErrorIndex
* @property {number} totalErrors
*/
/**
* A fixed header that shows the total runtime error count.
* @param {Document} document
* @param {HTMLElement} root
* @param {RuntimeErrorHeaderProps} props
* @returns {void}
*/
function RuntimeErrorHeader(document, root, props) {
const header = document.createElement('div');
header.innerText = 'Error ' + (props.currentErrorIndex + 1) + ' of ' + props.totalErrors;
header.style.backgroundColor = '#' + theme.red;
header.style.color = '#' + theme.white;
header.style.fontWeight = '500';
header.style.height = '2.5rem';
header.style.left = '0';
header.style.lineHeight = '2.5rem';
header.style.position = 'fixed';
header.style.textAlign = 'center';
header.style.top = '0';
header.style.width = '100vw';
header.style.zIndex = '2';
root.appendChild(header);
Spacer(document, root, { space: '2.5rem' });
}
module.exports = RuntimeErrorHeader;

View File

@@ -0,0 +1,79 @@
const ErrorStackParser = require('error-stack-parser');
const theme = require('../theme.js');
const utils = require('../utils.js');
/**
* @typedef {Object} RuntimeErrorStackProps
* @property {Error} error
*/
/**
* A formatter that turns runtime error stacks into highlighted HTML stacks.
* @param {Document} document
* @param {HTMLElement} root
* @param {RuntimeErrorStackProps} props
* @returns {void}
*/
function RuntimeErrorStack(document, root, props) {
const stackTitle = document.createElement('h4');
stackTitle.innerText = 'Call Stack';
stackTitle.style.color = '#' + theme.white;
stackTitle.style.fontSize = '1.0625rem';
stackTitle.style.fontWeight = '500';
stackTitle.style.lineHeight = '1.3';
stackTitle.style.margin = '0 0 0.5rem';
const stackContainer = document.createElement('div');
stackContainer.style.fontSize = '0.8125rem';
stackContainer.style.lineHeight = '1.3';
stackContainer.style.whiteSpace = 'pre-wrap';
let errorStacks;
try {
errorStacks = ErrorStackParser.parse(props.error);
} catch (e) {
errorStacks = [];
stackContainer.innerHTML = 'No stack trace is available for this error!';
}
for (let i = 0; i < Math.min(errorStacks.length, 10); i += 1) {
const currentStack = errorStacks[i];
const functionName = document.createElement('code');
functionName.innerHTML = '&emsp;' + currentStack.functionName || '(anonymous function)';
functionName.style.color = '#' + theme.yellow;
functionName.style.fontFamily = [
'"Operator Mono SSm"',
'"Operator Mono"',
'"Fira Code Retina"',
'"Fira Code"',
'"FiraCode-Retina"',
'"Andale Mono"',
'"Lucida Console"',
'Menlo',
'Consolas',
'Monaco',
'monospace',
].join(', ');
const fileName = document.createElement('div');
fileName.innerHTML =
'&emsp;&emsp;' +
utils.formatFilename(currentStack.fileName) +
':' +
currentStack.lineNumber +
':' +
currentStack.columnNumber;
fileName.style.color = '#' + theme.white;
fileName.style.fontSize = '0.6875rem';
fileName.style.marginBottom = '0.25rem';
stackContainer.appendChild(functionName);
stackContainer.appendChild(fileName);
}
root.appendChild(stackTitle);
root.appendChild(stackContainer);
}
module.exports = RuntimeErrorStack;

View File

@@ -0,0 +1,19 @@
/**
* @typedef {Object} SpacerProps
* @property {string} space
*/
/**
* An empty element to add spacing manually.
* @param {Document} document
* @param {HTMLElement} root
* @param {SpacerProps} props
* @returns {void}
*/
function Spacer(document, root, props) {
const spacer = document.createElement('div');
spacer.style.paddingBottom = props.space;
root.appendChild(spacer);
}
module.exports = Spacer;

View File

@@ -0,0 +1,25 @@
const CompileErrorTrace = require('../components/CompileErrorTrace.js');
const PageHeader = require('../components/PageHeader.js');
const Spacer = require('../components/Spacer.js');
/**
* @typedef {Object} CompileErrorContainerProps
* @property {string} errorMessage
*/
/**
* A container to render Webpack compilation error messages with source trace.
* @param {Document} document
* @param {HTMLElement} root
* @param {CompileErrorContainerProps} props
* @returns {void}
*/
function CompileErrorContainer(document, root, props) {
PageHeader(document, root, {
title: 'Failed to compile.',
});
CompileErrorTrace(document, root, { errorMessage: props.errorMessage });
Spacer(document, root, { space: '1rem' });
}
module.exports = CompileErrorContainer;

View File

@@ -0,0 +1,29 @@
const PageHeader = require('../components/PageHeader.js');
const RuntimeErrorStack = require('../components/RuntimeErrorStack.js');
const Spacer = require('../components/Spacer.js');
/**
* @typedef {Object} RuntimeErrorContainerProps
* @property {Error} currentError
*/
/**
* A container to render runtime error messages with stack trace.
* @param {Document} document
* @param {HTMLElement} root
* @param {RuntimeErrorContainerProps} props
* @returns {void}
*/
function RuntimeErrorContainer(document, root, props) {
PageHeader(document, root, {
message: props.currentError.message,
title: props.currentError.name,
topOffset: '2.5rem',
});
RuntimeErrorStack(document, root, {
error: props.currentError,
});
Spacer(document, root, { space: '1rem' });
}
module.exports = RuntimeErrorContainer;

View File

@@ -0,0 +1,339 @@
const RuntimeErrorFooter = require('./components/RuntimeErrorFooter.js');
const RuntimeErrorHeader = require('./components/RuntimeErrorHeader.js');
const CompileErrorContainer = require('./containers/CompileErrorContainer.js');
const RuntimeErrorContainer = require('./containers/RuntimeErrorContainer.js');
const theme = require('./theme.js');
const utils = require('./utils.js');
/**
* @callback RenderFn
* @returns {void}
*/
/* ===== Cached elements for DOM manipulations ===== */
/**
* The iframe that contains the overlay.
* @type {HTMLIFrameElement}
*/
let iframeRoot = null;
/**
* The document object from the iframe root, used to create and render elements.
* @type {Document}
*/
let rootDocument = null;
/**
* The root div elements will attach to.
* @type {HTMLDivElement}
*/
let root = null;
/**
* A Cached function to allow deferred render.
* @type {RenderFn | null}
*/
let scheduledRenderFn = null;
/* ===== Overlay State ===== */
/**
* The latest error message from Webpack compilation.
* @type {string}
*/
let currentCompileErrorMessage = '';
/**
* Index of the error currently shown by the overlay.
* @type {number}
*/
let currentRuntimeErrorIndex = 0;
/**
* The latest runtime error objects.
* @type {Error[]}
*/
let currentRuntimeErrors = [];
/**
* The render mode the overlay is currently in.
* @type {'compileError' | 'runtimeError' | null}
*/
let currentMode = null;
/**
* @typedef {Object} IframeProps
* @property {function(): void} onIframeLoad
*/
/**
* Creates the main `iframe` the overlay will attach to.
* Accepts a callback to be ran after iframe is initialized.
* @param {Document} document
* @param {HTMLElement} root
* @param {IframeProps} props
* @returns {HTMLIFrameElement}
*/
function IframeRoot(document, root, props) {
const iframe = document.createElement('iframe');
iframe.id = 'react-refresh-overlay';
iframe.src = 'about:blank';
iframe.style.border = 'none';
iframe.style.height = '100%';
iframe.style.left = '0';
iframe.style.minHeight = '100vh';
iframe.style.minHeight = '-webkit-fill-available';
iframe.style.position = 'fixed';
iframe.style.top = '0';
iframe.style.width = '100vw';
iframe.style.zIndex = '2147483647';
iframe.addEventListener('load', function onLoad() {
// Reset margin of iframe body
iframe.contentDocument.body.style.margin = '0';
props.onIframeLoad();
});
// We skip mounting and returns as we need to ensure
// the load event is fired after we setup the global variable
return iframe;
}
/**
* Creates the main `div` element for the overlay to render.
* @param {Document} document
* @param {HTMLElement} root
* @returns {HTMLDivElement}
*/
function OverlayRoot(document, root) {
const div = document.createElement('div');
div.id = 'react-refresh-overlay-error';
// Style the contents container
div.style.backgroundColor = '#' + theme.grey;
div.style.boxSizing = 'border-box';
div.style.color = '#' + theme.white;
div.style.fontFamily = [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'"Helvetica Neue"',
'Helvetica',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'Segoe UI Symbol',
].join(', ');
div.style.fontSize = '0.875rem';
div.style.height = '100%';
div.style.lineHeight = '1.3';
div.style.overflow = 'auto';
div.style.padding = '1rem 1.5rem 0';
div.style.paddingTop = 'max(1rem, env(safe-area-inset-top))';
div.style.paddingRight = 'max(1.5rem, env(safe-area-inset-right))';
div.style.paddingBottom = 'env(safe-area-inset-bottom)';
div.style.paddingLeft = 'max(1.5rem, env(safe-area-inset-left))';
div.style.width = '100vw';
root.appendChild(div);
return div;
}
/**
* Ensures the iframe root and the overlay root are both initialized before render.
* If check fails, render will be deferred until both roots are initialized.
* @param {RenderFn} renderFn A function that triggers a DOM render.
* @returns {void}
*/
function ensureRootExists(renderFn) {
if (root) {
// Overlay root is ready, we can render right away.
renderFn();
return;
}
// Creating an iframe may be asynchronous so we'll defer render.
// In case of multiple calls, function from the last call will be used.
scheduledRenderFn = renderFn;
if (iframeRoot) {
// Iframe is already ready, it will fire the load event.
return;
}
// Create the iframe root, and, the overlay root inside it when it is ready.
iframeRoot = IframeRoot(document, document.body, {
onIframeLoad: function onIframeLoad() {
rootDocument = iframeRoot.contentDocument;
root = OverlayRoot(rootDocument, rootDocument.body);
scheduledRenderFn();
},
});
// We have to mount here to ensure `iframeRoot` is set when `onIframeLoad` fires.
// This is because onIframeLoad() will be called synchronously
// or asynchronously depending on the browser.
document.body.appendChild(iframeRoot);
}
/**
* Creates the main `div` element for the overlay to render.
* @returns {void}
*/
function render() {
ensureRootExists(function () {
const currentFocus = rootDocument.activeElement;
let currentFocusId;
if (currentFocus.localName === 'button' && currentFocus.id) {
currentFocusId = currentFocus.id;
}
utils.removeAllChildren(root);
if (currentCompileErrorMessage) {
currentMode = 'compileError';
CompileErrorContainer(rootDocument, root, {
errorMessage: currentCompileErrorMessage,
});
} else if (currentRuntimeErrors.length) {
currentMode = 'runtimeError';
RuntimeErrorHeader(rootDocument, root, {
currentErrorIndex: currentRuntimeErrorIndex,
totalErrors: currentRuntimeErrors.length,
});
RuntimeErrorContainer(rootDocument, root, {
currentError: currentRuntimeErrors[currentRuntimeErrorIndex],
});
RuntimeErrorFooter(rootDocument, root, {
initialFocus: currentFocusId,
multiple: currentRuntimeErrors.length > 1,
onClickCloseButton: function onClose() {
clearRuntimeErrors();
},
onClickNextButton: function onNext() {
if (currentRuntimeErrorIndex === currentRuntimeErrors.length - 1) {
return;
}
currentRuntimeErrorIndex += 1;
ensureRootExists(render);
},
onClickPrevButton: function onPrev() {
if (currentRuntimeErrorIndex === 0) {
return;
}
currentRuntimeErrorIndex -= 1;
ensureRootExists(render);
},
});
}
});
}
/**
* Destroys the state of the overlay.
* @returns {void}
*/
function cleanup() {
// Clean up and reset all internal state.
document.body.removeChild(iframeRoot);
scheduledRenderFn = null;
root = null;
iframeRoot = null;
}
/**
* Clears Webpack compilation errors and dismisses the compile error overlay.
* @returns {void}
*/
function clearCompileError() {
if (!root || currentMode !== 'compileError') {
return;
}
currentCompileErrorMessage = '';
currentMode = null;
cleanup();
}
/**
* Clears runtime error records and dismisses the runtime error overlay.
* @param {boolean} [dismissOverlay] Whether to dismiss the overlay or not.
* @returns {void}
*/
function clearRuntimeErrors(dismissOverlay) {
if (!root || currentMode !== 'runtimeError') {
return;
}
currentRuntimeErrorIndex = 0;
currentRuntimeErrors = [];
if (typeof dismissOverlay === 'undefined' || dismissOverlay) {
currentMode = null;
cleanup();
}
}
/**
* Shows the compile error overlay with the specific Webpack error message.
* @param {string} message
* @returns {void}
*/
function showCompileError(message) {
if (!message) {
return;
}
currentCompileErrorMessage = message;
render();
}
/**
* Shows the runtime error overlay with the specific error records.
* @param {Error[]} errors
* @returns {void}
*/
function showRuntimeErrors(errors) {
if (!errors || !errors.length) {
return;
}
currentRuntimeErrors = errors;
render();
}
/**
* The debounced version of `showRuntimeErrors` to prevent frequent renders
* due to rapid firing listeners.
* @param {Error[]} errors
* @returns {void}
*/
const debouncedShowRuntimeErrors = utils.debounce(showRuntimeErrors, 30);
/**
* Detects if an error is a Webpack compilation error.
* @param {Error} error The error of interest.
* @returns {boolean} If the error is a Webpack compilation error.
*/
function isWebpackCompileError(error) {
return /Module [A-z ]+\(from/.test(error.message) || /Cannot find module/.test(error.message);
}
/**
* Handles runtime error contexts captured with EventListeners.
* Integrates with a runtime error overlay.
* @param {Error} error A valid error object.
* @returns {void}
*/
function handleRuntimeError(error) {
if (error && !isWebpackCompileError(error) && currentRuntimeErrors.indexOf(error) === -1) {
currentRuntimeErrors = currentRuntimeErrors.concat(error);
}
debouncedShowRuntimeErrors(currentRuntimeErrors);
}
module.exports = Object.freeze({
clearCompileError: clearCompileError,
clearRuntimeErrors: clearRuntimeErrors,
handleRuntimeError: handleRuntimeError,
showCompileError: showCompileError,
showRuntimeErrors: showRuntimeErrors,
});

View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View File

@@ -0,0 +1,39 @@
/**
* @typedef {Object} Theme
* @property {string[]} reset
* @property {string} black
* @property {string} red
* @property {string} green
* @property {string} yellow
* @property {string} blue
* @property {string} magenta
* @property {string} cyan
* @property {string} white
* @property {string} lightgrey
* @property {string} darkgrey
* @property {string} grey
* @property {string} dimgrey
*/
/**
* @type {Theme} theme
* A collection of colors to be used by the overlay.
* Partially adopted from Tomorrow Night Bright.
*/
const theme = {
reset: ['transparent', 'transparent'],
black: '000000',
red: 'D34F56',
green: 'B9C954',
yellow: 'E6C452',
blue: '7CA7D8',
magenta: 'C299D6',
cyan: '73BFB1',
white: 'FFFFFF',
lightgrey: 'C7C7C7',
darkgrey: 'A9A9A9',
grey: '474747',
dimgrey: '343434',
};
module.exports = theme;

View File

@@ -0,0 +1,74 @@
/**
* Debounce a function to delay invoking until wait (ms) have elapsed since the last invocation.
* @param {function(...*): *} fn The function to be debounced.
* @param {number} wait Milliseconds to wait before invoking again.
* @return {function(...*): void} The debounced function.
*/
function debounce(fn, wait) {
/**
* A cached setTimeout handler.
* @type {number | undefined}
*/
let timer;
/**
* @returns {void}
*/
function debounced() {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
return fn.apply(context, args);
}, wait);
}
return debounced;
}
/**
* Prettify a filename from error stacks into the desired format.
* @param {string} filename The filename to be formatted.
* @returns {string} The formatted filename.
*/
function formatFilename(filename) {
// Strip away protocol and domain for compiled files
const htmlMatch = /^https?:\/\/(.*)\/(.*)/.exec(filename);
if (htmlMatch && htmlMatch[1] && htmlMatch[2]) {
return htmlMatch[2];
}
// Strip everything before the first directory for source files
const sourceMatch = /\/.*?([^./]+[/|\\].*)$/.exec(filename);
if (sourceMatch && sourceMatch[1]) {
return sourceMatch[1].replace(/\?$/, '');
}
// Unknown filename type, use it as is
return filename;
}
/**
* Remove all children of an element.
* @param {HTMLElement} element A valid HTML element.
* @param {number} [skip] Number of elements to skip removing.
* @returns {void}
*/
function removeAllChildren(element, skip) {
/** @type {Node[]} */
const childList = Array.prototype.slice.call(
element.childNodes,
typeof skip !== 'undefined' ? skip : 0
);
for (let i = 0; i < childList.length; i += 1) {
element.removeChild(childList[i]);
}
}
module.exports = {
debounce: debounce,
formatFilename: formatFilename,
removeAllChildren: removeAllChildren,
};

View File

@@ -0,0 +1,147 @@
{
"name": "@pmmmwh/react-refresh-webpack-plugin",
"version": "0.5.17",
"description": "An **EXPERIMENTAL** Webpack plugin to enable \"Fast Refresh\" (also previously known as _Hot Reloading_) for React components.",
"keywords": [
"react",
"javascript",
"webpack",
"refresh",
"hmr",
"hotreload",
"livereload",
"live",
"edit",
"hot",
"reload"
],
"homepage": "https://github.com/pmmmwh/react-refresh-webpack-plugin#readme",
"bugs": {
"url": "https://github.com/pmmmwh/react-refresh-webpack-plugin/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pmmmwh/react-refresh-webpack-plugin.git"
},
"license": "MIT",
"author": "Michael Mok",
"main": "lib/index.js",
"types": "types/lib/index.d.ts",
"files": [
"client",
"lib",
"loader",
"options",
"overlay",
"sockets",
"types"
],
"scripts": {
"test": "run-s -c test:pre \"test:exec {@}\" test:post --",
"test:webpack-4": "cross-env WEBPACK_VERSION=4 yarn test",
"test:pre": "yarn add -D file:./",
"test:exec": "node scripts/test.js",
"test:post": "yarn remove @pmmmwh/react-refresh-webpack-plugin",
"lint": "eslint --report-unused-disable-directives --ext .js,.jsx .",
"lint:fix": "yarn lint --fix",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,md}\"",
"types:clean": "rimraf types",
"types:compile": "tsc -p tsconfig.json",
"types:prune-private": "del \"types/*/*\" \"!types/{lib,loader,options}/{index,types}.d.ts\"",
"generate-types": "run-s types:clean types:compile types:prune-private \"format --loglevel silent\"",
"prepublishOnly": "yarn generate-types"
},
"dependencies": {
"ansi-html": "^0.0.9",
"core-js-pure": "^3.23.3",
"error-stack-parser": "^2.0.6",
"html-entities": "^2.1.0",
"loader-utils": "^2.0.4",
"schema-utils": "^4.2.0",
"source-map": "^0.7.3"
},
"devDependencies": {
"@babel/core": "^7.14.2",
"@babel/plugin-transform-modules-commonjs": "^7.14.0",
"@types/cross-spawn": "^6.0.2",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.12",
"@types/json-schema": "^7.0.6",
"@types/module-alias": "^2.0.0",
"@types/node": "^20.5.0",
"@types/semver": "^7.3.9",
"@types/webpack": "^5.28.0",
"babel-loader": "^8.1.0",
"cross-env": "^7.0.3",
"cross-spawn": "^7.0.5",
"del-cli": "^5.1.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"fs-extra": "^11.2.0",
"get-port": "^5.1.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-environment-node": "^29.7.0",
"jest-junit": "^16.0.0",
"jest-location-mock": "^2.0.0",
"module-alias": "^2.2.2",
"nanoid": "^3.3.8",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.0",
"puppeteer": "^22.10.0",
"react-refresh": "^0.14.0",
"semver": "^7.5.2",
"sourcemap-validator": "^2.1.0",
"type-fest": "^4.0.0",
"typescript": "~5.4.5",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"webpack-cli-v4": "npm:webpack-cli@4.x",
"webpack-dev-server": "^5.0.4",
"webpack-dev-server-v3": "npm:webpack-dev-server@3.x",
"webpack-dev-server-v4": "npm:webpack-dev-server@4.x",
"webpack-hot-middleware": "^2.25.0",
"webpack-plugin-serve": "^1.4.1",
"webpack-v4": "npm:webpack@4.x",
"yn": "^4.0.0"
},
"peerDependencies": {
"@types/webpack": "4.x || 5.x",
"react-refresh": ">=0.10.0 <1.0.0",
"sockjs-client": "^1.4.0",
"type-fest": ">=0.17.0 <5.0.0",
"webpack": ">=4.43.0 <6.0.0",
"webpack-dev-server": "3.x || 4.x || 5.x",
"webpack-hot-middleware": "2.x",
"webpack-plugin-serve": "0.x || 1.x"
},
"peerDependenciesMeta": {
"@types/webpack": {
"optional": true
},
"sockjs-client": {
"optional": true
},
"type-fest": {
"optional": true
},
"webpack-dev-server": {
"optional": true
},
"webpack-hot-middleware": {
"optional": true
},
"webpack-plugin-serve": {
"optional": true
}
},
"resolutions": {
"type-fest": "^4.0.0"
},
"engines": {
"node": ">= 10.13"
},
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
}

View File

@@ -0,0 +1,32 @@
/* global __webpack_dev_server_client__ */
const getSocketUrlParts = require('./utils/getSocketUrlParts.js');
const getUrlFromParts = require('./utils/getUrlFromParts');
const getWDSMetadata = require('./utils/getWDSMetadata');
/**
* Initializes a socket server for HMR for webpack-dev-server.
* @param {function(*): void} messageHandler A handler to consume Webpack compilation messages.
* @param {string} [resourceQuery] Webpack's `__resourceQuery` string.
* @returns {void}
*/
function initWDSSocket(messageHandler, resourceQuery) {
if (typeof __webpack_dev_server_client__ !== 'undefined') {
let SocketClient = __webpack_dev_server_client__;
if (typeof __webpack_dev_server_client__.default !== 'undefined') {
SocketClient = __webpack_dev_server_client__.default;
}
const wdsMeta = getWDSMetadata(SocketClient);
const urlParts = getSocketUrlParts(resourceQuery, wdsMeta);
const connection = new SocketClient(getUrlFromParts(urlParts, wdsMeta));
connection.onMessage(function onSocketMessage(data) {
const message = JSON.parse(data);
messageHandler(message);
});
}
}
module.exports = { init: initWDSSocket };

View File

@@ -0,0 +1,31 @@
/**
* The hard-coded singleton key for webpack-hot-middleware's client instance.
*
* [Ref](https://github.com/webpack-contrib/webpack-hot-middleware/blob/cb29abb9dde435a1ac8e9b19f82d7d36b1093198/client.js#L152)
*/
const singletonKey = '__webpack_hot_middleware_reporter__';
/**
* Initializes a socket server for HMR for webpack-hot-middleware.
* @param {function(*): void} messageHandler A handler to consume Webpack compilation messages.
* @returns {void}
*/
function initWHMEventSource(messageHandler) {
const client = window[singletonKey];
client.useCustomOverlay({
showProblems: function showProblems(type, data) {
const error = {
data: data,
type: type,
};
messageHandler(error);
},
clear: function clear() {
messageHandler({ type: 'ok' });
},
});
}
module.exports = { init: initWHMEventSource };

View File

@@ -0,0 +1,51 @@
/* global ʎɐɹɔosǝʌɹǝs */
const { ClientSocket } = require('webpack-plugin-serve/lib/client/ClientSocket');
/**
* Initializes a socket server for HMR for webpack-plugin-serve.
* @param {function(*): void} messageHandler A handler to consume Webpack compilation messages.
* @returns {void}
*/
function initWPSSocket(messageHandler) {
/**
* The hard-coded options injection key from webpack-plugin-serve.
*
* [Ref](https://github.com/shellscape/webpack-plugin-serve/blob/aeb49f14e900802c98df4a4607a76bc67b1cffdf/lib/index.js#L258)
* @type {Object | undefined}
*/
let options;
try {
options = ʎɐɹɔosǝʌɹǝs;
} catch (e) {
// Bail out because this indicates the plugin is not included
return;
}
const { address, client = {}, secure } = options;
const protocol = secure ? 'wss' : 'ws';
const socket = new ClientSocket(client, protocol + '://' + (client.address || address) + '/wps');
socket.addEventListener('message', function listener(message) {
const { action, data } = JSON.parse(message.data);
switch (action) {
case 'done': {
messageHandler({ type: 'ok' });
break;
}
case 'problems': {
if (data.errors.length) {
messageHandler({ type: 'errors', data: data.errors });
} else if (data.warnings.length) {
messageHandler({ type: 'warnings', data: data.warnings });
}
break;
}
default: {
// Do nothing
}
}
});
}
module.exports = { init: initWPSSocket };

View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View File

@@ -0,0 +1,30 @@
/**
* Gets the source (i.e. host) of the script currently running.
* @returns {string}
*/
function getCurrentScriptSource() {
// `document.currentScript` is the most accurate way to get the current running script,
// but is not supported in all browsers (most notably, IE).
if ('currentScript' in document) {
// In some cases, `document.currentScript` would be `null` even if the browser supports it:
// e.g. asynchronous chunks on Firefox.
// We should not fallback to the list-approach as it would not be safe.
if (document.currentScript == null) return;
return document.currentScript.getAttribute('src');
}
// Fallback to getting all scripts running in the document,
// and finding the last one injected.
else {
const scriptElementsWithSrc = Array.prototype.filter.call(
document.scripts || [],
function (elem) {
return elem.getAttribute('src');
}
);
if (!scriptElementsWithSrc.length) return;
const currentScript = scriptElementsWithSrc[scriptElementsWithSrc.length - 1];
return currentScript.getAttribute('src');
}
}
module.exports = getCurrentScriptSource;

View File

@@ -0,0 +1,141 @@
const getCurrentScriptSource = require('./getCurrentScriptSource.js');
/**
* @typedef {Object} SocketUrlParts
* @property {string} [auth]
* @property {string} hostname
* @property {string} [protocol]
* @property {string} pathname
* @property {string} [port]
*/
/**
* Parse current location and Webpack's `__resourceQuery` into parts that can create a valid socket URL.
* @param {string} [resourceQuery] The Webpack `__resourceQuery` string.
* @param {import('./getWDSMetadata').WDSMetaObj} [metadata] The parsed WDS metadata object.
* @returns {SocketUrlParts} The parsed URL parts.
* @see https://webpack.js.org/api/module-variables/#__resourcequery-webpack-specific
*/
function getSocketUrlParts(resourceQuery, metadata) {
if (typeof metadata === 'undefined') {
metadata = {};
}
/** @type {SocketUrlParts} */
let urlParts = {};
// If the resource query is available,
// parse it and ignore everything we received from the script host.
if (resourceQuery) {
const parsedQuery = {};
const searchParams = new URLSearchParams(resourceQuery.slice(1));
searchParams.forEach(function (value, key) {
parsedQuery[key] = value;
});
urlParts.hostname = parsedQuery.sockHost;
urlParts.pathname = parsedQuery.sockPath;
urlParts.port = parsedQuery.sockPort;
// Make sure the protocol from resource query has a trailing colon
if (parsedQuery.sockProtocol) {
urlParts.protocol = parsedQuery.sockProtocol + ':';
}
} else {
const scriptSource = getCurrentScriptSource();
let url = {};
try {
// The placeholder `baseURL` with `window.location.href`,
// is to allow parsing of path-relative or protocol-relative URLs,
// and will have no effect if `scriptSource` is a fully valid URL.
url = new URL(scriptSource, window.location.href);
} catch (e) {
// URL parsing failed, do nothing.
// We will still proceed to see if we can recover using `resourceQuery`
}
// Parse authentication credentials in case we need them
if (url.username) {
// Since HTTP basic authentication does not allow empty username,
// we only include password if the username is not empty.
// Result: <username> or <username>:<password>
urlParts.auth = url.username;
if (url.password) {
urlParts.auth += ':' + url.password;
}
}
// `file://` URLs has `'null'` origin
if (url.origin !== 'null') {
urlParts.hostname = url.hostname;
}
urlParts.protocol = url.protocol;
urlParts.port = url.port;
}
if (!urlParts.pathname) {
if (metadata.version === 4) {
// This is hard-coded in WDS v4
urlParts.pathname = '/ws';
} else {
// This is hard-coded in WDS v3
urlParts.pathname = '/sockjs-node';
}
}
// Check for IPv4 and IPv6 host addresses that correspond to any/empty.
// This is important because `hostname` can be empty for some hosts,
// such as 'about:blank' or 'file://' URLs.
const isEmptyHostname =
urlParts.hostname === '0.0.0.0' || urlParts.hostname === '[::]' || !urlParts.hostname;
// We only re-assign the hostname if it is empty,
// and if we are using HTTP/HTTPS protocols.
if (
isEmptyHostname &&
window.location.hostname &&
window.location.protocol.indexOf('http') === 0
) {
urlParts.hostname = window.location.hostname;
}
// We only re-assign `protocol` when `protocol` is unavailable,
// or if `hostname` is available and is empty,
// since otherwise we risk creating an invalid URL.
// We also do this when no sockProtocol was passed to the config and 'https' is used,
// as it mandates the use of secure sockets.
if (
!urlParts.protocol ||
(urlParts.hostname &&
(isEmptyHostname || (!resourceQuery && window.location.protocol === 'https:')))
) {
urlParts.protocol = window.location.protocol;
}
// We only re-assign port when it is not available
if (!urlParts.port) {
urlParts.port = window.location.port;
}
if (!urlParts.hostname || !urlParts.pathname) {
throw new Error(
[
'[React Refresh] Failed to get an URL for the socket connection.',
"This usually means that the current executed script doesn't have a `src` attribute set.",
'You should either specify the socket path parameters under the `devServer` key in your Webpack config, or use the `overlay` option.',
'https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/API.md#overlay',
].join('\n')
);
}
return {
auth: urlParts.auth,
hostname: urlParts.hostname,
pathname: urlParts.pathname,
protocol: urlParts.protocol,
port: urlParts.port || undefined,
};
}
module.exports = getSocketUrlParts;

View File

@@ -0,0 +1,35 @@
/**
* Create a valid URL from parsed URL parts.
* @param {import('./getSocketUrlParts').SocketUrlParts} urlParts The parsed URL parts.
* @param {import('./getWDSMetadata').WDSMetaObj} [metadata] The parsed WDS metadata object.
* @returns {string} The generated URL.
*/
function urlFromParts(urlParts, metadata) {
if (typeof metadata === 'undefined') {
metadata = {};
}
let fullProtocol = 'http:';
if (urlParts.protocol) {
fullProtocol = urlParts.protocol;
}
if (metadata.enforceWs) {
fullProtocol = fullProtocol.replace(/^(?:http|.+-extension|file)/i, 'ws');
}
fullProtocol = fullProtocol + '//';
let fullHost = urlParts.hostname;
if (urlParts.auth) {
const fullAuth = urlParts.auth.split(':').map(encodeURIComponent).join(':') + '@';
fullHost = fullAuth + fullHost;
}
if (urlParts.port) {
fullHost = fullHost + ':' + urlParts.port;
}
const url = new URL(urlParts.pathname, fullProtocol + fullHost);
return url.href;
}
module.exports = urlFromParts;

View File

@@ -0,0 +1,44 @@
/**
* @typedef {Object} WDSMetaObj
* @property {boolean} enforceWs
* @property {number} version
*/
/**
* Derives WDS metadata from a compatible socket client.
* @param {Function} SocketClient A WDS socket client (SockJS/WebSocket).
* @returns {WDSMetaObj} The parsed WDS metadata object.
*/
function getWDSMetadata(SocketClient) {
let enforceWs = false;
if (
typeof SocketClient.name !== 'undefined' &&
SocketClient.name !== null &&
SocketClient.name.toLowerCase().includes('websocket')
) {
enforceWs = true;
}
let version;
// WDS versions <=3.5.0
if (!('onMessage' in SocketClient.prototype)) {
version = 3;
} else {
// WDS versions >=3.5.0 <4
if (
'getClientPath' in SocketClient ||
Object.getPrototypeOf(SocketClient).name === 'BaseClient'
) {
version = 3;
} else {
version = 4;
}
}
return {
enforceWs: enforceWs,
version: version,
};
}
module.exports = getWDSMetadata;

View File

@@ -0,0 +1,17 @@
export = ReactRefreshLoader;
/**
* A simple Webpack loader to inject react-refresh HMR code into modules.
*
* [Reference for Loader API](https://webpack.js.org/api/loaders/)
* @this {import('webpack').LoaderContext<import('./types').ReactRefreshLoaderOptions>}
* @param {string} source The original module source code.
* @param {import('source-map').RawSourceMap} [inputSourceMap] The source map of the module.
* @param {*} [meta] The loader metadata passed in.
* @returns {void}
*/
declare function ReactRefreshLoader(
this: import('webpack').LoaderContext<import('./types').ReactRefreshLoaderOptions>,
source: string,
inputSourceMap?: import('source-map').RawSourceMap | undefined,
meta?: any
): void;

View File

@@ -0,0 +1,24 @@
export type ESModuleOptions = {
/**
* Files to explicitly exclude from flagged as ES Modules.
*/
exclude?: string | RegExp | (string | RegExp)[] | undefined;
/**
* Files to explicitly include for flagged as ES Modules.
*/
include?: string | RegExp | (string | RegExp)[] | undefined;
};
export type ReactRefreshLoaderOptions = {
/**
* Enables usage of ES6 `const` and `let` in generated runtime code.
*/
const?: boolean | undefined;
/**
* Enables strict ES Modules compatible runtime.
*/
esModule?: boolean | ESModuleOptions | undefined;
};
export type NormalizedLoaderOptions = import('type-fest').SetRequired<
ReactRefreshLoaderOptions,
'const'
>;

View File

@@ -0,0 +1,29 @@
/**
* Sets a constant default value for the property when it is undefined.
* @template T
* @template {keyof T} Property
* @param {T} object An object.
* @param {Property} property A property of the provided object.
* @param {T[Property]} [defaultValue] The default value to set for the property.
* @returns {T[Property]} The defaulted property value.
*/
export function d<T, Property extends keyof T>(
object: T,
property: Property,
defaultValue?: T[Property] | undefined
): T[Property];
/**
* Resolves the value for a nested object option.
* @template T
* @template {keyof T} Property
* @template Result
* @param {T} object An object.
* @param {Property} property A property of the provided object.
* @param {function(T | undefined): Result} fn The handler to resolve the property's value.
* @returns {Result} The resolved option value.
*/
export function n<T, Property extends keyof T, Result>(
object: T,
property: Property,
fn: (arg0: T | undefined) => Result
): Result;