Rollup - 开发UI库入门

本文示例如何基于RollupVue编写一个HelloWorld的UI库

一、初始化项目

生成package.json

mkdir base-ui
cd base-ui
npm init

安装npm包

npm install --save-dev rollup rollup-plugin-vue vue vue-template-compiler 
npm install --save-dev rollup-plugin-babel @babel/core
npm install --save-dev rollup-plugin-node-resolve rollup-plugin-commonjs 
npm install --save-dev rollup-plugin-terser rollup-plugin-css-only clean-css

二、配置文件

增加rollup配置文件 rollup.config.js,支持umd es iiffe格式(分别用于node、es6、browser)

import commonjs from 'rollup-plugin-commonjs'
import resolve from 'rollup-plugin-node-resolve'
import babel from 'rollup-plugin-babel'
import vue from 'rollup-plugin-vue'
import { terser } from 'rollup-plugin-terser'
import css from 'rollup-plugin-css-only' // 提取css
import CleanCSS from 'clean-css'   // 压缩css
import { writeFileSync } from 'fs' // 写文件

module.exports = {
  input: 'src/index.js', // 入口
  output: [
{
    file: 'dist/base-ui.umd.js', // 打包文件名
    name: 'base-ui',
    format: 'umd', // 打包模块支持方案,可选 amd, cjs, es, iife, umd
  }, {
    file: 'dist/base-ui.es.js',
    format: 'es'
  }, {
    name: 'base-ui',
    file: 'dist/base-ui.min.js',
    format: 'iife'
  }],
  plugins: [
    // css({output: 'dist/base-ui.css'}),
    css({ output(style) {
      // 压缩 css 写入 dist/base-ui.css
      writeFileSync('dist/base-ui.css', new CleanCSS().minify(style).styles)
    } }),
    vue({
      // css: false 将<style>块转换为导入语句,rollup-plugin-css-only可以提取.vue文件中的样式
      css: false,
      normalizer : '~rollup-plugin-vue/runtime/normalize',
      // styleInjector : '~rollup-plugin-vue/runtime/browser',
    }),                    
    // terser(),
    resolve(),
    babel({
      exclude: ['node_modules/**']
    }),
    commonjs()
  ],
  external: [ // 不被打包的库
    'vue'
  ]
}

插件说明

  1. rollup-plugin-vue:负责处理vue文件,相当于webpackvue-loader。可选参数见这里

5.0以后的rollup-plugin-vue需要明确指定normalizer : '~rollup-plugin-vue/runtime/normalize',,否则打包输出文件有__vue_normalize__相关的引用错误。

2 rollup-plugin-commonjs
该插件负责将Commonjs模块转化成ES6以便rollup打包。很多npm包都是commonjs模块,需要结合此插件一起使用

  1. rollup-plugin-node-resolve
    rollup只能加载相对路径模块(即通过/,./, ../引入的模块),而三方模块(node_modules里模块)有两种方式打包:
    • rollup.config.js配置为external资源: 输出bundle中不包含此模块,作为external外部资源引入,由使用该bundle组件库的客户程序安装
// rollup.config.js
export default {
  entry: 'src/index.js',
  dest: 'bundle.js',
  format: 'cjs',
  external: [ 'vue' ] // <-- suppresses the warning
};

此时,需要在组件库的package.json中增加外部依赖

"dependencies": {
    "vue": "^2.5.0"
  }

客户端程序安装该组件库时会自动安装dependencies定义的模块

  • rollup-plugin-node-resolve:该插件可以将三方模块包含在输出bundle中。具体见官网介绍
  1. rollup-plugin-babel: 在rollup项目中使用babel,详细见这里

  2. rollup-plugin-terser: 压缩输出后js

  3. rollup-plugin-css-only: 提取css为独立文件

  4. clean-css:压缩css

package.json最终代码为:

{
 "name": "base-ui",
 "version": "1.0.0",
 "description": "A demo for how to create a UI library with Rollup",
 "main": "index.js",
 "scripts": {
   "build": "rollup -c",
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
  "@babel/core": "^7.7.4",
   "clean-css": "^4.2.1",
   "node-sass": "^4.13.0",
   "rollup": "^1.27.8",
   "rollup-plugin-babel": "^4.3.3",
   "rollup-plugin-commonjs": "^10.1.0",
   "rollup-plugin-css-only": "^1.0.0",
   "rollup-plugin-node-resolve": "^5.2.0",
   "rollup-plugin-terser": "^5.1.2",
   "rollup-plugin-vue": "^5.1.2",
   "vue-template-compiler": "^2.6.10"
 }
}

三、开发组件库

3.1 开发组件

