[学习笔记] vue3+webpack5配置

558 阅读6分钟

webpack的作用是什么?

Webpack 是 JavaScript 程序的静态模块打包器。它的主要作用是将应用程序中的各种资源,如 JavaScript、CSS、图片、字体等,作为模块进行管理,并将它们打包成一个或多个优化后的文件,以便在浏览器中高效加载和运行。

本文将从以下几个 webpack 功能讲解 vue3 环境搭建

  • 代码编译(Loader)
  • 扩展功能(Plugin)
  • 代码分割(splitChunks)
  • 开发环境支持(HMR)

Loader

Loader的作用就是将不同格式的文件转译为浏览器可以执行的文件,如.sass/.vue/.tsx等。Loader本质上是一个函数,负责代码的转译,即对接收到的内容进行转换后将转换后的结果返回 配置Loader通过在 modules.rules中以数组的形式配置。

编译vue3代码需要用到主要loader有:

  1. vue-loader
  2. babel-loader
  3. style-loader & css-loader & sass-loader
  4. file-loader(使用 webpack5 资源模块 asset/resource 代替)
  5. url-loader(使用 webpack5 资源模块 asset/inline 代替)
  6. raw-loader(使用 webpack5 资源模块 asset/source 代替)
  7. thread-loader
  • vue-loader

将单文件组件(SFC) 解析为 vue runtime是可识别的组件模块,需配合 VueLoaderPlugin 插件使用。

// rules
{
  test: /\.vue$/, // 只解析 .vue 文件
  use: [
     // 开启多线程和缓存
    'thread-loader',
    {
      loader: 'vue-loader',
      options: {
         // 生成使用 ES modules 语法的 JS 模块。
        esModule: true,
      },
    },
  ],
},
  • babel-loader

将ES6的代码转换成ES5版本,注意解析ts文件是需配置 @babel/preset-typescript,配置 allExtensions参数可以解析 vue 文件 setup 的 ts 代码

{
          test: /\.ts$/,
          use: [
            // 开启多线程和缓存
            'thread-loader',
            {
              loader: 'babel-loader',
              options: {
                presets: [
                  [
                    '@babel/preset-env',
                    {
                      // useBuiltIns: 'usage', // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
                      // corejs: 2, // 配置使用core-js使用的版本
                      loose: true, // 使用loose模式
                      // 设置兼容目标浏览器版本,这里可以不写,babel-loader会自动寻找上面配置好的文件.browserslistrc
                      targets: {
                        browsers: [
                          '> 0.1%',
                          'last 3 versions',
                          'ie 10',
                          'ie 11',
                        ],
                      },
                    },
                  ],
                  [
                    // 解析ts文件
                    '@babel/preset-typescript',
                    {
                      allExtensions: true, // 支持所有文件扩展名,解析.vue等文件
                    },
                  ],
                ],
              },
            },
          ],
        },
  • style-loader & css-loader & sass-loader & MiniCssExtractPlugin.loader
  • style-loader 的功能是在 DOM 里插入一个 <style> 标签,并且将 CSS 写入这个标签内。
  • css-loader 的功能是解析 @import 和 url() 的语法,然后将 css 拼接为字符串,最后用 ES module 导出。
  • scss-loader 的功能是将 Sass 编译成 CSS。
  • MiniCssExtractPlugin插件的作用,就是提取JS中的CSS样式,用link外部引入,减少JS文件的大小,简称CSS样式分离。

这里的 use 数组定义了一系列的加载器,它们按照从后往前的顺序执行。首先,sass-loader 会将 Sass 编译成 CSS。然后,css-loader 会解析 CSS 中的 @importurl()import/require() 并解析它们。最后,style-loader 会将 CSS 注入到 DOM 中。

// 使用 css-loader 解析 css
{
  test: /\.css$/,
  use: [
    // 开发环境使用style-looader,打包模式抽离css
    isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
    'css-loader',
  ],
},
// 使用 sass-loader 解析 scss
{
  test: /\.(scss|sass)$/,
  // 只处理 app/pages 目录下的文件
  use: [
    // 开发环境使用style-looader,打包模式抽离css
    isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
    'css-loader',
    'sass-loader',
  ],
},
  • webpack5 资源模块
  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
  • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
  • asset 会根据文件的类型选择使用 asset/resourceasset/inline
