Rollup用来干啥

Rollup是当前流行的库打包器,在Webpack、ESM(ES6模块)之后出现,Rollup支持开发者使用ESM模块语法开发。

文件类型

几乎只支持JS,其他类型的文件如CSS等均需要使用插件处理(后续会介绍 )

特点
  1. Rollup将代码转码成目标js(支持umd/commonjs/es)

  2. Rollup推崇ESM模块标准开发(借助浏览器对ESM的支持)

  3. Rollup打包产物对比Webpack较为精简,如下
    webpack:
    webpack_compare

     1. 有大量的诸如 __webpack_require__之类的代码,这些都是 Webpack 自身 Polyfill 的在运行时的模块加载,
     就是为了让产物代码在所有浏览器都能运行(wepack出现的时候还没有ESM ,当时的模块标准还很混乱,Webpack抹平了差异。)
     2. 用 IIFE 实现模块之间的隔离,并且用__webpack_require__ __webpack_exports__ 等 Polyfill 实现在浏览器环境里模拟 CJS 模块加载
     3. 用 Webpack 打包后的代码实际上更像是跑在 Webpack 给我们实现的“虚拟 Runtime”上
    

Rollup:
rollup_compare

    Rollup 诞生在 ESM 模块标准出来之后,所以 Rollup 完全遵从 ESM 标准,不需要像 Webpack 那样做很多 Runtime Polyfill,
    完全把代码交给浏览器运行。对于一些项目里依赖的老旧的 CJS 的包,也可以通过插件来对这些依赖处理。

快速0 -> 1

新建工程

创建空文件夹,如 mkdir proton-utils

安装rollup

cd proton-utils & code .

npm init -y

npm install rollup –save-dev

touch .gitignore & vim .gitignore

/node_modules

生成tsconfig.json配置文件

tsc –init
默认的配置其实已经够用,后续可以根据需要删减配置。

    {
    "compilerOptions": {
        "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
        /* Modules */
        "module": "commonjs",                                /* Specify what module code is generated. */
        /* Interop Constraints */
        "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
        "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
        /* Type Checking */
        "strict": true,                                      /* Enable all strict type-checking options. */
        "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
    }
    }
创建rollup.config.js

touch rollup.config.js

Rollup文件配置项
Rollup插件列表

    import resolve from 'rollup-plugin-node-resolve';
    import commonjs from 'rollup-plugin-commonjs';
    import typescript from 'rollup-plugin-typescript';
    import pkg from './package.json';

    export default {
    input: 'src/index.ts', // 打包入口
    output: { // 打包出口
        file: pkg.browser, // 最终打包出来的文件路径和文件名,这里是在package.json的browser: 'dist/index.js'字段中配置的
        format: 'umd', // umd是兼容amd/cjs/iife的通用打包格式,适合浏览器
        name:'proton-utils' // 包的全局变量名称
    },
    plugins: [ // 打包插件
        resolve(), // 查找和打包node_modules中的第三方模块
        commonjs(), // 将 CommonJS 转换成 ES2015 模块供 Rollup 处理
        typescript() // 解析TypeScript
    ]
    };

vim package.json

     "browser": "dist/index.ts",

npm i -D rollup typescript tslib rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-typescript

  • tslib在rollup.config.js没有引入,为啥安装? -> rollup-plugin-typescript插件依赖了该库。
编写需打包库源码

0 -> 1 小demo
跑通源码编写 -> 打包构建 -> 引入使用

源码编写

mkdir src & touch src/index.ts

vim src/index.ts

    console.log('baishu test rollup:')

:wq

打包构建

vim package.json

    "scripts": {
        "build":"rollup -c"
    },

:wq

npm run build
rollup_demo_dist

手动发布: npm publish

引入使用

mkdir web_app & cd web_app & yarn create @umijs/umi-app
umi_demo

构建发布脚本

在安装使用之前,需将打包构建好的文件发布至类似npm仓库( verdaccio搭建私有npm仓库后续文章介绍),供项目安装。
由于上一章节只是一个demo,则手动publish至npm仓库(源码也被推上去了,显然不稳当,如下图)
publish_manual

