慢慢认识世界,慢慢更新自己。
     
    
    
     大家好,我是
     
      柒八九
     
     。
    
    
     由于,新公司的项目打包是用的
     
      Vite
     
     ,而之前的所参与的项目都是用
     
      Webpack
     
     作为打包工具,原来对
     
      Vite
     
     的了解,只是一个把玩工具,没有过多的深入了解。本着
     
      干一行,爱一行
     
     的职业态度。所以,就找了很多相关资料学习和研究。
    
    
     以下的内容,都是基于本人对
     
      Vite
     
     的个人见解。不一定对,随便看看
     
    
    
     你能所学到的知识点
    
    
     
      - 
       vite 是个啥?
       
        推荐阅读指数
       
       ⭐️⭐️⭐️
      
 
      - 
       vite 打包阶段
       
        推荐阅读指数
       
       ⭐️⭐️⭐️⭐️⭐️
      
 
      - 
       打包阶段的插件执行顺序
       
        推荐阅读指数
       
       ⭐️⭐️⭐️⭐️⭐️
      
 
      - 
       Vite+React的项目打包优化(简单版)
       
        推荐阅读指数
       
       ⭐️⭐️⭐️⭐️
      
 
     
    
    
     好了,天不早了,干点正事哇。
    
    
     
      
     
    
    
     这里再多絮叨几句,下面的大部分内容,都是从
     
      Vite
     
     打包阶段聊,针对
     
      HMR
     
     一些内容,没有涉及。
    
    
     vite 是个啥?
    
    
     
      Vite
     
     是一种现代化的
     
      前端构建工具
     
     ,它的目标是提供一种快速、简单和易于使用的开发体验。
     
      Vite
     
     使用了一些新的技术来实现快速的开发体验,这些技术包括
     
      ES模块
     
     、
     
      即时编译
     
     和
     
      热重载
     
     。
    
    
     - 
      
       ES模块
      
      是一种新的
      
       JavaScript模块格式
      
      ,它是浏览器原生支持的。
      
       ES模块
      
      提供了一种简单、可读、可扩展的方式来组织代码,同时还提供了
      
       静态分析
      
      和
      
       优化的机会
      
      。
      
       Vite
      
      利用
      
       ES模块
      
      的特性,将应用程序拆分成更小的代码块,使得应用程序的加载时间更快。
      
    
    
    
    
     - 
      
       - 
        即时编译是指在代码更改时,
        
         Vite
        
        会立即编译代码并将其发送到浏览器中。
        
       - 
        这意味着开发人员不需要等待编译过程完成,就可以看到更改后的效果。
       
 
       - 
        这大大缩短了开发周期,并提高了开发效率。
       
 
      
      
    
    
    
     - 
      
       - 
        热重载是指在开发过程中,如果更改了代码,应用程序将自动重新加载,而无需手动刷新页面。这使得开发人员能够更快地看到更改后的效果,并且不会丢失任何数据。
       
 
      
      
    
    
     想了解更多关于
     
      Vite
     
     的介绍,可以参考
     
      官网
     
    
    
     
    
    
     vite 打包阶段
    
    
     在讨论
     
      Vite
     
     时,通常关注的是其作为
     
      开发服务器
     
     以及它如何在
     
      开发过程中
     
     实现即时反馈。但是,在生产环境下,
     
      Vite
     
     也会大放异彩。
    
    
     下面,我们就一起来了解一下
     
      Vite
     
     是如何在
     
      生产环境
     
     下,对你的项目代码进行打包/优化处理的。本文假定你已经对
     
      Vite
     
     有一定的了解。如果第一次听说,你可以先移步到
     
      为什么选 Vite
     
     来了解相关的设计理念和解决哪些痛点。
    
    
     用上帝视角看Vite如何处理资源
    
    
     在一个
     
      Vite
     
     项目中,
     
      index.html
     
     在项目最外层而不是在
     
      public
     
     文件夹内。这是有意而为之的:在本地开发阶段
     
      Vite
     
     是一个资源服务器,而
     
      index.html
     
     是该
     
      Vite
     
     项目的入口文件。
    
    
     
      
       Vite
      
      将
      
       index.html
      
      视为源码和模块图的一部分。
     
    
    
    
     这个
     
      HTML
     
     文件用于
     
      引入网站资源信息
     
     。
    
    
     - 
      通过设置
      
       type="module"
      
      的
      
       script
      
      标签引入
      
       JS
      
      资源(外部资源和内联脚本)
      
     - 
      通过设置
      
       rel="stylesheet"
      
      的
      
       link
      
      引入外部样式
      
    
    
     
      
       <!DOCTYPE html>
    <script type="module" src="/src/main.ts"></script>
    <script type="module">
      // 内联 script
    </script>
    <link rel="stylesheet" href="/src/main.css">
    <style>
      internal-style: {}
    </style>
  </head>
    <div id="app"></div>
  </body>
