Rollup是一个JavaScript模块打包器,它可以将小块代码编译并组合成更大、更复杂的代码,例如打包库或应用程序。它使用ESModules模块化标准,而不是以前的CommonJS和AMD等模块化方案。ES模块让你可以自由无缝地使用你最喜欢的库中最有用的独立函数,而不必在你的项目中包含其他未使用的代码。最近在团队里整理研究了Rollup这个话题。在重点介绍了Rollup的核心概念和插件的Hooks机制之后,为了让小伙伴对Rollup在实际项目中的应用有一个深入的了解。我们把目光投向了优秀的开源项目,然后选择了友达的Vue/Vite/Vue3项目。接下来,本文将首先介绍Rollup在Vue中的应用。dev命令在vue-2.6.14项目根目录下的package.json文件中。我们可以找到scripts字段,它定义了如何构建Vue项目的相关脚本。{“名称”:“vue”,“版本”:“2.6.14”,“sideEffects”:false,“脚本”:{“dev”:“rollup-w-cscripts/config.js--environmentTARGET:web-full-dev","dev:cjs":"rollup-w-cscripts/config.js--environmentTARGET:web-runtime-cjs-dev",...}这里我们将dev命令作为一个例如,介绍一下rollup相关的配置项:-c:指定rollup打包的配置文件;-w:开启监听模式,当文件发生变化时,自动打包;--environment:设置环境变量,设置后可以通过process.env对象获取配置值。从dev命令可以看出rollup配置文件为scripts/config.js://scripts/config.js//省略大部分代码if(process.env.TARGET){module.exports=genConfig(process.env.TARGET)}else{exports.getBuild=genConfigexports.getAllBuilds=()=>Object.keys(builds).map(genConfig)}观察上面的代码,我们可以看到当process.env.TARGET有一个值,会根据TARGET封装配置对象的值动态生成。//scripts/config.jsfunctiongenConfig(name){constopts=builds[name]constconfig={input:opts.entry,external:opts.external,plugins:[flow(),alias(Object.assign({},别名,opts.alias))].concat(opts.plugins||[]),output:{file:opts.dest,format:opts.format,banner:opts.banner,name:opts.moduleName||'Vue'},onwarn:(msg,warn)=>{if(!/Circular/.test(msg)){warn(msg)}}}//省略部分代码returnconfig}在genConfig函数里面,它将从builds对象中获取当前目标对应的构建配置对象。当target为'web-full-dev'时,其对应的配置对象如下://scripts/config.jsconstbuilds={'web-runtime-cjs-dev':{...},'web-runtime-cjs-prod':{...},//运行时+编译器开发构建(浏览器)'web-full-dev':{entry:resolve('web/entry-runtime-with-compiler.js'),dest:resolve('dist/vue.js'),format:'umd',env:'development',alias:{he:'./entity-decoder'},banner},}在每个构建配置对象中,会定义entry(入口文件)、dest(输出文件)、format(输出格式)等信息。获取构建配置对象后,根据rollup的要求生成对应的配置对象。需要注意的是,Vue项目的根目录下是没有web目录的。项目目录结构如下:├──BACKERS.md├──LICENSE├──README.md├──benchmarks├──dist├──examples├──flow├──package.json├──packages├──scripts├──src├──test├──types└──yarn.lockthenweb/entry-runtime-with-compiler.js入口文件的位置在哪里?其实rollup的@rollup/plugin-alias插件就是用来给地址起别名的。具体的映射规则定义在scripts/alias.js文件中://scripts/alias.jsconstpath=require('path')constresolve=p=>path.resolve(__dirname,'../',p)module.exports={vue:resolve('src/platforms/web/entry-runtime-with-compiler'),compiler:resolve('src/compiler'),core:resolve('src/core'),shared:resolve('src/shared'),web:resolve('src/platforms/web'),weex:resolve('src/platforms/weex'),server:resolve('src/server'),证监会:resolve('src/sfc')}根据上面的映射规则,我们可以定位到web别名对应的路径。该路径对应的文件结构如下:├──compiler├──entry-compiler.js├──entry-runtime-with-compiler.js├──entry-runtime.js├──entry-server-basic-renderer.js├──entry-server-renderer.js├──runtime├──server└──util这里结合前面介绍的builds对象,相信大家也知道Vue是如何打包不同类型的文件来满足不同场景的需求,例如带编译器和不带编译器的版本。分析完dev命令的处理流程,我来分析一下build命令。构建命令同上,在根目录下package.json的scripts字段中,我们可以找到构建命令的定义:{"name":"vue","version":"2.6.14","sideEffects":false,"scripts":{"build":"nodescripts/build.js",...}当您运行构建命令时,scripts/build.js文件将与节点应用程序一起执行://scripts/build.jsletbuilds=require('./config').getAllBuilds()//通过命令行过滤构建argif(process.argv[2]){constfilters=process.argv[2].split(',')builds=builds.filter(b=>{returnfilters.some(f=>b.output.file.indexOf(f)>-1||b._name.indexOf(f)>-1)})}else{//默认过滤掉weex构建builds=builds.filter(b=>{returnb.output.file.indexOf('weex')===-1})}build(builds)在scripts/build.js文件,都是构建目标,然后根据操作进行过滤,最后调用构建函数进行构建操作。这个函数的处理逻辑也很简单,就是遍历构建列表,然后调用buildEntry函数执行构建操作。//scripts/build.jsfunctionbuild(builds){letbuilt=0consttotal=builds.lengthconstnext=()=>{buildEntry(builds[built]).then(()=>{built++if(built