解决问题:

    1. 只发布打包后文件
    2. 发布版本号自动+1
    3. 脚本发布
拷贝文件

vim package.json

    "scripts": {
        ...,
        "copy": "cp package.json dist"
    },
修改文件

mkdir scripts & cd scripts & touch scripts/publish.js

npm i -D shelljs commander

vim package.json

    "scripts": {
        ...,
        "copy": "cp package.json dist",
        "build": "rollup -c && npm run copy"
    },

vim scripts/publish.js

    const path = require('path');
    const shelljs = require('shelljs');
    const program = require('commander');

    const targetFile = path.resolve(__dirname, '../dist/package.json');
    const packagejson = require(targetFile);
    const currentVersion = packagejson.version;
    const versionArr = currentVersion.split('.');
    const [mainVersion, subVersion, phaseVersion] = versionArr;

    // 默认版本号
    const defaultVersion = `${mainVersion}.${subVersion}.${+phaseVersion+1}`;

    let newVersion = defaultVersion;

    // 从命令行参数中取版本号
    program
    .option('-v, --versions <type>', 'Add release version number', defaultVersion);

    program.parse(process.argv);

    if (program.versions) {
    newVersion = program.versions;
    }

    function publish() {
    shelljs.sed('-i', '"name": "proton-utils"', '"name": "baishu-proton-utils"', targetFile); // 修改包名
    shelljs.sed('-i', `"version": "${currentVersion}"`, `"version": "${newVersion}"`, targetFile); // 修改版本号
    shelljs.cd('dist');
    shelljs.exec('npm publish'); // 发布
    }

    publish();
发布

vim package.json

    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "copy": "cp package.json dist",
        "build": "rollup -c && npm run copy",
        "publish": "node scripts/publish.js"
    },

npm run publish

总结

至此,我们已经完成从0 -> 1的过程

学会了:
1.如何配置Rollup和TypeScript
2.脚本自动化发布工具库到npm仓库

引入包内容(仅源码 归功于publish.js)
publish_script

接下来章节中,将会仔细介绍Rollup相关内容😄

tip_1

常用配置

Rollup文件配置项

input

入口文件地址

output
    output:{
        file:'bundle.js', // 输出文件
        format: 'cjs,  //  五种输出格式:amd /  es6 / iife / umd / cjs
        name:'A',  //当format为iife和umd时必须提供,将作为全局变量挂在window(浏览器环境)下:window.A=...
        sourcemap:true  //生成bundle.map.js文件,方便调试
    }
plugins

各种插件使用的配置

external

external:[‘lodash’] //告诉rollup不要将此lodash打包,而作为外部依赖

global
    global:{
        'jquery':'$' //告诉rollup 全局变量$即是jquery
    }

react-redux的rollup配置

    import nodeResolve from 'rollup-plugin-node-resolve' // 帮助寻找node_modules里的包
    import babel from 'rollup-plugin-babel' // rollup 的 babel 插件,ES6转ES5
    import replace from 'rollup-plugin-replace' // 替换待打包文件里的一些变量,如process在浏览器端是不存在的,需要被替换
    import commonjs from 'rollup-plugin-commonjs' // 将非ES6语法的包转为ES6可用
    import uglify from 'rollup-plugin-uglify' // 压缩包

    const env = process.env.NODE_ENV

    const config = {
    input: 'src/index.js',
    external: ['react', 'redux'], // 告诉rollup,不打包react,redux;将其视为外部依赖
    output: { 
        format: 'umd', // 输出 UMD格式,各种模块规范通用
        name: 'ReactRedux', // 打包后的全局变量,如浏览器端 window.ReactRedux 
        globals: {
        react: 'React', // 这跟external 是配套使用的,指明global.React即是外部依赖react
        redux: 'Redux'
        }
    },
    plugins: [
        nodeResolve(),
        babel({
        exclude: '**/node_modules/**'
        }),
        replace({
        'process.env.NODE_ENV': JSON.stringify(env)
        }),
        commonjs()
    ]
    }

    if (env === 'production') {
    config.plugins.push(
        uglify({
        compress: {
            pure_getters: true,
            unsafe: true,
            unsafe_comps: true,
            warnings: false
        }
        })
    )
    }

    export default config

