vivipure / blog

Funkun's blog.
https://funkun.hashnode.dev/
2 stars 0 forks source link

一次Webpack 升级 Vite 的失败之旅 #31

Open vivipure opened 1 year ago

vivipure commented 1 year ago

一. 写在前面

负责的项目一直使用 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 5Module 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的仓库中查找到了

https://github.com/originjs/vite-plugin-federation/issues/248#issuecomment-1260875464

组件库对于 vue 是配置了 singleton: true,这可能是导致无法正常加载的主要原因

4.3 其他问题

除了上面这个问题,我在使用 @vitejs/plugin-legacy ,也遇到和 vite-plugin-federation 不兼容的情况, 具体可看这个

https://github.com/vitejs/vite/issues/6133

4.4 如何处理

最终,我放弃了。插件目前不支持 singleton属性,除非我把组件库改成 Vite 构建的,这样的改动成本太大了。

五. 最后

最终我还是清除了 Vite 的依赖,如果只是为了优化热更新的时间,而配置两个构建工具,实在不太合适。

在这个过程中,还是有收获的。比如一些 Webpack 功能的迁移,Vite 插件的编写。

希望后续上面提到的问题能被解决,后面有时间我再继续进行折腾吧。