</html>
复制代码
       
      
     
    
     
      Vite
     
     接收
     
      HTML
     
     文件,来查找每个加载的JS模块、内联脚本和CSS样式表。
    
    
     - 
      
       JS源代码
      
      通过
      
       Rollup
      
      处理,解析和转换内部依赖项,生成一个
      
       不经常更新
      
      的
      
       vendor.js
      
      (带有依赖项)和一个
      
       index.js
      
      (应用程序的其余部分)。
      
    
    
     - 
      
       - 
        这些源文件被转换成的带有
        
         hash
        
        值的文件,以实现
        
         强缓存
        
        。
        
      
      
    
    
     - 
      任何在JS中被引用的
      
       CSS
      
      文件都会被打包到
      
       index.css
      
      文件中
      
    
    
    
     
      script
     
     或
     
      import
     
     可以指向任何文件类型,只要
     
      Vite
     
     知道如何转译它们即可。在上面的情况下,
     
      main.ts
     
     文件需要
     
      在打包过程中转换为JS文件
     
     。在
     
      Vite
     
     中,使用基于
     
      Go
     
     的打包工具
     
      esbuild
     
     实现对应资源的转换。
    
    
    
     在资源被转换后,也会生成一个新的资源地址,用它们替换原来的资源地址。并且
     
      Vite
     
     执行
     
      import
     
     的
     
      静态分析
     
     ,为每个JS插入
     
      模块预加载标记
     
     (
     
      rel="modulepreload"
     
     ),使浏览器可以
     
      并行加载
     
     这些资源,从而避免加载瀑布效应。
    
    
     
      
       <!DOCTYPE html>
    <script type="module" src="/assets/index.d93758c6.js"></script>
    <link rel="modulepreload" href="/assets/vendor.a9c538d6.js">
    <link rel="stylesheet" href="/assets/index.3015a40c.css">
    <style>
      internal-style: {}
    </style>
  </head>
    <div id="app"></div>
  </body>
</html>
复制代码
       
      
     
    
     
      Vite
     
     针对
     
      JS
     
     和
     
      CSS
     
     资源支持代码分割。当遇到
     
      动态导入
     
     时,会生成一个异步JS块和一个CSS块。
    
    
     其他资源,如图像、视频、
     
      wasm
     
     ,可以
     
      使用相对路径导入
     
     。在生成其输出文件时,
     
      Vite
     
     还会对这些文件进行
     
      hash
     
     处理,并重写JS和CSS文件中的
     
      URL
     
     并指向它们。
    
    
     另一方面,
     
      public
     
     文件夹中的资源会
     
      按原样复制到输出根目录
     
     ,并允许用户通过
     
      绝对路径引用这些文件
     
     。
    
    
    
     每个JS和CSS块都需要在生产中进行压缩。自从
     
      Vite 2.6.0
     
     以来,
     
      Vite
     
     也使用
     
      esbuild
     
     为两种语言执行压缩任务,以加快构建过程。
    
    
     Rollup的二次封装
    
    
     
      Vite
     
     应用的
     
      打包过程
     
     ,是基于
     
      Rollup
     
     的二次封装 。
     
      Vite
     
     中可以直接使用现有的
     
      Rollup
     
     插件,实现很多
     
      开箱即用
     
     的功能。
    
    
     但是,需要注意一些与
     
      Rollup
     
     插件的兼容性问题,但
     
      大多数来自
      
       Rollup
      
      生态系统的插件都可以直接作为
      
       Vite
      
      插件工作
     
     。
    
    
     Vite内部打包流程
    
    
     下图展示了,
     
      Vite
     
     打包的大体流程。
     
      
     
     上面的流程主要分四个步骤
    
    
     
      - 
       收集打包配置信息
      
 
      - 
       确认打包后的资源路径
      
 
      - 
       使用
       
        rollup
       
       打包处理
       
      - 
       资源输出
      
 
     
    
    
     当你执行
     
      vite build
     
     时,
     
      Vite CLI
     
     被运行。运行命令是用
     
      cac
     
     实现的。该操作触发了
     
      build
     
     函数。
    
    
     
      
       await build({ root, base, mode, config, logLevel, clearScreen, buildOptions })