// 解析图片文件
{
  test: /.(png|jpe?g|gif|svg)$/, // 匹配图片文件
  type: 'asset', // type选择asset
  parser: {
    dataUrlCondition: {
      maxSize: 10 * 1024, // 小于10kb转base64位
    },
  },
  generator: {
    filename: 'static/images/[name].[contenthash:8][ext]', // 文件输出目录和命名
    publicPath, // 文件路径
  },
},
// 解析字体
{
  test: /.(woff2?|eot|ttf|otf)$/, // 匹配字体图标文件
  type: 'asset', // type选择asset
  parser: {
    dataUrlCondition: {
      maxSize: 10 * 1024, // 小于10kb转base64位
    },
  },
  generator: {
    filename: 'static/fonts/[name].[contenthash:8][ext]', // 文件输出目录和命名
    publicPath,
  },
},
// 解析媒体文件
{
  test: /.(mp4|webm|ogg|mp3|wav|flac|aac)$/, // 匹配媒体文件
  type: 'asset', // type选择asset
  parser: {
    dataUrlCondition: {
      maxSize: 10 * 1024, // 小于10kb转base64位
    },
  },
  generator: {
    filename: 'static/media/[name].[contenthash:8][ext]', // 文件输出目录和命名
    publicPath,
  },
}

Plugin

通过社区丰富的Plugin可以实现多种强大的功能,例如代码分割、代码混淆、代码压缩、按需加载.....等等Plugin本质上是一个带有apply(compiler)的函数,基于tapable这个事件流框架来监听webpack构建/打包过程中发布的hooks来通过自定义的逻辑和功能来改变输出结果。 Plugin通过plugins 以数组的形式配置。

编译vue3代码需要用到主要Plugin有:

  • VueLoaderPlugin

VueLoaderPlugin的主要作用就是对vue不同模块配置不同的loader,在webpack安装插件时,也就是预处理阶段,VueLoaderPluginapply会被调用,该方法中拦截了用户自定义的rules属性,加入对 vue 单文件模块处理的规则后,返回调整后的rules列表

  • webpack.ProvidePlugin

ProvidePlugin 是 webpack 的内置插件,作用就是不需要 import 或 require 就可以在项目中到处使用配置好的变量。简单理解就是自动导入功能。

  • webpack.DefinePlugin

DefinePlugin 是 webpack 的内置插件。用来定义全局变量。

  • web-webpack-plugin

web 应用需要加载的资源都需要在 webpack 的 entry 里配置,最后输出对应的代码块,但是要让 web 应用运行起来还需要通过 html 加载这些资源放在浏览器里运行,web-webpack-plugin 作用就是为单页面应用输出HTML,性能优于html-webpack-plugin

  • mini-css-extract-plugin

mini-css-extract-plugin插件,结合optimization.splitChunks.cacheGroups配置,可以提取 CSS 公共部分文件,有效利用缓存,非公共部分文件使用inline方式,且可以设置存放路径(通过设置插件的filenamechunkFilename)。

  • clean-webpack-plugin

每次打包时删除上次打包的产物, 保证打包目录下的文件都是最新的

  • terser-webpack-plugin

使用 terser 来压缩js代码

  • css-minimizer-webpack-plugin

压缩CSS代码

  • html-webpack-inject-attributes-plugin

添加自定义属性注入标签

  • compression-webpack-plugin

生产环境采用gzip压缩JS和CSS

// 配置插件
plugins: [
  // 使用 VueLoaderPlugin 插件,用于解析 vue 文件
  // 将定义的规则应用到 vue 文件的解析对应的标签
  // 如匹配 .ts 的,解析script标签内容
  new VueLoaderPlugin(),
  // 把第三方库注入到 window.context 中
  new webpack.ProvidePlugin({
    Vue: 'vue',
  }),

  // 用于定义全局常量
  new webpack.DefinePlugin({
    __VUE_OPTIONS_API__: true,
    __VUE_PROD_DEVTOOLS__: false,
    __VUE_PROD_HYDARTION_MISMATCH_DETAILS__: false,
  }),

  // 清理dist目录
  new CleanWebpackPlugin({
    // root: path.resolve(__dirname, '..'),
    // 是否打印日志
    verbose: true,
  }),
  // 用于提取 CSS 公共部分文件,有效利用缓存,非公共部分文件使用inline方式
  new MiniCssExtractPlugin({
    filename: 'css/[name]_[contenthash:8].css', // 输出的文件名
  }),
  // gzip压缩
  new CompressionPlugin({
    test: /\.(js|css)$/, // 只生成css,js压缩文件
    filename: '[path][base].gz', // 文件命名
    algorithm: 'gzip', // 压缩格式,默认是gzip
    threshold: 1, // 只有大小大于该值的资源会被处理。默认值是 10k
    minRatio: 0.8, // 压缩率,默认值是 0.8
  }),
  // 浏览器在请求资源时不发送用户的身份凭证
  new HtmlWebpackInjectAttributes({
    crossorigin: 'anonymous',
  }),
  // 生成入口文件
  ...htmlWebpackPluginList,
]

