rollup+vue3开发个人的组件库

7,046

首先我们需要知道rollup是干嘛的

rollup是做什么的?

rollup是一个JavaScript打包模块器,可以将小代码编译成大块复杂的代码,例如 library 或应用程序。 Rollup 对代码模块使用新的标准化格式,这些标准都包含在 JavaScript 的 ES6 版本中,而不是以前的特殊解决方案,如 CommonJS 和 AMD。ES6 模块可以使你自由、无缝地使用你最喜爱的 library 中那些最有用独立函数,而你的项目不必携带其他未使用的代码。ES6 模块最终还是要由浏览器原生实现,但当前 Rollup 可以使你提前体验。

参考文档:
rollup中文文档
rollup插件集合

安装rollup

  1. 首选安装node.js
  2. 使用如下命令进行全局安装
npm install rollup --global // or npm i rollup -g
  1. 查看是否安装成功只需要在终端输入:rollup

image.png

如上则表明全局的rollup安装成功

实现一个简单的hello world

  1. 创建一个文件夹,在文件夹下创建index.js,hello.js hello.js代码如下:
export function hello() {
    return 'hello'
}

export function world() {
    return 'world'
}

index.js代码如下:

import {hello, world} from './hello.js'
const result = hello() + world()
  1. 编译,在终端输入如下指令:
npx rollup index.js --file dist/bundle.js --format iife

我们发现打包了一个dist文件夹如下:

image.png

接下来我们看看打包的内容:

image.png

这个时候我们就会很疑惑了,说好的hello,world呢?其实这个是因为tree-shaking的作用,是不是感觉和webpack类似了。那我们在做一下变形:

import {hello, world} from './hello.js'
const result = hello() + world()
document.getElementById('app').innerHTML = result

然后在打包看看输出的代码

image.png

这个时候就有了hello,world了,对比发现rollup打包后的代码比webpack更加的清晰,这个我们接下来探讨webpack和rollup的区别。

webpack和rollup

webpack

大型SPA项目的构建,也就是我们常说的web应用。

  • 通过各种Loader处理各种各样的资源文件
  • 通过各种插件Plugins对整体文件进行一些处理
  • code spliting对公共模块进行提取
  • 提供一个webpack-dev-server对本地进行开发
  • 支持HMR模块进行热替换

rollup

  • rollup设计之初就是面向ES module的构建出结构扁平,性能出众的类库
  • 目的是将ES module打包生成特定的JS模块文件,并减少它的体积
  • 编译出来的代码可读性更好,内容小,执行效率更高
  • 配置更加简单

顺带说一下ES module规则

  • import语句只能作为模块的顶层出现,不能出现在function if里面这点和commonJS不一样
  • ES module的模块名只能是字符串常量
  • 不管import的语句位置出现在哪,在模块初始化的时候所有的import都必须是导入完成的

webpack VS rollup

通过以上我们可以知道构建App应用时选用webpack适合,构建类库rollup更加适合。

接下来开始尝试配置rolluop吧

rolluop配置

  1. 新建一个文件夹rolluplearn目录下执行npm init -y
  2. 安装rollup
  3. 创建如下目录结构,并新建文件rollup.config.js

image.png

  1. 编写rollup配置如下:
// 读取json文件
import json from '@rollup/plugin-json';
export default {
    input: 'main.js',
    input: 'main.js', // 入口文件
    output: {        
        file: 'dist/bundle.js', //打包文件地址
        format: 'esm',          // 打包格式为esmodule
    }
    plugins: [json()]
}
  1. package.json中编辑打包脚本:
"scripts": {
    "build": "rollup --config rollup.config.js"
  }
  1. 开始编写main.j和src/test.js文件 src/test.js
const hell = ()=> {
    console.log('hell')
}
const fn = () => {
    console.log('fn')
}

export {
    hell,
    fn 
}

main.js

import { fn, hell }from './src/test'
import { version } from './package.json'
console.log(version)
fn()
  1. 执行 npm run build,结果如下:

image.png

rollup相对来说比较简单,没有weebpack的配置那么复杂,接下来我们介绍下vue3的插件开发。

vue3插件系统开发

