Rollup 是一个用于 JavaScript 的模块打包器 https://rollupjs.org/guide/en/#overview

为什么不是webpack

  • webpack 借助各种loader、plugin 帮我们解决各种问题 ,但是生成的代码有很多自由的模块加载功能,繁琐的配置及打包后的文件体积,不适合开发js库,所以更适合项目开发。
  • rollup 只是把源文件生成目标js代码,所以更适合做js库的开发。

    快速开始

    demo地址:https://github.com/chenzhijie521143/rollup-test

1.npm 初始化,生成package.json

  1. npm init

1.安装 rollup ,创建rollup.config.js

  1. yarn add rollup -D

3.添加.gitignore文件

  1. /node_modules
  2. /dist
  3. /example/dist

4.添加 README.md

  1. ### rollup-test
  2. #### 本地启动 `yarn dev`
  3. #### 打包 `yarn build`

5.创建src目录,用于编写要打包的代码

image.png

常用配置

input

项目入口文件

export default {
  input: 'src/index.jsx',
}

output

项目输出文件,支持数组

export default {
  input: 'src/index.jsx',
  output: {
    file: 'dist/bundle.js',
    format: 'umd',
  },
}

export default {
  input: 'src/index.jsx',
  output: [
    {
      file: 'dist/bundle.es.js',
      format: 'esm',
    },
    {
      file: 'dist/bundle.umd.js',
      format: 'umd',
      name: 'ReactUI'
    }
  ],
}

plugins

各种插件的配置位置

export default {
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    resolve(),
    commonjs(),
    json(),
    babel({
      exclude: 'node_modules/**',
    }),
  ]
}

external

配置第三方库不打包,而是使用外部依赖(需要单独引入)

export default {
  external: ['react', 'react-dom'], // 配置第三方库不打包,而是使用外部依赖
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: []
}

深入使用

使用babel

安装依赖

yarn add -D @rollup/plugin-babel @babel/core core-js@2 @babel/preset-env @babel/preset-react @babel/plugin-proposal-decorators

配置 rollup.config.js

import babel from '@rollup/plugin-babel';

export default {
  external: ['react', 'react-dom'], // 配置第三方库不打包,而是使用外部依赖
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    babel({
      exclude: 'node_modules/**',
    }),
  ]
}

配置 .babelrc

{
  "presets": [
    "@babel/preset-react", // react预设环境
    [
      "@babel/preset-env",
      {
        "modules": false, // 设置 "modules": false ,否则 Babel 会在 Rollup 有机会做处理之前,将我们的模块转成 CommonJS ,导致 Rollup 的一些处理失败。
        "corejs": 2,
        "useBuiltIns": "usage" // 只加载使用的polyfills
      }
    ]
  ],
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }], // 类装饰器使用
    "@babel/plugin-proposal-class-properties" // 支持 class 属性转化
  ]
}

node模块的引用

安装依赖

yarn add -D @rollup/plugin-node-resolve @rollup/plugin-commonjs

配置rollup.config.js

import babel from '@rollup/plugin-babel';

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default {
  external: ['react', 'react-dom'], // 配置第三方库不打包,而是使用外部依赖
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [

    //---new---- 
    resolve({ extensions }), // 在node_modules中找到并捆绑第三方依赖项
    commonjs(), // 用来将 CommonJS 转换成 ES6 模块

    babel({
      exclude: 'node_modules/**',
    }),
  ]
}

typescript支持

编译ts 有两种方案,babel编译、tsc编译,以下介绍两种方案的接入。两种方案都需要安装typescript

yarn add  -D typescript

配置tsconfig.json

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node",
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "react",
    "lib": ["es2015", "es2016", "es2017", "dom"],
    "baseUrl": ".",   
    "paths": {
      "@/*": ["./src/*"],
    }, 
    "declarationDir": "types",
    "declaration": true,
  },
  "include": [
    "src/**/*"
  ],
  "exclude":[
    "node_modules/**"
  ]
}

方案一:tsc编译

安装依赖

yarn add -D @rollup/plugin-typescript tslib

配置rollup.config.js

import typescript from '@rollup/plugin-typescript';

export default {
  input: 'src/index.jsx',
  output: {
    file: 'dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    typescript({
      tsconfig: './tsconfig.json', // 指定ts配置文件
    }),
  ]
}

配置执行脚本

"build": "rm -rf dist && rollup -c ./build/rollup.build.js",

方案二:babel编译

