美文网首页
Vite从0到1

Vite从0到1

作者: 麦西的西 | 来源:发表于2022-11-19 21:35 被阅读0次

    vite 初识

    创建一个 vite 项目,只需要:

    yarn create vite
    

    然后按照提示进行操作即可(这里选的是 react + js)。生成的项目目录如下:

    ├─ public
    │ └─ vite.svg
    ├─ src
    │ ├─ assets
    │ │ └─ react.svg
    │ ├─ App.css
    │ ├─ App.jsx
    │ ├─ index.css
    │ └─ main.jsx
    ├─ index.html
    ├─ package.json
    └─ vite.config.js
    

    这个命令先是安装一个全局依赖 create-vite,然后运行 create-vite命令。等同于:

    cnpm install create-vite -g && create-vite
    

    create-vite 就是一个 vite 的脚手架,会根据你的需要选择不同模板来克隆项目。
    需要注意的是, vite 对 node 版本有要求, 要求 node 版本是 ^14.18.0 || >=16.0.0

    可以看到根目录有个 index.html。这个是 vite 项目的入口文件。
    Vite 解析 <script type="module" src="..."> ,这个标签指向我们的 JavaScript 源码。

    为什么选择 vite

    Vite 是一种新型前端构建工具,能够 显著提升 前端开发体验
    为什么能够显著提升开发体验呢?首先我们了解结构建工具做了哪些工作。

    1. 传统构建工具所做的工作(自动化)

    • 模块化开发支持:支持直接从 node_modules 引入代码,支持多重模块化
    • 处理代码的兼容性:比如 ES6 的代码降级,jsx转换为js, less/sass 转换为 css(不是构建工具做的,构建工具将这些工具集成进来自动化处理)
    • 提高项目性能:压缩代码,代码分割
    • 提高开发体验:提供开发服务器,能够解决服务跨域的问题(本地代理)。监听文件的变化,文件变化后能够自动调用相应的工具重新处理、打包,在浏览器重新运行(热更新)

    这样,我们就不用管理代码如何处理,如何在浏览器运行,只需要关注开发工作即可。

    目前的构建工具,通常是这个流程:从入口构建依赖图 => 对所有模块打包 => 浏览器运行。如下图:


    当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。基于 JavaScript 开发的工具就会开始遇到性能瓶颈:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。
    Vite 旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,且越来越多 JavaScript 工具使用编译型语言编写。

    2.vite 的设计理念

    2.1 开发服务器

    Vite 通过在一开始将应用中的模块区分为 依赖源码 两类,改进了开发服务器启动时间。

    • 依赖 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。Vite 将会使用 esbuild 预构建依赖。esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。
    • 源码 通常包含一些并非直接是 JavaScript 的文件,需要转换(例如 JSX,CSS 或者 Vue/Svelte 组件),时常会被编辑。同时,并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。

    Vite 以 原生 ESM 方式提供源码。这实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理。如下图:


    2.2 热更新(HMR)

    传统的 HMR:当我们对代码做修改并保存后,webpack 会对修改的代码块以及该模块的依赖重新编译打包,并将新的模块发送至浏览器端,浏览器用新的模块代替旧的模块,从而实现了在不刷新浏览器的前提下更新页面。相比起直接刷新页面的方案,HMR 的优点是可以保存应用的状态。当然,随着项目体积的增长,热更新的速度也会随之下降。
    在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活(大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。
    Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。

    2.3 为什么生产环境仍需打包

    尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。

    依赖预构建

    1. 为什么需要依赖预构建

    我们看这样一个例子。创建下面的目录,并在 index.html 中以 module 的形式引入 main.js

    prebuild-demo    
    ├─ index.html    
    ├─ main.js       
    └─ package.json  
    
    // index.html
    // ...
    <script type="module" src="./main.js"></script>
    // ...
    

    接着,我们cnpm install lodash -S 安装 lodash,并在 main.js 中写入:

    // main.js
    import { throttle } from 'lodash';
    console.log(throttle);
    

    然后,在浏览器打开 index.html,会报错:


    这是因为 ESModule 中,相对引用要采用/, ./, 或 ../开头。因此,不能够通过依赖的方式直接引入。
    依赖预构建,能够重写这部分模块引入,从而解决问题:
    我们安装 vite,并用 vite 启动项目:
    cnpm install vite && npx vite
    

    打开控制台,throttle 已经能打印出来了。


    再看 main.js ,模块引入变成了具体的地址:

    除了依赖补全,依赖预构建还做了这两个工作:

    • CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。
    • 提高性能: Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。比如官网的例子,lodash-es。

    2. 缓存

    2.1 文件系统缓存

    Vite 会将预构建的依赖缓存到 node_modules/.vite。它根据几个源来决定是否需要重新运行预构建步骤:

    • package.json中的 dependencies 列表
    • 包管理器的 lockfile,例如 package-lock.json, yarn.lock,或者 pnpm-lock.yaml
    • 可能在 vite.config.js 相关字段中配置过的
      只有在上述其中一项发生更改时,才需要重新运行预构建。
      如果出于某些原因,你想要强制 Vite 重新构建依赖,你可以用 --force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。
    2.2 浏览器缓存

    解析后的依赖请求会以 HTTP 头 max-age=31536000,immutable 强缓存,以提高在开发时的页面重载性能。一旦被缓存,这些请求将永远不会再到达开发服务器。如果安装了不同的版本(这反映在包管理器的 lockfile 中),则附加的版本 query 会自动使它们失效。如果你想通过本地编辑来调试依赖项,你可以:

    1. 通过浏览器调试工具的 Network 选项卡暂时禁用缓存;
    2. 重启 Vite dev server,并添加--force 命令以重新构建依赖;
    3. 重新载入页面。

    常用功能与配置

    1. CSS Modules

    任何以 .module.css 为后缀名的 CSS 文件都被认为是一个 CSS modules 文件。导入这样的文件会返回一个相应的模块对象。
    也就是说我们直接可以模块引入:

    import styles from "./index.module.less";```
    

    CSS Modules 比较常用的配置:

    // ...
    css: {
        modules: {
          generateScopedName: "[path][name]__[local]__[hash:5]",
          localsConvention: "camelCaseOnly"
        }
      },
      // ...
    

    其中,

    • generateScopedName: 生成的类名格式


    • localsConvention:修改生成对象的 key 的展示形式 (驼峰还是中划线)


    2. CSS 预处理器

    vite 提供了对 sass/less/stylus 的内置支持。
    我们只需要安装相应的预处理器依赖即可直接使用。

    cnpm install less -D
    

    比较常用的配置:

    css: {
        // ...
        preprocessorOptions: {
          less: {
            additionalData: `@import '@/assets/styles/common.less';`, // 全局注入样式文件
            modifyVars: {
              'primary-color': '#409eff' // 全局样式变量
            },
            javascriptEnabled: true
          }
        },
        
        devSourcemap: true // 默认false,设为true开发阶段启动用sourcemap
      },
    

    3. PostCSS

    vite 提供了对 PostCSS 的内置支持。
    我们可以在 vite.config.js 中配置 PostCSS ,也可以直接新建 postcss.config.js 文件配置 PostCSS 。
    这里我们使用 postcss-preset-env 试一下,postcss-preset-env 包含一系列 PostCSS 的插件。比如浏览器前缀自动添加:

    cnpm install postcss-preset-env -D
    

    根目录新增 postcss.config.js ,并配置如下:

    import postcssPresetEnv from 'postcss-preset-env';
    
    export default {
      plugins: [postcssPresetEnv()]
    };
    

    重启开发服务器,就能够看到浏览器前缀自动添加了:


    4. 静态资源处理

    4.1 资源引入

    vite 中,引入一个静态资源会返回解析后的公共路径:

    import exampleImg from "/src/assets/example.png";
    

    exampleImg 在开发时会是 /src/assets/example.png,生产环境会是 /assets/example.2d8efhg.png。类似于 webpack4 中的 file-loader.

    • 常见的图像、媒体和字体文件类型被自动检测为资源。你可以使用 assetsInclude 选项 扩展内部列表。
    export default defineConfig({
      assetsInclude: ['**/*.gltf'] // 会把.gltf文件当做资源文件处理
    })
    
    • 较小的资源体积小于 assetsInlineLimit 选项值 则会被内联为 base64 data URL。
     // ...
     build: {
        assetsInlineLimit: 8 * 1024, // 小于 8 KB的资源会被内联成base64格式
        // ...
      },
    

    也可以通过 ?raw 后缀声明作为字符串引入。类似于 webpack4 中的 raw-loader

    import helloString from "./test.txt?raw";
    console.log("exampleImg", helloString); // hello, vite
    
    4.2 public 目录

    如果你有下列这些资源:

    • 不会被源码引用(例如 robots.txt)
    • 必须保持原有文件名(没有经过 hash)
    • ...或者你压根不想引入该资源,只是想得到其 URL。
      那么你可以将该资源放在指定的 public 目录中,它应位于你的项目根目录。该目录中的资源在开发时能直接通过 / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下。
      目录默认是 /public,但可以通过 publicDir 选项 来配置。
      请注意:
    • 引入 public 中的资源永远应该使用根绝对路径 —— 举个例子,public/icon.png 应该在源码中被引用为 /icon.png
    • public 中的资源不应该被 JavaScript 文件引用。

    5. alias 与 extensions

    通过下面的代码配置别名和扩展名缩写:

    import { resolve } from "path";
    // ...
    resolve: {
        alias: {
          "@": resolve(__dirname, "./src")
        },
        extensions: [".jsx", ".js", ".tsx", ".ts", ".json"]
    },
    

    6. 本地开发服务器

    server: {
        open: true, // 自动打开浏览器
        host: "0.0.0.0",
        port: 9999,
        strictPort: true, // 设置为false,端口被占用会直接退出
        proxy: {
          "/webapi": {
            target: "http://10.2.2.98:8090",
            changeOrigin: true,
            rewrite: (path) => path.replace(/^\/webapi/, "")
          }
        }
      }
    

    环境变量

    1. 内建环境变量

    Vite 在一个特殊的 import.meta.env 对象上暴露环境变量。这里有一些在所有情况下都可以使用的内建变量:

    • import.meta.env.MODE: {string} 应用运行的模式。
    • import.meta.env.BASE_URL: {string} 部署应用时的基本 URL。他由 base 配置项决定。
    • import.meta.env.PROD: {boolean} 应用是否运行在生产环境。
    • import.meta.env.DEV: {boolean} 应用是否运行在开发环境 (永远与 import.meta.env.PROD 相反)。
    • import.meta.env.SSR: {boolean} 应用是否运行在 server 上。

    2. .env 文件

    我们可以在 .env 文件中编写自己需要的环境变量。

    .env                # 所有情况下都会加载
    .env.local          # 所有情况下都会加载,但会被 git 忽略
    .env.[mode]         # 只在指定模式下加载
    .env.[mode].local   # 只在指定模式下加载,但会被 git 忽略
    

    自己编写的环境变量必须以 VITE_ 为前缀,比如:

    VITE_SOME_KEY=123
    DB_PASSWORD=foobar // 不合法
    
    console.log(import.meta.env.VITE_SOME_KEY) // 123
    console.log(import.meta.env.DB_PASSWORD) // undefined
    

    3. 模式

    默认情况下,开发服务器 (dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production (生产) 模式。
    如果我们需要额外的模式,则可以使用 --mode 覆盖默认的模式:

    vite build --mode staging
    

    同时我们还需要一个 .env.staging 文件来定义环境变量:

    # .env.staging
    NODE_ENV=production
    VITE_HTTP=http://10.2.2.245:8890
    

    生产构建优化

    1. 分包策略

    生产环境打包的时候,我们可能会需要分包。比如:把依赖单独打一个包,这样就可以避免依赖被重复打包。

        build: {
          assetsInlineLimit: 8 * 1024, // 小于8KB的资源base64内联
          rollupOptions: {
            output: {
              manualChunks(id) {
                if (id.includes("node_modules")) {
                  return "vendor";
                }
              }
            }
          }
        },
    

    rollupOptions 里还能够配置打包生成的目录,一个常用的配置:

     build: {
          rollupOptions: {
            output: {
              // ...
              assetFileNames: (assetInfo) => {
                var info = assetInfo.name.split(".");
                var extType = info[info.length - 1];
                if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)) {
                  extType = "media";
                } else if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetInfo.name)) {
                  extType = "img";
                } else if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name)) {
                  extType = "fonts";
                }
                return `static/${extType}/[name]-[hash][extname]`;
              },
              chunkFileNames: "static/js/[name]-[hash].js",
              entryFileNames: "static/js/[name]-[hash].js"
            }
          }
        },
    

    打包后的目录:


    2. 动态导入

    动态导入(import 函数)是 ES6 的新特性。使用动态导入语法能够实现分包,进而实现懒加载。通常用于路由的懒加载。下面是一个例子。

    // import { throttle } from 'lodash';
    // console.log('object :>> throttle', throttle);
    
    import('lodash').then(({ throttle }) => {
      console.log('object :>> throttle', throttle);
    });
    

    上面是直接导入,下面是动态导入。二者打包结果如下:


    可以看出,下面的 lodash 已经自动分包了。

    3. 图片压缩、gzip 压缩

    通过 vite-plugin-imageminvite-plugin-compression 插件可以实现图片压缩与 gzip 压缩。用法也比较简单:

    import compression from 'vite-plugin-compression';
    import imagemin from 'vite-plugin-imagemin';
    
    export default defineConfig({
      // ...
      plugins: [react(), compression(), imagemin()]
      // ...
    });
    

    4. CDN 优化(外网环境)

    通过 vite-plugin-cdn-import 插件能够将一些依赖使用 cdn 加载,从而降低包的大小,加快依赖加载速度。用法如下:

    import { Plugin as importToCDN } from 'vite-plugin-cdn-import';
    
    export default defineConfig({
      plugins: [
        react(),
        importToCDN({
          modules: [
            {
              name: 'lodash',
              var: '_',
              path: 'https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js'
            }
          ]
        })
      ]
    });
    

    参考文档

    Vite 官方文档: https://cn.vitejs.dev/guide/
    Vite 和 webpack、rollup 打包工具对比:https://blog.csdn.net/Ambibibition/article/details/127766551
    Vite世界指南(带你从0到1深入学习 vite):
    https://www.bilibili.com/video/BV1GN4y1M7P5/?spm_id_from=333.999.0.0&vd_source=41e6d1d28e504860272fd13300cb250c

    相关文章

      网友评论

          本文标题:Vite从0到1

          本文链接:https://www.haomeiwen.com/subject/nxsvxdtx.html