Webpack/Rollup/Vite构建Vue3项目的区别

171 阅读8分钟

vue3默认的打包工具是vite,可是想用webpack或rollup构建vue3项目该怎么办呢?本文将演示使用这三种技术构建vue3项目的基本过程和配置。

Webpack构建vue3项目

项目目录和源码准备

首先看一下项目目录

.
├── dist/*
├── package.json
├── src
│   ├── app.vue
│   └── index.js
├── index.html
└── webpack.config.js
  • dist, 是一个文件夹,为 Vue.js 3 代码的编译结果目录,最后的编译结果都是前端静态资源文件,例如 JS、CSS 和 HTML 等文件;
  • package.json,是一个 JSON 文件,为 Node.js 项目的声明文件,声明了模块依赖、脚本定义和版本名称等内容;
  • src,是一个文件夹,为 Vue.js3 项目的源码目录,主要开发的代码内容都放在这个文件夹里;
  • webpack.config.js,是一个 JavaScript 文件,是本次 Vue.js 3 项目核心内容,主要是 Webpack 配置代码。

在 src 的文件夹里新增两个 Vue.js 3 的源码内容,为后续编译做准备。这里是 src/app.vue 的源码内容:

<template>
  <div class="demo">
    <div class="text">Count: {{state.count}}</div>
    <button class="btn" @click="onClick">Add</button>
  </div>
</template>

<script setup>
  import { reactive } from 'vue';
  const state = reactive({
    count: 0
  });
  const onClick = () => {
    state.count ++;
  }
</script>

<style>
.demo {
  width: 200px;
  padding: 10px;
  box-shadow: 0px 0px 9px #00000066;
  text-align: center;
}
.demo .text {
  font-size: 28px;
  font-weight: bolder;
  color: #666666;
}
.demo .btn {
  font-size: 20px;
  padding: 0 10px;
  height: 32px;
  min-width: 80px;
  cursor: pointer;
} 
</style>

以下是 src/index.js 项目的入口文件源码:

import { createApp } from 'vue';
import App from './app.vue';

const app = createApp(App);
app.mount('#app');

以下是index.html模版文件的源码:

<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.templateParameters.css[0] %>" />
    <script src="<%= htmlWebpackPlugin.options.templateParameters.js[0] %>"></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
  <script src="<%= htmlWebpackPlugin.options.templateParameters.js[1] %>"></script>
</html>

安装项目依赖

以下是项目所需要的npm模块,包含开发需要的模块:

  "dependencies": {
    "vue": "^3.3.4"
  },
  "devDependencies": {
    "css-loader": "^6.8.1",
    "html-webpack-plugin": "^5.5.3",
    "mini-css-extract-plugin": "^2.7.6",
    "vue-loader": "^17.2.2",
    "webpack": "^5.88.2",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1"
  }

使用以下命令安装:

npm i --save vue
npm i --save-dev css-loader mini-css-extract-plugin vue-loader webpack webpack-cli html-webpack-plugin webpack-dev-server

Webpack配置

安装好依赖后,就是重点内容webpack.config.js文件了,完整代码如下:

const path = require('path');
const webpackMerge = require('webpack-merge').default;
const { VueLoaderPlugin } = require('vue-loader/dist/index')
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 基础配置
const baseConfig = {
  mode: process.env.NODE_ENV,// 声明打包模式
  entry: {
    'index' : path.join(__dirname, 'src/index.js'),
  }, // 从入口文件执行打包构建编译
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
  }, // 编译结果存放的目录
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: [
          'vue-loader'
        ]
      }, // 打包vue文件
      {  
        test: /\.(css|less)$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
        ]
      }, // 打包样式文件
      { 
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      }, // 打包图片
    ]
  },
  plugins: [
    new VueLoaderPlugin(),  // vue的加载插件
    new MiniCssExtractPlugin({
      filename: '[name].css'
    }),  // css分离插件
  ],
  externals: {
    'vue': 'window.Vue', // 将 Vue.js 3 的运行源码进行“排除打包”处理,缩短打包时间
  }
}

if (process.env.NODE_ENV === 'development') {
  // 开发环境
  config = webpackMerge(baseConfig, {
    devtool: 'inline-cheap-module-source-map', 
    devServer: {
      static: {
        directory: path.join(__dirname),
      },
      port: 6001,
      hot: false,
      compress: false,
    },
    plugins: [
      new HtmlWebpackPlugin({
        title: 'Hello Vue',
        filename: 'index.html',
        template:'./index.html',
        minify: false,
        inject: false,
        templateParameters: {
          publicPath: path.join(__dirname),
          js: [
            './node_modules/vue/dist/vue.runtime.global.js',
            './index.js'
          ],
          css: [
            './index.css'
          ],
        },
      })
    ]
  })
} else {
  // 生产环境
  config = webpackMerge(baseConfig, {
    optimization: {
      minimizer: [
        new TerserPlugin({}),
        new CssMinimizerPlugin({}),
      ],
    },
    plugins: [
      new HtmlWebpackPlugin({
        title: 'Hello Vue',
        filename: 'index.html',
        template:'./index.html',
        minify: false,
        inject: false,
        templateParameters: {
          publicPath: path.join(__dirname),
          js: [
            'https://unpkg.com/vue@3.2.37/dist/vue.runtime.global.js',
            './index.js'
          ],
          css: [
            './index.css'
          ],
        },
      })
    ]
  })
}

module.exports = config;

上面的重点内容是baseConfig,是webpack的基础配置,另外是使用于开发环境和生产环境的配置。

在开发环境中,配置devtool和devServer让编译的代码能在浏览器中看到,并且能够断点调试。此外,利用html-webpack-plugin 插件来处理 HTML 页面。

在生产环境中,最重要的是代码编译完后要进行压缩处理,减少体积,因此多了optimization的配置。这里的 unpkg.com/vue@3.2.37/… 只是临时模拟 CDN 的 Vue.js 3 运行时文件,实际企业级项目要换成公司内部的 CDN 资源。

编译脚本配置

package.json中的脚本配置:

{
  "scripts": {
    "dev": "NODE_ENV=development webpack serve -c ./webpack.config.js",
    "build": "NODE_ENV=production webpack -c ./webpack.config.js"
  }
}

使用开发模式进行 Vue.js 3 的项目开发时,执行以下命令:

npm run dev

访问命令行所提示的访问链接,你就可以在浏览器预览到实际代码渲染结果了。

使用生产模式打包时,执行以下命令:

npm run build

可以在dist目录中看到打包好的文件。

Rollup构建vue3项目

项目的目录和代码与使用Webpack构建是一致的,区别在与配置文件从webpack.config.js换成rollup.config.js。

项目内容

先更新一下index.html文件,把那两个script标签去掉。

<html>
  <head>
    <link rel="stylesheet" href="./index.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
  <script src="./index.js"></script>
</html>

依赖安装

接着安装Rollup需要的npm模块

npm i --save vue
npm i --save-dev @babel/core @babel/preset-env @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-html @rollup/plugin-node-resolve @rollup/plugin-replace rollup rollup-plugin-postcss rollup-plugin-serve rollup-plugin-vue

这些依赖的作用如下:

  • @babel/core,Babel 官方模块,用来编译 JavaScript 代码;
  • @babel/preset-env,Babel 官方预设模块,用来辅助 @babel/core 编译最新的 ES 特性;
  • @rollup/plugin-babel,Rollup 的 Babel 插件,必须配合 @bable/core 和 @babel/preset-env 一起使用;
  • @rollup/plugin-commonjs,是 Rollup 官方插件,用来处理打包编译过程中 CommonJS 模块类型的源码;
  • @rollup/plugin-html,是 Rollup 官方插件,用来管理项目的 HTML 页面文件;
  • @rollup/plugin-node-resolve,是 Rollup 官方插件,用来打包处理项目源码在 node_modules 里的使用第三方 npm 模块源码;
  • @rollup/plugin-replace,是 Rollup 官方插件,用来替换源码内容,例如 JavaScript 源码的全局变量 process.env.NODE_ENV;
  • rollup,Rollup 的核心模块,用来执行 Rollup 项目的编译操作;
  • rollup-plugin-postcss,第三方模块,用于将 Vue.js 项目源码的 CSS 内容分离出独立 CSS 文件;
  • rollup-plugin-serve,第三方模块,用于 Rollup 项目开发模式的 HTTP 服务;
  • rollup-plugin-vue,Vue.js 官方提供的 Rollup 插件模块。

安装完后,package.json的依赖内容如下:

{
  "devDependencies": {
    "@babel/core": "^7.18.6",
    "@babel/preset-env": "^7.18.6",
    "@rollup/plugin-babel": "^5.3.1",
    "@rollup/plugin-commonjs": "^22.0.1",
    "@rollup/plugin-html": "^0.2.4",
    "@rollup/plugin-node-resolve": "^13.3.0",
    "@rollup/plugin-replace": "^4.0.0",
    "rollup": "^2.77.0",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-serve": "^2.0.1",
    "rollup-plugin-vue": "^6.0.0"
  },
  "dependencies": {
    "vue": "^3.2.37"
  }
}

Rollup配置

rollup.config.js完整内容如下:

const path = require('path');
const fs = require('fs');
const { babel } = require('@rollup/plugin-babel');
const vue = require('rollup-plugin-vue');
const { nodeResolve } = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
const postcss = require('rollup-plugin-postcss');
const replace = require('@rollup/plugin-replace');
const html = require('@rollup/plugin-html');
const serve = require('rollup-plugin-serve');

const babelOptions = {
  "presets": [
    '@babel/preset-env',
  ],
  'babelHelpers': 'bundled'
}

module.exports = {
  input: path.join(__dirname, 'src/index.js'),
  output: {
    file: path.join(__dirname, 'dist/index.js'),
  }, 
  plugins: [
    vue(),
    postcss({
      extract: true,
      plugins: []
    }),
    nodeResolve(),
    commonjs(),
    babel(babelOptions),
    replace({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      preventAssignment: true
    }),
    html({
      fileName: 'index.html',
      template: () => {
        const htmlFilePath = path.join(__dirname, 'index.html')
        const html = fs.readFileSync(htmlFilePath, { encoding: 'utf8' })
        return html;
      }
    }),
    process.env.NODE_ENV === 'development' ? serve({
      port: 6001,
      contentBase: 'dist'
    }) : null
  ],
}

Rollup的配置主要包含三部分:

  • input,是声明了 Rollup 要执行打包构建编译时候从哪个文件开始编译的“入口文件”;
  • output,是声明 Rollup 编译的出口文件,也就是编译结果要放在哪个目录下的哪个文件里,这里我就对应地把出口目录配置在 dist 文件夹里;
  • plugins,这个是 Rollup 的插件配置,主要是贯穿 Rollup 的整个打包的生命周期。

Rollup的配置比Webpack更简单一些,它的技术生态就只有 Plugin 的概念,不像 Webpack 有 Loader 和 Plugin 两种技术生态和其它额外的官方配置。

最后在package.json里配置执行编译的脚本:

{
  "scripts": {
    "dev": "NODE_ENV=development rollup -w -c ./rollup.config.js",
    "build": "NODE_ENV=production rollup -c ./rollup.config.js"
  }
}

在命令行中执行npm run dev即可通过浏览器访问localhost:6001查看开发编译的结果;在命令行中执行npm run build即可在dist/目录下查看生产环境的编译结果。

Vite构建Vue3项目

使用Vite构建Vue3项目,与使用Rollup的最大区别在于配置文件vite.config.js,并且安装的依赖更简单很多。

项目内容

更新index.html文件,不再需要css链接,并且更新index.js的脚本引入路径。

<html>
  <body>
    <div id="app"></div>
  </body>
  <script type="module" src="/src/index.js"></script>
</html>

安装依赖

除了必须的vue依赖外,开发依赖只需要安装@vitejs/plugin-vue,

npm i --save vue
npm i --save-dev vite @vitejs/plugin-vue

Vite配置

vite.config.js的内容如下:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  base: './'
})

package.json中的脚本配置如下:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}

Vite和Rollup的对比

Vite 只是在生产模式下的打包编译用了 Rollup,开发模式下的打包编译用的是 esbuild。

Rollup 开发模式的执行过程:

  • 启动 Rollup 开发模式命令,Rollup 识别配置里的编译入口(input),从入口文件解析出所有依赖代码,进行编译;
  • 编译完后启动 HTTP 开发服务,同时也继续监听源码变化;
  • 开发者用浏览器访问页面;再次修改代码,Rollup 监听到源码变化,再整体重新编译代码。

Rollup 是直接把所有代码打包成 Bundle 文件格式,也就是最后只生成一个 JavaScript 文件和 CSS 文件。

Vite 的开发模式执行过程:

  • Vite 开发模式命令,VIte 启动 HTTP 服务和监听源码的变化;
  • 开发者用浏览器访问页面;
  • Vite 根据访问页面引用的 ESM 类型的 JavaScript 文件进行查找依赖,并将依赖通过 esbuild 编译成 ESM 模块的代码,保存在 node_modules/.vite/ 目录下;
  • 浏览器的 ESM 加载特性会根据页面依赖到 ESM 模块自动进行按需加载。
    • 再次修改代码,再次访问页面,会自动执行 ESM 按需加载,同时触发依赖到的变更文件重新单独编译;
    • 修改代码只会触发刷新页面,不会直接触发代码编译,而且源码编译是浏览器通过 ESM 模块加载访问到对应文件才进行编译的;
    • 开发模式下因为项目源码是通过 esbuild 编译,所以速度比 Rollup 快,同时由于是按页面里请求依赖进行按需编译,所以整体打包编译速度理论上是比 Rollup 快一些

Vite 的按需加载打包速度,理论上是比 Rollup 的打包编译 Bundle 文件要快一些,但是当项目页面依赖了很多 JavaScript 文件和 npm 模块,加载页面时候就要按需加载依赖的 ESM 文件,需要很长的浏览器请求时间,也会带来开发上的不好体验。在这种场景下,即使 esbuild 提升的打包速度节省的时间,也招架不住浏览器大量 ESM 模块请求消费的大量时间,降低了整体的开发模式体验。

总结

Vite,Rollup和Webpack都可用于打包vue项目,简单对比三种打包方案:

欢迎关注公众号 更新了啥,记录一个小前端的技术和生活的成长。

本文为极客时间上杨文坚的Vue3企业级项目实战课的笔记