惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
博客园 - 叶小钗
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
M
MIT News - Artificial intelligence
美团技术团队
aimingoo的专栏
aimingoo的专栏
博客园_首页
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Microsoft Security Blog
Microsoft Security Blog
Last Week in AI
Last Week in AI
The GitHub Blog
The GitHub Blog
小众软件
小众软件
T
Tailwind CSS Blog
Martin Fowler
Martin Fowler
B
Blog RSS Feed
月光博客
月光博客
量子位
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Hugging Face - Blog
Hugging Face - Blog
IT之家
IT之家
Y
Y Combinator Blog
B
Blog
MyScale Blog
MyScale Blog

kshao-blog-前端知识记录

win or mac clash 无 TUN 让 Antigravity、Chrome 强制 proxy(解决 Antigravity 无法加载选择 model、自动更新无法登录、跳转) 【大杂烩】在 pnpm 中直接修改 node_modules(.pnpm) 中的依赖项,项目中持久化 - pnpm 中的依赖处理、幽灵依赖、寻址规则等 一键在本地批量检测并升级更新 package.json 中的模块依赖,ncu(npm-check-updates)在 npm、pnpm 或 workspace 项目中的使用教程 解决 Mac Docker Desktop 中启动出现的问题合集 通过阿里云、腾讯云无服务器搭建自定义的企业域名邮箱,实现在 QQ邮箱 收发等功能(附腾讯 SMTP 和 IMAP) 解决使用代理(clash 等)进行 SSH 连接(如 Github ssh key clone/push)出现 kex_exchange_identification 错误 静态文件资源 cdnjs, jsdelivr 抖音字节国内快速 CDN 镜像推荐【2025】- 仍在使用 bootcdn 和 staticfile CDN 请注意验证资源的完整性(SRI) pnpm monorepo 中管理依赖的最佳实践,与 Catalogs(目录)协议的使用(monorepo 中统一版本管理) Web 安全中的 Secure Contexts(安全上下文)- 解决在本地中使用 clipboard 或 Crypto 等 API 限制或关闭上下文限制 使用 serve 配合 openssl 或 mkcert 创建本地自签名可信任的证书 - 创建本地 TLS\SSL https 协议服务 利用 Github Actions 和 Acme 自动申请、更新和部署至阿里云、腾讯云 CDN Lets Encrypt SSl\TLS ECC RSA 双证书 【CSS】解决在 flex 容器中使用 align-content 或 justify-content 属性 center 居中时的溢出滚动和截断问题 - 理解 safe 关键字 在线工具 - 一键获取下载抖音无水印视频、抖音去水印解析工具、下载抖音无水印高清图集【2025 最新】 【React Router】v6 data router 在非组件(或工具方法)中如何优雅的跳转路由 【React】为什么路由跳转时页面滚动高度不会被重置(保留上个页面高度)?理解 history scrollRestoration 的场景与使用,以及如何使用 React Router 重置和跳转前保留滚动高度 【React】在本地 Html 中快速 debug(调试)React 源码 【React】结合源码和 EventLoop 分析 - 为什么 useLayoutEffect 会阻止 DOM 重绘(而 useEffect 闪烁)?为什么其内部 useState 会“同步”执行? React Compiler - 解放在函数中编程时的性能焦虑(React Conf 2024)附 Next 在线演示 在 github actions 中获取时间,并转换为中国标准时间(中国时区) 【npm】npm ci - npm clean install,在 CI、CD 中保持构建的一致性和可重复性 eslint 9.x 升级或使用指南,eslint.config.js 配置,包含 react、typescript、prettier 等常用配置升级迁移 使用 Spicetify 自定义 Spotify - 歌词翻译、全屏展示、主题替换 在 node 中快速代理请求(Proxy),解决跨域或请求转发问题 - http-proxy-middleware 修改请求体和返回 在 Hexo 中使用 AI(Gemini、deepseek、Azure)生成文章摘要,支持自定义模板。hexo-ai-summaries 插件文档(默认适配主题 Butterfly) 解决在 webstorm 或 idea 等 jetbrains 工具中遇到 Git 无法 force push,或 force push 灰色禁用无法点击(protected branches) hexo-seo-submit,Hexo 博客 SEO 优化插件 - 每日定时自动或手动提交链接至百度、Bing、Google,支持 Github Actions 和 Coding Jenkins 等CI(Hexo 插件编写) 一文吃透 pnpm 如何使用 workspace 构建 monorepo,与 npm、yarn 的用法对比(pnpm 9.x 内部安装依赖问题 link-workspace-packages) 【Node】Corepack - 解决 pnpm 或 yarn 的多版本管理、解决本地版本与 packageManager 中的版本一致性问题 解决 npm、cnpm 或 pnpm install 遇到 certificate has expired (证书已过期) npm、yarn、pnpm 设置最新国内镜像源(附官方镜像源和最新阿里源),以及 nrm 的使用教程【2025】
在 html 中直接使用 Esm、Jsx 脚本快速调试和使用 React@19 ...
kshao · 2025-05-08 · via kshao-blog-前端知识记录