深入使用

使用Babel

为了正确解析我们的模块并使其与旧版浏览器兼容,使用babel编译输出。以便可以使用未被浏览器和 Node.js 支持的将来版本的 JavaScript特性。

  1. npm install rollup-plugin-babel –save-dev

  2. vim rollup.config.js

     import babel from 'rollup-plugin-babel'
     import resolve from 'rollup-plugin-node-resolve';
     import commonjs from 'rollup-plugin-commonjs';
     import typescript from 'rollup-plugin-typescript';
     import pkg from './package.json';
    
     const extensions = ['.js', '.ts', '.tsx', '.json']
    
     export default {
         input: 'src/index.ts', // 打包入口
         output: { // 打包出口
             file: pkg.browser, // 最终打包出来的文件路径和文件名,这里是在package.json的browser: 'dist/index.js'字段中配置的
             format: 'umd', // umd是兼容amd/cjs/iife的通用打包格式,适合浏览器
             name:'proton-utils' // 包的全局变量名称
         },
         plugins: [ // 打包插件
             resolve(), // 查找和打包node_modules中的第三方模块
             commonjs(), // 将 CommonJS 转换成 ES2015 模块供 Rollup 处理
             typescript(), // 解析TypeScript
             babel({
                 include: 'src/**/*',
                 exclude: '**/node_modules/**',
                 extensions,
             }),
         ]
     };
    
  3. cd src & touch .babelrc & vim .babelrc

     {
         "presets": [
             [
                 "@babel/env",
                 {
                     "modules":false
                 }
             ]
         ]
     }
    
     1.首先,设置 "modules": false ,否则 Babel 会在 Rollup 有机会做处理之前,将我们的模块转成 CommonJS ,导致 Rollup 的一些处理失败。
     2.将 .babelrc 文件放在 src 中,而不是根目录下。 这允许我们对于不同的任务有不同的 .babelrc 配置,比如像测试,如果我们以后需要的话 - 通常为单独的任务单独配置会更好。
    
  4. npm install @babel/core @babel/preset-env –save-dev

  5. npm run build
    打包后出来的文件内容经过babel转换后有es6语法变成了es5语法:
    babel_build

node模块的引用

使用rollup打包的项目有时会引用三方包(node_modules文件夹中的软件包)

rollup.js编译源码中的模块引用默认只支持 ES6+的模块方式import/export。然而大量的npm模块是基于CommonJS模块方式,这就导致了大量 npm 模块不能直接编译使用。所以辅助rollup.js编译支持 npm模块和CommonJS模块方式的插件就应运而生。

rollup-plugin-node-resolve :插件允许加载第三方模块
rollup-plugin-commonjs :将 CommonJS 转换成 ES2015 模块供 Rollup 处理

    npm i -D rollup typescript tslib rollup-plugin-node-resolve rollup-plugin-commonjs
使用三方库lodash
  1. npm install lodash –save-dev

  2. vim src/index.ts

     import _ from 'lodash'
    
     const flatArray = (arrs:any) => {
         return _.flattenDeep(arrs);
     }
    
     const isBaishu = (name:string) => {
         return name === 'baishu'
     }
     export {
         isBaishu,
         flatArray
     }
    
  3. npm run build
    打包后的文件多了很多内容,即ladash的代码,被打包整合进来了。
    loadsh_in

vim rollup.config.js

    export default {
        input:'',
        output:{},
        plugins:[],
        external:['lodash']
    };
使用typescript
  1. npm i rollup-plugin-typescript –save-dev

  2. 配置rollup.config.js

     plugins: [ // 打包插件
         ...
         typescript(), // 解析TypeScript
         ...
     ],
    
  3. 配置tsconfig.json

