开个头
vite跑一个项目的确要快得多,不过因为是新玩意儿,打包的时候还是webpack比较稳妥,现在vite只用在开发环境用以提高开发效率。
下面我会记录我将一个vue2工程移植到vite并兼容webpack打包功能的全过程。
第一步:想办法白嫖一个配置文件
从头开始写配置文件也行,但是总是没有白嫖香的。
这里使用了一个库https://github.com/originjs/webpack-to-vite/blob/main/README-zh.md
1.下载:
npm install @originjs/webpack-to-vite -g
2.使用:项目目录下输入命令
webpack-to-vite -d ./
3.效果:
可以看到更目录下多出三个文件,conversion.log,index.html,vite.config.js。
前两个都不重要,重点是vite.config.js,为我们提供了一个vite配置文件的基础模板,虽然还有些问题,不过再进行改造就好了。
除此之外,package.json也有了改动,
第二步:试着跑一下,然后解决报错
serve-vite
项目更目录下执行
npm run serve-vite
很快,且很顺利的跑出来了
但是事情并没有这么简单,打开页面后并没有看到想要的页面,而是一个大大的报错。
为什么会这样?因为vite和webpack有所不同,它不需要先打包,而是先启动web server,等到浏览器请求文件的时候再进行编译,更加详细的相关知识在第五步会有提到。
可以预见,后面还会有很多的报错,想要看到页面,需要一个一个的把这些报错解决掉才行。
variable @xxxxxx is undefined
@main_color是我们自己用less定义的全局变量,vite找不到这个变量,需要我们把自定义的全局变量从外部注入。
这一点就和webpack配置中使用style-resources-loader添加less的全局变量有点类似。
解决:在vite.config.js添加以下代码
css: {
preprocessorOptions: {
less: {
modifyVars: {
hack: `true; @import (reference) "${path.resolve('src/assets/styles/resources.less')}";`,
},
javascriptEnabled: true,
},
},
},
注意这里的src/assets/styles/resources.less指的是项目的less变量声明文件路径,并非一定要和我的一样。
Failed to parse source for import analysis…
具体原因不明,不过从现象上来看,是viteCommonjs将lodash.js被解析出了毛病。
人家的源码是正常的
猜测是因为解析过程中吧字符串中的//识别成了注释,所以发生了这种诡异的事情。毕竟很多vite的扩展还在快速的更新当中,还不够成熟,有这样的问题我们只能暂时放弃使用某些扩展。
解决:
vite.config.js中viteCommonjs扩展先删除(同时其余几个暂时用不到的也一并删除)
import envCompatible from 'vite-plugin-env-compatible';
import { injectHtml } from 'vite-plugin-html';
import { viteCommonjs } from '@originjs/vite-plugin-commonjs';
Uncaught ReferenceError: require is not defined
vite不支持require,能import引入的尽量用import吧,因为viteCommonjs造成了上一个报错还无法使用,又暂时还没有找到其他的扩展可以很好的处理这个问题。
注:不过require.context不用手动做处理,有ViteRequireContext帮我们解决。
解决:
将源代码中的require替换为import。
例如本项目中的
const Base64 = require('js-base64').Base64;
替换为
import { Base64 } from 'js-base64';
Uncaught ReferenceError: process is not defined
vite打出来折后无法直接通过process.env访问环境变量,秉持着尽量不动源码的原则,有了下面的解决方法:
在vite.config.js中添加以下配置
define: {
'process.env': process.env,
},
SyntaxError: The requested module ‘xxx/xxx’ does not provide an export named ‘xxx’
如果我们使用的包都在node_modiles里应该不会有这个问题,但是如果不是的话,就可能会遇到这个报错。
报错的原因是因为这些包没有ESM的导出方式,当我们使用import的方式引入,就会报错。
那为什么node_modiles里的包我们使用import不会出现这种问题?在vite的官方文档上给我们解释了:
虽然我们没有对这个属性进行配置,不过vite会自行的去抓取依赖项的入口点,并进行预构建。
那么只要让我们自己的包也进行与构建,就不会有问题了。
解决:
在vite.config.js中添加下面这段代码
// 依赖优化选项
optimizeDeps: {
// 默认情况下,不在 node_modules 中的,链接的包不会被预构建。使用此选项可强制预构建链接的包。
include: ['@/assets/js/fabric.min.js'],
},
到这里,我已经可以看到我的页面了,当然如果你尝试复刻这个过程又遇到了其他我没有记录的问题,欢迎在这里补充解决方法。
第三步:页面上到处点点,完善一些细节
icon-font没有正确显示
还记得webpack-to-vite在根路径下生成的那个index.html吗,造成这个问题的原因就是在这个文件中没有引入iconfont.css等资源。
解决:
①将public/index.html拷贝到根目录下,覆盖掉webpack-to-vite生成的index.html。
②文件中添加一句
<script type="module" src="/src/main.js"></script>
③修改一些资源的路径,因为文件路径不同了,可能会找不到导致报错。
有<%= BASE_URL %>之类的代码直接改为/即可。
一些图片404
并非所有的图片404,而是采用
url('~@/assets/images/czjl.png')
这种方式引入的图片会报错。
vite不认识这样的路径,我们需要特殊处理一下。
在vite.config.js中添加配置
resolve: {
alias: [
{
find: '~@',
replacement: path.resolve(__dirname, 'src'),
},
{
find: /^~/,
replacement: '',
},
{
find: '@',
replacement: path.resolve(__dirname, 'src'),
},
],
},
第四步:继续完善
webpack版本我们在eslint和stylelint报错的时候是会在页面上有提示的,不过现在vite跑出来的页面还没有这样的功能,我们需要一些扩展。
试用了很多之后最终选用以下两个
vite-plugin-eslint,@frsource/vite-plugin-stylelint
操作步骤
下载:
npm install @frsource/vite-plugin-stylelint --save-dev
npm install vite-plugin-eslint --save-dev
引入:
在vite.config.js中
import eslintPlugin from 'vite-plugin-eslint';
import stylelintPlugin from '@frsource/vite-plugin-stylelint';
使用:
plugins: [
createVuePlugin(),
ViteRequireContext(),
stylelintPlugin(),
eslintPlugin(),
],
第五步:重新认识一下vite
兼容性
需要node12或以上版本。
你就说快不快吧
冷启动
webpack
webpack执行build的时候会从入口文件开始,将整个项目的文件编译成一个或多个单独的 js 文件到内存中,然后再启动开发server。
当项目大了之后,冷启动很慢的问题就暴露出来了,因为打包这一步会因为项目文件太多而消耗大量的时间。
vite
vite启动开发server的时候,不会去提前打包所有文件,而是执行预构建依赖,然后启动服务。
因为使用esbuild 预构建依赖,据说比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。
当浏览器访问某个路由的时候,再在服务器端编译相应源码文件,以 原生 ESM 方式提供源码。
所以可以看到,vite启动服务是很快的;不过同时他第一次打开页面的速度会相对较慢,因为通过上面的描述可以知道,与webpack相比,vite让浏览器承担了一定的打包任务。
热更新
webpack
Webpack 的热更新会以当前修改的文件为入口重新 build 打包,所有涉及到的依赖也都会被重新加载一次。项目越大,依赖越多,时间越久。
vite
每个文件通过 http 头缓存在浏览器端,当编辑完一个文件,只需让此文件缓存失效。当基于 ES module 进行热更新时,仅需更新失效的模块,这使得更新时间不随包的增大而增大。
当然如果修改的文件不再缓存中,那就更方便了,你完全感受不到任何卡顿,因为vite根本不会去处理这个文件。
总结
所以vite较webpack,有以下几个优点
1.快速冷启动
2.按需编译
3.模块热更新
缺点
1.因为按需编译,所以切换到一个从来没进入过的路由时会感觉到一个明显卡顿。
2.因为尚且年轻,与webpack相比,缺少很多可以稳定使用的扩展。
3.vite本身只支持esm,对一些年龄较大的依赖包可能不能非常好的支持,同时对不支持esm的浏览器也不友好。
啥是esm,啥又是cjs、amd、umd呀
先停止联想,esm和ems没有关系,这个amd也和显卡无关,他们都是javascript模块化的方法。
可能这些名称看着有些懵,不过你肯定是用过import * from,require,这些方法的,这里简单地去介绍一下他们,方便你更清晰的理解vite。
esm
即ESModule,是ECMAScript自己的模块体系,在ES6引入,目前大部分浏览器都支持。
一般这么用:
// 导出:export命令
export const obj = {name: 'E1e'};
// 默认导出 export default命令
export default {name: 'E1e'};
// 引入接口:import命令
// 引入普通导出
import { obj } from './test.js';
// 引入默认导出
import obj from './test.js';
cjs
即CommonJS,浏览器无法直接运行,需要编译,一般使用方法:
// importing
const doSomething = require('./doSomething.js');
// exporting
module.exports = function doSomething(n) {
// do something
}
有时候会看到直接exports=xxx,你可以理解为node在上方自带了一行var exports = module.exports;
amd
即asynchronously,一般使用方法:
define(['dep1', 'dep2'], function (dep1, dep2) {
//Define the module value by returning a value.
return function () {};
});
//
define(function (require) {
var dep1 = require('dep1'),
dep2 = require('dep2');
return function () {};
});
umd
即Universal Module Definition,这是一种通用方法,其中既有cjs的身影,也有amd的影子,用来解决多平台的兼容问题,在依赖包中会很常见,一般会是这样:
(function (window, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this, function () {
//module ...
});
现在你知道为什么require方法在vite中无法使用了,因为vite只支持esm。
同时之前被我们放弃使用的viteCommonjs扩展,你光看名字应该也能才出他的用途了,就是为了让vite能够兼用cjs,但是很明显这个插件目前看来还不够成熟,无法投入实际使用,只能期待他尽快更新版本。
其他问题
1.为什么proxy的配置好像和vue.config.js中一样?
因为两者实现代理用的都是http-proxy,两者配置基本上可以通用。
2.define可以用来自定义全局变量吗?
不可以,官方文档写了,它是用来定义全局变量替换方式。是用来替换本来就有的变量的,自定义变量无法实现。
3.modifyVars是个啥?
这是一个less的设置选项,可以粗暴地理解为覆盖less全局变量。
@import (reference)则是将文件当做样式库引入的意思。