rollup相关整理

之前封装一些通用JavaScript类库或者vuejs组件库都是 webpack弄得,结果就不说了,吐槽的太多了……也许是用错了地方。偶然间遇到了rollup,并查了一下和 webpack的区别,rollup才是想要的嘛!这里记录一下关于他的一切,从ES6转ES5开始。

ES6+转ES5(rollup.config.js)

这个简直就是必备啊!虽然现在大部分浏览器对ES6+的支持已经很好了,但是永远不能保证用户的浏览器……JavaScript 的新特性越来越多,不用又有些难受,所以转换成低版本的成为了必不可少的工作。说道转ES5,就不得不说babel 了,他也是这章节的主角。最近还发现了一个和他类似的工具:Rust-based platform for the Web – SWC,好像还不像babel那么强大,这里只是记录一下。

下面就切入正题,先弄一个项目作为本章节的测试,目录结构如下:

//  下面是 src/index.js 文件的内容

document.addEventListener("DOMContentLoaded", () => {

    //  模板字符串``相关
    console.log(`模板字符串-反引号=>现在时间:${new Date()}`);

    //  解构赋值相关
    let { log } = console;
    const PI = 3.14;
    log("解构console之log方法,之后输入常量π的近似值:" + PI);

    let contextInfo = {
        readonly: false,
        userInfo: {
            name: "ddz001",
            pwd: "",
            tokenId: "666"
        },
        p1: "",
        fn1() {
            console.log("contextInfo.fn1");
        }
    };
    let { readonly: isReadOnly, p1 } = contextInfo;
    console.log("对象解构赋值之是否只读:" + isReadOnly);
    console.log("对象解构赋值之p1:" + p1);

    let [arrIndex0 = 10, arrIndex1, arrIndex2, arrIndex3 = 4] = [0, [1, 2], 3];
    console.log("数值解构赋值之第一个:" + arrIndex0);
    console.log("数值解构赋值之第二个:" + arrIndex1);
    console.log("数值解构赋值之第三个:" + arrIndex2);
    console.log("数值解构赋值之第四个:" + arrIndex3);

    //  类(Class)相关
    class Person {
        constructor(name) {
            this.name = name;
            this.gender = "";
            this.birthday = "";
            this.idCard = "";
        }

        sayName() {
            return this.name;
        }
    }
    let person1 = new Person("ddz001");
    console.log(person1);

    //  async、await 关键字相关
    async function promiseFn1() {
        return "async、await相关:该方法没有返回Promise,而是在方法前添加async";
    }
    let retPromiseFn1 = promiseFn1();
    console.log("async、await相关:" + Object.prototype.toString.call(retPromiseFn1));

    async function promiseFn2() {
        // let isTrue = await Promise.resolve(true);
        let isTrue = await true;
        let retPromiseFn1 = await promiseFn1();
        console.log("async、await相关:" + isTrue);
        console.log("async、await相关:" + retPromiseFn1);
    }
    promiseFn2();

    //**************************************************************************************************************/

    console.log("数组实例方法includes:" + [1, 2, 3].includes("1"));
    console.log("数组实例方法includes:" + [1, 2, 3].includes(3));
    let arrFromStr = Array.from("利用Array.from将字符串转成数组!");
    console.log(arrFromStr);

    let set1 = new Set([1, 6, 6, 6, 8, 8, 8, 9]);
    console.log(set1);

    Promise.resolve(999).then(x => {
        console.log("Promise返回值:" + x);
    });
});
        
//  下面是 package.json 文件的内容(还没有添加依赖)

{
  "name": "xxx",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "c": "rollup -c",
    "cw": "rollup -cw"
  }
}
        

第一次尝试

各种尝试主要是针对“打包依赖”和rollup.config.js来说的。第一次尝试最简单:

#   你可以用下面的命令安装

yarn add @babel/core @rollup/plugin-babel rollup -D
        
// 下面是 rollup.config.js 文件的内容

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

export default {
    input: './src/index.js',
    output: {
        file: 'dist/index.js',
        format: 'iife'
    },
    plugins: [
        babel()
    ]
};
        

babelHelpers: 'bundled' option was used by default. It is recommended to configure this option explicitly, read more here: https://github.com/rollup/plugins/tree/master/packages/babel#babelhelpers

说明:代码没有变化,只是外边添加一个立即执行函数

第二次尝试

第二次尝试,依赖慢慢添加。

#   你可以用下面的命令安装

yarn add @babel/core @babel/preset-env @rollup/plugin-babel rollup -D
        
// 下面是 rollup.config.js 文件的内容

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

export default {
    input: './src/index.js',
    output: {
        file: 'dist/index.js',
        format: 'iife'
    },
    plugins: [
        babel({
            presets: ['@babel/preset-env']
        })
    ]
};
        

说明:上面的配置只会转换一些语法,例如:let、const、class、数组或者对象的解构赋值等,async和await也会转换(转换之后用到了Promise);像新增的Array.from、Set、Promise等不会转换。

在浏览器中运行的时候报错了,还是Chrome,并且是最新版的,什么鬼……

Uncaught ReferenceError: regeneratorRuntime is not defined

之后又看了一下转码之后的代码,确实找到了regeneratorRuntime,并且还给出了提示 regenerator-runtime ,这时也想起之前用webapck转ES5时安装过这个包。但是这里node_modules目录下已经有这个包了,算了先不纠结他了,应该很少单独这么用。

第三次尝试

第三次尝试,依赖再次添加,终于像点样子了。

#   你可以用下面的命令安装

yarn add @babel/core @babel/preset-env @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve core-js rollup -D
        
