惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

win or mac clash 无 TUN 让 Antigravity、Chrome 强制 proxy(解决 Antigravity 无法加载选择 model、自动更新无法登录、跳转) 【大杂烩】在 pnpm 中直接修改 node_modules(.pnpm) 中的依赖项,项目中持久化 - pnpm 中的依赖处理、幽灵依赖、寻址规则等 在 html 中直接使用 Esm、Jsx 脚本快速调试和使用 React@19 和 Vue@3 源码,解决 React19 UMD 构建等问题 一键在本地批量检测并升级更新 package.json 中的模块依赖,ncu(npm-check-updates)在 npm、pnpm 或 workspace 项目中的使用教程 解决 Mac Docker Desktop 中启动出现的问题合集 通过阿里云、腾讯云无服务器搭建自定义的企业域名邮箱,实现在 QQ邮箱 收发等功能(附腾讯 SMTP 和 IMAP) 解决使用代理(clash 等)进行 SSH 连接(如 Github ssh key clone/push)出现 kex_exchange_identification 错误 静态文件资源 cdnjs, jsdelivr 抖音字节国内快速 CDN 镜像推荐【2025】- 仍在使用 bootcdn 和 staticfile CDN 请注意验证资源的完整性(SRI) pnpm monorepo 中管理依赖的最佳实践,与 Catalogs(目录)协议的使用(monorepo 中统一版本管理) Web 安全中的 Secure Contexts(安全上下文)- 解决在本地中使用 clipboard 或 Crypto 等 API 限制或关闭上下文限制 使用 serve 配合 openssl 或 mkcert 创建本地自签名可信任的证书 - 创建本地 TLS\SSL https 协议服务 利用 Github Actions 和 Acme 自动申请、更新和部署至阿里云、腾讯云 CDN Lets Encrypt SSl\TLS ECC RSA 双证书 【CSS】解决在 flex 容器中使用 align-content 或 justify-content 属性 center 居中时的溢出滚动和截断问题 - 理解 safe 关键字 在线工具 - 一键获取下载抖音无水印视频、抖音去水印解析工具、下载抖音无水印高清图集【2025 最新】 【React Router】v6 data router 在非组件(或工具方法)中如何优雅的跳转路由 【React】为什么路由跳转时页面滚动高度不会被重置(保留上个页面高度)?理解 history scrollRestoration 的场景与使用,以及如何使用 React Router 重置和跳转前保留滚动高度 【React】在本地 Html 中快速 debug(调试)React 源码 【React】结合源码和 EventLoop 分析 - 为什么 useLayoutEffect 会阻止 DOM 重绘(而 useEffect 闪烁)?为什么其内部 useState 会“同步”执行? React Compiler - 解放在函数中编程时的性能焦虑(React Conf 2024)附 Next 在线演示 在 github actions 中获取时间,并转换为中国标准时间(中国时区) 【npm】npm ci - npm clean install,在 CI、CD 中保持构建的一致性和可重复性 eslint 9.x 升级或使用指南,eslint.config.js 配置,包含 react、typescript、prettier 等常用配置升级迁移 使用 Spicetify 自定义 Spotify - 歌词翻译、全屏展示、主题替换 在 node 中快速代理请求(Proxy),解决跨域或请求转发问题 - http-proxy-middleware 修改请求体和返回 在 Hexo 中使用 AI(Gemini、deepseek、Azure)生成文章摘要,支持自定义模板。hexo-ai-summaries 插件文档(默认适配主题 Butterfly) 解决在 webstorm 或 idea 等 jetbrains 工具中遇到 Git 无法 force push,或 force push 灰色禁用无法点击(protected branches) hexo-seo-submit,Hexo 博客 SEO 优化插件 - 每日定时自动或手动提交链接至百度、Bing、Google,支持 Github Actions 和 Coding Jenkins 等CI(Hexo 插件编写) 一文吃透 pnpm 如何使用 workspace 构建 monorepo,与 npm、yarn 的用法对比(pnpm 9.x 内部安装依赖问题 link-workspace-packages) 【Node】Corepack - 解决 pnpm 或 yarn 的多版本管理、解决本地版本与 packageManager 中的版本一致性问题 解决 npm、cnpm 或 pnpm install 遇到 certificate has expired (证书已过期) npm、yarn、pnpm 设置最新国内镜像源(附官方镜像源和最新阿里源),以及 nrm 的使用教程【2025】 解决从 docker desktop 内镜像 linux 创建的容器,启动就停止、无法启动等问题(无进程容器) docker(docker desktop)中设置国内镜像源加速(阿里云、中科大),以及代理和容器代理设置,解决桌面端无法登陆就退出问题 【CSS】解决外边距重叠(重合)引起的 margin 垂直方向(top、bottom)不生效,无作用的问题 【CSS】CSS-Nesting:CSS 嵌套写法 —— 有望替代 less sass 的原生嵌套 【CSS】解决移动端(高清方案)下在谷歌浏览器中出现 字体大小布局异常,和设置的 font-size 不符(Text Autosizer、Font Boosting) 【CSS】主流 UI 库都在用的逻辑伪类选择器 not、where、is、has 【windows 11】使用 wePe 纯净 制作pe启动盘 安装\重装\升级 windows 11(windows 10)详细教程,附 windows 11 跳过联网、分区等教程 【Windows】一句话(一键、一分钟、一段代码)清除 Windows 11(10) 快捷方式角标(小箭头) 【Git】cherry-pick 使用场景介绍,如何在 WebStorm 中使用 cherry-pick;(从分支中提取 commit 功能) 【message-channel】了解频繁出现在框架的 Message Channel,及在事件循环(Event Loop)中的表现 【web-worker】浅析 useWorker 库如何只需函数方法即可在 worker 内运行;如何区分 Web Socket、Web Worker和Service Worker? 【React】理解学习 React 17中的批处理 与 18 批处理 为何用谷歌(Chrome)浏览器下载PDF时有时预览有时下载? 【less】Parent Selectors & 和 &&(多个父选择器的用法及回顾) 【React Router】在非组件(或工具方法)中如何优雅的跳转路由 - 理解 HashHistory 和 BrowserHistory 处理谷歌浏览器(google Chrome)https 站点将 http 资源自动转成 https - 附全站资源强制转 https vscode 国内直链接下载,提升(加速)下载速度 【css-tricks】从 flex 子元素超出父级(容器)宽度,探其原理 flex:1 时 width 的作用 linux,mac 终端(Terminal)上使用代理(http/https/socks5)提升速度,给 git 一键设置代理提升速度 修复 Win 11或10 Xbox Game Bar 录屏截屏打不开灰色等
【webpack】Externals(外部扩展)浅析 - webpack 5
kshao · 2023-05-25 · via

