Vue 是如何用 Rollup 打包的?

大家好,我是若川。持续组织了6个月源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列

Rollup 是一个 JavaScript 模块打包器,它将小块的代码编译并合并成更大、更复杂的代码,比如打包一个库或应用程序。它使用的是 ES Modules 模块化标准,而不是之前的模块化方案,如 CommonJS 和 AMD。ES 模块可以让你自由、无缝地使用你最喜爱库中那些最有用的独立函数,而让你的项目无需包含其他未使用的代码。

近期在团队内组织学习 Rollup 专题,在着重介绍了 Rollup 核心概念和插件的 Hooks 机制后,为了让小伙伴们能够深入了解 Rollup 在实际项目中的应用。我们就把目光转向了优秀的开源项目,之后就选择了尤大的 Vue/Vite/Vue3 项目,接下来本文将先介绍 Rollup 在 Vue 中的应用。

dev 命令

vue-2.6.14 项目根目录下的 package.json 文件中,我们可以找到 scripts 字段,在该字段内定义了如何构建 Vue 项目的相关脚本。

{"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 相关的配置项:

  • -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.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 函数内部,会从 builds 对象中获取当前目标对应的构建配置对象。当目标为 '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},
}

在每个构建配置对象中,会定义 entry(入口文件)、dest (输出文件)、format(输出格式)等信息。当获取构建配置对象后,就根据 rollup 的要求生成对应的配置对象。

需要注意的是,在 Vue 项目的根目录中是没有 web 目录的,该项目的目录结构如下所示:

├── 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 插件为地址取了个别名。具体的映射规则被定义在 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')
}

根据以上的映射规则,我们可以定位到 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 命令。

build 命令

同样,在根目录下 package.jsonscripts 字段,我们可以找到 build 命令的定义:

{"name": "vue","version": "2.6.14","sideEffects": false,"scripts": {"build": "node scripts/build.js",...
}

当你运行 build 命令时,会使用 node 应用程序执行 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 文件中,会先获取所有的构建目标,然后根据进行过滤操作,最后再调用 build 函数进行构建操作,该函数的处理逻辑也很简单,就是遍历构建列表,然后调用 buildEntry 函数执行构建操作。

// 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 函数执行时,就会开始调用 buildEntry 函数,在该函数内部就是根据传入了配置对象调用 rollup.rollup API 进行构建操作:

// 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) { // 若为正式环境,则进行压缩操作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)}})
}

当打包完成后,下一个环节就是生成文件。在 buildEntry 函数中是通过调用 write 函数来生成文件:

// 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 函数内部是通过 fs.writeFile 函数来生成文件,该函数还支持 zip 参数,用于输出经过 gzip 压缩后的大小。现在我们已经分析完了 devbuild 命令,最后我们来简单介绍一下构建过程中所使用的一些核心插件。

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" 插件在前面我们已经知道它的作用了。而其他插件的作用如下:

  • rollup-plugin-buble:该插件使用 buble 转换 ES2015 代码,它已经被移到新的仓库 @rollup/plugin-buble;

  • rollup-plugin-commonjs:该插件用于把 CommonJS 模块转换为 ES6 Modules,它已经移到新的仓库 @rollup/plugin-commonjs;

  • rollup-plugin-flow-no-whitespace:该插件用于移除 flow types 中的空格;

  • rollup-plugin-node-resolve:该插件用于支持使用 node_modules 中第三方模块,会使用 Node 模块解析算法来定位模块。它也被移动到新的仓库 @rollup/plugin-node-resolve;

  • rollup-plugin-replace:该插件用于在打包时执行字符串替换操作,它也被移动到新的仓库 @rollup/plugin-replace。

除了以上的插件,在实际的项目中,你也可以使用 Rollup 官方仓库提供的插件,来实现对应的功能,具体如下图所示(仅包含部分插件):

808699bed864f97943944f3a0e07bd2b.png

