我爱她。的英文。翻译。英语怎么说-普什图语
2023年4月6日发(作者:广东二级建造师成绩查询)
原来rollup这么简单之插件篇
⼤家好,我是⼩⾬⼩⾬,致⼒于分享有趣的、实⽤的技术⽂章。
内容分为翻译和原创,如果有问题,欢迎随时评论或私信,希望和⼤家⼀起进步。
⼤家的⽀持是我创作的动⼒。
计划
rollup系列打算⼀章⼀章的放出,内容更精简更专⼀更易于理解
这是rollup系列的最后⼀篇⽂章,以下是所有⽂章链接。
plugins<====当前⽂章
TL;DR
rollup的插件和其他⼤型框架⼤同⼩异,都是提供统⼀的标准接⼝,通过约定⼤于配置定义公共配置,注⼊当前构建结果相关的属性与⽅法,供开发者进⾏增删改查操作。为稳定可持
续增长提供了强⽽有⼒的铺垫!
但不想webpack区分loader和plugin,rollup的plugin既可以担任loader的⾓⾊,也可以胜任传统plugin的⾓⾊。rollup提供的钩⼦函数是核⼼,⽐如load、transform对chunk进⾏解析
更改,resolveFileUrl可以对加载模块进⾏合法解析,options对配置进⾏动态更新等等~
注意点
所有的注释都在,可⾃⾏阅读
!!!提⽰=>标有TODO为具体实现细节,会视情况分析。
!!!注意=>每⼀个⼦标题都是⽗标题(函数)内部实现
!!!强调=>rollup中模块(⽂件)的id就是⽂件地址,所以类似resolveID这种就是解析⽂件地址的意思,我们可以返回我们想返回的⽂件id(也就是地址,相
对路径、决定路径)来让rollup加载
rollup是⼀个核⼼,只做最基础的事情,⽐如提供,⽐如打包成不同风格的内容,我们的插件中提供了加载⽂件路径,解析⽂件内容(处理ts,sass等)等操
作,是⼀种插拔式的设计,和webpack类似
插拔式是⼀种⾮常灵活且可长期迭代更新的设计,这也是⼀个中⼤型框架的核⼼,⼈多⼒量⼤嘛~
主要通⽤模块以及含义
Graph:全局唯⼀的图,包含⼊⼝以及各种依赖的相互关系,操作⽅法,缓存等。是rollup的核⼼
PathTracker:引⽤(调⽤)追踪器
PluginDriver:插件驱动器,调⽤插件和提供插件环境上下⽂等
FileEmitter:资源操作器
GlobalScope:全局作⽤局,相对的还有局部的
ModuleLoader:模块加载器
NodeBase:ast各语法(ArrayExpression、AwaitExpression等)的构惟有饮者留其名 造基类
插件机制分析
rollup的插件其实⼀个普通的函数,函数返回⼀个对象,该对象包含⼀些基础属性(如name),和不同阶段的钩⼦函数,像这个样⼦:
functionplugin(options={}){
return{
name:\'rollup-plugin\',
transform(){
return{
code:\'code\',
map:{mappings:\'\'}
};
}
};
}
这⾥是官⽅建议遵守的.
我们平常书写rollup插件的时候,最关注的就是钩⼦函数部分了,钩⼦函数的调⽤时机有三类:
constchunks=执⾏期间的
tor(write)执⾏期间的
监听⽂件变化并重新执⾏构建的执⾏期间的watchChange钩⼦函数
除了类别不同,ro绝句古诗两个黄鹂鸣翠柳 llup也提供了⼏种的执⾏⽅式,每种⽅式都⼜分为同步或异步,⽅便内部使⽤:
async:处理promise的异步钩⼦,也有同步版本
first:如果多个插件实现了相同的钩⼦函数,那么会串式执⾏,从头到尾,但是,如果其中某个的返回值不是null也不是undefined的话,会直接终⽌掉后续插件。
sequential:如果多个插件实现了相同的钩⼦函数,那么会串式执⾏,按照使⽤插件的顺序从头到尾执⾏,如果是异步的,会等待之前处理完毕,在执⾏下⼀个插件。
parallel:同上,不过如果某个插件是异步的,其后的插件不会等待,⽽是并⾏执⾏。
⽂字表达⽐较苍⽩,咱们看⼏个实现:
钩⼦函数:hookFirst
使⽤场景:resolveId、resolveAssetUrl等
functionhookFirst
hookName:H,渭城朝雨浥轻尘的拼音
args:Args
replaceContext?:ReplaceContext|null,
skip?:number|null
):EnsurePromise
//初始化promise
letpromise:Promise
//s在初始化Graph的时候,进⾏了初始化
for(leti=0;i<;i++){
if(skip===i)continue;
//覆盖之前的promise,换⾔之就是串⾏执⾏钩⼦函数
promise=((result:any)=>{
//返回⾮null或undefined的时候,停⽌运⾏,返回结果
if(result!=null)returnresult;
//执⾏钩⼦函数
k(hookName,argsasany[],i,false,replaceContext);
});
}
//最后⼀个promise执⾏的结果
returnpromise;
}
钩⼦函数:hookFirstSync
使⽤场景:resolveFileUrl、resolveImportMeta等
//hookFirst的同步版本,也就是并⾏执⾏
functionhookFirstSync
hookName:H,
args:Args
replaceContext?:ReplaceContext
):R{
for(leti=0;i<;i++){
//runHook的同步版本
constresult=kSync(hookName,args,i,replaceContext);
//返回⾮null或undefined的时候,停⽌运⾏,返回结果
if(result!=null)returnresultasany;
}
//否则返回null
returnnullasany;
}
钩⼦函数:hookSeq
使⽤场景:onwrite、generateBundle等
//和hookFirst的区别就是不能中断
asyncfunctionhookSeq
hookName:H,
args:Args
replaceContext?:ReplaceContext
):Promise
letpromise:Promise
for(leti=0;i<;i++)
promise=(()=>
k
);
returnpromise;
}
钩⼦函数:hookParallel
使⽤场景:关于立冬的谚语 buildStart、buildEnd、renderStart等
//同步进⾏,利⽤的
functionhookParallel
hookName:H,
args:Args
replaceContext?:ReplaceContext
):Promise
//创建容器
constpromises:Promise
//遍历每⼀个plugin
for(leti=0;i<;i++){
//执⾏hook返回promise
consthookPromise=k
//如果没有那么不push
if(!hookPromise)continue;
(hookPromise);
}
//返回promise
(promises).then(()=>{});
}
钩⼦函数:hookReduceArg0
使⽤场景:outputOptions、renderChunk等
//对arg第⼀项进⾏reduce操作
functionhookReduceArg0
hookName:H,
[arg0,...args]:any[],//取出传⼊的数组的第⼀个参数,将剩余的置于⼀个数组中
reduce:Reduce
replaceContext?:ReplaceContext//替换当前plugin调⽤时候的上下⽂环境
){
letpromise=e(arg0);//默认返回
for(leti=0;i<;i++){
//第⼀个promise的时候只会接收到上⾯传递的arg0
//之后每⼀次promise接受的都是上⼀个清平乐村居古诗视频 插件处理过后的值
promise=(arg0=>{
consthookPromise=k(hookName,[arg0,...args],i,false,replaceContext);
//如果没有返回promise,那么直接返回arg0
if(!hookPromise)returnarg0;
//result代表插件执⾏完成的返回值
((result:any)=>
(Contexts[i],arg0,result,s[i])
);
});
}
returnpromise;
}
通过观察上⾯⼏种钩⼦函数的调⽤⽅式,我们可以发现,其内部有⼀个调⽤钩⼦函数的⽅法:runHook(Sync),该函数执⾏插件中提供的钩⼦函数。
实现很简单:
functionrunHook
hookName:string,
args:any[],
pluginIndex:number,
permitValues:boolean,
hookContext?:ReplaceContext|null
):Promise
(hookName);
//找到当前plugin
constplugin=s[pluginIndex];
//找到当前执⾏的在plugin中定义的hooks钩⼦函数
consthook=(pluginasany)[hookName];
if(!hook)returnundefinedasany;
//pluginContexts在初始化plugin驱动器类的时候定义,是个数组,数组保存对应着每个插件的上下⽂环境
letcontext=Contexts[pluginIndex];
//⽤于区分对待不同钩⼦函数的插件上下⽂
if(hookContext){
context=hookContext(context,plugin);
}
e()
.then(()=>{
//permitvaluesallowsvaluestobereturnedinsteadofafunctionalhook
if(typeofhook!==\'function\'){
if(permitValues)returnhook;
returnerror({
code:\'INVALID_PLUGIN_HOOK\',
message:`Errorrunningpluginhook${hookName}for${},expectedafunctionhook.`
});
}
//传⼊插件上下⽂和参数,返回插件执⾏结果
(context,args);
})
.catch(err=>throwPluginError(err,,{hook:hookName}));
}
当然,并不是每个⼈刚开始都会使⽤插件,所以rollup本⾝也提供了⼏个必需的钩⼦函数供我们使⽤,在Graph实例化的时候与⽤户⾃定义插件进⾏concat操作:
import{getRollupDefaultPlugin}from\'./defaultPlugin\';
s=(
//采⽤内置默认插件或者graph的插件驱动器的插件,不管怎么样,内置默认插件是肯定有的
//basePluginDriver是上⼀个PluginDriver初始化的插件
//preserveSymlinks:软连标志
basePluginDriver?s:[getRollupDefaultPlugin(preserveSymlinks)]
);
那rollup提供了哪些必需的钩⼦函数呢:
exportfunctiongetRollupDefaultPlugin(preserveSymlinks:boolean):Plugin{
return{
//插件名
name:\'RollupCore\',
//默认的模块(⽂件)加载机制,内部主要使⽤e
resolveId:createResolveId(preserveSymlinks)asResolveIdHook,
//rst(\'load\',[id])为异步调⽤,readFile内部⽤promise包装了le,并返回该promise
load(id){
returnreadFile(id);
},
//⽤来处理通过emitFile添加的urls或⽂件
resolveFileUrl({relativePath,format}){
//不同format会返回不同的⽂件解析地址
returnrelativeUrlMechanisms[format](relativePath);
},
//处理,参考地址:/api/#esm_import_meta)
resolveImportMeta(prop,{chunkId,format}){
//改变获取的信息的⾏为
constmechanism=importMetaMechanisms[format]&&importMetaMechanisms[format](prop,chunkId);
if(mechanism){
returnmechanism;
}
}
};
}
过⼀眼发现都是最基本处理路径解析内容的钩⼦函数。
不仅如此,rollup给钩⼦函数注⼊了context,也就是上下⽂环境,⽤来⽅便对chunks和其他构建信息进⾏增删改查。
中也写得很清楚,⽐如:
使⽤,调⽤rollup内部中的acron实例解析出ast
使⽤le来增加产出的⽂件,看这个.
我们通过transform操作来简单看下,之前对ast进⾏transform的时候,调⽤了transform钩⼦:
Driver
.hookReduceArg0
\'transform\',
[curSource,id],//和模块id
transformReducer,
//第四个参数是⼀个函数,⽤来声明某些钩⼦上下⽂中需要的⽅法
(pluginContext,plugin)=>{
//这⼀⼤堆是插件利⽤的,通过调⽤
curPlugin=plugin;
if(ey)customTransformCache=true;
elsetrackedPluginCache=getTrackedPluginCache();
return{
...pluginContext,
cache:trackedPluginCache?:,
warn(warning:RollupWarning|string,pos?:number|{col默而识之的意思 umn:number;line:number}){
if(typeofwarning===\'string\')warning={message:warning}asRollupWarning;
if(pos)augmentCodeLocation(warning,pos,curSource,id);
=id;
=\'transform\';
(warning);
},
error(err:RollupError|string,pos?:number|{column:number;line:number}):never{
if(typeoferr===\'string\')err={message:err};
if(pos)augmentCodeLocation(err,pos,curSource,id);
=id;
=\'transform\';
(err);
},
emitAsset(name:string,source?:string|Buffer){
constemittedFile={type:\'asset\'asconst,name,source};
({...emittedFile});
le(emittedFile);
},
emitChunk(id,options){
constemittedFile={type:\'chunk\'asconst,id,name:options&&};
({...emittedFile});
le(emittedFile);
},
emitFile(emittedFile:EmittedFile){
(emittedFile);
le(emittedFile);
},
addWatchFile(id:string){
(id);
chFile(id);
},
setAssetSource(assetReferenceId,source){
etSource(assetReferenceId,source);
if(!customTransformCache&&!setAssetSourceErr){
try{
({
code:\'INVALID_SETASSETSOURCE\',
message:`tFilewithasource,orcallsetAssetSourceinanotherhook.`
});
}catch(err){
setAssetSourceErr=err;
}
}
},
getCombinedSourcemap(){
constcombinedMap=collapseSourcemap(
graph,
id,
originalCode,
originalSourcemap,
sourcemapChain
);
if(!combinedMap){
constmagicString=newMagicString(originalCode);
teMap({includeContent:true,hires:true,source:id});
}
if(originalSourcemap!==combinedMap){
originalSourcemap=combinedMap;
=0;
}
returnnewSourceMap({
...combinedMap,
file:nullasany,
sourcesContent:sContent!
});
}
};
}
)
runHook中有⼀句判断,就是对上下⽂环境的使⽤:
functionrunHook
hookName:string,
args:any[],
pluginIndex:number,
permitValues:boolean,
hookContext?:ReplaceContext|null
){
//...
constplugin=s[pluginIndex];
//获取默认的上下⽂环境
letcontext=Contexts[pluginIndex];
//如果提供了,就替换
if(hookContext){
context=hookContext(context,plugin);
}
//...
}
⾄于rollup是什么时机调⽤插件提供的钩⼦函数的,这⾥就不啰嗦了,中分布很清晰,⼀看便知.
还有rollup为了⽅便咱们变化插件,还提供了⼀个,可以⾮常⽅便的进⾏模块的操作以及判断,有兴趣的⾃⾏查看。
插件的缓存
插件还提供缓存的能⼒,实现的⾮常巧妙:
exportfunctioncreatePluginCache(cache:SerializablePluginCache):PluginCache{
//利⽤闭包将cache缓存
return{
has(id:string){
constitem=cache[id];
if(!item)returnfalse;
item[0]=0;//如果访问了,那么重置访问过期次数,猜测:就是说明⽤户有意向主动去使⽤
returntrue;
},
get(id:string){
constitem=cache[id];
if(!item)returnundefined;
item[0]=0;//如果访问了,那么重置访问过期次数
returnitem[1];
},
set(id:string,value:any){
//存储单位是数组,第⼀项⽤来标记访问次数
cache[id]=[0,value];
},
delete(id:string){
returndeletecache[id];
}
};
}
然后创建缓存后,会添加在插件上下⽂中:
importcreatePluginCachefrom\'createPluginCache\';
constcacheInstance=createPluginCache(pluginCache[cacheKey]||(pluginCache[cacheKey]=(null)));
constcontext={
//...
cache:cacheInstance,
//...
}
之后我们就可以在插件中就可以使⽤cache进⾏插件环境下的缓存,进⼀步提升打包效率:
functiontestPlugin(){
return{
name:\'test-plugin\',
buildStart(){
if(!(\'prev\')){
(\'prev\',\'上⼀次插件执⾏的结果\');
}else{
//第⼆次执⾏rollup的时候会执⾏
((\'prev\'));
}
},
};
}
letcache;
asyncfunctionbuild(){
constchunks=({
input:\'src/\',
plugins:[testPlugin()],
//需要传递上次的打包结果
cache,
});
cache=;
}
build().then(()=>{
build();
});
不过需要注意的⼀点是options钩⼦函数是没有注⼊上下⽂环境的,它的调⽤⽅式也和其他钩⼦不⼀样:
functionapplyOptionHook(inputOptions:InputOptions,plugin:Plugin){
if(s){
//指定this和经过处理的input配置,并未传⼊context
({meta:{rollupVersion}},inputOptions)||inputOptions;
}
returninputOptions;
}
总结
rollup系列到此也就告⼀段落了,从开始阅读时的⼀脸懵逼,到读到依赖收集、各⼯具类的⼗脸懵逼,到现在的轻车熟路,真是⼀段难忘的经历~
学习⼤佬们的操作并取其精华,去其糟粕就像打怪升级⼀样,你品,你细品。哈哈
在这期间也是误导⼀些东西,看得多了,就会发现,其实套路都⼀样,摸索出它们的核⼼框架,再对功能缝缝补补,不断更新迭代,或许我们也可以成为开源⼤作的作者。
如果⽤⼏句话来描述rollup的话:
读取并合并配置->创建依赖图->读取⼊⼝模块内容->借⽤开源estree规范解析器进⾏源码分析,获取依赖,递归此操作->⽣成模块,挂载模块对应⽂件相关信息->分析ast,构建
各node实例->⽣成chu七律长征原文带拼音 nks->调⽤各node重写的render->利⽤magic-string进⾏字符串拼接和wrap操作->写⼊
精简⼀下就是:
字符串->AST->字符串
如果改系列能对你⼀丝丝帮忙,还请动动⼿指,⿎励⼀下~
拜了个拜~
更多推荐
rollup是什么意思lup在线翻译读音例句
发布评论