fix: prevent asset conflicts between React and Grid.js versions

Add coexistence checks to all enqueue methods to prevent loading
both React and Grid.js assets simultaneously.

Changes:
- ReactAdmin.php: Only enqueue React assets when ?react=1
- Init.php: Skip Grid.js when React active on admin pages
- Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0
- Customer.php, Product.php, License.php: Add coexistence checks

Now the toggle between Classic and React versions works correctly.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dwindown
2026-04-18 17:02:14 +07:00
parent bd9cdac02e
commit e8fbfb14c1
74973 changed files with 6658406 additions and 71 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,22 @@
/**
* Gets current bundle's global scope identifier for React Refresh.
* @param {Record<string, string>} runtimeGlobals The Webpack runtime globals.
* @returns {string} The React Refresh global scope within the Webpack bundle.
*/
module.exports.getRefreshGlobalScope = (runtimeGlobals) => {
return `${runtimeGlobals.require || '__webpack_require__'}.$Refresh$`;
};
/**
* Gets current Webpack version according to features on the compiler instance.
* @param {import('webpack').Compiler} compiler The current Webpack compiler instance.
* @returns {number} The current Webpack version.
*/
module.exports.getWebpackVersion = (compiler) => {
if (!compiler.hooks) {
throw new Error(`[ReactRefreshPlugin] Webpack version is not supported!`);
}
// Webpack v5+ implements compiler caching
return 'cache' in compiler ? 5 : 4;
};

View File

@@ -0,0 +1,367 @@
const { validate: validateOptions } = require('schema-utils');
const { getRefreshGlobalScope, getWebpackVersion } = require('./globals');
const {
getAdditionalEntries,
getIntegrationEntry,
getRefreshGlobal,
getSocketIntegration,
injectRefreshEntry,
injectRefreshLoader,
makeRefreshRuntimeModule,
normalizeOptions,
} = require('./utils');
const schema = require('./options.json');
class ReactRefreshPlugin {
/**
* @param {import('./types').ReactRefreshPluginOptions} [options] Options for react-refresh-plugin.
*/
constructor(options = {}) {
validateOptions(schema, options, {
name: 'React Refresh Plugin',
baseDataPath: 'options',
});
/**
* @readonly
* @type {import('./types').NormalizedPluginOptions}
*/
this.options = normalizeOptions(options);
}
/**
* Applies the plugin.
* @param {import('webpack').Compiler} compiler A webpack compiler object.
* @returns {void}
*/
apply(compiler) {
// Skip processing in non-development mode, but allow manual force-enabling
if (
// Webpack do not set process.env.NODE_ENV, so we need to check for mode.
// Ref: https://github.com/webpack/webpack/issues/7074
(compiler.options.mode !== 'development' ||
// We also check for production process.env.NODE_ENV,
// in case it was set and mode is non-development (e.g. 'none')
(process.env.NODE_ENV && process.env.NODE_ENV === 'production')) &&
!this.options.forceEnable
) {
return;
}
const webpackVersion = getWebpackVersion(compiler);
const logger = compiler.getInfrastructureLogger(this.constructor.name);
// Get Webpack imports from compiler instance (if available) -
// this allow mono-repos to use different versions of Webpack without conflicts.
const webpack = compiler.webpack || require('webpack');
const {
DefinePlugin,
EntryDependency,
EntryPlugin,
ModuleFilenameHelpers,
NormalModule,
ProvidePlugin,
RuntimeGlobals,
Template,
} = webpack;
// Inject react-refresh context to all Webpack entry points.
// This should create `EntryDependency` objects when available,
// and fallback to patching the `entry` object for legacy workflows.
const addEntries = getAdditionalEntries({
devServer: compiler.options.devServer,
options: this.options,
});
if (EntryPlugin) {
// Prepended entries does not care about injection order,
// so we can utilise EntryPlugin for simpler logic.
addEntries.prependEntries.forEach((entry) => {
new EntryPlugin(compiler.context, entry, { name: undefined }).apply(compiler);
});
const integrationEntry = getIntegrationEntry(this.options.overlay.sockIntegration);
const socketEntryData = [];
compiler.hooks.make.tap(
{ name: this.constructor.name, stage: Number.POSITIVE_INFINITY },
(compilation) => {
// Exhaustively search all entries for `integrationEntry`.
// If found, mark those entries and the index of `integrationEntry`.
for (const [name, entryData] of compilation.entries.entries()) {
const index = entryData.dependencies.findIndex(
(dep) => dep.request && dep.request.includes(integrationEntry)
);
if (index !== -1) {
socketEntryData.push({ name, index });
}
}
}
);
// Overlay entries need to be injected AFTER integration's entry,
// so we will loop through everything in `finishMake` instead of `make`.
// This ensures we can traverse all entry points and inject stuff with the correct order.
addEntries.overlayEntries.forEach((entry, idx, arr) => {
compiler.hooks.finishMake.tapPromise(
{ name: this.constructor.name, stage: Number.MIN_SAFE_INTEGER + (arr.length - idx - 1) },
(compilation) => {
// Only hook into the current compiler
if (compilation.compiler !== compiler) {
return Promise.resolve();
}
const injectData = socketEntryData.length ? socketEntryData : [{ name: undefined }];
return Promise.all(
injectData.map(({ name, index }) => {
return new Promise((resolve, reject) => {
const options = { name };
const dep = EntryPlugin.createDependency(entry, options);
compilation.addEntry(compiler.context, dep, options, (err) => {
if (err) return reject(err);
// If the entry is not a global one,
// and we have registered the index for integration entry,
// we will reorder all entry dependencies to our desired order.
// That is, to have additional entries DIRECTLY behind integration entry.
if (name && typeof index !== 'undefined') {
const entryData = compilation.entries.get(name);
entryData.dependencies.splice(
index + 1,
0,
entryData.dependencies.splice(entryData.dependencies.length - 1, 1)[0]
);
}
resolve();
});
});
})
).then(() => {});
}
);
});
} else {
compiler.options.entry = injectRefreshEntry(compiler.options.entry, addEntries);
}
// Inject necessary modules and variables to bundle's global scope
const refreshGlobal = getRefreshGlobalScope(RuntimeGlobals || {});
/** @type {Record<string, string | boolean>}*/
const definedModules = {
// Mapping of react-refresh globals to Webpack runtime globals
$RefreshReg$: `${refreshGlobal}.register`,
$RefreshSig$: `${refreshGlobal}.signature`,
'typeof $RefreshReg$': 'function',
'typeof $RefreshSig$': 'function',
// Library mode
__react_refresh_library__: JSON.stringify(
Template.toIdentifier(
this.options.library ||
compiler.options.output.uniqueName ||
compiler.options.output.library
)
),
};
/** @type {Record<string, string>} */
const providedModules = {
__react_refresh_utils__: require.resolve('./runtime/RefreshUtils'),
};
if (this.options.overlay === false) {
// Stub errorOverlay module so their calls can be erased
definedModules.__react_refresh_error_overlay__ = false;
definedModules.__react_refresh_polyfill_url__ = false;
definedModules.__react_refresh_socket__ = false;
} else {
definedModules.__react_refresh_polyfill_url__ = this.options.overlay.useURLPolyfill || false;
if (this.options.overlay.module) {
providedModules.__react_refresh_error_overlay__ = require.resolve(
this.options.overlay.module
);
}
if (this.options.overlay.sockIntegration) {
providedModules.__react_refresh_socket__ = getSocketIntegration(
this.options.overlay.sockIntegration
);
}
}
new DefinePlugin(definedModules).apply(compiler);
new ProvidePlugin(providedModules).apply(compiler);
const match = ModuleFilenameHelpers.matchObject.bind(undefined, this.options);
let loggedHotWarning = false;
compiler.hooks.compilation.tap(
this.constructor.name,
(compilation, { normalModuleFactory }) => {
// Only hook into the current compiler
if (compilation.compiler !== compiler) {
return;
}
// Tap into version-specific compilation hooks
switch (webpackVersion) {
case 4: {
const outputOptions = compilation.mainTemplate.outputOptions;
compilation.mainTemplate.hooks.require.tap(
this.constructor.name,
// Constructs the module template for react-refresh
(source, chunk, hash) => {
// Check for the output filename
// This is to ensure we are processing a JS-related chunk
let filename = outputOptions.filename;
if (typeof filename === 'function') {
// Only usage of the `chunk` property is documented by Webpack.
// However, some internal Webpack plugins uses other properties,
// so we also pass them through to be on the safe side.
filename = filename({
contentHashType: 'javascript',
chunk,
hash,
});
}
// Check whether the current compilation is outputting to JS,
// since other plugins can trigger compilations for other file types too.
// If we apply the transform to them, their compilation will break fatally.
// One prominent example of this is the HTMLWebpackPlugin.
// If filename is falsy, something is terribly wrong and there's nothing we can do.
if (!filename || !filename.includes('.js')) {
return source;
}
// Split template source code into lines for easier processing
const lines = source.split('\n');
// Webpack generates this line when the MainTemplate is called
const moduleInitializationLineNumber = lines.findIndex((line) =>
line.includes('modules[moduleId].call(')
);
// Unable to find call to module execution -
// this happens if the current module does not call MainTemplate.
// In this case, we will return the original source and won't mess with it.
if (moduleInitializationLineNumber === -1) {
return source;
}
const moduleInterceptor = Template.asString([
`${refreshGlobal}.setup(moduleId);`,
'try {',
Template.indent(lines[moduleInitializationLineNumber]),
'} finally {',
Template.indent(`${refreshGlobal}.cleanup(moduleId);`),
'}',
]);
return Template.asString([
...lines.slice(0, moduleInitializationLineNumber),
'',
outputOptions.strictModuleExceptionHandling
? Template.indent(moduleInterceptor)
: moduleInterceptor,
'',
...lines.slice(moduleInitializationLineNumber + 1, lines.length),
]);
}
);
compilation.mainTemplate.hooks.requireExtensions.tap(
this.constructor.name,
// Setup react-refresh globals as extensions to Webpack's require function
(source) => {
return Template.asString([source, '', getRefreshGlobal(Template)]);
}
);
normalModuleFactory.hooks.afterResolve.tap(
this.constructor.name,
// Add react-refresh loader to process files that matches specified criteria
(data) => {
return injectRefreshLoader(data, {
match,
options: { const: false, esModule: false },
});
}
);
compilation.hooks.normalModuleLoader.tap(
// `Number.POSITIVE_INFINITY` ensures this check will run only after all other taps
{ name: this.constructor.name, stage: Number.POSITIVE_INFINITY },
// Check for existence of the HMR runtime -
// it is the foundation to this plugin working correctly
(context) => {
if (!context.hot && !loggedHotWarning) {
logger.warn(
[
'Hot Module Replacement (HMR) is not enabled!',
'React Refresh requires HMR to function properly.',
].join(' ')
);
loggedHotWarning = true;
}
}
);
break;
}
case 5: {
// Set factory for EntryDependency which is used to initialise the module
compilation.dependencyFactories.set(EntryDependency, normalModuleFactory);
const ReactRefreshRuntimeModule = makeRefreshRuntimeModule(webpack);
compilation.hooks.additionalTreeRuntimeRequirements.tap(
this.constructor.name,
// Setup react-refresh globals with a Webpack runtime module
(chunk, runtimeRequirements) => {
runtimeRequirements.add(RuntimeGlobals.interceptModuleExecution);
runtimeRequirements.add(RuntimeGlobals.moduleCache);
runtimeRequirements.add(refreshGlobal);
compilation.addRuntimeModule(chunk, new ReactRefreshRuntimeModule());
}
);
normalModuleFactory.hooks.afterResolve.tap(
this.constructor.name,
// Add react-refresh loader to process files that matches specified criteria
(resolveData) => {
injectRefreshLoader(resolveData.createData, {
match,
options: {
const: compilation.runtimeTemplate.supportsConst(),
esModule: this.options.esModule,
},
});
}
);
NormalModule.getCompilationHooks(compilation).loader.tap(
// `Infinity` ensures this check will run only after all other taps
{ name: this.constructor.name, stage: Infinity },
// Check for existence of the HMR runtime -
// it is the foundation to this plugin working correctly
(context) => {
if (!context.hot && !loggedHotWarning) {
logger.warn(
[
'Hot Module Replacement (HMR) is not enabled!',
'React Refresh requires HMR to function properly.',
].join(' ')
);
loggedHotWarning = true;
}
}
);
break;
}
default: {
// Do nothing - this should be an impossible case
}
}
}
);
}
}
module.exports.ReactRefreshPlugin = ReactRefreshPlugin;
module.exports = ReactRefreshPlugin;

View File

@@ -0,0 +1,79 @@
{
"additionalProperties": false,
"type": "object",
"definitions": {
"Path": { "type": "string" },
"MatchCondition": {
"anyOf": [{ "instanceof": "RegExp" }, { "$ref": "#/definitions/Path" }]
},
"MatchConditions": {
"type": "array",
"items": { "$ref": "#/definitions/MatchCondition" },
"minItems": 1
},
"ESModuleOptions": {
"additionalProperties": false,
"type": "object",
"properties": {
"exclude": {
"anyOf": [
{ "$ref": "#/definitions/MatchCondition" },
{ "$ref": "#/definitions/MatchConditions" }
]
},
"include": {
"anyOf": [
{ "$ref": "#/definitions/MatchCondition" },
{ "$ref": "#/definitions/MatchConditions" }
]
}
}
},
"OverlayOptions": {
"additionalProperties": false,
"type": "object",
"properties": {
"entry": {
"anyOf": [{ "const": false }, { "$ref": "#/definitions/Path" }]
},
"module": {
"anyOf": [{ "const": false }, { "$ref": "#/definitions/Path" }]
},
"sockIntegration": {
"anyOf": [
{ "const": false },
{ "enum": ["wds", "whm", "wps"] },
{ "$ref": "#/definitions/Path" }
]
},
"sockHost": { "type": "string" },
"sockPath": { "type": "string" },
"sockPort": { "type": "number", "minimum": 0 },
"sockProtocol": { "enum": ["http", "https", "ws", "wss"] },
"useURLPolyfill": { "type": "boolean" }
}
}
},
"properties": {
"esModule": {
"anyOf": [{ "type": "boolean" }, { "$ref": "#/definitions/ESModuleOptions" }]
},
"exclude": {
"anyOf": [
{ "$ref": "#/definitions/MatchCondition" },
{ "$ref": "#/definitions/MatchConditions" }
]
},
"forceEnable": { "type": "boolean" },
"include": {
"anyOf": [
{ "$ref": "#/definitions/MatchCondition" },
{ "$ref": "#/definitions/MatchConditions" }
]
},
"library": { "type": "string" },
"overlay": {
"anyOf": [{ "type": "boolean" }, { "$ref": "#/definitions/OverlayOptions" }]
}
}
}

View File

