一. 写在前面
负责的项目一直使用 Webpack 5 进行构建,开发时 热更新速度 和 打包速度 总体上还行,但是和 Vite 还是有些差距,因此特意花费时间来进行构建工具的升级
项目技术栈: Vue 2.7 + TS
二. 适配过程
2.1 Vue 功能支持
使用官方提供的插件即可
import vue from '@vitejs/plugin-vue2'
...
plugins: [vue()]
...
2.2 less 全局变量
css: {
preprocessorOptions: {
less: {
additionalData: '@import "./src/assets/css/var.less";',
},
},
},
2.3 环境变量
项目使用 cross-env 设置环境变量,再通过构建工具暴露到全局变量中
define: {
HTTP_ENV: JSON.stringify(process.env.http_env),
},
2.4 xhtml 处理
项目的业务中使用 xhtml 作为模板文件,用于字符串的组装。在webpack 中使用 loader进行配置
{
test: /\.(xhtml)$/,
type: 'asset/source',
},
在 vite 中可以使用 ?raw ,但是每个导入的都需要加,很麻烦。所以我写了一个插件,可以直接进行转化
function xhtmlPlugin(): Plugin {
return {
name: 'xhtmlPlugin',
transform(code, id, options?) {
if (id.endsWith('.xhtml')) {
let buf = fs.readFileSync(id).toString()
// 过滤文本中存在的 `,${, 避免代码生成异常
if(buf.includes('`')) {
buf = buf.replace(/`/g, '\\`')
buf = buf.replace(/\${/g, '\\${')
}
return {
code: `export default \`${buf}\`;`,
};
}
},
}
}
2.5 Module Federation
迁移的难点主要在于 Module Federation , 项目使用了 Webpack 5 的 Module Federation 作为组件库,因此需要 Vite 也进行支持
这里我使用了 vite-plugin-federation
import federation from '@originjs/vite-plugin-federation'
...
federation({
name: 'remote-app',
filename: 'remoteEntry.js',
shared: {
...pkg.dependencies,
},
remotes: {
microFE: {
external: federationUrl,
format: 'var',
from: 'webpack',
},
},
})
...
配置之后,在开发模式时,无法使用测试环境的 remote ,会产生跨域的问题,因此写了一个插件进行兼容
const adaptiveModuleFederationPlugin = function (federationConfig: Parameters<typeof federation>[0]) {
const adpativePlugin = () => ({
name: 'adaptiveModuleFederation',
config(config, { command }) {
if (command === 'serve') {
if (!config.server.proxy) {
config.server.proxy = {}
}
const remotes = federationConfig.remotes!
const remoteKeys = Object.keys(remotes)
remoteKeys.forEach(key => {
const urlConfig = remotes[key]
let url = ''
if (typeof urlConfig === 'string') {
url = urlConfig
} else if (typeof urlConfig === 'object') {
url = urlConfig.external
}
const preffixUrl = url.substring(0, url.lastIndexOf('/'))
const endUrl = url.substring(url.lastIndexOf('/'))
const proxyPath = '/proxy' + key
const reg = new RegExp(`^${proxyPath}`)
config.server.proxy[proxyPath] = {
target: preffixUrl,
changeOrigin: true,
rewrite: path => path.replace(reg, ''),
}
const updateUrl = proxyPath + endUrl
if (typeof urlConfig === 'string') {
federationConfig.remotes![key] = updateUrl
} else if (typeof urlConfig === 'object') {
federationConfig.remotes![key].external = updateUrl
}
})
}
},
})
return [adpativePlugin(), federation(federationConfig)]
}
插件原理很简单,将地址代理到本地,即可避免跨域
...
plugins: [...adaptiveModuleFederationPlugin({...}),]
...
三. 运行效果
搞定完上面这些,就可以正常运行项目了。提升十分明显。Webpack 5 热更新时间大概 1s 多,Vite 直接无感,使用十分流畅。
四. 滑铁卢
4.1 遇到问题
开发模式没有了问题,就开始进行打包。打包表现的很正常,得到构建后的文件后,开始本地运行。
What? 页面空白
打开 devtools, 报错了,无法从 Vue 中找到 defineComponent. 随后便进行各种尝试,最终在去除掉 config 中的 shared 后,项目可以正常运行
...
...adaptiveModuleFederationPlugin({
shared: []
})
...
查看了使用的公共组件,还是存在问题,element-ui 的组件无法进行渲染。
shared 配置当前服务的共享模块,作为 host 端不进行配置,无法保证公共组件的加载。
4.2 寻找答案
我在vite-plugin-federation的仓库中查找到了 issue , 组件库对于 vue 是配置了 singleton: true,这可能是导致无法正常加载的主要原因
4.3 其他问题
除了上面这个问题,我在使用 @vitejs/plugin-legacy ,也遇到和 vite-plugin-federation 不兼容的情况, 具体可看这个 issue
4.4 如何处理
最终,我放弃了。插件目前不支持 singleton属性,除非我把组件库改成 Vite 构建的,这样的改动成本太大了。
五. 最后
最终我还是清除了 Vite 的依赖,如果只是为了优化热更新的时间,而配置两个构建工具,实在不太合适。
在这个过程中,还是有收获的。比如一些 Webpack 功能的迁移,Vite 插件的编写。
希望后续上面提到的问题能被解决,后面有时间我再继续进行折腾吧。