webpack学习

2024-05-28 13:39:31

概念

本质上,webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图,然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

loader

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。

在更高层面,在 webpack 的配置中,loader 有两个属性:

  1. test 属性,识别出哪些文件会被转换。
  2. use 属性,定义出在进行转换时,应该使用哪个 loader。

webpack.config.js

module.exports = {
  output: {
    filename: '[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.txt$/,
        use: 'raw-loader'
      }
    ]
  }
};

以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:testuse。这告诉 webpack 编译器(compiler) 如下信息:

“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。”

插件(plugin)

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建一个插件实例。

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
};

在上面的示例中,html-webpack-plugin 为应用程序生成一个 HTML 文件,并自动将生成的所有 bundle 注入到此文件中。

流程

一般来说,我们通过 npm run webpack 命令来启动 webpack。当我们在命令行运行以上命令后,npm 会让命令行工具进入 node_modules\.bin 目录查找是否存在 webpackwebpack.cmd 文件,如果存在就执行,不存在就抛出错误。

我们来看下 webpack.cmd 的内容:

@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0

IF EXIST "%dp0%\node.exe" (
  SET "_prog=%dp0%\node.exe"
) ELSE (
  SET "_prog=node"
  SET PATHEXT=%PATHEXT:;.JS;=;%
)

endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%\..\webpack\bin\webpack.js" %*

总体逻辑就是先判断当前目录下是否存在 node 执行文件,无论存不存在,最后都是使用 node 去执行 node_modules\webpack\bin\webpack.js

linux 下的启动脚本逻辑也基本一致。

首先 webpack.js 引用 node_modules\webpack-cli\bin\cli.js

然后 cli.js 引用 node_modules\webpack-cli\lib\bootstrap.js 并执行 runCLI 方法。

接着 runCLI 引用 node_modules\webpack-cli\lib\webpack-cli.js 并执行 run 方法。

async run(args, parseOptions) {
  ...
  const loadCommandByName = async (commandName, allowToInstall = false) => {
    const isBuildCommandUsed = isCommand(commandName, buildCommandOptions);
    const isWatchCommandUsed = isCommand(commandName, watchCommandOptions);
    if (isBuildCommandUsed || isWatchCommandUsed) {
      await this.makeCommand(isBuildCommandUsed ? buildCommandOptions : watchCommandOptions, async 
      () => {
        this.webpack = await this.loadWebpack();
        return this.getBuiltInOptions();
      }, async (entries, options) => {
        if (entries.length > 0) {
          options.entry = [...entries, ...(options.entry || [])];
        }
        await this.runWebpack(options, isWatchCommandUsed);
      });
    }
  };
  ...
  this.program.action(async (options, program) => {
    ...
    if (isKnownCommand(commandToRun)) {
      await loadCommandByName(commandToRun, true);
    }
    ...
    await this.program.parseAsync([commandToRun, ...commandOperands, ...unknown], {
      from: "user",
    });
  });
  await this.program.parseAsync(args, parseOptions);
}
const WEBPACK_PACKAGE_IS_CUSTOM = !!process.env.WEBPACK_PACKAGE;
const WEBPACK_PACKAGE = WEBPACK_PACKAGE_IS_CUSTOM
  ? process.env.WEBPACK_PACKAGE
  : "webpack";
async loadWebpack(handleError = true) {
  return this.tryRequireThenImport(WEBPACK_PACKAGE, handleError);
}
async runWebpack(options, isWatchCommand) {
  // eslint-disable-next-line prefer-const
  let compiler;
  ...
  compiler = await this.createCompiler(options, callback);
  if (!compiler) {
    return;
  }
  const isWatch = (compiler) => Boolean(this.isMultipleCompiler(compiler)
    ? compiler.compilers.some((compiler) => compiler.options.watch)
    : compiler.options.watch);
  if (isWatch(compiler) && this.needWatchStdin(compiler)) {
    process.stdin.on("end", () => {
      process.exit(0);
    });
    process.stdin.resume();
  }
}
async createCompiler(options, callback) {
  let config = await this.loadConfig(options);
  config = await this.buildConfig(config, options);
  let compiler;
  try {
    compiler = this.webpack(config.options, callback
      ? (error, stats) => {
        if (error && this.isValidationError(error)) {
          this.logger.error(error.message);
          process.exit(2);
        }
        callback(error, stats);
      }
      : callback);
    // @ts-expect-error error type assertion
  }
  catch (error) {
    if (this.isValidationError(error)) {
      this.logger.error(error.message);
    }
    else {
      this.logger.error(error);
    }
    process.exit(2);
  }
  return compiler;
}

webpack 入口文件 node_modules\webpack\lib\index.js

const fn = lazyFunction(() => require("./webpack"));
module.exports = mergeExports(fn, {
});

引用 node_modules\webpack\lib\webpack.js

const webpack = /** @type {WebpackFunctionSingle & WebpackFunctionMulti} */ (
  /**
   * @param {WebpackOptions | (ReadonlyArray<WebpackOptions> & MultiCompilerOptions)} options options
   * @param {Callback<Stats> & Callback<MultiStats>=} callback callback
   * @returns {Compiler | MultiCompiler | null} Compiler or MultiCompiler
   */
  (options, callback) => {
    const create = () => {
      if (!asArray(options).every(webpackOptionsSchemaCheck)) {
        getValidateSchema()(webpackOptionsSchema, options);
        util.deprecate(
          () => {},
          "webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.",
          "DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID"
        )();
      }
      /** @type {MultiCompiler|Compiler} */
      let compiler;
      /** @type {boolean | undefined} */
      let watch = false;
      /** @type {WatchOptions|WatchOptions[]} */
      let watchOptions;
      if (Array.isArray(options)) {
        /** @type {MultiCompiler} */
        compiler = createMultiCompiler(
          options,
          /** @type {MultiCompilerOptions} */ (options)
        );
        watch = options.some(options => options.watch);
        watchOptions = options.map(options => options.watchOptions || {});
      } else {
        const webpackOptions = /** @type {WebpackOptions} */ (options);
        /** @type {Compiler} */
        compiler = createCompiler(webpackOptions);
        watch = webpackOptions.watch;
        watchOptions = webpackOptions.watchOptions || {};
      }
      return { compiler, watch, watchOptions };
    };
    if (callback) {
      try {
        const { compiler, watch, watchOptions } = create();
        if (watch) {
          compiler.watch(watchOptions, callback);
        } else {
          compiler.run((err, stats) => {
            compiler.close(err2 => {
              callback(
                err || err2,
                /** @type {options extends WebpackOptions ? Stats : MultiStats} */
                (stats)
              );
            });
          });
        }
        return compiler;
      } catch (err) {
        process.nextTick(() => callback(/** @type {Error} */ (err)));
        return null;
      }
    } else {
      const { compiler, watch } = create();
      if (watch) {
        util.deprecate(
          () => {},
          "A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.",
          "DEP_WEBPACK_WATCH_WITHOUT_CALLBACK"
        )();
      }
      return compiler;
    }
  }
);
表情
Ctrl + Enter