大前端進階~如何構(gòu)建組件庫

前言

在日常開發(fā)過程中,構(gòu)建組件庫是必不可少的一環(huán)夕吻,此篇文章就是描述如何搭建一個完整的組件庫,解決組件庫開發(fā)發(fā)布過程中的如下問題:

1.如何在最少的依賴下快速開發(fā)一個vue組件仇味。
2.如何將所有的包放置在一個git倉庫內(nèi)硫惕。
3.如何將git倉庫內(nèi)的所有包一鍵發(fā)布。
4.如何管理所有包的依賴,減少包的體積截汪。
5.如何快速創(chuàng)建組件示例疾牲。
6.如何打包組件,webpack挫鸽?

快速原型開發(fā)

開發(fā)組件和開發(fā)項目是不一樣的说敏,在開發(fā)組件的時候,我們希望能夠有一種工具能夠快速針對某個vue文件搭建開發(fā)環(huán)境丢郊,并且在發(fā)布的時候能夠?qū)ζ溥M行打包編譯盔沫,此時我們可以使用@vue/cli-service-global。

  • 全局安裝
    此包必須全局安裝:

npm install -g @vue/cli-service-global

  • 創(chuàng)建vue文件
    在根目錄下創(chuàng)建App.vue文件:
<template>
  <h1>Hello!</h1>
</template>
  • 啟動開發(fā)服務(wù)器
    在命令行中執(zhí)行:

vue serve

入口可以是 main.js枫匾、index.js架诞、App.vue 或 app.vue 中的一個。你也可以顯式地指定入口文件:

vue serve App.vue

  • 執(zhí)行打包
    vue build

打包完成后干茉,打包結(jié)果會放到dist目錄下谴忧, 默認情況下,會打包生成一個應(yīng)用角虫,該應(yīng)用包含html和資源文件沾谓,可以直接部署為靜態(tài)站點。但是通常情況下戳鹅,我們需要將組件打包成一個庫均驶,以便發(fā)布后供項目使用。

打包成庫需要指定構(gòu)建目標:

vue build --target lib

添加構(gòu)建目標后枫虏,執(zhí)行打包妇穴,dist目錄中包含各種規(guī)范的js文件和一個demo示例html。

目前為止隶债,快速開發(fā)vue組件已經(jīng)完成腾它,我們可以快樂的開發(fā)各種組件,但是死讹,當所需開發(fā)的組件慢慢變多之后瞒滴,文件的組織方式成為我們需要考慮的事情。
可以想到有以下三種方式組織文件結(jié)構(gòu):

1.每一個組件都是一個單獨的倉庫赞警。
2.一個倉庫中包含多個組件vue文件逛腿,作為一個包發(fā)布。
3.一個倉庫中包含多個組件包仅颇,每個組件包單獨發(fā)布单默。

第一種方式,每一個組件都是一個單獨倉庫忘瓦,雖然有利于組件開發(fā)搁廓,但是組件維護起來比較麻煩引颈。組件越多,需要維護的倉庫也就越多境蜕,當其中部分組件依賴的如lodash需要升級時蝙场,我們需要一個個進行升級,比較麻煩粱年。

第二種方式售滤,將所有的組件作為一個包發(fā)布,雖然維護比較方便台诗,但是發(fā)布后完箩,別人只想使用其中的一個組件時,會需要把整個組件庫引入拉队,如果不提供按需加載弊知,那么會造成項目中引入很多不必要的代碼。

第三種方式可參考下文粱快。

monorepo

當我們查看 vue3 源碼時秩彤,可以看到,倉儲結(jié)構(gòu)如下:

packages
├── compiler-core
    ├──_tests_ #單元測試
    ├──src #源文件目錄
    ├──package.json
├── compiler-dom
    ├──_tests_ #單元測試
    ├──src #源文件目錄
    ├──package.json
package.json

這個就是典型的monorepo 事哭,monorepo是項目代碼的一種管理方式漫雷,指在一個倉庫中管理多個模塊/包。

monorepo追求的是在一個倉庫中管理多個模塊鳍咱,每個模塊有獨立的package.json管理各自依賴降盹,同時在項目根目錄下可以通過命令安裝或升級模塊依賴,并提供了一個模塊共享的node_modules流炕。

