Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 编码专题

  • 深入浅出 Vite

    • 00-目录大纲
    • 07-Vite预构建上手
      • 0.前言
      • 1.概念
        • 问题1:预构建解决了什么问题?
        • 问题2:构建结果存放位置?以及如何重新预构建?
      • 2.进阶
        • 问题3:vite 中的预构建可能存在什么问题?如何优化?
        • 问题4:预构建不想将某个包打进去,可不可以?
        • 问题5:遇到有些第三方包就是个坑,用不了怎么解决?
    • 09-Esbuild 插件开发实战
    • 10-Rollup 打包基本概念及使用
    • 11-Rollup打包机制及插件开发
    • 12-Vite插件开发实战
    • 13-Vite热更新
    • 16-Vite搭建 SSR 框架
  • 快速上手 API

  • 深入浅出Babel

  • 深入浅出 React

  • 百问掘金
  • 深入浅出 Vite
wangjiasheng
2023-04-12
目录

07-Vite预构建上手

# 0.前言

本节博客代码仓库 (opens new window),主要简述了预构建存在的问题,并且提前介绍了可能存在的一些坑点。

# 1.概念

# 问题1:预构建解决了什么问题?

主要解决了两个问题:

  1. 解决 请求瀑布流问题 。

    正所谓有舍必有得,享受现代浏览器按需加载的好处的同时,也有弊端,即庞大的第三方依赖所导致的网络请求。

  2. 将 cjs 包转化为 esm 格式。

    由于 vite 强制要求所有模块依赖必须为 esm 格式,但是 npm 库并不是所有的包都提供这种格式。比如说大名鼎鼎的 react 就只提供 cjs 格式。

    需要注意的是,上述的模块依赖不仅指的是直接依赖,间接依赖也必须为 esm 格式。下面会举例 @loadable/component 这个包,其本身具有 ESM 格式产物,但是其间接依赖 hoist-non-react-statics 及其子依赖 react-is 为 cjs 格式产物。

# 问题2:构建结果存放位置?以及如何重新预构建?

预构建的产物会被存放在 node_module/.vite/deps 目录下。

通过浏览器加载模块的时候就可以发现模块均从上述文件夹下引入的。

通过 network 面板发现,开发时可以利用到浏览器缓,第二次请求时会从缓存中读取。

重新触发构建的条件:

  1. 手动删除 node_modules/.vite 目录。

  2. 在 vite.config.js 中配置 optimizeDeps 中将 force 设置为 true

  3. 脚手架中使用 npx vite --force 加上 --force 参数。

    上述参数为合成指令:底层的话其实是先通过 npx vite optimize 对依赖进行预构建;再就是 vite dev 启动服务器。

# 2.进阶

# 问题3:vite 中的预构建可能存在什么问题?如何优化?

当 vite 使用异步模块导入时,会存在无法分析第三方依赖的问题。

由于 vite 是异步按需加载。因此不会提前对异步模块中的第三方模块进行依赖扫描。如果此时异步包中引入第三方包时会触发 二次依赖构建。

优化方案:手动将可能会扫描的包提前配置到 vite.config.js 中,避免开发阶段二次预构建的情况。

来看一个例子,在仓库代码中,App.tsx 中通过 importModule 动态引入了 zh_CN.ts 这个包

/* 预构建优化:动态导入 zh_CN.ts 模块,无法对其内部的模块进行分析 */
const importModule = (m: string) => import(`./locales/${m}.ts`);

function App() {
  importModule("zh_CN");
  return <div className="App"></div>
} 

// zh_CN
import objectAssign from "object-assign";
1
2
3
4
5
6
7
8
9
10

而这个动态模块中又依赖了一个第三方包:"object-assign",若直接启动项目,则会在控制台提示:

➜  Local:   http://127.0.0.1:5174/
➜  Network: use --host to expose
23:18:40 [vite] ✨ new dependencies optimized: object-assign
23:18:40 [vite] ✨ optimized dependencies changed. reloading
1
2
3
4

在 vite.config.ts 的 optimizeDeps.include 配置可手动提前预构建。

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  optimizeDeps: {
    include: [
      "object-assign",
    ],
  },
});
1
2
3
4
5
6
7
8
9

# 问题4:预构建不想将某个包打进去,可不可以?

可以,但是 不推荐 。

前面已经介绍预构建其中一个作用就是由 esbuild 提供模块规范转化功能(后续博客也会详细这部分机制,到时可以在项目中手动插件实现)。

以 @loadable/componets 举例,这个包本身支持 ESM 格式。

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  optimizeDeps: {
    exclude: ["@loadable/component"],
  },
});
1
2
3
4
5
6
7

如果直接 excludes 会报错,控制台打印如下:

