我从 webpack 换到 vite,又换回了 webpack | 您所在的位置:网站首页 › webpack文件解析前替换内容 › 我从 webpack 换到 vite,又换回了 webpack |
前言
Vite 经过一段时间的发展,目前的生态已经非常丰富了。它不仅用于 Vue,React、Svelte、Solid、Marko、Astro、Shopify Hydrogen,以及 Storybook、Laravel、Rails 等项目都已经接入了Vite,而且也趋于稳定,所以就着手把项目的 Webpack 替换为 Vite。 切换为 ViteVite 生态现在很丰富了,基本上插件按名称搜索一下,照着文档就可以把 webpack 替换到 Vite。因为每个项目的配置都不一样,所以也没有什么统一的操作步骤,下面列一些典型替换的例子。 入口index.html 的位置需要放到项目的最外层,而不是 public 文件夹内。同样 entry 的入口文件也需要从 pages 里换到 index.html 里。由 引入。 module.exports = defineConfig({ pages: { index: { // page 的入口 entry: 'src/main.ts', // 模板来源 template: 'index.html', chunks: ['chunk-vendors', 'chunk-common', 'index'] } } }) 文件loader这里挑几个例子(下面例子 webpack 版本都为 webpack5)。 yaml 由原来的 yaml-loader 替换为 rollup-plugin-yamlx rules: [ { test: /\.ya?ml$/, use: 'yaml-loader' } ] import PluginYamlX from 'rollup-plugin-yamlx' plugins: [ ...other, PluginYamlX() ] svg-sprite 由原来的 svg-sprite-loader 替换为 vite-plugin-svg-icons const resolve = (...dirs) => require('path').resolve(__dirname, ...dirs) chainWebpack(config) { const svgRule = config.module.rule('svg') svgRule.exclude.add(resolve('base/assets/icons')).end() config.module .rule('icons') .test(/\.svg$/) .include.add(resolve('base/assets/icons')) .end() .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: 'ys-svg-[name]' }) .end() } import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import { resolve } from 'path' const pathResolve = (dir: string): string => { return resolve(__dirname, '.', dir) } plugins: [ ...other, createSvgIconsPlugin({ // Specify the icon folder to be cached iconDirs: [pathResolve('base/assets/icons/svg')], // Specify symbolId format symbolId: 'ys-svg-[name]' }), ] 注意文件加载方式的一致性,比如原来的 svg-loader 直接 import 引用的是路径地址,而 vite-svg-loader 默认是 Vue 组件。 所以 Vite 需要把默认方式改成和 webpack lodaer 一致。 plugins: [ svgLoader({ defaultImport: 'url' }) ]每个替换的插件都要看一下文档,也许某个配置就是你需要的功能。 全局常量比如开发的版本信息,开发环境变量等等。 new webpack.DefinePlugin({ APP_VERSION: process.env.VUE_APP_VERSION, ENV_TEST: process.env.VUE_ENV_TEST }) import { defineConfig, loadEnv } from 'vite' const { VITE_SENV_TEST, VITE_APP_VERSION } = loadEnv(mode, process.cwd()) export default ({ mode }: { mode: string }) => { return defineConfig({ define: { APP_VERSION: VITE_APP_VERSION, ENV_TEST:VITE_SENV_TEST } }) })这里注意,Vite 和 webpack 默认暴露的环境变量前缀不一样。 自动加载模块比如 lodash plugins: [ new webpack.ProvidePlugin({ _: 'lodash' }), ] import inject from '@rollup/plugin-inject' plugins: [ inject({ _: 'lodash', exclude: ['**/*.css', '**/*.yaml'], include: ['**/*.ts', '**/*.js', '**/*.vue', '**/*.tsx', '**/*.jsx'] }), ]基本上所有在用的插件都可以找到对应替换的,甚至像 monaco,qiankun,sentry使用量相对没那么大的都有。 这里只是举例兼容旧代码,lodash 最好还是写个工具替换成 es-loadsh。 webpack require context在 webpack 中我们可以通过 require.context 方法动态解析模块。比较常用的一个做法就是指定某个目录,通过正则匹配等方式加载某些模块,这样在后续增加新的模块后,可以起到动态自动导入的效果。 比如 layout,router 的自动注册都可以这样用。 const modules = require.context('base/assets/icons/svg', false, /\.svg$/)Vite 支持使用特殊的 importa.glob 函数从文件系统导入多个模块: const modules = importa.glob('base/assets/icons/svg/*.svg') externals externals: { config: 'config', } import { viteExternalsPlugin } from 'vite-plugin-externals' plugins: [ viteExternalsPlugin({ config: 'config' }) ] ESM 模块由于 Vite 使用了 ESM 模块方式,所以 commonJs模块 都需要替换成 ESM模块。 const path = require('path') import path from 'path'也正是因为这个原因,所以才会又换回了 webpack,这个下面再讲。 自动化转换社区也有一些自动化从Wepback转为Vite的工具,比如vue-cli-plugin-vite,webpack-to-vite,wp2vite等等。 如果是小项目,可以尝试一下。大项目不建议使用,不可控。感兴趣的可以去看对应的文档。 ESM 的循环引用问题可以看到 Vite 的 Issues 有很多相关的问题讨论。 github.com/vitejs/vite… github.com/vitejs/vite… 如果是 Vue SFC 文件的循环引用,按官方文档来就可以解决。 如果是其他文件的循环引用,也可以梳理更改。但是吊诡的地方在于,调用栈会出现 null。这个在开发中出现了根本没办法debug。有时候有上下文,只是中间出现null还能推断一下,如果提示一串null,那根本没办法开发。 CommonJs 与 ESM 对于循环依赖的处理的策略是截然不同的,webpack 在运行时注入的 webpack_require 逻辑在处理循环依赖时的表现与 CommonJs 规范一致。Webapck 根据 moduleId,先到缓存里去找之前有没有加载过,如果有加载过,就直接拿缓存中的模块。如果没有,就新建一个 module,并赋值给缓存中,然后调用 moduleId 模块。所以由于缓存的存在,出现循环依赖时才不会出现无限循环调用的情况。 由于 ESM 的静态 import 能力,可以在代码运行之前对依赖链路进行静态分析。所以在 ESM 模式下,一旦发现循环依赖,ES6 本身就不会再去执行依赖的那个模块了,所以程序可以正常结束。这也说明了 ES6 本身就支持循环依赖,保证程序不会因为循环依赖陷入无限调用。 正是因为处理机制的不同,导致 Vite 下循环引用的文件都会出现调用栈为 null 的情况。 找了个webpack插件circular-dependency-plugin 检查了一下循环引用的文件,发现像下面这样跨多组件引用的地方有几十处。改代码也不太现实,只能先换回webpack了。 webpack 还是用官方封装的 Vue CLI。 缓存webpack4 还是使用 hard-source-webpack-plugin 为模块提供中间缓存的,但是 webpack5 已经内置了该功能。 module.exports = { chainWebpack(config) { config.cache(true) } }hard-source-webpack-plugin 作者已经被 webpack 招安了,原插件也已经不维护了,所以有条件还是升级到 webpack5 。 esbuild 编译编译可以使用 esbuild-loader 来替换 babel-loader,打包这一块就和 Vite 相差不多了。 看了下 vue-cli 的配置,需要换的 rule 是这几个。大概的配置如下: chainWebpack(config) { const rule = config.module.rule('js') // 清理自带的babel-loader rule.uses.clear() // 添加esbuild-loader rule .use('esbuild-loader') .loader('esbuild-loader') .options({ jsxFactory: 'h', jsxFragment: 'Fragment', loader: 'jsx', target: 'es2015' }) .end() const tsRule = config.module.rule('typescript') tsRule.uses.clear() tsRule .use('ts') .loader('esbuild-loader') .end() }注意,上面的 jsx 配置只适用于 Vue3,因为 Vue2 没有暴露 h 方法。 如果要在 Vue2 上使用 jsx 解析,得需要一个解析 Vue2 语法完整运行时的包。 pnpm i @lancercomet/vue2-jsx-runtime -D React 关于全新 JSX 转换的思想 @lancercomet/vue2-jsx-runtime github 大概就是把 jsx transform 从框架单独移了出来,以脱离框架适配 SWC,TSC 或者 ESBuild 的 jsx transform。 const rule = config.module.rule('js') // 清理自带的babel-loader rule.uses.clear() // 添加esbuild-loader rule .use('esbuild-loader') .loader('esbuild-loader') .options({ target: 'es2015', loader: 'jsx', jsx: 'automatic', jsxImportSource: '@lancercomet/vue2-jsx-runtime' }) .end()同时需要修改 tsconfig.json { "compilerOptions": { ... "jsx": "react-jsx", // Please set to "react-jsx". "jsxImportSource": "@lancercomet/vue2-jsx-runtime" // Please set to package name. } } 类型检查类型检查这块开发时可以交给 IDE 来处理,没必要再跑一个线程。 chainWebpack(config) { // disable type check and let `vue-tsc` handles it config.plugins.delete('fork-ts-checker') } 代码压缩这些其实性能影响已经不大了,聊胜于无。 const { ESBuildMinifyPlugin } = require('esbuild-loader') chainWebpack(config) { config.optimization.minimizers.delete('terser') config.optimization.minimizer('esbuild').use(ESBuildMinifyPlugin, [{ minify: true, css: true }]) } 优化结果这是 Vue-CLI 优化之后的打包,已经和 Vite 基本一致了。至于开发,两者的逻辑不一样,热更新确实是慢。 Vite 的生态已经很丰富了,基本能满足绝大多数的需求了。我们这次迁移由于平时开发遗留的一些问题而失败了。应该反省平时写代码不能只为了快,而忽略一些细节。 这就是本篇文章的全部内容了,感谢大家的观看。 |
CopyRight 2018-2019 实验室设备网 版权所有 |