(来源:https://github.com/rollup/plugins)

总结

本文只是简单介绍了 Rollup 在 Vue 2 中的应用,很多细节并没有展开介绍,感兴趣的小伙伴可以自行学习一下。如果遇到问题的话,欢迎跟我一起交流哈。另外,你们也可以自行分析一下在 Vue 3 和 Vite 项目中是如何利用 Rollup 进行打包的。

5a0e6320b2947bf8ff36b5c1d12401a1.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

6ac5979d7284a04ccba0b423324d4ff9.png

识别方二维码加我微信、拉你进源码共读

今日话题

略。分享、收藏、点赞、在看我的文章就是对我最大的支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/274893.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode 207课程表

class Solution { public:bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {//验证是否为DAG&#xff0c;每次验证指向的是否已经存在于当前图中//建图vector<int> indegree(numCourses,0);//入度vector<vector<int>> …

sketch怎么传到ps_2020年从Sketch移植到Figma的详细指南

sketch怎么传到psAs we’re locked up in our homes due to COVID-19 pandemic, many of us are working remotely and Figma is a go-to tool for designers for the same.由于COVID-19流行病使我们被关在家里&#xff0c;我们中的许多人都在远程工作&#xff0c;而Figma是设计…

还没搭建过Vue3.x项目?几行代码搞定~

大家好&#xff0c;我是若川。持续组织了6个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列相信现…

一步步创建 边栏 Gadget(二)

相信使用上篇中创建的边栏Gadget之后&#xff0c;大家会很郁闷。难道视频窗口就那么小吗&#xff1f;看起来真费劲。我能通过该Gadget看着一部电视剧。而不能够定制自己需要的或者想要看的电视剧。 在上一篇一步步创建 边栏 Gadget&#xff08;一&#xff09;中&#xff0c;我们…

tableau 自定义图表_一种新的十六进制美国地图布局的案例-Tableau中的自定义图表

tableau 自定义图表For whatever reason, 无论出于什么原因 maps are cool. Even though the earth has mostly been the same since those 地图很酷 。 即使自Pangaea days, we humans make and remake maps constantly. It might be that old maps remind us of how things …

2022,前端工具链十年盘点

大家好&#xff0c;我是若川。持续组织了6个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列2021 …

书籍排版学习心得_为什么排版是您可以学习的最佳技能

书籍排版学习心得重点 (Top highlight)I was introduced to design in a serpentine fashion. I don’t have any formal training. Instead, I’ve learned everything through the Web, books, and by interacting with designers daily.我被介绍为蛇形设计。 我没有任何正规…

若川的 2021 年度总结,弹指之间

1前言从2014年开始&#xff0c;每一年都会写年度总结&#xff0c;已经坚持了7个年头。7年的光阴就是弹指之间&#xff0c;转瞬即逝。正如孔子所说&#xff1a;逝者如斯夫&#xff0c;不舍昼夜。回顾2014&#xff0c;约定2015&#xff08;QQ空间日志&#xff09;2015年总结&…

线框图用什么软件_为什么要在线框中着色?

线框图用什么软件I was recently involved in a debate around why some wireframes (which were definitely not UI screens) were not 100% greyscale. This got me thinking — when is it ok to use colour in wireframes, and when is it going to cause you problems fur…

Linux 内核

Linux 内核是一个庞大而复杂的操作系统的核心&#xff0c;不过尽管庞大&#xff0c;但是却采用子系统和分层的概念很好地进行了组织。通过本专题&#xff0c;我们可以学习 Linux 的分层架构、内核配置和编译、内核性能调试和 Linux 2.6 中的许多提升功能。Linux 内核组成 Linux…

给asterisk写app供CLI调用

环境&#xff1a;CentOS6.2 Asterisk 1.8.7.1 一、添加源文件 复制app_verbose.c为app_testApp.c 复制app_verbose.exports为app_testApp.exports 主要是修改一些标识&#xff0c;编译不会出错就行&#xff0c;这里列出我进行的主要修改。 1、添加头文件 #include "aster…

前端,校招,面淘宝,指南

大家好&#xff0c;我是若川。持续组织了6个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列虽然是…

qq空间网页设计_网页设计中负空间的有效利用

qq空间网页设计Written by Alan Smith由艾伦史密斯 ( Alan Smith)撰写 Negative space is a key design element that you may come across in the fields of art, architecture, interior design, landscaping and web design. Rather than serving as awkward, empty areas …

Git 和 GitHub 教程——版本控制入门

大家好&#xff0c;我是若川。持续组织了6个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列Learn…

matlab中的:的优先级_内容早期设计:内容优先

matlab中的:的优先级By Simone Ehrlich, Content Strategy Manager由 西蒙埃利希 &#xff0c;内容策略经理 Words are cheap. Cheaper than wires; cheaper than mocks. That doesn’t mean words aren’t important, just less expensive to produce as a design asset. So …

我真的哭了,哭过后呢(-)

这些是山区的孩子们&#xff01; 这是他们的教室。这个也算是&#xff01;如此的师资力量自己解决吃饭问题冬天到了&#xff0c;一起烤烤火与泥土污水一起还好&#xff0c;最大的数字只是10老师抱着孩子来给我们上课了不知道山那边会是什么呢&#xff1f;又一双充满了渴望的大眼…

脑裂问题解决方案_从解决方案到问题

脑裂问题解决方案Once upon a time a couple of years ago, one of my mentors (and favourite people in the world) repeatedly drilled the idea above into my brain. Her advice for Product people was to “fall in love with the problem, not the solution”. At the …

Vue.js 官方团队成员霍春阳新作,深入解析 Vue.js 设计细节【文末送书】

霍春阳&#xff08;Hcy&#xff09;&#xff0c;Vue.js 官方团队成员。专注于 Web 研发领域&#xff0c;是 Vue.js 3 的核心贡献者之一&#xff0c;Vue.js 文档生成工具 Vuese 的作者&#xff0c;技术社区活跃者&#xff0c;曾撰写大量颇受好评的技术博客。经过一年的准备&…

LINQ之路 5:LINQ查询表达式

书写LINQ查询时又两种语法可供选择&#xff1a;方法语法&#xff08;Fluent Syntax&#xff09;和查询表达式&#xff08;Query Expression&#xff09;。 LINQ方法语法的本质是通过扩展方法和Lambda表达式来创建查询。C# 3.0对于LINQ表达式还引入了声明式的查询表达式&#xf…

调查谋杀案以换取Obra Dinn

回顾性 (RETROSPECTIVE) I am not sure if this is intentional, but Lucas Pope has a knack for turning the mundane into something special. This was evident in his release of Papers Please. In that game, you’re a border patrolman trying to provide for your fa…