首发于我的博客:https://www.ahonn.me/post/54

在 React 项目中使用 Webpack HMR 时,通常会使用 react-hot-loader 来进行局部热更新。但使用 react-hot-loader 需要对原有代码进行修改,这对多入口的老项目非常的不友好。

为了使用上 HMR 这一“激动人心”的功能,需要在构建时在原有代码上自动添加 react-hot-loader 的相关代码。
因此我们需要创建一个 webpack loader 来在 babel 处理前将 react-hot-loader 相关代码添加到源
码中。

react-hot-loader 的用法

react-hot-loader 的用法非常简单,安装 react-hot-loader 后在 babel 配置中加上 react-hot-loader/babel 插件:

// .babelrc
{"plugins": ["react-hot-loader/babel"]
}

并且在根组件文件中添加如下代码即可:

// App.js
import 'react-hot-loader';
import { hot } from 'react-hot-loader/root';
const App = () => <div>Hello World!</div>;
export default hot(App);

通过 webpack loader 修改代码

这里我们需要创建一个 webpack loader 来做这件事情,在代码被 babel-loader 处理前进行插入与修改。

Webpack Loader 用于对模块源码的转换,所以这里我们在源码顶部添加 import 'react-hot-loader'import { hot } from 'react-hot-loader/root',并将默认导出修改为 export default hot(xxx)

如何对模块源码进行分析并修改呢?答案是 @babel/parser 与 @babel/traverse,通过 @babel/parser 解析为 AST 之后使用 @babel/traverse 遍历节点并进行修改。

将模块代码转换为 AST

const ast = parse(source, {sourceType: 'module',plugins: ['jsx'],
});

对于 React 我们需要启用 jsx 插件(.babelrc 中需要有 babel-preset-react)才能正确的解析代码。如果使用了其他特性的话也需要启用相对应的插件,具体有哪些插件可以查看@babel/parser · Babel。

解析成 AST 之后我们就可以进行一些操作了,首先我们先把导入 react-hot-loader 的代码都加到模块源码的顶部。

导入 react-hot-loader

ast.program.body.unshift(// insert `import 'react-hot-loader';`t.importDeclaration([], t.stringLiteral('react-hot-loader')),// insert `import { hot } from 'react-hot-loader/root';`t.importDeclaration([t.importSpecifier(t.identifier('hot'), t.identifier('hot'))],t.stringLiteral('react-hot-loader/root'))
);

这里的 t 指的是 @babel/types,这个包包含了一些用于判断与创建 AST 节点对象的工具方法。上述代码在模块代码的最顶端导入了 react-hot-loader 以及从 react-hot-loader/root导入需要调用的 hot 方法。

导入完之后我们需要对导出进行修改,将默认导出的组件包上一层 HOC。

修改模块导出

traverse(ast, {ExportDefaultDeclaration: (path) => {path.node.declaration = t.callExpression(t.identifier(identifier),[path.node.declaration]);},
});

这样就能将 export default xxx 修改为 export default hot(xxx)。但现实世界是残酷的,我们还可能遇到这样的代码:

export default const App = () => {};
export default class App {};
// 或者这样
export default () => {}
export default class {}

对于直接导出匿名函数或者类的,我们无能为力(其实也是有办法的,给它命一个名再导出,但是我不想这样干,使用暂时先不管了)。
但是对于导出非匿名的函数或者类的话,我们就可以进行修改了。把 export default 抽到底部,再把组件调用 hot() 之后进行导出。

稍微修改一下实现:

const insertNodes = [];
traverse(ast, {const { declaration } = path.node;// 如果是 export default const App = () => {} 或者 export default class App {}if (t.isClassDeclaration(declaration) || t.isFunctionDeclaration(declaration)) {if (t.isIdentifier(declaration.id)) {path.replaceWith(declaration);// 在源码尾部进行默认导出insertNodes.push(t.ExportDefaultDeclaration(t.callExpression(t.identifier(identifier),[declaration.id])));}return;}// 如果是 export default Appif (t.isIdentifier(declaration)) {path.node.declaration = t.callExpression(t.identifier(identifier),[path.node.declaration]);return;}
});
if (insertNodes.length > 0) {ast.program.body.push(...insertNodes);
}

好的,我们完成了解析代码为 AST 与修改 AST。是时候把它转回代码交给下一个 loader了。

AST 转换为代码

与 @babel/parser 类似,babel 也提供了将 AST 转换回代码的包: @babel/generator。不需要什么乱七八糟的魔法或者咒语,只需要 const { code } = generate(ast); 就可以获得崭新的 hot exportd 组件代码了。将它返回之后就可以愉快的使用 webpack HMR 了!!

还有一些坑

  • 导入了就必须调用,没有例外

如果对每个模块代码都进行以上操作的话,会发现页面上会提示 hot update was not successful。这是因为在调用 ReactDOM.render() 的文件中 import { hot } from 'react-hot-loader' 之后没有进行调用。所以我们需要添加一些判断,只在导出 React 组件的模块中进行代码修改。

  • hot exportd 的组件被继承无效

如果 A 组件继承 B 组件,而 B 组件被自动添加了 react-hot-loader 相关代码的话,A 组件将无法继承 B 组件的 state 与 methods。这个时候 B 组件已经不是 B 组件了,而是 hot() 这个 HOC 返回的 ExportedComponent。理论上被继承的组件也是不应该调用 hot()的,因此我们需要添加配置函数,用来判断是否需要修改模块代码。

基于以上的实现以及发现的坑点,我写了一个 react-hot-export-loader 用来给 React 组件自动加上 react-hot-loader,并且添加了一些判断或者配置来避免上面的坑。具体的使用方式这里就不在赘述了,请移步 README.md。