yarn workspace

yarn workspace 是實現(xiàn)monorepo的一種方式。

使用yarn workspace要求在根目錄的package.json中添加如下屬性:

{
    "private": true,
    "workspaces": ["packages/*"]
}

private屬性指定根目錄是私有的仅胞,不會被發(fā)布工具發(fā)布到npm上每辟。

workspace屬性指定組件所在文件夾,支持通配符干旧。

修改完package.json之后渠欺,按照vue-next的項目結(jié)構(gòu)在packages文件夾下創(chuàng)建input測試組件。

假設(shè)椎眯,自定義的input組件依賴dayjs包挠将,可以在根目錄下執(zhí)行如下命令安裝:

yarn workspace m-input add dayjs

其中m-input并不是packages下組件文件夾的名稱,而是組件文件夾下package.json中的name屬性值编整。

安裝完成后舔稀,dayjs會自動添加到input組件的package.json下,但是包下載到了根目錄下的node_modules文件夾中掌测,這樣做可以更好的管理多組件包的依賴内贮。如果當前組件依賴的包版本和其他組件依賴的包版本不一樣,如其他組件依賴lodash@4,當前組件依賴lodash@3夜郁, 此時依賴包會被下載到當前組件文件夾下的node_modules中什燕。

通過yarn workspace可以執(zhí)行某個組件下的npm scripts,如給input組件添加一個build命令竞端,可以在根目錄下通過如下命令執(zhí)行build:

yarn workspace m-input run build

對于build這種命令屎即,幾乎所有組件都需要,那么yarn workspace提供了一個快捷命令事富,可以一鍵執(zhí)行所有組件包的build命令:

yarn workspaces run build

storybook

目前為止技俐,倉庫的整體文件結(jié)構(gòu)和組件庫的依賴包管理都已經(jīng)完成了,可以愉快的開發(fā)組件了,當組件開發(fā)完成后赵颅,一般開發(fā)人員都會編寫相應(yīng)的使用文檔虽另,文檔中包含相應(yīng)的使用示例.

storybook是可視化的組件管理展示平臺,支持在隔離的開發(fā)環(huán)境中饺谬,以交互式的方式展示組件捂刺,支持vue,react等募寨。

安裝使用:

npx -p @storybook/cli sb init --type vue

yarn add vue -W

yarn add vue-loader vue-template-compiler --dev -W

修改配置:

安裝完成之后族展,在根目錄的.storybook文件夾下存放著storybook使用的所有配置文件,修改main.js中stories屬性拔鹰,將其指向packages所有組件下的.stories.js文件仪缸。

"stories": [
    "../packages/**/*.stories.mdx",
    "../packages/**/*.stories.@(js|jsx|ts|tsx)"
]

添加組件示例:

在input組件包中添加 Input.stories.js 文件:

import MInput from './index'
export default {
    title: 'MInput',
    component: MInput
};

export const Text = () => ({
    components: { MInput },
    template: '<m-input />',
});

export const Password = () => ({
    components: { MInput },
    template: '<m-input type="password" placeholder="請輸入密碼"/>',
});

其中默認導出是storybook頁面左側(cè)導航欄,每一個具名導出都是一個樣例列肢。

最終執(zhí)行yarn storybook恰画,打開站點:


lerna

lerna 是babel團隊開源的用于管理多包倉庫的工具,也可以用于實現(xiàn)monorepo瓷马。

安裝lerna:

npm install lerna -g

初始化lerna:

lerna init

會在項目根目錄下添加lerna.json配置文件拴还。

可以使用lerna管理項目依賴:

如果當前form自定義組件依賴input自定義組件,可以使用:

lerna add input --scope=form

還可以使用import命令導入本地包:

lerna import <path-to-external-repository>

通過exec和run執(zhí)行包里面的相關(guān)命令

lerna run --scope my-component test

通過clean命令一鍵清除所有包的node_modules目錄:

lerna clean

learn最主要的功能是一鍵發(fā)布所有包的npm上:

lerna publish

發(fā)布包到npm需要登錄欧聘,可以通過 npm whoami 查看當前登錄用戶片林,通過 npm login 進行登錄。