使用 @babel/preset-typescript 预设支持ts编译,配合 tsc 做类型检查、输出声明文件等

安装依赖

yarn add -D @babel/preset-typescript

配置.babelrc

{
  "presets": [
    "@babel/preset-react",
    "@babel/typescript",
    [
      "@babel/preset-env",
      {
        "modules": false, // 设置 "modules": false ,否则 Babel 会在 Rollup 有机会做处理之前,将我们的模块转成 CommonJS ,导致 Rollup 的一些处理失败。
        "corejs": 2,
        "useBuiltIns": "usage" // 只加载使用的polyfills
      }
    ]
  ],
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    "@babel/plugin-proposal-class-properties"
  ]
}

配置rollup.config.js

import babel from '@rollup/plugin-babel';

const extensions = [
  '.js', '.jsx', '.ts', '.tsx',
];

export default {
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [
   babel({
      extensions,
      exclude: 'node_modules/**',
    }),
  ]
}

ts类型检查

配置tsc执行脚本

"type-check": "tsc --noEmit",

"type-check:watch": "tsc --noEmit --watch"

输出ts声明文件

  1. 新建 tsconfig.types.json 文件,只生成声明文件,不生成js文件

    {
    // 继承 tsconfig.json 中的通用配置
    "extends": "./tsconfig.json",
    "compilerOptions": {
     "declaration": true,                   /* 生成相应的 '.d.ts' file. */
     "declarationDir": "./dist/types", /* 类型声明文件输出目录 */
     "emitDeclarationOnly": true, /* 只生成声明文件,不生成 js 文件*/
     "rootDir": "./src", /* 指定输出文件目录(用于输出),用于控制输出目录结构 */
    }
    }
    
  2. 配置执行脚本 ```json “build:babel”: “rm -rf dist && yarn build:types && rollup -c ./build/rollup.build.babel.js”,

“build:types”: “tsc -b ./tsconfig.types.json”,

<a name="prJw5"></a>
### 总结
<a name="XJsQg"></a>
#### 方案一(@rollup/plugin-typescript)
优点:

1. 通过rollup插件编译,配置简单

缺点:

1. 每次修改都要经过  编译、类型检查,可能比较慢
1. 对于新api,如 Promise Set Map 或者 Object.assign Array.includes 等,ts编译器是不会做处理的,需要单独引入所需的polyfill。
1. rollup 插件编译ts,如何和 babel/polyfill 完美结合 ?
<a name="imFhS"></a>
#### 方案二(@babel/preset-typescript)
优点:

1. babel7 开始支持ts,通过预设@babel/preset-typescript 来实现
1. babel7.4 移除了babel/polyfill ,参考:[https://babeljs.io/docs/en/babel-polyfill](https://babeljs.io/docs/en/babel-polyfill)
1. babel7.4 支持corejs 按需加载,配合 useBuiltIns: usage 使用

缺点:

1. 不支持ts类型校验,需要配合 tsc 做类型校验
1. babel 是不支持编译部分语法的(不过不影响)
<a name="DUQj7"></a>
#### 最好的选择
综上所述,最好的方案就是 ,babel做编译,tsc做校验,两者结合。
<a name="nWi1Q"></a>
## 压缩代码
<a name="tHO7c"></a>
### 安装依赖
```shell
yarn add -D rollup-plugin-terser

配置rollup.config.js

import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

import { terser } from 'rollup-plugin-terser';

export default {
  external: ['react', 'react-dom'], // 配置第三方库不打包,而是使用外部依赖
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    resolve({ extensions }), // 在node_modules中找到并捆绑第三方依赖项
    commonjs(), // 用来将 CommonJS 转换成 ES6 模块
    babel({
      exclude: 'node_modules/**',
    }),

    //---new---- 
    terser(), // 压缩js代码
  ]
}

编译css

安装依赖

yarn add -D rollup-plugin-postcss postcss cssnano autoprefixer

配置rollup.config.js

import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';

import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';

export default {
  external: ['react', 'react-dom'], // 配置第三方库不打包,而是使用外部依赖
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    resolve({ extensions }), // 在node_modules中找到并捆绑第三方依赖项
    commonjs(), // 用来将 CommonJS 转换成 ES6 模块
    babel({
      exclude: 'node_modules/**',
    }),
    terser(), // 压缩js代码

    //---new---- 
    postcss({
      plugins: [
        autoprefixer(), // 加前缀
        cssnano(), // 压缩css
      ],
      extract: 'css/index.css', // 单独抽离css文件
    }),
  ]
}

