Rollup基础环境搭建

搭建 mini-vue3 基础开发环境全部按照 Vue-next 配置进行操作,删繁就简为实现一个简洁、可用、高效的环境!

创建完成后目录如下

build-your-own-vue3
├─ .gitignore
├─ doc
│  └─ init.md
├─ examples
│  └─ index.html
├─ package.json
├─ packages
│  ├─ reactivity
│  │  ├─ package.json
│  │  └─ src
│  │     └─ index.ts
│  ├─ size-check
│  │  └─ package.json
│  └─ vue
│     ├─ index.js
│     ├─ package.json
│     └─ src
│        ├─ index.ts
│        └─ runtime.ts
├─ README.md
├─ rollup.config.js
├─ scripts
│  ├─ dev.js
│  └─ utils.js
├─ tsconfig.json
└─ yarn.lock

1、创建基础配置

1、项目初始化

yarn init
{
  "private": true, // 私有属性,打包时忽略该包
  "version": "0.1.0",
  "workspaces": [
    // npm 多包管理,使用 packages 下的工作目录
    "packages/*"
  ],
  "description": "vue",
  "repository": "https://github.com/hefeng6500/build-your-own-vue3",
  "author": "hefeng6500 <2443992009@qq.com>",
  "license": "MIT"
}

2、安装依赖包

yarn add @babel/core @babel/preset-env @rollup/plugin-babel @rollup/plugin-json chalk execa minimist rollup rollup-plugin-typescript2 -D -W

配置 babel

module.exports = {
  input: {},
  output: {},
  plugins: [
    tsPlugin,
    getBabelOutputPlugin({
      allowAllFormats: true,
      presets: ["@babel/preset-env"],
    }),
  ],
};

初始化 ts 配置 根目录下生成 tsconfig.json

yarn global add typescript

tsc --init

修改 tsconfig.json

{
  "target": "esnext",
  "module": "esnext"
}
const tsPlugin = ts({
  tsconfig: path.resolve(__dirname, "tsconfig.json"),
  cacheRoot: path.resolve(__dirname, "node_modules/.rts2_cache"),
});

module.exports = {
  plugins: [tsPlugin],
};

添加 .gitignore

dist
.DS_Store
node_modules
coverage
temp
explorations
TODOs.md
*.log

2、构建开发环境脚本

创建 scripts/dev.js, scripts/utils.js

// utils.js
const fs = require("fs");
const chalk = require('chalk');

// 读取 packages 目录下需要打包的目录
const targets = (exports.targets = fs.readdirSync('packages').filter(f => {
  if (!fs.statSync(`packages/${f}`).isDirectory()) {
    return false
  }
  const pkg = require(`../packages/${f}/package.json`)
  if (pkg.private && !pkg.buildOptions) {
    return false
  }
  return true
}))

// 模糊匹配需要打包的 package name
exports.fuzzyMatchTarget = (partialTargets, includeAllMatching) => {
  const matched = []
  partialTargets.forEach(partialTarget => {
    for (const target of targets) {
      if (target.match(partialTarget)) {
        matched.push(target)
        if (!includeAllMatching) {
          break
        }
      }
    }
  })
  if (matched.length) {
    return matched
  } else {
    console.log()
    console.error(
      `  ${chalk.bgRed.white(' ERROR ')} ${chalk.red(
        `Target ${chalk.underline(partialTargets)} not found!`
      )}`
    )
    console.log()

    process.exit(1)
  }
}

导出的 targets 为 packages 目录下的包名,是一个数组例如: vuereactivity;

fuzzyMatchTarget 模糊匹配需要打包的目标包名,返回匹配的包名。如果未匹配到包将使用 chalk 插件高亮提示错误信息

// dev.js
const fs = require("fs")
const execa = require('execa');
const { fuzzyMatchTarget } = require('./utils')

const args = require('minimist')(process.argv.slice(2))
const target = args._.length ? fuzzyMatchTarget(args._)[0] : 'reactivity'
const formats = args.formats || args.f
const sourceMap = args.sourcemap || args.s
// const commit = execa.sync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7)

