阅读 1007

使用 Rollup 搭建 React + TypeScript 脚手架

在众多打包工具中,Webpack 在公司项目中是使用范围最广的了,但是像 Vue、React、Vite 等类库都是使用 Rollup 作为打包工具的。Rollup 在打包 JavaScript 库的能力上可以说是非常强的,不仅使用起来非常方便,并且生态也很完善,在平常的需求中基本不会踩到什么难以解决的坑。

今天,我给大家带来一篇使用 Rollup 构建 React + TypeScript 类库的教程。

如果你的目的只是想快速搭一个脚手架,其实必要看这篇博客,最推荐的是直接使用 Vite,零配置、开箱即用。关于使用 Vite 搭建类库的文档在这里

除此之外,如果您的目的不是搭建一个类库,而是搭建一个项目,也不是很推荐使用 Rollup,使用 Vite 或者 Webpack 会舒心很多。

Vite 依赖了 Rollup 这个包,如果想更好的使用 Vite 的话,还是要了解 Rollup 的。如果你的目的就是为了更好的了解一下 Rollup,那我们今天的文章很合适,它可以让你快速的对 Rollup 有个整体的了解,避免自己踩很多坑。


首先先初始化一下项目:

yarn init -y
mkdir src
touch src/index.js rollup.config.js
yarn -D add rollup
复制代码

再把我们项目的的文件都初始化一下:

// filename: src/index.js
function sum(a, b) {
  return a + b;
}

export {
  sum
}

// filename: rollup.config.js
export default {
  input: "src/index.js",
  output: {
    file: 'dist/bundle.js',
    format: 'cjs',
  },
}

// filename: package.json
{
  "name": "rollup-demo",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "build": "npx rollup -c rollup.config.js"
  },
  "license": "MIT",
  "devDependencies": {
    "rollup": "^2.55.1"
  }
}
复制代码

做完上面的工作之后,运行 yarn build,我们会在根目录的 dist 文件夹找到打包后的 bundle.js,这样我们就很轻松的完成了最基本的功能:打包普通的 JavaScript 文件。

接下来我们在 src/button.jsx 使用 React 写一个 Button 组件,并在 src/index.js 中导出一下:

// filename: button.jsx
import React from 'react';

function Button() {
  return <button>按钮</button>
}
复制代码

此时直接打包,会报错。

不仅仅是因为我们没有安装 React,还因为在这里,我们写了 import React from 'react' 这一行代码,而在 Rollup 中,我们要想使用 node_modules 里面的包,必须使用 node-resolve 这个插件才行,这一点和 Webpack 很不一样,在 Webpack,我们可以无需任何配置,就能直接使用 node_modules 的包。

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

export default {
  input: "src/index.js",
  plugins: [resolve()],
  output: {
    file: 'dist/bundle.js',
    format: 'cjs',
  },
}
复制代码

除此之外,还有一个坑,如果我们去看 React 源码的入口文件 index.js,会发现它是以 CommonJS 规范导出的。

Rollup 默认不支持引入 CommonJS 规范导出的包,因此,我们还要安装一个叫做 @rollup/plugin-commonjs的插件

// 这是 React 的入口文件
'use strict';

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}
复制代码

PS: 顺便提一句,React 这么分情况导出是为了方便我们开发环境 Debugger 它的源码。如果你也想让使用者在开发环境调试类库的源码,可以参考它的这种思路。

你以为到这里就完了吗,别着急,还有呢。因为 Rollup 不认识 JSX 语法,所以我们还要添加编译 JSX 语法的插件,这一点倒是和 Webpack 中是一样,我们使用 Babel 就好了:

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

export default {
  input: 'main.js',
  // 为了把 JSX 专为 JS
  plugins: [babel({ presets: ['@babel/preset-react'] })], 
  output: [
    {
      file: 'bundle.js',
      format: 'esm',
      // 为了兼容新语法,使用 babel 转译一下
      plugins: [getBabelOutputPlugin({ presets: ['@babel/preset-env'] })]
    }
  ]
};
复制代码

其实关于上面提的几点,说是坑也不合适,只是因为很多同学使用 Webpack 久了,就把它的一些东西想成自然而然的,想成了「常识」,而 Rollup 可能在某些方面会和我们的「常识」冲突。就会让我们在刚使用的时候有些不舒服。

最后,我们的 rollup.config.js 更新为:

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