本篇主要介绍 webpack 中的 externals 如何使用,顺带浅析一下 webpack 内部实现。

对于 Externals 就不过多描述了,蹭下热度,把这个问题交给 AI 小助手。

使用 Externals

项目目录

在 codesandbox 中预览项目,run build or build-externals

|--pages/
|   |--A.js
|--App.js
|--index.js
|--webpack.config.js
// Pages/A.js
import React from "react";
import { get } from "lodash";
import { Button } from "antd";

const A = () => {
  const obj = { a: 1 };

  return (
    <div>
      <Button
        onClick={() => {
          console.log(get(obj, "a"), "use loadsh-es");
        }}
      >
        click
      </Button>
    </div>
  );
};

export default A;
// webpack.config.js
{
	externals: {
    lodash: "_",
  },
}
<!-- public/index.html -->
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

产物对比

机器配置

产物分析

[{"url":"https://static.ksh7.com/post/webpack-externals/0085UwQ9gy1heagl58mc6j30zk0kcnic.webp?imageMogr2/thumbnail/!50p","dataset":{"originPic":"https://static.ksh7.com/post/webpack-externals/0085UwQ9gy1heagl58mc6j30zk0kcnic.webp","thumbnail":""}},{"url":"https://static.ksh7.com/post/webpack-externals/0085UwQ9gy1heagl3e932j30zk0kb7q5.webp?imageMogr2/thumbnail/!50p","dataset":{"originPic":"https://static.ksh7.com/post/webpack-externals/0085UwQ9gy1heagl3e932j30zk0kb7q5.webp","thumbnail":""}}]

速度对比

[{"url":"https://static.ksh7.com/post/webpack-externals/0085UwQ9gy1heagl45hc3j30q4160qmy.webp?imageMogr2/thumbnail/!50p","dataset":{"originPic":"https://static.ksh7.com/post/webpack-externals/0085UwQ9gy1heagl45hc3j30q4160qmy.webp","thumbnail":""}},{"url":"https://static.ksh7.com/post/webpack-externals/0085UwQ9gy1heagl0fzd6j30rg19g4pw.webp?imageMogr2/thumbnail/!50p","dataset":{"originPic":"https://static.ksh7.com/post/webpack-externals/0085UwQ9gy1heagl0fzd6j30rg19g4pw.webp","thumbnail":""}}]

