<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          使用 Vite 和 TypeScript 從零打造一個(gè)屬于自己的 Vue3 組件庫(kù)

          共 35104字,需瀏覽 71分鐘

           ·

          2022-07-31 08:19

          前言

          隨著前端技術(shù)的發(fā)展,業(yè)界涌現(xiàn)出了許多的UI組件庫(kù)。例如我們熟知的ElementUI,Vant,AntDesign等等。但是作為一個(gè)前端開發(fā)者,你知道一個(gè)UI組件庫(kù)是如何被打造出來的嗎?

          讀完這篇文章你將學(xué)會(huì):

          • 如何使用pnpm搭建出一個(gè)Monorepo環(huán)境
          • 如何使用vite搭建一個(gè)基本的Vue3腳手架項(xiàng)目
          • 如何開發(fā)調(diào)試一個(gè)自己的UI組件庫(kù)
          • 如何使用vite打包并發(fā)布自己的UI組件庫(kù)

          作為一個(gè)前端擁有一個(gè)屬于自己的UI組件庫(kù)是一件非常酷的事情。它不僅能讓我們對(duì)組件的原理有更深的理解,還能在找工作的時(shí)候?yàn)樽约涸錾簧佟T噯栍心膫€(gè)前端不想擁有一套屬于自己的UI組件庫(kù)呢?

          本文將使用Vue3和TypeScript來編寫一個(gè)組件庫(kù),使用Vite+Vue3來對(duì)這個(gè)組件庫(kù)中的組件進(jìn)行調(diào)試,最后使用vite來對(duì)組件庫(kù)進(jìn)行打包并且發(fā)布到npm上。最終的產(chǎn)物是一個(gè)名為kitty-ui的組件庫(kù)。

          話不多說~ 接下來讓我們開始搭建屬于我們自己的UI組件庫(kù)吧

          Monorepo環(huán)境

          首先我們要了解什么是menorepo及它是如何搭建的吧

          就是指在一個(gè)大的項(xiàng)目倉(cāng)庫(kù)中,管理多個(gè)模塊/包(package),這種類型的項(xiàng)目大都在項(xiàng)目根目錄下有一個(gè)packages文件夾,分多個(gè)項(xiàng)目管理。大概結(jié)構(gòu)如下:

          -- packages
            -- pkg1
              --package.json
            -- pkg2
              --package.json
          --package.json
            
          復(fù)制代碼

          簡(jiǎn)單來說就是單倉(cāng)庫(kù) 多項(xiàng)目

          目前很多我們熟知的項(xiàng)目都是采用這種模式,如Vant,ElementUI,Vue3等。打造一個(gè)menorepo環(huán)境的工具有很多,如:lerna、pnpm、yarn等,這里我們將使用pnpm來開發(fā)我們的UI組件庫(kù)。

          為什么要使用pnpm?

          因?yàn)樗?jiǎn)單高效,它沒有太多雜亂的配置,它相比于lerna操作起來方便太多

          好了,下面我們就開始用pnpm來進(jìn)行我們的組件庫(kù)搭建吧

          使用pnpm

          安裝

          npm install pnpm -g
          復(fù)制代碼

          初始化package.json

          pnpm init
          復(fù)制代碼

          新建配置文件 .npmrc

          shamefully-hoist = true
          復(fù)制代碼

          這里簡(jiǎn)單說下為什么要配置shamefully-hoist

          如果某些工具僅在根目錄的node_modules時(shí)才有效,可以將其設(shè)置為true來提升那些不在根目錄的node_modules,就是將你安裝的依賴包的依賴包的依賴包的...都放到同一級(jí)別(扁平化)。說白了就是不設(shè)置為true有些包就有可能會(huì)出問題。

          monorepo的實(shí)現(xiàn)

          接下就是pnpm如何實(shí)現(xiàn)monorepo的了。

          為了我們各個(gè)項(xiàng)目之間能夠互相引用我們要新建一個(gè)pnpm-workspace.yaml文件將我們的包關(guān)聯(lián)起來

          packages:
          - 'packages/**'
          - 'examples'
          復(fù)制代碼

          這樣就能將我們項(xiàng)目下的packages目錄和examples目錄關(guān)聯(lián)起來了,當(dāng)然如果你想關(guān)聯(lián)更多目錄你只需要往里面添加即可。根據(jù)上面的目錄結(jié)構(gòu)很顯然你在根目錄下新packages和examples文件夾,packages文件夾存放我們開發(fā)的包,examples用來調(diào)試我們的組件

          examples文件夾就是接下來我們要使用vite搭建一個(gè)基本的Vue3腳手架項(xiàng)目的地方

          安裝對(duì)應(yīng)依賴

          我們開發(fā)環(huán)境中的依賴一般全部安裝在整個(gè)項(xiàng)目根目錄下,方便下面我們每個(gè)包都可以引用,所以在安裝的時(shí)候需要加個(gè) -w

          pnpm i vue@next typescript less -D -w
          復(fù)制代碼

          因?yàn)槲覀冮_發(fā)的是vue3組件, 所以需要安裝vue3,當(dāng)然ts肯定是必不可少的(當(dāng)然如果你想要js開發(fā)也是可以的,甚至可以省略到很多配置和寫法。但是ts可以為我們組件加上類型,并且使我們的組件有代碼提示功能,未來ts也將成為主流);less為了我們寫樣式方便,以及使用它的命名空間(這個(gè)暫時(shí)這里沒用到,后面有時(shí)間再補(bǔ)

          • 配置tsconfit.json

          這里的配置就不細(xì)說了,可以自行搜索都是代表什么意思。或者你可以先直接復(fù)制

          npx tsc --init
          復(fù)制代碼

          tsconfig.json:

          {
            "compilerOptions": {
              "baseUrl"".",
              "jsx""preserve",
              "strict"true,
              "target""ES2015",
              "module""ESNext",
              "skipLibCheck"true,
              "esModuleInterop"true,
              "moduleResolution""Node",
              "lib": ["esnext""dom"]
            }
          }
          復(fù)制代碼

          手動(dòng)搭建一個(gè)基于vite的vue3項(xiàng)目

          其實(shí)搭建一個(gè)vite+vue3項(xiàng)目是非常容易的,因?yàn)関ite已經(jīng)幫我們做了大部分事情

          初始化倉(cāng)庫(kù)

          進(jìn)入examples文件夾,執(zhí)行

          pnpm init
          復(fù)制代碼

          安裝vite和@vitejs/plugin-vue

          @vitejs/plugin-vue用來支持.vue文件的轉(zhuǎn)譯

          pnpm install vite @vitejs/plugin-vue -D -w
          復(fù)制代碼

          這里安裝的插件都放在根目錄下

          配置vite.config.ts

          新建vite.config.ts

          import { defineConfig } from 'vite'
          import vue from '@vitejs/plugin-vue'

          export default defineConfig({
              plugins:[vue()]
          })

          復(fù)制代碼

          新建html文件

          @vitejs/plugin-vue 會(huì)默認(rèn)加載examples下的index.html

          新建index.html

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta http-equiv="X-UA-Compatible" content="IE=edge">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Document</title>
          </head>
          <body>
              <div id="app"></div>
              <script src="main.ts" type="module"></script>
          </body>
          </html>
          復(fù)制代碼

          注意: vite 是基于esmodule的 所以type="module"

          新建app.vue模板

          <template>
              <div>
                  啟動(dòng)測(cè)試
              </div>
          </template>
          復(fù)制代碼

          新建main.ts

          import {createApp} from 'vue'
          import App from './app.vue'

          const app = createApp(App)

          app.mount('#app')
          復(fù)制代碼

          此時(shí)會(huì)發(fā)現(xiàn)編譯器會(huì)提示個(gè)錯(cuò)誤:找不到模塊“./app.vue”或其相應(yīng)的類型聲明

          因?yàn)橹苯右?vue文件 TS會(huì)找不到對(duì)應(yīng)的類型聲明;所以需要新建typings(命名沒有明確規(guī)定,TS會(huì)自動(dòng)尋找.d.ts文件)文件夾來專門放這些聲明文件。

          typings/vue-shim.d.ts

          TypeScriptTS默認(rèn)只認(rèn)ES 模塊。如果你要導(dǎo)入.vue文件就要declare module把他們聲明出來。

          declare module '*.vue' {
              import type { DefineComponent } from "vue";
              const component:DefineComponent<{},{},any>
          }
          復(fù)制代碼

          配置腳本啟動(dòng)項(xiàng)目

          最后在package.json文件中配置scripts腳本

          ...
          "scripts": {
              "dev""vite"
            },
          ...
          復(fù)制代碼

          然后終端輸入我們熟悉的命令:pnpm run dev

          vite啟動(dòng)默認(rèn)端口為3000;在瀏覽器中打開localhost:3000 就會(huì)看我們的“啟動(dòng)測(cè)試”頁(yè)面。

          本地調(diào)試

          新建包文件

          本節(jié)可能和目前組件的開發(fā)關(guān)聯(lián)不大,但是未來組件需要引入一些工具方法的時(shí)候會(huì)用到

          接下來就是要往我們的packages文件夾沖填充內(nèi)容了。

          • utils包

          一般packages要有utils包來存放我們公共方法,工具函數(shù)等

          既然它是一個(gè)包,所以我們新建utils目錄后就需要初始化它,讓它變成一個(gè)包;終端進(jìn)入utils文件夾執(zhí)行:pnpm init 然后會(huì)生成一個(gè)package.json文件;這里需要改一下包名,我這里將name改成@kitty-ui/utils表示這個(gè)utils包是屬于kitty-ui這個(gè)組織下的。所以記住發(fā)布之前要登錄npm新建一個(gè)組織;例如kitty-ui

          {
            "name""@kitty-ui/utils",
            "version""1.0.0",
            "description""",
            "main""index.ts",
            "scripts": {
              "test""echo \"Error: no test specified\" && exit 1"
            },
            "keywords": [],
            "author""",
            "license""ISC"
          }

          復(fù)制代碼

          因?yàn)槲覀兪褂胻s寫的,所以需要將入口文件index.js改為index.ts,并新建index.ts文件:(先導(dǎo)出一個(gè)簡(jiǎn)單的加法函數(shù))

          export const testfun = (a:number,b:number):number=>{
              return a + b
          }
          復(fù)制代碼
          • 組件庫(kù)包(這里命名為kitty-ui)

          components是我們用來存放各種UI組件的包

          新建components文件夾并執(zhí)行 pnpm init 生成package.json

          {
            "name""kitty-ui",
            "version""1.0.0",
            "description""",
            "main""index.ts",
            "scripts": {
              "test""echo \"Error: no test specified\" && exit 1"
            },
            "keywords": [],
            "author""",
            "license""ISC"
          }

          復(fù)制代碼

          新建index.ts入口文件并引入utils包

          import {testfun} from '@kitty-ui/utils'

          const result = testfun (1,1)

          console.log(result)
          復(fù)制代碼
          • esno

          由于組件庫(kù)是基于ts的,所以需要安裝esno來執(zhí)行ts文件便于測(cè)試組件之間的引入情況

          控制臺(tái)輸入esno xxx.ts即可執(zhí)行ts文件

          npm i esno -g
          復(fù)制代碼

          包之間本地調(diào)試

          進(jìn)入components文件夾執(zhí)行

          pnpm install @kitty-ui/utils
          復(fù)制代碼

          你會(huì)發(fā)現(xiàn)pnpm會(huì)自動(dòng)創(chuàng)建個(gè)軟鏈接直接指向我們的utils包;此時(shí)components下的packages:

          {
            "name""kitty-ui",
            "version""1.0.0",
            "description""",
            "main""src/index.ts",
            "scripts": {
              "test""echo \"Error: no test specified\" && exit 1"
            },
            "keywords": [],
            "author""",
            "license""ISC",
            "dependencies": {
              "@kitty-ui/utils""workspace:^1.0.1"
            }
          }

          復(fù)制代碼

          你會(huì)發(fā)現(xiàn)它的依賴@kitty-ui/utils對(duì)應(yīng)的版本為:workspace:^1.0.0;因?yàn)閜npm是由workspace管理的,所以有一個(gè)前綴workspace可以指向utils下的工作空間從而方便本地調(diào)試各個(gè)包直接的關(guān)聯(lián)引用。

          到這里基本開發(fā)方法我們已經(jīng)知道啦;接下來就要進(jìn)入正題了,開發(fā)一個(gè)button組件

          試著開發(fā)一個(gè)button組件

          在components文件夾下新建src,同時(shí)在src下新建button組件目錄和icon組件目錄(新建icon為了便于調(diào)試);此時(shí)components文件目錄如下

          -- components
            -- src
              -- button
              -- icon
              -- index.ts
          -- package.json

          復(fù)制代碼

          讓我們先測(cè)試一下我們的button組件能否在我們搭建的examples下的vue3項(xiàng)目本引用~

          首先在button下新建一個(gè)簡(jiǎn)單的button.vue

          <template>
              <button>測(cè)試按鈕</button>
          </template>
          復(fù)制代碼

          然后在button/index.ts將其導(dǎo)出

          import Button from './button.vue'

          export default Button
          復(fù)制代碼

          因?yàn)槲覀冮_發(fā)組件庫(kù)的時(shí)候不可能只有button,所以我們需要一個(gè)components/index.ts將我們開發(fā)的組件一個(gè)個(gè)的集中導(dǎo)出

          import Button from './button'

          export {
              Button
          }

          復(fù)制代碼

          好了,一個(gè)組件的大體目錄差不多就是這樣了,接下來請(qǐng)進(jìn)入我們的examples來看看能否引入我們的button組件

          vue3項(xiàng)目使用button

          上面已經(jīng)說過執(zhí)行在workspace執(zhí)行 pnpm i xxx的時(shí)候pnpm會(huì)自動(dòng)創(chuàng)建個(gè)軟鏈接直接指向我們的xxx包。

          所以這里我們直接在examples執(zhí)行:pnpm i kitty-ui

          此時(shí)你就會(huì)發(fā)現(xiàn)packages.json的依賴多了個(gè)

          "kitty-ui""workspace:^1.0.0"
          復(fù)制代碼

          這時(shí)候我們就能直接在我們的測(cè)試項(xiàng)目下引入我們本地的components組件庫(kù)了,啟動(dòng)我們的測(cè)試項(xiàng)目,來到我們的 examples/app.vue 直接引入Button

          <template>
              <div>
                  <Button />
              </div>
          </template>
          <script lang="ts" setup>
          import { Button } from 'kitty-ui'
          </script>

          復(fù)制代碼

          不出意外的話你的頁(yè)面就會(huì)展示我們剛剛寫的button組件了

          好了萬事具...(其實(shí)還差個(gè)打包,這個(gè)后面再說~);接下來的工作就是專注于組件的開發(fā)了;讓我們回到我們的button組件目錄下(測(cè)試頁(yè)面不用關(guān),此時(shí)我們已經(jīng)可以邊開發(fā)邊調(diào)試邊看效果了)

          因?yàn)槲覀兊腷utton組件是需要接收很多屬性的,如type、size等等,所以我們要新建個(gè)types.ts文件來規(guī)范這些屬性

          在button目錄下新建types.ts



          import { ExtractPropTypes } from 'vue'


          export const ButtonType = ['default''primary''success''warning''danger']

          export const ButtonSize = ['large''normal''small''mini'];


          export const buttonProps = {
            type: {
              typeString,
              values: ButtonType
            },
            size: {
              typeString,
              values: ButtonSize
            }
          }

          export type ButtonProps = ExtractPropTypes<typeof buttonProps>







          復(fù)制代碼

          TIPS

          import type 表示只導(dǎo)入類型;ExtractPropTypes是vue3中內(nèi)置的類型聲明,它的作用是接收一個(gè)類型,然后把對(duì)應(yīng)的vue3所接收的props類型提供出來,后面有需要可以直接使用

          很多時(shí)候我們?cè)趘ue中使用一個(gè)組件會(huì)用的app.use 將組件掛載到全局。要使用app.use函數(shù)的話我們需要讓我們的每個(gè)組件都提供一個(gè)install方法,app.use()的時(shí)候就會(huì)調(diào)用這個(gè)方法;

          我們將button/index.ts調(diào)整為

          import button from './button.vue'
          import type {App,Plugin} from "vue"
          type SFCWithInstall<T> = T&Plugin
          const withInstall = <T>(comp:T) => {
              (comp as SFCWithInstall<T>).install = (app:App)=>{
                  //注冊(cè)組件
                  app.component((comp as any).name,comp)
              }
              return comp as SFCWithInstall<T>
          }
          const Button = withInstall(button)
          export default Button
          復(fù)制代碼

          此時(shí)我們就可以使用app.use來掛載我們的組件啦

          其實(shí)withInstall方法可以做個(gè)公共方法放到工具庫(kù)里,因?yàn)楹罄m(xù)每個(gè)組件都會(huì)用到,這里等后面開發(fā)組件的時(shí)候再調(diào)整

          到這里組件開發(fā)的基本配置已經(jīng)完成,最后我們對(duì)我們的組件庫(kù)以及工具庫(kù)進(jìn)行打包,打包之前如果要發(fā)公共包的話記得將我們的各個(gè)包的協(xié)議改為MIT開源協(xié)議

          ...
          "license""MIT",
          ...
          復(fù)制代碼

          vite打包

          配置文件

          打包們這里選擇vite,它有一個(gè)庫(kù)模式專門為我們來打包這種庫(kù)組件的。

          前面已經(jīng)安裝過vite了,所以這里直接在components下直接新建vite.config.ts(配置參數(shù)文件中已經(jīng)注釋):


          import { defineConfig } from "vite";
          import vue from "@vitejs/plugin-vue"
          export default defineConfig(
              {
                  build: {
                      target: 'modules',
                      //打包文件目錄
                      outDir: "es",
                      //壓縮
                      minify: false,
                      //css分離
                      //cssCodeSplit: true,
                      rollupOptions: {
                          //忽略打包vue文件
                          external: ['vue'],
                          input: ['src/index.ts'],
                          output: [
                              {
                                  format: 'es',
                                  //不用打包成.es.js,這里我們想把它打包成.js
                                  entryFileNames: '[name].js',
                                  //讓打包目錄和我們目錄對(duì)應(yīng)
                                  preserveModules: true,
                                  //配置打包根目錄
                                  dir: 'es',
                                  preserveModulesRoot: 'src'
                              },
                              {
                                  format: 'cjs',
                                  entryFileNames: '[name].js',
                                  //讓打包目錄和我們目錄對(duì)應(yīng)
                                  preserveModules: true,
                                  //配置打包根目錄
                                  dir: 'lib',
                                  preserveModulesRoot: 'src'
                              }
                          ]
                      },
                      lib: {
                          entry: './index.ts',
                          formats: ['es''cjs']
                      }
                  },
                  plugins: [
                      vue()
                  ]
              }
          )

          復(fù)制代碼

          這里我們選擇打包c(diǎn)js(CommonJS)和esm(ESModule)兩種形式,cjs模式主要用于服務(wù)端引用(ssr),而esm就是我們現(xiàn)在經(jīng)常使用的方式,它本身自帶treeShaking而不需要額外配置按需引入(前提是你將模塊分別導(dǎo)出),非常好用~

          其實(shí)到這里就已經(jīng)可以直接打包了;components下執(zhí)行:pnpm run build你就會(huì)發(fā)現(xiàn)打包了es和lib兩個(gè)目錄

          kitty_1.jpg

          到這里其實(shí)打包的組件庫(kù)只能給js項(xiàng)目使用,在ts項(xiàng)目下運(yùn)行會(huì)出現(xiàn)一些錯(cuò)誤,而且使用的時(shí)候還會(huì)失去代碼提示功能,這樣的話我們就失去了用ts開發(fā)組件庫(kù)的意義了。所以我們需要在打包的庫(kù)里加入聲明文件(.d.ts)。

          那么如何向打包后的庫(kù)里加入聲明文件呢?其實(shí)很簡(jiǎn)單,只需要引入vite-plugin-dts

          pnpm i vite-plugin-dts -D -w
          復(fù)制代碼

          然后修改一下我們的vite.config.ts引入這個(gè)插件

          import { defineConfig } from "vite";
          import vue from "@vitejs/plugin-vue"
          import dts from 'vite-plugin-dts'

          export default defineConfig(
              {
                  build: {...},
                  plugins: [
                      vue(),
                      dts({
                          //指定使用的tsconfig.json為我們整個(gè)項(xiàng)目根目錄下掉,如果不配置,你也可以在components下新建tsconfig.json
                          tsConfigFilePath'../../tsconfig.json'
                      }),
                      //因?yàn)檫@個(gè)插件默認(rèn)打包到es下,我們想讓lib目錄下也生成聲明文件需要再配置一個(gè)
                      dts({
                          outputDir:'lib',
                          tsConfigFilePath'../../tsconfig.json'
                      })

                  ]
              }
          )
          復(fù)制代碼

          因?yàn)檫@個(gè)插件默認(rèn)打包到es下,我們想讓lib目錄下也生成聲明文件需要再配置一個(gè)dts插件,暫時(shí)沒有想到其它更好的處理方法~

          然后執(zhí)行打包命令你就會(huì)發(fā)現(xiàn)你的es和lib下就有了聲明文件

          其實(shí)后面就可以進(jìn)行發(fā)布了,發(fā)布之前更改一下我們components下的package.json如下:

          {
            "name""kitty-ui",
            "version""1.0.0",
            "main""lib/index.js",
            "module":"es/index.js",
            "scripts": {
              "build""vite build"
            },
            "files": [
              "es",
              "lib"
            ],
            "keywords": [
              "kitty-ui",
              "vue3組件庫(kù)"
            ],
            "author""小月",
            "license""MIT",
            "description""",
            "typings""lib/index.d.ts"
          }

          復(fù)制代碼

          解釋一下里面部分字段

          pkg.module

          我們組件庫(kù)默認(rèn)入口文件是傳統(tǒng)的CommonJS模塊,但是如果你的環(huán)境支持ESModule的話,構(gòu)建工具會(huì)優(yōu)先使用我們的module入口

          pkg.files

          files是指我們1需要發(fā)布到npm上的目錄,因?yàn)椴豢赡躢omponents下的所有目錄都被發(fā)布上去

          開始發(fā)布

          做了那么多終于到發(fā)布的階段了;其實(shí)npm發(fā)包是很容易的,就拿我們的組件庫(kù)kitty-ui舉例吧

          發(fā)布之前記得到npm[1]官網(wǎng)注冊(cè)個(gè)賬戶,如果你要發(fā)布@xx/xx這種包的話需要在npm新建個(gè)組織組織組織名就是@后面的,比如我建的組織就是kitty-ui,注冊(cè)完之后你就可以發(fā)布了

          首先要將我們代碼提交到git倉(cāng)庫(kù),不然pnpm發(fā)布無法通過,后面每次發(fā)版記得在對(duì)應(yīng)包下執(zhí)行 pnpm version patch你就會(huì)發(fā)現(xiàn)這個(gè)包的版本號(hào)patch(版本號(hào)第三個(gè)數(shù)) +1 了,同樣的 pnpm version major major和 pnpm version minor 分別對(duì)應(yīng)版本號(hào)的第一和第二位增加。

          如果你發(fā)布的是公共包的話,在對(duì)應(yīng)包下執(zhí)行

          pnpm publish --access public
          復(fù)制代碼

          輸入你的賬戶和密碼(記得輸入密碼的時(shí)候是不顯示的,不要以為卡了)正常情況下應(yīng)該是發(fā)布成功了

          注意

          發(fā)布的時(shí)候要將npm的源切換到npm的官方地址(registry.npmjs.org/[2]); 如果你使用了其它鏡像源的話

          樣式問題

          引入我們打包后的組件你會(huì)發(fā)現(xiàn)沒有樣式,所以你需要在全局引入我們的style.css才行;如 main.ts中需要

          import 'kitty-ui/es/style.css';
          復(fù)制代碼

          很顯然這種組件庫(kù)并不是我們想要的,我們需要的組件庫(kù)是每個(gè)css樣式放在每個(gè)組件其對(duì)應(yīng)目錄下,這樣就不需要每次都全量導(dǎo)入我們的css樣式。

          下面就讓我們來看下如何把樣式拆分打包

          處理less文件

          首先我們需要做的是將less打包成css然后放到打包后對(duì)應(yīng)的文件目錄下,我們?cè)赾omponents下新建build文件夾來存放我們的一些打包工具,然后新建buildLess.ts,首先我們需要先安裝一些工具cpy和fast-glob

          pnpm i cpy fast-glob -D -w
          復(fù)制代碼
          • cpy

          它可以直接復(fù)制我們規(guī)定的文件并將我們的文件copy到指定目錄,比如buildLess.ts:

          import cpy from 'cpy'
          import { resolve } from 'path'

          const sourceDir = resolve(__dirname, '../src')
          //lib文件
          const targetLib = resolve(__dirname, '../lib')
          //es文件
          const targetEs = resolve(__dirname, '../es')
          console.log(sourceDir);
          const buildLess = async () => {
              await cpy(`${sourceDir}/**/*.less`, targetLib)
              await cpy(`${sourceDir}/**/*.less`, targetEs)
          }
          buildLess()

          復(fù)制代碼

          然后在package.json中新增命令

          ...
          "scripts": {
              "build""vite build",
              "build:less""esno build/buildLess"
            },
          ...
          復(fù)制代碼

          終端執(zhí)行 pnpm run build:less 你就會(huì)發(fā)現(xiàn)lib和es文件對(duì)應(yīng)目錄下就出現(xiàn)了less文件.

          但是我們最終要的并不是less文件而是css文件,所以我們要將less打包成css,所以我們需要用的less模塊.在ts中引入less因?yàn)樗旧頉]有聲明文件所以會(huì)出現(xiàn)類型錯(cuò)誤,所以我們要先安裝它的 @types/less

          pnpm i --save-dev @types/less -D -w
          復(fù)制代碼

          buildLess.ts如下(詳細(xì)注釋都在代碼中)

          import cpy from 'cpy'
          import { resolve, dirname } from 'path'
          import { promises as fs } from "fs"
          import less from "less"
          import glob from "fast-glob"
          const sourceDir = resolve(__dirname, '../src')
          //lib文件目錄
          const targetLib = resolve(__dirname, '../lib')
          //es文件目錄
          const targetEs = resolve(__dirname, '../es')

          //src目錄

          const srcDir = resolve(__dirname, '../src')

          const buildLess = async () => {
              //直接將less文件復(fù)制到打包后目錄
              await cpy(`${sourceDir}/**/*.less`, targetLib)
              await cpy(`${sourceDir}/**/*.less`, targetEs)

              //獲取打包后.less文件目錄(lib和es一樣)
              const lessFils = await glob("**/*.less", { cwd: srcDir, onlyFilestrue })

              //遍歷含有l(wèi)ess的目錄
              for (let path in lessFils) {

                  const filePath = `${srcDir}/${lessFils[path]}`
                  //獲取less文件字符串
                  const lessCode = await fs.readFile(filePath, 'utf-8')
                  //將less解析成css

                  const code = await less.render(lessCode, {
                      //指定src下對(duì)應(yīng)less文件的文件夾為目錄
                      paths: [srcDir, dirname(filePath)]
                  })

                  //拿到.css后綴path
                  const cssPath = lessFils[path].replace('.less''.css')


                  //將css寫入對(duì)應(yīng)目錄
                  await fs.writeFile(resolve(targetLib, cssPath), code.css)
                  await fs.writeFile(resolve(targetEs, cssPath), code.css)
              }



          }
          buildLess()


          復(fù)制代碼

          執(zhí)行打包命令之后你會(huì)發(fā)現(xiàn)對(duì)應(yīng)文件夾下多了.css文件

          1657259623489.jpg

          現(xiàn)在我已經(jīng)將css文件放入對(duì)應(yīng)的目錄下了,但是我們的相關(guān)組件并沒有引入這個(gè)css文件;所以我們需要的是每個(gè)打包后組件的index.js中出現(xiàn)如:

          import "xxx/xxx.css"
          復(fù)制代碼

          之類的代碼我們的css才會(huì)生效;所以我們需要對(duì)vite.config.ts進(jìn)行相關(guān)配置

          首先我們先將.less文件忽略**external: ['vue', /.less/],**這時(shí)候打包后的文件中如button/index.js就會(huì)出現(xiàn)

          import "./style/index.less";
          復(fù)制代碼

          然后我們?cè)賹⒋虬蟠a的.less換成.css就大功告成了

          ...
          plugins: [
                      ...

                      {
                          name: 'style',
                          generateBundle(config, bundle) {
                              //這里可以獲取打包后的文件目錄以及代碼code
                              const keys = Object.keys(bundle)

                              for (const key of keys) {
                                  const bundler: any = bundle[key as any]
                                  //rollup內(nèi)置方法,將所有輸出文件code中的.less換成.css,因?yàn)槲覀儺?dāng)時(shí)沒有打包less文件

                                  this.emitFile({
                                      type'asset',
                                      fileName: key,//文件名名不變
                                      source: bundler.code.replace(/\.less/g'.css')
                                  })
                              }
                          }
                      }
                  ...
                  ]
          ...
          復(fù)制代碼

          我們最終的vite.config.ts如下

          import { defineConfig } from "vite";
          import vue from "@vitejs/plugin-vue"
          import dts from 'vite-plugin-dts'

          export default defineConfig(
              {
                  build: {
                      target: 'modules',
                      //打包文件目錄
                      outDir: "es",
                      //壓縮
                      minify: false,
                      //css分離
                      //cssCodeSplit: true,
                      rollupOptions: {
                          //忽略打包vue和.less文件
                          external: ['vue', /\.less/],
                          input: ['src/index.ts'],
                          output: [
                              {
                                  format: 'es',
                                  //不用打包成.es.js,這里我們想把它打包成.js
                                  entryFileNames: '[name].js',
                                  //讓打包目錄和我們目錄對(duì)應(yīng)
                                  preserveModules: true,
                                  //配置打包根目錄
                                  dir: 'es',
                                  preserveModulesRoot: 'src'
                              },
                              {
                                  format: 'cjs',
                                  //不用打包成.mjs
                                  entryFileNames: '[name].js',
                                  //讓打包目錄和我們目錄對(duì)應(yīng)
                                  preserveModules: true,
                                  //配置打包根目錄
                                  dir: 'lib',
                                  preserveModulesRoot: 'src'
                              }
                          ]
                      },
                      lib: {
                          entry: './index.ts',
                          formats: ['es''cjs']
                      }
                  },




                  plugins: [
                      vue(),
                      dts({
                          //指定使用的tsconfig.json為我們整個(gè)項(xiàng)目根目錄下掉,如果不配置,你也可以在components下新建tsconfig.json
                          tsConfigFilePath: '../../tsconfig.json'
                      }),
                      //因?yàn)檫@個(gè)插件默認(rèn)打包到es下,我們想讓lib目錄下也生成聲明文件需要再配置一個(gè)
                      dts({
                          outputDir: 'lib',
                          tsConfigFilePath: '../../tsconfig.json'
                      }),

                      {
                          name: 'style',
                          generateBundle(config, bundle) {
                              //這里可以獲取打包后的文件目錄以及代碼code
                              const keys = Object.keys(bundle)

                              for (const key of keys) {
                                  const bundler: any = bundle[key as any]
                                  //rollup內(nèi)置方法,將所有輸出文件code中的.less換成.css,因?yàn)槲覀儺?dāng)時(shí)沒有打包less文件

                                  this.emitFile({
                                      type: 'asset',
                                      fileName: key,//文件名名不變
                                      source: bundler.code.replace(/\.less/g, '.css')
                                  })
                              }
                          }
                      }

                  ]
              }
          )

          復(fù)制代碼

          最后我們將打包less與打包組件合成一個(gè)命令(package.json):

          ...
          "scripts": {
              "build""vite build & esno build/buildLess"
            },
          ...
          復(fù)制代碼

          后續(xù)直接執(zhí)行pnpm run build 即可完成所有打包啦

          直接使用

          如果你不想一步步的搭建,想直接使用現(xiàn)成的話,你可以直接把項(xiàng)目clone下來-> kittyui[3],然后你只需要以下幾步便可將其完成

          • 安裝pnpm npm i pnpm -g
          • 安裝esno npm i esno -g
          • 安裝所有依賴 pnpm install
          • 本地測(cè)試 進(jìn)入examples文件夾執(zhí)行 pnpm run dev 啟動(dòng)vue3項(xiàng)目
          • 打包 pnpm run build

          寫在最后

          由于作者水平有限,難免會(huì)存在一些錯(cuò)誤或不妥之處,希望各位能夠不吝指出,一定及時(shí)修改。如果你對(duì)這個(gè)項(xiàng)目有更好的想法或者建議也歡迎在評(píng)論區(qū)提出,不勝感激。

          后續(xù)我會(huì)對(duì)一些常用組件進(jìn)行開發(fā),每個(gè)組件的開發(fā)都會(huì)以文章的形式展現(xiàn)出來以供大家參考。也歡迎大家將項(xiàng)目fork下來,提交自己組件或者對(duì)kittyui的修改到kittyui[4]~

          創(chuàng)作不易,你的點(diǎn)贊就是我的動(dòng)力!如果感覺對(duì)自己有幫助的話就請(qǐng)點(diǎn)個(gè)贊吧,感謝~

          作者:東方小月

          https://juejin.cn/post/7117886038126624805

          祝 您:2022 年暴富!萬事如意!

          點(diǎn)贊和在看就是最大的支持,比心??

          瀏覽 60
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  洋洋AV| 色爱综合网 | 俺去俺来也在线www色情网 | 青娱乐超碰在线 | 欧美日本高清视频 |