在 html 中直接使用 Esm、Jsx 脚本快速调试和使用 React@19 和 Vue@3 源码,解决 React19 UMD 构建等问题
当然说这么多可能的缺点,不是说构建工具存在的无意义,而是在 Html
中,你也可以使用 Jsx
、Tsx
、Esm
进行 demo
编写,简单场景中,可以获得和脚手架一样的编码体验,其优势旨在于代码与源码的关系更加纯粹,调试更加方便,无其他心智负担。
React@19 in Html
React@19
中移除了umd
的相关构建,若你的项目中使用了externals
类似功能,可参考 react-debug-in-html 和 umd-react 继续使用umd
构建(不推荐生产使用)
当然 webpack
的 externals
也支持原生 esm
模块的外部引入,看需求抉择
babel 转义 Jsx
importmap
缺点就是, 无论配置的是本地的 相对路径
,还是网络的 绝对路径
,在 IDE
中,无法直接定位方法对应的代码位置,这将在调试中造成一定困扰。当然你也可以直接使用本地的相对路径,可配合 IDE
进行快速调试
import React from 'react';
function App() {
return <h1>Hello, React!</h1>;
}
export default App;
import React from 'react';
function App() {
return /*#__PURE__*/ React.createElement('h1', {
children: 'Hello, React!',
});
}
export default App;
后续介绍使用 Jsx
部分时,有该问题的解决方案
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- esm from esm.sh, you also can use jsdelivr -->
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react?dev",
"react-dom/": "https://esm.sh/react-dom@19.1.0&dev/"
}
}
</script>
</head>
<body>
<div id="app"></div>
</body>
<script type="text/babel" data-type="module">
import React from 'react';
import { createRoot } from 'react-dom/client';
// babel 不会处理引入的脚本,你可以将 Count 中的 jsx 使用 createElement 手动转义,也行
// import Count from './Count.js';
function App() {
return <h1>Hello, React!</h1>;
}
const root = createRoot(document.getElementById('app'));
root.render(<App />);
</script>
</html>
【推荐】在 Html
中使用 jsx
后缀的脚本文件
注意,从这里将不使用远程的
esm
模块,而是使用本地的esm
模块(方便调试),且使用importmap
定义模块路径
将 cjs 转为 esm
下面是需要转换的
React@19
的相关源码地址(19
在构建时将Client
和Schedule
从Dom
中拆分出来,所以这两个也需要转换)
建议使用的时候去除 版本号
再转换,可以获取最新版
react
:https://cdn.jsdelivr.net/npm/react@19.1.0/cjs/react.development.jsReact-Dom
:https://cdn.jsdelivr.net/npm/react-dom@19.1.0/cjs/react-dom.development.jsReact-Dom/client
: https://cdn.jsdelivr.net/npm/react-dom@19.1.0/cjs/react-dom-client.development.jsscheduler
: https://cdn.jsdelivr.net/npm/scheduler@0.26.0/cjs/scheduler.development.js
通过 serviceWorker 转义 jsx 脚本文件
当然也可以将 tsx
转为 js
,demo
项目使用 ts
不是在找罪受嘛?还是 jsx
效率点~ 若需要转换 ts
或 vue
文件,需要注意 Response
中的 Content-Type
是否为 application/javascript
,毕竟浏览器只认这个
self.addEventListener('install', (e) => e.waitUntil(getBabel()));
self.addEventListener('fetch', (e) => e.respondWith(handleRequest(e.request)));
async function getBabel() {
const r = await fetch('https://unpkg.com/@babel/standalone@7.27.0/babel.min.js');
const babel = await r.text();
new Function(babel).apply(self);
}
async function handleRequest(request) {
const url = new URL(request.url);
const r = await fetch(request);
const nextResponse = new Response(r.body, r);
if (url.pathname.endsWith('.jsx')) {
nextResponse.headers.set('Content-Type', 'application/javascript');
}
if (nextResponse.status === 200 && url.pathname.endsWith('.jsx')) {
const jsx = await nextResponse.text();
const js = self.Babel.transform(jsx, { presets: ['react'] }).code;
return new Response(js, nextResponse);
} else {
return nextResponse;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>React Source Debug</title>
<script>
navigator.serviceWorker.register('/transform-code.js', {
scope: '/',
});
</script>
<script type="importmap">
{
"imports": {
"react": "/public/react/react.js",
"react-dom": "/public/react/react-dom.js",
"react-dom/client": "/public/react/react-dom-client.js",
"scheduler": "/public/react/scheduler.js"
}
}
</script>
</head>
<body>
<div id="app"></div>
</body>
<script type="module">
import './src/app.jsx';
</script>
</html>
serviceWorker
不生效?
注意 scope 的配置和
serviceWorker
文件的路径,推荐将注册脚本放在root
下,这样可以灵活进行scope
的配置。(scope
可以理解为serviceWorker
在哪个域生效)serviceWorker
注册成功了,但未生效?可以尝试刷新,或在控制面板中service workers
手动刷新serviceWorker
,或重新注册
vue 在 Html 中使用
vue 在官网就提供了相关示例,这里就不过多废话了。通过 CDN 使用 Vue
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
</head>
<body>
<div id="app">
<p>count: {{count}}</p>
</div>
</body>
<script type="module">
import { createApp, onMounted, ref } from 'vue';
createApp({
// template 优先 html 中的模板(app 中)
// template 中的语法遵循 SFC 中的规则,如 ref 变量在这里不需要 value(自动浅层解包)
template: `
<div>
<button @click="increment">计数:{{ count }}</button>
<p>双倍:{{ doubleCount }}</p>
<div>static node - 1</div>
<div>static node - 2</div>
<div>static node - 3</div>
</div>
`,
setup() {
const count = ref(0);
const increment = () => {
count.value++; // 更新计数
};
onMounted(function () {
console.log('mounted', count.value);
});
return {
count,
doubleCount: 0,
increment,
};
},
}).mount('#app');
</script>
</html>
调试建议
使用
console.trace
在源码中进行log
,可以快速定位到当前方法的完整调用栈
,比debugger
更清晰虽然是
development
的源码,但在压缩后,会出现与源码不完全一致的情况,如常量会直接使用对应值,好处是结合源码可以直接了解常量,坏处是缺失一定的语义化,不好理解,所以还是推荐结合源码注意源码的版本!
,如html
中引入的是React@19.1
,则需要注意对应源码的版本,因为在github
中,几乎每天都在主分支上进行改动,这将是后面的feature
,clone
的版本与实际使用的版本不一致# 推荐设置 depth 减少 commit 的储存占用和 clone 时间 git clone git@github.com:facebook/react.git -b v19.1.0 --depth=1
也可通过
performance
标签分析执行流程或当前流程执行的是 宏任务还是微任务,也可以观察渲染时机等(Chrome Plugin
会影响采集内容)
总结
preview 无内容?请刷新后再查看,若仍无法预览,请确保
services worker
可在你的浏览器中正常使用