@@ -0,0 +1,286 @@
/* global __webpack_require__ */
var Refresh = require('react-refresh/runtime');
/**
* Extracts exports from a webpack module object.
* @param {string} moduleId A Webpack module ID.
* @returns {*} An exports object from the module.
*/
function getModuleExports(moduleId) {
if (typeof moduleId === 'undefined') {
// `moduleId` is unavailable, which indicates that this module is not in the cache,
// which means we won't be able to capture any exports,
// and thus they cannot be refreshed safely.
// These are likely runtime or dynamically generated modules.
return {};
}
var maybeModule = __webpack_require__.c[moduleId];
if (typeof maybeModule === 'undefined') {
// `moduleId` is available but the module in cache is unavailable,
// which indicates the module is somehow corrupted (e.g. broken Webpacak `module` globals).
// We will warn the user (as this is likely a mistake) and assume they cannot be refreshed.
console.warn('[React Refresh] Failed to get exports for module: ' + moduleId + '.');
return {};
}
var exportsOrPromise = maybeModule.exports;
if (typeof Promise !== 'undefined' && exportsOrPromise instanceof Promise) {
return exportsOrPromise.then(function (exports) {
return exports;
});
}
return exportsOrPromise;
}
/**
* Calculates the signature of a React refresh boundary.
* If this signature changes, it's unsafe to accept the boundary.
*
* This implementation is based on the one in [Metro](https://github.com/facebook/metro/blob/907d6af22ac6ebe58572be418e9253a90665ecbd/packages/metro/src/lib/polyfills/require.js#L795-L816).
* @param {*} moduleExports A Webpack module exports object.
* @returns {string[]} A React refresh boundary signature array.
*/
function getReactRefreshBoundarySignature(moduleExports) {
var signature = [];
signature.push(Refresh.getFamilyByType(moduleExports));
if (moduleExports == null || typeof moduleExports !== 'object') {
// Exit if we can't iterate over exports.
return signature;
}
for (var key in moduleExports) {
if (key === '__esModule') {
continue;
}
signature.push(key);
signature.push(Refresh.getFamilyByType(moduleExports[key]));
}
return signature;
}
/**
* Creates a data object to be retained across refreshes.
* This object should not transtively reference previous exports,
* which can form infinite chain of objects across refreshes, which can pressure RAM.
*
* @param {*} moduleExports A Webpack module exports object.
* @returns {*} A React refresh boundary signature array.
*/
function getWebpackHotData(moduleExports) {
return {
signature: getReactRefreshBoundarySignature(moduleExports),
isReactRefreshBoundary: isReactRefreshBoundary(moduleExports),
};
}
/**
* Creates a helper that performs a delayed React refresh.
* @returns {function(function(): void): void} A debounced React refresh function.
*/
function createDebounceUpdate() {
/**
* A cached setTimeout handler.
* @type {number | undefined}
*/
var refreshTimeout;
/**
* Performs react refresh on a delay and clears the error overlay.
* @param {function(): void} callback
* @returns {void}
*/
function enqueueUpdate(callback) {
if (typeof refreshTimeout === 'undefined') {
refreshTimeout = setTimeout(function () {
refreshTimeout = undefined;
Refresh.performReactRefresh();
callback();
}, 30);
}
}
return enqueueUpdate;
}
/**
* Checks if all exports are likely a React component.
*
* This implementation is based on the one in [Metro](https://github.com/facebook/metro/blob/febdba2383113c88296c61e28e4ef6a7f4939fda/packages/metro/src/lib/polyfills/require.js#L748-L774).
* @param {*} moduleExports A Webpack module exports object.
* @returns {boolean} Whether the exports are React component like.
*/
function isReactRefreshBoundary(moduleExports) {
if (Refresh.isLikelyComponentType(moduleExports)) {
return true;
}
if (moduleExports === undefined || moduleExports === null || typeof moduleExports !== 'object') {
// Exit if we can't iterate over exports.
return false;
}
var hasExports = false;
var areAllExportsComponents = true;
for (var key in moduleExports) {
hasExports = true;
// This is the ES Module indicator flag
if (key === '__esModule') {
continue;
}
// We can (and have to) safely execute getters here,
// as Webpack manually assigns harmony exports to getters,
// without any side-effects attached.
// Ref: https://github.com/webpack/webpack/blob/b93048643fe74de2a6931755911da1212df55897/lib/MainTemplate.js#L281
var exportValue = moduleExports[key];
if (!Refresh.isLikelyComponentType(exportValue)) {
areAllExportsComponents = false;
}
}
return hasExports && areAllExportsComponents;
}
/**
* Checks if exports are likely a React component and registers them.
*
* This implementation is based on the one in [Metro](https://github.com/facebook/metro/blob/febdba2383113c88296c61e28e4ef6a7f4939fda/packages/metro/src/lib/polyfills/require.js#L818-L835).
* @param {*} moduleExports A Webpack module exports object.
* @param {string} moduleId A Webpack module ID.
* @returns {void}
*/
function registerExportsForReactRefresh(moduleExports, moduleId) {
if (Refresh.isLikelyComponentType(moduleExports)) {
// Register module.exports if it is likely a component
Refresh.register(moduleExports, moduleId + ' %exports%');
}
if (moduleExports === undefined || moduleExports === null || typeof moduleExports !== 'object') {
// Exit if we can't iterate over the exports.
return;
}
for (var key in moduleExports) {
// Skip registering the ES Module indicator
if (key === '__esModule') {
continue;
}
var exportValue = moduleExports[key];
if (Refresh.isLikelyComponentType(exportValue)) {
var typeID = moduleId + ' %exports% ' + key;
Refresh.register(exportValue, typeID);
}
}
}
/**
* Compares previous and next module objects to check for mutated boundaries.
*
* This implementation is based on the one in [Metro](https://github.com/facebook/metro/blob/907d6af22ac6ebe58572be418e9253a90665ecbd/packages/metro/src/lib/polyfills/require.js#L776-L792).
* @param {*} prevSignature The signature of the current Webpack module exports object.
* @param {*} nextSignature The signature of the next Webpack module exports object.
* @returns {boolean} Whether the React refresh boundary should be invalidated.
*/
function shouldInvalidateReactRefreshBoundary(prevSignature, nextSignature) {
if (prevSignature.length !== nextSignature.length) {
return true;
}
for (var i = 0; i < nextSignature.length; i += 1) {
if (prevSignature[i] !== nextSignature[i]) {
return true;
}
}
return false;
}
var enqueueUpdate = createDebounceUpdate();
function executeRuntime(moduleExports, moduleId, webpackHot, refreshOverlay, isTest) {
registerExportsForReactRefresh(moduleExports, moduleId);
if (webpackHot) {
var isHotUpdate = !!webpackHot.data;
var prevData;
if (isHotUpdate) {
prevData = webpackHot.data.prevData;
}
if (isReactRefreshBoundary(moduleExports)) {
webpackHot.dispose(
/**
* A callback to performs a full refresh if React has unrecoverable errors,
* and also caches the to-be-disposed module.
* @param {*} data A hot module data object from Webpack HMR.
* @returns {void}
*/
function hotDisposeCallback(data) {
// We have to mutate the data object to get data registered and cached
data.prevData = getWebpackHotData(moduleExports);
}
);
webpackHot.accept(
/**
* An error handler to allow self-recovering behaviours.
* @param {Error} error An error occurred during evaluation of a module.
* @returns {void}
*/
function hotErrorHandler(error) {
if (typeof refreshOverlay !== 'undefined' && refreshOverlay) {
refreshOverlay.handleRuntimeError(error);
}
if (typeof isTest !== 'undefined' && isTest) {
if (window.onHotAcceptError) {
window.onHotAcceptError(error.message);
}
}
__webpack_require__.c[moduleId].hot.accept(hotErrorHandler);
}
);
if (isHotUpdate) {
if (
prevData &&
prevData.isReactRefreshBoundary &&
shouldInvalidateReactRefreshBoundary(
prevData.signature,
getReactRefreshBoundarySignature(moduleExports)
)
) {
webpackHot.invalidate();
} else {
enqueueUpdate(
/**
* A function to dismiss the error overlay after performing React refresh.
* @returns {void}
*/
function updateCallback() {
if (typeof refreshOverlay !== 'undefined' && refreshOverlay) {
refreshOverlay.clearRuntimeErrors();
}
}
);
}
}
} else {
if (isHotUpdate && typeof prevData !== 'undefined') {
webpackHot.invalidate();
}
}
}
}
module.exports = Object.freeze({
enqueueUpdate: enqueueUpdate,
executeRuntime: executeRuntime,
getModuleExports: getModuleExports,
isReactRefreshBoundary: isReactRefreshBoundary,
registerExportsForReactRefresh: registerExportsForReactRefresh,
});

View File

@@ -0,0 +1,36 @@
/**
* @typedef {Object} ErrorOverlayOptions
* @property {string | false} [entry] Path to a JS file that sets up the error overlay integration.
* @property {string | false} [module] The error overlay module to use.
* @property {string} [sockHost] The socket host to use (WDS only).
* @property {import('type-fest').LiteralUnion<'wds' | 'whm' | 'wps' | false, string>} [sockIntegration] Path to a JS file that sets up the Webpack socket integration.
* @property {string} [sockPath] The socket path to use (WDS only).
* @property {number} [sockPort] The socket port to use (WDS only).
* @property {'http' | 'https' | 'ws' | 'wss'} [sockProtocol] The socket protocol to use (WDS only).
* @property {boolean} [useURLPolyfill] Uses a polyfill for the DOM URL API (WDS only).
*/
/**
* @typedef {import('type-fest').SetRequired<ErrorOverlayOptions, 'entry' | 'module' | 'sockIntegration'>} NormalizedErrorOverlayOptions
*/
/**
* @typedef {Object} ReactRefreshPluginOptions
* @property {boolean | import('../loader/types').ESModuleOptions} [esModule] Enables strict ES Modules compatible runtime.
* @property {string | RegExp | Array<string | RegExp>} [exclude] Files to explicitly exclude from processing.
* @property {boolean} [forceEnable] Enables the plugin forcefully.
* @property {string | RegExp | Array<string | RegExp>} [include] Files to explicitly include for processing.
* @property {string} [library] Name of the library bundle.
* @property {boolean | ErrorOverlayOptions} [overlay] Modifies how the error overlay integration works in the plugin.
*/
/**
* @typedef {Object} OverlayOverrides
* @property {false | NormalizedErrorOverlayOptions} overlay Modifies how the error overlay integration works in the plugin.
*/
/**
* @typedef {import('type-fest').SetRequired<import('type-fest').Except<ReactRefreshPluginOptions, 'overlay'>, 'exclude' | 'include'> & OverlayOverrides} NormalizedPluginOptions
*/
module.exports = {};

View File

@@ -0,0 +1,95 @@
const querystring = require('querystring');
/**
* @typedef {Object} AdditionalEntries
* @property {string[]} prependEntries
* @property {string[]} overlayEntries
*/
/**
* Creates an object that contains two entry arrays: the prependEntries and overlayEntries
* @param {Object} optionsContainer This is the container for the options to this function
* @param {import('../types').NormalizedPluginOptions} optionsContainer.options Configuration options for this plugin.
* @param {import('webpack').Compiler["options"]["devServer"]} [optionsContainer.devServer] The webpack devServer config
* @returns {AdditionalEntries} An object that contains the Webpack entries for prepending and the overlay feature
*/
function getAdditionalEntries({ devServer, options }) {
/** @type {Record<string, string | number>} */
let resourceQuery = {};
if (devServer) {
const { client, https, http2, sockHost, sockPath, sockPort } = devServer;
let { host, path, port } = devServer;
let protocol = https || http2 ? 'https' : 'http';
if (sockHost) host = sockHost;
if (sockPath) path = sockPath;
if (sockPort) port = sockPort;
if (client && client.webSocketURL != null) {
let parsedUrl = client.webSocketURL;
if (typeof parsedUrl === 'string') parsedUrl = new URL(parsedUrl);
let auth;
if (parsedUrl.username) {
auth = parsedUrl.username;
if (parsedUrl.password) {
auth += ':' + parsedUrl.password;
}
}
if (parsedUrl.hostname != null) {
host = [auth != null && auth, parsedUrl.hostname].filter(Boolean).join('@');
}
if (parsedUrl.pathname != null) {
path = parsedUrl.pathname;
}
if (parsedUrl.port != null) {
port = !['0', 'auto'].includes(String(parsedUrl.port)) ? parsedUrl.port : undefined;
}
if (parsedUrl.protocol != null) {
protocol = parsedUrl.protocol !== 'auto' ? parsedUrl.protocol.replace(':', '') : 'ws';
}
}
if (host) resourceQuery.sockHost = host;
if (path) resourceQuery.sockPath = path;
if (port) resourceQuery.sockPort = port;
resourceQuery.sockProtocol = protocol;
}
if (options.overlay) {
const { sockHost, sockPath, sockPort, sockProtocol } = options.overlay;
if (sockHost) resourceQuery.sockHost = sockHost;
if (sockPath) resourceQuery.sockPath = sockPath;
if (sockPort) resourceQuery.sockPort = sockPort;
if (sockProtocol) resourceQuery.sockProtocol = sockProtocol;
}
// We don't need to URI encode the resourceQuery as it will be parsed by Webpack
const queryString = querystring.stringify(resourceQuery, undefined, undefined, {
/**
* @param {string} string
* @returns {string}
*/
encodeURIComponent(string) {
return string;
},
});
const prependEntries = [
// React-refresh runtime
require.resolve('../../client/ReactRefreshEntry'),
];
const overlayEntries = [
// Error overlay runtime
options.overlay &&
options.overlay.entry &&
`${require.resolve(options.overlay.entry)}${queryString ? `?${queryString}` : ''}`,
].filter(Boolean);
return { prependEntries, overlayEntries };
}
module.exports = getAdditionalEntries;

View File

@@ -0,0 +1,22 @@
/**
* Gets entry point of a supported socket integration.
* @param {'wds' | 'whm' | 'wps' | string} integrationType A valid socket integration type or a path to a module.
* @returns {string | undefined} Path to the resolved integration entry point.
*/
function getIntegrationEntry(integrationType) {
let resolvedEntry;
switch (integrationType) {
case 'whm': {
resolvedEntry = 'webpack-hot-middleware/client';
break;
}
case 'wps': {
resolvedEntry = 'webpack-plugin-serve/client';
break;
}
}
return resolvedEntry;
}
module.exports = getIntegrationEntry;

View File

@@ -0,0 +1,96 @@
const { getRefreshGlobalScope } = require('../globals');
/**
* @typedef {Object} RuntimeTemplate
* @property {function(string, string[]): string} basicFunction
* @property {function(): boolean} supportsConst
* @property {function(string, string=): string} returningFunction
*/
/**
* Generates the refresh global runtime template.
* @param {import('webpack').Template} Template The template helpers.
* @param {Record<string, string>} [RuntimeGlobals] The runtime globals.
* @param {RuntimeTemplate} [RuntimeTemplate] The runtime template helpers.
* @returns {string} The refresh global runtime template.
*/
function getRefreshGlobal(
Template,
RuntimeGlobals = {},
RuntimeTemplate = {
basicFunction(args, body) {
return `function(${args}) {\n${Template.indent(body)}\n}`;
},
supportsConst() {
return false;
},
returningFunction(returnValue, args = '') {
return `function(${args}) { return ${returnValue}; }`;
},
}
) {
const declaration = RuntimeTemplate.supportsConst() ? 'const' : 'var';
const refreshGlobal = getRefreshGlobalScope(RuntimeGlobals);
return Template.asString([
`${refreshGlobal} = {`,
Template.indent([
// Lifecycle methods - They should be specific per module and restored after module execution.
// These stubs ensure unwanted calls (e.g. unsupported patterns, broken transform) would not error out.
// If the current module is processed by our loader,
// they will be swapped in place during module initialisation by the `setup` method below.
`register: ${RuntimeTemplate.returningFunction('undefined')},`,
`signature: ${RuntimeTemplate.returningFunction(
RuntimeTemplate.returningFunction('type', 'type')
)},`,
// Runtime - This should be a singleton and persist throughout the lifetime of the app.
// This stub ensures calls to `runtime` would not error out.
// If any module within the bundle is processed by our loader,
// it will be swapped in place via an injected import.
'runtime: {',
Template.indent([
`createSignatureFunctionForTransform: ${RuntimeTemplate.returningFunction(
RuntimeTemplate.returningFunction('type', 'type')
)},`,
`register: ${RuntimeTemplate.returningFunction('undefined')}`,
]),
'},',
// Setup - This handles initialisation of the global runtime.
// It should never be touched throughout the lifetime of the app.
`setup: ${RuntimeTemplate.basicFunction('currentModuleId', [
// Store all previous values for fields on `refreshGlobal` -
// this allows proper restoration in the `cleanup` phase.
`${declaration} prevModuleId = ${refreshGlobal}.moduleId;`,
`${declaration} prevRegister = ${refreshGlobal}.register;`,
`${declaration} prevSignature = ${refreshGlobal}.signature;`,
`${declaration} prevCleanup = ${refreshGlobal}.cleanup;`,
'',
`${refreshGlobal}.moduleId = currentModuleId;`,
'',
`${refreshGlobal}.register = ${RuntimeTemplate.basicFunction('type, id', [
`${declaration} typeId = currentModuleId + " " + id;`,
`${refreshGlobal}.runtime.register(type, typeId);`,
])}`,
'',
`${refreshGlobal}.signature = ${RuntimeTemplate.returningFunction(
`${refreshGlobal}.runtime.createSignatureFunctionForTransform()`
)};`,
'',
`${refreshGlobal}.cleanup = ${RuntimeTemplate.basicFunction('cleanupModuleId', [
// Only cleanup if the module IDs match.
// In rare cases, it might get called in another module's `cleanup` phase.
'if (currentModuleId === cleanupModuleId) {',
Template.indent([
`${refreshGlobal}.moduleId = prevModuleId;`,
`${refreshGlobal}.register = prevRegister;`,
`${refreshGlobal}.signature = prevSignature;`,
`${refreshGlobal}.cleanup = prevCleanup;`,
]),
'}',
])}`,
])}`,
]),
'};',
]);
}
module.exports = getRefreshGlobal;

View File