splitChunks

代码分割配置,将代码分割成多个代码块,充分利用浏览器缓存机制,以减少加载时间

  • 什么是chunks?

要理解什么chunks,首先要搞清楚modulechunkbundle三者的关系。

上面说到 webpack 的作用是将应用程序中的各种资源,作为模块进行管理,并将它们打包成一个或多个优化后的文件。那么我们可以理解为未处理前的各种资源模块就是 module,而打包处理过后的产物就是bundle

对于打包产物bundle 有些情况下,我们觉得太大了。 为了优化性能,比如快速打开首屏,利用缓存等,我们需要对bundle进行以下拆分,对于拆分出来的东西,我们叫它chunk

// 配置优化,如代码分割,压缩,tree shaking 等
optimization: {
  minimizer: [
    new CssMinimizerPlugin(), // 压缩css
    new TerserPlugin({ // 压缩js
      parallel: true, // 开启多线程压缩
      terserOptions: {
        compress: {
          pure_funcs: ['console.log'], // 删除console.log
        },
      },
    }),
  ],

  /**
   * 代码分割配置,将代码分割成多个代码块,充分利用浏览器缓存机制,以减少加载时间
   */
  splitChunks: {
    chunks: 'all', // 分割代码块的范围,包括所有模块
    minSize: 20000, // 分割代码块的最小大小,单位为字节
    maxAsyncRequests: 10, // 按需加载时的最大并行请求数
    maxInitialRequests: 10, // 入口文件的最大并行请求数
    cacheGroups: {
      // 提取第三方库
      vendors: {
        test: /[\\/]node_modules[\\/]/, // 匹配 node_modules 目录下的模块
        priority: 1, // 优先级,值越大表示优先级越高
        name: 'vendors', // 生成的代码块名称
        chunks: 'initial', // 只提取初始化就能获取到的模块,不管异步的
        enforce: true, // 强制分割
        reuseExistingChunk: true, // 重用已存在的代码块
      },
      // 提取页面公共代码
      commons: {
        name: 'commons', // 生成的代码块名称
        minChunks: 2, // 最少被引用次数
        priority: 0, // 优先级
        reuseExistingChunk: true, // 重用已存在的代码块
        chunks: 'initial', // 只提取初始化就能获取到的模块,不管异步的
        minSize: 0, // 提取代码体积大于0就提取出来
      },
    },
  },
}

实现HMR

HMR的有两种实现方式,一种是通过 devserver 配置和插件 HotModuleReplacementPlugin 实现,一种是通过在自定义开发服务下,使用插件webpack-dev-middlewarewebpack-Hot-middleware配合实现 HMR,本文演示后者实现方式。

  • webpack-dev-middleware

webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server),实现监听文件改动并重新编译。

webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。

  • webpack-Hot-middleware

是用来进行页面的热重载的,刷新浏览器

import express from 'express';
import path from 'node:path';
import { type Configuration, webpack } from 'webpack';
import devMiddleware from 'webpack-dev-middleware';
import hotMiddleware from 'webpack-hot-middleware';

const DEV_SERVER_CONFIG = {
  HOST: '127.0.0.1',
  PROP: 9002,
  HMR_PATH: '__webpack_hmr',
  TIMEOUT: 20000,
};


// 获取当前app根目录
const basePath = path.resolve(__dirname, '../');

// 合并配置
const webpackDevConfig = merge(baseConfig, devConfig);

// 启动服务器
const app = express();

// webpack 配置
const compiler = webpack(webpackDevConfig);

// 指定静态目录
app.use(express.static(path.resolve(__dirname, '../public/dist')));

// 运用 webpack-dev-middleware 中间件,监听文件改动并重新编译
app.use(devMiddleware(compiler, {
  // 落地文件, 其他文件打包到内存,入口文件写入磁盘
  writeToDisk: filePath => filePath.endsWith('.tpl'),

  // 资源文件路径
  publicPath: webpackDevConfig.output?.publicPath,
  // headers
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
    'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
  },
  // 输出日志
  stats: {
    colors: true,
  },
}));

// 运用 webpack-hot-middleware 中间件,实现热更新
app.use(hotMiddleware(compiler, {
  path: `/${DEV_SERVER_CONFIG.HMR_PATH}`,
  log: () => { },
}));

console.info('构建中...');

const { PROP } = DEV_SERVER_CONFIG;
app.listen(PROP, () => {
  console.log('app listening on port', PROP);
});

dy:哲玄前端(全栈)- 哲玄课堂《大前端全栈实现》