src/button/index.vue 编写UI组件

<template>
  <h1>Button</h1>
</template>

<script>
export default {
  name: 'my-buttom',
  created() {
    console.log('hello world')
  }
}
</script>

3.2 打包全部组件

src/index.js 输出组件。其中install函数可使组件库当做Vue插件使用,全局注入组件库。请参考VUE插件

import Button from './button/Index.vue'

const components = [Button];
// 全局注入组件
const install = function(Vue) {
  if (!Vue || install.installed) return
  components.forEach(component => {
     Vue.component(component.name, component);
  })
}

 if (typeof window != 'undefined' && window.Vue) {
    install(window.Vue)
  }

export {
  Button
}

export default {
  install
};
npm run build

输出文件dist/base-ui.umd.js如下

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

  var script = {
    name: 'my-button',
    created() {
      console.log('hello world');
    }

  };

  ...

  var index = {
    install
  };

export default index;
export {  __vue_component__ as Button};
})));

3.3 输出单独组件

输出单独组件以支持按需加载
rollup.component.config.js增加用于输出组件的配置文件

import commonjs from 'rollup-plugin-commonjs'
import resolve from 'rollup-plugin-node-resolve'
import babel from 'rollup-plugin-babel'
import vue from 'rollup-plugin-vue'
import { terser } from 'rollup-plugin-terser'
import css from 'rollup-plugin-css-only' // 提取css
import CleanCSS from 'clean-css'   // 压缩css
import { writeFileSync } from 'fs' // 写文件

module.exports = {
  input: 'src/Button/index.js', // 入口
  output: {
    file: 'dist/button/index.js',
    format: 'es'
  },
  plugins: [
    css({ output(style) {
      // 压缩 css 写入 dist/base-ui.css
      writeFileSync('dist/button/index.css', new CleanCSS().minify(style).styles)
    } }),
    vue({
      // css: false 将<style>块转换为导入语句,rollup-plugin-css-only可以提取.vue文件中的样式
      css: false,
      normalizer : '~rollup-plugin-vue/runtime/normalize',
      // styleInjector : '~rollup-plugin-vue/runtime/browser',
    }),                    
    // terser(),
    resolve(),
    babel({
      exclude: ['node_modules/**']
    }),
    commonjs()
  ]
}

执行命令rollup -c rollup.component.config.js输出组件

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

  var script = {
    name: 'my-button',
    created() {
      console.log('hello world');
    }

  };

  ...

export {  __vue_component__ as Button};
})));

四、测试组件库

4.1 创建测试项目

vue create examples 创建项目,用于模拟实际项目引入组件库

全局引入
examples/src/main.js中引入组件,全局注入组件

import MyComponent from '../../dist/base-ui.es'
import MyComponent from '../../dist/base-ui.css'
Vue.use(MyComponent)

examples/src/components/HelloWorld.vue中使用组件

<template>
  <div id="app">
    <my-index></my-index>
  </div>
</template>

按需引入
src/components/HelloWorld.vue中引入Button

...
import { Button } from '../../../dist/button/index.es'

export default {
  components: {
    [Button.name]: Button
  },
}
...

bable-plugin-import
bable-plugin-import可以帮助用户方便地实现按需加载
bable.config.js配置文件中增加配置(如下为mand-mobile配置):

{
  "plugins": [
    ["import", {
      "libraryName": "mand-mobile",  // 组件库名称
      "libraryDirectory": "lib"  // 组件所在目录
    }]
  ]
}

import { Button } from 'mand-mobile'
babel-plugin-import会将以上引入语句转换为下面写法
import Button from 'mand-mobile/lib/button'

4.2 测试

组件项目package,json下增加执行命令, 启动组件库和示例项目的开发模式

"scripts": {
    "dev": "rollup -c --watch",
    "example": "cd examples && npm run serve",
    ...
}

组件项目 package.json里增加入口文件

...
"main": "dist/base-ui.umd.js",
"module": "dist/base-ui.es.js",
"unpkg": "dist/base-ui.min.js",
...

Issues

examples项目里从组件输出目录(/dist)引入组件时默认会启动eslint,编译会报语法错误。暂时取消lint


源码:https://github.com/guolixincl/examples/tree/master/rollup/base-ui


参考文章

  1. https://jsprogram.cn/blog/rollup-config-for-vue-ui-library/
  2. https://www.jianshu.com/p/0c42ae0f1f2b
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,063评论 6 510
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,805评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,403评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,110评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,130评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,877评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,533评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,429评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,947评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,078评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,204评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,894评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,546评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,086评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,195评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,519评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,198评论 2 357

推荐阅读更多精彩内容