@@ -0,0 +1,30 @@
/**
* Gets the socket integration to use for Webpack messages.
* @param {'wds' | 'whm' | 'wps' | string} integrationType A valid socket integration type or a path to a module.
* @returns {string} Path to the resolved socket integration module.
*/
function getSocketIntegration(integrationType) {
let resolvedSocketIntegration;
switch (integrationType) {
case 'wds': {
resolvedSocketIntegration = require.resolve('../../sockets/WDSSocket');
break;
}
case 'whm': {
resolvedSocketIntegration = require.resolve('../../sockets/WHMEventSource');
break;
}
case 'wps': {
resolvedSocketIntegration = require.resolve('../../sockets/WPSSocket');
break;
}
default: {
resolvedSocketIntegration = require.resolve(integrationType);
break;
}
}
return resolvedSocketIntegration;
}
module.exports = getSocketIntegration;

View File

@@ -0,0 +1,19 @@
const getAdditionalEntries = require('./getAdditionalEntries');
const getIntegrationEntry = require('./getIntegrationEntry');
const getRefreshGlobal = require('./getRefreshGlobal');
const getSocketIntegration = require('./getSocketIntegration');
const injectRefreshEntry = require('./injectRefreshEntry');
const injectRefreshLoader = require('./injectRefreshLoader');
const makeRefreshRuntimeModule = require('./makeRefreshRuntimeModule');
const normalizeOptions = require('./normalizeOptions');
module.exports = {
getAdditionalEntries,
getIntegrationEntry,
getRefreshGlobal,
getSocketIntegration,
injectRefreshEntry,
injectRefreshLoader,
makeRefreshRuntimeModule,
normalizeOptions,
};

View File

@@ -0,0 +1,98 @@
/** @typedef {string | string[] | import('webpack').Entry} StaticEntry */
/** @typedef {StaticEntry | import('webpack').EntryFunc} WebpackEntry */
const EntryParseError = new Error(
[
'[ReactRefreshPlugin]',
'Failed to parse the Webpack `entry` object!',
'Please ensure the `entry` option in your Webpack config is specified.',
].join(' ')
);
/**
* Webpack entries related to socket integrations.
* They have to run before any code that sets up the error overlay.
* @type {string[]}
*/
const socketEntries = [
'webpack-dev-server/client',
'webpack-hot-middleware/client',
'webpack-plugin-serve/client',
'react-dev-utils/webpackHotDevClient',
];
/**
* Checks if a Webpack entry string is related to socket integrations.
* @param {string} entry A Webpack entry string.
* @returns {boolean} Whether the entry is related to socket integrations.
*/
function isSocketEntry(entry) {
return socketEntries.some((socketEntry) => entry.includes(socketEntry));
}
/**
* Injects an entry to the bundle for react-refresh.
* @param {WebpackEntry} [originalEntry] A Webpack entry object.
* @param {import('./getAdditionalEntries').AdditionalEntries} additionalEntries An object that contains the Webpack entries for prepending and the overlay feature
* @returns {WebpackEntry} An injected entry object.
*/
function injectRefreshEntry(originalEntry, additionalEntries) {
const { prependEntries, overlayEntries } = additionalEntries;
// Single string entry point
if (typeof originalEntry === 'string') {
if (isSocketEntry(originalEntry)) {
return [...prependEntries, originalEntry, ...overlayEntries];
}
return [...prependEntries, ...overlayEntries, originalEntry];
}
// Single array entry point
if (Array.isArray(originalEntry)) {
if (originalEntry.length === 0) {
throw EntryParseError;
}
const socketEntryIndex = originalEntry.findIndex(isSocketEntry);
let socketAndPrecedingEntries = [];
if (socketEntryIndex !== -1) {
socketAndPrecedingEntries = originalEntry.splice(0, socketEntryIndex + 1);
}
return [...prependEntries, ...socketAndPrecedingEntries, ...overlayEntries, ...originalEntry];
}
// Multiple entry points
if (typeof originalEntry === 'object') {
const entries = Object.entries(originalEntry);
if (entries.length === 0) {
throw EntryParseError;
}
return entries.reduce(
(acc, [curKey, curEntry]) => ({
...acc,
[curKey]:
typeof curEntry === 'object' && curEntry.import
? {
...curEntry,
import: injectRefreshEntry(curEntry.import, additionalEntries),
}
: injectRefreshEntry(curEntry, additionalEntries),
}),
{}
);
}
// Dynamic entry points
if (typeof originalEntry === 'function') {
return (...args) =>
Promise.resolve(originalEntry(...args)).then((resolvedEntry) =>
injectRefreshEntry(resolvedEntry, additionalEntries)
);
}
throw EntryParseError;
}
module.exports = injectRefreshEntry;
module.exports.socketEntries = socketEntries;

View File

@@ -0,0 +1,58 @@
const path = require('path');
/**
* @callback MatchObject
* @param {string} [str]
* @returns {boolean}
*/
/**
* @typedef {Object} InjectLoaderOptions
* @property {MatchObject} match A function to include/exclude files to be processed.
* @property {import('../../loader/types').ReactRefreshLoaderOptions} [options] Options passed to the loader.
*/
const resolvedLoader = require.resolve('../../loader');
const reactRefreshPath = path.dirname(require.resolve('react-refresh'));
const refreshUtilsPath = path.join(__dirname, '../runtime/RefreshUtils');
/**
* Injects refresh loader to all JavaScript-like and user-specified files.
* @param {*} moduleData Module factory creation data.
* @param {InjectLoaderOptions} injectOptions Options to alter how the loader is injected.
* @returns {*} The injected module factory creation data.
*/
function injectRefreshLoader(moduleData, injectOptions) {
const { match, options } = injectOptions;
// Include and exclude user-specified files
if (!match(moduleData.matchResource || moduleData.resource)) return moduleData;
// Include and exclude dynamically generated modules from other loaders
if (moduleData.matchResource && !match(moduleData.request)) return moduleData;
// Exclude files referenced as assets
if (moduleData.type.includes('asset')) return moduleData;
// Check to prevent double injection
if (moduleData.loaders.find(({ loader }) => loader === resolvedLoader)) return moduleData;
// Skip react-refresh and the plugin's runtime utils to prevent self-referencing -
// this is useful when using the plugin as a direct dependency,
// or when node_modules are specified to be processed.
if (
moduleData.resource.includes(reactRefreshPath) ||
moduleData.resource.includes(refreshUtilsPath)
) {
return moduleData;
}
// As we inject runtime code for each module,
// it is important to run the injected loader after everything.
// This way we can ensure that all code-processing have been done,
// and we won't risk breaking tools like Flow or ESLint.
moduleData.loaders.unshift({
loader: resolvedLoader,
options,
});
return moduleData;
}
module.exports = injectRefreshLoader;

View File

