我爱她。的英文。翻译。英语怎么说-普什图语


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=e();

//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=e();

for(leti=0;i<;i++)

promise=(()=>

k(hookName,argsasany[],i,false,replaceContext)秋夜曲王维拼音

);

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(hookName,argsasany[],i,false,replaceContext);

//如果没有那么不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在线翻译读音例句