对webpack工程化的理解

webpack是什么?

webpack是一个现代的前端打包工具,用于构建和优化Web应用程序的前端资源,包括js,css,图片,字体等。它的主要目标试讲所有依赖项打包到一个或多个静态文件中,以便在浏览器中加载,提高了代码的可维护性和性能。下面是我对webpack配置的一些理解。

入口配置entry

首先关键就是需要配置入口文件,例如我有两个入口文件,entry.page1和entry.page2,则需要进行如下配置:

js 复制代码
// 入口配置
  entry: {
      'entry.page1': '/app/pages/page1/entry.page1.js' // 文件路径
      'entry.page2': '/app/pages/page1/entry.page1.js'
  },

同时需要配置htmlWebpackPlugin如下:

js 复制代码
// html-webpack-plugin 辅助注入打包后的 bundle 文件到 tpl文件中
   new HtmlWebpackPlugin({
      // 产物(最终输出路径)
      filename: path.resolve(
        process.cwd(),
        "./app/public/dist",
        `entry.page1.tpl`
      ),
      // 指定要使用的模板文件
      template: path.resolve(process.cwd(), "./app/view/entry.tpl"),
      // 要注入的代码块
      chunks: ['entry.page1'],
    })
    new HtmlWebpackPlugin({
      // 产物(最终输出路径)
      filename: path.resolve(
        process.cwd(),
        "./app/public/dist",
        `entry.page2.tpl`
      ),
      // 指定要使用的模板文件
      template: path.resolve(process.cwd(), "./app/view/entry.tpl"),
      // 要注入的代码块
      chunks: ['entry.page2'],
    })

通过上面可以看出这样配置显然不太合理,如果有多个入口文件,难道要一个一个去写吗?所以这时针对多文件就需要来动态构造,具体实现如下:

js 复制代码
// 动态构造
const pageEntries = {};
const htmlWebpackPluginList = [];

// 获取app/pages 目录下所有入口文件(entry.xxx.js)
const entryList = path.resolve(process.cwd(), `./app/pages/**/entry.*.js`);
glob.sync(entryList).forEach((file) => {
  const entryName = path.basename(file, ".js");
  // 构造entry
  pageEntries[entryName] = file;
  // 构造最终渲染的页面文件
  htmlWebpackPluginList.push(
    // html-webpack-plugin 辅助注入打包后的 bundle 文件到 tpl文件中
    new HtmlWebpackPlugin({
      // 产物(最终输出路径)
      filename: path.resolve(
        process.cwd(),
        "./app/public/dist",
        `${entryName}.tpl`
      ),
      // 指定要使用的模板文件
      template: path.resolve(process.cwd(), "./app/view/entry.tpl"),
      // 要注入的代码块
      chunks: [entryName],
    })
  );
});

通过这种方法不管有多少入口文件,就不需要每个都去配置,直接使用 pageEntrieshtmlWebpackPluginList 即可,大大的提高了我们的开发效率。

分包策略

好的分包策略目的是把改动和引用频率不一样的js区分出来,以达到更好利用浏览器缓存的效果。大体上可以分为三类,第一种是第三方lib库,在开发过程中基本不会进行改动,除非依赖版本升级;第二种是业务组件代码的公共部分,改动较少;第三种就是我们的业务代码需要经常改动。具体分包方式如下:

js 复制代码
// 配置打包输出优化(代码分割,模块合并,缓存,TreeShaking,压缩等优化策略)
  optimization: {
    /**
     * 把js文件打包成3种类型
     * 1.vendor: 第三方 lib 库,基本不会改动,除非依赖版本升级
     * 2.common: 业务组件代码的公共部分抽离出来,改动较少
     * 3.entry.{page}: 不同页面entry里的业务组件代码的差异部分,会经常改动
     * 目的:把改动和引用频率不一样的js区分出来,以达到更好利用浏览器缓存的效果
     */
    splitChunks: {
      chunks: "all", // 对同步和异步模块都进行分割
      maxAsyncRequests: 10, // 每次异步加载的最大并行请求数
      maxInitialRequests: 10, // 入口点的最大并行请求数
      cacheGroups: {
        // 第三方依赖库
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendor", // 模块名称
          priority: 20, // 优先级,数字越大优先级越高
          enforce: true, // 强制执行
          reuseExistingChunk: true, // 如果当前 chunk 包含的模块已经被抽取出去了,那么它将被忽略
        },
        // 公共模块
        common: {
          name: "common",
          minChunks: 2, // 最小引用次数
          minSize: 1, // 最小引用字节
          priority: 10, // 优先级,数字越大优先级越高
          reuseExistingChunk: true, // 复用已有的公共chunk
        },
      },
    },
    runtimeChunk: true,
  },