复制代码
       
      
     
    
     
      build
     
     又调用
     
      doBuild
     
     。实现逻辑如下。
    
    
     
      
       async function doBuild(inlineConfig: InlineConfig = {}): RollupOutput{
  // ①收集打包配置信息
  const config = await resolveConfig(inlineConfig, 'build', 'production')
  // ②确认打包后的资源路径
  const outDir = resolve(config.build.outDir)
  prepareOutDir(outDir, config)
  // ③打包处理
  const bundle = await rollup.rollup({
    input: resolve('index.html'),
    plugins: config.plugins
    
  // ④资源输出
  return await bundle.write({
    dir: outDir,
    format: 'es',
    exports: 'auto',
    sourcemap: config.build.sourcemap,
    entryFileNames: path.join(config.assetsDir, `[name].[hash].js`),
    chunkFileNames: path.join(config.assetsDir, `[name].[hash].js`),
    assetFileNames: path.join(config.assetsDir, `[name].[hash].[ext]`),
    manualChunks: createMoveToVendorChunkFn()
复制代码
       
      
     
    
     
      首先
     
     ,调用
     
      resolveConfig
     
     用于解析
     
      用户配置
     
     、
     
      项目配置文件
     
     和
     
      Vite默认值
     
     来生成一个具体的
     
      ResolvedConfig
     
     。
    
    
     
      
       const config = await resolveConfig(inlineConfig, 'build', 'production')
复制代码
       
      
     
    
     
      接下来
     
     ,确认好输出目录,并
     
      在生成资产之前清空它
     
     。这个函数还将
     
      publicDir
     
     的内容复制到项目
     
      dist文件夹
     
     。
    
    
     
      
       const outDir = resolve(config.build.outDir)
prepareOutDir(outDir, config)
复制代码
       
      
     
    
     
      然后
     
     ,基于
     
      index.html
     
     和
     
      config.plugins
     
     创建了
     
      rollup
     
     的
     
      bundle
     
     对象。
    
    
    
     
      
       const bundle = await rollup.rollup({
    input: resolve('index.html'),
    plugins: config.plugins
复制代码
       
      
     
    
     
      最后
     
     ,
     
      bundle.write
     
     被调用,用于生成输出目录中的资源信息。
    
    
     
      
       return await bundle.write({
    dir: outDir,
    format: 'es',
    exports: 'auto',
    sourcemap: config.build.sourcemap,
    entryFileNames: path.join(options.assetsDir, `[name].[hash].js`),
    chunkFileNames: path.join(options.assetsDir, `[name].[hash].js`),
    assetFileNames: path.join(options.assetsDir, `[name].[hash].[ext]`),
    manualChunks: createMoveToVendorChunkFn()
复制代码
       
      
     
    
     
      createMoveToVendorChunkFn
     
     函数定义了
     
      默认的分块策略
     
     ,定义了
     
      JS
     
     被打包后,何种资源被分配到
     
      index.js
     
     和
     
      vendor.js
     
     中。
    
    
     具体实现如下:
    
    
     
      
       function createMoveToVendorChunkFn() {
  return (id, { getModuleInfo }) => {
      id.includes('node_modules') &&
      !isCSSRequest(id) &&
    
      staticImportedByEntry(id, getModuleInfo)
      return 'vendor'
复制代码
       
      
     
    
     通过对
     
      Vite
     
     的打包做了一个简单的分析,我们可以得知:
    
    
     
      
       Vite
      
      的构建过程,就是以
      
       Rollup
      
      为基础,借助插件对资源的二次处理过程。
     
    
    
     
    
    
     常见的插件
    
    
     插件在
     
      开发阶段
     
     和
     
      打包阶段
     
     是通过
     
      resolvedPlugins
     
     进行解析。
     
      Vite
     
     在
     
      打包时
     
     通过
     
      resolveBuildPlugins
     
     插入
     
      额外的插件,以处理压缩和其他优化
     
     。
    
    
     有一些关键插件。
    
    
     - 
      
       vite:build-html
      
      和
      
       vite:html-inline-proxy-plugin
      
      用于处理
      
       HTML
      
      ,将
      
       JS
      
      和
      
       CSS
      
      替换为经
      
       Vite
      
      优化过的对应资源。
      
     - 
      
       vite:css
      
      和
      
       vite:css-post
      
      用于处理
      
       CSS
      
      和预处理器。
      
     - 
      
       vite:esbuild
      
      用于为每个模块转换
      
       TypeScript
      
      和
      
       JSX
      
      。
      
     - 
      
       vite:asset
      
      用于管理静态资源。
      
     - 
      
       vite:build-import-analysis
      
      用于
      
       预加载优化
      
      、支持全局导入和
      
       URL
      
      重写。
      
     - 
      
       vite:esbuild-transpile
      
      用于将
      
       chunks
      
      转换为合适的目标和压缩对应资源。
      
    
    
     还有一些插件是
     
      官方Rollup插件
     
    
    
     - 
      
       alias
      
      
     - 
      
       commonjs
      
      
     - 
      
       rollup-plugin-dynamic-import-variables
      
      
    
    
     
    
    
     打包阶段的插件执行顺序
    
    
     就
     
      webpac
     
     k来说,
     
      plugins
     
     的作用在于强化其构建过程中,所遇到的一些工程化的问题,比如代码压缩,资源压缩等,所以
     
      vite
     
     作为新时代的构建工具,理应当也具有插件系统来解决构建项目的整个生命周期中所遇到的工程化的问题,说白了,插件就是为了解决某一类型的问题而出现的一个或一种工具函数。比如
     
      lodash
     
     ,他被称之为一个库,也可以认作是一个插件。所以
     
      vite
     
     会在不同的生命周期中调用不同的插件去达成不同的目的。
    
    
     让我们深入了解每个插件的使用方式和职责权限。
    
    
     一个
     
      Vite
     
     插件可以额外指定一个
     
      enforce
     
     属性来调整它的应用顺序。
     
      enforce
     
     的值可以是
     
      pre
     
     或
     
      post
     
     。解析后的插件将按照以下顺序排列:
    
    
    
     - 
      
       Alias
      
      
     - 
      带有
      
       enforce: 'pre'
      
      的用户插件(
      
       前置插件
      
      )
      
     - 
      
       Vite
      
      核心插件
      
     - 
      没有
      
       enforce
      
      值的用户插件(
      
       常规插件
      
      )
      
     - 
      
       Vite
      
      构建用的插件
      
     - 
      带有
      
       enforce: 'post'
      
      的用户插件(
      
       后置插件
      
      )
      
     - 
      
       Vite
      
      后置构建插件(最小化,
      
       manifest
      
      ,报告)
      
    
    
     
    
    
     
      Vite
     
     构建运行的插件的执行顺序如下:
    
    
     - 
      
       alias
      
      
     - 
      带有
      
       enforce: 'pre'
      
      的用户插件(
      
       前置插件
      
      )
      
     - 
      
       vite:modulePreload
      
      
     - 
      
       vite:resolve
      
      
     - 
      
       vite:html-inline-proxy-plugin
      
      
     - 
      
       vite:css
      
      
     - 
      
       vite:esbuild
      
      
     - 
      
       vite:json
      
      
     - 
      
       vite:wasm
      
      
     - 
      
       vite:worker
      
      
     - 
      
       vite:asset
      
      
     - 
      没有
      
       enforce
      
      值的用户插件(
      
       常规插件
      
      )
      
     - 
      
       vite:define
      
      
     - 
      
       vite:css-post
      
      
     - 
      
       vite:build-html
      
      
     - 
      
       commonjs
      
      
     - 
      
       vite:data-uri
      
      
     - 
      
       rollup-plugin-dynamic-import-variables
      
      
     - 
      
       vite:asset-import-meta-url
      
      
     - 
      带有
      
       enforce: 'post'
      
      的用户插件(
      
       后置插件
      
      )
      
     - 
      
       vite:build-import-analysis
      
      
     - 
      
       vite:esbuild-transpile
      
      
     - 
      
       vite:terser
      
      
     - 
      
       vite:manifest
      
      
     - 
      
       vite:ssr-manifest
      
      
     - 
      
       vite:reporter
      
      
    
    
     
    
    
     1. alias
    
    
     该插件,用于在打包时
     
      定义别名
     
     ,与
     
      Webpack
     
     中的
     
      resolve.alias
     
     相同。
    
    
     - 
      可以是一个对象
     
 
    
    
    
     - 
      或一个
      
       { find, replacement, customResolver }
      
      的数组
      
    
    
     - 
      
       - 
        
         Array<{ find: string | RegExp, replacement: string, customResolver?: ResolverFunction | ResolverObject }>
        
        
      
      
    
    
     用以下方法配置
     
      Vite
     
    
    
    
     
      
       resolve: {
  alias: { 
      '@components': path.resolve(__dirname, 'src/components') 
复制代码
       
      
     
    
     可以让你从源码中的
     
      任何地方
     
     导入你想要导出的数据
    
    
     
      
       import Button from '@components/Button.tsx'
复制代码
       
      
     
    
     这个插件
     
      解析路径并将它们转译为真实的路径
     
    
    
     
      
       import Button from '../../components/Button.tsx'
复制代码
       
      
     
    
     
    
    
     2. 带有
     
      enforce: 'pre'
     
     的用户插件(
     
      前置插件
     
     )
    
    
     这些是带有
     
      enforce: 'pre'
     
     的插件。例如,
     
      @rollup/plugin-image
     
     配置了该属性,它就在
     
      Vite
     
     的内置插件之前运行。
    
    
     
      
       import image from "@rollup/plugin-image"
export default {
  plugins: [
      ...image(),
      enforce: 'pre',
复制代码
       
      
     
    
     3.
     
      vite:modulePreload
     
    
    
     - 
      类型:
      
       boolean
      
      |
      
       { polyfill?: boolean, resolveDependencies?: ResolveModulePreloadDependenciesFn }
      
      
     - 
      默认值:
      
       { polyfill: true }
      
      
    
    
     默认情况下,一个
     
      模块预加载
     
     
      polyfill
     
     会被自动注入。该
     
      polyfill
     
     会自动注入到每个
     
      index.html
     
     入口的的代理模块中。
    
    
    
     如果构建通过
     
      build.rollupOptions.input
     
     被配置为了使用
     
      非 HTML 入口的形式
     
     ,那么必须要在你的自定义入口中
     
      手动引入
     
     该
     
      polyfill
     
     :
    
    
     
      
       import 'vite/modulepreload-polyfill'
复制代码
       
      
     
    
     polyfill 实现
    
    
     
      
       <script>
  function processPreload () {
    const fetchOpts = {};
    if (script.integrity)
      fetchOpts.integrity = script.integrity;
    if (script.referrerpolicy)
      fetchOpts.referrerPolicy = script.referrerpolicy;
    if (script.crossorigin === 'use-credentials')
      fetchOpts.credentials = 'include';
    else if (script.crossorigin === 'anonymous')
      fetchOpts.credentials = 'omit';
      fetchOpts.credentials = 'same-origin';
   fetch(link.href, fetchOpts)
      .then(res => res.ok && res.arrayBuffer());
  const links = document.querySelectorAll('link[rel=modulepreload]');
  for (const link of links)
    processPreload(link);
    
</script>
复制代码
       
      
     
    
     
      该
      
       polyfill
      
      允许
      
       Vite
      
      
       预加载模块以避免加载瀑布
      
      ,支持非
      
       Chromium
      
      浏览器。
     
    
    
     4.
     
      vite:resolve
     
    
    
     它使用
     
      Node解析算法
     
     来定位
     
      node_modules
     
     中的
     
      第三方模块
     
     。它与官方的
     
      rollup
     
     插件不同,因为需要对
     
      Vite
     
     特定功能(
     
      SSR
     
     和
     
      devServer
     
     )进行特殊处理。
    
    
     Node 文件定位
    
    
     
      
     
    
    
     5.
     
      vite:html-inline-proxy-plugin
     
    
    
     该插件将
     
      入口HTML文件
     
     中的
     
      内联脚本作为单独的模块加载
     
     。这些脚本由
     
      vite:build-html
     
     插件将其从
     
      HTML
     
     中删除,并替换为一个
     
      type="module"
     
     的
     
      script
     
     。
    
    
     6.
     
      vite:css
     
    
    
     该插件与
     
      vite:css-post
     
     插件一起使用来实现
     
      Vite的CSS功能
     
     。支持预处理器(
     
      postCSS
     
     、
     
      sass
     
     、
     
      less
     
     ),包括解析导入的URL。
    
    
     7.
     
      vite:esbuild
     
    
    
     类型:
     
      ESBuildOptions | false
     
    
    
     
      ESBuildOptions
     
     继承自
     
      esbuild
     
     
      转换选项
     
     。
    
    
     最常见的用例是自定义 JSX:
    
    
     
      
       export default defineConfig({
  esbuild: {
    jsxFactory: 'h',
    jsxFragment: 'Fragment',
复制代码
       
      
     
    
     默认情况下,
     
      esbuild
     
     会被应用在
     
      ts
     
     、
     
      jsx
     
     、
     
      tsx
     
     文件。你可以通过
     
      esbuild.include
     
     和
     
      esbuild.exclude
     
     对要处理的文件类型进行配置。
    
    
    
     此外,你还可以通过
     
      esbuild.jsxInject
     
     来自动为
     
      每一个被
      
       esbuild
      
      转换的文件注入 JSX helper
     
     。
    
    
     
      
       export default defineConfig({
  esbuild: {
    jsxInject: `import React from 'react'`,
复制代码
       
      
     
    
     8. vite:json
    
    
     处理
     
      JSON
     
     文件的导入。
    
    
     
      
       // 导入整个对象
import json from './example.json'
// 导入一个根字段作为命名的出口, 便于tree shaking
import { field } from './example.json'
复制代码
       
      
     
    
     9.
     
      vite:wasm
     
    
    
     此插件允许用户直接导入预编译的
     
      .wasm
     
     文件。
    
    
     预编译的
     
      .wasm
     
     文件可以通过
     
      ?init
     
     来导入。默认导出一个初始化函数,返回值为所导出
     
      wasm
     
     实例对象的
     
      Promise
     
     :
    
    
     
      
       import init from './example.wasm?init'
init().then((instance) => {
  instance.exports.test()
复制代码
       
      
     
    
     在生产构建当中,体积小于
     
      assetInlineLimit
     
     的
     
      .wasm
     
     文件将会被内联为
     
      base64
     
     字符串。否则,它们将作为资源复制到
     
      dist
     
     目录中,并按需获取。
    
    
    
     10.
     
      'vite:worker'
     
    
    
     通过构造器导入
    
    
     一个
     
      Web Worker
     
     可以使用
     
      new Worker()
     
     和
     
      new SharedWorker()
     
     导入。与
     
      worker
     
     后缀相比,这种语法更接近于标准,是创建
     
      worker
     
     的 推荐 方式。
    
    
     
      
       const worker = new Worker(
          new URL('./worker.js', import.meta.url)
复制代码
       
      
     
    
     
      worker
     
     构造函数会接受可以用来创建 “模块”
     
      worker
     
     的选项:
    
    
     
      
       const worker = new Worker(
      new URL('./worker.js', import.meta.url), 
      { type: 'module',}
复制代码
       
      
     
    
     带有查询后缀的导入
    
    
     你可以在导入请求上添加
     
      ?worker
     
     或
     
      ?sharedworker
     
     查询参数来直接导入一个
     
      web worker
     
     脚本。默认导出会是一个自定义
     
      worker
     
     的构造函数:
    
    
     
      
       import MyWorker from './worker?worker'
const worker = new MyWorker()
复制代码
       
      
     
    
     默认情况下,
     
      worker
     
     脚本将在生产构建中编译成
     
      单独的 chunk
     
     。如果你想将
     
      worker
     
     内联为
     
      base64
     
     字符串,请添加
     
      inline
     
     查询参数:
    
    
     
      
       import MyWorker from './worker?worker&inline'
    
复制代码
       
      
     
    
     如果你想要以一个
     
      URL
     
     的形式读取该
     
      worker
     
     ,请添加
     
      url
     
     这个
     
      query
     
     :
    
    
     
      
       import MyWorker from './worker?worker&url'
复制代码
       
      
     
    
     11.
     
      'vite:asset'
     
    
    
     该插件用于资源的处理。
    
    
     将资源引入为 URL
    
    
     引入一个静态资源会返回解析后的公共路径:
    
    
     
      
       import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl
复制代码
       
      
     
    
     例如,
     
      imgUrl
     
     在开发时会是
     
      /img.png
     
     ,在生产构建后会是
     
      /assets/img.2d8efhg.png
     
     。
    
    
     - 
      常见的
      
       图像
      
      、
      
       媒体
      
      和
      
       字体文件
      
      类型被
      
       自动检测为资源
      
      。你
      
    
    
     - 
      
       - 
        可以使用
        
         assetsInclude
        
        选项扩展内部列表。(当从
        
         HTML
        
        引用它们或直接通过
        
         fetch
        
        或
        
         XHR
        
        请求它们时,它们将
        
         被插件转换管道排除在外
        
        。)
        
      
      
    
    
     - 
      引用的资源作为构建资源图的一部分包括在内,将生成散列文件名,并可以由插件进行处理以进行优化。
     
 
     - 
      较小的资源体积小于
      
       assetsInlineLimit
      
      选项值 则会被内联为
      
       base64 data URL
      
      。
      
    
    
     显式 URL 引入
    
    
     未被包含在
     
      内部列表
     
     或
     
      assetsInclude
     
     中的资源,可以使用
     
      ?url
     
     后缀显式导入为一个 URL。
    
    
     
      
       import workletURL from 'extra-scalloped-border/worklet.js?url'
CSS.paintWorklet.addModule(workletURL)
复制代码
       
      
     
    
     将资源引入为字符串
    
    
     资源可以使用
     
      ?raw
     
     后缀声明作为字符串引入。
    
    
    
     
      
       import shaderString from './shader.glsl?raw'
复制代码
       
      
     
    
     public 目录
    
    
     如果你有下列这些资源:
    
    
     - 
      不会被源码引用(例如 robots.txt)
     
 
     - 
      必须保持原有文件名(没有经过 hash)
     
 
     - 
      ...或者你压根不想引入该资源,只是想得到其 URL。
     
 
    
    
     那么你可以将该资源放在指定的
     
      public
     
     目录中,它应位于你的
     
      项目根目录
     
     。该目录中的资源
     
      在开发时能直接通过 / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下
     
     。
    
    
     目录默认是
     
      /public
     
     ,但可以通过
     
      publicDir
     
     选项 来配置。
    
    
     请注意:
    
    
     - 
      引入
      
       public
      
      中的资源
      
       永远应该使用根绝对路径
      
      
    
    
     - 
      
       - 
        举个例子,
        
         public/icon.png
        
        应该在源码中被引用为
        
         /icon.png
        
        。
        
      
      
    
    
     - 
      
       public
      
      中的资源不应该被
      
       JavaScript
      
      文件引用。
      
    
    
     12. 常规插件
    
    
     没有
     
      enforce
     
     值的用户插件
    
    
     13.
     
      'vite:define'
     
    
    
     定义全局常量替换方式。其中每项在开发环境下会被定义在全局,而在
     
      构建时被静态替换
     
     。
    
    
     类型:
     
      Record
     
    
    
     
      String
     
     值会以原始表达式形式使用,所以如果定义了一个字符串常量,它需要被显式地打引号。(例如使用
     
      JSON.stringify
     
     )
    
    
     
      
       define: {
  __APP_VERSION__: `JSON.stringify(${version})`
复制代码
       
      
     
    
     对于使用
     
      TypeScript
     
     的项目,还需要
     
      env.d.ts
     
     或
     
      vite-env.d.ts
     
     文件中添加
     
      类型声明
     
     ,以获得类型检查以及代码提示。
    
    
     
      
       // vite-env.d.ts
declare const __APP_VERSION__: string
复制代码
       
      
     
    
     14. vite:css-post
    
    
     这个插件用
     
      esbuild
     
     对CSS资源进行最小化。
    
    
     css资源的URL占位符被解析为其最终的构建路径。
    
    
     它还实现了
     
      CSS代码拆分
     
     。
     
      Vite
     
     会自动地将一个异步
     
      chunk
     
     模块中使用到的
     
      CSS
     
     代码抽取出来并为其生成一个单独的文件。这个
     
      CSS
     
     文件将在该异步
     
      chunk
     
     加载完成时
     
      自动通过
     
     一个  标签载入,该
     
      异步
      
       chunk
      
      会保证只在
      
       CSS
      
      加载完毕后再执行
     
     ,避免发生
     
      FOUC
     
     。
    
    
    
     
      {无样式内容的闪光|flash of unstyled content}(
      
       FOUC
      
      )是指在加载外部CSS样式表之前,
      
       网页以浏览器的默认样式短暂出现
      
      的情况,这是由于网络浏览器引擎在检索到所有信息之前渲染了该网页。
     
    
    
     下面是
     
      Vite
     
     中预加载插件的的简化版本。
    
    
     
      
       function createLink(dep) {
  // JS  -> <link rel="modulepreload" href="dep" />
  // CSS -> <link rel="stylesheet" href="dep" />
function preload(importModule, deps) {
  return Promise.all(
    deps.map(dep => {
      if (!alreadyLoaded(dep)) { 
        document.head.appendChild(createLink(dep))      
        if (isCss(dep)) {
          // 等CSS资源加载,避免出现FOUC 
          return new Promise((resolve, reject) => {
            link.addEventListener('load', resolve)
            link.addEventListener('error', reject)
  ).then(() => importModule())
复制代码
       
      
     
    
     这个插件将使用上面的辅助函数来转换动态导入。以下是
    
    
     
      
       import('./async.js')
复制代码
    
       
      
     
    
     将被转换为
    
    
     
      
       preload(
  () => import('/assets/async.js),
  ['/assets/async.css','/assets/async-dep.js']
复制代码
       
      
     
    
     如果
     
      build.cssCodeSplit
     
     的值为
     
      false
     
     ,这些块会被
     
      vite:build-html
     
     插件作为注入。
    
    
     15. vite:build-html
    
    
     这个插件会将
     
      HTML
     
     文件中的  标签编译成一个 JS 模块。
    
    
     - 
      它会在
      
       transform
      
      钩子中移除
      
       HTML
      
      中的
      
       script
      
      标签,生成一个
      
       JS
      
      文件,用于引入每个模块和资源文件。
      
     - 
      随后,在
      
       generateBundle
      
      钩子中插入 JS 文件,并使用
      
       vite:asset
      
      插件插入资源文件。
      
    
    
     16. commonjs
    
    
     它将
     
      CommonJS
     
     模块转换为
     
      ES6
     
     ,这样它们就可以被包含在
     
      Rollup
     
     的包中。
    
    
     - 
      在开发过程中,
      
       Vite
      
      使用
      
       esbuild
      
      进行资源的
      
       pre-bundling
      
      ,它负责将
      
       CommonJS
      
      转换为
      
       ES6
      
      
     - 
      但在构建过程中,没有
      
       pre-bundling
      
      的这步,所以需要
      
       commonjs
      
      插件。
      
    
    
     17. vite:data-uri
    
    
     它从
     
      data-URI
     
     导入模块。
    
    
     
      
       Data URL
      
      ,即前缀为
      
       data:
      
      协议的
      
       URL
      
      ,其允许内容创建者向文档中嵌入小文件。
     
     
      - 
       
        Data URL
       
       由四个部分组成:前缀(
       
        data:
       
       )、指示数据类型的
       
        MIME
       
       类型、如果非文本则为可选的
       
        base64
       
       标记、数据本身:
       
      - 
       
        data:[][;base64],
       
       
     
    
    
     我们可以从
     
      DataURL
     
     中导入模块。
    
    
     
      
       import batman from 'data:application/json;base64, eyAiYmF0bWFuIjogInRydWUiIH0=';
复制代码
    
       
      
     
    
     18. rollup/plugin-dynamic-import-vars
    
    
     用于支持动态导入中的变量。
    
    
     它是用
     
      build.dynamicImportVarsOptions
     
     来配置的。
    
    
     
      
       import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
export default {
  plugins: [
    dynamicImportVars({
      // options
复制代码
       
      
     
    
     允许用户编写动态解析的导入:
    
    
     
      
       function importLocale(locale) {
  return import(`./locales/${locale}.js`);
复制代码
       
      
     
    
     19. vite:asset-import-meta-url
    
    
     将
     
      new URL(path, import.meta.url)
     
     转换为内置
     
      URL
     
    
    
     
      import.meta.url
     
     是一个
     
      ESM
     
     的原生功能,会暴露当前模块的
     
      URL
     
     。将它与原生的
     
      URL 构造器
     
     组合使用,在一个
     
      JavaScript
     
     模块中,
     
      通过相对路径我们就能得到一个被完整解析的静态资源 URL
     
     :
    
    
     
      
       const imgUrl = new URL('./img.png', import.meta.url).href
document.getElementById('hero-img').src = imgUrl
复制代码
       
      
     
    
     这个模式同样还可以通过字符串模板支持动态 URL:
    
    
     
      
       function getImageUrl(name) {
  return new URL(`./dir/${name}.png`, import.meta.url).href
复制代码
    
       
      
     
    
     在生产构建时,
     
      Vite
     
     才会进行必要的转换保证 URL 在打包和资源哈希后仍指向正确的地址。然而,
     
      这个
      
       URL
      
      字符串必须是静态的
     
     ,这样才好分析。否则代码将被原样保留、因而在
     
      build.target
     
     不支持
     
      import.meta.url
     
     时会导致运行时错误。
    
    
     
      
       // Vite 不会转换这个
const imgUrl = new URL(imagePath, import.meta.url).href
复制代码
       
      
     
    
     20. 后置插件
    
    
     带有
     
      enforce: 'post'
     
     的用户插件。
    
    
     21. vite:build-import-analysis
    
    
     这个插件会对
     
      URL 导入
     
     进行词法分析、解析、重写和分析。
    
    
     动态导入会增加预加载指令。在客户端代码中注入一个
     
      辅助函数
     
     ,用于在异步块本身异步加载时
     
      并行预加载 CSS
     
     和直接导入的异步块。
    
    
     
      Glob 导入
     
     会被识别并使用
     
      transformImportGlob
     
     进行转译。例如:
    
    
     
      
       const modules = import.meta.glob('./dir/*.js')
复制代码
       
      
     
    
     被转化为
    
    
     
      
       const modules = {
  './dir/foo.js': () => import('./dir/foo.js'),
  './dir/bar.js': () => import('./dir/bar.js')
复制代码
       
      
     
    
     22. vite:esbuild-transpile
    
    
     这个插件对每个渲染的块进行转译,以支持配置的目标。
    
    
     如果
     
      build.minify
     
     是
     
      "esbuild"
     
     (
     
      Vite3+
     
     的版本是默认值),它也将使用
     
      esbuild
     
     来最小化代码,避免了对
     
      terser
     
     的需求。它比
     
      terser
     
     快 20-40 倍,压缩率只差
     
      1%-2%
     
     。
    
    
     23. vite:terser
    
    
     如果
     
      build.minify
     
     是
     
      'terser'
     
     ,这个插件就会被用来使用
     
      terser
     
     对每个渲染的块进行最小化。
    
    
     24. vite:manifest
    
    
     当设置为
     
      true
     
     ,构建后将会生成
     
      manifest.json
     
     文件,包含了没有被 hash 过的资源文件名和 hash 后版本的映射。可以为一些服务器框架渲染时提供正确的资源引入链接。当该值为一个字符串时,它将作为
     
      manifest
     
     文件的名字。
    
    
    
     25. vite:ssr-manifest
    
    
     当设置为
     
      true
     
     时,构建也将生成
     
      SSR
     
     的
     
      manifest
     
     文件,以确定生产中的样式链接与资产预加载指令。当该值为一个字符串时,它将作为
     
      manifest
     
     文件的名字。
    
    
     26. vite:reporter
    
    
     一个记录进度的插件,以及一份包含生成块和资源信息的报告。
    
    
     
      
       $ vite build
vite v3.1.0 building for production...
✓ 34 modules transformed.
dist/assets/favicon.17e50649.svg   1.49 KiB
dist/assets/logo.ecc203fb.svg      2.61 KiB
dist/index.html                    0.52 KiB
dist/assets/index.3015a40c.js      1.39 KiB / gzip:  0.73 KiB
dist/assets/index.d93758c6.css     0.77 KiB / gzip:  0.49 KiB
dist/assets/vendor.a9c538d6.js   129.47 KiB / gzip: 41.77 KiB
Done in 2.90s.
复制代码
       
      
     
    
     
    
    
     Vite+React的项目打包优化(简单版)
    
    
     可以从以下几点出发
    
    
     - 
      代码分割
     
 
     - 
      预取/预加载
     
 
     - 
      代码压缩
     
 
     - 
      图片压缩
     
 
     - 
      缓存策略
     
 
    
    
     代码分割
    
    
     使用代码分割可以将代码划分成较小的块,从而减少页面加载时间。可以使用
     
      Vite
     
     提供的
     
      import()函数
     
     或
     
      React
     
     的
     
      React.lazy()
     
     函数来实现代码分割。
    
    
     
      
       import React, { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function MyComponent() {
  return (
    
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
复制代码
       
      
     
    
     预取/预加载
    
    
     使用预取/预加载可以在用户访问页面之前预加载页面所需的资源,从而加快页面加载时间。可以使用Vite提供的
     
      标签来实现预取/预加载。
     
    
    
     
      
       <link rel="prefetch" href="./lazy-component.js" />
复制代码
       
      
     
    
     
      代码压缩
     
    
    
     
      使用代码压缩可以减小文件大小,从而加快页面加载时间。可以在
      
       Vite
      
      配置文件中启用代码压缩选项。
     
    
    
     
      
       // vite.config.ts
import { defineConfig } from 'vite';
import reactRefresh from '@vitejs/plugin-react-refresh';
import { terser } from 'rollup-plugin-terser';
export default defineConfig({
  plugins: [reactRefresh(), terser()],
复制代码
       
      
     
    
     
      图片压缩
     
    
    
     
      使用图片压缩可以减小图片大小,从而加快页面加载时间。可以使用
      
       Vite
      
      提供的
      
       imagemin
      
      插件来实现图片压缩。
     
    
    
     
      
       // vite.config.ts
import { defineConfig } from 'vite';
    
import reactRefresh from '@vitejs/plugin-react-refresh';
import { imagemin } from 'rollup-plugin-imagemin';
export default defineConfig({
  plugins: [
    reactRefresh(),
    imagemin({
      plugins: [
        // add imagemin plugins here
复制代码
       
      
     
    
     
      缓存策略
     
    
    
     
      使用缓存策略可以减少重复的网络请求,从而加快页面加载时间。可以在
      
       Vite
      
      配置文件中配置缓存策略选项。
     
    
    
     
      
       // vite.config.ts
import { defineConfig } from 'vite';
import reactRefresh from '@vitejs/plugin-react-refresh';
export default defineConfig({
  plugins: [reactRefresh()],
  build: {
    // set cache options here
    cacheDir: '.vite-cache',
复制代码
       
      
     
    
     
    
    
     
      后记
     
    
    
     
      
       分享是一种态度
      
      。
     
    
    
     
      参考资料:
     
    
    
     - 
      
       
        vite-plugin
       
      
      
     - 
      
       
        vite-github
       
      
      
     - 
      
       
        vite-build
       
      
      
    
    
     
      
       全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。