API 介绍

// webpack.config.js
{
  externalsType: 'var'
  externals: {
	// key is the module name, value is the global variable name
    "module-name": "global-variable-name"
  }
}

externalsType - 模块类型

模块类型,例如 amd、module(esm)。默认 var,从 全局 当中取变量,具体看模块资源 export 的方式,其他类型可在 externals 中自定义。

// pseudocode
// 编译后 - externalsType: 'var'
const jq = $;
jq('.my-element').animate(/* ... */);

externals

externals 支持多种类型,这里简单介绍对象方式的配置

externals: {
  // key is the module name, value is the global variable name
  "module-name": "global-variable-name",
  "lodash": "_",
}

key => module-name

webpack 编译时会替换 引入模块 路径中匹配的 module-name,将其替换为 global-variable-name

value => global-variable-name

模块资源中暴露的变量名称,一般资源文件在结尾处都会导出,例如 lodash 编译后的 lodash.min.js 中(或 lodash.js,此类文件未经压缩方便查看),正常情况下 变量名称 和 包名一致。

CDN / 资源文件

externals 配置的是全局变量名称,所以需要我们在 index.html 文件中引入相关资源来确保变量存在,若资源有依赖性需要确保引入的顺序

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

Features:

externalsType = script
写法1:
// webpack.config.js 
externalsType: 'script',
externals: {
  "module-name": ["library", "global-variable-name"],
  react: ['https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js', 'React'],
  'react-dom': ['https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.profiling.min.js', 'ReactDOM'],
  lodash: ['https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js', '_'],
},
写法2:

string 语法类似,你可以使用${externalsType} ${libraryName}语法在数组的第一项中指定外部库类型,例如:

// webpack.config.js 
externals: {
  "module-name": ["${externalsType} ${libraryName}", "global-variable-name"],
  react: ['script https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js', 'React'],
  'react-dom': ['script https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.profiling.min.js', 'ReactDOM'],
  lodash: ['script https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js', '_'],
}

lodash 只会在 visible 为 true 时引入资源。

// lodash in test
const Test = React.lazy(() => import(/* webpackChunkName: "test" */'./Test'));

<React.Suspense fallback={<div>loading...</div>}>
  {this.state.visible && <Test/>}
</React.Suspense>

把玩链接,run start-externals

externalsType = module

当 type 为 module 时,此时我们可以为所欲为了,直接使用 esm,不需要考虑各个模块的变量直接引入资源即可,确保资源为 esm 导出的即可。

// webpack.config.js
externals: {
  react: 'https://cdn.jsdelivr.net/npm/react@18.2.0/+esm',
  'react-dom': 'https://cdn.jsdelivr.net/npm/react-dom@18.2.0/+esm',
  lodash: "https://cdn.jsdelivr.net/npm/lodash@4.17.21/+esm",
}
externalsType: "module",
experiments: {
  outputModule: true,
},
import * as __WEBPACK_EXTERNAL_MODULE_lodash__ from 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/+esm';

const lodash = __WEBPACK_EXTERNAL_MODULE_jquery__['default'];
lodash.xx

需要注意的是 module 为实验性的 不建议生产使用!

浅析

externals 是如何替换模块的?

在配置了 Externals 时,webpack 会注册 ExternalsPlugin,插件中会在 webpack 提供的钩子内订阅事件(normalModuleFactory.hooks.factorize),并在 callback 中重定义了当前 module 的代码生成逻辑,跳过常规模块的 resolve 和 module create 等流程。

Webpack 大致流程

Externals 大致流程

Source code

ExternalsPlugin

class ExternalsPlugin {
  // ...
  apply(compiler): {
    compiler.hooks.compile.tap("ExternalsPlugin", ({ normalModuleFactory }) => {
			new ExternalModuleFactoryPlugin(this.type, this.externals).apply(
				normalModuleFactory
			);
		});
  }
}

ExternalModuleFactoryPlugin

在 normalModuleFactory.hooks.factorize 钩子中判断当前模块是否为扩展模块