// 下面是 rollup.config.js 文件的内容

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

export default {
    input: './src/index.js',
    output: {
        file: 'dist/index.js',
        format: 'iife'
    },
    plugins: [
        nodeResolve({
            browser: true
        }),
        commonjs(),
        babel({
            babelrc: false,
            exclude: /node_modules/,
            // @babel/preset-env 选项设置参考  https://babeljs.io/docs/en/babel-preset-env
            presets: [
                ["@babel/preset-env", {
                    useBuiltIns: "usage",
                    corejs: {
                        version: 3,
                        proposals: true,
                    },
                    //  targets选项 参考:https://babeljs.io/docs/en/babel-preset-env#targets
                    // targets: {
                    //     chrome: 96,
                    // },
                    // targets: {
                    //     ie: 8
                    // },
                    targets: {
                        browsers: ["last 2 versions"]
                    }
                }]
            ],
        })
    ]
};
        

说明:采用上面的配置,像新增的Array.prototype.includes、Array.from、Set、Promise将会“转换”,这种方式会Array、Array.prototype或者window上补齐方法。 一般都推荐在自己的应用程序中使用,如果是通用的类库则不推荐使用这种方式

第四次尝试

第四次尝试,和第三次差不多,都实现转换,各有利弊。

#   你可以用下面的命令安装

yarn add @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/runtime @babel/runtime-corejs3 @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup -D
        
// 下面是 rollup.config.js 文件的内容

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

export default {
    input: './src/index.js',
    output: {
        file: 'dist/index.js',
        format: 'iife'
    },
    plugins: [
        nodeResolve({
            browser: true
        }),
        commonjs(),
        babel({
            babelrc: false,
            exclude: /node_modules/,
            babelHelpers: 'runtime',
            presets: ['@babel/preset-env'],
            plugins: [
                ['@babel/plugin-transform-runtime', {
                    corejs: {
                        version: 3,
                        proposals: true
                    },
                    useESModules: true
                }]
            ]
        })
    ]
};
        

说明:这种方式没有在Array、Array.prototype或者window上补齐缺少的方法而是重命名(或者叫做替换)的方式。这种方式对大环境没有影响。

第五次尝试

综合第三次和第四次的优点, GitHub - babel/babel-polyfills: A set of Babel plugins that enable injecting different polyfills with different strategies in your compiled code. 我们期待他的到来……

ES6+转ES5(JavaScript API)

上一章节是使用配置文件处理的,这一章节介绍一下使用API处理。为了做一下对比,上面的配置也修改一下来生成map文件,这里是在“第四次尝试”的基础上进行修改。先看一下修改之后的配置:

// 下面是 rollup.config.js 文件的内容

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

export default {
    input: './src/index.js',
    output: {
        file: 'dist/index.js',
        format: 'iife',
        sourcemap: true,    //  和“第四次尝试”对比新增的选项
    },
    plugins: [
        nodeResolve({
            browser: true
        }),
        commonjs(),
        babel({
            babelrc: false,
            exclude: /node_modules/,
            babelHelpers: 'runtime',
            presets: ['@babel/preset-env'],
            plugins: [
                ['@babel/plugin-transform-runtime', {
                    corejs: {
                        version: 3,
                        proposals: true
                    },
                    useESModules: true
                }]
            ]
        })
    ]
};
        

赶紧试试吧!先生成一个,方便后面的对比,看看是不是一样。

下面就切入正题,介绍一下使用API。其实关于rollup的配置,就是拆分成了两部分:输入选项和输出选项;之后就是调用rollup的API了;最后就是将返回的内容写入文件了……这里新建一个"build.js"文件,和"rollup.config.js"文件同级

//      下面是 build.js 文件的内容

const rollup = require("rollup");
const { babel } = require("@rollup/plugin-babel");
const { nodeResolve } = require("@rollup/plugin-node-resolve");
const commonjs = require("@rollup/plugin-commonjs");

const fs = require("fs");
if (!fs.existsSync("dist")) {
    fs.mkdirSync("dist");
}

const inputOptions = {
    input: './src/index.js',
    plugins: [
        nodeResolve({
            browser: true
        }),
        commonjs(),
        babel({
            babelrc: false,
            exclude: /node_modules/,
            babelHelpers: 'runtime',
            presets: ['@babel/preset-env'],
            plugins: [
                ['@babel/plugin-transform-runtime', {
                    corejs: {
                        version: 3,
                        proposals: true
                    },
                    useESModules: true
                }]
            ]
        })
    ]
};
const outputOptions = {
    file: 'dist/index.js',
    format: 'iife',
    sourcemap: true,
};

rollup.rollup(inputOptions).then((bundle) => {
    bundle.generate(outputOptions).then((outputData) => {
        let {
            output: [{ code, map }],
        } = outputData,
            //  暂时先这样获取文件名
            outputFileName = outputOptions.file.slice(outputOptions.file.lastIndexOf("/") + 1),
            jsMapFileName = "";
        //  对于map先这样处理,暂时不考虑sourcemapFile
        if (outputOptions.sourcemap) {
            code += `//# sourceMappingURL=${outputFileName}.map`;
            jsMapFileName = outputOptions.file + ".map";
        }
        fs.writeFile(outputOptions.file, code, (err) => {
            console.log(JSON.stringify(err));
        });
        if (outputOptions.sourcemap) {
            fs.writeFile(
                jsMapFileName,
                JSON.stringify(map),
                (err) => {
                    console.log(JSON.stringify(err));
                }
            );
        }
    });
});
        

之后在控制台执行node build.js就可以了。这里对比发现转码之后的JS文件以及对应的map文件完全相同。