export default {
  input: "src/index.js",
  plugins: [
    resolve(),
    commonjs(),
    babel({ 
      presets: ['@babel/preset-react'], 
      babelHelpers: 'bundled'  
    })
  ],
  output: {
    file: 'dist/bundle.js',
    format: 'esm',
    plugins: [
      getBabelOutputPlugin({
        presets: ['@babel/preset-env'],
      })
    ]
  },
}
复制代码

这样子我们就可以顺利的打包出来了。

接下来我们需要添加对 TypeScript 的支持,此时我们需要引入的包有 acorn-jsx@rollup/plugin-typescript。如果二者单独使用的话,会是这样子:

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

export default {
  // … other options …
  acornInjectPlugins: [jsx()],
  plugins: [typescript({ jsx: 'preserve' })]
};
复制代码

在 Rollup 中,插件的执行顺序是按数组的索引顺序的,如果把二者加到我们配置文件中去,最后的 rollup.config.js 是这样子的:

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import babel, { getBabelOutputPlugin } from '@rollup/plugin-babel';
import jsx from 'acorn-jsx';

export default {
  input: "src/index.ts",
  acornInjectPlugins: [jsx()],
  plugins: [
    resolve(),
    commonjs(),
    typescript({ jsx: 'preserve' }),
    babel({ 
      presets: ['@babel/preset-react'], 
      babelHelpers: 'bundled',
      extensions: ['.js', '.jsx', '.es6', '.es', '.mjs', '.ts', '.tsx']  
    })
  ],
  output: {
    file: 'dist/bundle.js',
    format: 'esm',
    plugins: [
      getBabelOutputPlugin({
        presets: ['@babel/preset-env'],
      })
    ]
  },
}
复制代码

这样之后,我们就能顺利的打包 React + TypeScript 的项目了。不过,作为一个类库,一般是不需要安装 React 的,这应该是使用我们项目的人安装的,所以,我们要把 React 从我们的打包结果中排出去,这里使用到了 Rollup 的一个配置:

export default {
  input: "src/index.ts",
  acornInjectPlugins: [jsx()],
  external: ['react'] // 增加了这一行。
}
复制代码

做到这里,我们就基本完成了脚手架的搭建,如何您想更方便的管理您的类库,可以考虑使用 release-itlerna 这样的包。

以上就是全部啦,撒花。

吐槽时间

我好像从来没有写过关于打包工具的文章,因为他们太繁琐了。

最开始接触的打包工具是 Webpack 2,对一个新手来说,读 Webpack 的各种配置项简直就是像跟看天书一样,那时候简简单单的搭一个 React 项目的脚手架都要踩很多坑,耗时之久、配置的繁琐,甚至都觉得可以招聘 Webpack 工程师这一岗位了。

前公司的老项目也是使用的 Webpack 2,我们在本地做一点变更,等待项目的更新时间就要等一分半,可想而知,开发体验得有多差。后来我把它迁移到 Webpack 4,HRM 已经很快了,但是打包的速度依然很慢。

有了这些使用它的糟糕体验,直到现在,也不是特别喜欢 Webpack。如果让我配置一个 Webpack 项目,第一时间就是去找一个配置生成器,然后把配置生成器生成的项目克隆到本地使用,无论配置多少次 Webpack 的项目,我都没有信心记住 Webpack 的配置项......

最开始对 Vite 的印象是从别人口中听来的:快。

但当真正实际体验一段时间之后,我发现自己真的爱上了它,它的体验太丝滑了,让你完全可以很无脑的去使用。「快」可能是让我尝试 Vite 的原因,而「体验感」是让我完全入坑 Vite 的原因,我们还需要像学 Webpack 那样去学 Vite 吗?完全不用,因为 Vite 不需要学,比起 Webpack 那晦涩难懂的文档,Vite 的文档读起来也轻松多了,这才是工具的本质吧。

以前对 Rollup 一直很陌生,在这一周对 Vite 的使用过程,我又重新认识了 Rollup,发现它的很多理念也非常的棒,在以后,使用 Webpack 的次数应该会越来越少了。

但是我依然承认 Webpack 的伟大。上面的一些感悟都抛开了时代这个前提,放在前端的历史长河中,它们都很厉害。我们不能仅仅从一个方面就说亚里士多德不如牛顿伟大,说牛顿不如爱因斯坦伟大,说爱因斯坦不如波尔伟大,他们都很厉害,只不过每个人生活的环境、时代的局限性限制了他们的发展。写到这里,不禁感叹,这世上,有多少人生不逢时,又有多少人错过。

文章分类
前端
文章标签