Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

一文带你快速上手Rollup #50

Open
Cosen95 opened this issue Sep 7, 2020 · 1 comment
Open

一文带你快速上手Rollup #50

Cosen95 opened this issue Sep 7, 2020 · 1 comment

Comments

@Cosen95
Copy link
Owner

Cosen95 commented Sep 7, 2020

前言

项目中一直用的都是webpack,前一段需要开发几个类库供其他平台使用,本来打算继续用webpack的,但感觉webpack用来开发js库,不仅繁琐而且打包后的文件体积也比较大。正好之前看vue源码,知道vue也是通过rollup打包的。这次又是开发类库的,于是就快速上手了rollup

本篇文章是我有了一定的项目实践后,回过来给大家分享一下如何从零快速上手rollup

什么是rollup

系统的了解rollup之前,我们先来简单了解下What is rollup?

关于rollup的介绍,官方文档已经写的很清楚了:

Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。

Webpack偏向于应用打包的定位不同,rollup.js更专注于Javascript类库打包。

我们熟知的VueReact等诸多知名框架或类库都是通过rollup.js进行打包的。

为什么是rollup

webpack我相信做前端的同学大家都用过,那么为什么有些场景还要使用rollup呢?这里我简单对webpackrollup做一个比较:

总体来说webpackrollup在不同场景下,都能发挥自身优势作用。webpack对于代码分割和静态资源导入有着“先天优势”,并且支持热模块替换(HMR),而rollup并不支持。

所以当开发应用时可以优先选择webpack,但是rollup对于代码的Tree-shakingES6模块有着算法优势上的支持,若你项目只需要打包出一个简单的bundle包,并是基于ES6模块开发的,可以考虑使用rollup

其实webpack2.0开始就已经支持Tree-shaking,并在使用babel-loader的情况下还可以支持es6 module的打包。实际上,rollup已经在渐渐地失去了当初的优势了。但是它并没有被抛弃,反而因其简单的API、使用方式被许多库开发者青睐,如ReactVue等,都是使用rollup作为构建工具的。

快速上手

我们先花大概十分钟左右的时间来了解下rollup的基本使用以及完成一个hello world

安装

首先全局安装rollup

npm i rollup -g

目录准备(hello world)

接着,我们初始化一个如下所示的项目目录

├── dist # 编译结果
├── example # HTML引用例子
   └── index.html
├── package.json
└── src # 源码
    └── index.js

首先我们在src/index.js中写入如下代码:

console.log("柯森");

然后在命令行执行以下命令:

rollup src/index.js -f umd -o dist/bundle.js

执行命令,我们即可在dist目录下生成bundle.js文件:

(function (factory) {
	typeof define === 'function' && define.amd ? define(factory) :
	factory();
}((function () { 'use strict';

	console.log("柯森");

})));

这时,我们再在example/index.html中引入上面打包生成的bundle.js文件,打开浏览器:

如我们所预料的,控制台输出了柯森

到这里,我们就用rollup打包了一个最最简单的demo

