美文网首页
前端进阶(6) - webpack 之外的另一种选择:rollu

前端进阶(6) - webpack 之外的另一种选择:rollu

作者: senntyou | 来源:发表于2018-08-24 09:41 被阅读555次

    webpack 之外的另一种选择:rollup

    webpack 对前端来说是再熟悉不过的工具了,它提供了强大的功能来构建前端的资源,包括 html/js/ts/css/less/scss ... 等语言脚本,也包括 images/fonts ... 等二进制文件。

    其实,webpack 发起之初主要是为了解决以下两个问题:

    1. 代码拆分(Code Splitting): 可以将应用程序分解成可管理的代码块,可以按需加载,这样用户便可快速与应用交互,而不必等到整个应用程序下载和解析完成才能使用,以此构建复杂的单页应用程序(SPA);
    2. 静态资源(Static Assets): 可以将所有的静态资源,如 js、css、图片、字体等,导入到应用程序中,然后由 webpack 使用 hash 重命名需要的资源文件,而无需为文件 URL 增添 hash 而使用 hack 脚本,并且一个资源还能依赖其他资源。

    正是因为 webpack 拥有如此强大的功能,所以 webpack 在进行资源打包的时候,就会产生很多冗余的代码(如果你有查看过 webpack 的 bundle 文件,便会发现)。

    比如,把 export default str => str; 这段代码用 webpack 打包就会得到下面的结果:

    /******/ (function(modules) { // webpackBootstrap
    /******/    // The module cache
    /******/    var installedModules = {};
    /******/
    /******/    // The require function
    /******/    function __webpack_require__(moduleId) {
    /******/
    /******/        // Check if module is in cache
    /******/        if(installedModules[moduleId]) {
    /******/            return installedModules[moduleId].exports;
    /******/        }
    /******/        // Create a new module (and put it into the cache)
    /******/        var module = installedModules[moduleId] = {
    /******/            i: moduleId,
    /******/            l: false,
    /******/            exports: {}
    /******/        };
    /******/
    /******/        // Execute the module function
    /******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    /******/
    /******/        // Flag the module as loaded
    /******/        module.l = true;
    /******/
    /******/        // Return the exports of the module
    /******/        return module.exports;
    /******/    }
    /******/
    /******/
    /******/    // expose the modules object (__webpack_modules__)
    /******/    __webpack_require__.m = modules;
    /******/
    /******/    // expose the module cache
    /******/    __webpack_require__.c = installedModules;
    /******/
    /******/    // define getter function for harmony exports
    /******/    __webpack_require__.d = function(exports, name, getter) {
    /******/        if(!__webpack_require__.o(exports, name)) {
    /******/            Object.defineProperty(exports, name, {
    /******/                configurable: false,
    /******/                enumerable: true,
    /******/                get: getter
    /******/            });
    /******/        }
    /******/    };
    /******/
    /******/    // getDefaultExport function for compatibility with non-harmony modules
    /******/    __webpack_require__.n = function(module) {
    /******/        var getter = module && module.__esModule ?
    /******/            function getDefault() { return module['default']; } :
    /******/            function getModuleExports() { return module; };
    /******/        __webpack_require__.d(getter, 'a', getter);
    /******/        return getter;
    /******/    };
    /******/
    /******/    // Object.prototype.hasOwnProperty.call
    /******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    /******/
    /******/    // __webpack_public_path__
    /******/    __webpack_require__.p = "";
    /******/
    /******/    // Load entry module and return exports
    /******/    return __webpack_require__(__webpack_require__.s = 0);
    /******/ })
    /************************************************************************/
    /******/ ([
    /* 0 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    
    /* harmony default export */ __webpack_exports__["default"] = (str => str);
    
    
    /***/ })
    /******/ ]);
    

    这在以下的一些情境中就不太高效,需要寻求更好的解决方案:

    1. 需要 js 高效运行。因为 webpack 对子模块定义和运行时的依赖处理(__webpack_require__),不仅导致文件体积增大,还会大幅拉低性能;
    2. 项目(特别是类库)只有 js,而没有其他的静态资源文件,使用 webpack 就有点大才小用了,因为 webpack bundle 文件的体积略大,运行略慢,可读性略低。

    在这种情况下,就想要寻求一种更好的解决方案,这便是 rollup.

    现在已经有很多类库都在使用 rollup 进行打包了,比如:react, vue, preact, three.js, moment, d3 等。

    1. 工具

    安装

    npm i -g rollup          # 全局安装
    
    npm i -D rollup          # 本地安装
    

    使用

    rollup -c                # 使用一个配置文件,进行打包操作
    

    更多详细的用法,参考 rollup.js - command-line-flags.

    2. 配置

    rollup 的配置与 webpack 的配置类似,定义在 rollup.config.js 文件中,比如:

    // rollup.config.js
    export default {
      input: 'src/index.js',
      output: {
        file: 'bundle.js',
        // amd, cjs, esm, iife, umd, system
        format: 'cjs'
      }
    };
    

    常用的几个配置项:

    1. input: 源码入口文件,一般是一个文件,如 src/index.js
    2. output: 定义输出,如文件名,目标目录,输出模块范式(es6, commonjs, amd, umd, iife 等),模块导出名称,外部库声明,全局变量等。
    3. plugins: 插件,比如 rollup-plugin-json 可以让 rollup 从 .json 文件中导入 json 数据。

    更多详细的配置,参考 rollup.js - configuration-files.

    3. rollup 与 webpack 对比

    先拿段代码来来看看他们打包之后各自是什么效果。

    源代码

    # 目录
    |-- src/
        |-- index.js
        |-- prefix.js
        |-- suffix.js
    
        
    # prefix.js
    const prefix = 'prefix';
    
    export default str => `${prefix} | ${str}`;
        
        
    # suffix.js
    const suffix = 'suffix';
    
    export default str => `${str} | ${suffix}`;
    
    
    # index.js
    import prefix from './prefix';
    import suffix from './suffix';
    
    export default str => suffix(prefix(str)); 
    

    配置

    # webpack.config.js
    module.exports = {
      entry: './src/index.js',
      output: {
        filename: 'dist/webpack.bundle.js',
        library: 'demo',
        libraryTarget: 'umd'
      }
    };
    
    
    # rollup.config.js
    export default {
      input: 'src/index.js',
      output: {
        file: 'dist/rollup.bundle.js',
        name: 'demo',
        format: 'umd'
      }
    };
    

    运行

    # webpack 打包
    webpack
    
    
    # rollup 打包
    rollup -c
    

    webpack.bundle.js

    (function webpackUniversalModuleDefinition(root, factory) {
        if(typeof exports === 'object' && typeof module === 'object')
            module.exports = factory();
        else if(typeof define === 'function' && define.amd)
            define([], factory);
        else if(typeof exports === 'object')
            exports["demo"] = factory();
        else
            root["demo"] = factory();
    })(typeof self !== 'undefined' ? self : this, function() {
    return /******/ (function(modules) { // webpackBootstrap
    /******/    // The module cache
    /******/    var installedModules = {};
    /******/
    /******/    // The require function
    /******/    function __webpack_require__(moduleId) {
    /******/
    /******/        // Check if module is in cache
    /******/        if(installedModules[moduleId]) {
    /******/            return installedModules[moduleId].exports;
    /******/        }
    /******/        // Create a new module (and put it into the cache)
    /******/        var module = installedModules[moduleId] = {
    /******/            i: moduleId,
    /******/            l: false,
    /******/            exports: {}
    /******/        };
    /******/
    /******/        // Execute the module function
    /******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    /******/
    /******/        // Flag the module as loaded
    /******/        module.l = true;
    /******/
    /******/        // Return the exports of the module
    /******/        return module.exports;
    /******/    }
    /******/
    /******/
    /******/    // expose the modules object (__webpack_modules__)
    /******/    __webpack_require__.m = modules;
    /******/
    /******/    // expose the module cache
    /******/    __webpack_require__.c = installedModules;
    /******/
    /******/    // define getter function for harmony exports
    /******/    __webpack_require__.d = function(exports, name, getter) {
    /******/        if(!__webpack_require__.o(exports, name)) {
    /******/            Object.defineProperty(exports, name, {
    /******/                configurable: false,
    /******/                enumerable: true,
    /******/                get: getter
    /******/            });
    /******/        }
    /******/    };
    /******/
    /******/    // getDefaultExport function for compatibility with non-harmony modules
    /******/    __webpack_require__.n = function(module) {
    /******/        var getter = module && module.__esModule ?
    /******/            function getDefault() { return module['default']; } :
    /******/            function getModuleExports() { return module; };
    /******/        __webpack_require__.d(getter, 'a', getter);
    /******/        return getter;
    /******/    };
    /******/
    /******/    // Object.prototype.hasOwnProperty.call
    /******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    /******/
    /******/    // __webpack_public_path__
    /******/    __webpack_require__.p = "";
    /******/
    /******/    // Load entry module and return exports
    /******/    return __webpack_require__(__webpack_require__.s = 0);
    /******/ })
    /************************************************************************/
    /******/ ([
    /* 0 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__prefix__ = __webpack_require__(1);
    /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__suffix__ = __webpack_require__(2);
    
    
    
    /* harmony default export */ __webpack_exports__["default"] = (str => Object(__WEBPACK_IMPORTED_MODULE_1__suffix__["a" /* default */])(Object(__WEBPACK_IMPORTED_MODULE_0__prefix__["a" /* default */])(str)));
    
    
    /***/ }),
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    const prefix = 'prefix';
    
    /* harmony default export */ __webpack_exports__["a"] = (str => `${prefix} | ${str}`);
    
    
    /***/ }),
    /* 2 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    const suffix = 'suffix';
    
    /* harmony default export */ __webpack_exports__["a"] = (str => `${str} | ${suffix}`);
    
    
    /***/ })
    /******/ ]);
    });
    

    rollup.bundle.js

    (function (global, factory) {
        typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
        (global.demo = factory());
    }(this, (function () { 'use strict';
    
        const prefix = 'prefix';
    
        var prefix$1 = str => `${prefix} | ${str}`;
    
        const suffix = 'suffix';
    
        var suffix$1 = str => `${str} | ${suffix}`;
    
        var index = str => suffix$1(prefix$1(str));
    
        return index;
    
    })));
    

    其实,你也基本上看出来了,在这种场景下,rollup 的优势在哪里:

    1. 文件很小,几乎没什么多余代码,除了必要的 cjs, umd 头外,bundle 代码基本和源码差不多,也没有奇怪的 __webpack_require__, Object.defineProperty 之类的东西;
    2. 执行很快,因为没有 webpack bundle 中的 __webpack_require__, Object.defineProperty 之类的冗余代码;
    3. 另外,rollup 也对 es 模块输出及 iife 格式打包有很好的支持。

    4. 结论

    rollup 相对 webpack 而言,要小巧、干净利落一些,但不具备 webpack 的一些强大的功能,如热更新,代码分割,公共依赖提取等。

    所以,一个不错的选择是,应用使用 webpack,类库使用 rollup。

    5. 后续

    更多博客,查看 https://github.com/senntyou/blogs

    作者:深予之 (@senntyou)

    版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

    相关文章

      网友评论

          本文标题:前端进阶(6) - webpack 之外的另一种选择:rollu

          本文链接:https://www.haomeiwen.com/subject/ztxbiftx.html