用Rollup打包一个Taro组件库

1,501 阅读7分钟

Taro组件库开发

rollup

本次使用的打包工具为rollup

  • 之所以选择rollup,是因为之前用webpack打包typescript校验一直报错,捣鼓半天没解决,果断脱坑另辟蹊径。

rollup的优势

rollup 打包体积小代码精简较少注入,动态加载路由代码不能用,所以单页项目不合适,项目使用人不多,支持的插件较少。

如有单独的js 基础类适合用rollup 打包,如有react/vue 打包建议用webpack,必要时两者都用,也不错。

Rollup最主要的优点是它是基于ES2015模块的,相比于webpack或Browserify所使用的CommonJS模块更加有效率,因为Rollup使用一种叫做tree-shaking的特性来移除模块中未使用的代码,这也就是说当我们引用一个库的时候,我们只用到一个库的某一段的代码的时候,它不会把所有的代码打包进来,而仅仅打包使用到的代码(webpack2.0 貌似也引入了tree-shaking)

注意:Rollup只会在ES6模块中支持tree-shaking特性。目前按照CommonJS模块编写的jquery不能被支持tree-shaking.

项目结构配置

作者用的项目结构是taro初始化的脚手架,建立src/compontents文件夹存放组件,使用一个src/components/index.ts暴露组件,最后在src/index.ts中暴露全部组件。

所用到Rollup插件

@rollup/plugin-json

处理json文件的打包

@rollup/plugin-node-resolve

rollup项目中只支持相对路径的引用,而绝对路径需要插件支持。

比如你使用rollup打包,产生报错:

Cannot use import statement outside a module 

这是因为rollup打包会处理相对路径,对于npm包的绝对路径引用是不会做任何处理的。这种情况可以用插件处理。

@rollup/plugin-commonjs

rollup默认不支持commonjs语法,需要插件支持

rollup-plugin-typescript2

支持typescript

@rollup/plugin-image

支持import引入图片

rollup-plugin-clear

每次打包清空前自动打包文件夹中的内容

rollup-plugin-babel

配置babel,rollup对taro的babel配置如下

RollupBabel({
  runtimeHelpers: true,
  "presets": [["taro", {
    framework: 'react'
  }]],
  "plugins": ["@babel/plugin-transform-runtime"]
}),
rollup-plugin-copy

支持直接复制文件到包中

rollup-plugin-postcss

支持sass、less,并且实现移动端像素转换,配置如下

RollupPostCss({ // 可选:处理sass
  plugins: [
    autoprefixer(),
    cssnano(),
    pxtransform({
      platform: 'h5'
    }),
    // constparse()
  ]
}),
rollup-plugin-dts

打包d.ts声明文件,提供typescript类型声明

rollup-plugin-terser

压缩代码

autoprefixer

自动管理浏览器前缀的插件,解析CSS文件并且添加浏览器前缀到CSS内容里,如--webkit-overflow

cssnano

压缩css

postcss-pxtransform

css像素单位转换

postcss-url

支持css内引入静态资源

postcss-import

支持sass的@import语法,在css内引用其他css文件

rollup-plugin-postcss-inject-to-css

支持将sass打包后的.scss.js后缀文件修改为.css后缀文件(解决组件打包小程序时不支持.scss.js内样式问题)

rollup完整配置