给vue3应用添加全局功能,一般是Object有一个install方法或者是直接使用function,它们没有严格的限制,一般有如下几个功能:

  • 添加全局方法和属性
  • 添加全局资源和指令
  • 通过全局混入添加一些组件选项
  • 通过config.globalProperties来添加app的实例方法

开发一个插件

全局方法

使用vue-cli创建一个项目,在components下创建test.plugin.ts文件,代码如下:

import {App} from 'vue'
const plugins = {
    install(app: App) {
        app.config.globalProperties.$echo = ()=>{
            console.log('echo plugin')
        }
    }
}
export default plugins

接下来在main.ts中使用进行全局使用

import testPlugin from './components/test.plugin'
createApp(App)
.use(store)
.use(router)
.use(testPlugin)
.mount('#app')

此时我们就注册成功了一个全局方法$echo,接下来我们调用试试看能否成功, 在App.vue写入以下代码:

<script lang="ts">
import { defineComponent, getCurrentInstance } from 'vue'
export default defineComponent({
  setup() {
      // getCurrentInstance 返回当前组件的实例对象
    getCurrentInstance()?.appContext.config.globalProperties.$echo()
  }
})
</script>

查看浏览器控制台

image.png

说明我们的全局方法已经添加成功,接下来我们看看如何添加全局组件。

全局组件

还是在mian.ts中进行一些修改

import {App} from 'vue'
const plugins = {
    install(app: App) {
        app.config.globalProperties.$echo = ()=>{
            console.log('echo plugin')
        }
        app.component(HelloWord.name, HelloWord)
    }
}
export default plugins

在全局方法的使用中我们已经在main.ts中使用了use方法进行了全局注册,接下来我们只需要在App.vue中进行使用即可,如下:

<template>
  <div id="nav">
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </div>
  <router-view/>
</template>

查看浏览器发现全局组件已经注册成功

image.png

整体来看其实是和vue2差不多的,主要的区别就是:

  • vue2全局方法是挂载在vue的原型对象上的,vue3挂载在app.config.globalProperties方法上
  • 调用的时候vue2可以直接使用this.xxx进行调用,vue3需要getCurrentInstance()?.appContext.config.globalProperties进行调用 到这里使用vue3开发一个插件基本算是完成了,接下来我们需要了解一个组件库入口应该如何开发。

组件库入口文件设计

我们使用一个组件库的时候一般会有两种引入方式,一个是全局引入,一个是按需加载。所以在导出的时候应该有这样一个index.ts文件:

import componentA from './a'
const componentList = [
    componentA
]
const install = (app: App) {
      ...
}
// 导出单个
expoert {
...
}
// 导出所有
export default {
    install
}

componentA也应该有一个install方法,那么应该如何实现呢?🤔️ 在原有的vue-cli下载下来的项目进行一些改造,目录如下:

image.png

现在主要实现components/TText/index.tsindex.ts components/TText/index.ts

import { App } from 'vue'
// 随便写一个组件就行
import TText from './TText.vue'

// 在组件上添加install方法,方便直接使用单个组件
TText.install = (app: App)=> {
    app.component(TText.name, TText)
}

export default TText

index.ts

import { App } from 'vue'
import TText from './components/TText'
// 组件列表
const components = [
  TText
] 
// 使用所有组件
const install = (app: App)=> {
    components.forEach(component => {
      app.component(component.name, component)
    })
  }
export {
  TText,
  install
}
export default { install }

到这里我们就完成组件入口文件的开发,其他的基本就是按照这个模式直接造轮子就好了,接下来我们就使用rollup来打包成umd和esmodule格式的文件。

添加rollup配置并打包

根目录创建build文件夹,并依此创建

  1. rollup.config.js:公共基础配置
  2. rollup.esm.config.js:打包esmodule文件配置
  3. rollup.umd.config.js打包umd文件配置 因为都是配置就直接写了,可以看后面的备注 rollup.config.js