Uncaught SyntaxError: The requested module '/@fs/Users/jiashengwang/Project/Learn-vite/node_modules/.pnpm/hoist-non-react-statics@3.3.2/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js?v=5d0e453c' does not provide an export named 'default' (at loadable.esm.js?v=5d0e453c:7:8)

提示 hoist-non-react-statics 这个包缺少一个 default 导出。什么情况下会存在这个报错,就是当包为cjs 格式时会报这个错误。因为 cjs 就没有 default。如果真实项目中 esm 要导入 cjs 包的话,需要在 ts 中开启 esModuleInterop 这个参数,嵌入一些辅助代码。

解决方案:就是将不满足要求的间接依赖抽出来。

export default defineConfig({
  plugins: [react()],
  optimizeDeps: {
    include: [
      "@loadable/component > hoist-non-react-statics",
      "@loadable/component > hoist-non-react-statics > react-is",
    ],
    exclude: ["@loadable/component"],
  },
});
1
2
3
4
5
6
7
8
9
10

新版 @loadable/component 还会多依赖个 react-is 库。

使用 exclude 属性的前提是需要自己提前分析出包依赖的格式规范,可以发现这个操作是非常困难的。


下面顺便分析下 exclude 前后模块依赖:

  • exclude前:

image-20230412233413691

@lodable_component 请求头为:http://127.0.0.1:5174/node_modules/.vite/deps/@loadable_component.js?v=9c67aeed

  • exclude 后:

image-20230412233316581

直接导入的是 loadable/component 提供的 esm 模块,并且请求地址为:http://127.0.0.1:5174/@fs/.../node_modules/.pnpm/@loadable+component@5.15.3/node_modules/@loadable/component/dist/loadable.esm.js?v=27562b28

是从 node_modules/.pnpm 仓库中导出的。

并且会多出两个请求是上面配置的 include 中的内容,因为这两个请求是从 .vite/deps 中取出给 loadasble.esm.js 引用的。

# 问题5:遇到有些第三方包就是个坑,用不了怎么解决?

举例:react-virtualized 包

由于无法保证第三方包的质量,有些包产物就是有问题的,对于此类问题就是具体问题具体分析,找到报错点。

首先说这个包的坑点在于下面这段 错语句,WindowScroller.js 并没有导出这个模块。

import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";
1

好在用不到这段代码,因此第一种方案是直接到 node_modules 中删除这端代码。

为了将 node_modules 的更改记录保留下来,可以使用到 patch 技术,新版 pnpm 已支持 patch 操作,否则需要安装 patch-package。

使用 pnpm 新增 patch 步骤如下:

  1. pnpm patch react-virtualized@9.22.3

    注意需要明确指定出版本号,此时在终端会显示如下:

    $ pnpm patch react-virtualized@9.22.3
    You can now edit the following folder: /private/var/folders/j6/c5rrdls52jg_9crpkxt01tym0000gn/T/37cc1d7e255fd6737dd810e542253644
    Once you're done with your changes, run "pnpm patch-commit /private/var/folders/j6/c5rrdls52jg_9crpkxt01tym0000gn/T/37cc1d7e255fd6737dd810e542253644"
    
    1
    2
    3
  2. 然后就可以直接在 node_modules 中修改了,修改结束后,根据上述提示输入:

    pnpm patch-commit /private/var/folders/j6/c5rrdls52jg_9crpkxt01tym0000gn/T/37cc1d7e255fd6737dd810e542253644
    
    1
  3. 修改结束后,会对应生成 patches 文件夹,在 package.json 新增:

    "pnpm": {
      "patchedDependencies": {
        "react-virtualized@9.22.3": "patches/react-virtualized@9.22.3.patch"
      }
    }
    
    1
    2
    3
    4
    5

第二种方案,则是使用 ESBuild 插件的方式完成删除操作

插件开发也很简单就两步:

  1. 找到对应的模块入口。
  2. 使用 replace 删除语句。
// vite.config.ts
const esbuildPatchPlugin = {
  name: "react-virtualized-patch",
  setup(build) {
    build.onLoad(
      {
        filter: /* 过滤插件 */
          /react-virtualized\/dist\/es\/WindowScroller\/utils\/onScroll.js$/,
      },
      async (args) => {
        const text = await fs.promises.readFile(args.path, "utf8");
        return {
          contents: text.replace(
            'import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";',
            ""
          ),
        };
      }
    );
  },
};

// 插件加入 Vite 预构建配置
{
  optimizeDeps: {
    esbuildOptions: {
      plugins: [esbuildPatchPlugin];
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
编辑 (opens new window)
上次更新: 2023/04/13, 9:04:00
00-目录大纲
09-Esbuild 插件开发实战

← 00-目录大纲 09-Esbuild 插件开发实战→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式