import RollupJson from '@rollup/plugin-json'
import RollupNodeResolve from '@rollup/plugin-node-resolve'
import RollupCommonjs from '@rollup/plugin-commonjs'
import RollupTypescript from 'rollup-plugin-typescript2'
import RollupBabel from 'rollup-plugin-babel'
import dts from 'rollup-plugin-dts'
import RollupImage from '@rollup/plugin-image'
import RollupClear from 'rollup-plugin-clear'
import RollupPostCss from 'rollup-plugin-postcss'
import autoprefixer from 'autoprefixer';
import pxtransform from 'postcss-pxtransform';
import postUrl from 'postcss-url';
import postImport from 'postcss-import';
import cssnano from 'cssnano';
import Uglifyjs from 'uglify-js';
import RollPostcssInject2Css from './copy-rollup-postcss-inject-to-css.js'; // 复制修改后的插件
​
​
const externalPackages = ['react', 'react-dom', '@tarojs/components', '@tarojs/runtime', '@tarojs/taro', '@tarojs/react']
​
export default [
  {
    input: './src/index.ts',
    output: [
      {
        format: 'esm',
        dir: './dist/lancooUI-Mobile',
        exports: 'named', // 指定导出模式(自动、默认、命名、无)
        preserveModules: true, // 保留模块结构
        preserveModulesRoot: 'src', // 将保留的模块放在根级别的此路径下
      }
    ],
    external: externalPackages,
    plugins: [
      RollupClear({
        targets: ['dist'], // 每次打包清空dist目录,从新生成
        watch: true,
      }),
      RollupPostCss({ // 可选:处理sass
        extract: false, // 非导出模式
        inject: true,  // 内联模式
        plugins: [
          autoprefixer(),
          cssnano(),
          postImport(),
          pxtransform({
            platform: 'h5'
          }),
          postUrl({
            url: "inline",
            maxSize: 70
          })
        ]
      }),
      RollPostcssInject2Css(),
      RollupNodeResolve({
        customResolveOptions: {
          moduleDirectory: 'node_modules',
        },
      }),
      RollupCommonjs({
        include: [//node_modules//],
      }),
      RollupJson(),
      RollupBabel({
        runtimeHelpers: true,
        "presets": [
          [
            "taro", {
              framework: 'react'
            }
          ]
        ],
        "plugins": ["@babel/plugin-transform-runtime"]
      }),
      RollupTypescript({
        tsconfig: './tsconfig.json'
      }),
      RollupImage({
        include: ['**/*.png', '**/*.jpg', '**/*.svg']
      }),
      // 自定义插件,用于压缩代码(使用rollup的terser插件压缩的话,rollup-postcss-inject-to-css失效,故自己引入UglifyJs压缩代码)
      {
        name: 'rollup-my-terser',
        generateBundle(_, bundle) {
          const bundleIdList = Object.keys(bundle)
          bundleIdList.forEach((bundleId) => {
            const bundleItem = bundle[bundleId];
            const code = bundleItem.code;
            // 压缩代码
            if (/.*.js/.test(bundleId) && !/.*.scss.*/.test(bundleId) && !/.*.css.*/.test(bundleId) && typeof code === 'string') {
              Uglifyjs.minify(code).code === undefined && console.log(bundleId);
              bundleItem.code = Uglifyjs.minify(code).code;
            }
          })
        },
      }
    ]
  },
  {
    input: './src/index.ts',
    output: [
      {
        format: 'esm',
        dir: './dist/lancooUI-Mobile',
        exports: 'named', // 指定导出模式(自动、默认、命名、无)
        preserveModules: true, // 保留模块结构
        preserveModulesRoot: 'src', // 将保留的模块放在根级别的此路径下
      }
    ],
    external: id => /(node_modules|.*.scss|.*.css)/.test(id),
    plugins: [
      RollupPostCss(),
      RollupNodeResolve({
        customResolveOptions: {
          moduleDirectory: 'node_modules',
        },
      }),
      RollupCommonjs({
        include: [//node_modules//],
      }),
      RollupJson(),
      RollupBabel({
        runtimeHelpers: true,
        "presets": [
          [
            "taro", {
              framework: 'react'
            }
          ]
        ],
        "plugins": ["@babel/plugin-transform-runtime"]
      }),
      RollupTypescript({
        tsconfig: './tsconfig.json'
      }),
      dts()
    ]
  }
]

配置解读:

  • 配置项为数组,第一项打包源代码,故未配置dts插件,第二项配置了dts打包声明文件
  • 输出output配置采用preserveModules: true保留项目结构,使用户能清晰看到所对应组件的声明文件。
  • postcss-pxtransfrom配置参考taro的配置,暂且使用了h5配置,后续考虑根据打包方式动态更改。
  • package.json中需要配置"type": "module" ,否则打包时会报错。
  • 输出格式选用esm,支持项目中es6语法引入。
  • 源代码进行了代码压缩,声明文件为了方便查阅,不采取压缩。
  • 由于rollup-postcss-inject-to-css对.css.js不支持,且代码中code为undefined时仍然读取code[0]导致报错,复制代码并加以修改一份。
  • 使用terser压缩代码,rollup-postcss-inject-to-css对.scss.js的转换出错,故引入UglifyJs自己完成代码压缩。

最后,运行以下命令进行打包即可

rollup -c

难点

打包后的组件库难点有JSX支持类型声明SASS支持像素转换多端支持

解决思路:

JSX支持: jsx的支持依靠babel的配置,和react的jsx支持同理。

类型声明: 观察其他组件库,类型声明均是d.ts文件,了解rollup如何打包出d.ts文件即可。

SASS支持: 最初打包组件库时存在样式丢失的问题,后面增加postcss配置后完美解决(不能单单是引入postcss,其中还有像素转换、css内静态资源打包与@import语法,需要额外插件支持)。

像素转换: 打包后测试时发现移动端的像素仍保持与pc端一致,查阅了解后使用postcss-pxtransform插件,最初未配置option,仍未解决问题。查看tarojs的postcss.config.js文件源码后,依葫芦画瓢增加option解决问题。

多端支持: 使用rollup-plugin-postcss-inject-to-css解决小程序不支持.scss.js的问题。

总结

组件库打包磕磕绊绊,总结自身原因,一是对打包工具的不了解,对配置、插件作用的认知不清晰,二是对组件库结构的了解不足,不清楚其工作原理。三是国内网上组件库开发的资料甚少,尤其对taro组件库开发的资料几乎是荒漠。

本次打包收获如下:

  • 对rollup以及其部分插件有了一定的了解。
  • 了解如何编写rollup插件。
  • 对组件库的打包以及结构有了自己的理解。

踩坑

  • 打包完后发现字体未打包进去,但引入rollup-plugin-font没有起到作用。后面发现,不是该插件配置问题,而是cssurl()方式引入的静态资源压根没打包进去(rollup-plugin-font和rollup-plugin-image只对js中import引入的静态资源生效),引入postcss-url解决问题
  • 打包后存在部分样式失效问题,发现该css文件中使用了sass的@import语法,结合postcss-url踩坑经验,引入postcss-import后解决问题
  • 打包sass后,产生的文件后缀为.scss.js,h5能正常显示样式,但打包成微信小程序无法正确识别,遂考虑到使用rollup-plugin-copy将sass文件直接复制到包中,跳过sass编译,但使用起来需一个个引入样式,太过于麻烦。后面寻找将sass文件打包为css文件的方法,找到了rollup-plugin-postcss-inject-to-css插件,能将.scss.js转换为.css。然而使用起来时报空错误,简单修改了下源码后解决问题。