// 处理vue文件插件
import vue from 'rollup-plugin-vue'
// 处理css文件插件
import css from 'rollup-plugin-css-only'
// 处理ts插件
import typescript from 'rollup-plugin-typescript2'
// 用于在节点单元模块中使用第三方模块
import { nodeResolve } from '@rollup/plugin-node-resolve'
import { name } from '../package.json'
// 输出打包后的文件名称type 1.esm 2.umd
const file = type => `dist/${name}.${type}.js`
const overrides = {
  compilerOptions: { declaration: true }, // 生成.d.ts的文件
  exclude: ["tests/**/*.ts", "tests/**/*.tsx"] 
}
export { name, file }
export default {
  input: 'src/index.ts',
  output: {
    name,
    file: file('esm'),
    format: 'es'
  },
  plugins: [
    nodeResolve(),
    typescript({ tsconfigOverride: overrides }),
    vue(),
    css({ output: 'bundle.css' }) // 可自行修改output文件名
  ],
  external: ['vue', 'lodash-es'] // 规定哪些是外部引用的模块
}

rollup.esm.config.js

import basicConfig, {file, name} from './rollup.config'
export default {
    ...basicConfig,
  output: {
    name,
    file: file('esm'),
    format: 'es'
  }
}

rollup.umd.config.js

import basicConfig, { name, file } from './rollup.config'
export default {
  ...basicConfig,
  output: {
    name: 'thComponents',
    file: file('umd'),
    format: 'umd',
    globals: { // 设定全局变量的名称
      'vue': 'Vue',
      'lodash-es': '_'
    },
    exports: 'named'
  }
}

编写打包脚本

"scripts": {
    "build": "npm run clean && npm run build:esm && npm run build:umd", // 整体打包指令
    "build:esm": "rollup --config ./build/rollup.esm.config.js", // 打包esmodule
    "build:umd": "rollup --config ./build/rollup.umd.config.js", // 打包umd格式
    "clean": "rimraf ./dist" // 清除dist
  },

运行 npm run build

查看结果

image.png

组件已经打包完成,接下来我们进行在本地使用 npm link进行测试

发布组件

使用npm link进行组件库测试

  1. 配置package.json
{
    "name": "th-bricks",
    "version": "0.1.0",
    "private": false,
    "author": "linlei",
    "main": "dist/th-bricks.umd.js",
    "module": "dist/th-bricks.esm.js",
    "types": "dist/index.d.ts"
    ...
 }
  1. 根目录下执行:npm link

image.png

  1. 在项目中使用
  • 配置
"dependencies": {
    ...
    "th-bricks": "0.1.0"
  }
  1. 执行 npm link th-bricks

image.png

  1. 在项目的main.ts中引入,并在App.vue中使用 main.ts
import { createApp } from 'vue'
import App from './App.vue'
import thBricks from 'th-bricks'
import 'th-bricks/dist/bundle.css'
import router from './router'
import store from './store'
createApp(App)
.use(store)
.use(router)
.use(thBricks)
.mount('#app')

App.vue

<template>
  <div id="nav">
    <!-- 使用 -->
    <t-text text="hello" tag="h2" />
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </div>
  <router-view/>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({

  setup() {
    return {}
  }
})
</script>
  1. 查看结果

image.png

可以看到已经渲染出了组件 坑:如果出现可以打印thBricks无法使用的情况,可以重启电脑试试。

发布npm

  1. 首先查看是否登录 npm whami
  2. 如果已经登录就直接跳过,否则使用npm login进行登录,没有npm账号的就需要注册一个了
  3. 发布npm publish

image.png

image.png

可以看到已经发布成功了。

🤔我们每次执行npm publish的时候并不能保证我们一定执行了npm run build,那么有什么方法可以处理呢? 经过查看各种资料发现了可以这样处理:

image.png

"scripts": {
    "build": "npm run clean && npm run build:esm && npm run build:umd",
    "build:esm": "rollup --config ./build/rollup.esm.config.js",
    "build:umd": "rollup --config ./build/rollup.umd.config.js",
    "clean": "rimraf ./dist",
    "prepublishOnly": "npm run build" // npm publish的时候先执行npm run build
  }

写在最后

  • 基本的组件库的流程基本上已经完成了,但是离真正的一个完善组件库还有很远的距离,需要不断的丰富组件库,例如:tree,table,message,还有各种项目特定的组件等等
  • 组件库代码