單元測試

單元測試是組件化開發(fā)中必不可少的部分

安裝依賴:

npm i jest @vue/test-utils vue-jest babel-jest -D

1.添加jest配置文件jest.config.js

module.exports = {
    "testMatch": ["**/_tests_/**/*.[jt]s?(x)"],
    "moduleFileExtensions": [
        "js",
        "json",
        // 告訴 Jest 處理 `*.vue` 文件
        "vue"
    ],
    "transform": {
        // 用 `vue-jest` 處理 `*.vue` 文件
        ".*\\.(vue)$": "vue-jest",
        // 用 `babel-jest` 處理 js
        ".*\\.(js)$": "babel-jest"
    }
}

1.添加babel配置文件babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env'
    ]
  ]
}

1.添加測試命令
"test": "jest"

1.添加測試文件
在組件包的tests文件夾下添加相關(guān)js文件怀骤,如input包下面添加input.test.js

import input from '../src/index.js'
import { mount } from '@vue/test-utils'

describe('m-input', () => {
  test('input-text', () => {
    const wrapper = mount(input)
    expect(wrapper.html()).toContain('input type="text"')
  })
})

1.執(zhí)行測試命令
yarn test

測試可以在命令行中看到單元測試執(zhí)行結(jié)果:


rollup打包

rollup是一個基于ESM的模塊打包工具费封,和webpack相比,其打包結(jié)果更小蒋伦,因此適合打包框架或者組件庫弓摘。

安裝必須的依賴:

npm i rollup rollup-plugin-terser rollup-plugin-vue@5.1.9 vue-template-compiler -D

需要注意的是安裝vue時需要指定版本,否則會安裝vue3痕届。

  • 單組件打包
    1.添加配置文件
    在組件中添加rollup.config.js文件衣盾,該文件是rollup打包的配置文件寺旺,指定起始文件,輸出文件位置及格式势决,插件阻塑。
import { terser } from 'rollup-plugin-terser'
import vue from 'rollup-plugin-vue'

module.exports = {
    input: 'src/index.js',
    output: [
        {
            file: 'dist/index.js',
            format: 'es'
        }
    ],
    plugins: [
        vue({
            css: true,
            compileTemplate: true
        }),
        terser()
    ]
}

1.添加可執(zhí)行命令
在package.json文件的scripts屬性下添加打包命令:

"build": "rollup -c"

-c指的是使用當前項目目錄下的配置文件rollup.config.js

1.執(zhí)行命令
yarn build

執(zhí)行完畢之后,可以看到打包結(jié)果果复。

  • 多組件打包
    雖然可以用上述單組件打包的方式為每一個組件打包陈莽,但是這樣比較麻煩,可以在項目根目錄下通過一個配置文件打包所有組件虽抄。

此時需要添加額外依賴:

npm i @rollup/plugin-json rollup-plugin-postcss @rollup/plugin-node-resolve cross-env -D

1.為組件指定入口文件
在每個包下的package.json文件中添加main和module屬性:

"main": "dist/cjs/index.js",

"module": "dist/es/index.js",

1.設(shè)置環(huán)境變量
利用cross-env設(shè)置環(huán)境變量走搁,區(qū)分開發(fā)環(huán)境和生產(chǎn)環(huán)境:

"build:prod": "cross-env NODE_ENV=production rollup -c",

"build:dev": "cross-env NODE_ENV=development rollup -c"

1.添加配置文件
在項目的根目錄下添加rollup.config.js文件,該文件會遍歷packages文件夾下的所有文件夾并打包:

import fs from 'fs'
import path from 'path'
import json from '@rollup/plugin-json'
import vue from 'rollup-plugin-vue'
import { terser } from 'rollup-plugin-terser'
import postcss from 'rollup-plugin-postcss'
import { nodeResolve } from '@rollup/plugin-node-resolve'

const isDev = process.env.NODE_ENV !== 'production'

// 公共插件配置
const plugins = [
    vue({
        css: true,
        compileTemplate: true
    }),
    json(),
    nodeResolve(),
    postcss({
        // 把 css 插入到 style 中
        // inject: true,
        // 把 css 放到和js同一目錄
        extract: true
    })
]