可能很多同学看到这里对于上面命令行中的参数不是很明白,我依次说明下:

  • -f-f参数是--format的缩写,它表示生成代码的格式,amd表示采用AMD标准,cjsCommonJS标准,esm(或 es)为ES模块标准。-f的值可以为amdcjssystemesm('es’也可以)、iifeumd中的任何一个。
  • -o-o指定了输出的路径,这里我们将打包后的文件输出到dist目录下的bundle.js

其实除了这两个,还有很多其他常用的命令(这里我暂且列举剩下两个也比较常用的,完整的rollup 命令行参数):

  • -c。指定rollup的配置文件。
  • -w。监听源文件是否有改动,如果有改动,重新打包。

使用配置文件(rollup.config.js)

使用命令行的方式,如果选项少没什么问题,但是如果添加更多的选项,这种命令行的方式就显得麻烦了。

为此,我们可以创建配置文件来囊括所需的选项

在项目中创建一个名为rollup.config.js的文件,增加如下代码:

export default {
  input: ["./src/index.js"],
  output: {
    file: "./dist/bundle.js",
    format: "umd",
    name: "experience",
  },
};

然后命令行执行:

rollup -c

打开dist/bundle.js文件,我们会发现和上面采用命令行的方式打包出来的结果是一样的。

这里,我对配置文件的选项做下简单的说明:

  • input表示入口文件的路径(老版本为 entry,已经废弃)
  • output表示输出文件的内容,它允许传入一个对象或一个数组,当为数组时,依次输出多个文件,它包含以下内容:
    • output.file:输出文件的路径(老版本为 dest,已经废弃)
    • output.format:输出文件的格式
    • output.banner:文件头部添加的内容
    • output.footer:文件末尾添加的内容

到这里,相信你已经差不多上手rollup了。

进阶

但是,这对于真实的业务场景是远远不够的。

下面,我将介绍rollup中的几种常用的插件以及external属性、tree-shaking机制。

resolve插件

为什么要使用resolve插件

在上面的入门案例中,我们打包的对象是本地的js代码和库,但实际开发中,不太可能所有的库都位于本地,我们大多会通过npm下载远程的库。

webpackbrowserify这样的其他捆绑包不同,rollup不知道如何打破常规去处理这些依赖。因此我们需要添加一些配置。

resolve插件使用

首先在我们的项目中添加一个依赖the-answer,然后修改src/index.js文件:

import answer from "the-answer";

export default function () {
  console.log("the answer is " + answer);
}

执行npm run build

这里为了方便,我将原本的rollup -c -w添加到了package.jsonscripts中:"build": "rollup -c -w"

会得到以下报错:

打包后的bundle.js仍然会在Node.js中工作,但是the-answer不包含在包中。为了解决这个问题,将我们编写的源码与依赖的第三方库进行合并,rollup.js为我们提供了resolve插件。

首先,安装resolve插件:

npm i -D @rollup/plugin-node-resolve

修改配置文件rollup.config.js

import resolve from "@rollup/plugin-node-resolve";

export default {
  input: ["./src/index.js"],
  output: {
    file: "./dist/bundle.js",
    format: "umd",
    name: "experience",
  },
  plugins: [resolve()],
};

这时再次执行npm run build,可以发现报错已经没有了:

打开dist/bundle.js文件:

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';

  var index = 42;

  function index$1 () {
    console.log("the answer is " + index);
  }

  return index$1;

})));

打包文件bundle.js中已经包含了引用的模块。

有些场景下,虽然我们使用了resolve插件,但可能我们仍然想要某些库保持外部引用状态,这时我们就需要使用external属性,来告诉rollup.js哪些是外部的类库。

external 属性

修改rollup.js的配置文件:

import resolve from "@rollup/plugin-node-resolve";

export default {
  input: ["./src/index.js"],
  output: {
    file: "./dist/bundle.js",
    format: "umd",
    name: "experience",
  },
  plugins: [resolve()],
  external: ["the-answer"],
};

重新打包,打开dist/bundle.js文件:

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('the-answer')) :
  typeof define === 'function' && define.amd ? define(['the-answer'], factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory(global.answer));
}(this, (function (answer) { 'use strict';

  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

  var answer__default = /*#__PURE__*/_interopDefaultLegacy(answer);

  function index () {
    console.log("the answer is " + answer__default['default']);
  }

  return index;

})));

这时我们看到the-answer已经是做为外部库被引入了。

commonjs插件

为什么需要commonjs插件

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

因此使得rollup.js编译支持npm模块和CommonJS模块方式的插件就应运而生:@rollup/plugin-commonjs

commonjs插件使用

首先,安装该模块:

npm i -D @rollup/plugin-commonjs

然后修改rollup.config.js文件:

import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
export default {
  input: ["./src/index.js"],
  output: {
    file: "./dist/bundle.js",
    format: "umd",
    name: "experience",
  },
  plugins: [resolve(), commonjs()],
  external: ["the-answer"],
};

babel插件

为什么需要babel插件?

我们在src目录下添加es6.js文件(⚠️ 这里我们使用了 es6 中的箭头函数):

const a = 1;
const b = 2;
console.log(a, b);
export default () => {
  return a + b;
};

然后修改rollup.config.js配置文件:

import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
export default {
  input: ["./src/es6.js"],
  output: {
    file: "./dist/esBundle.js",
    format: "umd",
    name: "experience",
  },
  plugins: [resolve(), commonjs()],
  external: ["the-answer"],
};

执行打包,可以看到dist/esBundle.js文件内容如下:

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';

  const a = 1;
  const b = 2;
  console.log(a, b);
  var es6 = () => {
    return a + b;
  };

  return es6;

})));

可以看到箭头函数被保留下来,这样的代码在不支持ES6的环境下将无法运行。我们期望在rollup.js打包的过程中就能使用babel完成代码转换,因此我们需要babel插件。

babel插件的使用

首先,安装:

npm i -D @rollup/plugin-babel

同样修改配置文件rollup.config.js

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

