├── fixtures
├── vite
│ ├── src
│ │ ├── count.js
│ │ ├── Counter.jsx
│ │ ├── index.jsx
│ │ └── App.jsx
│ ├── vite.config.js
│ ├── package.json
│ └── index.html
├── parcel
│ ├── src
│ │ ├── count.js
│ │ ├── Counter.jsx
│ │ ├── index.jsx
│ │ ├── index.html
│ │ └── App.jsx
│ └── package.json
├── webpack-cjs
│ ├── src
│ │ ├── count.js
│ │ ├── index.js
│ │ ├── Counter.js
│ │ └── App.js
│ ├── config
│ │ ├── webpack
│ │ │ └── persistentCache
│ │ │ │ └── createEnvironmentHash.js
│ │ ├── webpackDevServer.config.js
│ │ ├── env.js
│ │ ├── paths.js
│ │ ├── modules.js
│ │ └── webpack.config.js
│ ├── public
│ │ └── index.html
│ ├── .gitignore
│ ├── package.json
│ └── scripts
│ │ ├── start.js
│ │ └── build.js
├── webpack
│ ├── src
│ │ ├── count.js
│ │ ├── Counter.js
│ │ ├── index.js
│ │ └── App.js
│ ├── public
│ │ └── index.html
│ ├── .gitignore
│ └── package.json
├── webpack-esm
│ ├── src
│ │ ├── count.js
│ │ ├── Counter.js
│ │ ├── index.js
│ │ └── App.js
│ ├── public
│ │ └── index.html
│ ├── .gitignore
│ └── package.json
├── jest-cjs
│ ├── package.json
│ └── test.js
└── jest-esm
│ ├── test.js
│ └── package.json
├── use-hot-module-reload
├── .npmignore
├── tsup.cjs.ts
├── tsup.mjs.ts
├── tsconfig.json
├── src
│ ├── env.d.ts
│ ├── use-hot-module-reload.cts
│ └── use-hot-module-reload.mts
└── package.json
├── .editorconfig
├── .eslintrc
├── .gitignore
├── CHANGELOG.md
├── README.md
├── LICENSE
└── package.json
/fixtures/vite/src/count.js:
--------------------------------------------------------------------------------
1 | export const count = 0
2 |
--------------------------------------------------------------------------------
/fixtures/parcel/src/count.js:
--------------------------------------------------------------------------------
1 | export const count = 3
2 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/src/count.js:
--------------------------------------------------------------------------------
1 | exports.count = 0
2 |
--------------------------------------------------------------------------------
/fixtures/webpack/src/count.js:
--------------------------------------------------------------------------------
1 | export const count = 0
2 |
--------------------------------------------------------------------------------
/fixtures/webpack-esm/src/count.js:
--------------------------------------------------------------------------------
1 | export const count = 0
2 |
--------------------------------------------------------------------------------
/use-hot-module-reload/.npmignore:
--------------------------------------------------------------------------------
1 | tsup.cjs.ts
2 | tsup.mjs.ts
3 | src
4 |
--------------------------------------------------------------------------------
/fixtures/vite/vite.config.js:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | })
7 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/config/webpack/persistentCache/createEnvironmentHash.js:
--------------------------------------------------------------------------------
1 | const {createHash} = require('crypto')
2 |
3 | module.exports = (env) => {
4 | const hash = createHash('md5')
5 | hash.update(JSON.stringify(env))
6 |
7 | return hash.digest('hex')
8 | }
9 |
--------------------------------------------------------------------------------
/use-hot-module-reload/tsup.cjs.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'tsup'
2 |
3 | export default defineConfig({
4 | entry: ['src/use-hot-module-reload.cts'],
5 | splitting: false,
6 | sourcemap: true,
7 | clean: false,
8 | format: 'cjs',
9 | dts: true,
10 | })
11 |
--------------------------------------------------------------------------------
/use-hot-module-reload/tsup.mjs.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'tsup'
2 |
3 | export default defineConfig({
4 | entry: ['src/use-hot-module-reload.mts'],
5 | splitting: false,
6 | sourcemap: true,
7 | clean: false,
8 | format: 'esm',
9 | dts: true,
10 | })
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; editorconfig.org
2 | root = true
3 | charset= utf8
4 |
5 | [*]
6 | end_of_line = lf
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 | indent_style = space
10 | indent_size = 2
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/fixtures/parcel/src/Counter.jsx:
--------------------------------------------------------------------------------
1 | import {count} from './count'
2 |
3 | const counterStyle = {
4 | position: 'absolute',
5 | bottom: '10px',
6 | right: '10px',
7 | fontSize: '20px',
8 | }
9 |
10 | export function Counter() {
11 | return
{count}
12 | }
13 |
--------------------------------------------------------------------------------
/fixtures/parcel/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 |
5 | const root = ReactDOM.createRoot(document.getElementById('root'))
6 | root.render(
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/fixtures/vite/src/Counter.jsx:
--------------------------------------------------------------------------------
1 | import {count} from './count'
2 |
3 | const counterStyle = {
4 | position: 'absolute',
5 | bottom: '10px',
6 | right: '10px',
7 | fontSize: '20px',
8 | }
9 |
10 | export function Counter() {
11 | return {count}
12 | }
13 |
--------------------------------------------------------------------------------
/fixtures/vite/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 |
5 | const root = ReactDOM.createRoot(document.getElementById('root'))
6 | root.render(
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/fixtures/webpack/src/Counter.js:
--------------------------------------------------------------------------------
1 | import {count} from './count'
2 |
3 | const counterStyle = {
4 | position: 'absolute',
5 | bottom: '10px',
6 | right: '10px',
7 | fontSize: '20px',
8 | }
9 |
10 | export function Counter() {
11 | return {count}
12 | }
13 |
--------------------------------------------------------------------------------
/fixtures/webpack/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 |
5 | const root = ReactDOM.createRoot(document.getElementById('root'))
6 | root.render(
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/fixtures/webpack-esm/src/Counter.js:
--------------------------------------------------------------------------------
1 | import {count} from './count.js'
2 |
3 | const counterStyle = {
4 | position: 'absolute',
5 | bottom: '10px',
6 | right: '10px',
7 | fontSize: '20px',
8 | }
9 |
10 | export function Counter() {
11 | return {count}
12 | }
13 |
--------------------------------------------------------------------------------
/fixtures/webpack-esm/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.js'
4 |
5 | const root = ReactDOM.createRoot(document.getElementById('root'))
6 | root.render(
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/src/index.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const ReactDOM = require('react-dom/client')
3 | const App = require('./App')
4 |
5 | const root = ReactDOM.createRoot(document.getElementById('root'))
6 | root.render(
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/src/Counter.js:
--------------------------------------------------------------------------------
1 | const {count} = require('./count')
2 |
3 | const counterStyle = {
4 | position: 'absolute',
5 | bottom: '10px',
6 | right: '10px',
7 | fontSize: '20px',
8 | }
9 |
10 | function Counter() {
11 | return {count}
12 | }
13 |
14 | module.exports = {Counter}
15 |
--------------------------------------------------------------------------------
/fixtures/webpack/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/fixtures/vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-fixture",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "vite build",
7 | "clean": "rimraf dist",
8 | "start": "vite dev"
9 | },
10 | "dependencies": {
11 | "@vitejs/plugin-react": "^2.0.1",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0",
14 | "vite": "^3.0.9"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/fixtures/webpack-esm/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/use-hot-module-reload/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*"],
3 | "compilerOptions": {
4 | "outDir": "lib",
5 | "strict": true,
6 | "module": "Preserve",
7 | "lib": ["ES2018", "DOM"],
8 | "noUncheckedIndexedAccess": true,
9 | "noUnusedLocals": true,
10 | "noUnusedParameters": true,
11 | "noImplicitReturns": true,
12 | "forceConsistentCasingInFileNames": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/fixtures/webpack/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/fixtures/webpack-esm/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/fixtures/vite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vite React App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "extends": [
5 | "plugin:@typescript-eslint/recommended",
6 | "sanity/typescript",
7 | "prettier/@typescript-eslint",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "prettier/prettier": "error"
12 | },
13 | "env": {
14 | "node": true,
15 | "browser": true
16 | },
17 | "parserOptions": {
18 | "ecmaVersion": 2018
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/fixtures/parcel/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Parcel React App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | *.log
3 |
4 | # Compiled files
5 | dist
6 | lib
7 |
8 | # Coverage directories used by tools like istanbul
9 | coverage
10 | .nyc_output
11 |
12 | # Dependency directories
13 | node_modules
14 |
15 | # MacOS
16 | .DS_Store
17 |
18 | # Non-yarn lockfiles
19 | package-lock.json
20 | pnpm-lock.json
21 |
22 | # Readme that is copied pre-publish
23 | use-hot-module-reload/README.md
24 |
25 | # Bundler caches
26 | .parcel-cache
27 | .vite
28 |
--------------------------------------------------------------------------------
/fixtures/jest-cjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jest-cjs-fixture",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "jest",
7 | "clean": "echo 'nothing to do'"
8 | },
9 | "jest": {
10 | "testEnvironment": "jsdom"
11 | },
12 | "devDependencies": {
13 | "@testing-library/jest-dom": "^5.16.5",
14 | "@testing-library/react": "^14.0.0",
15 | "jest": "^29.5.0",
16 | "jest-environment-jsdom": "^29.5.0",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0",
19 | "use-hot-module-reload": "^1.0.1"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/fixtures/jest-esm/test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {render, screen} from '@testing-library/react'
3 | import {useHotModuleReload} from 'use-hot-module-reload'
4 |
5 | import '@testing-library/jest-dom'
6 |
7 | function SomeComponent() {
8 | useHotModuleReload(() => console.log('it reloaded'))
9 | return React.createElement('div', {role: 'test'}, 'hello')
10 | }
11 |
12 | test('does not crash when using', async () => {
13 | render(React.createElement(SomeComponent, {}))
14 | await screen.findByRole('test')
15 | expect(screen.getByRole('test')).toHaveTextContent('hello')
16 | })
17 |
--------------------------------------------------------------------------------
/fixtures/jest-cjs/test.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const {render, screen} = require('@testing-library/react')
3 | const {useHotModuleReload} = require('use-hot-module-reload')
4 |
5 | require('@testing-library/jest-dom')
6 |
7 | function SomeComponent() {
8 | useHotModuleReload(() => console.log('it reloaded'))
9 | return React.createElement('div', {role: 'test'}, 'hello')
10 | }
11 |
12 | test('does not crash when using', async () => {
13 | render(React.createElement(SomeComponent, {}))
14 | await screen.findByRole('test')
15 | expect(screen.getByRole('test')).toHaveTextContent('hello')
16 | })
17 |
--------------------------------------------------------------------------------
/fixtures/jest-esm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jest-esm-fixture",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "NODE_OPTIONS=--experimental-vm-modules jest",
8 | "clean": "echo 'nothing to do'"
9 | },
10 | "jest": {
11 | "testEnvironment": "jsdom",
12 | "transform": {}
13 | },
14 | "devDependencies": {
15 | "@testing-library/jest-dom": "^5.16.5",
16 | "@testing-library/react": "^14.0.0",
17 | "jest": "^29.5.0",
18 | "jest-environment-jsdom": "^29.5.0",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "use-hot-module-reload": "^1.0.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/fixtures/parcel/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parcel-fixture",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "parcel build src/index.html",
7 | "clean": "rimraf dist",
8 | "start": "parcel src/index.html"
9 | },
10 | "browserslist": {
11 | "production": [
12 | ">0.2%",
13 | "not dead",
14 | "not op_mini all"
15 | ],
16 | "development": [
17 | "last 1 chrome version",
18 | "last 1 firefox version",
19 | "last 1 safari version"
20 | ]
21 | },
22 | "dependencies": {
23 | "parcel": "^2.7.0",
24 | "react": "^18.2.0",
25 | "react-dom": "^18.2.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/fixtures/vite/src/App.jsx:
--------------------------------------------------------------------------------
1 | import {useState, useCallback} from 'react'
2 | import {useHotModuleReload} from 'use-hot-module-reload'
3 | import {Counter} from './Counter'
4 |
5 | function App() {
6 | const [lastHMRed, setLastHMRed] = useState('')
7 | const updateHMRTime = useCallback(() => setLastHMRed(new Date().toISOString()), [])
8 | useHotModuleReload(updateHMRTime)
9 |
10 | return (
11 | <>
12 | useHotModuleReload() demo
13 |
14 |
15 | {lastHMRed
16 | ? `Last hot module reload at: ${lastHMRed}`
17 | : 'Edit some file to trigger an updated lastHMR'}
18 |
19 |
20 |
21 | >
22 | )
23 | }
24 |
25 | export default App
26 |
--------------------------------------------------------------------------------
/fixtures/parcel/src/App.jsx:
--------------------------------------------------------------------------------
1 | import {useState, useCallback} from 'react'
2 | import {useHotModuleReload} from 'use-hot-module-reload'
3 | import {Counter} from './Counter'
4 |
5 | function App() {
6 | const [lastHMRed, setLastHMRed] = useState('')
7 | const updateHMRTime = useCallback(() => setLastHMRed(new Date().toISOString()), [])
8 | useHotModuleReload(updateHMRTime)
9 |
10 | return (
11 | <>
12 | useHotModuleReload() demo
13 |
14 |
15 | {lastHMRed
16 | ? `Last hot module reload at: ${lastHMRed}`
17 | : 'Edit some file to trigger an updated lastHMR'}
18 |
19 |
20 |
21 | >
22 | )
23 | }
24 |
25 | export default App
26 |
--------------------------------------------------------------------------------
/fixtures/webpack/src/App.js:
--------------------------------------------------------------------------------
1 | import {useState, useCallback} from 'react'
2 | import {useHotModuleReload} from 'use-hot-module-reload'
3 | import {Counter} from './Counter'
4 |
5 | function App() {
6 | const [lastHMRed, setLastHMRed] = useState('')
7 | const updateHMRTime = useCallback(() => setLastHMRed(new Date().toISOString()), [])
8 | useHotModuleReload(updateHMRTime)
9 |
10 | return (
11 | <>
12 | useHotModuleReload() demo
13 |
14 |
15 | {lastHMRed
16 | ? `Last hot module reload at: ${lastHMRed}`
17 | : 'Edit some file to trigger an updated lastHMR'}
18 |
19 |
20 |
21 | >
22 | )
23 | }
24 |
25 | export default App
26 |
--------------------------------------------------------------------------------
/fixtures/webpack-esm/src/App.js:
--------------------------------------------------------------------------------
1 | import {useState, useCallback} from 'react'
2 | import {useHotModuleReload} from 'use-hot-module-reload'
3 | import {Counter} from './Counter.js'
4 |
5 | function App() {
6 | const [lastHMRed, setLastHMRed] = useState('')
7 | const updateHMRTime = useCallback(() => setLastHMRed(new Date().toISOString()), [])
8 | useHotModuleReload(updateHMRTime)
9 |
10 | return (
11 | <>
12 | useHotModuleReload() demo
13 |
14 |
15 | {lastHMRed
16 | ? `Last hot module reload at: ${lastHMRed}`
17 | : 'Edit some file to trigger an updated lastHMR'}
18 |
19 |
20 |
21 | >
22 | )
23 | }
24 |
25 | export default App
26 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/src/App.js:
--------------------------------------------------------------------------------
1 | const {useState, useCallback} = require('react')
2 | const {useHotModuleReload} = require('use-hot-module-reload')
3 | const {Counter} = require('./Counter')
4 |
5 | function App() {
6 | const [lastHMRed, setLastHMRed] = useState('')
7 | const updateHMRTime = useCallback(() => setLastHMRed(new Date().toISOString()), [])
8 | useHotModuleReload(updateHMRTime)
9 |
10 | return (
11 | <>
12 | useHotModuleReload() demo
13 |
14 |
15 | {lastHMRed
16 | ? `Last hot module reload at: ${lastHMRed}`
17 | : 'Edit some file to trigger an updated lastHMR'}
18 |
19 |
20 |
21 | >
22 | )
23 | }
24 |
25 | module.exports = App
26 |
--------------------------------------------------------------------------------
/fixtures/webpack/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-fixture",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "react-scripts build",
7 | "clean": "rimraf build",
8 | "start": "react-scripts start"
9 | },
10 | "browserslist": {
11 | "production": [
12 | ">0.2%",
13 | "not dead",
14 | "not op_mini all"
15 | ],
16 | "development": [
17 | "last 1 chrome version",
18 | "last 1 firefox version",
19 | "last 1 safari version"
20 | ]
21 | },
22 | "eslintConfig": {
23 | "extends": [
24 | "react-app"
25 | ]
26 | },
27 | "dependencies": {
28 | "react": "^18.2.0",
29 | "react-dom": "^18.2.0",
30 | "react-scripts": "5.0.1",
31 | "use-hot-module-reload": "^1.0.1"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/use-hot-module-reload/src/env.d.ts:
--------------------------------------------------------------------------------
1 | // Added by `vite-plugin-react`
2 | interface Window {
3 | // eslint-disable-next-line camelcase
4 | __vite_plugin_react_timeout: number
5 | }
6 |
7 | // Added by webpack in CommonJS mode
8 | interface NodeModule {
9 | hot?: {
10 | addStatusHandler: (handler: (status: string) => void) => void
11 | removeStatusHandler: (handler: (status: string) => void) => void
12 | }
13 | }
14 |
15 | interface ImportMeta {
16 | // Added by webpack in ESM mode
17 | webpackHot?: {
18 | addStatusHandler: (handler: (status: string) => void) => void
19 | removeStatusHandler: (handler: (status: string) => void) => void
20 | }
21 |
22 | // Added by vite
23 | hot?: {
24 | on: (event: 'vite:beforeUpdate', handler: () => void) => void
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/fixtures/webpack-esm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-esm-fixture",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "react-scripts build",
8 | "clean": "rimraf build",
9 | "start": "react-scripts start"
10 | },
11 | "browserslist": {
12 | "production": [
13 | ">0.2%",
14 | "not dead",
15 | "not op_mini all"
16 | ],
17 | "development": [
18 | "last 1 chrome version",
19 | "last 1 firefox version",
20 | "last 1 safari version"
21 | ]
22 | },
23 | "eslintConfig": {
24 | "extends": [
25 | "react-app"
26 | ]
27 | },
28 | "dependencies": {
29 | "react": "^18.2.0",
30 | "react-dom": "^18.2.0",
31 | "react-scripts": "5.0.1",
32 | "use-hot-module-reload": "^1.0.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # 📓 Changelog
4 |
5 | All notable changes to this project will be documented in this file. See
6 | [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
7 |
8 | ## [2.0.0](https://github.com/sanity-io/ui/compare/v1.0.3...v2.0.0) (2024-03-22)
9 |
10 | ### ⚠ BREAKING CHANGES
11 |
12 | - Changed the way ESM/CJS is handled, defaulting to ESM mode with `type: 'module'` and using the `.js` extension by default. CommonJS imports are now done through a `.cjs` extension implicitly. While this _shouldn't_ be breaking, getting bundlers and environments conditional loading "just right" is a complex and sometimes brittle process. If you run into issues, please let us know.
13 |
14 | ### Bug Fixes
15 |
16 | - correct hasHMR check in ESM mode ([92a69e2](https://github.com/rexxars/use-hot-module-reload/commit/92a69e2df1252cfdf8079b560f96ececda1ca282))
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # use-hot-module-reload
2 |
3 | React hook that triggers a callback after hot-module reloading has been performed (for any module, not just the one it was used in).
4 |
5 | Works with modern versions of Webpack and Vite.
6 | Other bundlers may be added if they expose a way to listen for updates.
7 | Unsupported bundlers will not trigger the callback, but the hook should not break anything.
8 |
9 | ## Installation
10 |
11 | ```
12 | npm install --save use-hot-module-reload
13 | ```
14 |
15 | ## Usage
16 |
17 | ```tsx
18 | import {useState, useCallback} from 'react'
19 | import {useHotModuleReload} from 'use-hot-module-reload'
20 |
21 | export function MyComponent() {
22 | const [lastHMRed, setLastHMRed] = useState('')
23 | const updateHMRTime = useCallback(() => setLastHMRed(new Date().toISOString()), [])
24 | useHotModuleReload(updateHMRTime)
25 |
26 | return {lastHMRed && `Last hot module reload at: ${lastHMRed}`}
27 | }
28 | ```
29 |
30 | ## License
31 |
32 | MIT © [Espen Hovlandsdal](https://espen.codes/)
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Espen Hovlandsdal
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/config/webpackDevServer.config.js:
--------------------------------------------------------------------------------
1 | const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware')
2 | const ignoredFiles = require('react-dev-utils/ignoredFiles')
3 | const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware')
4 | const paths = require('./paths')
5 |
6 | const host = process.env.HOST || '0.0.0.0'
7 |
8 | module.exports = function () {
9 | return {
10 | headers: {
11 | 'Access-Control-Allow-Origin': '*',
12 | 'Access-Control-Allow-Methods': '*',
13 | 'Access-Control-Allow-Headers': '*',
14 | },
15 | static: {
16 | directory: paths.appPublic,
17 | publicPath: [paths.publicUrlOrPath],
18 | watch: {
19 | ignored: ignoredFiles(paths.appSrc),
20 | },
21 | },
22 | client: {
23 | overlay: {
24 | errors: true,
25 | warnings: false,
26 | },
27 | },
28 | devMiddleware: {
29 | publicPath: paths.publicUrlOrPath.slice(0, -1),
30 | },
31 | host,
32 | historyApiFallback: {
33 | disableDotRule: true,
34 | index: paths.publicUrlOrPath,
35 | },
36 | onBeforeSetupMiddleware(devServer) {
37 | devServer.app.use(evalSourceMapMiddleware(devServer))
38 | },
39 | onAfterSetupMiddleware(devServer) {
40 | devServer.app.use(redirectServedPath(paths.publicUrlOrPath))
41 | },
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/use-hot-module-reload/src/use-hot-module-reload.cts:
--------------------------------------------------------------------------------
1 | import {useEffect} from 'react'
2 |
3 | // Allow us to short-circuit in production/non-HMR environments
4 | const hasHMR = (() => {
5 | try {
6 | return Boolean(typeof module !== 'undefined' && module.hot)
7 | } catch (err) {
8 | return false
9 | }
10 | })()
11 |
12 | /**
13 | * Trigger a callback after hot-module reloads (any, not only the module using the hook).
14 | * Use it to force recomputation of stale values and state that do not automatically update.
15 | * This should be an escape hatch - ideally you shouldn't need this.
16 | *
17 | * @param callback - The callback to be triggered after hot-module reloads.
18 | */
19 | export function useHotModuleReload(callback: () => void): void {
20 | if (!hasHMR) {
21 | return undefined
22 | }
23 |
24 | return useCJSHotModuleReload(callback)
25 | }
26 |
27 | function useCJSHotModuleReload(callback: () => void): void {
28 | useEffect(() => {
29 | if (typeof module.hot === 'undefined' || typeof module.hot?.addStatusHandler !== 'function') {
30 | return undefined
31 | }
32 |
33 | // Webpack in CommonJS mode
34 | const statusHandler = (status: string): void => (status === 'idle' ? callback() : undefined)
35 | module.hot.addStatusHandler(statusHandler)
36 |
37 | return () => module.hot?.removeStatusHandler(statusHandler)
38 | }, [callback])
39 | }
40 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/config/env.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 |
4 | // Make sure that including paths.js after env.js will read .env variables.
5 | delete require.cache[require.resolve('./paths')]
6 |
7 | const NODE_ENV = process.env.NODE_ENV
8 | if (!NODE_ENV) {
9 | throw new Error('The NODE_ENV environment variable is required but was not specified.')
10 | }
11 |
12 | const appDirectory = fs.realpathSync(process.cwd())
13 | process.env.NODE_PATH = (process.env.NODE_PATH || '')
14 | .split(path.delimiter)
15 | .filter((folder) => folder && !path.isAbsolute(folder))
16 | .map((folder) => path.resolve(appDirectory, folder))
17 | .join(path.delimiter)
18 |
19 | const REACT_APP = /^REACT_APP_/i
20 |
21 | function getClientEnvironment(publicUrl) {
22 | const raw = Object.keys(process.env)
23 | .filter((key) => REACT_APP.test(key))
24 | .reduce(
25 | (env, key) => {
26 | env[key] = process.env[key]
27 | return env
28 | },
29 | {
30 | NODE_ENV: process.env.NODE_ENV || 'development',
31 | PUBLIC_URL: publicUrl,
32 | FAST_REFRESH: true,
33 | }
34 | )
35 |
36 | const stringified = {
37 | 'process.env': Object.keys(raw).reduce((env, key) => {
38 | env[key] = JSON.stringify(raw[key])
39 | return env
40 | }, {}),
41 | }
42 |
43 | return {raw, stringified}
44 | }
45 |
46 | module.exports = getClientEnvironment
47 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-cjs-fixture",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "node scripts/build.js",
7 | "clean": "rimraf build",
8 | "start": "node scripts/start.js"
9 | },
10 | "babel": {
11 | "presets": [
12 | "react-app"
13 | ]
14 | },
15 | "browserslist": {
16 | "production": [
17 | ">0.2%",
18 | "not dead",
19 | "not op_mini all"
20 | ],
21 | "development": [
22 | "last 1 chrome version",
23 | "last 1 firefox version",
24 | "last 1 safari version"
25 | ]
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app"
30 | ]
31 | },
32 | "dependencies": {
33 | "@babel/core": "^7.16.0",
34 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
35 | "babel-loader": "^8.2.3",
36 | "browserslist": "^4.18.1",
37 | "case-sensitive-paths-webpack-plugin": "^2.4.0",
38 | "fs-extra": "^10.0.0",
39 | "html-webpack-plugin": "^5.5.0",
40 | "react": "^18.2.0",
41 | "react-dev-utils": "^12.0.1",
42 | "react-dom": "^18.2.0",
43 | "react-refresh": "^0.11.0",
44 | "resolve": "^1.20.0",
45 | "resolve-url-loader": "^4.0.0",
46 | "source-map-loader": "^3.0.0",
47 | "terser-webpack-plugin": "^5.2.5",
48 | "use-hot-module-reload": "^1.0.1",
49 | "webpack": "^5.64.4",
50 | "webpack-dev-server": "^4.6.0"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/config/paths.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath')
4 |
5 | const appDirectory = fs.realpathSync(process.cwd())
6 | const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath)
7 |
8 | const publicUrlOrPath = getPublicUrlOrPath(
9 | process.env.NODE_ENV === 'development',
10 | require(resolveApp('package.json')).homepage,
11 | process.env.PUBLIC_URL
12 | )
13 |
14 | const buildPath = process.env.BUILD_PATH || 'build'
15 |
16 | const moduleFileExtensions = [
17 | 'web.mjs',
18 | 'mjs',
19 | 'web.js',
20 | 'js',
21 | 'web.ts',
22 | 'ts',
23 | 'web.tsx',
24 | 'tsx',
25 | 'json',
26 | 'web.jsx',
27 | 'jsx',
28 | ]
29 |
30 | // Resolve file paths in the same order as webpack
31 | const resolveModule = (resolveFn, filePath) => {
32 | const extension = moduleFileExtensions.find((extension) =>
33 | fs.existsSync(resolveFn(`${filePath}.${extension}`))
34 | )
35 |
36 | if (extension) {
37 | return resolveFn(`${filePath}.${extension}`)
38 | }
39 |
40 | return resolveFn(`${filePath}.js`)
41 | }
42 |
43 | module.exports = {
44 | appPath: resolveApp('.'),
45 | appBuild: resolveApp(buildPath),
46 | appPublic: resolveApp('public'),
47 | appHtml: resolveApp('public/index.html'),
48 | appIndexJs: resolveModule(resolveApp, 'src/index'),
49 | appPackageJson: resolveApp('package.json'),
50 | appSrc: resolveApp('src'),
51 | appNodeModules: resolveApp('node_modules'),
52 | appWebpackCache: resolveApp('node_modules/.cache'),
53 | publicUrlOrPath,
54 | }
55 |
56 | module.exports.moduleFileExtensions = moduleFileExtensions
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": [
4 | "use-hot-module-reload",
5 | "fixtures/jest-cjs",
6 | "fixtures/jest-esm",
7 | "fixtures/parcel",
8 | "fixtures/vite",
9 | "fixtures/webpack",
10 | "fixtures/webpack-cjs",
11 | "fixtures/webpack-esm"
12 | ],
13 | "scripts": {
14 | "build": "yarn workspace use-hot-module-reload build",
15 | "clean": "rimraf fixtures/*/node_modules && yarn workspaces run clean && rimraf node_modules .parcel-cache",
16 | "jest-cjs": "yarn workspace jest-cjs-fixture build",
17 | "jest-esm": "yarn workspace jest-esm-fixture build",
18 | "parcel": "yarn workspace parcel-fixture start",
19 | "publish": "yarn test && yarn workspace use-hot-module-reload publish",
20 | "start": "yarn workspace use-hot-module-reload start",
21 | "test": "yarn workspaces run build",
22 | "vite": "yarn workspace vite-fixture start",
23 | "webpack": "yarn workspace webpack-fixture start",
24 | "webpack-cjs": "yarn workspace webpack-cjs-fixture start",
25 | "webpack-esm": "yarn workspace webpack-esm-fixture start"
26 | },
27 | "prettier": {
28 | "bracketSpacing": false,
29 | "plugins": [
30 | "prettier-plugin-packagejson"
31 | ],
32 | "printWidth": 100,
33 | "semi": false,
34 | "singleQuote": true
35 | },
36 | "devDependencies": {
37 | "@typescript-eslint/eslint-plugin": "^5.33.1",
38 | "@typescript-eslint/parser": "^5.33.1",
39 | "eslint": "^8.22.0",
40 | "eslint-config-prettier": "^8.5.0",
41 | "eslint-config-sanity": "^6.0.0",
42 | "prettier": "^2.7.1",
43 | "prettier-plugin-packagejson": "^2.4.12",
44 | "rimraf": "^4.4.0"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/use-hot-module-reload/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-hot-module-reload",
3 | "version": "2.0.0",
4 | "description": "React hook that triggers a callback after hot-module reloading has been performed (for any module, not just the one it was used in)",
5 | "keywords": [
6 | "hmr",
7 | "hot-module-reload",
8 | "webpack-vite"
9 | ],
10 | "homepage": "https://github.com/rexxars/use-hot-module-reload#readme",
11 | "bugs": {
12 | "url": "https://github.com/rexxars/use-hot-module-reload/issues"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+ssh://git@github.com/rexxars/use-hot-module-reload.git"
17 | },
18 | "license": "MIT",
19 | "author": "Espen Hovlandsdal ",
20 | "sideEffects": false,
21 | "type": "module",
22 | "exports": {
23 | ".": {
24 | "require": "./dist/use-hot-module-reload.cjs",
25 | "default": "./dist/use-hot-module-reload.js"
26 | }
27 | },
28 | "main": "./dist/use-hot-module-reload.cjs",
29 | "module": "./dist/use-hot-module-reload.js",
30 | "types": "dist/use-hot-module-reload.d.ts",
31 | "scripts": {
32 | "prebuild": "rimraf dist",
33 | "build": "concurrently \"tsup --config tsup.cjs.ts\" \"tsup --config tsup.mjs.ts\"",
34 | "clean": "rimraf dist",
35 | "prepublishOnly": "cp ../README.md . && npm run build",
36 | "test": "jest",
37 | "watch": "concurrently --kill-others \"tsup --watch --config tsup.cjs.ts\" \"tsup --watch --config tsup.mjs.ts\""
38 | },
39 | "devDependencies": {
40 | "@types/node": "^14.0.0",
41 | "@types/react": "^18.2.67",
42 | "concurrently": "^7.3.0",
43 | "react": "^18.2.0",
44 | "rimraf": "^3.0.2",
45 | "tsup": "^8.0.2",
46 | "typescript": "^5.4.2"
47 | },
48 | "peerDependencies": {
49 | "react": ">=17.0.0"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/config/modules.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const paths = require('./paths')
3 |
4 | /**
5 | * Get additional module paths based on the baseUrl of a compilerOptions object.
6 | *
7 | * @param {Object} options
8 | */
9 | function getAdditionalModulePaths(options = {}) {
10 | const baseUrl = options.baseUrl
11 |
12 | if (!baseUrl) {
13 | return ''
14 | }
15 |
16 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl)
17 |
18 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is
19 | // the default behavior.
20 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') {
21 | return null
22 | }
23 |
24 | // Allow the user set the `baseUrl` to `appSrc`.
25 | if (path.relative(paths.appSrc, baseUrlResolved) === '') {
26 | return [paths.appSrc]
27 | }
28 |
29 | // If the path is equal to the root directory we ignore it here.
30 | // We don't want to allow importing from the root directly as source files are
31 | // not transpiled outside of `src`. We do allow importing them with the
32 | // absolute path (e.g. `src/Components/Button.js`) but we set that up with
33 | // an alias.
34 | if (path.relative(paths.appPath, baseUrlResolved) === '') {
35 | return null
36 | }
37 |
38 | // Otherwise, throw an error.
39 | throw new Error(
40 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." +
41 | ' Create React App does not support other values at this time.'
42 | )
43 | }
44 |
45 | /**
46 | * Get webpack aliases based on the baseUrl of a compilerOptions object.
47 | *
48 | * @param {*} options
49 | */
50 | function getWebpackAliases(options = {}) {
51 | const baseUrl = options.baseUrl
52 |
53 | if (!baseUrl) {
54 | return {}
55 | }
56 |
57 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl)
58 |
59 | if (path.relative(paths.appPath, baseUrlResolved) === '') {
60 | return {
61 | src: paths.appSrc,
62 | }
63 | }
64 | }
65 |
66 | function getModules() {
67 | return {
68 | additionalModulePaths: getAdditionalModulePaths({}),
69 | webpackAliases: getWebpackAliases({}),
70 | }
71 | }
72 |
73 | module.exports = getModules()
74 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/config/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const HtmlWebpackPlugin = require('html-webpack-plugin')
3 | const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
4 | const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')
5 | const TerserPlugin = require('terser-webpack-plugin')
6 | const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin')
7 | const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin')
8 | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
9 | const paths = require('./paths')
10 | const modules = require('./modules')
11 | const getClientEnvironment = require('./env')
12 |
13 | module.exports = function (webpackEnv) {
14 | const isEnvDevelopment = webpackEnv === 'development'
15 | const isEnvProduction = webpackEnv === 'production'
16 | const isEnvProductionProfile = isEnvProduction && process.argv.includes('--profile')
17 | const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1))
18 |
19 | const shouldUseReactRefresh = env.raw.FAST_REFRESH
20 |
21 | return {
22 | target: ['browserslist'],
23 | stats: 'errors-warnings',
24 | mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
25 | bail: isEnvProduction,
26 | devtool: isEnvProduction ? 'source-map' : 'cheap-module-source-map',
27 | entry: paths.appIndexJs,
28 | output: {
29 | path: paths.appBuild,
30 | pathinfo: isEnvDevelopment,
31 | filename: isEnvProduction
32 | ? 'static/js/[name].[contenthash:8].js'
33 | : isEnvDevelopment && 'static/js/bundle.js',
34 | publicPath: paths.publicUrlOrPath,
35 | },
36 | optimization: {
37 | minimize: isEnvProduction,
38 | minimizer: [
39 | new TerserPlugin({
40 | terserOptions: {parse: {ecma: 8}},
41 | }),
42 | ],
43 | },
44 | resolve: {
45 | modules: ['node_modules', paths.appNodeModules].concat(modules.additionalModulePaths || []),
46 | extensions: paths.moduleFileExtensions
47 | .map((ext) => `.${ext}`)
48 | .filter((ext) => !ext.includes('ts')),
49 | alias: {
50 | ...(isEnvProductionProfile && {
51 | 'react-dom$': 'react-dom/profiling',
52 | 'scheduler/tracing': 'scheduler/tracing-profiling',
53 | }),
54 | ...(modules.webpackAliases || {}),
55 | },
56 | },
57 | module: {
58 | strictExportPresence: true,
59 | rules: [
60 | isEnvProduction && {
61 | enforce: 'pre',
62 | exclude: /@babel(?:\/|\\{1,2})runtime/,
63 | test: /\.(js|mjs|jsx|ts|tsx|css)$/,
64 | loader: require.resolve('source-map-loader'),
65 | },
66 | {
67 | test: /\.(js|mjs|jsx|ts|tsx)$/,
68 | include: paths.appSrc,
69 | loader: require.resolve('babel-loader'),
70 | options: {
71 | sourceType: 'script',
72 | presets: [
73 | '@babel/preset-env',
74 | [
75 | '@babel/preset-react',
76 | {
77 | runtime: 'automatic',
78 | },
79 | ],
80 | ],
81 | plugins: [
82 | '@babel/plugin-transform-runtime',
83 | isEnvDevelopment && shouldUseReactRefresh && require.resolve('react-refresh/babel'),
84 | ].filter(Boolean),
85 | },
86 | },
87 | ].filter(Boolean),
88 | },
89 | plugins: [
90 | new HtmlWebpackPlugin({
91 | inject: true,
92 | template: paths.appHtml,
93 | }),
94 | isEnvProduction && new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
95 | new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
96 | new ModuleNotFoundPlugin(paths.appPath),
97 | new webpack.DefinePlugin(env.stringified),
98 | isEnvDevelopment &&
99 | new ReactRefreshWebpackPlugin({
100 | overlay: false,
101 | }),
102 | new CaseSensitivePathsPlugin(),
103 | ].filter(Boolean),
104 | performance: false,
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/scripts/start.js:
--------------------------------------------------------------------------------
1 | // Do this as the first thing so that any code reading it knows the right env.
2 | process.env.BABEL_ENV = 'development'
3 | process.env.NODE_ENV = 'development'
4 |
5 | // Makes the script crash on unhandled rejections instead of silently
6 | // ignoring them. In the future, promise rejections that are not handled will
7 | // terminate the Node.js process with a non-zero exit code.
8 | process.on('unhandledRejection', (err) => {
9 | throw err
10 | })
11 |
12 | // Ensure environment variables are read.
13 | require('../config/env')
14 |
15 | const fs = require('fs')
16 | const chalk = require('react-dev-utils/chalk')
17 | const webpack = require('webpack')
18 | const WebpackDevServer = require('webpack-dev-server')
19 | const clearConsole = require('react-dev-utils/clearConsole')
20 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles')
21 | const {
22 | choosePort,
23 | createCompiler,
24 | prepareProxy,
25 | prepareUrls,
26 | } = require('react-dev-utils/WebpackDevServerUtils')
27 | const openBrowser = require('react-dev-utils/openBrowser')
28 | const paths = require('../config/paths')
29 | const configFactory = require('../config/webpack.config')
30 | const createDevServerConfig = require('../config/webpackDevServer.config')
31 | const getClientEnvironment = require('../config/env')
32 | const react = require(require.resolve('react', {paths: [paths.appPath]}))
33 |
34 | const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1))
35 | const useYarn = true
36 | const isInteractive = process.stdout.isTTY
37 |
38 | // Warn and crash if required files are missing
39 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
40 | process.exit(1)
41 | }
42 |
43 | // Tools like Cloud9 rely on this.
44 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000
45 | const HOST = process.env.HOST || '0.0.0.0'
46 |
47 | if (process.env.HOST) {
48 | console.log(
49 | chalk.cyan(
50 | `Attempting to bind to HOST environment variable: ${chalk.yellow(
51 | chalk.bold(process.env.HOST)
52 | )}`
53 | )
54 | )
55 | console.log(`If this was unintentional, check that you haven't mistakenly set it in your shell.`)
56 | console.log(`Learn more here: ${chalk.yellow('https://cra.link/advanced-config')}`)
57 | console.log()
58 | }
59 |
60 | // We require that you explicitly set browsers and do not fall back to
61 | // browserslist defaults.
62 | const {checkBrowsers} = require('react-dev-utils/browsersHelper')
63 | checkBrowsers(paths.appPath, isInteractive)
64 | .then(() => {
65 | // We attempt to use the default port but if it is busy, we offer the user to
66 | // run on a different port. `choosePort()` Promise resolves to the next free port.
67 | return choosePort(HOST, DEFAULT_PORT)
68 | })
69 | .then((port) => {
70 | if (port == null) {
71 | // We have not found a port.
72 | return
73 | }
74 |
75 | const config = configFactory('development')
76 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'
77 | const appName = require(paths.appPackageJson).name
78 |
79 | const urls = prepareUrls(protocol, HOST, port, paths.publicUrlOrPath.slice(0, -1))
80 | // Create a webpack compiler that is configured with custom messages.
81 | const compiler = createCompiler({
82 | appName,
83 | config,
84 | urls,
85 | useYarn,
86 | useTypeScript: false,
87 | webpack,
88 | })
89 | // Load proxy config
90 | const proxySetting = require(paths.appPackageJson).proxy
91 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic, paths.publicUrlOrPath)
92 | // Serve webpack assets generated by the compiler over a web server.
93 | const serverConfig = {
94 | ...createDevServerConfig(proxyConfig, urls.lanUrlForConfig),
95 | host: HOST,
96 | port,
97 | }
98 | const devServer = new WebpackDevServer(serverConfig, compiler)
99 | // Launch WebpackDevServer.
100 | devServer.startCallback(() => {
101 | if (isInteractive) {
102 | clearConsole()
103 | }
104 |
105 | console.log(chalk.cyan('Starting the development server...\n'))
106 | openBrowser(urls.localUrlForBrowser)
107 | })
108 | ;['SIGINT', 'SIGTERM'].forEach(function (sig) {
109 | process.on(sig, function () {
110 | devServer.close()
111 | process.exit()
112 | })
113 | })
114 |
115 | if (process.env.CI !== 'true') {
116 | // Gracefully exit when stdin ends
117 | process.stdin.on('end', function () {
118 | devServer.close()
119 | process.exit()
120 | })
121 | }
122 | })
123 | .catch((err) => {
124 | if (err && err.message) {
125 | console.log(err.message)
126 | }
127 | process.exit(1)
128 | })
129 |
--------------------------------------------------------------------------------
/use-hot-module-reload/src/use-hot-module-reload.mts:
--------------------------------------------------------------------------------
1 | import {useEffect} from 'react'
2 |
3 | // Even accessing `import` for a `typeof` check will throw in non-ESM mode,
4 | // thus the try/catch
5 | const isModule = (() => {
6 | try {
7 | return typeof import.meta !== 'undefined'
8 | } catch (err) {
9 | return false
10 | }
11 | })()
12 |
13 | // Allow us to short-circuit in production/non-HMR environments
14 | const hasHMR = (() => {
15 | try {
16 | return Boolean(isModule && (import.meta.hot || import.meta.webpackHot))
17 | } catch (err) {
18 | return false
19 | }
20 | })()
21 |
22 | /**
23 | * Trigger a callback after hot-module reloads (any, not only the module using the hook).
24 | * Use it to force recomputation of stale values and state that do not automatically update.
25 | * This should be an escape hatch - ideally you shouldn't need this.
26 | *
27 | * @param callback - The callback to be triggered after hot-module reloads.
28 | */
29 | export function useHotModuleReload(callback: () => void): void {
30 | if (!hasHMR) {
31 | return undefined
32 | }
33 |
34 | return typeof import.meta.webpackHot === 'undefined'
35 | ? // eslint-disable-next-line react-hooks/rules-of-hooks -- Vite vs Webpack won't change at runtime
36 | useViteHotModuleReload(callback)
37 | : // eslint-disable-next-line react-hooks/rules-of-hooks -- Vite vs Webpack won't change at runtime
38 | useWebpackHotModuleReload(callback)
39 | }
40 |
41 | function useWebpackHotModuleReload(callback: () => void): void {
42 | useEffect(() => {
43 | if (import.meta.webpackHot) {
44 | // Webpack in CommonJS mode
45 | const statusHandler = (status: string): void => (status === 'idle' ? callback() : undefined)
46 | import.meta.webpackHot.addStatusHandler(statusHandler)
47 |
48 | return () => import.meta.webpackHot?.removeStatusHandler(statusHandler)
49 | }
50 |
51 | return undefined
52 | }, [callback])
53 | }
54 |
55 | function useViteHotModuleReload(callback: () => void): void {
56 | useEffect(() => {
57 | // Note: not using early return here in order to optimize tree-shaking
58 | if (import.meta.hot) {
59 | /**
60 | * Unfortunately, there is currently no `vite:afterUpdate` event
61 | * (see https://github.com/vitejs/vite/pull/9810), and `react-refresh`
62 | * does not expose a way to listen for flushed updates (the vite HMR only
63 | * _schedules_ an update - the update is then done asynchronously).
64 | *
65 | * Many attempts were done to find a way to know that React actually updated
66 | * with the new code, but because of layers of timeouts, debouncing, partially
67 | * updated trees and whatnot; this is complicated.
68 | *
69 | * What this actually does is to wait for a `beforeUpdate` event, and then
70 | * probe for the `__vite_plugin_react_timeout` window global to become non-zero.
71 | * When it is, it means an update is _scheduled_, which has a 30ms timeout and
72 | * a 16 ms debounce - so in essence it should be non-zero for about 46ms.
73 | *
74 | * Once it goes back to 0, it means the update is done, and we can trigger the
75 | * callback. Since this is a bit best-effort and not 100% reliable, we also add
76 | * a 1000ms timeout to make sure we don't wait forever should the update already
77 | * have been applied before we observed the change, or if vite changes the
78 | * implementation details (I have requested a callback/event from the vite crew).
79 | */
80 | const disposers = new Set<() => void>()
81 | import.meta.hot.on('vite:beforeUpdate', () => {
82 | let flushTimeout: number | ReturnType
83 | let hasSeenScheduledUpdate = window.__vite_plugin_react_timeout > 0
84 |
85 | const refreshProber = setInterval(() => {
86 | const now = window.__vite_plugin_react_timeout
87 | if (hasSeenScheduledUpdate && now === 0) {
88 | complete()
89 | } else if (!hasSeenScheduledUpdate && now > 0) {
90 | hasSeenScheduledUpdate = true
91 | }
92 | }, 10)
93 |
94 | const fallbackTimeout = setTimeout(complete, 1000, 'fallback')
95 |
96 | function clear() {
97 | clearInterval(refreshProber)
98 | clearTimeout(fallbackTimeout)
99 | clearTimeout(flushTimeout)
100 | }
101 |
102 | function complete() {
103 | clear()
104 |
105 | // While the react refresh has been _triggered_ by this point, it may
106 | // not be flushed yet. Wait for an additional 50ms to make sure it is.
107 | flushTimeout = setTimeout(callback, 50)
108 | }
109 |
110 | disposers.add(clear)
111 | })
112 |
113 | return () => {
114 | disposers.forEach((disposer) => disposer())
115 | disposers.clear()
116 | }
117 | }
118 |
119 | return undefined
120 | }, [callback])
121 | }
122 |
--------------------------------------------------------------------------------
/fixtures/webpack-cjs/scripts/build.js:
--------------------------------------------------------------------------------
1 | // Do this as the first thing so that any code reading it knows the right env.
2 | process.env.BABEL_ENV = 'production'
3 | process.env.NODE_ENV = 'production'
4 |
5 | // Makes the script crash on unhandled rejections instead of silently
6 | // ignoring them. In the future, promise rejections that are not handled will
7 | // terminate the Node.js process with a non-zero exit code.
8 | process.on('unhandledRejection', (err) => {
9 | throw err
10 | })
11 |
12 | // Ensure environment variables are read.
13 | require('../config/env')
14 |
15 | const path = require('path')
16 | const chalk = require('react-dev-utils/chalk')
17 | const fs = require('fs-extra')
18 | const webpack = require('webpack')
19 | const configFactory = require('../config/webpack.config')
20 | const paths = require('../config/paths')
21 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles')
22 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages')
23 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions')
24 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter')
25 | const printBuildError = require('react-dev-utils/printBuildError')
26 |
27 | const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild
28 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild
29 | const useYarn = true
30 |
31 | // These sizes are pretty large. We'll warn for bundles exceeding them.
32 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024
33 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024
34 |
35 | const isInteractive = process.stdout.isTTY
36 |
37 | // Warn and crash if required files are missing
38 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
39 | process.exit(1)
40 | }
41 |
42 | // Generate configuration
43 | const config = configFactory('production')
44 |
45 | // We require that you explicitly set browsers and do not fall back to
46 | // browserslist defaults.
47 | const {checkBrowsers} = require('react-dev-utils/browsersHelper')
48 | checkBrowsers(paths.appPath, isInteractive)
49 | .then(() => {
50 | // First, read the current file sizes in build directory.
51 | // This lets us display how much they changed later.
52 | return measureFileSizesBeforeBuild(paths.appBuild)
53 | })
54 | .then((previousFileSizes) => {
55 | // Remove all content but keep the directory so that
56 | // if you're in it, you don't end up in Trash
57 | fs.emptyDirSync(paths.appBuild)
58 | // Merge with the public folder
59 | copyPublicFolder()
60 | // Start the webpack build
61 | return build(previousFileSizes)
62 | })
63 | .then(
64 | ({stats, previousFileSizes, warnings}) => {
65 | if (warnings.length) {
66 | console.log(chalk.yellow('Compiled with warnings.\n'))
67 | console.log(warnings.join('\n\n'))
68 | console.log(
69 | '\nSearch for the ' +
70 | chalk.underline(chalk.yellow('keywords')) +
71 | ' to learn more about each warning.'
72 | )
73 | console.log(
74 | 'To ignore, add ' + chalk.cyan('// eslint-disable-next-line') + ' to the line before.\n'
75 | )
76 | } else {
77 | console.log(chalk.green('Compiled successfully.\n'))
78 | }
79 |
80 | console.log('File sizes after gzip:\n')
81 | printFileSizesAfterBuild(
82 | stats,
83 | previousFileSizes,
84 | paths.appBuild,
85 | WARN_AFTER_BUNDLE_GZIP_SIZE,
86 | WARN_AFTER_CHUNK_GZIP_SIZE
87 | )
88 | console.log()
89 |
90 | const appPackage = require(paths.appPackageJson)
91 | const publicUrl = paths.publicUrlOrPath
92 | const publicPath = config.output.publicPath
93 | const buildFolder = path.relative(process.cwd(), paths.appBuild)
94 | printHostingInstructions(appPackage, publicUrl, publicPath, buildFolder, useYarn)
95 | },
96 | (err) => {
97 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'
98 | if (tscCompileOnError) {
99 | console.log(
100 | chalk.yellow(
101 | 'Compiled with the following type errors (you may want to check these before deploying your app):\n'
102 | )
103 | )
104 | printBuildError(err)
105 | } else {
106 | console.log(chalk.red('Failed to compile.\n'))
107 | printBuildError(err)
108 | process.exit(1)
109 | }
110 | }
111 | )
112 | .catch((err) => {
113 | if (err && err.message) {
114 | console.log(err.message)
115 | }
116 | process.exit(1)
117 | })
118 |
119 | // Create the production build and print the deployment instructions.
120 | function build(previousFileSizes) {
121 | console.log('Creating an optimized production build...')
122 |
123 | const compiler = webpack(config)
124 | return new Promise((resolve, reject) => {
125 | compiler.run((err, stats) => {
126 | let messages
127 | if (err) {
128 | if (!err.message) {
129 | return reject(err)
130 | }
131 |
132 | let errMessage = err.message
133 |
134 | // Add additional information for postcss errors
135 | if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) {
136 | errMessage += '\nCompileError: Begins at CSS selector ' + err['postcssNode'].selector
137 | }
138 |
139 | messages = formatWebpackMessages({
140 | errors: [errMessage],
141 | warnings: [],
142 | })
143 | } else {
144 | messages = formatWebpackMessages(stats.toJson({all: false, warnings: true, errors: true}))
145 | }
146 | if (messages.errors.length) {
147 | // Only keep the first error. Others are often indicative
148 | // of the same problem, but confuse the reader with noise.
149 | if (messages.errors.length > 1) {
150 | messages.errors.length = 1
151 | }
152 | return reject(new Error(messages.errors.join('\n\n')))
153 | }
154 | if (
155 | process.env.CI &&
156 | (typeof process.env.CI !== 'string' || process.env.CI.toLowerCase() !== 'false') &&
157 | messages.warnings.length
158 | ) {
159 | // Ignore sourcemap warnings in CI builds. See #8227 for more info.
160 | const filteredWarnings = messages.warnings.filter(
161 | (w) => !/Failed to parse source map/.test(w)
162 | )
163 | if (filteredWarnings.length) {
164 | console.log(
165 | chalk.yellow(
166 | '\nTreating warnings as errors because process.env.CI = true.\n' +
167 | 'Most CI servers set it automatically.\n'
168 | )
169 | )
170 | return reject(new Error(filteredWarnings.join('\n\n')))
171 | }
172 | }
173 |
174 | const resolveArgs = {
175 | stats,
176 | previousFileSizes,
177 | warnings: messages.warnings,
178 | }
179 |
180 | return resolve(resolveArgs)
181 | })
182 | })
183 | }
184 |
185 | function copyPublicFolder() {
186 | fs.copySync(paths.appPublic, paths.appBuild, {
187 | dereference: true,
188 | filter: (file) => file !== paths.appHtml,
189 | })
190 | }
191 |
--------------------------------------------------------------------------------