@@ -0,0 +1,96 @@
/**
* Makes a runtime module to intercept module execution for React Refresh.
* This module creates an isolated `__webpack_require__` function for each module,
* and injects a `$Refresh$` object into it for use by React Refresh.
* @param {import('webpack')} webpack The Webpack exports.
* @returns {new () => import('webpack').RuntimeModule} The runtime module class.
*/
function makeRefreshRuntimeModule(webpack) {
return class ReactRefreshRuntimeModule extends webpack.RuntimeModule {
constructor() {
// Second argument is the `stage` for this runtime module -
// we'll use the same stage as Webpack's HMR runtime module for safety.
super('react refresh', webpack.RuntimeModule.STAGE_BASIC);
}
/**
* @returns {string} runtime code
*/
generate() {
if (!this.compilation) throw new Error('Webpack compilation missing!');
const { runtimeTemplate } = this.compilation;
const declareVar = runtimeTemplate.supportsConst() ? 'const' : 'var';
return webpack.Template.asString([
`${declareVar} setup = ${runtimeTemplate.basicFunction('moduleId', [
`${declareVar} refresh = {`,
webpack.Template.indent([
`moduleId: moduleId,`,
`register: ${runtimeTemplate.basicFunction('type, id', [
`${declareVar} typeId = moduleId + " " + id;`,
`refresh.runtime.register(type, typeId);`,
])},`,
`signature: ${runtimeTemplate.returningFunction(
'refresh.runtime.createSignatureFunctionForTransform()'
)},`,
`runtime: {`,
webpack.Template.indent([
`createSignatureFunctionForTransform: ${runtimeTemplate.returningFunction(
runtimeTemplate.returningFunction('type', 'type')
)},`,
`register: ${runtimeTemplate.emptyFunction()}`,
]),
`},`,
]),
`};`,
`return refresh;`,
])}`,
'',
`${webpack.RuntimeGlobals.interceptModuleExecution}.push(${runtimeTemplate.basicFunction(
'options',
[
`${declareVar} originalFactory = options.factory;`,
// Using a function declaration -
// ensures `this` would propagate for modules relying on it
`options.factory = function(moduleObject, moduleExports, webpackRequire) {`,
webpack.Template.indent([
// Our require function delegates to the original require function
`${declareVar} hotRequire = ${runtimeTemplate.returningFunction(
'webpackRequire(request)',
'request'
)};`,
// The propery descriptor factory below ensures all properties but `$Refresh$`
// are proxied through to the original require function
`${declareVar} createPropertyDescriptor = ${runtimeTemplate.basicFunction('name', [
`return {`,
webpack.Template.indent([
`configurable: true,`,
`enumerable: true,`,
`get: ${runtimeTemplate.returningFunction('webpackRequire[name]')},`,
`set: ${runtimeTemplate.basicFunction('value', [
'webpackRequire[name] = value;',
])},`,
]),
`};`,
])};`,
`for (${declareVar} name in webpackRequire) {`,
webpack.Template.indent([
`if (Object.prototype.hasOwnProperty.call(webpackRequire, name) && name !== "$Refresh$") {`,
webpack.Template.indent([
`Object.defineProperty(hotRequire, name, createPropertyDescriptor(name));`,
]),
`}`,
]),
`}`,
`hotRequire.$Refresh$ = setup(options.id);`,
`originalFactory.call(this, moduleObject, moduleExports, hotRequire);`,
]),
'};',
]
)});`,
]);
}
};
}
module.exports = makeRefreshRuntimeModule;

View File

@@ -0,0 +1,44 @@
const { d, n } = require('../../options');
/**
* Normalizes the options for the plugin.
* @param {import('../types').ReactRefreshPluginOptions} options Non-normalized plugin options.
* @returns {import('../types').NormalizedPluginOptions} Normalized plugin options.
*/
const normalizeOptions = (options) => {
d(options, 'exclude', /node_modules/i);
d(options, 'include', /\.([cm]js|[jt]sx?|flow)$/i);
d(options, 'forceEnable');
d(options, 'library');
n(options, 'overlay', (overlay) => {
/** @type {import('../types').NormalizedErrorOverlayOptions} */
const defaults = {
entry: require.resolve('../../client/ErrorOverlayEntry'),
module: require.resolve('../../overlay'),
sockIntegration: 'wds',
};
if (overlay === false) {
return false;
}
if (typeof overlay === 'undefined' || overlay === true) {
return defaults;
}
d(overlay, 'entry', defaults.entry);
d(overlay, 'module', defaults.module);
d(overlay, 'sockIntegration', defaults.sockIntegration);
d(overlay, 'sockHost');
d(overlay, 'sockPath');
d(overlay, 'sockPort');
d(overlay, 'sockProtocol');
d(options, 'useURLPolyfill');
return overlay;
});
return options;
};
module.exports = normalizeOptions;

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,28 @@
Copyright (c) 2009-2011, Mozilla Foundation and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the names of the Mozilla Foundation nor the names of project
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,837 @@
# Source Map
[![NPM](https://nodei.co/npm/source-map.png?downloads=true&downloadRank=true)](https://www.npmjs.com/package/source-map)
This is a library to generate and consume the source map format
[described here][format].
[format]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
## Use with Node
$ npm install source-map
## Use on the Web
```html
<script src="https://unpkg.com/source-map@0.7.3/dist/source-map.js"></script>
<script>
sourceMap.SourceMapConsumer.initialize({
"lib/mappings.wasm": "https://unpkg.com/source-map@0.7.3/lib/mappings.wasm",
});
</script>
```
---
## Table of Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Examples](#examples)
- [Consuming a source map](#consuming-a-source-map)
- [Generating a source map](#generating-a-source-map)
- [With SourceNode (high level API)](#with-sourcenode-high-level-api)
- [With SourceMapGenerator (low level API)](#with-sourcemapgenerator-low-level-api)
- [API](#api)
- [SourceMapConsumer](#sourcemapconsumer)
- [SourceMapConsumer.initialize(options)](#sourcemapconsumerinitializeoptions)
- [new SourceMapConsumer(rawSourceMap)](#new-sourcemapconsumerrawsourcemap)
- [SourceMapConsumer.with](#sourcemapconsumerwith)
- [SourceMapConsumer.prototype.destroy()](#sourcemapconsumerprototypedestroy)
- [SourceMapConsumer.prototype.computeColumnSpans()](#sourcemapconsumerprototypecomputecolumnspans)
- [SourceMapConsumer.prototype.originalPositionFor(generatedPosition)](#sourcemapconsumerprototypeoriginalpositionforgeneratedposition)
- [SourceMapConsumer.prototype.generatedPositionFor(originalPosition)](#sourcemapconsumerprototypegeneratedpositionfororiginalposition)
- [SourceMapConsumer.prototype.allGeneratedPositionsFor(originalPosition)](#sourcemapconsumerprototypeallgeneratedpositionsfororiginalposition)
- [SourceMapConsumer.prototype.hasContentsOfAllSources()](#sourcemapconsumerprototypehascontentsofallsources)
- [SourceMapConsumer.prototype.sourceContentFor(source[, returnNullOnMissing])](#sourcemapconsumerprototypesourcecontentforsource-returnnullonmissing)
- [SourceMapConsumer.prototype.eachMapping(callback, context, order)](#sourcemapconsumerprototypeeachmappingcallback-context-order)
- [SourceMapGenerator](#sourcemapgenerator)
- [new SourceMapGenerator([startOfSourceMap])](#new-sourcemapgeneratorstartofsourcemap)
- [SourceMapGenerator.fromSourceMap(sourceMapConsumer)](#sourcemapgeneratorfromsourcemapsourcemapconsumer)
- [SourceMapGenerator.prototype.addMapping(mapping)](#sourcemapgeneratorprototypeaddmappingmapping)
- [SourceMapGenerator.prototype.setSourceContent(sourceFile, sourceContent)](#sourcemapgeneratorprototypesetsourcecontentsourcefile-sourcecontent)
- [SourceMapGenerator.prototype.applySourceMap(sourceMapConsumer[, sourceFile[, sourceMapPath]])](#sourcemapgeneratorprototypeapplysourcemapsourcemapconsumer-sourcefile-sourcemappath)
- [SourceMapGenerator.prototype.toString()](#sourcemapgeneratorprototypetostring)
- [SourceNode](#sourcenode)
- [new SourceNode([line, column, source[, chunk[, name]]])](#new-sourcenodeline-column-source-chunk-name)
- [SourceNode.fromStringWithSourceMap(code, sourceMapConsumer[, relativePath])](#sourcenodefromstringwithsourcemapcode-sourcemapconsumer-relativepath)
- [SourceNode.prototype.add(chunk)](#sourcenodeprototypeaddchunk)
- [SourceNode.prototype.prepend(chunk)](#sourcenodeprototypeprependchunk)
- [SourceNode.prototype.setSourceContent(sourceFile, sourceContent)](#sourcenodeprototypesetsourcecontentsourcefile-sourcecontent)
- [SourceNode.prototype.walk(fn)](#sourcenodeprototypewalkfn)
- [SourceNode.prototype.walkSourceContents(fn)](#sourcenodeprototypewalksourcecontentsfn)
- [SourceNode.prototype.join(sep)](#sourcenodeprototypejoinsep)
- [SourceNode.prototype.replaceRight(pattern, replacement)](#sourcenodeprototypereplacerightpattern-replacement)
- [SourceNode.prototype.toString()](#sourcenodeprototypetostring)
- [SourceNode.prototype.toStringWithSourceMap([startOfSourceMap])](#sourcenodeprototypetostringwithsourcemapstartofsourcemap)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Examples
### Consuming a source map
```js
const rawSourceMap = {
version: 3,
file: "min.js",
names: ["bar", "baz", "n"],
sources: ["one.js", "two.js"],
sourceRoot: "http://example.com/www/js/",
mappings:
"CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA",
};
const whatever = await SourceMapConsumer.with(rawSourceMap, null, consumer => {
console.log(consumer.sources);
// [ 'http://example.com/www/js/one.js',
// 'http://example.com/www/js/two.js' ]
console.log(
consumer.originalPositionFor({
line: 2,
column: 28,
})
);
// { source: 'http://example.com/www/js/two.js',
// line: 2,
// column: 10,
// name: 'n' }
console.log(
consumer.generatedPositionFor({
source: "http://example.com/www/js/two.js",
line: 2,
column: 10,
})
);
// { line: 2, column: 28 }
consumer.eachMapping(function (m) {
// ...
});
return computeWhatever();
});
```
### Generating a source map
In depth guide:
[**Compiling to JavaScript, and Debugging with Source Maps**](https://hacks.mozilla.org/2013/05/compiling-to-javascript-and-debugging-with-source-maps/)
#### With SourceNode (high level API)
```js
function compile(ast) {
switch (ast.type) {
case "BinaryExpression":
return new SourceNode(
ast.location.line,
ast.location.column,
ast.location.source,
[compile(ast.left), " + ", compile(ast.right)]
);
case "Literal":
return new SourceNode(
ast.location.line,
ast.location.column,
ast.location.source,
String(ast.value)
);
// ...
default:
throw new Error("Bad AST");
}
}
var ast = parse("40 + 2", "add.js");
console.log(
compile(ast).toStringWithSourceMap({
file: "add.js",
})
);
// { code: '40 + 2',
// map: [object SourceMapGenerator] }
```
#### With SourceMapGenerator (low level API)
```js
var map = new SourceMapGenerator({
file: "source-mapped.js",
});
map.addMapping({
generated: {
line: 10,
column: 35,
},
source: "foo.js",
original: {
line: 33,
column: 2,
},
name: "christopher",
});
console.log(map.toString());
// '{"version":3,"file":"source-mapped.js","sources":["foo.js"],"names":["christopher"],"mappings":";;;;;;;;;mCAgCEA"}'
```
## API
Get a reference to the module:
```js
// Node.js
var sourceMap = require("source-map");
// Browser builds
var sourceMap = window.sourceMap;
// Inside Firefox
const sourceMap = require("devtools/toolkit/sourcemap/source-map.js");
```
### SourceMapConsumer
A `SourceMapConsumer` instance represents a parsed source map which we can query
for information about the original file positions by giving it a file position
in the generated source.
#### SourceMapConsumer.initialize(options)
When using `SourceMapConsumer` outside of node.js, for example on the Web, it
needs to know from what URL to load `lib/mappings.wasm`. You must inform it by
calling `initialize` before constructing any `SourceMapConsumer`s.
The options object has the following properties:
- `"lib/mappings.wasm"`: A `String` containing the URL of the
`lib/mappings.wasm` file, or an `ArrayBuffer` with the contents of `lib/mappings.wasm`.
```js
sourceMap.SourceMapConsumer.initialize({
"lib/mappings.wasm": "https://example.com/source-map/lib/mappings.wasm",
});
```
#### new SourceMapConsumer(rawSourceMap)
The only parameter is the raw source map (either as a string which can be
`JSON.parse`'d, or an object). According to the spec, source maps have the
following attributes:
- `version`: Which version of the source map spec this map is following.
- `sources`: An array of URLs to the original source files.
- `names`: An array of identifiers which can be referenced by individual
mappings.
- `sourceRoot`: Optional. The URL root from which all sources are relative.
- `sourcesContent`: Optional. An array of contents of the original source files.
- `mappings`: A string of base64 VLQs which contain the actual mappings.
- `file`: Optional. The generated filename this source map is associated with.
- `x_google_ignoreList`: Optional. An additional extension field which is an array
of indices refering to urls in the sources array. This is used to identify third-party
sources, that the developer might want to avoid when debugging. [Read more](https://developer.chrome.com/articles/x-google-ignore-list/)
The promise of the constructed souce map consumer is returned.
When the `SourceMapConsumer` will no longer be used anymore, you must call its
`destroy` method.
```js
const consumer = await new sourceMap.SourceMapConsumer(rawSourceMapJsonData);
doStuffWith(consumer);
consumer.destroy();
```
Alternatively, you can use `SourceMapConsumer.with` to avoid needing to remember
to call `destroy`.
#### SourceMapConsumer.with
Construct a new `SourceMapConsumer` from `rawSourceMap` and `sourceMapUrl`
(see the `SourceMapConsumer` constructor for details. Then, invoke the `async function f(SourceMapConsumer) -> T` with the newly constructed consumer, wait
for `f` to complete, call `destroy` on the consumer, and return `f`'s return
value.
You must not use the consumer after `f` completes!
By using `with`, you do not have to remember to manually call `destroy` on
the consumer, since it will be called automatically once `f` completes.
```js
const xSquared = await SourceMapConsumer.with(
myRawSourceMap,
null,
async function (consumer) {
// Use `consumer` inside here and don't worry about remembering
// to call `destroy`.
const x = await whatever(consumer);
return x * x;
}
);
// You may not use that `consumer` anymore out here; it has
// been destroyed. But you can use `xSquared`.
console.log(xSquared);
```
#### SourceMapConsumer.prototype.destroy()
Free this source map consumer's associated wasm data that is manually-managed.
```js
consumer.destroy();
```
Alternatively, you can use `SourceMapConsumer.with` to avoid needing to remember
to call `destroy`.
#### SourceMapConsumer.prototype.computeColumnSpans()
Compute the last column for each generated mapping. The last column is
inclusive.
```js
// Before:
consumer.allGeneratedPositionsFor({ line: 2, source: "foo.coffee" });
// [ { line: 2,
// column: 1 },
// { line: 2,
// column: 10 },
// { line: 2,
// column: 20 } ]
consumer.computeColumnSpans();
// After:
consumer.allGeneratedPositionsFor({ line: 2, source: "foo.coffee" });
// [ { line: 2,
// column: 1,
// lastColumn: 9 },
// { line: 2,
// column: 10,
// lastColumn: 19 },
// { line: 2,
// column: 20,
// lastColumn: Infinity } ]
```
#### SourceMapConsumer.prototype.originalPositionFor(generatedPosition)
Returns the original source, line, and column information for the generated
source's line and column positions provided. The only argument is an object with
the following properties:
- `line`: The line number in the generated source. Line numbers in
this library are 1-based (note that the underlying source map
specification uses 0-based line numbers -- this library handles the
translation).
- `column`: The column number in the generated source. Column numbers
in this library are 0-based.
- `bias`: Either `SourceMapConsumer.GREATEST_LOWER_BOUND` or
`SourceMapConsumer.LEAST_UPPER_BOUND`. Specifies whether to return the closest
element that is smaller than or greater than the one we are searching for,
respectively, if the exact element cannot be found. Defaults to
`SourceMapConsumer.GREATEST_LOWER_BOUND`.
and an object is returned with the following properties:
- `source`: The original source file, or null if this information is not
available.
- `line`: The line number in the original source, or null if this information is
not available. The line number is 1-based.
- `column`: The column number in the original source, or null if this
information is not available. The column number is 0-based.
- `name`: The original identifier, or null if this information is not available.
```js
consumer.originalPositionFor({ line: 2, column: 10 });
// { source: 'foo.coffee',
// line: 2,
// column: 2,
// name: null }
consumer.originalPositionFor({
line: 99999999999999999,
column: 999999999999999,
});
// { source: null,
// line: null,
// column: null,
// name: null }
```
#### SourceMapConsumer.prototype.generatedPositionFor(originalPosition)
Returns the generated line and column information for the original source,
line, and column positions provided. The only argument is an object with
the following properties:
- `source`: The filename of the original source.
- `line`: The line number in the original source. The line number is
1-based.
- `column`: The column number in the original source. The column
number is 0-based.
and an object is returned with the following properties:
- `line`: The line number in the generated source, or null. The line
number is 1-based.
- `column`: The column number in the generated source, or null. The
column number is 0-based.
```js
consumer.generatedPositionFor({ source: "example.js", line: 2, column: 10 });
// { line: 1,
// column: 56 }
```
#### SourceMapConsumer.prototype.allGeneratedPositionsFor(originalPosition)
Returns all generated line and column information for the original source, line,
and column provided. If no column is provided, returns all mappings
corresponding to a either the line we are searching for or the next closest line
that has any mappings. Otherwise, returns all mappings corresponding to the
given line and either the column we are searching for or the next closest column
that has any offsets.
The only argument is an object with the following properties:
- `source`: The filename of the original source.
- `line`: The line number in the original source. The line number is
1-based.
- `column`: Optional. The column number in the original source. The
column number is 0-based.
and an array of objects is returned, each with the following properties:
- `line`: The line number in the generated source, or null. The line
number is 1-based.
- `column`: The column number in the generated source, or null. The
column number is 0-based.
```js
consumer.allGeneratedPositionsFor({ line: 2, source: "foo.coffee" });
// [ { line: 2,
// column: 1 },
// { line: 2,
// column: 10 },
// { line: 2,
// column: 20 } ]
```
#### SourceMapConsumer.prototype.hasContentsOfAllSources()
Return true if we have the embedded source content for every source listed in
the source map, false otherwise.
In other words, if this method returns `true`, then
`consumer.sourceContentFor(s)` will succeed for every source `s` in
`consumer.sources`.
```js
// ...
if (consumer.hasContentsOfAllSources()) {
consumerReadyCallback(consumer);
} else {
fetchSources(consumer, consumerReadyCallback);
}
// ...
```
#### SourceMapConsumer.prototype.sourceContentFor(source[, returnNullOnMissing])
Returns the original source content for the source provided. The only
argument is the URL of the original source file.
If the source content for the given source is not found, then an error is
thrown. Optionally, pass `true` as the second param to have `null` returned
instead.
```js
consumer.sources;
// [ "my-cool-lib.clj" ]
consumer.sourceContentFor("my-cool-lib.clj");
// "..."
consumer.sourceContentFor("this is not in the source map");
// Error: "this is not in the source map" is not in the source map
consumer.sourceContentFor("this is not in the source map", true);
// null
```
#### SourceMapConsumer.prototype.eachMapping(callback, context, order)
Iterate over each mapping between an original source/line/column and a
generated line/column in this source map.
- `callback`: The function that is called with each mapping. Mappings have the
form `{ source, generatedLine, generatedColumn, originalLine, originalColumn, name }`
- `context`: Optional. If specified, this object will be the value of `this`
every time that `callback` is called.
- `order`: Either `SourceMapConsumer.GENERATED_ORDER` or
`SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to iterate over
the mappings sorted by the generated file's line/column order or the
original's source/line/column order, respectively. Defaults to
`SourceMapConsumer.GENERATED_ORDER`.
```js
consumer.eachMapping(function (m) {
console.log(m);
});
// ...
// { source: 'illmatic.js',
// generatedLine: 1,
// generatedColumn: 0,
// originalLine: 1,
// originalColumn: 0,
// name: null }
// { source: 'illmatic.js',
// generatedLine: 2,
// generatedColumn: 0,
// originalLine: 2,
// originalColumn: 0,
// name: null }
// ...
```
### SourceMapGenerator
An instance of the SourceMapGenerator represents a source map which is being
built incrementally.
#### new SourceMapGenerator([startOfSourceMap])
You may pass an object with the following properties:
- `file`: The filename of the generated source that this source map is
associated with.
- `sourceRoot`: A root for all relative URLs in this source map.
- `skipValidation`: Optional. When `true`, disables validation of mappings as
they are added. This can improve performance but should be used with
discretion, as a last resort. Even then, one should avoid using this flag when
running tests, if possible.
```js
var generator = new sourceMap.SourceMapGenerator({
file: "my-generated-javascript-file.js",
sourceRoot: "http://example.com/app/js/",
});
```
#### SourceMapGenerator.fromSourceMap(sourceMapConsumer)
Creates a new `SourceMapGenerator` from an existing `SourceMapConsumer` instance.
- `sourceMapConsumer` The SourceMap.
```js
var generator = sourceMap.SourceMapGenerator.fromSourceMap(consumer);
```
#### SourceMapGenerator.prototype.addMapping(mapping)
Add a single mapping from original source line and column to the generated
source's line and column for this source map being created. The mapping object
should have the following properties:
- `generated`: An object with the generated line and column positions.
- `original`: An object with the original line and column positions.
- `source`: The original source file (relative to the sourceRoot).
- `name`: An optional original token name for this mapping.
```js
generator.addMapping({
source: "module-one.scm",
original: { line: 128, column: 0 },
generated: { line: 3, column: 456 },
});
```
#### SourceMapGenerator.prototype.setSourceContent(sourceFile, sourceContent)
Set the source content for an original source file.
- `sourceFile` the URL of the original source file.
- `sourceContent` the content of the source file.
```js
generator.setSourceContent(
"module-one.scm",
fs.readFileSync("path/to/module-one.scm")
);
```
#### SourceMapGenerator.prototype.applySourceMap(sourceMapConsumer[, sourceFile[, sourceMapPath]])
Applies a SourceMap for a source file to the SourceMap.
Each mapping to the supplied source file is rewritten using the
supplied SourceMap. Note: The resolution for the resulting mappings
is the minimum of this map and the supplied map.
- `sourceMapConsumer`: The SourceMap to be applied.
- `sourceFile`: Optional. The filename of the source file.
If omitted, sourceMapConsumer.file will be used, if it exists.
Otherwise an error will be thrown.
- `sourceMapPath`: Optional. The dirname of the path to the SourceMap
to be applied. If relative, it is relative to the SourceMap.
This parameter is needed when the two SourceMaps aren't in the same
directory, and the SourceMap to be applied contains relative source
paths. If so, those relative source paths need to be rewritten
relative to the SourceMap.
If omitted, it is assumed that both SourceMaps are in the same directory,
thus not needing any rewriting. (Supplying `'.'` has the same effect.)
#### SourceMapGenerator.prototype.toString()
Renders the source map being generated to a string.
```js
generator.toString();
// '{"version":3,"sources":["module-one.scm"],"names":[],"mappings":"...snip...","file":"my-generated-javascript-file.js","sourceRoot":"http://example.com/app/js/"}'
```
### SourceNode
SourceNodes provide a way to abstract over interpolating and/or concatenating
snippets of generated JavaScript source code, while maintaining the line and
column information associated between those snippets and the original source
code. This is useful as the final intermediate representation a compiler might
use before outputting the generated JS and source map.
#### new SourceNode([line, column, source[, chunk[, name]]])
- `line`: The original line number associated with this source node, or null if
it isn't associated with an original line. The line number is 1-based.
- `column`: The original column number associated with this source node, or null
if it isn't associated with an original column. The column number
is 0-based.
- `source`: The original source's filename; null if no filename is provided.
- `chunk`: Optional. Is immediately passed to `SourceNode.prototype.add`, see
below.
- `name`: Optional. The original identifier.
```js
var node = new SourceNode(1, 2, "a.cpp", [
new SourceNode(3, 4, "b.cpp", "extern int status;\n"),
new SourceNode(5, 6, "c.cpp", "std::string* make_string(size_t n);\n"),
new SourceNode(7, 8, "d.cpp", "int main(int argc, char** argv) {}\n"),
]);
```
#### SourceNode.fromStringWithSourceMap(code, sourceMapConsumer[, relativePath])
Creates a SourceNode from generated code and a SourceMapConsumer.
- `code`: The generated code
- `sourceMapConsumer` The SourceMap for the generated code
- `relativePath` The optional path that relative sources in `sourceMapConsumer`
should be relative to.
```js
const consumer = await new SourceMapConsumer(
fs.readFileSync("path/to/my-file.js.map", "utf8")
);
const node = SourceNode.fromStringWithSourceMap(
fs.readFileSync("path/to/my-file.js"),
consumer
);
```
#### SourceNode.prototype.add(chunk)
Add a chunk of generated JS to this source node.
- `chunk`: A string snippet of generated JS code, another instance of
`SourceNode`, or an array where each member is one of those things.
```js
node.add(" + ");
node.add(otherNode);
node.add([leftHandOperandNode, " + ", rightHandOperandNode]);
```
#### SourceNode.prototype.prepend(chunk)
Prepend a chunk of generated JS to this source node.
- `chunk`: A string snippet of generated JS code, another instance of
`SourceNode`, or an array where each member is one of those things.
```js
node.prepend("/** Build Id: f783haef86324gf **/\n\n");
```
#### SourceNode.prototype.setSourceContent(sourceFile, sourceContent)
Set the source content for a source file. This will be added to the
`SourceMap` in the `sourcesContent` field.
- `sourceFile`: The filename of the source file
- `sourceContent`: The content of the source file
```js
node.setSourceContent(
"module-one.scm",
fs.readFileSync("path/to/module-one.scm")
);
```
#### SourceNode.prototype.walk(fn)
Walk over the tree of JS snippets in this node and its children. The walking
function is called once for each snippet of JS and is passed that snippet and
the its original associated source's line/column location.
- `fn`: The traversal function.
```js
var node = new SourceNode(1, 2, "a.js", [
new SourceNode(3, 4, "b.js", "uno"),
"dos",
["tres", new SourceNode(5, 6, "c.js", "quatro")],
]);
node.walk(function (code, loc) {
console.log("WALK:", code, loc);
});
// WALK: uno { source: 'b.js', line: 3, column: 4, name: null }
// WALK: dos { source: 'a.js', line: 1, column: 2, name: null }
// WALK: tres { source: 'a.js', line: 1, column: 2, name: null }
// WALK: quatro { source: 'c.js', line: 5, column: 6, name: null }
```
#### SourceNode.prototype.walkSourceContents(fn)
Walk over the tree of SourceNodes. The walking function is called for each
source file content and is passed the filename and source content.
- `fn`: The traversal function.
```js
var a = new SourceNode(1, 2, "a.js", "generated from a");
a.setSourceContent("a.js", "original a");
var b = new SourceNode(1, 2, "b.js", "generated from b");
b.setSourceContent("b.js", "original b");
var c = new SourceNode(1, 2, "c.js", "generated from c");
c.setSourceContent("c.js", "original c");
var node = new SourceNode(null, null, null, [a, b, c]);
node.walkSourceContents(function (source, contents) {
console.log("WALK:", source, ":", contents);
});
// WALK: a.js : original a
// WALK: b.js : original b
// WALK: c.js : original c
```
#### SourceNode.prototype.join(sep)
Like `Array.prototype.join` except for SourceNodes. Inserts the separator
between each of this source node's children.
- `sep`: The separator.
```js
var lhs = new SourceNode(1, 2, "a.rs", "my_copy");
var operand = new SourceNode(3, 4, "a.rs", "=");
var rhs = new SourceNode(5, 6, "a.rs", "orig.clone()");
var node = new SourceNode(null, null, null, [lhs, operand, rhs]);
var joinedNode = node.join(" ");
```
#### SourceNode.prototype.replaceRight(pattern, replacement)
Call `String.prototype.replace` on the very right-most source snippet. Useful
for trimming white space from the end of a source node, etc.
- `pattern`: The pattern to replace.
- `replacement`: The thing to replace the pattern with.
```js
// Trim trailing white space.
node.replaceRight(/\s*$/, "");
```
#### SourceNode.prototype.toString()
Return the string representation of this source node. Walks over the tree and
concatenates all the various snippets together to one string.
```js
var node = new SourceNode(1, 2, "a.js", [
new SourceNode(3, 4, "b.js", "uno"),
"dos",
["tres", new SourceNode(5, 6, "c.js", "quatro")],
]);
node.toString();
// 'unodostresquatro'
```
#### SourceNode.prototype.toStringWithSourceMap([startOfSourceMap])
Returns the string representation of this tree of source nodes, plus a
SourceMapGenerator which contains all the mappings between the generated and
original sources.
The arguments are the same as those to `new SourceMapGenerator`.
```js
var node = new SourceNode(1, 2, "a.js", [
new SourceNode(3, 4, "b.js", "uno"),
"dos",
["tres", new SourceNode(5, 6, "c.js", "quatro")],
]);
node.toStringWithSourceMap({ file: "my-output-file.js" });
// { code: 'unodostresquatro',
// map: [object SourceMapGenerator] }
```

View File

@@ -0,0 +1,100 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/**
* A data structure which is a combination of an array and a set. Adding a new
* member is O(1), testing for membership is O(1), and finding the index of an
* element is O(1). Removing elements from the set is not supported. Only
* strings are supported for membership.
*/
class ArraySet {
constructor() {
this._array = [];
this._set = new Map();
}
/**
* Static method for creating ArraySet instances from an existing array.
*/
static fromArray(aArray, aAllowDuplicates) {
const set = new ArraySet();
for (let i = 0, len = aArray.length; i < len; i++) {
set.add(aArray[i], aAllowDuplicates);
}
return set;
}
/**
* Return how many unique items are in this ArraySet. If duplicates have been
* added, than those do not count towards the size.
*
* @returns Number
*/
size() {
return this._set.size;
}
/**
* Add the given string to this set.
*
* @param String aStr
*/
add(aStr, aAllowDuplicates) {
const isDuplicate = this.has(aStr);
const idx = this._array.length;
if (!isDuplicate || aAllowDuplicates) {
this._array.push(aStr);
}
if (!isDuplicate) {
this._set.set(aStr, idx);
}
}
/**
* Is the given string a member of this set?
*
* @param String aStr
*/
has(aStr) {
return this._set.has(aStr);
}
/**
* What is the index of the given string in the array?
*
* @param String aStr
*/
indexOf(aStr) {
const idx = this._set.get(aStr);
if (idx >= 0) {
return idx;
}
throw new Error('"' + aStr + '" is not in the set.');
}
/**
* What is the element at the given index?
*
* @param Number aIdx
*/
at(aIdx) {
if (aIdx >= 0 && aIdx < this._array.length) {
return this._array[aIdx];
}
throw new Error("No element indexed by " + aIdx);
}
/**
* Returns the array representation of this set (which has the proper indices
* indicated by indexOf). Note that this is a copy of the internal array used
* for storing the members so that no one can mess with internal state.
*/
toArray() {
return this._array.slice();
}
}
exports.ArraySet = ArraySet;

View File

@@ -0,0 +1,94 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*
* Based on the Base 64 VLQ implementation in Closure Compiler:
* https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
*
* Copyright 2011 The Closure Compiler Authors. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
const base64 = require("./base64");
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
// length quantities we use in the source map spec, the first bit is the sign,
// the next four bits are the actual value, and the 6th bit is the
// continuation bit. The continuation bit tells us whether there are more
// digits in this value following this digit.
//
// Continuation
// | Sign
// | |
// V V
// 101011
const VLQ_BASE_SHIFT = 5;
// binary: 100000
const VLQ_BASE = 1 << VLQ_BASE_SHIFT;
// binary: 011111
const VLQ_BASE_MASK = VLQ_BASE - 1;
// binary: 100000
const VLQ_CONTINUATION_BIT = VLQ_BASE;
/**
* Converts from a two-complement value to a value where the sign bit is
* placed in the least significant bit. For example, as decimals:
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
*/
function toVLQSigned(aValue) {
return aValue < 0 ? (-aValue << 1) + 1 : (aValue << 1) + 0;
}
/**
* Returns the base 64 VLQ encoded value.
*/
exports.encode = function base64VLQ_encode(aValue) {
let encoded = "";
let digit;
let vlq = toVLQSigned(aValue);
do {
digit = vlq & VLQ_BASE_MASK;
vlq >>>= VLQ_BASE_SHIFT;
if (vlq > 0) {
// There are still more digits in this value, so we must make sure the
// continuation bit is marked.
digit |= VLQ_CONTINUATION_BIT;
}
encoded += base64.encode(digit);
} while (vlq > 0);
return encoded;
};

View File

@@ -0,0 +1,19 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
const intToCharMap =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("");
/**
* Encode an integer in the range of 0 to 63 to a single base 64 digit.
*/
exports.encode = function (number) {
if (0 <= number && number < intToCharMap.length) {
return intToCharMap[number];
}
throw new TypeError("Must be between 0 and 63: " + number);
};

View File

@@ -0,0 +1,113 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
exports.GREATEST_LOWER_BOUND = 1;
exports.LEAST_UPPER_BOUND = 2;
/**
* Recursive implementation of binary search.
*
* @param aLow Indices here and lower do not contain the needle.
* @param aHigh Indices here and higher do not contain the needle.
* @param aNeedle The element being searched for.
* @param aHaystack The non-empty array being searched.
* @param aCompare Function which takes two elements and returns -1, 0, or 1.
* @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
* 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
* closest element that is smaller than or greater than the one we are
* searching for, respectively, if the exact element cannot be found.
*/
function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
// This function terminates when one of the following is true:
//
// 1. We find the exact element we are looking for.
//
// 2. We did not find the exact element, but we can return the index of
// the next-closest element.
//
// 3. We did not find the exact element, and there is no next-closest
// element than the one we are searching for, so we return -1.
const mid = Math.floor((aHigh - aLow) / 2) + aLow;
const cmp = aCompare(aNeedle, aHaystack[mid], true);
if (cmp === 0) {
// Found the element we are looking for.
return mid;
} else if (cmp > 0) {
// Our needle is greater than aHaystack[mid].
if (aHigh - mid > 1) {
// The element is in the upper half.
return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
}
// The exact needle element was not found in this haystack. Determine if
// we are in termination case (3) or (2) and return the appropriate thing.
if (aBias === exports.LEAST_UPPER_BOUND) {
return aHigh < aHaystack.length ? aHigh : -1;
}
return mid;
}
// Our needle is less than aHaystack[mid].
if (mid - aLow > 1) {
// The element is in the lower half.
return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
}
// we are in termination case (3) or (2) and return the appropriate thing.
if (aBias == exports.LEAST_UPPER_BOUND) {
return mid;
}
return aLow < 0 ? -1 : aLow;
}
/**
* This is an implementation of binary search which will always try and return
* the index of the closest element if there is no exact hit. This is because
* mappings between original and generated line/col pairs are single points,
* and there is an implicit region between each of them, so a miss just means
* that you aren't on the very start of a region.
*
* @param aNeedle The element you are looking for.
* @param aHaystack The array that is being searched.
* @param aCompare A function which takes the needle and an element in the
* array and returns -1, 0, or 1 depending on whether the needle is less
* than, equal to, or greater than the element, respectively.
* @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
* 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
* closest element that is smaller than or greater than the one we are
* searching for, respectively, if the exact element cannot be found.
* Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
*/
exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
if (aHaystack.length === 0) {
return -1;
}
let index = recursiveSearch(
-1,
aHaystack.length,
aNeedle,
aHaystack,
aCompare,
aBias || exports.GREATEST_LOWER_BOUND
);
if (index < 0) {
return -1;
}
// We have found either the exact element, or the next-closest element to
// the one we are searching for. However, there may be more than one such
// element. Make sure we always return the smallest of these.
while (index - 1 >= 0) {
if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
break;
}
--index;
}
return index;
};

View File

@@ -0,0 +1,83 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2014 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
const util = require("./util");
/**
* Determine whether mappingB is after mappingA with respect to generated
* position.
*/
function generatedPositionAfter(mappingA, mappingB) {
// Optimized for most common case
const lineA = mappingA.generatedLine;
const lineB = mappingB.generatedLine;
const columnA = mappingA.generatedColumn;
const columnB = mappingB.generatedColumn;
return (
lineB > lineA ||
(lineB == lineA && columnB >= columnA) ||
util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0
);
}
/**
* A data structure to provide a sorted view of accumulated mappings in a
* performance conscious manner. It trades a negligible overhead in general
* case for a large speedup in case of mappings being added in order.
*/
class MappingList {
constructor() {
this._array = [];
this._sorted = true;
// Serves as infimum
this._last = { generatedLine: -1, generatedColumn: 0 };
}
/**
* Iterate through internal items. This method takes the same arguments that
* `Array.prototype.forEach` takes.
*
* NOTE: The order of the mappings is NOT guaranteed.
*/
unsortedForEach(aCallback, aThisArg) {
this._array.forEach(aCallback, aThisArg);
}
/**
* Add the given source mapping.
*
* @param Object aMapping
*/
add(aMapping) {
if (generatedPositionAfter(this._last, aMapping)) {
this._last = aMapping;
this._array.push(aMapping);
} else {
this._sorted = false;
this._array.push(aMapping);
}
}
/**
* Returns the flat, sorted array of mappings. The mappings are sorted by
* generated position.
*
* WARNING: This method returns internal data without copying, for
* performance. The return value must NOT be mutated, and should be treated as
* an immutable borrow. If you want to take ownership, you must make your own
* copy.
*/
toArray() {
if (!this._sorted) {
this._array.sort(util.compareByGeneratedPositionsInflated);
this._sorted = true;
}
return this._array;
}
}
exports.MappingList = MappingList;

View File

@@ -0,0 +1,23 @@
"use strict";
let mappingsWasm = null;
module.exports = function readWasm() {
if (typeof mappingsWasm === "string") {
return fetch(mappingsWasm).then(response => response.arrayBuffer());
}
if (mappingsWasm instanceof ArrayBuffer) {
return Promise.resolve(mappingsWasm);
}
throw new Error(
"You must provide the string URL or ArrayBuffer contents " +
"of lib/mappings.wasm by calling " +
"SourceMapConsumer.initialize({ 'lib/mappings.wasm': ... }) " +
"before using SourceMapConsumer"
);
};
module.exports.initialize = input => {
mappingsWasm = input;
};

View File

@@ -0,0 +1,27 @@
"use strict";
// Note: This file is replaced with "read-wasm-browser.js" when this module is
// bundled with a packager that takes package.json#browser fields into account.
const fs = require("fs");
const path = require("path");
module.exports = function readWasm() {
return new Promise((resolve, reject) => {
const wasmPath = path.join(__dirname, "mappings.wasm");
fs.readFile(wasmPath, null, (error, data) => {
if (error) {
reject(error);
return;
}
resolve(data.buffer);
});
});
};
module.exports.initialize = _ => {
console.debug(
"SourceMapConsumer.initialize is a no-op when running in node.js"
);
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,439 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
const base64VLQ = require("./base64-vlq");
const util = require("./util");
const ArraySet = require("./array-set").ArraySet;
const MappingList = require("./mapping-list").MappingList;
/**
* An instance of the SourceMapGenerator represents a source map which is
* being built incrementally. You may pass an object with the following
* properties:
*
* - file: The filename of the generated source.
* - sourceRoot: A root for all relative URLs in this source map.
*/
class SourceMapGenerator {
constructor(aArgs) {
if (!aArgs) {
aArgs = {};
}
this._file = util.getArg(aArgs, "file", null);
this._sourceRoot = util.getArg(aArgs, "sourceRoot", null);
this._skipValidation = util.getArg(aArgs, "skipValidation", false);
this._sources = new ArraySet();
this._names = new ArraySet();
this._mappings = new MappingList();
this._sourcesContents = null;
}
/**
* Creates a new SourceMapGenerator based on a SourceMapConsumer
*
* @param aSourceMapConsumer The SourceMap.
*/
static fromSourceMap(aSourceMapConsumer) {
const sourceRoot = aSourceMapConsumer.sourceRoot;
const generator = new SourceMapGenerator({
file: aSourceMapConsumer.file,
sourceRoot,
});
aSourceMapConsumer.eachMapping(function (mapping) {
const newMapping = {
generated: {
line: mapping.generatedLine,
column: mapping.generatedColumn,
},
};
if (mapping.source != null) {
newMapping.source = mapping.source;
if (sourceRoot != null) {
newMapping.source = util.relative(sourceRoot, newMapping.source);
}
newMapping.original = {
line: mapping.originalLine,
column: mapping.originalColumn,
};
if (mapping.name != null) {
newMapping.name = mapping.name;
}
}
generator.addMapping(newMapping);
});
aSourceMapConsumer.sources.forEach(function (sourceFile) {
let sourceRelative = sourceFile;
if (sourceRoot != null) {
sourceRelative = util.relative(sourceRoot, sourceFile);
}
if (!generator._sources.has(sourceRelative)) {
generator._sources.add(sourceRelative);
}
const content = aSourceMapConsumer.sourceContentFor(sourceFile);
if (content != null) {
generator.setSourceContent(sourceFile, content);
}
});
return generator;
}
/**
* Add a single mapping from original source line and column to the generated
* source's line and column for this source map being created. The mapping
* object should have the following properties:
*
* - generated: An object with the generated line and column positions.
* - original: An object with the original line and column positions.
* - source: The original source file (relative to the sourceRoot).
* - name: An optional original token name for this mapping.
*/
addMapping(aArgs) {
const generated = util.getArg(aArgs, "generated");
const original = util.getArg(aArgs, "original", null);
let source = util.getArg(aArgs, "source", null);
let name = util.getArg(aArgs, "name", null);
if (!this._skipValidation) {
this._validateMapping(generated, original, source, name);
}
if (source != null) {
source = String(source);
if (!this._sources.has(source)) {
this._sources.add(source);
}
}
if (name != null) {
name = String(name);
if (!this._names.has(name)) {
this._names.add(name);
}
}
this._mappings.add({
generatedLine: generated.line,
generatedColumn: generated.column,
originalLine: original && original.line,
originalColumn: original && original.column,
source,
name,
});
}
/**
* Set the source content for a source file.
*/
setSourceContent(aSourceFile, aSourceContent) {
let source = aSourceFile;
if (this._sourceRoot != null) {
source = util.relative(this._sourceRoot, source);
}
if (aSourceContent != null) {
// Add the source content to the _sourcesContents map.
// Create a new _sourcesContents map if the property is null.
if (!this._sourcesContents) {
this._sourcesContents = Object.create(null);
}
this._sourcesContents[util.toSetString(source)] = aSourceContent;
} else if (this._sourcesContents) {
// Remove the source file from the _sourcesContents map.
// If the _sourcesContents map is empty, set the property to null.
delete this._sourcesContents[util.toSetString(source)];
if (Object.keys(this._sourcesContents).length === 0) {
this._sourcesContents = null;
}
}
}
/**
* Applies the mappings of a sub-source-map for a specific source file to the
* source map being generated. Each mapping to the supplied source file is
* rewritten using the supplied source map. Note: The resolution for the
* resulting mappings is the minimium of this map and the supplied map.
*
* @param aSourceMapConsumer The source map to be applied.
* @param aSourceFile Optional. The filename of the source file.
* If omitted, SourceMapConsumer's file property will be used.
* @param aSourceMapPath Optional. The dirname of the path to the source map
* to be applied. If relative, it is relative to the SourceMapConsumer.
* This parameter is needed when the two source maps aren't in the same
* directory, and the source map to be applied contains relative source
* paths. If so, those relative source paths need to be rewritten
* relative to the SourceMapGenerator.
*/
applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
let sourceFile = aSourceFile;
// If aSourceFile is omitted, we will use the file property of the SourceMap
if (aSourceFile == null) {
if (aSourceMapConsumer.file == null) {
throw new Error(
"SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, " +
'or the source map\'s "file" property. Both were omitted.'
);
}
sourceFile = aSourceMapConsumer.file;
}
const sourceRoot = this._sourceRoot;
// Make "sourceFile" relative if an absolute Url is passed.
if (sourceRoot != null) {
sourceFile = util.relative(sourceRoot, sourceFile);
}
// Applying the SourceMap can add and remove items from the sources and
// the names array.
const newSources =
this._mappings.toArray().length > 0 ? new ArraySet() : this._sources;
const newNames = new ArraySet();
// Find mappings for the "sourceFile"
this._mappings.unsortedForEach(function (mapping) {
if (mapping.source === sourceFile && mapping.originalLine != null) {
// Check if it can be mapped by the source map, then update the mapping.
const original = aSourceMapConsumer.originalPositionFor({
line: mapping.originalLine,
column: mapping.originalColumn,
});
if (original.source != null) {
// Copy mapping
mapping.source = original.source;
if (aSourceMapPath != null) {
mapping.source = util.join(aSourceMapPath, mapping.source);
}
if (sourceRoot != null) {
mapping.source = util.relative(sourceRoot, mapping.source);
}
mapping.originalLine = original.line;
mapping.originalColumn = original.column;
if (original.name != null) {
mapping.name = original.name;
}
}
}
const source = mapping.source;
if (source != null && !newSources.has(source)) {
newSources.add(source);
}
const name = mapping.name;
if (name != null && !newNames.has(name)) {
newNames.add(name);
}
}, this);
this._sources = newSources;
this._names = newNames;
// Copy sourcesContents of applied map.
aSourceMapConsumer.sources.forEach(function (srcFile) {
const content = aSourceMapConsumer.sourceContentFor(srcFile);
if (content != null) {
if (aSourceMapPath != null) {
srcFile = util.join(aSourceMapPath, srcFile);
}
if (sourceRoot != null) {
srcFile = util.relative(sourceRoot, srcFile);
}
this.setSourceContent(srcFile, content);
}
}, this);
}
/**
* A mapping can have one of the three levels of data:
*
* 1. Just the generated position.
* 2. The Generated position, original position, and original source.
* 3. Generated and original position, original source, as well as a name
* token.
*
* To maintain consistency, we validate that any new mapping being added falls
* in to one of these categories.
*/
_validateMapping(aGenerated, aOriginal, aSource, aName) {
// When aOriginal is truthy but has empty values for .line and .column,
// it is most likely a programmer error. In this case we throw a very
// specific error message to try to guide them the right way.
// For example: https://github.com/Polymer/polymer-bundler/pull/519
if (
aOriginal &&
typeof aOriginal.line !== "number" &&
typeof aOriginal.column !== "number"
) {
throw new Error(
"original.line and original.column are not numbers -- you probably meant to omit " +
"the original mapping entirely and only map the generated position. If so, pass " +
"null for the original mapping instead of an object with empty or null values."
);
}
if (
aGenerated &&
"line" in aGenerated &&
"column" in aGenerated &&
aGenerated.line > 0 &&
aGenerated.column >= 0 &&
!aOriginal &&
!aSource &&
!aName
) {
// Case 1.
} else if (
aGenerated &&
"line" in aGenerated &&
"column" in aGenerated &&
aOriginal &&
"line" in aOriginal &&
"column" in aOriginal &&
aGenerated.line > 0 &&
aGenerated.column >= 0 &&
aOriginal.line > 0 &&
aOriginal.column >= 0 &&
aSource
) {
// Cases 2 and 3.
} else {
throw new Error(
"Invalid mapping: " +
JSON.stringify({
generated: aGenerated,
source: aSource,
original: aOriginal,
name: aName,
})
);
}
}
/**
* Serialize the accumulated mappings in to the stream of base 64 VLQs
* specified by the source map format.
*/
_serializeMappings() {
let previousGeneratedColumn = 0;
let previousGeneratedLine = 1;
let previousOriginalColumn = 0;
let previousOriginalLine = 0;
let previousName = 0;
let previousSource = 0;
let result = "";
let next;
let mapping;
let nameIdx;
let sourceIdx;
const mappings = this._mappings.toArray();
for (let i = 0, len = mappings.length; i < len; i++) {
mapping = mappings[i];
next = "";
if (mapping.generatedLine !== previousGeneratedLine) {
previousGeneratedColumn = 0;
while (mapping.generatedLine !== previousGeneratedLine) {
next += ";";
previousGeneratedLine++;
}
} else if (i > 0) {
if (
!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])
) {
continue;
}
next += ",";
}
next += base64VLQ.encode(
mapping.generatedColumn - previousGeneratedColumn
);
previousGeneratedColumn = mapping.generatedColumn;
if (mapping.source != null) {
sourceIdx = this._sources.indexOf(mapping.source);
next += base64VLQ.encode(sourceIdx - previousSource);
previousSource = sourceIdx;
// lines are stored 0-based in SourceMap spec version 3
next += base64VLQ.encode(
mapping.originalLine - 1 - previousOriginalLine
);
previousOriginalLine = mapping.originalLine - 1;
next += base64VLQ.encode(
mapping.originalColumn - previousOriginalColumn
);
previousOriginalColumn = mapping.originalColumn;
if (mapping.name != null) {
nameIdx = this._names.indexOf(mapping.name);
next += base64VLQ.encode(nameIdx - previousName);
previousName = nameIdx;
}
}
result += next;
}
return result;
}
_generateSourcesContent(aSources, aSourceRoot) {
return aSources.map(function (source) {
if (!this._sourcesContents) {
return null;
}
if (aSourceRoot != null) {
source = util.relative(aSourceRoot, source);
}
const key = util.toSetString(source);
return Object.prototype.hasOwnProperty.call(this._sourcesContents, key)
? this._sourcesContents[key]
: null;
}, this);
}
/**
* Externalize the source map.
*/
toJSON() {
const map = {
version: this._version,
sources: this._sources.toArray(),
names: this._names.toArray(),
mappings: this._serializeMappings(),
};
if (this._file != null) {
map.file = this._file;
}
if (this._sourceRoot != null) {
map.sourceRoot = this._sourceRoot;
}
if (this._sourcesContents) {
map.sourcesContent = this._generateSourcesContent(
map.sources,
map.sourceRoot
);
}
return map;
}
/**
* Render the source map being generated to a string.
*/
toString() {
return JSON.stringify(this.toJSON());
}
}
SourceMapGenerator.prototype._version = 3;
exports.SourceMapGenerator = SourceMapGenerator;

View File

@@ -0,0 +1,430 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
const SourceMapGenerator = require("./source-map-generator").SourceMapGenerator;
const util = require("./util");
// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
// operating systems these days (capturing the result).
const REGEX_NEWLINE = /(\r?\n)/;
// Newline character code for charCodeAt() comparisons
const NEWLINE_CODE = 10;
// Private symbol for identifying `SourceNode`s when multiple versions of
// the source-map library are loaded. This MUST NOT CHANGE across
// versions!
const isSourceNode = "$$$isSourceNode$$$";
/**
* SourceNodes provide a way to abstract over interpolating/concatenating
* snippets of generated JavaScript source code while maintaining the line and
* column information associated with the original source code.
*
* @param aLine The original line number.
* @param aColumn The original column number.
* @param aSource The original source's filename.
* @param aChunks Optional. An array of strings which are snippets of
* generated JS, or other SourceNodes.
* @param aName The original identifier.
*/
class SourceNode {
constructor(aLine, aColumn, aSource, aChunks, aName) {
this.children = [];
this.sourceContents = {};
this.line = aLine == null ? null : aLine;
this.column = aColumn == null ? null : aColumn;
this.source = aSource == null ? null : aSource;
this.name = aName == null ? null : aName;
this[isSourceNode] = true;
if (aChunks != null) this.add(aChunks);
}
/**
* Creates a SourceNode from generated code and a SourceMapConsumer.
*
* @param aGeneratedCode The generated code
* @param aSourceMapConsumer The SourceMap for the generated code
* @param aRelativePath Optional. The path that relative sources in the
* SourceMapConsumer should be relative to.
*/
static fromStringWithSourceMap(
aGeneratedCode,
aSourceMapConsumer,
aRelativePath
) {
// The SourceNode we want to fill with the generated code
// and the SourceMap
const node = new SourceNode();
// All even indices of this array are one line of the generated code,
// while all odd indices are the newlines between two adjacent lines
// (since `REGEX_NEWLINE` captures its match).
// Processed fragments are accessed by calling `shiftNextLine`.
const remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
let remainingLinesIndex = 0;
const shiftNextLine = function () {
const lineContents = getNextLine();
// The last line of a file might not have a newline.
const newLine = getNextLine() || "";
return lineContents + newLine;
function getNextLine() {
return remainingLinesIndex < remainingLines.length
? remainingLines[remainingLinesIndex++]
: undefined;
}
};
// We need to remember the position of "remainingLines"
let lastGeneratedLine = 1,
lastGeneratedColumn = 0;
// The generate SourceNodes we need a code range.
// To extract it current and last mapping is used.
// Here we store the last mapping.
let lastMapping = null;
let nextLine;
aSourceMapConsumer.eachMapping(function (mapping) {
if (lastMapping !== null) {
// We add the code from "lastMapping" to "mapping":
// First check if there is a new line in between.
if (lastGeneratedLine < mapping.generatedLine) {
// Associate first line with "lastMapping"
addMappingWithCode(lastMapping, shiftNextLine());
lastGeneratedLine++;
lastGeneratedColumn = 0;
// The remaining code is added without mapping
} else {
// There is no new line in between.
// Associate the code between "lastGeneratedColumn" and
// "mapping.generatedColumn" with "lastMapping"
nextLine = remainingLines[remainingLinesIndex] || "";
const code = nextLine.substr(
0,
mapping.generatedColumn - lastGeneratedColumn
);
remainingLines[remainingLinesIndex] = nextLine.substr(
mapping.generatedColumn - lastGeneratedColumn
);
lastGeneratedColumn = mapping.generatedColumn;
addMappingWithCode(lastMapping, code);
// No more remaining code, continue
lastMapping = mapping;
return;
}
}
// We add the generated code until the first mapping
// to the SourceNode without any mapping.
// Each line is added as separate string.
while (lastGeneratedLine < mapping.generatedLine) {
node.add(shiftNextLine());
lastGeneratedLine++;
}
if (lastGeneratedColumn < mapping.generatedColumn) {
nextLine = remainingLines[remainingLinesIndex] || "";
node.add(nextLine.substr(0, mapping.generatedColumn));
remainingLines[remainingLinesIndex] = nextLine.substr(
mapping.generatedColumn
);
lastGeneratedColumn = mapping.generatedColumn;
}
lastMapping = mapping;
}, this);
// We have processed all mappings.
if (remainingLinesIndex < remainingLines.length) {
if (lastMapping) {
// Associate the remaining code in the current line with "lastMapping"
addMappingWithCode(lastMapping, shiftNextLine());
}
// and add the remaining lines without any mapping
node.add(remainingLines.splice(remainingLinesIndex).join(""));
}
// Copy sourcesContent into SourceNode
aSourceMapConsumer.sources.forEach(function (sourceFile) {
const content = aSourceMapConsumer.sourceContentFor(sourceFile);
if (content != null) {
if (aRelativePath != null) {
sourceFile = util.join(aRelativePath, sourceFile);
}
node.setSourceContent(sourceFile, content);
}
});
return node;
function addMappingWithCode(mapping, code) {
if (mapping === null || mapping.source === undefined) {
node.add(code);
} else {
const source = aRelativePath
? util.join(aRelativePath, mapping.source)
: mapping.source;
node.add(
new SourceNode(
mapping.originalLine,
mapping.originalColumn,
source,
code,
mapping.name
)
);
}
}
}
/**
* Add a chunk of generated JS to this source node.
*
* @param aChunk A string snippet of generated JS code, another instance of
* SourceNode, or an array where each member is one of those things.
*/
add(aChunk) {
if (Array.isArray(aChunk)) {
aChunk.forEach(function (chunk) {
this.add(chunk);
}, this);
} else if (aChunk[isSourceNode] || typeof aChunk === "string") {
if (aChunk) {
this.children.push(aChunk);
}
} else {
throw new TypeError(
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " +
aChunk
);
}
return this;
}
/**
* Add a chunk of generated JS to the beginning of this source node.
*
* @param aChunk A string snippet of generated JS code, another instance of
* SourceNode, or an array where each member is one of those things.
*/
prepend(aChunk) {
if (Array.isArray(aChunk)) {
for (let i = aChunk.length - 1; i >= 0; i--) {
this.prepend(aChunk[i]);
}
} else if (aChunk[isSourceNode] || typeof aChunk === "string") {
this.children.unshift(aChunk);
} else {
throw new TypeError(
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " +
aChunk
);
}
return this;
}
/**
* Walk over the tree of JS snippets in this node and its children. The
* walking function is called once for each snippet of JS and is passed that
* snippet and the its original associated source's line/column location.
*
* @param aFn The traversal function.
*/
walk(aFn) {
let chunk;
for (let i = 0, len = this.children.length; i < len; i++) {
chunk = this.children[i];
if (chunk[isSourceNode]) {
chunk.walk(aFn);
} else if (chunk !== "") {
aFn(chunk, {
source: this.source,
line: this.line,
column: this.column,
name: this.name,
});
}
}
}
/**
* Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
* each of `this.children`.
*
* @param aSep The separator.
*/
join(aSep) {
let newChildren;
let i;
const len = this.children.length;
if (len > 0) {
newChildren = [];
for (i = 0; i < len - 1; i++) {
newChildren.push(this.children[i]);
newChildren.push(aSep);
}
newChildren.push(this.children[i]);
this.children = newChildren;
}
return this;
}
/**
* Call String.prototype.replace on the very right-most source snippet. Useful
* for trimming whitespace from the end of a source node, etc.
*
* @param aPattern The pattern to replace.
* @param aReplacement The thing to replace the pattern with.
*/
replaceRight(aPattern, aReplacement) {
const lastChild = this.children[this.children.length - 1];
if (lastChild[isSourceNode]) {
lastChild.replaceRight(aPattern, aReplacement);
} else if (typeof lastChild === "string") {
this.children[this.children.length - 1] = lastChild.replace(
aPattern,
aReplacement
);
} else {
this.children.push("".replace(aPattern, aReplacement));
}
return this;
}
/**
* Set the source content for a source file. This will be added to the SourceMapGenerator
* in the sourcesContent field.
*
* @param aSourceFile The filename of the source file
* @param aSourceContent The content of the source file
*/
setSourceContent(aSourceFile, aSourceContent) {
this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
}
/**
* Walk over the tree of SourceNodes. The walking function is called for each
* source file content and is passed the filename and source content.
*
* @param aFn The traversal function.
*/
walkSourceContents(aFn) {
for (let i = 0, len = this.children.length; i < len; i++) {
if (this.children[i][isSourceNode]) {
this.children[i].walkSourceContents(aFn);
}
}
const sources = Object.keys(this.sourceContents);
for (let i = 0, len = sources.length; i < len; i++) {
aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
}
}
/**
* Return the string representation of this source node. Walks over the tree
* and concatenates all the various snippets together to one string.
*/
toString() {
let str = "";
this.walk(function (chunk) {
str += chunk;
});
return str;
}
/**
* Returns the string representation of this source node along with a source
* map.
*/
toStringWithSourceMap(aArgs) {
const generated = {
code: "",
line: 1,
column: 0,
};
const map = new SourceMapGenerator(aArgs);
let sourceMappingActive = false;
let lastOriginalSource = null;
let lastOriginalLine = null;
let lastOriginalColumn = null;
let lastOriginalName = null;
this.walk(function (chunk, original) {
generated.code += chunk;
if (
original.source !== null &&
original.line !== null &&
original.column !== null
) {
if (
lastOriginalSource !== original.source ||
lastOriginalLine !== original.line ||
lastOriginalColumn !== original.column ||
lastOriginalName !== original.name
) {
map.addMapping({
source: original.source,
original: {
line: original.line,
column: original.column,
},
generated: {
line: generated.line,
column: generated.column,
},
name: original.name,
});
}
lastOriginalSource = original.source;
lastOriginalLine = original.line;
lastOriginalColumn = original.column;
lastOriginalName = original.name;
sourceMappingActive = true;
} else if (sourceMappingActive) {
map.addMapping({
generated: {
line: generated.line,
column: generated.column,
},
});
lastOriginalSource = null;
sourceMappingActive = false;
}
for (let idx = 0, length = chunk.length; idx < length; idx++) {
if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
generated.line++;
generated.column = 0;
// Mappings end at eol
if (idx + 1 === length) {
lastOriginalSource = null;
sourceMappingActive = false;
} else if (sourceMappingActive) {
map.addMapping({
source: original.source,
original: {
line: original.line,
column: original.column,
},
generated: {
line: generated.line,
column: generated.column,
},
name: original.name,
});
}
} else {
generated.column++;
}
}
});
this.walkSourceContents(function (sourceFile, sourceContent) {
map.setSourceContent(sourceFile, sourceContent);
});
return { code: generated.code, map };
}
}
exports.SourceNode = SourceNode;

View File

@@ -0,0 +1,13 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
"use strict";
// Note: This file is overridden in the 'package.json#browser' field to
// substitute lib/url-browser.js instead.
// Use the URL global for Node 10, and the 'url' module for Node 8.
module.exports = typeof URL === "function" ? URL : require("url").URL;

View File

@@ -0,0 +1,444 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
const URL = require("./url");
/**
* This is a helper function for getting values from parameter/options
* objects.
*
* @param args The object we are extracting values from
* @param name The name of the property we are getting.
* @param defaultValue An optional value to return if the property is missing
* from the object. If this is not specified and the property is missing, an
* error will be thrown.
*/
function getArg(aArgs, aName, aDefaultValue) {
if (aName in aArgs) {
return aArgs[aName];
} else if (arguments.length === 3) {
return aDefaultValue;
}
throw new Error('"' + aName + '" is a required argument.');
}
exports.getArg = getArg;
const supportsNullProto = (function () {
const obj = Object.create(null);
return !("__proto__" in obj);
})();
function identity(s) {
return s;
}
/**
* Because behavior goes wacky when you set `__proto__` on objects, we
* have to prefix all the strings in our set with an arbitrary character.
*
* See https://github.com/mozilla/source-map/pull/31 and
* https://github.com/mozilla/source-map/issues/30
*
* @param String aStr
*/
function toSetString(aStr) {
if (isProtoString(aStr)) {
return "$" + aStr;
}
return aStr;
}
exports.toSetString = supportsNullProto ? identity : toSetString;
function fromSetString(aStr) {
if (isProtoString(aStr)) {
return aStr.slice(1);
}
return aStr;
}
exports.fromSetString = supportsNullProto ? identity : fromSetString;
function isProtoString(s) {
if (!s) {
return false;
}
const length = s.length;
if (length < 9 /* "__proto__".length */) {
return false;
}
/* eslint-disable no-multi-spaces */
if (
s.charCodeAt(length - 1) !== 95 /* '_' */ ||
s.charCodeAt(length - 2) !== 95 /* '_' */ ||
s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
s.charCodeAt(length - 4) !== 116 /* 't' */ ||
s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
s.charCodeAt(length - 8) !== 95 /* '_' */ ||
s.charCodeAt(length - 9) !== 95 /* '_' */
) {
return false;
}
/* eslint-enable no-multi-spaces */
for (let i = length - 10; i >= 0; i--) {
if (s.charCodeAt(i) !== 36 /* '$' */) {
return false;
}
}
return true;
}
function strcmp(aStr1, aStr2) {
if (aStr1 === aStr2) {
return 0;
}
if (aStr1 === null) {
return 1; // aStr2 !== null
}
if (aStr2 === null) {
return -1; // aStr1 !== null
}
if (aStr1 > aStr2) {
return 1;
}
return -1;
}
/**
* Comparator between two mappings with inflated source and name strings where
* the generated positions are compared.
*/
function compareByGeneratedPositionsInflated(mappingA, mappingB) {
let cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if (cmp !== 0) {
return cmp;
}
cmp = strcmp(mappingA.source, mappingB.source);
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalColumn - mappingB.originalColumn;
if (cmp !== 0) {
return cmp;
}
return strcmp(mappingA.name, mappingB.name);
}
exports.compareByGeneratedPositionsInflated =
compareByGeneratedPositionsInflated;
/**
* Strip any JSON XSSI avoidance prefix from the string (as documented
* in the source maps specification), and then parse the string as
* JSON.
*/
function parseSourceMapInput(str) {
return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, ""));
}
exports.parseSourceMapInput = parseSourceMapInput;
// We use 'http' as the base here because we want URLs processed relative
// to the safe base to be treated as "special" URLs during parsing using
// the WHATWG URL parsing. This ensures that backslash normalization
// applies to the path and such.
const PROTOCOL = "http:";
const PROTOCOL_AND_HOST = `${PROTOCOL}//host`;
/**
* Make it easy to create small utilities that tweak a URL's path.
*/
function createSafeHandler(cb) {
return input => {
const type = getURLType(input);
const base = buildSafeBase(input);
const url = new URL(input, base);
cb(url);
const result = url.toString();
if (type === "absolute") {
return result;
} else if (type === "scheme-relative") {
return result.slice(PROTOCOL.length);
} else if (type === "path-absolute") {
return result.slice(PROTOCOL_AND_HOST.length);
}
// This assumes that the callback will only change
// the path, search and hash values.
return computeRelativeURL(base, result);
};
}
function withBase(url, base) {
return new URL(url, base).toString();
}
function buildUniqueSegment(prefix, str) {
let id = 0;
do {
const ident = prefix + id++;
if (str.indexOf(ident) === -1) return ident;
} while (true);
}
function buildSafeBase(str) {
const maxDotParts = str.split("..").length - 1;
// If we used a segment that also existed in `str`, then we would be unable
// to compute relative paths. For example, if `segment` were just "a":
//
// const url = "../../a/"
// const base = buildSafeBase(url); // http://host/a/a/
// const joined = "http://host/a/";
// const result = relative(base, joined);
//
// Expected: "../../a/";
// Actual: "a/"
//
const segment = buildUniqueSegment("p", str);
let base = `${PROTOCOL_AND_HOST}/`;
for (let i = 0; i < maxDotParts; i++) {
base += `${segment}/`;
}
return base;
}
const ABSOLUTE_SCHEME = /^[A-Za-z0-9\+\-\.]+:/;
function getURLType(url) {
if (url[0] === "/") {
if (url[1] === "/") return "scheme-relative";
return "path-absolute";
}
return ABSOLUTE_SCHEME.test(url) ? "absolute" : "path-relative";
}
/**
* Given two URLs that are assumed to be on the same
* protocol/host/user/password build a relative URL from the
* path, params, and hash values.
*
* @param rootURL The root URL that the target will be relative to.
* @param targetURL The target that the relative URL points to.
* @return A rootURL-relative, normalized URL value.
*/
function computeRelativeURL(rootURL, targetURL) {
if (typeof rootURL === "string") rootURL = new URL(rootURL);
if (typeof targetURL === "string") targetURL = new URL(targetURL);
const targetParts = targetURL.pathname.split("/");
const rootParts = rootURL.pathname.split("/");
// If we've got a URL path ending with a "/", we remove it since we'd
// otherwise be relative to the wrong location.
if (rootParts.length > 0 && !rootParts[rootParts.length - 1]) {
rootParts.pop();
}
while (
targetParts.length > 0 &&
rootParts.length > 0 &&
targetParts[0] === rootParts[0]
) {
targetParts.shift();
rootParts.shift();
}
const relativePath = rootParts
.map(() => "..")
.concat(targetParts)
.join("/");
return relativePath + targetURL.search + targetURL.hash;
}
/**
* Given a URL, ensure that it is treated as a directory URL.
*
* @param url
* @return A normalized URL value.
*/
const ensureDirectory = createSafeHandler(url => {
url.pathname = url.pathname.replace(/\/?$/, "/");
});
/**
* Given a URL, strip off any filename if one is present.
*
* @param url
* @return A normalized URL value.
*/
const trimFilename = createSafeHandler(url => {
url.href = new URL(".", url.toString()).toString();
});
/**
* Normalize a given URL.
* * Convert backslashes.
* * Remove any ".." and "." segments.
*
* @param url
* @return A normalized URL value.
*/
const normalize = createSafeHandler(url => {});
exports.normalize = normalize;
/**
* Joins two paths/URLs.
*
* All returned URLs will be normalized.
*
* @param aRoot The root path or URL. Assumed to reference a directory.
* @param aPath The path or URL to be joined with the root.
* @return A joined and normalized URL value.
*/
function join(aRoot, aPath) {
const pathType = getURLType(aPath);
const rootType = getURLType(aRoot);
aRoot = ensureDirectory(aRoot);
if (pathType === "absolute") {
return withBase(aPath, undefined);
}
if (rootType === "absolute") {
return withBase(aPath, aRoot);
}
if (pathType === "scheme-relative") {
return normalize(aPath);
}
if (rootType === "scheme-relative") {
return withBase(aPath, withBase(aRoot, PROTOCOL_AND_HOST)).slice(
PROTOCOL.length
);
}
if (pathType === "path-absolute") {
return normalize(aPath);
}
if (rootType === "path-absolute") {
return withBase(aPath, withBase(aRoot, PROTOCOL_AND_HOST)).slice(
PROTOCOL_AND_HOST.length
);
}
const base = buildSafeBase(aPath + aRoot);
const newPath = withBase(aPath, withBase(aRoot, base));
return computeRelativeURL(base, newPath);
}
exports.join = join;
/**
* Make a path relative to a URL or another path. If returning a
* relative URL is not possible, the original target will be returned.
* All returned URLs will be normalized.
*
* @param aRoot The root path or URL.
* @param aPath The path or URL to be made relative to aRoot.
* @return A rootURL-relative (if possible), normalized URL value.
*/
function relative(rootURL, targetURL) {
const result = relativeIfPossible(rootURL, targetURL);
return typeof result === "string" ? result : normalize(targetURL);
}
exports.relative = relative;
function relativeIfPossible(rootURL, targetURL) {
const urlType = getURLType(rootURL);
if (urlType !== getURLType(targetURL)) {
return null;
}
const base = buildSafeBase(rootURL + targetURL);
const root = new URL(rootURL, base);
const target = new URL(targetURL, base);
try {
new URL("", target.toString());
} catch (err) {
// Bail if the URL doesn't support things being relative to it,
// For example, data: and blob: URLs.
return null;
}
if (
target.protocol !== root.protocol ||
target.user !== root.user ||
target.password !== root.password ||
target.hostname !== root.hostname ||
target.port !== root.port
) {
return null;
}
return computeRelativeURL(root, target);
}
/**
* Compute the URL of a source given the the source root, the source's
* URL, and the source map's URL.
*/
function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) {
// The source map spec states that "sourceRoot" and "sources" entries are to be appended. While
// that is a little vague, implementations have generally interpreted that as joining the
// URLs with a `/` between then, assuming the "sourceRoot" doesn't already end with one.
// For example,
//
// sourceRoot: "some-dir",
// sources: ["/some-path.js"]
//
// and
//
// sourceRoot: "some-dir/",
// sources: ["/some-path.js"]
//
// must behave as "some-dir/some-path.js".
//
// With this library's the transition to a more URL-focused implementation, that behavior is
// preserved here. To acheive that, we trim the "/" from absolute-path when a sourceRoot value
// is present in order to make the sources entries behave as if they are relative to the
// "sourceRoot", as they would have if the two strings were simply concated.
if (sourceRoot && getURLType(sourceURL) === "path-absolute") {
sourceURL = sourceURL.replace(/^\//, "");
}
let url = normalize(sourceURL || "");
// Parsing URLs can be expensive, so we only perform these joins when needed.
if (sourceRoot) url = join(sourceRoot, url);
if (sourceMapURL) url = join(trimFilename(sourceMapURL), url);
return url;
}
exports.computeSourceURL = computeSourceURL;

View File

@@ -0,0 +1,138 @@
const readWasm = require("../lib/read-wasm");
/**
* Provide the JIT with a nice shape / hidden class.
*/
function Mapping() {
this.generatedLine = 0;
this.generatedColumn = 0;
this.lastGeneratedColumn = null;
this.source = null;
this.originalLine = null;
this.originalColumn = null;
this.name = null;
}
let cachedWasm = null;
module.exports = function wasm() {
if (cachedWasm) {
return cachedWasm;
}
const callbackStack = [];
cachedWasm = readWasm()
.then(buffer => {
return WebAssembly.instantiate(buffer, {
env: {
mapping_callback(
generatedLine,
generatedColumn,
hasLastGeneratedColumn,
lastGeneratedColumn,
hasOriginal,
source,
originalLine,
originalColumn,
hasName,
name
) {
const mapping = new Mapping();
// JS uses 1-based line numbers, wasm uses 0-based.
mapping.generatedLine = generatedLine + 1;
mapping.generatedColumn = generatedColumn;
if (hasLastGeneratedColumn) {
// JS uses inclusive last generated column, wasm uses exclusive.
mapping.lastGeneratedColumn = lastGeneratedColumn - 1;
}
if (hasOriginal) {
mapping.source = source;
// JS uses 1-based line numbers, wasm uses 0-based.
mapping.originalLine = originalLine + 1;
mapping.originalColumn = originalColumn;
if (hasName) {
mapping.name = name;
}
}
callbackStack[callbackStack.length - 1](mapping);
},
start_all_generated_locations_for() {
console.time("all_generated_locations_for");
},
end_all_generated_locations_for() {
console.timeEnd("all_generated_locations_for");
},
start_compute_column_spans() {
console.time("compute_column_spans");
},
end_compute_column_spans() {
console.timeEnd("compute_column_spans");
},
start_generated_location_for() {
console.time("generated_location_for");
},
end_generated_location_for() {
console.timeEnd("generated_location_for");
},
start_original_location_for() {
console.time("original_location_for");
},
end_original_location_for() {
console.timeEnd("original_location_for");
},
start_parse_mappings() {
console.time("parse_mappings");
},
end_parse_mappings() {
console.timeEnd("parse_mappings");
},
start_sort_by_generated_location() {
console.time("sort_by_generated_location");
},
end_sort_by_generated_location() {
console.timeEnd("sort_by_generated_location");
},
start_sort_by_original_location() {
console.time("sort_by_original_location");
},
end_sort_by_original_location() {
console.timeEnd("sort_by_original_location");
},
},
});
})
.then(Wasm => {
return {
exports: Wasm.instance.exports,
withMappingCallback: (mappingCallback, f) => {
callbackStack.push(mappingCallback);
try {
f();
} finally {
callbackStack.pop();
}
},
};
})
.then(null, e => {
cachedWasm = null;
throw e;
});
return cachedWasm;
};

View File

@@ -0,0 +1,79 @@
{
"name": "source-map",
"description": "Generates and consumes source maps",
"version": "0.7.6",
"homepage": "https://github.com/mozilla/source-map",
"author": "Nick Fitzgerald <nfitzgerald@mozilla.com>",
"contributors": [
"Tobias Koppers <tobias.koppers@googlemail.com>",
"Duncan Beevers <duncan@dweebd.com>",
"Stephen Crane <scrane@mozilla.com>",
"Ryan Seddon <seddon.ryan@gmail.com>",
"Miles Elam <miles.elam@deem.com>",
"Mihai Bazon <mihai.bazon@gmail.com>",
"Michael Ficarra <github.public.email@michael.ficarra.me>",
"Todd Wolfson <todd@twolfson.com>",
"Alexander Solovyov <alexander@solovyov.net>",
"Felix Gnass <fgnass@gmail.com>",
"Conrad Irwin <conrad.irwin@gmail.com>",
"usrbincc <usrbincc@yahoo.com>",
"David Glasser <glasser@davidglasser.net>",
"Chase Douglas <chase@newrelic.com>",
"Evan Wallace <evan.exe@gmail.com>",
"Heather Arthur <fayearthur@gmail.com>",
"Hugh Kennedy <hughskennedy@gmail.com>",
"David Glasser <glasser@davidglasser.net>",
"Simon Lydell <simon.lydell@gmail.com>",
"Jmeas Smith <jellyes2@gmail.com>",
"Michael Z Goddard <mzgoddard@gmail.com>",
"azu <azu@users.noreply.github.com>",
"John Gozde <john@gozde.ca>",
"Adam Kirkton <akirkton@truefitinnovation.com>",
"Chris Montgomery <christopher.montgomery@dowjones.com>",
"J. Ryan Stinnett <jryans@gmail.com>",
"Jack Herrington <jherrington@walmartlabs.com>",
"Chris Truter <jeffpalentine@gmail.com>",
"Daniel Espeset <daniel@danielespeset.com>",
"Jamie Wong <jamie.lf.wong@gmail.com>",
"Eddy Bruël <ejpbruel@mozilla.com>",
"Hawken Rives <hawkrives@gmail.com>",
"Gilad Peleg <giladp007@gmail.com>",
"djchie <djchie.dev@gmail.com>",
"Gary Ye <garysye@gmail.com>",
"Nicolas Lalevée <nicolas.lalevee@hibnet.org>"
],
"repository": {
"type": "git",
"url": "http://github.com/mozilla/source-map.git"
},
"main": "./source-map.js",
"types": "./source-map.d.ts",
"browser": {
"./lib/read-wasm.js": "./lib/read-wasm-browser.js"
},
"files": [
"source-map.js",
"source-map.d.ts",
"lib/"
],
"engines": {
"node": ">= 12"
},
"license": "BSD-3-Clause",
"scripts": {
"lint": "eslint --fix *.js lib/ test/ --ignore-pattern 'test/source-map-tests/**'",
"test": "git submodule update --init --recursive; node test/run-tests.js",
"coverage": "c8 --reporter=text --reporter=html npm test",
"prettier": "prettier --write .",
"clean": "rm -rf coverage",
"toc": "doctoc --github --notitle README.md CONTRIBUTING.md"
},
"devDependencies": {
"c8": "^7.12.0",
"doctoc": "^2.2.1",
"eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.7.1"
},
"dependencies": {}
}

View File

@@ -0,0 +1,423 @@
// Type definitions for source-map 0.7
// Project: https://github.com/mozilla/source-map
// Definitions by: Morten Houston Ludvigsen <https://github.com/MortenHoustonLudvigsen>,
// Ron Buckton <https://github.com/rbuckton>,
// John Vilk <https://github.com/jvilk>
// Definitions: https://github.com/mozilla/source-map
export type SourceMapUrl = string;
export interface StartOfSourceMap {
file?: string;
sourceRoot?: string;
skipValidation?: boolean;
}
export interface RawSourceMap {
version: number;
sources: string[];
names: string[];
sourceRoot?: string;
sourcesContent?: string[];
mappings: string;
file: string;
}
export interface RawIndexMap extends StartOfSourceMap {
version: number;
sections: RawSection[];
}
export interface RawSection {
offset: Position;
map: RawSourceMap;
}
export interface Position {
line: number;
column: number;
}
export interface NullablePosition {
line: number | null;
column: number | null;
lastColumn: number | null;
}
export interface MappedPosition {
source: string;
line: number;
column: number;
name?: string;
}
export interface NullableMappedPosition {
source: string | null;
line: number | null;
column: number | null;
name: string | null;
}
export interface MappingItem {
source: string;
generatedLine: number;
generatedColumn: number;
lastGeneratedColumn: number | null;
originalLine: number;
originalColumn: number;
name: string;
}
export interface Mapping {
generated: Position;
original: Position;
source: string;
name?: string;
}
export interface CodeWithSourceMap {
code: string;
map: SourceMapGenerator;
}
export interface SourceMappings {
"lib/mappings.wasm": SourceMapUrl | ArrayBuffer;
}
export interface SourceMapConsumer {
/**
* When using SourceMapConsumer outside of node.js, for example on the Web, it
* needs to know from what URL to load lib/mappings.wasm. You must inform it
* by calling initialize before constructing any SourceMapConsumers.
*
* @param mappings an object with the following property:
* - "lib/mappings.wasm": A String containing the URL of the
* lib/mappings.wasm file, or an ArrayBuffer with the contents of
* lib/mappings.wasm.
*/
initialize(mappings: SourceMappings): void;
/**
* Compute the last column for each generated mapping. The last column is
* inclusive.
*/
computeColumnSpans(): void;
/**
* Returns the original source, line, and column information for the generated
* source's line and column positions provided. The only argument is an object
* with the following properties:
*
* - line: The line number in the generated source.
* - column: The column number in the generated source.
* - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
* 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
* closest element that is smaller than or greater than the one we are
* searching for, respectively, if the exact element cannot be found.
* Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
*
* and an object is returned with the following properties:
*
* - source: The original source file, or null.
* - line: The line number in the original source, or null.
* - column: The column number in the original source, or null.
* - name: The original identifier, or null.
*/
originalPositionFor(
generatedPosition: Position & { bias?: number }
): NullableMappedPosition;
/**
* Returns the generated line and column information for the original source,
* line, and column positions provided. The only argument is an object with
* the following properties:
*
* - source: The filename of the original source.
* - line: The line number in the original source.
* - column: The column number in the original source.
* - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
* 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
* closest element that is smaller than or greater than the one we are
* searching for, respectively, if the exact element cannot be found.
* Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
*
* and an object is returned with the following properties:
*
* - line: The line number in the generated source, or null.
* - column: The column number in the generated source, or null.
*/
generatedPositionFor(
originalPosition: MappedPosition & { bias?: number }
): NullablePosition;
/**
* Returns all generated line and column information for the original source,
* line, and column provided. If no column is provided, returns all mappings
* corresponding to a either the line we are searching for or the next
* closest line that has any mappings. Otherwise, returns all mappings
* corresponding to the given line and either the column we are searching for
* or the next closest column that has any offsets.
*
* The only argument is an object with the following properties:
*
* - source: The filename of the original source.
* - line: The line number in the original source.
* - column: Optional. the column number in the original source.
*
* and an array of objects is returned, each with the following properties:
*
* - line: The line number in the generated source, or null.
* - column: The column number in the generated source, or null.
*/
allGeneratedPositionsFor(
originalPosition: MappedPosition
): NullablePosition[];
/**
* Return true if we have the source content for every source in the source
* map, false otherwise.
*/
hasContentsOfAllSources(): boolean;
/**
* Returns the original source content. The only argument is the url of the
* original source file. Returns null if no original source content is
* available.
*/
sourceContentFor(
source: string,
returnNullOnMissing?: boolean
): string | null;
/**
* Iterate over each mapping between an original source/line/column and a
* generated line/column in this source map.
*
* @param callback
* The function that is called with each mapping.
* @param context
* Optional. If specified, this object will be the value of `this` every
* time that `aCallback` is called.
* @param order
* Either `SourceMapConsumer.GENERATED_ORDER` or
* `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
* iterate over the mappings sorted by the generated file's line/column
* order or the original's source/line/column order, respectively. Defaults to
* `SourceMapConsumer.GENERATED_ORDER`.
*/
eachMapping(
callback: (mapping: MappingItem) => void,
context?: any,
order?: number
): void;
/**
* Free this source map consumer's associated wasm data that is manually-managed.
* Alternatively, you can use SourceMapConsumer.with to avoid needing to remember to call destroy.
*/
destroy(): void;
}
export interface SourceMapConsumerConstructor {
prototype: SourceMapConsumer;
GENERATED_ORDER: number;
ORIGINAL_ORDER: number;
GREATEST_LOWER_BOUND: number;
LEAST_UPPER_BOUND: number;
new (
rawSourceMap: RawSourceMap,
sourceMapUrl?: SourceMapUrl
): Promise<BasicSourceMapConsumer>;
new (
rawSourceMap: RawIndexMap,
sourceMapUrl?: SourceMapUrl
): Promise<IndexedSourceMapConsumer>;
new (
rawSourceMap: RawSourceMap | RawIndexMap | string,
sourceMapUrl?: SourceMapUrl
): Promise<BasicSourceMapConsumer | IndexedSourceMapConsumer>;
/**
* Create a BasicSourceMapConsumer from a SourceMapGenerator.
*
* @param sourceMap
* The source map that will be consumed.
*/
fromSourceMap(
sourceMap: SourceMapGenerator,
sourceMapUrl?: SourceMapUrl
): Promise<BasicSourceMapConsumer>;
/**
* Construct a new `SourceMapConsumer` from `rawSourceMap` and `sourceMapUrl`
* (see the `SourceMapConsumer` constructor for details. Then, invoke the `async
* function f(SourceMapConsumer) -> T` with the newly constructed consumer, wait
* for `f` to complete, call `destroy` on the consumer, and return `f`'s return
* value.
*
* You must not use the consumer after `f` completes!
*
* By using `with`, you do not have to remember to manually call `destroy` on
* the consumer, since it will be called automatically once `f` completes.
*
* ```js
* const xSquared = await SourceMapConsumer.with(
* myRawSourceMap,
* null,
* async function (consumer) {
* // Use `consumer` inside here and don't worry about remembering
* // to call `destroy`.
*
* const x = await whatever(consumer);
* return x * x;
* }
* );
*
* // You may not use that `consumer` anymore out here; it has
* // been destroyed. But you can use `xSquared`.
* console.log(xSquared);
* ```
*/
with<T>(
rawSourceMap: RawSourceMap | RawIndexMap | string,
sourceMapUrl: SourceMapUrl | null | undefined,
callback: (
consumer: BasicSourceMapConsumer | IndexedSourceMapConsumer
) => Promise<T> | T
): Promise<T>;
}
export const SourceMapConsumer: SourceMapConsumerConstructor;
export interface BasicSourceMapConsumer extends SourceMapConsumer {
file: string;
sourceRoot: string;
sources: string[];
sourcesContent: string[];
}
export interface BasicSourceMapConsumerConstructor {
prototype: BasicSourceMapConsumer;
new (rawSourceMap: RawSourceMap | string): Promise<BasicSourceMapConsumer>;
/**
* Create a BasicSourceMapConsumer from a SourceMapGenerator.
*
* @param sourceMap
* The source map that will be consumed.
*/
fromSourceMap(sourceMap: SourceMapGenerator): Promise<BasicSourceMapConsumer>;
}
export const BasicSourceMapConsumer: BasicSourceMapConsumerConstructor;
export interface IndexedSourceMapConsumer extends SourceMapConsumer {
sources: string[];
}
export interface IndexedSourceMapConsumerConstructor {
prototype: IndexedSourceMapConsumer;
new (rawSourceMap: RawIndexMap | string): Promise<IndexedSourceMapConsumer>;
}
export const IndexedSourceMapConsumer: IndexedSourceMapConsumerConstructor;
export class SourceMapGenerator {
constructor(startOfSourceMap?: StartOfSourceMap);
/**
* Creates a new SourceMapGenerator based on a SourceMapConsumer
*
* @param sourceMapConsumer The SourceMap.
*/
static fromSourceMap(
sourceMapConsumer: SourceMapConsumer
): SourceMapGenerator;
/**
* Add a single mapping from original source line and column to the generated
* source's line and column for this source map being created. The mapping
* object should have the following properties:
*
* - generated: An object with the generated line and column positions.
* - original: An object with the original line and column positions.
* - source: The original source file (relative to the sourceRoot).
* - name: An optional original token name for this mapping.
*/
addMapping(mapping: Mapping): void;
/**
* Set the source content for a source file.
*/
setSourceContent(sourceFile: string, sourceContent: string): void;
/**
* Applies the mappings of a sub-source-map for a specific source file to the
* source map being generated. Each mapping to the supplied source file is
* rewritten using the supplied source map. Note: The resolution for the
* resulting mappings is the minimium of this map and the supplied map.
*
* @param sourceMapConsumer The source map to be applied.
* @param sourceFile Optional. The filename of the source file.
* If omitted, SourceMapConsumer's file property will be used.
* @param sourceMapPath Optional. The dirname of the path to the source map
* to be applied. If relative, it is relative to the SourceMapConsumer.
* This parameter is needed when the two source maps aren't in the same
* directory, and the source map to be applied contains relative source
* paths. If so, those relative source paths need to be rewritten
* relative to the SourceMapGenerator.
*/
applySourceMap(
sourceMapConsumer: SourceMapConsumer,
sourceFile?: string,
sourceMapPath?: string
): void;
toString(): string;
toJSON(): RawSourceMap;
}
export class SourceNode {
children: SourceNode[];
sourceContents: any;
line: number;
column: number;
source: string;
name: string;
constructor();
constructor(
line: number | null,
column: number | null,
source: string | null,
chunks?: Array<string | SourceNode> | SourceNode | string,
name?: string
);
static fromStringWithSourceMap(
code: string,
sourceMapConsumer: SourceMapConsumer,
relativePath?: string
): SourceNode;
add(chunk: Array<string | SourceNode> | SourceNode | string): SourceNode;
prepend(chunk: Array<string | SourceNode> | SourceNode | string): SourceNode;
setSourceContent(sourceFile: string, sourceContent: string): void;
walk(fn: (chunk: string, mapping: MappedPosition) => void): void;
walkSourceContents(fn: (file: string, content: string) => void): void;
join(sep: string): SourceNode;
replaceRight(pattern: string, replacement: string): SourceNode;
toString(): string;
toStringWithSourceMap(startOfSourceMap?: StartOfSourceMap): CodeWithSourceMap;
}

View File

@@ -0,0 +1,10 @@
/*
* Copyright 2009-2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
exports.SourceMapGenerator =
require("./lib/source-map-generator").SourceMapGenerator;
exports.SourceMapConsumer =
require("./lib/source-map-consumer").SourceMapConsumer;
exports.SourceNode = require("./lib/source-node").SourceNode;

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 };

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,21 @@
export = ReactRefreshPlugin;
declare class ReactRefreshPlugin {
/**
* @param {import('./types').ReactRefreshPluginOptions} [options] Options for react-refresh-plugin.
*/
constructor(options?: import('./types').ReactRefreshPluginOptions | undefined);
/**
* @readonly
* @type {import('./types').NormalizedPluginOptions}
*/
readonly options: import('./types').NormalizedPluginOptions;
/**
* Applies the plugin.
* @param {import('webpack').Compiler} compiler A webpack compiler object.
* @returns {void}
*/
apply(compiler: import('webpack').Compiler): void;
}
declare namespace ReactRefreshPlugin {
export { ReactRefreshPlugin };
}

View File

@@ -0,0 +1,77 @@
export type ErrorOverlayOptions = {
/**
* Path to a JS file that sets up the error overlay integration.
*/
entry?: string | false | undefined;
/**
* The error overlay module to use.
*/
module?: string | false | undefined;
/**
* The socket host to use (WDS only).
*/
sockHost?: string | undefined;
/**
* Path to a JS file that sets up the Webpack socket integration.
*/
sockIntegration?:
| import('type-fest').LiteralUnion<false | 'wds' | 'whm' | 'wps', string>
| undefined;
/**
* The socket path to use (WDS only).
*/
sockPath?: string | undefined;
/**
* The socket port to use (WDS only).
*/
sockPort?: number | undefined;
/**
* The socket protocol to use (WDS only).
*/
sockProtocol?: 'http' | 'https' | 'ws' | 'wss' | undefined;
/**
* Uses a polyfill for the DOM URL API (WDS only).
*/
useURLPolyfill?: boolean | undefined;
};
export type NormalizedErrorOverlayOptions = import('type-fest').SetRequired<
ErrorOverlayOptions,
'entry' | 'module' | 'sockIntegration'
>;
export type ReactRefreshPluginOptions = {
/**
* Enables strict ES Modules compatible runtime.
*/
esModule?: boolean | import('../loader/types').ESModuleOptions | undefined;
/**
* Files to explicitly exclude from processing.
*/
exclude?: string | RegExp | (string | RegExp)[] | undefined;
/**
* Enables the plugin forcefully.
*/
forceEnable?: boolean | undefined;
/**
* Files to explicitly include for processing.
*/
include?: string | RegExp | (string | RegExp)[] | undefined;
/**
* Name of the library bundle.
*/
library?: string | undefined;
/**
* Modifies how the error overlay integration works in the plugin.
*/
overlay?: boolean | ErrorOverlayOptions | undefined;
};
export type OverlayOverrides = {
/**
* Modifies how the error overlay integration works in the plugin.
*/
overlay: false | NormalizedErrorOverlayOptions;
};
export type NormalizedPluginOptions = import('type-fest').SetRequired<
import('type-fest').Except<ReactRefreshPluginOptions, 'overlay'>,
'exclude' | 'include'
> &
OverlayOverrides;

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;