[ Vite ]使用 vite 重构 webpack 项目过程中对两者之间差异对比的思考( 一 )

1,200 阅读5分钟

基于 vite 的优点速度快和热拔插功能,最近也在尝试将原来 webpack 构建的项目迁移改用 vite 构建代码,这里将他们迁移过程遇到的问题和总结记录下来。

编译方面

webpack 是将 所有的资源从头到尾打包成 bundle 再来引用, 而的 vite 是将依赖和源码先区分再分别处理。

项目的依赖构建( Dependency Pre-Bundling )

Dependency Pre-Bundling 实际上就是 vite 使用 esbuild 这一类 bundle 将这些依赖模块整体打包成 ES Module 。像 ESbuild 它最大的优点是快,它是用 Go 语言来编写的,从速度上来说相比 webpack 能快上数十倍。 同时,他还具备缓存功能同时,只有改动到指定类型文件的时候才会重新运行预构建 ,说明冷启动重新构建的效率也会很高。

项目的源码构建

对于项目中的源码,vite 是通过利用浏览器对 ES Module 的支持,直接在浏览器中通过指定的路径请求引入当前需要使用的模块,引用官网的两张图片来看

vite-001.png

vite-002.png

从上面的图片来看,他最核心的思想就是由原来的所有模块合成一个模块使用,变成由一个入口按需引用对应的模块。

文档提及

在文档尾部也提到,生产环境下是还是需要打包的,原因是虽说 vite 可以进行请求和热更新,但是这个前提是基于网络请求的条件下,那么,在生产环境下肯定要尽可能避免多次请求减少页面的出错情况。所以,生产环境还是要将资源进行打包处理。 当然,如果在开发或者测试阶段,那就可以完全忽略这个问题,可以充分享受热更新带来的便捷和提高开发效率的体验。

另一点是,打包工具目前是使用 Rolluop 这个工具,虽然说是一个成熟的构建工具,但是真正在实际开发上是不是会遇到一些兼容性或者使用上的问题,这个也不好说。如果仅仅从使用体验上来说的话,我人觉得这个工具用起来并没有 webpack 那么好用,他的一些 plugins 的开发思想也是来自 webpack , 使用的一些方法可以说完全按另一套方式来开发, 还有就是以后会不会使用 esbuild 来构建也不好说。

是否可以用于线上线项目

目前,我也看到了公司内部的一些项目开始做了一些重构使用了 vite 进行开发,总体的体验上来说虽然会有各种小问出现比如将 vite 版本升级到了 2.9.6,就和其他的依赖相互影响,出现无处解析 css 的问题。但是,使用 vite 确实是可以提高开发效率很方便尤其是在热更新这一方面。 可以在小项目上先做尝试,等 vite 稍微再更新一两个版本就可以接入试试。

入口对比

webpack 的入口配置是在配置模块的中的 entry 中,而 Vite 中的入口则是在 rollupOptions 的build 选项下。当然,在 Vite 1.0 的时候,还是有 entry 入口,只不过是在 2.0的时候废弃了。 vite 2.0 开始所有构建相关的全部挪到了 build 字段中。

// webpack 
module.exports = async ( env ) => {
    const common = {
        entry: {
            index: './pages/index/entry.js',
            details: './pages/details/entry.js',
        },
        // ... 
    }
    return common
}

// vite
module.exports = defineConfig(() => {
	const { input, plugins } = getAllPages();
	return {
		build: {
			polyfillModulePreload: false,
			rollupOptions: {
				input: input,
				plugins: [...plugins],
			},
      //...
		},
	};
});

解析对比

在使用 webpack 构建时,静态的资源都需要相应的 loader 进行处理,比如像 scss,或者 ts ,他们都分别需要使用sass-loader 和 ts-loader,而 ts 还需要加其他的配置,像 webpack 需要增加 scss 规则,需要配置 sass 的样式解析器。

// webpack
module.exports = async (env) => {
    // debugger
    const common = {
        // ...
        module: {
            rules: [
                {
                    test: /\.vue$/,
                    loader: 'vue-loader'
                },
                {
                    test: /\.s[ac]ss$/i,
                    use: [MiniCssExtractPlugin.loader, 'css-loader', "sass-loader"]
                },
                {
                    test: /\.(png|svg|jpg|jpeg|gif)$/i,
                    type: 'asset/resource',
                },
            ],
        },
        plugins: [
            // 
        ],
    }
    return common;
}

而vite 提供了对 .scss, .sass, .less, .styl 和 .stylus 文件的内置支持。没有必要像 webpack 一样安装特定的解析插件,但必须安装相应的预处理器依赖。不仅仅是样式上 vite 提供了内置支持,还有 JSX ,JSON,TS,Web Worker 等等,这也不一定能解决所有问题,但也是也比较方便的地方。

