Vue 是如何用 Rollup 打包的?
大家好,我是若川。持續(xù)組織了6個月源碼共讀活動,感興趣的可以點此加我微信 ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構(gòu)系列》?包含20余篇源碼文章。歷史面試系列
Rollup 是一個 JavaScript 模塊打包器,它將小塊的代碼編譯并合并成更大、更復雜的代碼,比如打包一個庫或應用程序。它使用的是 ES Modules 模塊化標準,而不是之前的模塊化方案,如 CommonJS 和 AMD。ES 模塊可以讓你自由、無縫地使用你最喜愛庫中那些最有用的獨立函數(shù),而讓你的項目無需包含其他未使用的代碼。
近期在團隊內(nèi)組織學習 Rollup 專題,在著重介紹了 Rollup 核心概念和插件的 Hooks 機制后,為了讓小伙伴們能夠深入了解 Rollup 在實際項目中的應用。我們就把目光轉(zhuǎn)向了優(yōu)秀的開源項目,之后就選擇了尤大的 Vue/Vite/Vue3 項目,接下來本文將先介紹 Rollup 在 Vue 中的應用。
dev 命令
在 vue-2.6.14 項目根目錄下的 package.json 文件中,我們可以找到 scripts 字段,在該字段內(nèi)定義了如何構(gòu)建 Vue 項目的相關(guān)腳本。
{"name":?"vue","version":?"2.6.14","sideEffects":?false,"scripts":?{"dev":?"rollup?-w?-c?scripts/config.js?--environment?TARGET:web-full-dev","dev:cjs":?"rollup?-w?-c?scripts/config.js?--environment?TARGET:web-runtime-cjs-dev",... }這里我們以 dev 命令為例,來介紹一下與 rollup 相關(guān)的配置項:
-c:指定 rollup 打包的配置文件;
-w:開啟監(jiān)聽模式,當文件發(fā)生變化的時候,會自動打包;
--environment:設置環(huán)境變量,設置后可以通過 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 有值的話,就會根據(jù) TARGET 的值動態(tài)生成打包配置對象。
//?scripts/config.js function?genConfig?(name)?{const?opts?=?builds[name]const?config?=?{input:?opts.entry,external:?opts.external,plugins:?[flow(),alias(Object.assign({},?aliases,?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)}}}//?省略部分代碼return?config }在 genConfig 函數(shù)內(nèi)部,會從 builds 對象中獲取當前目標對應的構(gòu)建配置對象。當目標為 'web-full-dev' 時,它對應的配置對象如下所示:
//?scripts/config.js const?builds?={?'web-runtime-cjs-dev':?{?...?},'web-runtime-cjs-prod':?{?...?},//?Runtime+compiler?development?build?(Browser)'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}, }在每個構(gòu)建配置對象中,會定義 entry(入口文件)、dest (輸出文件)、format(輸出格式)等信息。當獲取構(gòu)建配置對象后,就根據(jù) rollup 的要求生成對應的配置對象。
需要注意的是,在 Vue 項目的根目錄中是沒有 web 目錄的,該項目的目錄結(jié)構(gòu)如下所示:
├──?BACKERS.md ├──?LICENSE ├──?README.md ├──?benchmarks ├──?dist ├──?examples ├──?flow ├──?package.json ├──?packages ├──?scripts ├──?src ├──?test ├──?types └──?yarn.lock那么 web/entry-runtime-with-compiler.js 入口文件的位置在哪呢?其實是利用了 rollup 的 @rollup/plugin-alias 插件為地址取了個別名。具體的映射規(guī)則被定義在 scripts/alias.js 文件中:
//?scripts/alias.js const?path?=?require('path') const?resolve?=?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'),sfc:?resolve('src/sfc') }根據(jù)以上的映射規(guī)則,我們可以定位到 web 別名對應的路徑,該路徑對應的文件結(jié)構(gòu)如下:
├──?compiler ├──?entry-compiler.js ├──?entry-runtime-with-compiler.js ├──?entry-runtime.js ├──?entry-server-basic-renderer.js ├──?entry-server-renderer.js ├──?runtime ├──?server └──?util到這里結(jié)合前面介紹的 builds 對象,相信你也知道了 Vue 是如何打包不同類型的文件,以滿足不同場景的需求,比如含有編譯器和不包含編譯器的版本。分析完 dev 命令的處理流程,下面我來分析 build 命令。
build 命令
同樣,在根目錄下 package.json 的 scripts 字段,我們可以找到 build 命令的定義:
{"name":?"vue","version":?"2.6.14","sideEffects":?false,"scripts":?{"build":?"node?scripts/build.js",... }當你運行 build 命令時,會使用 node 應用程序執(zhí)行 scripts/build.js 文件:
//?scripts/build.js let?builds?=?require('./config').getAllBuilds()//?filter?builds?via?command?line?arg if?(process.argv[2])?{const?filters?=?process.argv[2].split(',')builds?=?builds.filter(b?=>?{return?filters.some(f?=>?b.output.file.indexOf(f)?>?-1?||?b._name.indexOf(f)?>?-1)}) }?else?{//?filter?out?weex?builds?by?defaultbuilds?=?builds.filter(b?=>?{return?b.output.file.indexOf('weex')?===?-1}) }build(builds)在 scripts/build.js 文件中,會先獲取所有的構(gòu)建目標,然后根據(jù)進行過濾操作,最后再調(diào)用 build 函數(shù)進行構(gòu)建操作,該函數(shù)的處理邏輯也很簡單,就是遍歷構(gòu)建列表,然后調(diào)用 buildEntry 函數(shù)執(zhí)行構(gòu)建操作。
//?scripts/build.js function?build?(builds)?{let?built?=?0const?total?=?builds.lengthconst?next?=?()?=>?{buildEntry(builds[built]).then(()?=>?{built++if?(built?<?total)?{next()}}).catch(logError)}next() }當 next 函數(shù)執(zhí)行時,就會開始調(diào)用 buildEntry 函數(shù),在該函數(shù)內(nèi)部就是根據(jù)傳入了配置對象調(diào)用 rollup.rollup API 進行構(gòu)建操作:
//?scripts/build.js function?buildEntry?(config)?{const?output?=?config.outputconst?{?file,?banner?}?=?outputconst?isProd?=?/(min|prod)\.js$/.test(file)return?rollup.rollup(config).then(bundle?=>?bundle.generate(output)).then(({?output:?[{?code?}]?})?=>?{if?(isProd)?{?//?若為正式環(huán)境,則進行壓縮操作const?minified?=?(banner???banner?+?'\n'?:?'')?+?terser.minify(code,?{toplevel:?true,output:?{ascii_only:?true},compress:?{pure_funcs:?['makeMap']}}).codereturn?write(file,?minified,?true)}?else?{return?write(file,?code)}}) }當打包完成后,下一個環(huán)節(jié)就是生成文件。在 buildEntry 函數(shù)中是通過調(diào)用 write 函數(shù)來生成文件:
//?scripts/build.js const?fs?=?require('fs')function?write?(dest,?code,?zip)?{return?new?Promise((resolve,?reject)?=>?{function?report?(extra)?{console.log(blue(path.relative(process.cwd(),?dest))?+?'?'?+?getSize(code)?+?(extra?||?''))resolve()}fs.writeFile(dest,?code,?err?=>?{if?(err)?return?reject(err)if?(zip)?{zlib.gzip(code,?(err,?zipped)?=>?{if?(err)?return?reject(err)report('?(gzipped:?'?+?getSize(zipped)?+?')')})}?else?{report()}})}) }write 函數(shù)內(nèi)部是通過 fs.writeFile 函數(shù)來生成文件,該函數(shù)還支持 zip 參數(shù),用于輸出經(jīng)過 gzip 壓縮后的大小。現(xiàn)在我們已經(jīng)分析完了 dev 和 build 命令,最后我們來簡單介紹一下構(gòu)建過程中所使用的一些核心插件。
rollup 插件
在 package.json ?文件中,我們可以看到 Vue2 項目中用到的 rollup 插件:
//?package.json {"name":?"vue","version":?"2.6.14","devDependencies":?{"rollup-plugin-alias":?"^1.3.1","rollup-plugin-buble":?"^0.19.6","rollup-plugin-commonjs":?"^9.2.0","rollup-plugin-flow-no-whitespace":?"^1.0.0","rollup-plugin-node-resolve":?"^4.0.0","rollup-plugin-replace":?"^2.0.0",} }其中,"rollup-plugin-alias" 插件在前面我們已經(jīng)知道它的作用了。而其他插件的作用如下:
rollup-plugin-buble:該插件使用 buble 轉(zhuǎn)換 ES2015 代碼,它已經(jīng)被移到新的倉庫 @rollup/plugin-buble;
rollup-plugin-commonjs:該插件用于把 CommonJS 模塊轉(zhuǎn)換為 ES6 Modules,它已經(jīng)移到新的倉庫 @rollup/plugin-commonjs;
rollup-plugin-flow-no-whitespace:該插件用于移除 flow types 中的空格;
rollup-plugin-node-resolve:該插件用于支持使用 node_modules 中第三方模塊,會使用 Node 模塊解析算法來定位模塊。它也被移動到新的倉庫 @rollup/plugin-node-resolve;
rollup-plugin-replace:該插件用于在打包時執(zhí)行字符串替換操作,它也被移動到新的倉庫 @rollup/plugin-replace。
除了以上的插件,在實際的項目中,你也可以使用 Rollup 官方倉庫提供的插件,來實現(xiàn)對應的功能,具體如下圖所示(僅包含部分插件):
(來源:https://github.com/rollup/plugins)
總結(jié)
本文只是簡單介紹了 Rollup 在 Vue 2 中的應用,很多細節(jié)并沒有展開介紹,感興趣的小伙伴可以自行學習一下。如果遇到問題的話,歡迎跟我一起交流哈。另外,你們也可以自行分析一下在 Vue 3 和 Vite 項目中是如何利用 Rollup 進行打包的。
·················?若川簡介?·················
你好,我是若川,畢業(yè)于江西高校。現(xiàn)在是一名前端開發(fā)“工程師”。寫有《學習源碼整體架構(gòu)系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結(jié),已經(jīng)寫了7篇,點擊查看年度總結(jié)。
同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內(nèi)前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。分享、收藏、點贊、在看我的文章就是對我最大的支持~
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的Vue 是如何用 Rollup 打包的?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(2962):前一天回顾
- 下一篇: 还没搭建过Vue3.x项目?几行代码搞定