Vite:构建生产版本

准备将应用部署到生产环境时,只需运行 vite build。默认情况下,它以 <root>/index.html 为构建入口,生成适合由静态托管服务提供的应用包。常见服务的部署指南见 部署静态站点。

也可以在 Scrimba 观看交互式课程。

浏览器兼容性

默认情况下,生产构建面向符合 Baseline Widely Available 的最低浏览器版本;基准日期由每个主版本固定。本主版本默认支持:

  • Chrome ≥111
  • Edge ≥111
  • Firefox ≥114
  • Safari ≥16.4

可以通过 build.target 指定自定义目标,最低目标为 es2015。即使设定较低目标,Vite 仍依赖原生 ESM 动态导入和 import.meta,因此要求至少:

  • Chrome ≥64
  • Firefox ≥67
  • Safari ≥11.1
  • Edge ≥79

默认情况下,Vite 只处理语法转换,不包含 polyfill。可查看 cdnjs 的 polyfill 服务,它会根据用户浏览器的 UserAgent 自动生成 polyfill 包。

可通过 @vitejs/plugin-legacy 支持旧浏览器。它自动生成旧版代码块及对应的 ECMAScript 语言特性 polyfill;这些代码块只会在不支持原生 ESM 的浏览器中加载。

公共基础路径

相关内容:静态资源处理。

如果项目部署在嵌套的公共路径下,只需设置 base,全部资源路径就会相应重写。也可以使用命令行标志,例如 vite build --base=/my/public/path/。

构建时,JavaScript 导入的资源 URL、CSS 的 url() 引用以及 HTML 文件中的资源引用,都会自动根据该选项调整。

例外情况是运行时需要动态拼接 URL。此时可使用全局注入的 import.meta.env.BASE_URL,其值即公共基础路径。这个变量在构建时被静态替换,因此必须原样书写;import.meta.env['BASE_URL'] 不会生效。

更多控制方式见下文的高级基础路径选项。

相对基础路径

如果事先不知道基础路径,可以设定 "base": "./" 或 "base": ""。这样,生成的全部 URL 都相对于各自所在文件。

自定义构建

可以通过各种 构建配置选项 定制构建。特别是,可用 build.rolldownOptions 直接调整底层 Rolldown 选项:

vite.config.js

export default defineConfig({
  build: {
    rolldownOptions: {
      // https://rolldown.rs/reference/
    },
  },
})

例如,可以指定多个 Rolldown 输出,并搭配只在构建时应用的插件。

代码分块策略

可通过 build.rolldownOptions.output.codeSplitting 配置代码块如何拆分,详见 Rolldown 文档。如果使用框架,请参考框架文档了解如何配置分块。

处理加载错误

动态导入加载失败时,Vite 会触发 vite:preloadError 事件。event.payload 包含原始导入错误。调用 event.preventDefault() 后,该错误不会继续抛出。

window.addEventListener('vite:preloadError', (event) => {
  window.location.reload() // for example, refresh the page
})

新部署发生时,托管服务可能删除上一次部署的资源。因此,在新部署前访问过站点的用户可能遇到导入错误:设备上运行的资源已过期,代码试图导入的旧代码块也已被删除。这个事件可用于处理该情况。此时,务必为 HTML 文件设置 Cache-Control: no-cache,否则仍可能引用旧资源。

文件变化时重新构建

使用 vite build --watch 可启用 Rolldown 监听器。也可以通过 build.watch 直接调整底层 WatcherOptions:

vite.config.js

export default defineConfig({
  build: {
    watch: {
      // https://rolldown.rs/reference/InputOptions.watch
    },
  },
})

启用 --watch 后,待打包文件发生变化就会触发重建。注意,配置及其依赖发生变化时,需要重新启动构建命令。

多页面应用

假设源码目录结构如下:

├── package.json
├── vite.config.js
├── index.html
├── main.js
└── nested
    ├── index.html
    └── nested.js

开发时,直接访问或链接到 /nested/ 即可,行为与常规静态文件服务器一致。构建时,只需把多个 HTML 文件指定为入口:

vite.config.js

import { resolve } from 'node:path'
import { defineConfig } from 'vite'

export default defineConfig({
  input: {
    main: resolve(import.meta.dirname, 'index.html'),
    nested: resolve(import.meta.dirname, 'nested/index.html'),
  },
})

如果指定了不同的 root,请记住:解析输入路径时,import.meta.dirname 仍表示 vite.config.js 所在文件夹。因此,需要把项目根目录作为参数加入 resolve 调用。

对于 HTML 文件,Vite 会忽略 rolldownOptions.input 对象中赋予入口的名称,而依据文件解析后的 ID 生成 dist 中的 HTML 资源,确保目录结构与开发服务器一致。

库模式

开发面向浏览器的库时,大部分时间可能都在使用一个导入实际库的测试或演示页面。通过 Vite,可以用 index.html 承担此用途,并获得流畅的开发体验。

准备打包发布库时,使用 build.lib 配置。务必将不希望打入库包的依赖设为外部依赖,例如 vue 或 react:

vite.config.js:单入口

import { resolve } from 'node:path'
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: resolve(import.meta.dirname, 'lib/main.js'),
      name: 'MyLib',
      // the proper extensions will be added
      fileName: 'my-lib',
    },
    rolldownOptions: {
      // make sure to externalize deps that shouldn't be bundled
      // into your library
      external: ['vue'],
      output: {
        // Provide global variables to use in the UMD build
        // for externalized deps
        globals: {
          vue: 'Vue',
        },
      },
    },
  },
})