在资源路径上,webpack 的 alias 配置项可以替换修改资源链接路径,到了 vite 这边使用的就是 @rollup/plugin-alias 的插件。用法上有差异但是都是那么一回事。

还有一点是 vite 也以供了一套构建优化,可以对 css 做优化切割,自动化生成公用的 chunks ,这个也是可以配置的。

插件 plugin

插件方面 vite 的生态没有 webpack 的那么成熟,它没有那么多的插件可以使用即使是有,可能和需要的又有点差异或者有兼容性的问题。往往如果根据实际业务需要,或者实现不麻烦的情况可以自己开发一个 plugins ,但是这个就需要一些成本。举个例子:

例如 webpack 中最为常用的就是 HtmlWebpackPlugin ,而在 vite 里面使用一个 @rollup/plugin-html 插件 ,他们在逻辑上都是一样的的东西创建和引入和静态资源。

相互对比:webpack 的使用方法上会更加清晰和容易维护。

HtmlWebpackPlugin

在多目录文件的情况下, webpack plugins 只要定义好 html 的模版文件,在配置一下文件名称和 chunks ,或者是 配置将资源注入在 html 中的位置等相关配置,另外也可以单独对 html 模版文件进行通过templateParameters 进行传参数渲染。使用上也是比较灵活。

  const plugins = [];
  forEach(( item )=>{ 
    plugins.push(
    new HtmlWebpackPlugin({ 
        template: relHtmlPath,
        filename: `${outPutPath}/${item}.html`,
        chunks: ['vender', `${item}`],
        chunksSortMode: 'manual',
        inject: true,
        templateParameters:{
            title: `${item}`,
        }
    })
  })
);

以下面最简单的例子来看,如果要传递 title 这个参数,那么只要在 templateParameters.title 这里赋值,也可以单拎出一个文件存放 meta 再引入。

<html>
  <head>
    <%= headTags %>
  </head>
  <title><%= title %></title>
  <body>
    <%= bodyTags %>
  </body>
</html>

@rollup/plugin-html

@rollup/plugin-html 在使用上会有点区别,他是在 rollup 配置项 build 字段的 rollupOptions 里面传入参数,也是一个对象。

const html = require('@rollup/plugin-html');

module.exports = defineConfig(() =>{
    const html = [];
    const plugins = ['index', 'details'];
    plugins.forEach((item)=>{
			html({
				fileName: `${item}.html`,
				meta: [],
				template: ({ attributes, bundle, files, publicPath, title }) => {
					const jsFile = files.js.find((items) => {
						const reg = eval(`/${item}.*?.js$/`);
						return reg.test(items.fileName);
					});
					const cssFile = files.css.find((items) => {
						const reg = eval(`/${item}.*?.css$/`);
						return reg.test(items.fileName);
					});
					debugger;

					return `<!DOCTYPE html>
                    <html lang="${attributes.html.lang}">
                      <head>
                        <title>${title}</title>
                        <link href="${cssFile.fileName}">
                      </head>
                      <body>
                        <div id="app"></div>
                        <script src="${jsFile.fileName}"></script>
                      </body>
                  </html>`;
				},
				title: `${item}`,
			})
    })
    return {
        build:{
            rollupOptions: [ ...html ]
        }
    }
});

上面这一个段 plugins 有两个文件的 html ,分别是 index 和 details。 这里稍微有一点麻烦 ,他不能像 webpack 一样自动的注入静态资源 而是需要手动的注入静态资源文件。 它在 html 的 template 里面, 他有 bundle 和 files 的文件,files 是一个数组,我习惯写一个正则匹配出他的 js 和 css 文件,再将这些已经打包过的静态资源注入到 html 里面去。

我在他文档的最后一段那里看到

This plugin was inspired by and is based upon mini-html-webpack-plugin by Juho Vepsäläinen and Artem Sapegin, with permission.

那其实这个插件也是基于 mini-html-webpack-plugin 的插件来做的,只是这样注入静态资源的方式不是特别好,可能我也没有发现其他更好的方法,如果有知道更加好的引入方法的小伙伴欢迎留言。

另外,我还发现另一个问题,就是多目录的情况下,每个目录下的 scss 文件如果内容是一样的话,打包的时候 rollup-html 这个插件直接认为只有一个 scss ,它只构建一个另外的 scss 直接给忽略了,感觉这算是一个 bug。

在 3 个 scss 的文件样式是一样的时候:

vite-01.png

在对 3 个 scss 的文件样式做了一点修改之后:

vite-02.png

总是在看到 success 之后又发现一些新的问题。

最后折腾下来,我觉得还不如自己写一个插件,毕竟风险可控也不需要要去看开发者的文档和熟悉。

小结:vite 的思路是好的,但使用起来还是有点不太好上手,需要进一步再了解编译细节。