class ExternalModuleFactoryPlugin {
  // ...
  apply(normalModuleFactory) {
    // ...
    /**
     * 订阅 normalModuleFactory.hooks.factorize 后,会将重新定义后的 module callback 透传下去
    */
    normalModuleFactory.hooks.factorize.tapAsync(
      "ExternalModuleFactoryPlugin",
      (resolveData, callback) => {
        const dependency = resolveData.dependencies[0];
        // ...
        
        if (typeof externals === "object") {
         	// ...
          /**
           * resolvedExternals: config 内的 externals 配置
           * request: request 一般为 import 的路径,例:import lodash from "lodash/get"; request: 'lodash/get'
          */
          if (
            Object.prototype.hasOwnProperty.call(
              resolvedExternals,
              dependency.request
            )
          ) {
            callback(
              null,
              new ExternalModule(
                externalConfig,
                type || globalType,
                dependency.request
              )
            );
          } 
        }
      }
    )
  }
}

ExternalModule

重新定义当前 module 的构建和代码生成等(以 ExternalsType = script 为例)

class ExternalModule extends Module {
  // ...
  
  codeGeneration({
    runtimeTemplate,
    moduleGraph,
    chunkGraph,
    runtime,
    concatenationScope
  }) {
		const { request, externalType } = this._getRequestAndExternalType();
		switch (externalType) {
			default: {
        // 根据 ExternalsType 生成对应模版代码
				const sourceData = this._getSourceData(
					request,
					externalType,
					runtimeTemplate,
					moduleGraph,
					chunkGraph,
					runtime
				);

				let sourceString = sourceData.expression;
				// ...
				if (sourceData.init)
					sourceString = `${sourceData.init}\n${sourceString}`;

				let data = undefined;
				if (sourceData.chunkInitFragments) {
					data = new Map();
					data.set("chunkInitFragments", sourceData.chunkInitFragments);
				}

				const sources = new Map();
				if (this.useSourceMap || this.useSimpleSourceMap) {
					sources.set(
						"javascript",
						new OriginalSource(sourceString, this.identifier())
					);
				} else {
					sources.set("javascript", new RawSource(sourceString));
				}

				let runtimeRequirements = sourceData.runtimeRequirements;
				if (!concatenationScope) {
					if (!runtimeRequirements) {
						runtimeRequirements = RUNTIME_REQUIREMENTS;
					} else {
						const set = new Set(runtimeRequirements);
						// "module"
						set.add(RuntimeGlobals.module);
						runtimeRequirements = set;
					}
				}

				return {
					sources,
					runtimeRequirements:
						runtimeRequirements || EMPTY_RUNTIME_REQUIREMENTS,
					data
				};
			}
		}
	}
  
  build(options, compilation, resolver, fs, callback) {
		this.buildMeta = {
			async: false,
			exportsType: undefined
		};
		this.buildInfo = {
			strict: true,
			topLevelDeclarations: new Set(),
			module: compilation.outputOptions.module
		};
		const { request, externalType } = this._getRequestAndExternalType();
		this.buildMeta.exportsType = "dynamic";
		let canMangle = false;
		this.clearDependenciesAndBlocks();
    // ...
		switch (externalType) {
      // ....
			case "script":
			case "promise":
				this.buildMeta.async = true;
				break;
		}
		this.addDependency(new StaticExportsDependency(true, canMangle));
		callback();
	}
  
  // ...
}

在构建时根据 Type 修改对应 module 实例 build 属性,例如 script 为异步加载所以添加 buildMeta.async 为 true,编译时则会对该模块在引入部分代码做修改。
在生成阶段(seal)针对不同 Type 生成不同的运行时代码

type 为 script 时的 Module 代码生成逻辑。

