dogc之rollup配置

1,014 阅读5分钟

rollup要达到的目的

  • 支持typescript
  • 导出esm、cjs、umd模块规范
  • 组件维度split code

为什么要用typescript

TypeScript 的诞生不是为了取代 JavaScript ,而是让 JavaScript 变得更好。

  • 类型检查 静态类型检查让 TS 在编辑器中披上强类型语言的“马甲”,使得开发者在编码时就可以避免大多数类型错误的情况发生,而开发者要做的就只是声明变量时多写一个符号和一个单词
  • 代码自动补全、智能提示 类型系统配合声明文件给我们带来了编辑器中完善的自动补全智能提示,大大增加了开发效率,也再不会因为拼错变量名或函数名而导致运行时的错误。

为什么要导出esm、cjs、umd模块规范

  • esm: 主要提供给现代的打包工具(Webpack, Rollup)(npm 引入)使用,现代的打包工具会识别 package.json 中的 module 字段,如果包含这个字段,则会优先加载使用这个字段所对应的 ES Module, 在结合组件库的 sideEffect 配置可以实现 tree-shaking , 从而实现代码体积优化。
  • umd: 是一个通用模块定义,结合amd cjs iife 为一体,其打包后不会按照组件 code-splitting 而是打包为一个整体,主要直接提供给浏览器使用。
  • cjs: 即 CommonJS 规范定义的模块,同样提供给 node 和 打包工具使用(旧版本的 Webpack, Gulp等不能直接导入 ES Module 的情况)。

为什么要组件维度split code

需要哪个组件就引入哪个组件,做到按需引入。

遇到的问题

  • typescript声明文件不知怎么生成
  // 安装@rollup/plugin-typescript插件,然后在tsconfig.json可配置即可。
  // tsconfig.json配置 { "declaration": true, declarationDir:"./lib/es/types"}
  // @rollup/plugin-typescript配置 typescript({tsconfig: "./tsconfig.json"}),可覆盖tsconfig.json的配置。
  • 如何做到低版本浏览器兼容 babel默认只转换新的 JavaScript 语法,比如箭头函数、扩展运算(spread)。不转换新的 API,例如IteratorGeneratorSetMapsProxyReflectSymbolPromise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。如果想使用这些新的对象和方法,则需要为当前环境提供一个垫片(polyfill)。babel-polyfillbabel-runtime是两种不同的解决方案。

babel-polyfill

(从 Babel 7.4.0 开始,这个包已经被弃用,以支持直接包含core-js/stable(以 polyfill ECMAScript 功能)和regenerator-runtime/runtime)。

通过改写全局prototype的方式实现,它会加载整个polyfill,针对编译的代码中新的API进行处理,并且在代码中插入一些帮助函数,比较适合单独运行的项目。

babel-polyfill解决了Babel不转换新API的问题,但是直接在代码中插入帮助函数,会导致污染了全局环境,并且不同的代码文件中包含重复的代码,导致编译后的代码体积变大。虽然这对于应用程序或命令行工具来说可能是好事,但如果你的代码打算发布为供其他人使用的库,或你无法完全控制代码运行的环境,则会成为问题。

babel-runtime

Babel为了解决上述问题,提供了单独的包babel-runtime用以提供编译模块的工具函数,启用插件babel-plugin-transform-runtime后,Babel就会使用babel-runtime下的工具函数。

babel-runtime插件能够将这些工具函数的代码转换成require语句,指向为对babel-runtime的引用。每当要转译一个api时都要手动加上require('babel-runtime')。简单说 babel-runtime 更像是一种按需加载的实现,比如你哪里需要使用 Promise,只要在这个文件头部 require Promise from 'babel-runtime/core-js/promise'就行了

不过如果你许多文件都要使用 Promise,难道每个文件都要 import 一遍不成?

babel-plugin-transform-runtime

为了方便使用 babel-runtime,解决手动 require 的苦恼。它会分析我们的 ast 中,是否有引用 babel-rumtime 中的垫片(通过映射关系),如果有,就会在当前模块顶部插入我们需要的垫片。

transform-runtime 是利用 plugin 自动识别并替换代码中的新特性,你不需要再引入,只需要装好 babel-runtime 和 配好 plugin 就可以了。

好处是按需替换,检测到你需要哪个,就引入哪个 polyfill,如果只用了一部分,打包完的文件体积对比 babel-polyfill 会小很多。而且 transform-runtime 不会污染原生的对象,方法,也不会对其他 polyfill 产生影响。

所以 transform-runtime 的方式更适合开发工具包,库,一方面是体积够小,另一方面是用户(开发者)不会因为引用了我们的工具,包而污染了全局的原生方法,产生副作用,还是应该留给用户自己去选择。

这里我选择了babel-runtime,作为第三方库,不会造成全局污染。

  // 1、安装babel相应的环境
  // 2、安装@babel/plugin-transform-runtime、@bebel/preset-env、core-js、@babel/runtime
 // .babelrc配置
 {
  "presets": [
    [
      "@babel/env",
      {
        "modules": false
      }
    ],
    "@babel/react"
  ],

  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": {
          "version": 3,
          "proposals": true
        },
        "useESModules": true
      }
    ]
  ]
}
  • rollup如何按组件维度split code 我们可以用rollup“多入口文件”,同时还搭配使用 output.preserveModules 、output.preserveModulesRoot 来帮助我们生成与源码目录结构类似的分割包。具体可看rollup官网。
// rollup.config.js
const entryFile = 'src/index.ts';
const componentDir = 'src/components';
const cModuleNames = fs.readdirSync(path.resolve(componentDir));
const componentEntryFiles = cModuleNames.map((name) => /^[A-Z]\w*/.test(name) ? `${componentDir}/${name}/index.tsx` : undefined).filter(n => !!n);

export default {
    input: [entryFile, ...componentEntryFiles],
    output: { 
        dir: 'dist/es', 
        format: 'es',
        preserveModules: true,
        preserveModulesRoot: 'src',
    },
}

这里推荐一篇关于rollup配置等文章,超详细

最后,附上github地址