乐趣区

关于javascript:rollup入门以及vue使用rollup构建源码分析

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')
}

以上就是本次分享的内容,心愿大家能够一步一步跟着去操作,进行实际。

退出移动版