Skip to content

Commit

Permalink
wip: react refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Dec 11, 2020
1 parent d7dd433 commit 0115522
Show file tree
Hide file tree
Showing 16 changed files with 475 additions and 57 deletions.
29 changes: 29 additions & 0 deletions packages/playground/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { useState } from 'react'

function App() {
const [count, setCount] = useState(0)

return (
<div className="App">
<header className="App-header">
<p>Hello Vite + React!</p>
<p>
<button onClick={() => setCount(count => count + 1)}>count is: {count}</button>
</p>
<p>
Edit <code>App.jsx</code> and save to test HMR updates?!@#.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)
}

export default App
6 changes: 2 additions & 4 deletions packages/playground/index.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<h1>hello world fsef</h1>
<img width="300" src="/logo.png" alt="">
<div id="app"></div>
<script id="main" type="module" src="/main.js"></script>
<script id="main" type="module" src="/main.jsx"></script>
<script>
document.getElementById('main').onerror = (e) => {
console.log('efsfsef')
console.log(`entry script load failure:`, e)
}
</script>
26 changes: 0 additions & 26 deletions packages/playground/main.js

This file was deleted.

8 changes: 8 additions & 0 deletions packages/playground/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx'

ReactDOM.render(
<App/>,
document.getElementById('app')
);
3 changes: 3 additions & 0 deletions packages/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
"debug": "node --inspect-brk ../vite/bin/vite.js"
},
"devDependencies": {
"@vitejs/plugin-react-refresh": "^1.0.0",
"rollup-plugin-vue": "^6.0.0",
"sass": "^1.30.0",
"sirv-cli": "^1.0.9"
},
"dependencies": {
"@pika/react": "^16.13.1",
"@pika/react-dom": "^16.13.1",
"lodash-es": "^4.17.15",
"preact": "^10.5.7",
"vue": "^3.0.4"
Expand Down
7 changes: 4 additions & 3 deletions packages/playground/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { defineConfig } from 'vite'
import vue from 'rollup-plugin-vue'
import reactRefresh from '@vitejs/plugin-react-refresh'

export default defineConfig({
// @ts-ignore (when linked locally the types of different rollup installations
// conflicts, but for end user this will work properly)
plugins: [vue()],
plugins: [reactRefresh],
esbuild: {
jsxFactory: 'h',
jsxFragment: 'Fragment'
// jsxFactory: 'h',
// jsxFragment: 'Fragment'
},
server: {
proxy: {
Expand Down
175 changes: 175 additions & 0 deletions packages/plugin-react-refresh/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// @ts-check
const fs = require('fs')

const runtimePublicPath = '/@react-refresh.js'
const runtimeFilePath = require.resolve(
'react-refresh/cjs/react-refresh-runtime.development.js'
)

function debounce(fn, delay) {
let handle
return () => {
clearTimeout(handle)
handle = setTimeout(fn, delay)
}
}

const runtimeCode = `
const exports = {}
${fs.readFileSync(runtimeFilePath, 'utf-8')}
${debounce.toString()}
exports.performReactRefresh = debounce(exports.performReactRefresh, 16)
export default exports
`

/**
* @type { import('vite').Plugin }
*/
const resolve = {
name: 'react-refresh-resolve',
resolveId(id) {
if (id === 'react') {
return this.resolve('@pika/react/source.development.js')
}
if (id === 'react-dom') {
return this.resolve('@pika/react-dom/source.development.js')
}
if (id === runtimePublicPath) {
return runtimeFilePath
}
},

load(id) {
if (id === runtimeFilePath) {
return runtimeCode
}
}
}

/**
* @type { import('vite').Plugin }
*/
const transform = {
name: 'react-refresh-transform',

// make sure this is applied after vite's internal esbuild transform
// which handles (j|t)sx
enforce: 'post',

transform(code, id) {
// @ts-ignore
if (!this.serverContext) {
return
}
if (!/\.(t|j)sx?$/.test(id) || id.includes('node_modules')) {
return
}

const isReasonReact = id.endsWith('.bs.js')
const result = require('@babel/core').transformSync(code, {
plugins: [
require('@babel/plugin-syntax-import-meta'),
require('react-refresh/babel')
],
ast: !isReasonReact,
sourceMaps: true,
sourceFileName: id
})

if (!/\$RefreshReg\$\(/.test(result.code)) {
// no component detected in the file
return code
}

const header = `
import RefreshRuntime from "${runtimePublicPath}";
let prevRefreshReg;
let prevRefreshSig;
if (!window.__vite_plugin_react_preamble_installed__) {
throw new Error(
"vite-plugin-react can't detect preamble. Something is wrong. See https://github.com/vitejs/vite-plugin-react/pull/11#discussion_r430879201"
);
}
if (import.meta.hot) {
prevRefreshReg = window.$RefreshReg$;
prevRefreshSig = window.$RefreshSig$;
window.$RefreshReg$ = (type, id) => {
RefreshRuntime.register(type, ${JSON.stringify(id)} + " " + id)
};
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
}`.replace(/[\n]+/gm, '')

const footer = `
if (import.meta.hot) {
window.$RefreshReg$ = prevRefreshReg;
window.$RefreshSig$ = prevRefreshSig;
${
isReasonReact || isRefreshBoundary(result.ast)
? `import.meta.hot.accept();`
: ``
}
if (!window.__vite_plugin_react_timeout) {
window.__vite_plugin_react_timeout = setTimeout(() => {
window.__vite_plugin_react_timeout = 0;
RefreshRuntime.performReactRefresh();
}, 30);
}
}`

return {
code: `${header}${result.code}${footer}`,
map: result.map
}
},

transformIndexHtml() {
return [
{
tag: 'script',
attrs: { type: 'module' },
children: `
import RefreshRuntime from "${runtimePublicPath}"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
`
}
]
}
}

/**
* @param {import('@babel/types').File} ast
*/
function isRefreshBoundary(ast) {
// Every export must be a React component.
return ast.program.body.every((node) => {
if (node.type !== 'ExportNamedDeclaration') {
return true
}
const { declaration, specifiers } = node
if (declaration && declaration.type === 'VariableDeclaration') {
return declaration.declarations.every(
({ id }) => id.type === 'Identifier' && isComponentishName(id.name)
)
}
return specifiers.every(
({ exported }) =>
exported.type === 'Identifier' && isComponentishName(exported.name)
)
})
}

/**
* @param {string} name
*/
function isComponentishName(name) {
return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z'
}

module.exports = [resolve, transform]
12 changes: 12 additions & 0 deletions packages/plugin-react-refresh/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@vitejs/plugin-react-refresh",
"version": "1.0.0",
"dependencies": {
"@babel/core": "^7.12.10",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"react-refresh": "^0.9.0"
},
"devDependencies": {
"@babel/types": "^7.12.10"
}
}
4 changes: 0 additions & 4 deletions packages/plugin-react/package.json

This file was deleted.

2 changes: 1 addition & 1 deletion packages/vite/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vite",
"version": "2.0.0-beta.1",
"version": "2.0.0-alpha.1",
"license": "MIT",
"author": "Evan You",
"description": "Native-ESM powered web dev build tool",
Expand Down
3 changes: 2 additions & 1 deletion packages/vite/src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ cli
options.mode,
options.config
).catch((e) => {
console.error(chalk.red(e.toString()))
console.log(chalk.red('[vite] failed to start dev server'))
console.error(e.stack)
process.exit(1)
})
})
Expand Down
7 changes: 4 additions & 3 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface UserConfig {
/**
* List of vite plugins to use.
*/
plugins?: Plugin[]
plugins?: (Plugin | Plugin[])[]
/**
* Universal rollup options (used in both serve and build)
* Use function config to use conditional options for serve/build
Expand Down Expand Up @@ -184,7 +184,7 @@ export async function resolveConfig(
const normalPlugins: Plugin[] = []

if (plugins) {
plugins.forEach((p) => {
plugins.flat().forEach((p) => {
if (p.enforce === 'pre') prePlugins.push(p)
else if (p.enforce === 'post') postPlugins.push(p)
else normalPlugins.push(p)
Expand Down Expand Up @@ -220,7 +220,8 @@ export async function resolveConfig(
resolved.plugins = resolvePlugins(
command,
resolved,
[...prePlugins, ...normalPlugins],
prePlugins,
normalPlugins,
postPlugins
)

Expand Down
2 changes: 2 additions & 0 deletions packages/vite/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ export function resolvePlugins(
command: 'build' | 'serve',
config: ResolvedConfig,
prePlugins: Plugin[],
normalPlugins: Plugin[],
postPlugins: Plugin[]
): Plugin[] {
const isBuild = command === 'build'

return [
...prePlugins,
...normalPlugins,
resolvePlugin(config),
nodeResolve({
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
Expand Down
Loading

0 comments on commit 0115522

Please sign in to comment.