压缩代码
  1. npm i rollup-plugin-terser –save-dev

  2. 配置rollup.config.js

     import babel from 'rollup-plugin-babel'
     import resolve from 'rollup-plugin-node-resolve';
     import commonjs from 'rollup-plugin-commonjs';
     import typescript from 'rollup-plugin-typescript';
     import pkg from './package.json';
     import { terser } from "rollup-plugin-terser";
    
     const extensions = ['.js', '.ts', '.tsx', '.json']
    
     export default {
         input: 'src/index.ts', // 打包入口
         output: { // 打包出口
             file: pkg.browser, // 最终打包出来的文件路径和文件名,这里是在package.json的browser: 'dist/index.js'字段中配置的
             format: 'umd', // umd是兼容amd/cjs/iife的通用打包格式,适合浏览器
             name:'proton-utils' // 包的全局变量名称
         },
         plugins: [ // 打包插件
             resolve(), // 查找和打包node_modules中的第三方模块
             commonjs(), // 将 CommonJS 转换成 ES2015 模块供 Rollup 处理
             typescript(), // 解析TypeScript
             babel({
                 include: 'src/**/*',
                 exclude: '**/node_modules/**',
                 extensions,
             }),
             terser()
         ],
         external:['lodash']
     };
    
  3. npm run build
    terser

编译css

一般是具体业务项目需css编译,使用webpack
js类库若必须使用css的话,rollup也是有插件编译css的

  1. npm install rollup-plugin-postcss –save-dev

  2. 配置rollup.config.js

     import babel from 'rollup-plugin-babel'
     import resolve from 'rollup-plugin-node-resolve';
     import commonjs from 'rollup-plugin-commonjs';
     import typescript from 'rollup-plugin-typescript';
     import pkg from './package.json';
     import { terser } from "rollup-plugin-terser";
     import postcss from "rollup-plugin-postcss";
    
     const extensions = ['.js', '.ts', '.tsx', '.json']
    
     export default {
         input: 'src/index.ts', // 打包入口
         output: { // 打包出口
             file: pkg.browser, // 最终打包出来的文件路径和文件名,这里是在package.json的browser: 'dist/index.js'字段中配置的
             format: 'umd', // umd是兼容amd/cjs/iife的通用打包格式,适合浏览器
             name:'proton-utils' // 包的全局变量名称
         },
         plugins: [ // 打包插件
             resolve(), // 查找和打包node_modules中的第三方模块
             commonjs(), // 将 CommonJS 转换成 ES2015 模块供 Rollup 处理
             typescript(), // 解析TypeScript
             babel({
                 include: 'src/**/*',
                 exclude: '**/node_modules/**',
                 extensions,
             }),
             terser(),
             postcss()
         ],
         external:['lodash']
     };
    
区分开发环境和生产环境

在开发环境我们需要sourcemap开启,配置热更新和本地服务,在生产环境我们需要sourcemap关闭,不需要热更新和本地服务,需要代码压缩等,所以需要区分。

  1. rollup.config.js拆分成两个rollup.config.dev.js和rollup.config.build.js

  2. 修改 package.json 中的打包命名

     "scripts": {
         "test": "echo \"Error: no test specified\" && exit 1",
         "build": "rm -rf ./dist && rollup --config rollup.config.build.js && npm run copy",
         "dev": "rm -rf ./dist && rollup --config rollup.config.dev.js -w",
         "copy": "cp package.json README.md dist",
         "publish": "node scripts/publish.js",
         "webpack": "webpack"
     },
    
开启本地服务
  1. npm install rollup-plugin0dev –save-dev

  2. 配置rollup.config.dev.js

     import babel from 'rollup-plugin-babel'
     import resolve from 'rollup-plugin-node-resolve';
     import commonjs from 'rollup-plugin-commonjs';
     import typescript from 'rollup-plugin-typescript';
     import pkg from './package.json';
     // import { terser } from "rollup-plugin-terser";
     // import postcss from "rollup-plugin-postcss";
     import dev from 'rollup-plugin-dev';
    
     const extensions = ['.js', '.ts', '.tsx', '.json']
    
     export default {
         input: 'src/index.ts', // 打包入口
         output: { // 打包出口
             file: './dist/index.js', // 最终打包出来的文件路径和文件名,这里是在package.json的browser: 'dist/index.js'字段中配置的
             format: 'umd', // umd是兼容amd/cjs/iife的通用打包格式,适合浏览器
             name:'proton-utils' // 包的全局变量名称
         },
         plugins: [ // 打包插件
             resolve(), // 查找和打包node_modules中的第三方模块
             commonjs(), // 将 CommonJS 转换成 ES2015 模块供 Rollup 处理
             typescript(), // 解析TypeScript
             babel({
                 include: 'src/**/*',
                 exclude: '**/node_modules/**',
                 extensions,
             }),
             // terser(),
             // postcss(),
             dev({
                 port:8010,
                 dirs:["dist",'./'],
             })
         ],
         external:['lodash'],
     };
    
  3. 根目录创建页面
    touch index.html & vim index.html

    Rollup Example baishu test
  4. npm run dev 生成 dist/index.js,同时可访问 8010 端口:
    run_dev