execa(
  'rollup',
  [
    '-wc',
    '--environment',
    [
      // `COMMIT:${commit}`,
      `TARGET:${target}`,
      `FORMATS:${formats || 'global'}`,
      sourceMap ? `SOURCE_MAP:true` : ``
    ]
      .filter(Boolean)
      .join(',')
  ],
  {
    stdio: 'inherit'
  }
)

execa 是一个管理 node 命令行执行的插件,minimist 是一个格式化命令参数选项的插件。 dev.js 使用了 execa 管理 rollup 进行打包,-w 实时监听文件进行热更新

3、配置 rollup

import path from "path";
import ts from "rollup-plugin-typescript2";
import json from "@rollup/plugin-json";
import { getBabelOutputPlugin } from "@rollup/plugin-babel";

const masterVersion = require("./package.json").version;
const packagesDir = path.resolve(__dirname, "packages");
const packageDir = path.resolve(packagesDir, process.env.TARGET);
const resolve = (p) => path.resolve(packageDir, p);
const pkg = require(resolve(`package.json`));
const packageOptions = pkg.buildOptions || {};
const name = packageOptions.filename || path.basename(packageDir);

const outputConfigs = {
  "esm-bundler": {
    file: resolve(`dist/${name}.esm-bundler.js`),
    format: `es`,
  },
  "esm-browser": {
    file: resolve(`dist/${name}.esm-browser.js`),
    format: `es`,
  },
  cjs: {
    file: resolve(`dist/${name}.cjs.js`),
    format: `cjs`,
  },
  global: {
    file: resolve(`dist/${name}.global.js`),
    format: `iife`,
  },

  // runtime-only builds, for main "vue" package only
  "esm-bundler-runtime": {
    file: resolve(`dist/${name}.runtime.esm-bundler.js`),
    format: `es`,
  },
  "esm-browser-runtime": {
    file: resolve(`dist/${name}.runtime.esm-browser.js`),
    format: "es",
  },
  "global-runtime": {
    file: resolve(`dist/${name}.runtime.global.js`),
    format: "iife",
  },
};
// const defaultFormats = ["esm-bundler", "cjs"];
// const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(",");

const packageFormats = packageOptions.formats;

const tsPlugin = ts({
  tsconfig: path.resolve(__dirname, "tsconfig.json"),
  cacheRoot: path.resolve(__dirname, "node_modules/.rts2_cache"),
});

const packageConfigs = packageFormats.map((format) =>
  createConfig(format, outputConfigs[format])
);

export default packageConfigs;

function createConfig(format, output, plugins = []) {
  let entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`;

  const isGlobalBuild = /global/.test(format)
  
  if (isGlobalBuild) {
    output.name = packageOptions.name
  }

  return {
    input: resolve(entryFile),
    output,
    plugins: [
      json({
        namedExports: false,
      }),
      tsPlugin,
      getBabelOutputPlugin({
        allowAllFormats: true,
        presets: ["@babel/preset-env"],
      }),
    ],
  };
}

outputConfigs 对象存储了 rollup 导出对象的 名称文件导出模块规范;更多前端模块规范基础知识请进: 前端模块化:CommonJS,AMD,CMD,ES6

format 格式分为:

  • es: es module,是 ES 模块规范,使用 import 和 export 关键字对模块进行导入导出
  • cjs: commonjs 规范, 使用 module.exports 和 require 关键字对模块进行导入导出
  • iife: 立即调用函数表达式。是一个自执行函数,这里打包后会将结果赋值给一个全局变量,形如:
var VueReactivity = function () {
  'use strict';

  function effect() {
    console.log(123);
  }

  return effect;
}();

这里 export default packageConfigs; packageFormats 是单个 npm 包配置文件需要输出的文件格式,像 package/vue 下的 package.json 如下:

{
  "name": "vue",
  "version": "0.1.0",
  "main": "index.js",
  "buildOptions": {
    "name": "Vue",
    "formats": [
      "esm-bundler",
      "esm-bundler-runtime",
      "cjs",
      "global",
      "global-runtime",
      "esm-browser",
      "esm-browser-runtime"
    ]
  }
}

packageConfigs 会对 formats 下定义的各种模块几个进行打包,最后输出多个打包文件

注意:rollup.config.js ,当前的配置是对一个 npm 包下的多个导出格式进行打包,而不是对多个npm包进行打包