大部分时间,启动一个 ReactVue 项目,都离不开脚手架,哪怕只是写个简单的 Hello Worlddemo,也需要等待依赖安装,启动等流程。如果需要对源码进行调试,只能依靠 debug,若是在 Html 中,模块之间的关系是纯粹的,debug 只会在源码和代码中流转,并不会存在其他的模块关系,但通过构建工具的编译,将使代码关系变得不再纯粹,增加调试复杂度。且不同构建工具对依赖的处理不同,单纯的替换 Response 调试也会相当的繁琐。

可能正常的调试流程为:本地维护一个 HMRvueReact 源码,再 link 对应的构建结果,每次修改源码还需要等待热更新的重新构建和对应 link 项目的重新构建,才能完成调试。

当然说这么多可能的缺点,不是说构建工具存在的无意义,而是在 Html 中,你也可以使用 JsxTsxEsm 进行 demo 编写,简单场景中,可以获得和脚手架一样的编码体验,其优势旨在于代码与源码的关系更加纯粹,调试更加方便,无其他心智负担。

React@19 中移除了 umd 的相关构建,若你的项目中使用了 externals 类似功能,可参考 react-debug-in-htmlumd-react 继续使用 umd 构建(不推荐生产使用

当然 webpackexternals 也支持原生 esm 模块的外部引入,看需求抉择

babel 转义 Jsx

由于没有 UMD 产物,所以使用原生支持的 Esm 导入,通过 importmap 定义模块路径,可简单理解为 alias,后续无论是什么地方,直接导入 react 即可。

importmap 缺点就是, 无论配置的是本地的 相对路径,还是网络的 绝对路径,在 IDE 中,无法直接定位方法对应的代码位置,这将在调试中造成一定困扰。当然你也可以直接使用本地的相对路径,可配合 IDE 进行快速调试

通过 babel/standalonescript type 为 text/babel 中的相关 JSX 转义为 js。如下,类似在 loader 中做的工作

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;

需要注意的是,babel/standalone 虽然支持 module,但只会对 script type="module" 中的脚本进行转换,引入的 脚本 并不会处理

后续介绍使用 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 只提供了 cjs 产物,但还是可以通过万能工具 babel 将其转为 esm。通过 babeljs.io 进行在线转换,和使用 babel loader 差不多,链接中已经包含可用的相关预设plugin,直接将 cjs 代码粘贴即可

下面是需要转换的 React@19 的相关源码地址(19 在构建时将 ClientScheduleDom 中拆分出来,所以这两个也需要转换)

建议使用的时候去除 版本号 再转换,可以获取最新版

注意事项:

1. 上述 cjs 源码会携带 "production" !== process.env.NODE_ENV 在粘贴时记得删除(如图 1)
2. 在线工具的输出结果无法直接选中复制,可将其 wrapcontent-editable 设置为 true 即可(如图 2)
3. cjs 的产物本身由于编译压缩等原因,与源码可能会有一些差异,如常量直接会使用对应值展示、由于压缩导致的函数内联机制(如纯函数内的代码会被直接提取出来)、dev 判断等,但方法名和参数名基本是一致的,你可以通过本地源码快速定位方法,再到源码中查看

[{"url":"https://static.ksh7.com/post/framework-source-html/react-source-extra.webp?imageMogr2/thumbnail/!50p","dataset":{"originPic":"https://static.ksh7.com/post/framework-source-html/react-source-extra.webp","thumbnail":""}},{"url":"https://static.ksh7.com/post/framework-source-html/react-souce-copy.webp?imageMogr2/thumbnail/!50p","dataset":{"originPic":"https://static.ksh7.com/post/framework-source-html/react-souce-copy.webp","thumbnail":""}}]

通过 serviceWorker 转义 jsx 脚本文件

使用 serviceWorker 拦截并修改资源后缀为 jsx 的脚本文件,通过 babel 将其转为 js

当然也可以将 tsx 转为 jsdemo 项目使用 ts 不是在找罪受嘛?还是 jsx 效率点~ 若需要转换 tsvue 文件,需要注意 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 不生效?

  1. 注意 scope 的配置和 serviceWorker 文件的路径,推荐将注册脚本放在 root 下,这样可以灵活进行 scope 的配置。(scope 可以理解为 serviceWorker 在哪个域生效)

  2. serviceWorker 注册成功了,但未生效?可以尝试刷新,或在控制面板中 service workers 手动刷新 serviceWorker或重新注册

Why is my service worker failing to register?

vue 在 Html 中使用

vue 在官网就提供了相关示例,这里就不过多废话了。通过 CDN 使用 Vue

本地调试下载 vue.esm-browser.js 即可,其包含了 vue-loader@vitejs/plugin-vue 在编译时做的一些优化(@vue/compiler-dom),如静态节点提升、事件缓存等,原本这些你可能需要查看编译后的文件,或在 template-explorer.vuejs 中查看,现可在 vue.esm-browser.js 中进行”编译时”的调试

<!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 中,几乎每天都在主分支上进行改动,这将是后面的 featureclone 的版本与实际使用的版本不一致

    # 推荐设置 depth 减少 commit 的储存占用和 clone 时间
    git clone git@github.com:facebook/react.git -b v19.1.0 --depth=1
  • 也可通过 performance 标签分析执行流程或当前流程执行的是 宏任务还是微任务,也可以观察渲染时机等(Chrome Plugin 会影响采集内容)

总结

废话太多了?直接在 codesandbox 中查看吧

preview 无内容?请刷新后再查看,若仍无法预览,请确保 services worker 可在你的浏览器中正常使用