├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── example-esm
├── .babelrc
├── package.json
├── public
│ └── index.html
├── rollup.config.js
└── src
│ ├── App.js
│ ├── MyClassComponent.js
│ ├── MyHookComponent.js
│ └── main.js
├── example
├── .babelrc
├── package.json
├── public
│ └── index.html
├── rollup.config.js
└── src
│ ├── App.js
│ ├── MyClassComponent.js
│ ├── MyHookComponent.js
│ └── main.js
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | dist/
3 | *.log
4 | node_modules/
5 | .DS_STORE
6 | *.swp
7 | .npm
8 | .eslintcache
9 | target/
10 | *.stackdump
11 | package-lock.json
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | example/
2 | example-esm/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Paul Sweeney
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rollup-plugin-react-refresh
2 |
3 | Development plugin for [Nollup](https://github.com/PepsRyuu/nollup) / [Rollup](https://rollupjs.org/guide/en/), to enable Hot Module Replacement for React applications. Wraps [react-refresh](https://github.com/facebook/react/tree/master/packages/react-refresh).
4 |
5 | See ```example``` on how to use.
6 |
7 | ## Using the Plugin
8 |
9 | Run ```npm install rollup-plugin-react-refresh react-refresh --save-dev```
10 |
11 | Import the plugin and add it to your Rollup configuration during development.
12 |
13 | ```
14 | import refresh from 'rollup-plugin-react-refresh';
15 |
16 | module.exports = {
17 | plugins: [
18 | process.env.NODE_ENV === 'development' && refresh()
19 | ]
20 | }
21 | ```
22 |
23 | You'll also need to update your ```.babelrc``` to include the ```react-refresh``` Babel transform during development:
24 |
25 | ```
26 | {
27 | "presets": ["@babel/preset-react"],
28 | "env": {
29 | "development": {
30 | "plugins": ["react-refresh/babel"]
31 | }
32 | }
33 | }
34 | ```
35 |
36 | Finally, run Nollup:
37 |
38 | ```
39 | nollup -c
40 | ```
41 |
42 | You can then start modifying your React components and see changes in your app as you save.
--------------------------------------------------------------------------------
/example-esm/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-react"],
3 | "env": {
4 | "development": {
5 | "plugins": ["react-refresh/babel"]
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/example-esm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "shx rm -rf dist",
4 | "start": "cross-env NODE_ENV=development nollup -c --hot --content-base public --port 9001",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c",
6 | "install": "shx mkdir node_modules/rollup-plugin-react-refresh && shx cp ../*.js* node_modules/rollup-plugin-react-refresh/"
7 | },
8 | "dependencies": {
9 | "react": "npm:@pika/react@^16.13.1",
10 | "react-dom": "npm:@pika/react-dom@^16.13.1"
11 | },
12 | "devDependencies": {
13 | "@babel/core": "^7.13.14",
14 | "@babel/preset-react": "^7.13.13",
15 | "@rollup/plugin-babel": "^5.3.0",
16 | "@rollup/plugin-node-resolve": "^11.2.1",
17 | "cross-env": "^7.0.3",
18 | "nollup": "^0.15.6",
19 | "react-refresh": "^0.9.0",
20 | "rollup": "^2.44.0",
21 | "rollup-plugin-alias": "^2.2.0",
22 | "rollup-plugin-static-files": "^0.2.0",
23 | "rollup-plugin-terser": "^7.0.2",
24 | "shx": "^0.3.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example-esm/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React Refresh Example
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example-esm/rollup.config.js:
--------------------------------------------------------------------------------
1 | import node_resolve from '@rollup/plugin-node-resolve';
2 | import babel from '@rollup/plugin-babel';
3 | import static_files from 'rollup-plugin-static-files';
4 | import { terser } from 'rollup-plugin-terser';
5 | import refresh from 'rollup-plugin-react-refresh';
6 | import alias from 'rollup-plugin-alias';
7 |
8 | let config = {
9 | input: './src/main.js',
10 | output: {
11 | dir: 'dist',
12 | format: 'esm',
13 | entryFileNames: '[name].[hash].js',
14 | assetFileNames: '[name].[hash][extname]'
15 | },
16 | plugins: [
17 | process.env.NODE_ENV === 'development' && alias({
18 | entries:[
19 | { find:'react', replacement: require.resolve('react/source.development') },
20 | { find:'react-dom', replacement: require.resolve('react-dom/source.development') },
21 | ]
22 | }),
23 | babel(),
24 | node_resolve(),
25 | process.env.NODE_ENV === 'development' && refresh()
26 | ]
27 | }
28 |
29 | if (process.env.NODE_ENV === 'production') {
30 | config.plugins = config.plugins.concat([
31 | static_files({
32 | include: ['./public']
33 | }),
34 | terser()
35 | ]);
36 | }
37 |
38 | export default config;
--------------------------------------------------------------------------------
/example-esm/src/App.js:
--------------------------------------------------------------------------------
1 | import MyClassComponent from './MyClassComponent';
2 | import MyHookComponent from './MyHookComponent';
3 | import React from 'react';
4 |
5 | let App = () => (
6 |
7 |
Hello World
8 |
9 |
10 |
11 | );
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/example-esm/src/MyClassComponent.js:
--------------------------------------------------------------------------------
1 | import React , { Component } from 'react';
2 |
3 | export default class MyClassComponent extends Component {
4 | constructor () {
5 | super();
6 |
7 | this.state = {
8 | count: 0
9 | };
10 | }
11 |
12 | componentDidMount() {
13 | this.interval = setInterval(() => {
14 | this.setState({
15 | count: this.state.count + 1
16 | })
17 | }, 200);
18 | }
19 |
20 | componentWillUnmount() {
21 | clearInterval(this.interval)
22 | }
23 |
24 | render() {
25 | return MyClassComponent: {this.state.count}
26 | }
27 | }
--------------------------------------------------------------------------------
/example-esm/src/MyHookComponent.js:
--------------------------------------------------------------------------------
1 | import React , { useState, useEffect } from 'react';
2 |
3 | export default function MyHookComponent () {
4 | let [ count, setCount ] = useState(0);
5 |
6 | useEffect(() => {
7 | let interval = setInterval(() => {
8 | setCount(c => c + 1);
9 | }, 200);
10 |
11 | return () => {
12 | clearInterval(interval)
13 | };
14 | }, []);
15 |
16 | return (
17 | MyHookComponent: {count}
18 | );
19 | }
--------------------------------------------------------------------------------
/example-esm/src/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | let root = document.querySelector('#app');
6 | document.body.appendChild(root);
7 | ReactDOM.render(, root);
--------------------------------------------------------------------------------
/example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@babel/preset-react", {
3 | "runtime": "automatic"
4 | }]],
5 | "env": {
6 | "development": {
7 | "plugins": ["react-refresh/babel"]
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "shx rm -rf dist",
4 | "start": "cross-env NODE_ENV=development nollup -c --hot --content-base public --port 9001",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c",
6 | "install": "shx mkdir node_modules/rollup-plugin-react-refresh && shx cp ../*.js* node_modules/rollup-plugin-react-refresh/"
7 | },
8 | "dependencies": {
9 | "react": "^17.0.2",
10 | "react-dom": "^17.0.2"
11 | },
12 | "devDependencies": {
13 | "@babel/core": "^7.13.14",
14 | "@babel/preset-react": "^7.13.13",
15 | "@rollup/plugin-babel": "^5.3.0",
16 | "@rollup/plugin-node-resolve": "^11.2.1",
17 | "cross-env": "^7.0.3",
18 | "nollup": "^0.15.6",
19 | "react-refresh": "^0.9.0",
20 | "rollup": "^2.44.0",
21 | "rollup-plugin-commonjs-alternate": "^0.8.0",
22 | "rollup-plugin-static-files": "^0.2.0",
23 | "rollup-plugin-terser": "^7.0.2",
24 | "shx": "^0.3.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React Refresh Example
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example/rollup.config.js:
--------------------------------------------------------------------------------
1 | import node_resolve from '@rollup/plugin-node-resolve';
2 | import babel from '@rollup/plugin-babel';
3 | import commonjs from 'rollup-plugin-commonjs-alternate';
4 | import static_files from 'rollup-plugin-static-files';
5 | import { terser } from 'rollup-plugin-terser';
6 | import refresh from 'rollup-plugin-react-refresh';
7 |
8 | let config = {
9 | input: './src/main.js',
10 | output: {
11 | dir: 'dist',
12 | format: 'esm',
13 | entryFileNames: '[name].[hash].js',
14 | assetFileNames: '[name].[hash][extname]'
15 | },
16 | plugins: [
17 | babel(),
18 | node_resolve(),
19 | commonjs({
20 | define: {
21 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
22 | }
23 | }),
24 | process.env.NODE_ENV === 'development' && refresh()
25 | ]
26 | }
27 |
28 | if (process.env.NODE_ENV === 'production') {
29 | config.plugins = config.plugins.concat([
30 | static_files({
31 | include: ['./public']
32 | }),
33 | terser()
34 | ]);
35 | }
36 |
37 | export default config;
--------------------------------------------------------------------------------
/example/src/App.js:
--------------------------------------------------------------------------------
1 | import MyClassComponent from './MyClassComponent';
2 | import MyHookComponent from './MyHookComponent';
3 | import React from 'react';
4 |
5 | let App = () => (
6 |
7 |
Hello World
8 |
9 |
10 |
11 | );
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/example/src/MyClassComponent.js:
--------------------------------------------------------------------------------
1 | import React , { Component } from 'react';
2 |
3 | export default class MyClassComponent extends Component {
4 | constructor () {
5 | super();
6 |
7 | this.state = {
8 | count: 0
9 | };
10 | }
11 |
12 | componentDidMount() {
13 | this.interval = setInterval(() => {
14 | this.setState({
15 | count: this.state.count + 1
16 | })
17 | }, 200);
18 | }
19 |
20 | componentWillUnmount() {
21 | clearInterval(this.interval)
22 | }
23 |
24 | render() {
25 | return MyClassComponent: {this.state.count}
26 | }
27 | }
--------------------------------------------------------------------------------
/example/src/MyHookComponent.js:
--------------------------------------------------------------------------------
1 | import React , { useState, useEffect } from 'react';
2 |
3 | export default function MyHookComponent () {
4 | let [ count, setCount ] = useState(0);
5 |
6 | useEffect(() => {
7 | let interval = setInterval(() => {
8 | setCount(c => c + 1);
9 | }, 200);
10 |
11 | return () => {
12 | clearInterval(interval)
13 | };
14 | }, []);
15 |
16 | return (
17 | MyHookComponent: {count}
18 | );
19 | }
--------------------------------------------------------------------------------
/example/src/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | let root = document.querySelector('#app');
6 | document.body.appendChild(root);
7 | ReactDOM.render(, root);
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | let fs = require('fs');
2 |
3 | let runtime = fs.readFileSync(require.resolve('react-refresh/cjs/react-refresh-runtime.development.js'), 'utf8');
4 |
5 | runtime = runtime.replace('process.env.NODE_ENV', JSON.stringify(process.env.NODE_ENV));
6 |
7 | function ReactRefresh (opts = {}) {
8 | return {
9 | nollupBundleInit () {
10 | return `
11 | (function () {
12 | let exports = {}; let module = { exports: exports };
13 |
14 | ${runtime};
15 |
16 | window.$RefreshRuntime$ = exports;
17 | })();
18 |
19 | window.$RefreshRuntime$.injectIntoGlobalHook(window);
20 | window.$RefreshReg$ = () => {};
21 | window.$RefreshSig$ = () => type => type;
22 | `
23 | },
24 |
25 | resolveId (id) {
26 | if (id === 'react-refresh-runtime') {
27 | return id;
28 | }
29 | },
30 |
31 | load (id) {
32 | if (id === 'react-refresh-runtime') {
33 | return `
34 | export function isReactRefreshBoundary(moduleExports) {
35 | for (let key in moduleExports) {
36 | let _c = moduleExports[key];
37 | if ($RefreshRuntime$.isLikelyComponentType(_c)) {
38 | $RefreshReg$(_c, _c.displayName || _c.name);
39 | }
40 | }
41 |
42 | if ($RefreshRuntime$.isLikelyComponentType(moduleExports)) {
43 | return true;
44 | }
45 | if (moduleExports == null || typeof moduleExports !== 'object') {
46 | // Exit if we can't iterate over exports.
47 | return false;
48 | }
49 | let hasExports = false;
50 | let areAllExportsComponents = true;
51 | for (const key in moduleExports) {
52 | hasExports = true;
53 | if (key === '__esModule') {
54 | continue;
55 | }
56 | const desc = Object.getOwnPropertyDescriptor(moduleExports, key);
57 | if (desc && desc.get) {
58 | // Don't invoke getters as they may have side effects.
59 | return false;
60 | }
61 | const exportValue = moduleExports[key];
62 | if (!$RefreshRuntime$.isLikelyComponentType(exportValue)) {
63 | areAllExportsComponents = false;
64 | }
65 | }
66 | return hasExports && areAllExportsComponents;
67 | };
68 |
69 | export function __$RefreshCheck$__(m) {
70 | if (isReactRefreshBoundary(m.exports)) {
71 | m.hot.accept(() => require(m.id))
72 | setTimeout(function () {
73 | $RefreshRuntime$.performReactRefresh()
74 | }, 0);
75 | }
76 | }
77 | `;
78 | }
79 | },
80 |
81 | nollupModuleWrap (code) {
82 | return `
83 | var prevRefreshReg = window.$RefreshReg$;
84 | var prevRefreshSig = window.$RefreshSig$;
85 | var RefreshRuntime = window.$RefreshRuntime$
86 |
87 | if (RefreshRuntime) {
88 | window.$RefreshReg$ = function (type, id) {
89 | var fullId = module.id + ' ' + id;
90 | RefreshRuntime.register(type, fullId);
91 | }
92 | window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
93 | }
94 |
95 | try {
96 | ${code}
97 | } finally {
98 | window.$RefreshReg$ = prevRefreshReg;
99 | window.$RefreshSig$ = prevRefreshSig;
100 | }
101 | `;
102 | },
103 |
104 | transform (code, id) {
105 | if (id === 'react-refresh-runtime' || id.includes('node_modules') ) {
106 | return;
107 | }
108 |
109 | if (
110 | code.indexOf('React.createElement') === -1 && // React < 17
111 | code.indexOf('react/jsx') === -1 // React 17+ (react/jsx-runtime & react/jsx-dev-runtime)
112 | ) {
113 | return;
114 | }
115 |
116 | return {
117 | code: [
118 | code,
119 | 'import { __$RefreshCheck$__ } from "react-refresh-runtime"',
120 | '__$RefreshCheck$__(module)'
121 | ].join(';'),
122 | map: null
123 | };
124 | }
125 | }
126 | }
127 |
128 | module.exports = ReactRefresh;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rollup-plugin-react-refresh",
3 | "version": "0.0.3",
4 | "main": "index.js",
5 | "author": "Paul Sweeney",
6 | "license": "MIT",
7 | "peerDependencies": {
8 | "react-refresh": "^0.9.0"
9 | },
10 | "devDependencies": {
11 | "react-refresh": "^0.9.0"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------