export default {
  input: ["./src/es6.js"],
  output: {
    file: "./dist/esBundle.js",
    format: "umd",
    name: "experience",
  },
  plugins: [resolve(), commonjs(), babel()],
  external: ["the-answer"],
};

然后打包,发现会出现报错:

提示我们缺少@babel/core,因为@babel/corebabel的核心。我们来进行安装:

npm i @babel/core

再次执行打包,发现这次没有报错了,但是我们尝试打开dist/esBundle.js

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';

  const a = 1;
  const b = 2;
  console.log(a, b);
  var es6 = (() => {
    return a + b;
  });

  return es6;

})));

可以发现箭头函数仍然存在,显然这是不正确的,说明我们的babel插件没有起到作用。这是为什么呢?

原因是由于我们缺少.babelrc文件,添加该文件:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false,
        // "useBuiltIns": "usage"
      }
    ]
  ]
}

我们看.babelrc配置了preset env,所以先安装这个插件:

npm i @babel/preset-env

这次再次执行打包,我们打开dist/esBundle.js文件:

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';

  var a = 1;
  var b = 2;
  console.log(a, b);
  var es6 = (function () {
    return a + b;
  });

  return es6;

})));

可以看到箭头函数被转换为了function,说明babel插件正常工作。

json插件

为什么要使用json插件?

src目录下创建json.js文件:

import json from "../package.json";
console.log(json.author);

内容很简单,就是引入package.json,然后去打印author字段。

修改rollup.config.js配置文件:

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

export default {
  input: ["./src/json.js"],
  output: {
    file: "./dist/jsonBundle.js",
    format: "umd",
    name: "experience",
  },
  plugins: [resolve(), commonjs(), babel()],
  external: ["the-answer"],
};

执行打包,发现会发生如下报错:

提示我们缺少@rollup/plugin-json插件来支持json文件。

json插件的使用

来安装该插件:

npm i -D @rollup/plugin-json

同样修改下配置文件,将插件加入plugins数组即可。

然后再次打包,发现打包成功了,我们打开生成的dist/jsonBundle目录:

(function (factory) {
  typeof define === 'function' && define.amd ? define(factory) :
  factory();
}((function () { 'use strict';

  var name = "rollup-experience";
  var version = "1.0.0";
  var description = "";
  var main = "index.js";
  var directories = {
  	example: "example"
  };
  var scripts = {
  	build: "rollup -c -w",
  	test: "echo \"Error: no test specified\" && exit 1"
  };
  var author = "Cosen";
  var license = "ISC";
  var dependencies = {
  	"@babel/core": "^7.11.6",
  	"@babel/preset-env": "^7.11.5",
  	"the-answer": "^1.0.0"
  };
  var devDependencies = {
  	"@rollup/plugin-babel": "^5.2.0",
  	"@rollup/plugin-commonjs": "^15.0.0",
  	"@rollup/plugin-json": "^4.1.0",
  	"@rollup/plugin-node-resolve": "^9.0.0"
  };
  var json = {
  	name: name,
  	version: version,
  	description: description,
  	main: main,
  	directories: directories,
  	scripts: scripts,
  	author: author,
  	license: license,
  	dependencies: dependencies,
  	devDependencies: devDependencies
  };

  console.log(json.author);

})));

完美!!

tree-shaking机制

这里我们以最开始的src/index.js为例进行说明:

import answer from "the-answer";

export default function () {
  console.log("the answer is " + answer);
}

修改上述文件:

const a = 1;
const b = 2;
export default function () {
  console.log(a + b);
}

执行打包。打开dist/bundle.js文件:

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';

  var a = 1;
  var b = 2;
  function index () {
    console.log(a + b);
  }

  return index;

})));

再次修改src/index.js文件:

const a = 1;
const b = 2;
export default function () {
  console.log(a);
}

再次执行打包,打开打包文件:

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';

  var a = 1;
  function index () {
    console.log(a);
  }

  return index;

})));

发现了什么?

我们发现关于变量b的定义没有了,因为源码中并没有用到这个变量。这就是ES模块著名的tree-shaking机制,它动态地清除没有被使用过的代码,使得代码更加精简,从而可以使得我们的类库获得更快的加载速度。

总结

本文大致向大家介绍了什么是rollup以及如何快速上手rollup。文中提到的这些其实只是冰山一角,rollup能玩的东西还有很多,关于更多可以去rollup 官网查询

❤️ 爱心三连击

1.如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~

2.关注公众号前端森林,定期为你推送新鲜干货好文。

3.特殊阶段,带好口罩,做好个人防护。

@Max199909
Copy link

good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants