vue3-rollup打包

首先先介绍需要的依赖

依赖 作用
typescript 支持typescript
rollup 打包工具
rollup-plugin-typescript2 rollup和ts的桥梁
@rollup/plugin-node-resolve 解析node第三方模块
@rollup/plugin-json 支持引入json
execa 开启子进程方便执行命令

初始化项目 yarn init -y
然后配置 package.json文件

{
  "private": true,// 私有
  "workspaces":[ //工作的目录
    "packages/*"
  ],
  "name": "rollup-vue3",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}

workspaces 规定我们的代码 都在 packages目录下

所有新建一个packages文件夹

然后 这次的例子我们创建 两个文件夹 分别为 reactivity 和shared 实现vue3的这两个功能 。并且进入 这两个文件夹 分别 初始化 yarn init -y

然后再到 reactivity和shared 文件夹下创建 入口文件 src/index.ts

接下来 我们 要修改 reactivity 和shared 文件夹下的 package.json文件;
比如 reactivity 的初始化 文件

{
  "name": "reactivity",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}

我们要把name 修改一下 ,为了打包的时候告诉rollup 这个包叫什么。
所有 vue的包都用 @vue/ 开头

{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}

还需要一个入口 ,main是给commonjs服务端用的(require)。如果我们要用 es6(import),需要增加一个入口 module

{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "main": "index.js",
  "module": "dist/reactivity.esm-bundler.js",
  "license": "MIT"
}

入口文件为 打包后的 dist文件夹下的reactivity.esm-bundler.js 文件

继续
我们还需要配置一个自定义 配置属性 buildOptions

{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "main": "index.js",
  "module": "dist/reactivity.esm-bundler.js",
  "license": "MIT",
  "buildOptions": {
    "name": "VueReactivity", //打包成 全局模块 的命名 类似于暴露的方法挂载到window.VueReactivity上
    "formats": [    // 当前模块可以构建成哪些模块
      "cjs",  // commonjs
      "esm-bundler", // es6 module
      "global" // 全局 模块
    ]
  }
}

buildOptions中 name 是为了给打包为全局模考的命名, 类似于暴露的方法挂载到window.VueReactivity上
formats 是 告诉rollup 我需要打包出多少种模块
其中

  • cjs-->commonjs
  • esm-bundler --> es6 module
  • global --> 全局

shared模块我们不需要打包为全局. 其实这里的name: VueShared 没有用到

{
  "name": "@vue/shared",
  "version": "1.0.0",
  "main": "index.js",
  "module": "dist/shared.esm-bundler.js",
  "license": "MIT",
  "buildOptions": {
    "name": "VueShared",
    "formats": [    
      "cjs", 
      "esm-bundler" 
    ]
  }
}

安装依赖
yarn add typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa
安装依赖会报错

image.png

因为项目中有多个package.json文件, 如果只给 根安装 需要添加 --ignore-workspace-root-check

重新安装依赖
yarn add typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa --ignore-workspace-root-check 或者
yarn add typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa -W

如果安装的时候 报错 看一下 yarn源地址 是不是修改过

yarn config get registry  // 查看yarn当前镜像源
yarn config set registry https://registry.yarnpkg.com   // yarn  原本的地址

安装成功之后。我们去根 package.json中配置脚本

{
  "private": true,
  "workspaces":[
    "packages/*"
  ],
  "scripts": {
    "dev": "node scripts/dev.js",
    "build": "node scripts/build.js"
  },
  "name": "rollup-vue3",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}

当运行命令 yarn dev 时 我们去scripts运行 dev.js (打包单个模块)
yarn build 时我们去scripts运行 build.js(打包packages下所有模块)

我们先编写 build.js文件
首先获取 packages下所有的目录,并且过滤只要文件夹

const fs = require('fs')

//  读取packages文件夹下所有文件, 并且过滤 
const targets = fs.readdirSync('packages').filter(f => fs.statSync(`packages/${f}`).isDirectory())

console.log('targets', targets)
image.png

然后对目标一次进行并行打包

/**
 * 对目标进行依次打包,并且是并行打包
 * */
// 打包
function build(){}

// 循环目标 依次打包
function runParallel(targets, iteratorFn){

}
// 执行 
runParallel(targets, build)

我们对runParallel 进行一次打包返回Promise。在build中console看一下

// 打包 packages 下所有包

const fs = require('fs')

//  读取packages文件夹下所有文件, 并且过滤 
const targets = fs.readdirSync('packages').filter(f => fs.statSync(`packages/${f}`).isDirectory())

/**
 * 对目标进行依次打包,并且是并行打包
 * */
// 打包
async function build(target){
  console.log('target', target);
}

// 循环目标 依次打包
function runParallel(targets, iteratorFn){
  const res = [] // 保存打包结果
  // 遍历
  for(const item of targets){
    // 依次执行
    const p = iteratorFn(item)
    res.push(p)
  }
  return Promise.all(res)
}
// 执行 
runParallel(targets, build)
image.png

我们依次拿到了需要的包;

然后引入 execa 包 开启子进程

async function build(target){
  console.log('target', target);
  // 第一参数 是命令
  // 第二个参数 是rollup 运行时的执行的参数  
  //          -c 采用配置文件 
  //          --environment 设置环境变量 
  //          `TARGET:${target}` 环境变量 里面设置的对象。在rollup 配置文件执行时,可以获取到
  // 第三个参数 execa 执行的参数  stdio: 'inherit' 子进程打包的信息 共享给父进程
  await execa('rollup', ['-c', '--envrionment', `TARGET:${target}`], {
    stdio: 'inherit'
  })
}

由于rollup执行的配置文件为 rollup.config.js,所有我们创建一个rollup.config.js来配置rollup执行时候的参数

首先我们需要拿到 --environment传入的环境变量,我们先再rollup.config.js中console.log(process.env.TARGET) 一下,获取环境变量中的target属性 去获取 对应模块中的package.json

// rollup 配置

import path from 'path'

console.log('___________________', process.env.TARGET);
// 根据环境变量中 target 属性,获取对应模块中的 package.json
if (!process.env.TARGET) {
  throw new Error('TARGET package must be specified via --environment flag.')}

// 获取packages 目录
const packagesDir = path.resolve(__dirname, 'packages')

// 获取要打包的某个包 (打包的基本目录)
const packageDir = path.resolve(packagesDir, process.env.TARGET)

// 获取 对应打包目录 下的文件(这里用来取 package.json文件)
const resolve = p => path.resolve(packageDir, p)

// 获取package.json文件
const pkg = require(resolve(`package.json`))

// 获取 package.json文件中我们自定义的属性 buildOptions
const packageOptions = pkg.buildOptions || {}

// 获取我们 文件名
const name = packageOptions.filename || path.basename(packageDir)

之后我们需要 对打包类型 做一个映射表 ,根据package.json中的 formats 来格式化 需要打包的内容

//  对打包类型 做一个映射表 ,根据package.json中的 formats 来格式化 需要打包的内容
const outputConfigs = {
  'esm-bundler': {
    file: resolve(`dist/${name}.esm-bundler.js`), // 打包后的文件 
    format: `es` // 采用的 格式
  },
  cjs: {
    file: resolve(`dist/${name}.cjs.js`),
    format: `cjs`
  },
  global: {
    file: resolve(`dist/${name}.global.js`),
    format: `iife` // 立即执行函数
  }
}

好了, 有了映射表,我们来取package.json中 formats参数

// 获取 package.json中 formats
const defaultFormats = ['esm-bundler', 'cjs'] // 默认formats 配置
const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(',') // 环境变量中获取fromats配置
// 首先取 build.js中环境变量 formats 否则 到package.json中取  都没有取默认配置formats
const packageFormats = inlineFormats || packageOptions.formats || defaultFormats

比如 reactivity中package.json中的 formats

formats:[
      "cjs", 
      "esm-bundler", 
      "global" 
    ]

然后我们 把formats 循环调用 createConfig函数处理

// 循环调用 createConfig 处理 formats (比如: formats=['cjs', 'esm-bundler', 'global'])
const packageConfigs = packageFormats.map(format => createConfig(format, outputConfigs[format]))


function createConfig(format, output) {

}

// 导出配置变量
export default packageConfigs

好,我们来处理createConfig 文件。
format比如就是global: 那么 output就是 映射表中的{ file: resolve(`dist/${name}.global.js`), format: `iife` // 立即执行函数 }

处理createConfig函数

function createConfig(format, output) {

  // 如果是全局模式  需要 配置名字
  const isGlobalBuild = /global/.test(format)
  if (isGlobalBuild) {
    output.name = packageOptions.name
  }
 
  // 生成sourcemap文件
  output.sourcemap = !!process.env.SOURCE_MAP

  // 生成roullup 配置
  return {
    input: resolve('src/index.ts'), // 入口
    output, //出口 就是上面的output
    plugins: [ // 插件  从上到下 
      json(), // import json from '@rollup/plugin-json'
      ts(), // import ts from 'rollup-plugin-typescript2'
      resolvePlugin(), //import resolvePlugin from '@rollup/plugin-node-resolve'
    ]
  }
}

对应 ts处理,我们需要一个 ts配置文件
来到根目录 运行 npx tsc --init 生成配置文件

生成 tsconfig.json 文件后,我们把默认配置项 target 和module 都改为 esnext (最新 JavaScript / ECMAScript 特性https://esnext.justjavac.com/)

然后 回到 createConfig 函数,修改 返回生成 rollup配置中的 ts()

function createConfig(format, output) {

  // 如果是全局模式  需要 配置名字
  const isGlobalBuild = /global/.test(format)
  if (isGlobalBuild) {
    output.name = packageOptions.name
  }

  // 生成sourcemap文件
  output.sourcemap = !!process.env.SOURCE_MAP

  // 生成roullup 配置
  return {
    input: resolve('src/index.ts'), // 入口
    output, //出口 就是上面的output
    plugins: [ // 插件  从上到下 
      json(), // import json from '@rollup/plugin-json'
      ts({ // import ts from 'rollup-plugin-typescript2'
        tsconfig: path.resolve(__dirname, 'tsconfig.json')
      }), 
      resolvePlugin(), //import resolvePlugin from '@rollup/plugin-node-resolve'
    ]
  }
}

继续 终端运行 命令 yarn build打包


image.png

打包成功。 由于shared没有配置golbal.所有 没有打包出global.js文件;
而且 reactivity.global.js中 全局变量也是我们 在package.json 的buildOptions总 配置的


image.png

cjs.js 会采用commonjs的方法


image.png

esm-bundler.js 会采用es6的方法


image.png

接下来我们处理dev.js 打包单个 文件。而不是build.js打包所有。

首先我们安装 yarn add minimist -D -W 来处理我们运行 yarn dev 时 传递的参数
比如我们运行 yarn dev --test=123;
我们再dev.js中就可以 获取到 {test:123}

dev.js

const execa = require('execa')// 开启子进程 打包, 最终还是rollup来打包的

// 获取 yarn dev --target=xxx 比如 yarn dev --target=reactivity 可以 执行 reactivity打包
const args = require('minimist')(process.argv.slice(2))
const target = args.target

build(target)
/**
 * 对目标进行依次打包,并且是并行打包
 * */
// 打包
async function build(target) {
  // 第一参数 是命令
  // 第二个参数 是rollup 运行时的执行的参数  
  //          -c 采用配置文件 
  //          --environment 设置环境变量 
  //          `TARGET:${target}` 环境变量 里面设置的对象。在rollup 配置文件执行时,可以获取到
  //          SOURCE_MAP 是否生成 sourceMap文件
  // 第三个参数 execa 执行的参数  stdio: 'inherit' 子进程打包的信息 共享给父进程
  await execa('rollup', ['-c', '--environment', [`TARGET:${target}`, `SOURCE_MAP:ture`].join(',')], { stdio: 'inherit' })
}

image.png

可以看到此时,就单独完成了reactivity 的打包

我们还能去 dev.js中 一直监听文件打包 增加 -w
await execa('rollup', ['-cw',

现在打包好, 但是还有点问题。
我们 yarn install 在node_modules 下会生成一个 @vue 文件夹 下面的
reactivity 和 shared会生成一个软链接,链接到我们写的packages文件夹下的真实文件;

比如: 如果我们要在reactivit中 用 @vue下的shared文件

import {shared} from '@vue/shared'

const Reactivity = {

}
export {
  Reactivity
}
image.png

可以看到 '@vue/shared' 报错,找不到这个模块


image.png

我们需要到tsconfig.json中 增加配置

    // 解析规则为  node 
    "moduleResolution": "node",
    // 做一个映射表  依赖于 baseUrl配置
    "paths":{
      "@vue/*": [
        "packages/*/src"
      ]
    }, 
    // 基本目录
    "baseUrl": "./", 

然后 把 reactivity/src/index.ts重新打开一下


image.png

不报错了。我们 点击 安装command 鼠标左键点击 '@vue/shared' 也会跳转到 /packages/shared/src/index.ts 中

ok。下面是 开发文件
ts.config.js

{
  // 其他为默认配置
  "compilerOptions": {                       
    "target": "esnext", // esnext 最新的js标准
    "module": "esnext", // 修改为esnext
    "strict": false,    
    "skipLibCheck": true,  
    "forceConsistentCasingInFileNames": true,        
    "esModuleInterop": true,  
    "moduleResolution": "node",
    // 做一个映射表  依赖于 baseUrl配置
    "paths":{
      "@vue/*": [
        "packages/*/src"
      ]
    }, 
    // 基本目录
    "baseUrl": "./"
  }
}

rollup.config.js

// rollup 配置

import path from 'path'
import ts from 'rollup-plugin-typescript2'
import json from '@rollup/plugin-json'
import resolvePlugin from '@rollup/plugin-node-resolve'

console.log('___________________', process.env.TARGET);
// 根据环境变量中 target 属性,获取对应模块中的 package.json
if (!process.env.TARGET) {
  throw new Error('TARGET package must be specified via --environment flag.')
}

// 获取packages 目录
const packagesDir = path.resolve(__dirname, 'packages')

// 获取要打包的某个包 (打包的基本目录)
const packageDir = path.resolve(packagesDir, process.env.TARGET)

// 获取 对应打包目录 下的文件(这里用来取 package.json文件)
const resolve = p => path.resolve(packageDir, p)

// 获取package.json文件
const pkg = require(resolve(`package.json`))

// 获取 package.json文件中我们自定义的属性 buildOptions
const packageOptions = pkg.buildOptions || {}

// 获取我们 打包后的全局模块命名  
const name = packageOptions.filename || path.basename(packageDir)

//  对打包类型 做一个映射表 ,根据package.json中的 formats 来格式化 需要打包的内容
const outputConfigs = {
  'esm-bundler': {
    file: resolve(`dist/${name}.esm-bundler.js`), // 打包后的文件 
    format: `es` // 采用的 格式
  },
  cjs: {
    file: resolve(`dist/${name}.cjs.js`),
    format: `cjs`
  },
  global: {
    file: resolve(`dist/${name}.global.js`),
    format: `iife` // 立即执行函数
  }
}

// 获取 package.json中 formats
const defaultFormats = ['esm-bundler', 'cjs'] // 默认formats 配置
const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(',') // 环境变量中获取fromats配置
// 首先取 build.js中环境变量 formats 否则 到package.json中取  都没有取默认配置formats
const packageFormats = inlineFormats || packageOptions.formats || defaultFormats

// 循环调用 createConfig 处理 formats (比如: formats=['cjs', 'esm-bundler', 'global'])
const packageConfigs = packageFormats.map(format => createConfig(format, outputConfigs[format]))


function createConfig(format, output) {

  // 如果是全局模式  需要 配置名字
  const isGlobalBuild = /global/.test(format)
  if (isGlobalBuild) {
    output.name = packageOptions.name
  }

  // 生成sourcemap文件
  output.sourcemap = !!process.env.SOURCE_MAP

  // 生成roullup 配置
  return {
    input: resolve('src/index.ts'), // 入口
    output, //出口 就是上面的output
    plugins: [ // 插件  从上到下 
      json(), // import json from '@rollup/plugin-json'
      ts({ // import ts from 'rollup-plugin-typescript2'
        tsconfig: path.resolve(__dirname, 'tsconfig.json')
      }), 
      resolvePlugin(), //import resolvePlugin from '@rollup/plugin-node-resolve'
    ]
  }
}

// 导出配置变量
export default packageConfigs

package.json

{
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "dev": "node scripts/dev.js",
    "build": "node scripts/build.js"
  },
  "name": "rollup-vue3",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "@rollup/plugin-json": "^4.1.0",
    "@rollup/plugin-node-resolve": "^13.0.0",
    "execa": "^5.0.0",
    "rollup": "^2.48.0",
    "rollup-plugin-typescript2": "^0.30.0",
    "typescript": "^4.2.4"
  }
}

scrpts/build.js

// 打包 packages 下所有包

const fs = require('fs')
const execa = require('execa')// 开启子进程 打包, 最终还是rollup来打包的

//  读取packages文件夹下所有文件, 并且过滤 
const targets = fs.readdirSync('packages').filter(f => fs.statSync(`packages/${f}`).isDirectory())

/**
 * 对目标进行依次打包,并且是并行打包
 * */
// 打包
async function build(target) {
  console.log('target', target);
  // 第一参数 是命令
  // 第二个参数 是rollup 运行时的执行的参数  
  //          -c 采用配置文件 
  //          --environment 设置环境变量 
  //          `TARGET:${target}` 环境变量 里面设置的对象。在rollup 配置文件执行时,可以获取到
  //          SOURCE_MAP 是否生成 sourceMap文件
  // 第三个参数 execa 执行的参数  stdio: 'inherit' 子进程打包的信息 共享给父进程
  await execa('rollup', ['-c', '--environment', [`TARGET:${target}`, `SOURCE_MAP:ture`].join(',')], { stdio: 'inherit' })
}

// 循环目标 依次打包
function runParallel(targets, iteratorFn) {
  const res = [] // 保存打包结果
  // 遍历
  for (const item of targets) {
    // 依次执行
    const p = iteratorFn(item)
    res.push(p)
  }
  return Promise.all(res)
}
// 执行 
runParallel(targets, build)

scripts/dev.js

const execa = require('execa')// 开启子进程 打包, 最终还是rollup来打包的

// 获取 yarn dev --target=xxx 比如 yarn dev --target=reactivity 可以 执行 reactivity打包
const args = require('minimist')(process.argv.slice(2))
const target = args.target

build(target)
/**
 * 对目标进行依次打包,并且是并行打包
 * */
// 打包
async function build(target) {
  // 第一参数 是命令
  // 第二个参数 是rollup 运行时的执行的参数  
  //          -c 采用配置文件 
  //          --environment 设置环境变量 
  //          `TARGET:${target}` 环境变量 里面设置的对象。在rollup 配置文件执行时,可以获取到
  //          SOURCE_MAP 是否生成 sourceMap文件
  // 第三个参数 execa 执行的参数  stdio: 'inherit' 子进程打包的信息 共享给父进程
  await execa('rollup', ['-cw', '--environment', [`TARGET:${target}`, `SOURCE_MAP:ture`].join(',')], { stdio: 'inherit' })
}

packages/reactivity/package.json

{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "main": "index.js",
  "module": "dist/reactivity.esm-bundler.js",
  "license": "MIT",
  "buildOptions": {
    "name": "VueReactivity",
    "formats": [    
      "cjs", 
      "esm-bundler", 
      "global" 
    ]
  }
}

packages/reactivity/src/index.ts

import { Shared } from '@vue/shared'

const Reactivity = {

}
export {
  Reactivity
}

目录结构:


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容