共计 9789 个字符,预计需要花费 25 分钟才能阅读完成。
rollup 简介
首先,rollup.js 是一个 JavaScript 模块打包器。
能够将咱们本人编写的 js 代码与第三方模块打包在一起,也能够将小块代码编译成大块简单的代码,例如 library 或应用程序。
rollup 间接反对 tree shaking 只有 ES 模块才反对,在打包构建时,会对编译的代码进行动态剖析,打包后果只蕴含应用到的代码,这样能够大幅精简代码量。
和咱们相熟的 webpack 相比,它更专一于 js 类库打包,而 webpack 更偏差于利用打包。
Rollup 根本利用
从 0 应用 Rollup 实现一个根底应用案例, 全程应用 npm 操作,yarn 同理(论团队对立 npm 和 yarn 以及对应版本对立的重要性)
初始化操作
npm i rollup -g | |
mkdir roolup-demo | |
cd roolup-demo | |
npm init | |
mkdir src | |
vim src/name.js | |
// i 输出 | |
const name = 'Ada' | |
export default name | |
// esc :wq | |
vim src/main.js | |
// 输出 | |
import name from './name.js' | |
export default function() {console.log(name) | |
} |
预览打包后源码
rollup src/main.js -f es
输入
解释一下命令参数
-f –format 的缩写,代表生成代码的格局,amd 就是 AMD 规范,cjs 是 commonjs,es 就是 es 规范
上面咱们输入一下构建后的文件
rollup src/main.js -f es -o dist/output.js
-o 等同 output.file
输入文件:
再来输入一个 commonjs 格调文件
rollup src/main.js --format cjs --o dist/output-cjs.js
test
$ node | |
> const fn = require('./dist/index-es.js') | |
> fn() | |
Ada |
拓展 node 不反对 ES 规范 如何解决
所以默认以上代码应用 node 执行 es 版本会报错, 咱们能够应用 babel-node 解决
装置如下
npm i @babel/core @babel/node @babel/cli -g
而后创立 babel 的配置文件.babelrc
touch .babelrc | |
// 内容 | |
{"presets": ["@babel/preset-env"] | |
} |
装置依赖
npm i -D @babel/core @babel/preset-env
而后通过 babel 编译代码
babel dist/output.js | |
$ babel-node | |
> require('./dist/output.js') | |
{default: [Function: main] } | |
> require('./dist/output.js').default() | |
Ada |
这里 babel 认为 default 是 function name
上面再介绍一下 rollup.js 配置文件
// 创立配置文件 | |
touch rollup.config.js |
内容如下
export default { | |
input: './src/main.js', | |
output: [{ | |
file: './dist/index-cjs.js', | |
format: 'cjs', | |
banner: '// cjs banner', | |
footer: '// cjs footer' | |
}, { | |
file: './dist/index-es.js', | |
format: 'es', | |
banner: '// es test banner', | |
footer: '// es test footer' | |
}] | |
} |
rollup 的配置文件阐明
rollup 的配置文件须要采纳 ES 模块规范编写
input 示意入口文件的门路
output 示意输入文件的内容:
- output.file:输入文件的门路(老版本为 dest,曾经废除)
- output.format:输入文件的格局
- output.banner:文件头部增加的内容
- output.footer:文件开端增加的内容
有了配置文件就能够间接应用 rollup - c 进行打包了
最初咱们来实现一个简略地 build 代码
npm i -D rollup | |
touch rollup-build.js | |
touch rollup-input.js | |
touch rollup-output.js | |
// 内容 | |
// input | |
module.exports = {input: './src/main.js'} | |
// output | |
module.exports = [{ | |
file: './dist/index-cjs.js', | |
format: 'cjs', | |
banner: '// hello', | |
footer: '// bye' | |
}, | |
{ | |
file: './dist/index-es.js', | |
format: 'es', | |
banner: '// hello A', | |
footer: '// bye A' | |
},{ | |
file: './dist/index-amd.js', | |
format: 'amd', | |
banner: '// hello B', | |
footer: '// bye B' | |
}, | |
{ | |
file: './dist/index-umd.js', | |
format: 'umd', | |
name: 'umd', | |
banner: '// hello C', | |
footer: '// bye C' | |
}] | |
// build | |
// 通过 rollup.rollup(input)获取输出 | |
// 通过 bundle.write(output)输入文件 | |
const rollup = require('rollup') | |
const inputOptions = require('./rollup-input') | |
const outputOptions = require('./rollup-output') | |
async function rollupBuild(input, output) {const bundle = await rollup.rollup(input) // 依据 input 配置进行打包 | |
console.log(`current:${output.file}`) | |
await bundle.write(output) // 依据 output 配置输入文件 | |
console.log(`${output.file} success!`) | |
} | |
(async function () {for (let i = 0; i < outputOptions.length; i++) {await rollupBuild(inputOptions, outputOptions[i]) | |
} | |
})() |
执行测试一下
node rollup-build.js
根底拓展:watch 监听文件变动主动 build
touch rollup-watch-build.js | |
touch rollup-watch.js | |
module.exports = { | |
include: 'src/**', // 监听的文件夹 | |
exclude: 'node_modules/**' // 排除监听的文件夹 | |
} |
watch build
const rollup = require('rollup') | |
const inputOptions = require('./rollup-input') | |
const outputOptions = require('./rollup-output') | |
const watchOptions = require('./rollup-watch') | |
const options = { | |
...inputOptions, | |
output: outputOptions, | |
watchOptions | |
} | |
const watcher = rollup.watch(options) // 调用 rollup 的 api 启动监听 | |
watcher.on('event', event => {console.log('从新打包...', event.code) | |
}) |
这样当咱们批改 src 目录文件后就会主动从新打包
平时咱们也能够脚本间接写 rollup -wc 命令
rollup 插件扫盲 不便浏览 vue 构建源码
插件 | 形容 |
---|---|
resolve 插件 | 集成内部模块 |
commonjs 插件 | 反对 CommonJS 模块 |
babel 插件 | 编译 ES6 语法,兼容低版本浏览器 |
commonjs 插件 | 反对 CommonJS 模块 |
uglify 插件 | 代码最小化打包 |
json 插件 | 反对 json |
插件的应用都很简略,大家能够按需进行尝试
上面咱们进入 vue build 局部, 去剖析一下在 vue 源码中 build 做了那些事。
vue build 剖析
间接从源码动手,以下代码外围局部已削减对应备注。
1. 首先将下载源码到本地装置依赖
git clone https://github.com/vuejs/vue.git" | |
cd vuejs | |
npm i |
2. 咱们来看一下 package.json 咱们以后须要关怀的局部
"scripts": { | |
"build": "node scripts/build.js", | |
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer", | |
"build:weex": "npm run build -- weex" | |
... | |
}, | |
"devDependencies": { | |
"rollup": "^1.0.0", | |
"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", | |
... | |
} |
由依赖能够看出 vue 有应用 rollup
上面咱们依据 build scripts 去看一下构建脚本都做了什么事
3.scripts/build.js
正文曾经退出源码之中 咱们间接来看源码 一个惯例 vue 源码构建咱们次要看以下三个文件就能够 build.js config.js alias.js
build 执行大抵能够分为以下 5 局部
以下为 build.js 源码请联合正文进行查看
const fs = require('fs') // 文件解决 | |
const path = require('path') // 本地门路解析 | |
const zlib = require('zlib') // gzip 压缩 | |
const rollup = require('rollup') // 打包工具 | |
const terser = require('terser') // 代码压缩 | |
// 1、创立 dist 目录 | |
// 判断 dist 是否存在 不存在进行创立 | |
if (!fs.existsSync('dist')) {fs.mkdirSync('dist') | |
} | |
// 2、通过 config 生成 rollup 配置 | |
let builds = require('./config').getAllBuilds() | |
// 3、rollup 配置文件过滤。// 依据传入的参数,对 rollup 配置文件的内容进行过滤,排除不必要的打包我的项目 | |
// filter builds via command line arg | |
console.log('process.argv', red(process.argv)) | |
if (process.argv[2]) {const filters = process.argv[2].split(',') | |
console.log('filters', red(filters)) | |
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 default | |
builds = builds.filter(b => {return b.output.file.indexOf('weex') === -1 | |
}) | |
} | |
// 4.rollup 打包 | |
build(builds) | |
function build (builds) { | |
let built = 0 // 以后打包项序号 | |
const total = builds.length // // 须要打包的总次数 | |
const next = () => {buildEntry(builds[built]).then(() => { | |
built++ | |
if (built < total) {next() // 如果打包序号小于打包总次数,则继续执行 next()函数} | |
}).catch(logError) | |
} | |
next()} | |
// 打包执行外围函数 | |
function buildEntry (config) { | |
// 获取 rollup 配置信息 | |
const output = config.output | |
const {file, banner} = output | |
const isProd = /(min|prod)\.js$/.test(file) // 是否压缩 min 结尾 标识 | |
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'] | |
} | |
}).code | |
return write(file, minified, true) | |
} else {return write(file, code) | |
} | |
}) | |
} | |
// 5. 文件输入 zlib.gzip 压缩 | |
// dest 门路 code 源码 | |
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() | |
} | |
}) | |
}) | |
} | |
// 获取文件大小 | |
function getSize (code) {return (code.length / 1024).toFixed(2) + 'kb' | |
} | |
function logError (e) {console.log(e) | |
} | |
// 生成蓝色文本 ANSI 本义码 \x1b[(文字装璜);(颜色代码): | |
function blue (str) {return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'} | |
// 同理咱们实现一个 输出红色文本 | |
function red (str) {return '\x1B[31m' + str + '\x1B[39m'} |
config.js 选取局部内容
const path = require('path') // 本地门路解析 | |
const buble = require('rollup-plugin-buble') // es6+ 语法转为 es5 | |
const alias = require('rollup-plugin-alias') // 替换模块别名 | |
const cjs = require('rollup-plugin-commonjs') // 反对 commonjs 模块 | |
const replace = require('rollup-plugin-replace') // 替换代码中变量为指定值 | |
const node = require('rollup-plugin-node-resolve') // 内部模块集成 | |
const flow = require('rollup-plugin-flow-no-whitespace') // 去除 flow 动态类型查看 | |
const version = process.env.VERSION || require('../package.json').version // 获取以后版本 | |
const weexVersion = process.env.WEEX_VERSION || require('../packages/weex-vue-framework/package.json').version | |
// weex 版本 Weex 是一款轻量级的挪动端跨平台动态性技术解决方案 The Weex podling retired on 2021-05-14 | |
const featureFlags = require('./feature-flags') // 增加 2 个构建常量 | |
// 打包后 banner | |
const banner = | |
'/*!\n' + | |
` * Vue.js v${version}\n` + | |
` * (c) 2014-${new Date().getFullYear()} Evan You\n` + | |
'* Released under the MIT License.\n' + | |
'*/' | |
// 打包 weex-factory 应用 | |
const weexFactoryPlugin = {intro () {return 'module.exports = function weexFactory (exports, document) {'}, | |
outro () {return '}' | |
} | |
} | |
const aliases = require('./alias') // 别名及其对应的绝对路径 | |
const resolve = p => {const base = p.split('/')[0] | |
// aliases 匹配 name 是否存在 | |
if (aliases[base]) {return path.resolve(aliases[base], p.slice(base.length + 1)) | |
} else { | |
// 不存在则合并根门路和传入 | |
return path.resolve(__dirname, '../', p) | |
} | |
} | |
// build 配置 | |
const builds = {// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify | |
'web-runtime-cjs-dev': {entry: resolve('web/entry-runtime.js'), | |
dest: resolve('dist/vue.runtime.common.dev.js'), | |
format: 'cjs', | |
env: 'development', | |
banner | |
}, | |
'web-runtime-cjs-prod': {entry: resolve('web/entry-runtime.js'), | |
dest: resolve('dist/vue.runtime.common.prod.js'), | |
format: 'cjs', | |
env: 'production', | |
banner | |
}, | |
// Runtime+compiler CommonJS build (CommonJS) | |
'web-full-cjs-dev': {entry: resolve('web/entry-runtime-with-compiler.js'), | |
dest: resolve('dist/vue.common.dev.js'), | |
format: 'cjs', | |
env: 'development', | |
alias: {he: './entity-decoder'}, | |
banner | |
}, | |
'web-full-cjs-prod': {entry: resolve('web/entry-runtime-with-compiler.js'), | |
dest: resolve('dist/vue.common.prod.js'), | |
format: 'cjs', | |
env: 'production', | |
alias: {he: './entity-decoder'}, | |
banner | |
}, | |
// Runtime only ES modules build (for bundlers) | |
'web-runtime-esm': {entry: resolve('web/entry-runtime.js'), | |
dest: resolve('dist/vue.runtime.esm.js'), | |
format: 'es', | |
banner | |
}, | |
... | |
} | |
// 依据 name 生成对应环境的 rollup 配置 | |
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) | |
} | |
} | |
} | |
// built-in vars | |
const vars = { | |
__WEEX__: !!opts.weex, | |
__WEEX_VERSION__: weexVersion, | |
__VERSION__: version | |
} | |
// feature flags | |
Object.keys(featureFlags).forEach(key => {vars[`process.env.${key}`] = featureFlags[key] | |
}) | |
// build-specific env | |
if (opts.env) {vars['process.env.NODE_ENV'] = JSON.stringify(opts.env) | |
} | |
config.plugins.push(replace(vars)) | |
if (opts.transpile !== false) {config.plugins.push(buble()) | |
} | |
Object.defineProperty(config, '_name', { | |
enumerable: false, | |
value: name | |
}) | |
// console.log('vars', vars) | |
// console.log('config', JSON.stringify(config, null, 2)) | |
return config | |
} | |
// 判断环境变量 TARGET 是否定义 存在即输入 | |
if (process.env.TARGET) {module.exports = genConfig(process.env.TARGET) | |
} else { | |
exports.getBuild = genConfig | |
exports.getAllBuilds = () => Object.keys(builds).map(genConfig) | |
} |
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') | |
} |
以上就是本次分享的内容,心愿大家能够一步一步跟着去操作,进行实际。