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.json
的 scripts
字段,我们可以找到 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
压缩后的大小。现在我们已经分析完了 dev
和 build
命令,最后我们来简单介绍一下构建过程中所使用的一些核心插件。
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 官方仓库提供的插件,来实现对应的功能,具体如下图所示(仅包含部分插件):
(来源:https://github.com/rollup/plugins)
总结
本文只是简单介绍了 Rollup 在 Vue 2 中的应用,很多细节并没有展开介绍,感兴趣的小伙伴可以自行学习一下。如果遇到问题的话,欢迎跟我一起交流哈。另外,你们也可以自行分析一下在 Vue 3 和 Vite 项目中是如何利用 Rollup 进行打包的。
················· 若川简介 ·················
你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。
识别上方二维码加我微信、拉你进源码共读群
今日话题
略。分享、收藏、点赞、在看我的文章就是对我最大的支持~
Vue 是如何用 Rollup 打包的?相关推荐
- Rollup 打包并发布到 npm
前言 其实用 webpack 也可以打包库,不过根据create-react-app项目贡献者的说法:rollup适合发布 js 库,而webpack更适合做应用程序.简而言之就是:rollup 打包 ...
- 如何使用rollup打包前端组件/库
如何使用rollup打包前端组件/库 目前主流的前端框架vue和react都采用rollup来打包,为了探索rollup的奥妙,接下来就让我们一步步来探索,并基于rollup搭建一个库打包脚手架,来发 ...
- 使用模块化工具Rollup打包自己开发的JS库
使用模块化工具Rollup打包自己开发的JS库 打包JS库demo项目地址:https://github.com/Miazzy/xdata-utils-btools 背景 最近有个需求,需要为小程序写 ...
- rollup打包工具
Rollup webpack的打包比较繁琐,打包体积比较大. rollup主要是用来打包js库的. vue/react等都在用rollup作为打包工具. 使用 安装依赖 yarn add @babel ...
- 用 rollup 打包 library
打包一个最简单的 library 新建 test-rollup 文件夹.进入文件夹,执行 pnpm init 自动生成 package.json 文件: {"name": &quo ...
- vuejs出的手机app有哪些_详解Vue webapp项目通过HBulider打包原生APP
Vue webapp项目通过HBulider打包原生APP 1.webapp项目已经通过vue-cli搭建的脚手架写好了,然后通过webpack打包成一个部署文件list,如下: 2.打开HBulid ...
- 使用Gradle整合SpringBoot+Vue.js-开发调试与打包
为什么80%的码农都做不了架构师?>>> 非常感谢两位作者: kevinz分享的文章<springboot+gradle+vue+webpack 组合使用> 首席卖 ...
- vue中实现文件批量打包压缩下载(以及下载跨域问题分析)
上次做了一个选择多个数据生成多个二维码并下载,当时项目催的紧,就简单写了个循环生成二维码下载,一次性会下载很多文件,特别难整理: 刚好这次项目又遇到类似这种功能,需要一次性批量下载多个文件,那么就安排 ...
- 怎么将vue的移动端项目打包成手机的app软件apk格式的。hbuilderx 云打包。
献丑了 第一步注册hbuilderx账号需要实名认证,要不然获取appid会获取不了. hbuilder 下载 https://www.dcloud.io/hbuilderx.html vue项目np ...
最新文章
- Java--------------Mysql中时间按要求查询
- 利用NLTK进行分句分词
- nginx php环境搭建_php+nginx环境配置
- linux 运行选择哪个cpu核,判断Linux进程在哪个CPU核运行的方法
- 开源流媒体云视频平台EasyDarwin中EasyCMS服务是如何进行命令转发和消息路由的
- Jenkins 自动化集成之路 Linux 安装 maven
- codevs 3186 队列练习2
- java程序包r不存在_java - 从命令行使用Gradle构建时,“程序包R不存在”错误 - 堆栈内存溢出...
- 李宏毅机器学习——无监督学习(三)
- Linux进阶之排错
- Monotonic Renumeration- codeforce
- 每天CookBook之JavaScript-018
- LIVE MINI ESP32开发板教程系列(四)NeoPixel + ws2812b实现炫彩显示
- 卡尔曼滤波-卡尔曼滤波全篇讲解
- 2011年安徽省公务员考试行测真题(3)
- 依图科技从科创板“退赛”:三年半累计亏损72亿,研发费用高企
- Canonical Coordinate System
- 水晶报表 图表 百分比
- 大型网站架构演变过程、大并发服务器架构
- Markdown语法学习
热门文章
- DOxygen for C++使用说明——Markdown支持
- vfp赋值超过7位出错_JDK1.7下的HashMap的源码分析
- python编程入门第一课_python入门前的第一课 python怎样入门
- 商品评价判别,文本分类——学习笔记
- nodejs之express入门
- OnItemClickListener,OnScrollListener应用
- [转]让你赚大钱成富翁的4个投资习惯
- WaitForSingleObject的用法
- # 和 ## 的区别
- 全国计算机等级考试题库二级C操作题100套(第85套)