写在最后

其实按照一般套路,我们只需要在项目的入口处加上几句 react-hot-loader 代码就可以了。但是无奈的是有些时候总是不会按照套路出牌,例如 webpack 打包的逻辑是公共的,打包的是多入口,或者入口你根本就不知道是什么样的。所以才会出现这样一篇文章,这里权当做记录解决这一问题的方案,顺带输出一个 loader 给有缘人。

react打包后图片丢失_给 React 组件自动加上 react-hot-loader相关推荐

  1. react打包后图片丢失_使用 webpack 搭建 React 项目

    简评:相信很多开发者在入门 react 的时候都是使用 create-react-app 或 react-slingshot 这些脚手架来快速创建应用,当有特殊需求,需要修改 eject 出来的 we ...

  2. react打包后图片丢失_手写Webpack从0编译Vue/React项目

    当前前端开发,90%的项目都是Vue和React,然而70%的同学都基于脚手架创建项目,因为脚手架会包含项目基本框架.webpack配置.scss/sass/less解析.babel配置.DevSer ...

  3. react打包后图片丢失_宜信技术实践|指尖前端重构(React)技术调研分析

    一.为什么选择React React是当前前端应用最广泛的框架.三大SPA框架 Angular.React.Vue比较. Angular出现最早,但其在原理上并没有React创新的性能优化,且自身相对 ...

  4. react打包后图片丢失_手搭一个 React,Typescript,Koa,GraphQL 环境

    本文系原创,转载请附带作者信息:yhlben 项目地址:https://github.com/yhlben/cdfang-spider 前言 在实际的开发过程中,从零开始初始化一个项目往往很麻烦,所以 ...

  5. react打包后图片丢失_如何快速构建React组件库

    俗话说:"麻雀虽小,五脏俱全",搭建一个组件库,知之非难,行之不易,涉及到的技术方方面面,犹如海面风平浪静,实则暗礁险滩,处处惊险〜 目前团队内已经有较为成熟的 Vue 技术栈的 ...

  6. react打包后图片丢失_React系列四 - React脚手架

    一. 认识脚手架 1.1. 前端工程的复杂化 如果我们只是开发几个小的demo程序,那么永远不需要考虑一些复杂的问题: 比如目录结构如何组织划分: 比如如何管理文件之间的相互依赖: 比如如何管理第三方 ...

  7. react打包后图片丢失_React中型项目的优化实践

    本文可能涉及的内容-- 项目介绍 整个项目大概有60+个页面,用到的组件大概150+,package里面的依赖大概有70+个,应该勉强算得上是一个中型的React的项目了. 下面给大家看看我们现在bu ...

  8. vite+vue3打包后图片404问题:已解决

    let content = [{title:"每周菜单",cont:[{url:"/procurementchildren/meke_menu",name:&q ...

  9. vue-webpack打包后图片找不到

    一.问题描述 项目在使用webpack打包后,很常见的一个问题就是dev环境可见的图片在打包之后找不到文件. 二.异常原因 1.图片未被打包 当使用的图片url是动态拼接的,可能会出现webpack没 ...

  10. react 打包体积过大_解决 webpack 打包文件体积过大

    webpack 把我们所有的文件都打包成一个 JS 文件,这样即使你是小项目,打包后的文件也会非常大.下面就来讲下如何从多个方面进行优化. 去除不必要的插件 刚开始用 webpack 的时候,开发环境 ...

最新文章

  1. Android Fragment 你应该知道的一切
  2. SpringMVC获取请求参数-POJO类型参数
  3. 用sqoop将mysql的数据导入到hive表中,原理分析
  4. delphi7 安装delphi 5 delphi 6控件
  5. 仿淘宝分页按钮效果简单美观易使用的JS分页控件
  6. DM8168 DMM(2)
  7. Linux 命令(128)—— useradd 命令
  8. python运维方法_Python运维开发基础09-函数基础【转】
  9. SpringBoot整合Sharding-JDBC实现水平分表
  10. 全球130多个国家的货币代码对照表
  11. NJUPT【电工电子基础实验】
  12. 二级c语言考试怎么调试程序,计算机二级C语言上机考试操作步骤及流程和注意事项...
  13. sim3在orbslam2与gis中的应用
  14. Android 实现图片闪烁效果
  15. 数据库 -- 基础操作(二)
  16. Wangle中EchoClient分析
  17. 初入编程 - HTML + CSS
  18. 计算机打字不盲打可以吗,能盲打了,为什么打字速度还不快?现在才40左右,怎样才能达到60以上呢?...
  19. 包含空格的项目的文件/路径部分需要用括号括起来
  20. 画布渐变以及svg图形

热门文章

  1. 看不见的“网” ,一文读懂阿里云基础设施网络
  2. 凌云一周看点 | 混合云多Region架构;云上用户定制化网络;边缘云全站加速;什么是操作系统的云原生...
  3. 2020阿里云线上峰会预告 | 凌云时刻
  4. OTA时代来了!由新一代私有云揭开序幕
  5. linux如何实现c语言程序,在Linux下如何利用C语言来实现一个Sniffer
  6. oracle数据库导dump,oracle数据导入,导出dump文件
  7. 【故障诊断分析】基于matlab GUI小波包能量可视化设计【含Matlab源码 1788期】
  8. 【优化算法】杂草优化算法(IWO)【含Matlab源码 1076期】
  9. python中的字典操作_python中的字典以及相关操作
  10. java后台处理跨域问题_用cros解决前后端分离的跨域问题