支持预编译(less)

安装less ,即可引入 .less 文件。stylus/sass 的支持,分别安装 stylus/node-sass 既可。

yarn add -D less

环境变量

安装依赖

yarn add -D rollup-plugin-replace

配置rollup.config.js

import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';

import replace from 'rollup-plugin-replace';

export default {
  external: ['react', 'react-dom'], // 配置第三方库不打包,而是使用外部依赖
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    resolve({ extensions }), // 在node_modules中找到并捆绑第三方依赖项
    commonjs(), // 用来将 CommonJS 转换成 ES6 模块
    babel({
      exclude: 'node_modules/**',
    }),
    terser(), // 压缩js代码
    postcss({ // css处理
      plugins: [
        autoprefixer(), // 加前缀
        cssnano(), // 压缩css
      ],
      extract: 'css/index.css', // 单独抽离css文件
    }),

    //---new---- 
    replace({ // 设置环境变量
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ]
}

本地服务器

安装依赖

yarn add -D rollup-plugin-serve

配置rollup.config.js

import path from 'path';
import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import replace from 'rollup-plugin-replace';

import serve from 'rollup-plugin-serve';

export default {
  external: ['react', 'react-dom'], // 配置第三方库不打包,而是使用外部依赖
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    resolve({ extensions }), // 在node_modules中找到并捆绑第三方依赖项
    commonjs(), // 用来将 CommonJS 转换成 ES6 模块
    babel({
      exclude: 'node_modules/**',
    }),
    terser(), // 压缩js代码
    postcss({ // css处理
      plugins: [
        autoprefixer(), // 加前缀
        cssnano(), // 压缩css
      ],
      extract: 'css/index.css', // 单独抽离css文件
    }), 

    //---new---- 
    serve({
      // open: true, // 自动打开页面
      port: 8000,
      sourcemap: true,
      contentBase: path.join(__dirname, '../example'),
    }),

    replace({ // 设置环境变量
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ]
}

开启热更新

安装依赖

yarn add -D rollup-plugin-livereload

配置rollup.config.js

import path from 'path';
import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import serve from 'rollup-plugin-serve';
import replace from 'rollup-plugin-replace';
import serve from 'rollup-plugin-serve';

import livereload from 'rollup-plugin-livereload';

export default {
  external: ['react', 'react-dom'], // 配置第三方库不打包,而是使用外部依赖
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    resolve({ extensions }), // 在node_modules中找到并捆绑第三方依赖项
    commonjs(), // 用来将 CommonJS 转换成 ES6 模块
    babel({
      exclude: 'node_modules/**',
    }),
    terser(), // 压缩js代码
    postcss({ // css处理
      plugins: [
        autoprefixer(), // 加前缀
        cssnano(), // 压缩css
      ],
      extract: 'css/index.css', // 单独抽离css文件
    }), 

    //---new----
    livereload(path.join(__dirname, '../example/dist')),  // 热更新 监听本地文件

    serve({
      // open: true, // 自动打开页面
      port: 8000,
      sourcemap: true,
      contentBase: path.join(__dirname, '../example'),
    }),

    replace({ // 设置环境变量
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ]
}

配置别名

安装依赖

yarn add -D @rollup/plugin-alias

配置rollup.config.js

import path from 'path';
import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import replace from 'rollup-plugin-replace';
import serve from 'rollup-plugin-serve';
import livereload from 'rollup-plugin-livereload';

import alias from '@rollup/plugin-alias';

export default {
  external: ['react', 'react-dom'], // 配置第三方库不打包,而是使用外部依赖
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    resolve({ extensions }), // 在node_modules中找到并捆绑第三方依赖项
    commonjs(), // 用来将 CommonJS 转换成 ES6 模块
    babel({
      exclude: 'node_modules/**',
    }),
    terser(), // 压缩js代码
    postcss({ // css处理
      plugins: [
        autoprefixer(), // 加前缀
        cssnano(), // 压缩css
      ],
      extract: 'css/index.css', // 单独抽离css文件
    }), 

    //---new----
    alias({
      entries: [
        { find: '@', replacement: path.join(__dirname, '../src') },
      ]
    }),

    livereload(path.join(__dirname, '../example/dist')),  // 热更新 监听本地文件
    serve({
      // open: true, // 自动打开页面
      port: 8000,
      sourcemap: true,
      contentBase: path.join(__dirname, '../example'),
    }),
    replace({ // 设置环境变量
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ]
}

json

安装依赖

yarn add -D @rollup/plugin-json

配置rollup.config.js

import path from 'path';
import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import replace from 'rollup-plugin-replace';
import livereload from 'rollup-plugin-livereload';
import alias from '@rollup/plugin-alias';

import json from '@rollup/plugin-json';

export default {
  external: ['react', 'react-dom'], // 配置第三方库不打包,而是使用外部依赖
  input: 'src/index.jsx',
  output: {
    file: 'example/dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    resolve({ extensions }), // 在node_modules中找到并捆绑第三方依赖项
    commonjs(), // 用来将 CommonJS 转换成 ES6 模块

    //---new----
    json(), // 支持json 文件导入

    babel({
      exclude: 'node_modules/**',
    }),
    terser(), // 压缩js代码
    postcss({ // css处理
      plugins: [
        autoprefixer(), // 加前缀
        cssnano(), // 压缩css
      ],
      extract: 'css/index.css', // 单独抽离css文件
    }), 

    alias({
      entries: [
        { find: '@', replacement: path.join(__dirname, '../src') },
      ]
    }),

    livereload(path.join(__dirname, '../example/dist')),  // 热更新 监听本地文件
    serve({
      // open: true, // 自动打开页面
      port: 8000,
      sourcemap: true,
      contentBase: path.join(__dirname, '../example'),
    }),
    replace({ // 设置环境变量
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ]
}

支持eslint

基本依赖

这里我使用的是 aribnb 编码规范

yarn add -D eslint eslint-config-airbnb eslint-plugin-import  eslint-plugin-react  eslint-plugin-react-hooks and eslint-plugin-jsx-a11y

解析ts

 yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser

模版配置

module.exports =  {
  parser:  '@typescript-eslint/parser',  // Specifies the ESLint parser
  parserOptions: {
    ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
    sourceType: 'module' // Allows for the use of imports
  },
  extends:  [
    'airbnb',  // Uses airbnb, it including the react rule(eslint-plugin-react/eslint-plugin-jsx-a11y)
    // 'plugin:@typescript-eslint/recommended', // Optional enable, will more stricter
    // 'plugin:import/typescript', // Use prettier/react to pretty react syntax
    // 'plugin:promise/recommended',
    'plugin:react/recommended',
    // 'plugin:prettier/recommended',
    // 'prettier/@typescript-eslint'
  ],
  settings: {
    'import/parsers': {
      '@typescript-eslint/parser': ['.ts', '.tsx']
    },
    'import/resolver': {
      // use <root>/path/to/folder/tsconfig.json
      typescript: {
        directory: './tsconfig.json'
      }
    }
  },
  env: {
    browser: true // enable all browser global variables
  },
  plugins: ['@typescript-eslint', 'react-hooks', 'promise'],
  rules:  {
    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
    // e.g. '@typescript-eslint/explicit-function-return-type': 'off',
    'no-useless-constructor': 0,
    'react/jsx-filename-extension': [1, { 'extensions': ['.ts', '.tsx'] }],
    'react/jsx-one-expression-per-line': 0,
    'react-hooks/rules-of-hooks': 'error',

    /**
     * @description rules of @typescript-eslint
     */
    '@typescript-eslint/prefer-interface': 'off', // also want to use 'type'
    '@typescript-eslint/explicit-function-return-type': 'off', // annoying to force return type
    '@typescript-eslint/indent': 'off' // avoid conflict with airbn

    /**
     * @description rules of eslint-plugin-prettier
     */
    // 'prettier/prettier': [
    //   error, {
    //     singleQuote: true,
    //     semi: false
    //   }
    // ]
  },
};

遇到的问题

由于官方 @rollup/plugin-typescript 和 @rollup/plugin-eslint 两个插件同时使用有冲突,似乎ESLint是在编译后的代码上执行的,所以避免同时使用。在官方渠道也没找到合理的方案,以下提供三种替代方案。

方案一

将ts插件替换为 rollup-plugin-typescript2
修改 rollup.config.js 配置

import eslint from '@rollup/plugin-eslint';
// import typescript from '@rollup/plugin-typescript';
import typescript from 'rollup-plugin-typescript2';
export default {
  input: 'src/index.jsx',
  output: {
    file: 'dist/bundle.js',
    format: 'umd',
  },
  plugins: [
    eslint(),
    typescript({
      tsconfig: './tsconfig.json', // 指定ts配置文件
    }),
  ]
}

方案二

移除 @rollup/plugin-eslint,替换为以下插件

具体可参考: