webpack DefinePlugin解析

news2024/10/6 6:45:35

DefinePlugin是webpack的一个官方内置插件,它允许在 编译时 将你代码中的变量替换为其他值或表达式。这在需要根据开发模式与生产模式进行不同的操作时,非常有用。例如,如果想在开发构建中进行日志记录,而不在生产构建中进行,就可以定义一个全局常量去判断是否记录日志。这就是 DefinePlugin 的发光之处,设置好它,就可以忘掉开发环境和生产环境的构建规则。

new webpack.DefinePlugin({
  PRODUCTION: JSON.stringify(true),
  VERSION: JSON.stringify('5fa3b9'),
  BROWSER_SUPPORTS_HTML5: true,
  TWO: '1+1',
  'typeof window': JSON.stringify('object'),
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
});
​ 

demo

console.log(PRODUCTION,VERSION,BROWSER_SUPPORTS_HTML5,TWO,typeof window,process.env.NODE_ENV); 

源码入口

parser是一个hookMap,它就相当于一个管理hook的Map结构。

 apply(compiler) {const definitions = this.definitions;compiler.hooks.compilation.tap("DefinePlugin",(compilation, { normalModuleFactory }) => {
                      
                //...
                normalModuleFactory.hooks.parser
                  .for("javascript/auto")
                  .tap("DefinePlugin", handler);
                normalModuleFactory.hooks.parser
                  .for("javascript/dynamic")
                  .tap("DefinePlugin", handler);
                normalModuleFactory.hooks.parser
                  .for("javascript/esm")
                  .tap("DefinePlugin", handler);                
                //...
          })
  }
​
​ 

parser的call时机在哪?完全就在于NormalModuleFactory.createParser时机

所以这个钩子的语义就是parser创建时的初始化钩子。

 createParser(type, parserOptions = {}) {parserOptions = mergeGlobalOptions(this._globalParserOptions,type,parserOptions);const parser = this.hooks.createParser.for(type).call(parserOptions);if (!parser) {throw new Error(`No parser registered for ${type}`);}this.hooks.parser.for(type).call(parser, parserOptions);return parser;} 

好,现在让我们看看具体初始化了什么逻辑。

首先现在program上定义一个钩子,在遍历JavaScript AST前(该时机由program定义位置所知),注册buildInfo.valueDependencies=new Map();

并定义

buildInfo.valueDependencies.set(VALUE_DEP_MAIN, mainValue);

const handler = parser => {const mainValue = compilation.valueCacheVersions.get(VALUE_DEP_MAIN);
    //mainValue是在DefinePlugin最初初始化时定义到compilation.valueCacheVersions上的parser.hooks.program.tap("DefinePlugin", () => {const { buildInfo } = parser.state.module;if (!buildInfo.valueDependencies)buildInfo.valueDependencies = new Map();buildInfo.valueDependencies.set(VALUE_DEP_MAIN, mainValue);});
//....
  walkDefinitions(definitions, "");
} 