生产环境和开发环境的不同配置

生产环境

生产环境是最终需要打包发布上线的,所以通常需要一些额外的配置以达到更好的效果。

output(输出配置)

js 复制代码
// 生产环境的output配置
  output: {
    filename: "js/[name]_[chunkhash:8].bundle.js",
    path: path.join(process.cwd(), "./app/public/dist/prod"),
    publicPath: "/dist/prod",
    crossOriginLoading: "anonymous",
  },

多线程打包配置

这里使用的happypack,还有另一种方式thread-loader也可完成。

js 复制代码
const HappyPack = require("happypack");
const os = require("os");

// 多线程build设置
const happypackCommonConfig = {
  debug: false,
  threadPool: HappyPack.ThreadPool({ size: os.cpus().length }),
};

plugins: [
// 多线程打包js,加快打包速度
   new HappyPack({
      ...happypackCommonConfig,
      id: "js",
      loaders: [
        `babel-loader?${JSON.stringify({
          presets: ["@babel/preset-env"],
          plugins: ["@babel/plugin-transform-runtime"],
        })}`,
      ],
    }),
// 多线程打包css,加快打包速度
   new HappyPack({
      ...happypackCommonConfig,
      id: "css",
      loaders: [
        {
          path: "css-loader",
          options: {
            importLoaders: 1,
          },
        },
      ],
    }),
]

配置TerserWebpackPlugin

使用TerserWebpackPlugin的并发和缓存,提升压缩阶段的性能,同时清除console.log的打印信息。

js 复制代码
const TerserWebpackPlugin = require("terser-webpack-plugin");
optimization: {
    // 使用TerserWebpackPlugin的并发和缓存,提升压缩阶段的性能
    // 清除console.log
    minimize: true,
    minimizer: [
      new TerserWebpackPlugin({
        cache: true, // 启用缓存来加速构建过程
        parallel: true, // 利用多核CPU的优势来加快压缩速度
        extractComments: false,
        terserOptions: {
          compress: {
            drop_console: true, // 删除console.log
          },
        },
      }),
    ],
  },

开发环境

开发环境为了提高开发效率需要配置热更新插件,以便在业务文件改动时可以实时的更新页面。主要流程就是通过一个devServer中间件,可以监控到业务文件的改动,并通知浏览器进行更新,总结来说就是需要拥有监控文件改动和通知页面更新的能力。具体实现方式如下:

js 复制代码
// webpack.dev.js
const baseConfig = require("./webpack.base.js");
const merge = require("webpack-merge");
const webpack = require("webpack");
const path = require("path");

// devServer的配置
const DEV_SERVER_CONFIG = {
  HOST: "127.0.0.1",
  PORT: 9002,
  HMR_PATH: "__webpack_hmr", // 官方规定
  TIMEOUT: 20000,
};
const { HOST, PORT, HMR_PATH, TIMEOUT } = DEV_SERVER_CONFIG;

// 开发阶段的 entry 配置需要加入 hmr
Object.keys(baseConfig.entry).forEach((key) => {
  // 第三方包不作为hmr入口
  if (key !== "vendor") {
    baseConfig.entry[key] = [
      // 主入口文件
      baseConfig.entry[key],
      // hmr 更新入口,官方指定的 hmr 路径
      `webpack-hot-middleware/client?path=http://${HOST}:${PORT}/${HMR_PATH}&timeout=${TIMEOUT}&reload=true`,
    ];
  }
});