开启热更新
  1. npm install rollup-plugin-livereload –save-dev

  2. 配置rollup.config.dev.js

     import babel from 'rollup-plugin-babel'
     import resolve from 'rollup-plugin-node-resolve';
     import commonjs from 'rollup-plugin-commonjs';
     import typescript from 'rollup-plugin-typescript';
     import pkg from './package.json';
     // import { terser } from "rollup-plugin-terser";
     // import postcss from "rollup-plugin-postcss";
     import dev from 'rollup-plugin-dev';
     import livereload from "rollup-plugin-livereload";
    
     const extensions = ['.js', '.ts', '.tsx', '.json']
    
     export default {
         input: 'src/index.ts', // 打包入口
         output: { // 打包出口
             file: './dist/index.js', // 最终打包出来的文件路径和文件名,这里是在package.json的browser: 'dist/index.js'字段中配置的
             format: 'umd', // umd是兼容amd/cjs/iife的通用打包格式,适合浏览器
             name:'proton-utils' // 包的全局变量名称
         },
         plugins: [ // 打包插件
             resolve(), // 查找和打包node_modules中的第三方模块
             commonjs(), // 将 CommonJS 转换成 ES2015 模块供 Rollup 处理
             typescript(), // 解析TypeScript
             babel({
                 include: 'src/**/*',
                 exclude: '**/node_modules/**',
                 extensions,
             }),
             // terser(),
             // postcss(),
             dev({
                 port:8010,
                 dirs:["dist",'./'],
             }),
             livereload(),
         ],
         external:['lodash'],
     };
    
多个输入输出文件
  1. 配置rollup.config.dev.js
    import babel from ‘rollup-plugin-babel’
    import resolve from ‘rollup-plugin-node-resolve’;
    import commonjs from ‘rollup-plugin-commonjs’;
    import typescript from ‘rollup-plugin-typescript’;
    import pkg from ‘./package.json’;
    import { terser } from “rollup-plugin-terser”;
    // import postcss from “rollup-plugin-postcss”;

const extensions = [‘.js’, ‘.ts’, ‘.tsx’, ‘.json’]

export default [
// UMD for browser-friendly build
{
input: ‘src/index.ts’, // 打包入口
output: { // 打包出口
file: pkg.browser, // 最终打包出来的文件路径和文件名,这里是在package.json的browser: ‘dist/index.js’字段中配置的
format: ‘umd’, // umd是兼容amd/cjs/iife的通用打包格式,适合浏览器
name:’proton-utils’ // 包的全局变量名称
},
plugins: [ // 打包插件
resolve(), // 查找和打包node_modules中的第三方模块
commonjs(), // 将 CommonJS 转换成 ES2015 模块供 Rollup 处理
typescript(), // 解析TypeScript
babel({
include: ‘src//*’,
exclude: ‘
/node_modules/**’,
extensions,
}),
terser()
// postcss()
],
external:[‘lodash’]
},
// CommonJS for Node and ES module for bundlers build
{
input: ‘src/index.ts’,
plugins: [
typescript()
],
output: [
{ file: pkg.main, format: ‘cjs’, exports: ‘auto’ },
{ file: pkg.module, format: ‘es’, exports: ‘auto’ }
],
external:[‘lodash’]
}
];
2. npm run build
打包后dist目录:
paths

这种模式并不常见,更常见的模式是前面的一个入口文件,多个输出文件,其中输出文件的不同在于使用了不同的模块定义,比如同时输出 ES6 模块和 CommonJS 模块。

更多插件

参考:https://github.com/rollup/plugins