// 如果不是開發(fā)環(huán)境迈窟,開啟壓縮
isDev || plugins.push(terser())
// packages 文件夾路徑
const root = path.resolve(__dirname, 'packages')


module.exports = fs.readdirSync(root)
    .filter(item => fs.statSync(path.resolve(root, item)).isDirectory())
    .map(item => {
        // 獲取每個包的配置文件
        const pkg = require(path.resolve(root, item, 'package.json'))
        return {
            input: path.resolve(root, item, 'src/index.js'),
            output: [
                {
                    exports: 'auto',
                    file: path.resolve(root, item, pkg.main),
                    format: 'cjs'
                },
                {
                    exports: 'auto',
                    file: path.join(root, item, pkg.module),
                    format: 'es'
                },
            ],
            plugins: plugins
        }
    })

此時執(zhí)行打包命令私植,可以一次性為所有組件包打包。

現(xiàn)在有個問題车酣,每次打包的時候需要刪除上次打包結(jié)果曲稼,因此需要添加刪除命令:

安裝依賴包:

npm i -D rimraf

為每個組件包添加del命令:

"del": "rimraf dist"

在根目錄下添加clean命令:

"clean": "yarn workspaces run del"

此時執(zhí)行yarn clean 就可以清除所有包的dist目錄。

plop模版

截止到目前為止湖员,項目的整體結(jié)構(gòu)已經(jīng)完成贫悄,接下來就是無休止的添加組件了,但是考慮到每個組件的初始化有很多相同的工作需要手動完成娘摔,此時可以通過plop將這部分工作交給機器窄坦。

安裝依賴:

npm i plop -D

1.創(chuàng)建模版文件
在項目中添加plop-template/component文件夾,此文件夾下放置創(chuàng)建組件用的所有模版文件凳寺。

1.添加plopfile.js
該文件是plop插件執(zhí)行的入口文件:

module.exports = plop => {
    plop.setGenerator('component', {
      description: 'create a custom component',
      prompts: [
        {
          type: 'input',
          name: 'name',
          message: 'component name',
          default: 'MyComponent'
        }
      ],
      actions: [
        {
          type: 'add',
          path: 'packages/{{name}}/src/{{name}}.vue',
          templateFile: 'plop-template/component/src/component.hbs'
        }
      ]
    })
}

為plop添加一個可執(zhí)行的命令鸭津,該命令會詢問用戶組件的名稱,然后將模版中所有的文件拷貝到packages相關(guān)文件夾內(nèi)肠缨。

1.添加scripts命令
"plop": "plop"

此時在命令行中執(zhí)行yarn plop component就可以創(chuàng)建組件了逆趋。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市怜瞒,隨后出現(xiàn)的幾起案子父泳,更是在濱河造成了極大的恐慌般哼,老刑警劉巖吴汪,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒸眠,居然都是意外死亡漾橙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門楞卡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霜运,“玉大人脾歇,你說我怎么就攤上這事√约瘢” “怎么了藕各?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長焦除。 經(jīng)常有香客問我激况,道長,這世上最難降的妖魔是什么膘魄? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任乌逐,我火速辦了婚禮,結(jié)果婚禮上创葡,老公的妹妹穿的比我還像新娘浙踢。我一直安慰自己,他們只是感情好灿渴,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布洛波。 她就那樣靜靜地躺著,像睡著了一般逻杖。 火紅的嫁衣襯著肌膚如雪奋岁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天荸百,我揣著相機與錄音闻伶,去河邊找鬼。 笑死够话,一個胖子當著我的面吹牛蓝翰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播女嘲,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼畜份,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了欣尼?” 一聲冷哼從身側(cè)響起爆雹,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎愕鼓,沒想到半個月后钙态,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡菇晃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年册倒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磺送。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡驻子,死狀恐怖灿意,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情崇呵,我是刑警寧澤缤剧,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站域慷,受9級特大地震影響鞭执,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芒粹,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一兄纺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧化漆,春花似錦估脆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至朦拖,卻和暖如春圃阳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背璧帝。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工捍岳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人睬隶。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓锣夹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親苏潜。 傳聞我的和親對象是個殘疾皇子银萍,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355