// 开发环境webpack配置
const webpackConfig = merge.smart(baseConfig, {
  mode: "development", // 指定开发环境
  // sourceMap 开发工具,呈现代码的映射关系,便于在开发过程中调试代码
  devtool: "eval-cheap-module-source-map",
  // 开发环境的output配置
  output: {
    filename: "js/[name]_[chunkhash:8].js",
    path: path.resolve(process.cwd(), "./app/public/dist/dev/"), // 输出文件存储路径
    publicPath: `http://${HOST}:${PORT}/public/dist/dev/`, // 外部资源公共路径
    globalObject: "this", // 全局变量
  },
  // 开发阶段插件
  plugins: [
    // 模块热替换允许在应用程序运行时替换,提高开发效率
    new webpack.HotModuleReplacementPlugin({
      multiStep: false,
    }), // 热更新插件
  ],
});

module.exports = {
  webpackConfig,
  // devServer 配置,给dev.js 使用
  DEV_SERVER_CONFIG,
};

dev.js

js 复制代码
// 本地开发启动 devServer
const express = require("express");
const path = require("path");
const webpack = require("webpack");
const devMiddleware = require("webpack-dev-middleware");
const hotMiddleware = require("webpack-hot-middleware");
const consoler = require("consoler");

// 从webpack.dev.js获取配置
const { webpackConfig, DEV_SERVER_CONFIG } = require("./config/webpack.dev.js");

const app = express();
const compiler = webpack(webpackConfig);

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

// 引用devMiddleware中间件(监控文件改动)
app.use(
  devMiddleware(compiler, {
    // 落地文件
    writeToDisk: (filePath) => filePath.endsWith(".tpl"),
    // 资源路径
    publicPath: webpackConfig.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,
    },
  })
);

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

consoler("请等待webpack初次构建完成提示....");

// 启动 devServer
const port = DEV_SERVER_CONFIG.PORT;
app.listen(port, () => {
  console.log(`app listening on port ${port}`);
});

可以看到我们使用了 HotModuleReplacementPlugin 热替换插件允许在应用程序运行时替换,再借助 devMiddleware 监控文件改动,以及hotMiddleware 实现热更新通讯,同时也配置了sourceMap开发工具,便于在开发过程中调试代码。

总结

以上就是生产环境和开发环境的一些不同配置,当然webpack还有许多其他的配置这里没有一一列出,例如一些loader,处理vue文件的vue-loader,处理css文件的css-loader等,大家可以根据自己需要进行各种不一样的配置。通过这一章节对webpack的学习,让我更加清晰的了解了工程化相关知识,对项目中需要配置哪些东西也有了更深的理解,重要的是了解了一些主要思想,以后再使用其他工具如vite,rollup等也能够很快上手啦。fighting~

引用:抖音"哲玄前端"《大前端全栈实践》

相关推荐
355984268550557 小时前
医保服务平台 Webpack逆向
前端·webpack·node.js
不能只会打代码10 小时前
六十天前端强化训练之第三十一天之Webpack 基础配置 大师级讲解(接下来几天给大家讲讲工具链与工程化)
前端·webpack·node.js
大专哥11 小时前
基于vite官方开源脚手架预设,实现一个 npm create template-vue3-ts-preset(2):分析入口文件
webpack·开源·源码
ak啊17 小时前
Webpack 构建阶段:模块解析流程
前端·webpack·源码
前端与小赵18 小时前
webpack和vite之间的区别
前端·webpack·vite
Moment1 天前
从 Webpack 源码来深入学习 Tree Shaking 实现原理 🤗🤗🤗
前端·javascript·webpack
henujolly1 天前
优化webpack打包体积思路
webpack
EricXJ1 天前
为什么选择 tsup?
前端·webpack·typescript
SuperherRo1 天前
Web开发-JS应用&WebPack构建&打包Mode&映射DevTool&源码泄漏&识别还原
前端·javascript·webpack·源码泄露·识别还原
小浣熊喜欢揍臭臭1 天前
webpack配置详解+项目实战
前端·webpack·node.js