vite.config.js:多入口

import { resolve } from 'node:path'
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: {
        'my-lib': resolve(import.meta.dirname, 'lib/main.js'),
        secondary: resolve(import.meta.dirname, 'lib/secondary.js'),
      },
      name: 'MyLib',
    },
    rolldownOptions: {
      // make sure to externalize deps that shouldn't be bundled
      // into your library
      external: ['vue'],
      output: {
        // Provide global variables to use in the UMD build
        // for externalized deps
        globals: {
          vue: 'Vue',
        },
      },
    },
  },
})

入口文件包含导出,供软件包用户导入:

lib/main.js

import Foo from './Foo.vue'
import Bar from './Bar.vue'
export { Foo, Bar }

使用以上配置运行 vite build 时,会使用面向库发布的 Rollup 预设,生成两种格式的包:

  • 单入口:es 和 umd
  • 多入口:es 和 cjs

格式可通过 build.lib.formats 配置。

$ vite build
building for production...
dist/my-lib.js      0.08 kB / gzip: 0.07 kB
dist/my-lib.umd.cjs 0.30 kB / gzip: 0.16 kB

推荐的软件包配置如下:

package.json:单入口

{
  "name": "my-lib",
  "type": "module",
  "files": ["dist"],
  "main": "./dist/my-lib.umd.cjs",
  "module": "./dist/my-lib.js",
  "exports": {
    ".": {
      "import": "./dist/my-lib.js",
      "require": "./dist/my-lib.umd.cjs"
    }
  }
}

package.json:多入口

{
  "name": "my-lib",
  "type": "module",
  "files": ["dist"],
  "main": "./dist/my-lib.cjs",
  "module": "./dist/my-lib.js",
  "exports": {
    ".": {
      "import": "./dist/my-lib.js",
      "require": "./dist/my-lib.cjs"
    },
    "./secondary": {
      "import": "./dist/secondary.js",
      "require": "./dist/secondary.cjs"
    }
  }
}

CSS 支持

如果库导入了 CSS,它会被打包成一个独立 CSS 文件,与生成的 JavaScript 文件放在一起,例如 dist/my-lib.css。名称默认为 build.lib.fileName,也可以通过 build.lib.cssFileName 更改。

可以在 package.json 中导出 CSS 文件,供用户导入:

{
  "name": "my-lib",
  "type": "module",
  "files": ["dist"],
  "main": "./dist/my-lib.umd.cjs",
  "module": "./dist/my-lib.js",
  "exports": {
    ".": {
      "import": "./dist/my-lib.js",
      "require": "./dist/my-lib.umd.cjs"
    },
    "./style.css": "./dist/my-lib.css"
  }
}

环境变量

库模式的生产构建会静态替换所有 import.meta.env.* 用法,但不会替换 process.env.*,以便库的使用者动态更改。如果不希望如此,可以使用 define: { 'process.env.NODE_ENV': '"production"' } 将其静态替换,或者使用 esm-env,获得更好的打包器和运行时兼容性。

高级用法

库模式为面向浏览器的库和 JavaScript 框架库提供简单、带有明确约定的配置。如果构建的库不面向浏览器,或需要高级构建流程,可以直接使用 tsdown 或 Rolldown。

高级基础路径选项

对于高级场景,部署后的资源和公共文件可能位于不同路径,例如采用不同的缓存策略。用户可能希望部署到三条不同路径:

  • 生成的入口 HTML 文件,可能在 SSR 期间处理。
  • 生成的带哈希资源,包括 JavaScript、CSS、图片等。
  • 复制过去的 public 文件。

在这些情况下,单一静态 base 并不足够。Vite 通过 experimental.renderBuiltUrl,为构建时的高级基础路径选项提供实验性支持:

experimental: {
  renderBuiltUrl(filename, { hostType }) {
    if (hostType === 'js') {
      return { runtime: `window.__toCdnUrl(${JSON.stringify(filename)})` }
    } else {
      return { relative: true }
    }
  },
},

如果带哈希资源与公共文件并非部署在一起,可以使用函数第二个上下文参数中的资源类型,为两类资源分别定义选项:

experimental: {
  renderBuiltUrl(filename, { hostId, hostType, type }) {
    if (type === 'public') {
      return 'https://www.domain.com/' + filename
    } else if (path.extname(hostId) === '.js') {
      return {
        runtime: `window.__assetsPath(${JSON.stringify(filename)})`
      }
    } else {
      return 'https://cdn.domain.com/assets/' + filename
    }
  },
},

传入的 filename 是已解码的 URL;如果函数返回 URL 字符串,该字符串也应已解码。Vite 会在渲染 URL 时自动处理编码。如果返回包含 runtime 的对象,则需在必要时自行编码,因为运行时代码会原样输出。

来源与许可

原文:Vite 官方文档,© 2019-present VoidZero Inc. and Vite contributors。MIT 许可。本文为中文翻译,保留本次来源快照中的 Rolldown 配置及原文的 Rollup 预设用语。

官方原文 · 许可证与版权声明

原文参考链接

许可证原文

MIT License

Copyright (c) 2019-present, VoidZero Inc. and Vite contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容