然后开始遍历Definitions(这是用户提供的配置项,比如 PRODUCTION: JSON.stringify(true),)

 const walkDefinitions = (definitions, prefix) => {Object.keys(definitions).forEach(key => {const code = definitions[key];if (code &&typeof code === "object" &&!(code instanceof RuntimeValue) &&!(code instanceof RegExp)) {//如果是对象就递归调用walkDefinitions(code, prefix + key + ".");applyObjectDefine(prefix + key, code);return;}applyDefineKey(prefix, key);applyDefine(prefix + key, code);});}; 

applyDefine

 const applyDefine = (key, code) => {const originalKey = key;const isTypeof = /^typeof\s+/.test(key);if (isTypeof) key = key.replace(/^typeof\s+/, "");let recurse = false;let recurseTypeof = false;if (!isTypeof) {parser.hooks.canRename.for(key).tap("DefinePlugin", () => {addValueDependency(originalKey);return true;});parser.hooks.evaluateIdentifier.for(key).tap("DefinePlugin", expr => {/** * this is needed in case there is a recursion in the DefinePlugin * to prevent an endless recursion * e.g.: new DefinePlugin({ * "a": "b", * "b": "a" * }); */if (recurse) return;addValueDependency(originalKey);recurse = true;const res = parser.evaluate(toCode(code,parser,compilation.valueCacheVersions,key,runtimeTemplate,null));recurse = false;res.setRange(expr.range);return res;});parser.hooks.expression.for(key).tap("DefinePlugin", expr => {addValueDependency(originalKey);const strCode = toCode(code,parser,compilation.valueCacheVersions,originalKey,runtimeTemplate,!parser.isAsiPosition(expr.range[0]));if (/__webpack_require__\s*(!?.)/.test(strCode)) {return toConstantDependency(parser, strCode, [RuntimeGlobals.require])(expr);} else if (/__webpack_require__/.test(strCode)) {return toConstantDependency(parser, strCode, [RuntimeGlobals.requireScope])(expr);} else {return toConstantDependency(parser, strCode)(expr);}});}parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {/** * this is needed in case there is a recursion in the DefinePlugin * to prevent an endless recursion * e.g.: new DefinePlugin({ * "typeof a": "typeof b", * "typeof b": "typeof a" * }); */if (recurseTypeof) return;recurseTypeof = true;addValueDependency(originalKey);const codeCode = toCode(code,parser,compilation.valueCacheVersions,originalKey,runtimeTemplate,null);const typeofCode = isTypeof? codeCode: "typeof (" + codeCode + ")";const res = parser.evaluate(typeofCode);recurseTypeof = false;res.setRange(expr.range);return res;});parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {addValueDependency(originalKey);const codeCode = toCode(code,parser,compilation.valueCacheVersions,originalKey,runtimeTemplate,null);const typeofCode = isTypeof? codeCode: "typeof (" + codeCode + ")";const res = parser.evaluate(typeofCode);if (!res.isString()) return;return toConstantDependency(parser,JSON.stringify(res.string)).bind(parser)(expr);});};
​ 

hooks.expression

在applyDefine中定义的hooks.expression定义了对表达式的替换处理。

当代码解析到语句【key】时,便会触发如下钩子逻辑,不过先别急,我们先搞清楚expression钩子在何处会被触发。

parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
//...} 

触发时机

单单指demo中的情况

比如PRODUCTION会被acron解析为Identifier

而在parse阶段中,会有这么一句

 if (this.hooks.program.call(ast, comments) === undefined) {  //...其他解析语句this.walkStatements(ast.body);}
//然后会走到这
     walkIdentifier(expression) {this.callHooksForName(this.hooks.expression, expression.name, expression);}
//最后const hook = hookMap.get(name);//获取hookif (hook !== undefined) {const result = hook.call(...args); //执行hookif (result !== undefined) return result;} 

具体逻辑

 parser.hooks.expression.for(key).tap("DefinePlugin", expr =>{addValueDependency(originalKey);const strCode = toCode(code,parser,compilation.valueCacheVersions,originalKey,runtimeTemplate,!parser.isAsiPosition(expr.range[0]));if (/__webpack_require__\s*(!?.)/.test(strCode)) {return toConstantDependency(parser, strCode, [RuntimeGlobals.require])(expr);} else if (/__webpack_require__/.test(strCode)) {return toConstantDependency(parser, strCode, [RuntimeGlobals.requireScope])(expr);} else {return toConstantDependency(parser, strCode)(expr);}});
} 
addValueDependency
//这里会影响needBuild的逻辑,是控制是否构建模块的const addValueDependency = key => {const { buildInfo } = parser.state.module;//这里就可以理解为设置key,valuebuildInfo.valueDependencies.set(VALUE_DEP_PREFIX + key,compilation.valueCacheVersions.get(VALUE_DEP_PREFIX + key));};
​ 

要搞懂addValueDependency到底做了什么,首先得理解

  • compilation.valueCacheVersions这个map结构做了什么?
  • buildInfo.valueDependencies这里的依赖收集起来有什么用?
toCode获取strCode
toConstantDependency

设置ConstDependency静态转换依赖。

exports.toConstantDependency = (parser, value, runtimeRequirements) => {return function constDependency(expr) {const dep = new ConstDependency(value, expr.range, runtimeRequirements);dep.loc = expr.loc;parser.state.module.addPresentationalDependency(dep);return true;};
}; 

ConstDependency是如何替换源码的?

出处在seal阶段

 if (module.presentationalDependencies !== undefined) {for (const dependency of module.presentationalDependencies) {this.sourceDependency(module,dependency,initFragments,source,generateContext);}} 

sourceDenpendency,会获取依赖上的执行模板

 const constructor = /** @type {new (...args: any[]) => Dependency} */ (dependency.constructor);//template可以理解为代码生成模板const template = generateContext.dependencyTemplates.get(constructor);
        
///....
template.apply(dependency, source, templateContext);//然后执行 

而ConstPendency的执行模板直接替换了源码中的某个片段内容

const dep = /** @type {ConstDependency} */ (dependency);if (dep.runtimeRequirements) {for (const req of dep.runtimeRequirements) {templateContext.runtimeRequirements.add(req);}}if (typeof dep.range === "number") {source.insert(dep.range, dep.expression);return;}
​source.replace(dep.range[0], dep.range[1] - 1, dep.expression); 

hooks.canRename

在applyDefine中,也有hooks.canRename的调用:

 parser.hooks.canRename.for(key).tap("DefinePlugin", () => {addValueDependency(key);return true;
   }); 

在这里其实就是允许key可以被重命名,并借机收集key作为依赖,但这个重命名的工作不是替换,并不在definePlugin内做,有点点奇怪。

详细:

该hook会在js ast遍历时多处被call

0.walkAssignmentExpression 赋值表达式
1._walkIIFE CallExpression 函数调用表达式发现是IIFE时
2.walkVariableDeclaration 声明语句

canRename有什么用?其实是配合rename使用的钩子

当其返回true,rename钩子才能起作用。如下:

 walkVariableDeclaration(statement) {for (const declarator of statement.declarations) {switch (declarator.type) {case "VariableDeclarator": {const renameIdentifier =declarator.init && this.getRenameIdentifier(declarator.init);if (renameIdentifier && declarator.id.type === "Identifier") {const hook = this.hooks.canRename.get(renameIdentifier);if (hook !== undefined && hook.call(declarator.init)) {// renaming with "var a = b;"const hook = this.hooks.rename.get(renameIdentifier);if (hook === undefined || !hook.call(declarator.init)) {this.setVariable(declarator.id.name, renameIdentifier);}break;}}
                   //...}}}} 

官方也有所介绍这个钩子

hooks.typeof

 parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {addValueDependency(originalKey);const codeCode = toCode(code,parser,compilation.valueCacheVersions,originalKey,runtimeTemplate,null);const typeofCode = isTypeof? codeCode: "typeof (" + codeCode + ")";const res = parser.evaluate(typeofCode);if (!res.isString()) return;return toConstantDependency(parser,JSON.stringify(res.string)).bind(parser)(expr);}); 

先执行typeof再用toConstantDependency替换。

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/183273.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1603_MIT 6.828 “El Torito” Bootable CD-ROM Format Specification阅读

全部学习汇总: GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 最近正好趁着假期看了下MIT的OS课程,这是里面推荐阅读的一份资料。简单看了一下,整理一下自己的阅读笔记。 只要是涉及到有历史的计算机技术…

一文带你深刻的进入python,并且了解python的优缺点

嗯,你好,感谢您能打开我的文章,在这里我有一个好消息来告诉大家呢,那就是:还有350多天就要过年啦,哈哈哈哈,开不开心,兴不兴奋 名字:阿玥的小东东 学习:pytho…

零代码应用搭建规范建议

文/顿唯 况育军 贺书钿 编辑/杜逸敏 在明道云零代码开发平台里,一个完整的业务应用包含了工作表、视图、角色、自定义页面、工作流五大核心模块(统计和外部门户为可选模块功能),基于这些能力模块组件,我们能呈现给用…

飞桨时序建模库PaddleTS及产业应用实践

时间序列: 一种普遍存在的数据形态 众所周知,时间序列是一种普遍存在的数据形态,与我们的日常生活及生产活动密切相关。如:股票指数、原油价格等金融市场数据;温度、湿度等天气数据;振动、转速等工业设备运…

【技术短文】汽车软件质量改善

一、根因分析 根据汽车软件脆弱性主要因素分析,共有10余种因素会导致软件质量问题: 1.项目时间点压力,占比 71%2.缺乏安全编程理解/培训,占比 60%3.偶然编程错误,占比 55%4.缺乏质量…

Tarjan算法的应用---缩点与割点

图论中有时候会涉及到一些连通性问题,主要是针对于点来说,在有向图中有时候需要计算强连通分量,这时候代表分量的的点就非常重要;在无向图中有时候会需要知道割点,用到的算法都是Tarjan,这个算法还是有难理…

了解多线程与并发

文章目录前言继承Thread类实现Runnable接口实现Callable和Future接口线程生命周期线程优先级线程加入操作线程休眠操作中断线程线程安全问题线程同步机制1. 同步代码块2. 同步方法线程暂停与恢复知识拓展死锁前言 📋前言📋 💝博客&#xff1a…

用Devc++与easyx一步一步做游戏[启动界面部分]-之按钮制作

前面我们介绍了如何为dev c配置好easyx,至于用easyx能够做一些什么呢?大用处我不敢说,用来学习了解消息机制还是不错的。这我们来实现一个简单的游戏启动界面的设计,主要是按钮的设计。总体设计好的效果如下: GIF截图…

(免费分享)springboot音乐网站

开发工具:eclipse,数据库mysql5.7 jdk1.8技术:springbootmybatis/** * * * */package com.bjpowernode.music.ss.service.impl;import javax.annotation.Resource;import com.bjpowernode.music.common.AbstractService; import com.bjpowe…

基于 ROS 机器人和 RTAB-MAP 算法实现室内三维重建

本文叙如何利用RTAB-Map算法和Turtlebot3机器人在自己构建的室内场景中建图 文章目录1、安装依赖2、创建工作空间3、安装rtabmap和rtabmap_ros4、建立gazebo场景功能包5、建立机器人功能包6、为机器人添加kinect相机参考7、编译工作空间8、建立环境地图9、建图1、安装依赖 必要…

数据结构第五周 :(进制转换问题 + 迷宫自动行走问题 + 杨辉三角形 + 队列元素逆置 + 银行排队 + 整数划分问题 + 卡特兰数)

目录进制转换问题迷宫自动行走问题杨辉三角形队列元素逆置银行排队——队列整数划分问题买票问题——卡特兰数小兔的棋盘——卡特兰数进制转换问题 【问题描述】根据课堂讲授,请用“顺序栈”解决进制转换问题,不采用顺序栈,不给分。 【输入形…

前端屏幕录制工具 + 录制<video>标签内容

一、录制的实现思路 1.开始录制、停止录制、下载视频 2.Blob介绍 3.概念 var mediaRecord //用于录制视频 var mediaStream //视频流 var videoBuffer [] //保存的视频数据二、屏幕录制工具 下载地址: https://chrome.google.com/webstore/detail/tampermonkey…

Linux- 系统随你玩之--文本处理三剑客--grep继任者awk

文章目录1、sed概述1.1、 与vim等编辑器的区别:1.2、sed工作原理1.3 、sed数据处理原理1.4 、正则表达式概念2、 sed语法和常用选项2.1、语法:2.2、sed常用内部命令2.3、参数:3、 sed 正则表达式(定位)3.1 、数字定址…

管理机密(RH294)

在ansible中有一个命令行工具ansible-vault可用于创建 编辑 加密 解密 查看文件举个栗子ansible-vaultcreate filenameNew Vault password: #输入密码Confirm New Vault password: #确认密码也可以使用别的方法 比如创建一个密码文件ansible-vaultcreate…

互联网开发必读Git工具利器-《30天精通Git版本控管》中文版免费分享

本书介绍在软体开发领域,对原始码进行版本控管是非常重要的一件事,有别于Subversion或TFS这类集中式版本控管系统,Git是一套分散式版本控管系统,并带来许多版本控管上的各种优势与解决传统集中式版本控管的缺失,例如支…

Spring Cloud_Hystrix断路器

目录一、概述1.分布式系统面临的问题2.是什么3.能干嘛4.官网资料5.Hystrix官宣,停更进维二、Hystrix重要概念1.服务降级Fallback2.服务熔断Breaker3.服务限流Flowlimit三、hystrix案例1.构建2.高并发测试3.故障现象和导致原因4.上诉结论5.如何解决?解决的…

面试_Selenium常见问题

1.selenium 工作原理 1.对于每一条Selenium脚本,一个http请求会被创建并且发送给浏览器的驱动 2.浏览器驱动中包含了一个HTTP Server,用来接收这些http请求 3.HTTP Server接收到请求后根据请求来具体操控对应的浏览器 4.浏览器执行具体的测试步骤 5.浏览…

Smali语法小记

Smali语法小记 介绍 在执行 Android Java 层的代码时,其实就是 Dalvik(ART) 虚拟机(使用 C 或 C 代码实现)在解析 Dalvik 字节码,从而模拟程序的执行过程。 自然,Dalvik 字节码晦涩难懂,研究人员们给出了…

通过 eShopOnContainers 项目学习一下微服务

这里是项目地址 https://github.com/dotnet-architecture/eShopOnContainers, 这是微软创建的一个基于 .NET 平台的微服务架构的示例应用程序,里面基本上市面上主流的时髦的技术都用上了。 因为涉及的内容比较多,所以我们只简单查看一下微服务的代码实现…

信息抽取命名实体识别和关系抽取)

信息抽取的定义为:从自然语言文本中抽取指定类型的实体,关系、事件等事实信息。并形成结构化数据输出的文本处理技术。 信息抽取是从文本数据中抽取特定信息的一种技术,文本数据由医学具体的单位构成,例如,句子、段落、…