1.webpack5loader和plugin原理解析
2.java中classloader的码分defineclass()方法是如何实现的?
3.Webpack进阶less-loader、css-loader、码分style-loader源码解析
4.unimrcpclient源码分析mrcp components
5.CALL 、码分libc++、码分libc
webpack5loader和plugin原理解析
大家好,码分今天为大家解析下loader和plugin一、码分网页悬浮窗倒计时源码区别loader是码分文件加载器,能够加载资源文件,码分并对这些文件进行一些处理,码分诸如编译、码分压缩等,码分最终一起打包到指定的码分文件中
plugin赋予了Webpack各种灵活的功能,例如打包优化、码分资源管理、码分环境变量注入等,码分目的是解决loader无法实现的其他事从整个运行时机上来看,如下图所示:
可以看到,两者在运行时机上的区别:
loader运行在打包文件之前plugins在整个编译周期都起作用在Webpack运行的生命周期中会广播出许多事件,Plugin可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果
对于loader,实质是一个转换器,将A文件进行编译形成B文件,操作的是文件,比如将A.scss或A.less转变为B.css,单纯的文件转换过程
下面我们来看看loader和plugin实现的原理
Loader原理loader概念帮助webpack将不同类型的文件转换为webpack可识别的模块。
loader执行顺序分类
pre:前置loader
normal:普通loader
inline:内联loader
post:后置loader
执行顺序
4类loader的执行优级为:pre>normal>inline>post。
相同优先级的loader执行顺序为:从右到左,从下到上。
例如:
//此时loader执行顺序:loader3-loader2-loader1module:{ rules:[{ test:/\.js$/,loader:"loader1",},{ test:/\.js$/,loader:"loader2",},{ test:/\.js$/,loader:"loader3",},],},//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},使用loader的方式
配置方式:在webpack.config.js文件中指定loader。(pre、normal、希望ol 源码postloader)
内联方式:在每个import语句中显式指定loader。(inlineloader)
开发一个loader1.最简单的loader//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};它接受要处理的源码作为参数,输出转换后的js代码。
2.loader接受的参数content源文件的内容
mapSourceMap数据
meta数据,可以是任何内容
loader分类1.同步loadermodule.exports=function(content,map,meta){ returncontent;};this.callback方法则更灵活,因为它允许传递多个参数,而不仅仅是content。
module.exports=function(content,map,meta){ //传递map,让source-map不中断//传递meta,让下一个loader接收到其他参数this.callback(null,content,map,meta);return;//当调用callback()函数时,总是返回undefined};2.异步loadermodule.exports=function(content,map,meta){ constcallback=this.async();//进行异步操作setTimeout(()=>{ callback(null,result,map,meta);},);};由于同步计算过于耗时,在Node.js这样的单线程环境下进行此操作并不是好的方案,我们建议尽可能地使你的loader异步化。但如果计算量很小,同步loader也是可以的。
3.RawLoader默认情况下,资源文件会被转化为UTF-8字符串,然后传给loader。通过设置raw为true,loader可以接收原始的Buffer。
module.exports=function(content){ //content是一个Buffer数据returncontent;};module.exports.raw=true;//开启RawLoader4.PitchingLoadermodule.exports=function(content){ returncontent;};module.exports.pitch=function(remainingRequest,precedingRequest,data){ console.log("dosomethings");};webpack会先从左到右执行loader链中的每个loader上的pitch方法(如果有),然后再从右到左执行loader链中的每个loader上的普通loader方法。
在这个过程中如果任何pitch有返回值,则loader链被阻断。webpack会跳过后面所有的的pitch和loader,直接进入上一个loader。
loaderAPI方法名含义用法this.async异步回调loader。返回this.callbackconstcallback=this.async()this.callback可以同步或者异步调用的并返回多个结果的函数this.callback(err,content,sourceMap?,meta?)this.getOptions(schema)获取loader的optionsthis.getOptions(schema)this.emitFile产生一个文件this.emitFile(name,content,sourceMap)this.utils.contextify返回一个相对路径this.utils.contextify(context,request)this.utils.absolutify返回一个绝对路径this.utils.absolutify(context,request)更多文档,请查阅webpack官方loaderapi文档
手写clean-log-loader作用:用来清理js代码中的console.log
//loaders/clean-log-loader.jsmodule.exports=functioncleanLogLoader(content){ //将console.log替换为空returncontent.replace(/console\.log\(.*\);?/g,"");};手写banner-loader作用:给js代码添加文本注释
loaders/banner-loader/index.js
constschema=require("./schema.json");module.exports=function(content){ //获取loader的options,同时对options内容进行校验//schema是code充值源码options的校验规则(符合JSONschema规则)constoptions=this.getOptions(schema);constprefix=`/**Author:${ options.author}*/`;return`${ prefix}\n${ content}`;};loaders/banner-loader/schema.json
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},0手写babel-loader作用:编译js代码,将ES6+语法编译成ES5-语法。
下载依赖
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},1loaders/babel-loader/index.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},2loaders/banner-loader/schema.json
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},3手写file-loader作用:将文件原封不动输出出去
下载包
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},4loaders/file-loader.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},5loader配置
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},6手写style-loader作用:动态创建style标签,插入js中的样式代码,使样式生效。
loaders/style-loader.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},7Plugin原理Plugin的作用通过插件我们可以扩展webpack,加入自定义的构建行为,使webpack可以执行更广泛的任务,拥有更强的构建能力。
Plugin工作原理webpack就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack通过Tapable来组织这条复杂的生产线。webpack在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。webpack的事件流机制保证了插件的有序性,使得整个系统扩展性很好。——「深入浅出Webpack」
站在代码逻辑的角度就是:webpack在编译代码过程中,会触发一系列Tapable钩子事件,插件所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当webpack构建的源码apk下载时候,插件注册的事件就会随着钩子的触发而执行了。
Webpack内部的钩子什么是钩子钩子的本质就是:事件。为了方便我们直接介入和控制编译过程,webpack把编译过程中触发的各类关键事件封装成事件接口暴露了出来。这些接口被很形象地称做:hooks(钩子)。开发插件,离不开这些钩子。
TapableTapable为webpack提供了统一的插件接口(钩子)类型定义,它是webpack的核心功能库。webpack中目前有十种hooks,在Tapable源码中可以看到,他们是:
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},8Tapable还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:
tap:可以注册同步钩子和异步钩子。
tapAsync:回调方式注册异步钩子。
tapPromise:Promise方式注册异步钩子。
Plugin构建对象Compilercompiler对象中保存着完整的Webpack环境配置,每次启动webpack构建时它都是一个独一无二,仅仅会创建一次的对象。
这个对象会在首次启动Webpack时创建,我们可以通过compiler对象上访问到Webapck的主环境配置,比如loader、plugin等等配置信息。
它有以下主要属性:
compiler.options可以访问本次启动webpack时候所有的配置文件,包括但不限于loaders、entry、output、plugin等等完整配置信息。
compiler.inputFileSystem和compiler.outputFileSystem可以进行文件操作,相当于Nodejs中fs。
compiler.hooks可以注册tapable的asp源码部署不同种类Hook,从而可以在compiler生命周期中植入不同的逻辑。
compilerhooks文档
Compilationcompilation对象代表一次资源的构建,compilation实例能够访问所有的模块和它们的依赖。
一个compilation对象会对构建依赖图中所有模块,进行编译。在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、分块(chunk)、哈希(hash)和重新创建(restore)。
它有以下主要属性:
compilation.modules可以访问所有模块,打包的每一个文件都是一个模块。
compilation.chunkschunk即是多个modules组成而来的一个代码块。入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk。
compilation.assets可以访问本次打包生成所有文件的结果。
compilation.hooks可以注册tapable的不同种类Hook,用于在compilation编译模块阶段进行逻辑添加以及修改。
compilationhooks文档
生命周期简图开发一个插件最简单的插件plugins/test-plugin.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},9注册hook//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};0启动调试通过调试查看compiler和compilation对象数据情况。
package.json配置指令
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};1运行指令
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};2此时控制台输出以下内容:
PSC:\Users\\Desktop\source>//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};2>source@1.0.0debug>node--inspect-brk./node_modules/webpack-cli/bin/cli.jsDebuggerlisteningonws://.0.0.1:/ea-7b--a7-fccForhelp,see:/post/开发思路:
我们需要借助html-webpack-plugin来实现
在html-webpack-plugin输出index.html前将内联runtime注入进去
删除多余的runtime文件
如何操作html-webpack-plugin?官方文档
实现:
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};7java中classloader的defineclass()方法是如何实现的?
在Java中,ClassLoader的defineClass()方法用于将字节码定义为类。此方法允许外部提供类的字节码,以便在Java虚拟机(JVM)中加载并执行它们。此功能尤其适用于从网络或其他外部源加载类,而不必依赖于预先存在的类文件。
该方法通过JNI(Java Native Interface)实现,使用C/C++语言编写。它通过将字节码转换为类,使得类可以在JVM环境中运行。要深入了解其内部实现,可以查阅JVM的源代码,但这里不再赘述。
值得注意的是,对于同一类名,可以多次提供不同的字节码。这使得实现类似反射的功能成为可能。与Java内置反射机制相比,这种方法在某些情况下能提供倍的调用效率。不过,这种高效实现主要针对动态语言功能的需求,而反射的使用场景和效率可能在其他情况下有所不同。
此外,如果类名未知,defineClass()方法允许传入null。这意呀着JVM在加载所有字节码后设置类名,这可能会增加额外的步骤和资源消耗。然而,考虑到可能出现字节码中不包含类名的情况,提供这种实现方式是合理的,但在这种情况下必须明确指定类名。
Webpack进阶less-loader、css-loader、style-loader源码解析
Webpack进阶学习中,Loader的运用是关键环节。在深入理解Loader基础后,本文将解析less-loader、css-loader和style-loader的内部工作原理。
less-loader是专为处理Less样式文件设计的,它将Less代码转换为浏览器能识别的CSS。以less文件为例,其工作原理是调用less库的功能,将扩展了CSS特性的Less代码转化为CSS,如变量、Mixin和函数等。
css-loader的功能则更为复杂,它不仅处理@import和url语句,还支持css-modules,将样式文件内容合并并作为JavaScript模块输出。以多个样式文件(如a.css、b.css和c.css)为例,css-loader会将它们合并成一个JavaScript模块,输出包含所有样式内容的字符串。
style-loader的作用在于将css-loader转化后的CSS样式代码插入到DOM中。理论上,我们可能期望直接在JavaScript中插入CSS代码,但css-loader返回的是模块化的代码,不能直接放入style标签。style-loader的实现通过一种巧妙的方式,将这些模块代码适当地包装,确保样式能正确插入到文档中。
style-loader的设计思路独特,其内部逻辑涉及Loader调用链、执行顺序和模块化输出等多个层面,理解style-loader的运作机制,对于深化对Webpack和Loader的理解至关重要。深入研究这些Loader的源码,无疑能提升你对Webpack进阶应用的掌握程度。
unimrcpclient源码分析mrcp components
配置样例包含六个部分。
函数unimrcp_client_components_load主要负责这六个组件的加载,下面详细解析各个组件的加载过程。
加载resource组件时,其结构体mrcp_resource_t定义在mrcp_resource.h,字符串使用apt_str_table_item_t(定义在mrcp_resource_loader.c),mrcp_resource_loader_t和mrcp_resource_factory_t分别位于mrcp_resource_loader.c和mrcp_resource_factory.c。构造mrcp_resource_loader内部是mrcp_resource_factory_t *factory,factory通过mrcp_resource_factory_create构建,参数为MRCP_RESOURCE_TYPE_COUNT的第5个值,整型值为4。
加载sip-uac组件时,mrcp_sofia_client_config_t结构体(mrcp_sofiasip_client_agent.c)应用于sofia sip协议栈。默认端口和名称设定如下:config->local_port = DEFAULT_SIP_PORT; 默认端口为,config->user_agent_name = DEFAULT_SOFIASIP_UA_NAME; 默认agent name为UniMRCP SofiaSIP,config->origin = DEFAULT_SDP_ORIGIN; 默认sdp origin为UniMRCPClient。IP地址配置通过unimrcp_client_ip_address_get(unimrcp_client.c)实现,可配置为“auto”或“iface”。然后使用mrcp_sofiasip_client_agent_create(mrcp_sofiasip_client_agent.c)创建sofiasip_client。
加载rtsp-uac组件时,指代MRCPv1版本的uac组件。rtsp_client_config_t结构体(mrcp_unirtsp_client_agent.h)用于配置。组件加载通过unimrcp_client_rtsp_uac_load函数完成。
加载media-engine组件时,配置项仅包含realtime_rate。unimrcp_client_media_engine_load函数负责加载此组件。mpf_engine_t结构体(mpf_engine.c)用于创建mpf_engine,使用mpf_engine_create函数。
加载rtp-factory组件时,mpf_rtp_config_t结构体(mpf_rtp_descriptor.h)包含可配置项。unimrcp_client_rtp_factory_load函数用于加载此组件,并通过mpf_rtp_termination_factory_create(mpf_rtp_termination_factory.c)创建相应的工厂。
CALL 、libc++、libc
CALL 是 **(Compiler/Assembler/Linker/Loader)**的简称。如果你和 C/C++ 打过交道,这几个词应该不会陌生。
Levels of Representation/Interpretation
抛开正确性,我们可以尝试填空(Python / C++、Go)并且知道:
但是同时,Python 也能通过一些方式打包成 exe (虽然很巨大),同时 LLVM 这些层次的引入让我们的理解模糊了起来。所以我们要明确一下这个 Level。
这个问题实际上是很含糊不清的,第三点里面 JIT 等的引入更让事情扑朔迷离了起来。具体其实可以参考这个链接里的说法: /question/。
一般被称为“解释型语言”的是主流实现为解释器的语言,但并不是说它就无法编译。例如说经常被认为是“解释型语言”的 Scheme就有好几种编译器实现,其中率先支持 R6RS规范的大部分内容的是 Ikarus,支持在x上编译Scheme;它最终不是生成某种虚拟机的字节码,而是直接生成x机器码。
实际上解释器的性能劣势也不一定是一种坏事,像我去年去 PyCon 听的“慢解释是一种优势”,虽然有点破罐子破摔的味道,但是如果你在 C/C++ 下开 asan/valgrind 或者带gcc -g, 和 Go 这种带 Runtime 的、V8这些可以提供的debug比较,难免会有羡慕的想法。
Interpreter provides instruction set independence: run on any machine
就是这样。
CALL chain
这是一张水图。可能还要处理一下预处理之类的过程,但是大概流程是这样没错。
Compile
Compile 的过程大概是
不过看上面转的那篇文章,似乎形式有变,这方面我不是很了解。Lexer/Parser 的部分可以参考我之前的 Lex/Yacc 入门。总之,我们现在把源代码编译后可以转化为一种对应的 IR, 即nmsl.c -> nmsl.S.
Assembler
Assembler 接下来会nmsl.s -> nmsl.o.
顺便给出这个 part 一个很有意思的 slide:
ELF
这个我觉得还是 csapp 写得好...总之生成的目标文件会满足这样的形式。
Linker
Combines several object (.o) files into a single executable (“linking”)
这段我感觉 CSAPP 讲的稍微详细一些。
在应用层面上,这里其实还涉及(不一定是这里引入的)name mangling,calling convention这种 C/C++ 相关的问题,所以可能extern "C" 在这种情况下就相对很好理解了。
Loader
When one is run, loader’s job is to load it into memory and start it running In reality, loader is the operating system (OS)
这里可以参考 CSAPP 里面链接的时机相关的概念。
libc/libc++
qsort 是一个 下的函数,如果你去 libc++ 找的话,会发现事情好像不太对:
github.com/llvm-mirror/...
这里只有using 和函数声明,没有对应的实现。
实际上 C++ 的标准库(以 libc++) 为例,可能会根据模版生成需要的函数/类。所以我们可以看到对应的一些源代码。
C语言的库函数实际上通常以链接库的形式在 libc 中提供,链接的时候我们找到: stackoverflow.com/quest...