Jun 13, 2024
Home | Vite 官方中文文档 (vitejs.dev)
Vite 运行 Dev 命令后只做了两件事情,一是启动了一个用于承载资源服务的 service;二是使用 esbuild 预构建 npm 依赖包。之后就一直躺着,直到浏览器以 http 方式发来 ESM 规范的模块请求时,Vite 才开始“「按需编译」”被请求的模块。(由于使用了ESM,vite不能使用reuqire,只能使用import和export)
使用官网模板构建vue项目(vue3)
bash# npm 6.x npm create vite@latest my-vue-app --template vue # npm 7+, extra double-dash is needed: npm create vite@latest my-vue-app -- --template vue # yarn yarn create vite my-vue-app --template vue # pnpm pnpm create vite my-vue-app -- --template vue
Vite
默认不提供 Vue2
项目的创建方式。这里搭建的时候vue3的项目,可以通过替换vue版本实现vue2,后续插件的版本也需要对应;
我们也可以使用 Vite
创建一个原生项目,然后再安装 Vue2
的生态进行开发,具体参考Vite 搭建 Vue2 项目(Vue2 + vue-router + vuex) - 简书 (jianshu.com)
bashnpm init vite@latest 选择 vanilla 即可
bashnpm install vue npm install vite-plugin-vue2 --dev npm install vue-template-compiler
在项目根目录下创建 src
目录。
创建项目的 main.js
。
搭建完成后可以看到vite的目录结构
index.html
在项目最外层而不是在 public
文件夹内。这是有意而为之的:在开发期间 Vite 是一个服务器,而 index.html
是该 Vite 项目的入口文件。
手动引入 src/main.js 启动入口,设置
type=module
html<script type="module" src="/src/main.js"></script>
要在 vite
里运行 vue2
项目,需要安装一个 vite
的插件:vite-plugin-vue2
shnpm install vite-plugin-vue2 --dev
配置如下
jsimport { createVuePlugin } from 'vite-plugin-vue2' export default { plugins: [ createVuePlugin({ vueTemplateOptions: {} }), ] }
还需要安装vue-template-compiler,注意版本要和vue版本一致
bashnpm install vue-template-compiler
3.2.1.导入时省略文件类型后缀
我们在导入 vue
、js
等文件时往往喜欢省略类型后缀,这需要配置 extensions
jsresolve: { extensions: ['.vue', '.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'], },
3.2.2.路径别名配置
jsimport path from 'path'; const resolve = dir => path.resolve(__dirname, dir); export default defineConfig({ resolve: { alias: { '@': resolve('src'), }, }, });
3.2.3.scss配置
在vite中需要额外安装sass
bashyarn add sass
配置如下
jscss: { preprocessorOptions: { scss: { // additionalData: '@import "@/styles/scss/global.scss";' } }, },
3.2.4.打包压缩
使用 vite-plugin-compression
做 gzip 压缩。
jsimport compressPlugin from "vite-plugin-compression"; export default defineConfig({ plugins: [ compressPlugin({ filter: /\.(js|css)$/i, // 压缩文件类型 deleteOriginFile: true, // 压缩后删除源文件 }), ], });
3.2.5.api 代理
proxy的配置和原先一样
4.1安装vue-router
bashnpm install vue-router
jsimport VueRouter from 'vue-router' Vue.use(VueRouter)
因为默认的 router/index.js
用到了环境变量,根据vite文档,这里需要做修改
javascriptconst router = new VueRouter({ mode: "history", base: import.meta.env.BASE_URL, routes });
从 process.env
改成了 import.meta.env
在main.js中全局注册
jsimport Vue from 'vue' import App from './App.vue' import router from './router/index.js' new Vue({ router, render: h => h(App) }).$mount('#app')
bashnpm install vuex --save
jsimport Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: {}, getters: { }, mutations: { } }, actions: {} })
全局注册vuex
jsimport Vue from 'vue' import store from './store' new Vue({ store, render: h => h(App) }).$mount('#app')
bashyarn dev
js"scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" //本地调试 --port },
bashyarn build
使用 webpack-to-vite 完成基础转换
bash$ npm install @originjs/webpack-to-vite -g $ webpack-to-vite <project path>
html<!-- vue-cli --> <head> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <body> <div id="app"></div> <!-- built files will be auto injected --> </body> </head> <!-- vite --> <head> <link rel="icon" href="/favicon.ico" /> <body> <div id="app"></div> <script type="module" src="/src/main.js"></script> </body> </head>
vite 与 webpack 对比 (1.2)
index.html
中的 URL 将被自动转换,因此不再需要 BASE_URL
占位符webpack
中的入口文件被放在 index.html
中解析js// vue-cli const TerserPlugin = require("terser-webpack-plugin"); const CompressionPlugin = require("compression-webpack-plugin"); const path = require("path"); function resolve(dir) { return path.join(__dirname, dir); } module.exports = { publicPath: "/", devServer: { host: "0.0.0.0", port: 9096, open: true }, css: { loaderOptions: { sass: { data: `@import "@/styles/index.scss";` } } }, chainWebpack: config => { config.resolve.alias .set("@", resolve("src")); config.module .rule("workder") .test(/\.worker\.js$/) .use("worker-loader") .loader("worker-loader") .options({ inline: true }) .end(); config.module.rule("images"). .test(/\.(png|jpe?g|gif|webp|svg)(\?.*)?$/) .exclude.add(resolve("src/icon/svg")) .end(); }, configureWebpack: { optimization: { minimizer: [ new TerserPlugin({ terserOptions: { warnings: false, compress: { drop_console: false, drop_debugger: true } }, parallel: true, sourceMap: true }) ] } }, plugins: process.env.NODE_ENV === "production" ? [ new CompressionPlugin({ filename: "[path].gz[query]", algorithm: "gzip", test: new RegExp( "\\.(" + ["js", "css"].join("|") + ")$" ), threshold: 10240, minRatio: 0.8 }) ] : [] }
js// vite import { defineConfig, loadEnv } from "vite"; import path from "path"; import { createVuePlugin } from "vite-plugin-vue2"; import ViteRequireContext from "@originjs/vite-plugin-require-context"; import envCompatible from "vite-plugin-env-compatible"; import { viteCommonjs } from "@originjs/vite-plugin-commonjs"; export default ({ mode })=>defineConfig({ base: loadEnv(mode, process.cwd()).DEV ? "/" : "./", server: { strictPort: false, port: 9096, host: "0.0.0.0", open: true }, css: { preprocessorOptions: { sass: { additionalData: '@import "@/styles/index.scss";' }, scss: { additionalData: '@import "@/styles/index.scss";', exclude: "node_modules", javascriptEnabled: true, charset: false } } }, resolve: { alias: [ { find: /^~/, replacement: "" }, { find: "@", replacement: path.resolve(__dirname, "src") } ] }, plugins: [ createVuePlugin({ jsx: true, vueTemplateOptions: { compilerOptions: { whitespace: "condense" } } }), ViteRequireContext(), viteCommonjs(), envCompatible(), viteCompression() ], build: { minify: "terser", terserOptions: { compress: { keep_infinity: true, drop_console: true, drop_debugger: true } }, chunkSizeWarningLimit: 2000 }, })
vite 与 webpack 对比 (1.3)
gzip
、terser
配置不同Vite
提供了对 .scss
, .sass
, .less
, .styl
和 .stylus
文件的内置支持,不必像wepack
一样为它们安装特定的 插件,但必须安装相应的预处理器依赖,例如sass
,less
,stylus
web workder
Vite
对jsx
语法显式声明jsx
语法的js文件后缀名改为.jsx
,在使用到jsx
语法的vue文件中将脚本加上 lang="jsx"
标识jsx
语法转换为render
函数vue<script lang="jsx"> export default { render(){ return <div>JSX Render</div> } } </script>
:export
语法,可以通过以下方式替代jsimport targetStyle from "./target.module.scss";
/deep/
不再被支持,用::v-deep
替代Web worker
脚本可以通过 ?worker
或 ?sharedworker
后缀导入为 web worker
jsimport Worker from "path/target.worker.js?worker";
图片
图片可以被当作URL引入,或者直接在<img>
标签中引入图片路径
jsimport imgUrl from './img.png'; // const imgUrl = new URL('./img.png', import.meta.url).href; document.getElementById('hero-img').src = imgUrl
Autoprefixer
(一款PostCSS 插件,用于解析 CSS 并使用 Can I Use 中的值浏览器前缀添加到 CSS 规则中)
在vue-cli项目中,Autoprefixer
作为@vue/cli-service
的依赖被引入,在vite项目中需要在package.json中声明这一依赖。
pdfmaker 与 CommonJS
由于 Vite 只支持 ES Module 包,对于 CommonJS 或 UMD 包必须经过转换才可使用,而 Vite 的智能导入分析可能存在缺漏,因此需要手动配置让依赖被预构建处理。
jsexport default defineConfig({ optimizeDeps: { include: ['pica', 'tinycolor2'], } });
同时,项目中可能出现单独引入某一CommonJS依赖下的某个文件,在这种情况下optimizeDeps
也不起作用,需要引入@rollup/plugin-commonjs
手动处理
jsexport default defineConfig({ plugins: [ commonjs({ dynamicRequireTargets: [ "node_modules/pdfmake/build/pdfmake.js", "node_modules/pdfmake/build/vfs_fonts" ] }) ], optimizeDeps: { include: [ "pdfmake/build/pdfmake", "pdfmake/build/vfs_fonts" ] } })
.env
Vite 在一个特殊的 import.meta.env
对象上暴露环境变量,常用变量有:import.meta.env.MODE``import.meta.env.BASE_URL
,import.meta.env.PROD
,import.meta.env.DEV
。
Vite 通过.env文件加载的环境变量也会通过 import.meta.env
暴露给客户端源码。
为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_
为前缀的变量才会暴露给经过 vite 处理的代码。
bash// .env.permission VITE_APP_PERMISSION = true
webpack: 先打包后启动
vite: 先启动再按需编译资源
用Esbuild
整理不会发生变化的 node_modules 依赖,进行预构建,减少网络请求次数。
vite
会自动抓取源代码,从代码中找到需要预构建的依赖,即:以index.html
作为查找入口(entryPoints
),将所有的来自node_modules
以及在配置文件的optimizeDeps.include
选项中指定的模块找出来;vite
在启动时为提升速度,会检查缓存是否有效,有效的话就可以跳过预构建环节,缓存是否有效的判定是对比缓存中的hash
值与当前的hash
值是否相同。由于hash
的生成算法是基于vite
配置文件和项目依赖的,所以配置文件和依赖的的变化都会导致hash
发生变化,从而重新进行预构建。
CommonJS 和 UMD 兼容性:开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。当转换 CommonJS 依赖时,Vite 会执行智能导入分析,这样即使导出是动态分配的(如 React),按名导入也会符合预期效果:
性能: Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。
一些包将它们的 ES 模块构建作为许多单独的文件相互导入。例如,
lodash-es
有超过 600 个内置模块!当我们执行import { debounce } from 'lodash-es'
时,浏览器同时发出 600 多个 HTTP 请求!尽管服务器在处理这些请求时没有问题,但大量的请求会在浏览器端造成网络拥塞,导致页面的加载速度相当慢。通过预构建
lodash-es
成为一个模块,我们就只需要一个 HTTP 请求了!
node_modules/.vite
。 package.json
中的依赖列表、lockfile
、vite.config.js
的变化、node_modules/.vite
被删除、或者用--force
启动开发服务器,都会导致重新运行预构建。max-age=31536000,immutable
强缓存,以提高在开发时的页面重载性能。一旦被缓存,这些请求将永远不会再到达开发服务器。Vite提供了一个开发服务器,然后结合原生的ESM,当代码中出现import的时候,发送一个资源请求,Vite开发服务器拦截请求,根据不同文件类型,在服务端完成模块的改写(比如单文件的解析编译等)和请求处理,实现真正的按需编译,然后返回给浏览器。请求的资源在服务器端按需编译返回,完全跳过了打包这个概念,不需要生成一个大的bundle。服务器随起随用,所以开发环境下的初次启动是非常快的。而且热更新的速度不会随着模块增多而变慢,因为代码改动后,并不会有bundle的过程。
资源类型 | 服务端操作 |
---|---|
CSS LESS SCSS | CSS 预处理,打包为ESModule动态插入style标签 |
jsx ts tsx vue | JS编译 |
json | 打包为ESModule |
静态资源 | 打包为ESModule,输出本地路径 |
Vite 通过 WebSocket
来实现热更新通信。
Vite的客户端监听来自服务端的 HMR 消息推送完成对应的更新操作,消息包含:
connected
: WebSocket 连接成功
vue-reload
: Vue 组件重新加载(当你修改了 script 里的内容时)
vue-rerender
: Vue 组件重新渲染(当你修改了 template 里的内容时)
style-update
: 样式更新
style-remove
: 样式移除
js-update
: js 文件更新
full-reload
: fallback 机制,网页重刷新
Vite在服务端监听文件变更,根据不同文件类型来做不同的处理。例如:对于 Vue
文件的热更新而言,主要是重新编译 Vue
文件,检测 template
、script
、style
的改动,如果有改动就通过 WS 服务端发起对应的热更新请求;对于热更新 js
文件而言,会递归地查找引用这个文件的 importer
,如果找不到就发送full-reload
。
Bundle(Webpack) | Bundleless(Vite/Snowpack) | |
---|---|---|
启动时间 | 长,完成打包项目 | 短,只启动Server 按需加载 |
构建时间 | 随项目体积线性增长 | 构建时间复杂度O(1) |
加载性能 | 打包后加载对应Bundle | 请求映射至本地文件 |
缓存能力 | 缓存利用率一般,受split方式影响 | 缓存利用率近乎完美 |
文件更新 | 重新打包 | 重新请求单个文件 |
调试体验 | 通常需要SourceMap进行调试 | 不强依赖SourceMap,可单文件调试 |
生态 | 非常完善 | 目前先对不成熟,但是发展很快 |
底层 | Node.js | Esbuild(Go) |