对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],
    })
  );
});

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

分包策略

好的分包策略目的是把改动和引用频率不一样的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~

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

相关推荐
liangshanbo121517 小时前
Webpack vs Vite 底层原理高频追问的问题
前端·webpack·node.js
liangshanbo121519 小时前
面试题:AI 对话上下文超限怎么办?
面试·webpack·devops
liangshanbo12152 天前
Webpack 文件指纹面试题整理
前端·webpack·node.js
liangshanbo12152 天前
Webpack 常见 Plugin 面试题
前端·webpack·node.js
liangshanbo12152 天前
Webpack Tree Shaking 面试题整理
前端·webpack·node.js
陪我一起学编程2 天前
vue3笔记
前端·webpack·vue3·vite·脚手架·vue3笔记·组件式
liangshanbo12152 天前
Webpack的分包策略面试题
前端·webpack·node.js
liangshanbo12153 天前
面试题:如何优化 Webpack 的打包速度?
前端·webpack·node.js
晚风醉蝶5 天前
webpack 模块提取
前端·webpack·node.js·ast·逆向分析
晚风醉蝶15 天前
保姆级教程:用 AST 自动提取 webpack / rspack 打包后的模块
webpack·web逆向