基于 Koa 二次封装的前端BFF应用程序(二)

177 阅读5分钟

前端工程化设计与实现(WebPack)

前端工程化是一个老生常谈的话题,但大多都是讨论使用 webpack 还是 vite ,亦或者是 rspack 。其实工程化使用什么工具并不是主要的,而是了解工程化的意义,理解每一个配置是在做什么,这样即可一通百通,而工程化也不仅仅是项目的构建打包工具而已,还要包括Eslint、CI/CD等一系列的东西,但本文篇幅有限,仅介绍 webpack 相关配置与环境搭建。

为什么选择webpack

可能有小伙伴会问,vitewebpack 快很多啊,而且配置也简单,为啥不用 vite ,而还是选用 webpack 呢?还是像前面说的,使用什么工具不重要,重要的是理解概念,而 vite 封装程度比较高,配置简单,不太利于深入理解各项配置,因此选用 webpack 来介绍。

webpack 配置

entry

entry 是入口配置,主要配置页面的入口文件路径。

loader

loader 的作用是将不同的文件转义为浏览器可执行的文件,在 module.rules 中配置。

vue-loader

  1. 允许以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。
  2. 允许为 Vue 组件的每个部分使用其它的 webpack loader,例如在 <style> 的部分使用 Sass 和在 <template> 的部分使用 Pug。
  3. 为每个组件模拟出 scoped CSS。

需配合 VueLoaderPlugin 插件使用。

babel-loader

  1. 将 ES6 代码转义为 5 的代码,有更好的兼容性。
  2. 可以使用 @babel/preset-typescript ,并配置 allExtensions: true来支持解析TS文件与 vue 文件中的 TS 代码。

css-loader

css-loader 会对 @import 和 url() 进行处理,就像 js 解析 import/require() 一样。

需与 style-loaderMiniCssExtractPlugin.loader 一起使用

style-loader

使用多个 <style></style> 将 CSS 插入到 DOM 中,通常在开发环境中与 css-loader 一起使用。

MiniCssExtractPlugin.loader

提取 CSS,创建单独的 css 文件,以便以后能够使用 CSS/JS 资源的并行加载。通常在生产环境中与 css-loader 一起使用。

sass-loader

加载 Sass/SCSS 文件并将他们编译为 CSS。

thread-loader

开启多进程打包,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右。同时会限制跨进程的数据交换。应仅在耗时的操作中使用此 loader

webpack5资源模块

webpack5 提供了资源模块,使用type: 'asset',将以往需要使用对应 loader 处理的静态资源文件改为使用资源模块来处理:

  1. asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
  2. asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
  3. asset 会根据文件的类型选择使用 asset/resourceasset/inline

output

output 配置产物输出的路径。

resolve

resolve 配置模块解析的具体行为(定义 webpack 在打包时,如何找到并解析具体模块的路径)如:@ 等快捷路径。

plugins

plugins 配置配置 webpack 插件,通过社区丰富的插件来强化 webpack 的能力,如 VueLoaderPlugin 处理 vue 文件、 CleanWebpackPlugin 每次 build 前, 自动清空 pubilc/dist 目录、 MiniCssExtractPlugin 提取 css 的公共部分,有效利用缓存、 CssMinimizerPlugin 优化并压缩 css 资源, AutoImport 自动引入 API , Components 自动引入组件等等。

optimization

optimization 配置打包输出优化 (代码分割、模块合并、缓存、压缩、Tree Shaking等)。

splitChunks

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

/**
     * 分包策略
     * 把 js 文件打包成3种类型
     * 1. 第三方库 node_modules 中的库
     * 2. 业务代码差异部分: 经常改动
     * 3. 公共代码 common: 业务组件代码抽离出的公共部分
     */
    splitChunks: {
      chunks: "all",  // 对同步和异步模块都切割
      maxAsyncRequests: 10, // 最大异步请求数
      maxInitialRequests: 10, // 最大初始请求数
      cacheGroups: {
        vendors: {  // 第三方库
          test: /[\\/]node_modules[\\/]/,  // 匹配 node_modules 目录下的文件
          name: "vendors",  // 指定名称
          priority: 20,  // 优先级
          enforce: true,  // 强制执行
          reuseExistingChunk: true,  // 如果模块已经存在,则不重新创建
        },
        common: {  // 公共代码
          name: "common",  // 指定名称
          priority: 10,  // 优先级
          minChunks: 2,  // 最小引用次数(被两处引用即为公共模块)
          minSize: 0,  // 最小切割大小
        },
      },
    },
    // 将 webpack 代码抽离成单独的文件 runtime.js
    runtimeChunk: true,

minimizer

使用 TerserPlugin 的并发和缓存, 提升压缩阶段的性能。

minimize: true,
minimizer: [
  new TerserPlugin({
    // 开启多进程并行压缩
    parallel: true,
    terserOptions: {
      compress: {
        // 删除代码中的 console.log 语句
        drop_console: true
      }
    }
  }),
],

实现HMR

HMR 即热更新,有两种实现方式,一种是通过 devserver 配置和插件 HotModuleReplacementPlugin 实现,一种是通过在自定义开发服务下,使用插件webpack-dev-middlewarewebpack-Hot-middleware配合实现 HMR。本篇介绍第二种方式,使用 express 搭建自定义开发服务器。

webpack-dev-middleware

webpack-dev-middleware 是一个容器wrapper ,它可以把 webpack 处理后的文件传递给一个服务器server ,实现监听文件改动并重新编译。 webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。

webpack-Hot-middleware

用来进行页面的热更新,刷新浏览器

示例代码

// 本地开发启动 devServe
import express from 'express';
import path from 'path';
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import { DEV_SERVER_CONFIG } from './config/hmr-config';
// 从 webpack.dev 中获取配置
import webpackDevConfig from './config/webpack.dev';

const app = express();

const { HOST, PORT, HMR_PATH } = DEV_SERVER_CONFIG

const compiler = webpack(webpackDevConfig);

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

// 引用 divMiddleware 中间件 (监控文件改动)
app.use(webpackDevMiddleware(compiler, {
  // 落地文件(不用打包)
  writeToDisk: (filePath) => {
    return filePath.endsWith('.tpl')
  },
  // 资源路径
  publicPath: webpackDevConfig.output!.publicPath!,
  // headers 配置(允许跨域)
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With',
  },
  // 日志输出
  stats: {
    colors: true,
  }
}));

// 引用 hotMiddleware 中间件 (实现热更新)
app.use(webpackHotMiddleware(compiler, {
  path: `/${HMR_PATH}`,
  log: () => {}
}));

console.log("构建中......");

// 启动 devServer
app.listen(PORT, HOST, () => {
  console.log(`Server is running on http://${HOST}:${PORT}`);
});