使用 Rollup 搭建 React + TypeScript 脚手架

6,240 阅读3分钟

首先先初始化一下项目:

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 这样的包。

以上就是全部啦,撒花。