// ExternalModule
const getSourceForScriptExternal = (urlAndGlobal, runtimeTemplate) => {
	if (typeof urlAndGlobal === "string") {
		urlAndGlobal = extractUrlAndGlobal(urlAndGlobal);
	}
	const url = urlAndGlobal[0];
	const globalName = urlAndGlobal[1];
	return {
		init: "var __webpack_error__ = new Error();",
		expression: `new Promise(${runtimeTemplate.basicFunction(
			"resolve, reject",
			[
				`if(typeof ${globalName} !== "undefined") return resolve();`,
				`${RuntimeGlobals.loadScript}(${JSON.stringify(
					url
				)}, ${runtimeTemplate.basicFunction("event", [
					`if(typeof ${globalName} !== "undefined") return resolve();`,
					"var errorType = event && (event.type === 'load' ? 'missing' : event.type);",
					"var realSrc = event && event.target && event.target.src;",
					"__webpack_error__.message = 'Loading script failed.\\n(' + errorType + ': ' + realSrc + ')';",
					"__webpack_error__.name = 'ScriptExternalLoadError';",
					"__webpack_error__.type = errorType;",
					"__webpack_error__.request = realSrc;",
					"reject(__webpack_error__);"
				])}, ${JSON.stringify(globalName)});`
			]
		)}).then(${runtimeTemplate.returningFunction(
			`${globalName}${propertyAccess(urlAndGlobal, 2)}`
		)})`,
    // 在运行时代码内插入
    // RUNTIME_REQUIREMENTS_FOR_SCRIPT = __webpack_require__.l
    // __webpack_require__.l :基于 JSONP 实现的异步模块加载函数
		runtimeRequirements: RUNTIME_REQUIREMENTS_FOR_SCRIPT
	};
};
// 编译后
var __webpack_error__ = new Error();
module.exports = new Promise((resolve, reject) => {
	if(typeof _ !== "undefined") return resolve();
	__webpack_require__.l("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js", (event) => {
		if(typeof _ !== "undefined") return resolve();
		var errorType = event && (event.type === 'load' ? 'missing' : event.type);
		var realSrc = event && event.target && event.target.src;
		__webpack_error__.message = 'Loading script failed.\n(' + errorType + ': ' + realSrc + ')';
		__webpack_error__.name = 'ScriptExternalLoadError';
		__webpack_error__.type = errorType;
		__webpack_error__.request = realSrc;
		reject(__webpack_error__);
	}, "_");
  // rerutn Module 变量,通过 module.exports 暴露
}).then(() => (_));

当为 var 时

const getSourceForDefaultCase = (optional, request, runtimeTemplate) => {
	// ...
	const variableName = request[0]; // 模块全局变量,例如 lodash 为 _
	const objectLookup = propertyAccess(request, 1);
	return {
		init: optional
			? checkExternalVariable(variableName, request.join("."), runtimeTemplate)
			: undefined,
    // _
		expression: `${variableName}${objectLookup}`
	};
};
/* 1 */ Module id
/*!********************!*\
  !*** external "_" ***!
  \********************/
/*! dynamic exports */
/*! exports [maybe provided (runtime-defined)] [no usage info] */
/*! runtime requirements: module */
/***/ ((module) => {
// type 为 var 时,Externals 只关注全局变量,所以需要确保变量的存在
module.exports = _;

/***/ })

NormalModule

构建流程

  1. 调用 handleModuleCreate,根据文件类型构建module子类
  2. 调用 loader-runner 仓库的runLoaders转译module内容,通常是从各类资源类型转译为 JavaScript 文本
  3. 调用 acorn 将 JS 文本解析为AST
  4. 遍历 AST,触发各种钩子
    a. 在HarmonyExportDependencyParserPlugin插件监听exportImportSpecifier钩子,解读 JS 文本对应的资源依赖
    b. 调用module对象的addDependency将依赖对象加入到module依赖列表中
  5. AST 遍历完毕后,调用module.handleParseResult处理模块依赖
  6. 对于module新增的依赖,调用handleModuleCreate,控制流回到第一步
  7. 所有依赖都解析完毕后,构建阶段结束
class NormalModule extends Module {
  _doBuild(..., callback) {
    // ...
    runLoaders(...);
  }
  
  // 在build时,会根据该文件匹配到的loader,执行runLoaders转换文件内容。
  // 根据 resolve 解析文件
  build(options, compilation, resolver, fs, callback) {  
    // ...
    return this._doBuild(...)
  }
}

默认情况下,模块会在 NormalModuleFactory 中的 factorize.tapAsync 来创建,由于其定义了 stage: 100,总会在其他事件完成后执行,若其他订阅事件的 callback 带参返回则会终止发布事件(tapable.tapAsync),跳过常规模块创建流程(NormalModule)。

// NormalModuleFactory.js
class NormalModuleFactory extends ModuleFactory {
  // ...
  constructor() {
    // ...
    this.hooks.factorize.tapAsync(
      {
        name: "NormalModuleFactory",
        // 执行权重,优先执行其他 factorize
				stage: 100
      },
      () => {
        // ...
        this.hooks.resolve.callAsync(..., () => {
          createdModule = new NormalModule(
            /** @type {NormalModuleCreateData} */ (createData)
          );
        })
      }
    )
  }
  
  // ...
  
  create() {
    // ...
    this.hooks.factorize.callAsync(
      ...,
      (err, module) => {
      // ...
      const factoryResult = {
        module,
        // ...
      };
      callback(null, factoryResult);
    })
  }
}
// Compilation.js
class Compilation {
  // ...
  _factorizeModule({ factory }) {
    // ...
    
    factory.create({...}, (err, result) => {
      // ...
      callback(null, factoryResult ? result : result.module);
    })
    
    // ...
  }
}

externals 和 alias 之间有影响吗?

‒ 不会,从执行顺序上看 externals 在 normalModuleFactory.hooks.factorize 中执行,时机会早于 resolve,此时 alias 后的路径在 resolveData.creatData(afterResolve) 中,将在创建 module 时将其带入。
‒ 在创建 module 之前的阶段(resolve 等)中对 模块 路径(request)的判断应遵循代码,之后(after afterResolve)遵循模块真实路径。

Resolve 阶段

import lodash from "我被Alias了";

console.log(lodash);
// webpack.config.js
resolve: {
    alias: {
      我被Alias了: 'lodash',
    },
 },

Resolve 后

// in AliasPlugin.js
class AliasTest {
	apply(compiler) {
			normalModuleFactory.hooks.afterResolve.tap('AliasTest-afterResolve', (resolveData) => {
				if (resolveData.request.includes('Alias')) {
					console.log(`${resolveData.request}(request) | -- afterResolve.resolveData -- | ${resolveData.context}(context)`);
					console.log(resolveData.createData?.request);
					console.log('----- afterResolve --- \n');
				}
			})
			
			normalModuleFactory.hooks.createModule.tap('AliasTest-createModule', (createData, resolveData) => {
				if (resolveData.request.includes('Alias')) {
					console.log(`${createData.request}(request) | -- createModule.createData -- | ${createData.context}(context)`);
					console.log(`${resolveData.request}(request) | -- createModule.resolveData -- | ${resolveData.context}(context)`);
					console.log('----- createModule ---- \n');
				}
			})
		});
		
	}
}

AliasPlugin.js

Resolve

class NormalModuleFactory extends ModuleFactory {
  constructor() {
    // ...
    this.hooks.resolve.tapAsync(..., (resolveData) => {
      //...
      // createData 包含 alias 解析后路径
      Object.assign(resolveData.createData, {
        // ...
        request: stringifyLoadersAndResource(
          allLoaders,
          resourceData.resource
        ),
        userRequest,
        rawRequest: request,
      });
    })
  }
}

Module

class NormalModuleFactory extends ModuleFactory {
  constructor() {
    // ...
    this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
      // ...

      const createData = resolveData.createData;

      this.hooks.createModule.callAsync(
        createData,
        resolveData,
        (err, createdModule) => {
          if (!createdModule) {
            // ...
						// 创建 module
            createdModule = new NormalModule(createData);
          }

          createdModule = this.hooks.module.call(
            createdModule,
            createData,
            resolveData
          );

          return callback(null, createdModule);
        }
      );
    });
  }
}

基本上获取路径都会去取 request,例如 IgnorePlugin 插件,在 beforeResolve 阶段处理。此时的路径为代码内路径(例如:”我被Alias了”)。
怎么判断插件是拿什么匹配的?一般涉及路径都接收多种类型,例如函数,console 即可。

class IgnorePlugin {
  checkIgnore(resolveData) {
    if (
			this.options.checkResource(resolveData.request, resolveData.context)
		) {
			return false;
		}
  }
  
  apply(compiler) {
    compiler.hooks.normalModuleFactory.tap("IgnorePlugin", nmf => {
      nmf.hooks.beforeResolve.tap("IgnorePlugin", this.checkIgnore);
    });
		// ...
  }
}
// webpack.config.js plugins
new webpack.IgnorePlugin({
  resourceRegExp: /我被Alias了/,
})

https://mp.weixin.qq.com/s/SbJNbSVzSPSKBe2YStn2Zw
https://mp.weixin.qq.com/s?__biz=Mzg3OTYwMjcxMA==&mid=2247484088&idx=1&sn=41bf509a72f2cbcca1521747bf5e28f4&chksm=cf00bfc1f87736d76681e1e1db39deb1fa3121686bcd45a42709fa58aaba6d7ffba6d7fb4975&scene=178&cur_album_id=1856066636768722949#rd
https://mp.weixin.qq.com/s/tXkGx6Ckt9ucT2o8tNM-8w