<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>

          【Vue】1856- 一文揭秘Vue3組件庫的優(yōu)雅打包與細節(jié)

          共 15987字,需瀏覽 32分鐘

           ·

          2023-11-11 02:08

          千呼萬喚始出來干貨滿滿的組件庫打包分享!這次,我終于要給大家?guī)黻P(guān)于組件庫的工程化打包升級與細節(jié)的文章啦?;仡櫧M件庫的搭建,已經(jīng)是很久以前了,目前組件庫也發(fā)展到一個瓶頸期需要升級架構(gòu)和打包了...

          我認(rèn)為,前端工程化更像是個命題作文,沒有標(biāo)準(zhǔn)答案,能解決問題即可。所以,本文旨在以我解決問題的實戰(zhàn)經(jīng)過來分享一種組件庫的打包方式,希望大家都會有所啟發(fā)、有所收獲!廢話少說,直接開始吧。

          現(xiàn)狀&目標(biāo)

          看過我文章的同學(xué)應(yīng)該知道很久之前我搞了個組件庫,還寫了些文章——快上車!從零開始搭建一個屬于自己的組件庫![1]...之類的分享給大家。但其實組件庫一直處于一個發(fā)展階段,還有很多東西都不成熟,就好比今天的主角——組件庫打包。

          這里先簡單回顧一下之前的組件庫打包:

          1. 直接配置 vite.config 文件。只配置了lib模式打包
          2. 運行 pnpm run build,其實就是執(zhí)行 vite build 簡單粗暴完成組件庫打包
          3. 輸出 umd、iifees、cjs 模式的產(chǎn)物文件
          4. 輸出一個 style.css 樣式文件

          這是最終打包完的 dist 包下的目錄結(jié)構(gòu):

          image.png

          簡單點開看一下 es 包的產(chǎn)物代碼:

          image.png

          好了,以上就是之前的組件庫打包的方案和結(jié)果,簡單又好用,簡直了。但是為什么現(xiàn)在的我要選擇升級打包方式呢?是基于什么樣的痛點?這一個我會在后文慢慢道來。這里,大家暫且先跟我一起看看本文的實戰(zhàn)目標(biāo)。

          講到實戰(zhàn)目標(biāo),不得不以優(yōu)秀的開源項目為標(biāo)桿。這里我就直接按照 element-plus 的產(chǎn)物格式作為目標(biāo)了。跟大家簡單的看看它的打包產(chǎn)物的結(jié)構(gòu),如下圖所示:

          image.png

          其中再點開它的 es 目錄看看究竟:

          image.png

          非常工整的結(jié)構(gòu),也就是打包前的原項目結(jié)構(gòu)。感興趣的可以去 unpkg[2]或者自己裝一個在node_modules中查看。這里我們直接分析產(chǎn)物結(jié)構(gòu)如下:

          • dist。放整包的,簡單理解為一個打包所有代碼并壓縮的.min.js
          • es。es包,產(chǎn)物按項目的目錄結(jié)構(gòu)生成。簡單理解為只將ts、.vue文件編譯成js
          • lib。跟上面的 es 的一樣,只是這里是 cjs 模式的
          • theme-chalk。樣式代碼,各組件的css文件和一個整體的index文件
          • package.json。emmm...這個大家自己翻譯吧
          • *.d.ts??膳浜?vscode 的 voloar 插件實現(xiàn)代碼提示
          • *.json。用于 webstorm 的代碼提示

          所以,上述就是本文的目標(biāo)了,我也要通過對組件庫打包的升級改造,讓組件庫的打包產(chǎn)物跟上述結(jié)構(gòu)、功能相似。不過,本文只著重分享組件庫的打包,代碼提示相關(guān)的實踐并不會涉及,并且我打算另外寫一篇文章來分享組件庫的代碼提示實戰(zhàn)。

          分離CSS

          這一小節(jié),我將為大家解開上一小節(jié)我遺留下來的一個問題:我是基于什么樣的痛點才要升級組件庫的打包?畢竟這種大佬都不愿意投入資源的非kpi項目,我有那時間摸摸魚不香嗎是吧~

          回到本小節(jié)的主題,為什么要分離css?那就得看之前是怎么開發(fā)的了。

          image.png

          如上圖所示,這是最基本的 vue 開發(fā)模式了:template + script + css。這樣寫法的組件,通過 vitelib 模式直接打包,可以得到產(chǎn)物:**.js 和一個 style.css,這一點沒問題吧,前文已經(jīng)講過了。

          這樣有什么樣的問題?這樣會導(dǎo)致所有組件的樣式都被打包進了同一個 css 文件里,按需引用對于樣式文件來說就不存在了。當(dāng)然,這也只是其中一個問題,而且還是個小問題而已,我肯定不會因此而升級打包方式和改變項目組成結(jié)構(gòu)的,所以我們接著往下看先。

          如果之前有看過我另一篇組件庫實戰(zhàn)文章:組件庫實戰(zhàn)——按需加載工程化[3] 的同學(xué)應(yīng)該知道,那時候的解決方案其實也是有缺陷的,這也是當(dāng)前遇到痛點必須解決的地方。當(dāng)然,沒看過的也沒關(guān)系,這里我簡單的說明一下:

          在使用element-plus按需導(dǎo)入[4]的項目中,如果直接使用自己組件庫的組件(我是vc-前綴的),會丟掉原本el-組件的樣式。比如說我直接在代碼中使用 vc-button,而他又依賴el-button的樣式,此時就會丟失el-button的樣式。原因就是按需引入的插件匹配 vc-button 的組件時并不知道需要引入 el-button 的樣式,于是我的自己寫一個插件工具來實現(xiàn)這一點。

          resolver說明.png

          如上圖所示,這個插件工具——resolver當(dāng)解析到 template 中有 vc-button 的時候,會去 import vcButton 的組件代碼import el-button 的樣式代碼。(此時 vc-button 的樣式文件是全局引入的。因為打包后只有一個 style.css

          雖然這個自動導(dǎo)入的問題是解決了,但是又遺留了另一個問題就是組合組件的樣式問題。比如說此時我的 vc-button 不僅用了 el-button,還組合了 el-tagel-select 等組件使用,那上述插件工具就沒用了,因為缺少import另外兩個組件的樣式。

          如需在使用組合組件的時候樣式能正常引入,那你得告訴插件這個組件用了哪些組件的樣式是吧?那這個要怎么做呢?其實我們可以參考老大哥——element-plus 的實現(xiàn)。因為他就有這樣的案例,比如說他的 select 組件,就是多個 el- 組件組合成的。我們?nèi)タ匆幌滤脑创a:

          image.png

          可以看到 select 目錄下有一個 style 目錄,點開里面的文件可以看到其做了一個樣式的集成。引用了 inputtag、popper 等組件的樣式。雖然之前參與這個項目的時候沒怎么留意過這個 style 目錄(也不知道是干嘛的),但是現(xiàn)在大概可以猜出他就是一個樣式關(guān)系表,做整合的。結(jié)合上述說明,我們重新畫個圖來看看就很清晰了:

          resolver說明2.png

          這里,我們可以看出 import 樣式那一塊上不再是直接用 el-button 的樣式了,而是用了 vc-button的樣式索引文件(是個js文件),而這個索引文件呢,引用了各種它所需要的樣式文件。如此一來,之前遺留下來的缺陷問題也就引刃而解了。

          這也是將 css 拆分出來的好處,讓他形成一個 原子css 的概念。將每一個組件的樣式都單獨抽出來寫,每一個組件的樣式就是一個原子css,這樣組合使用的時候也就很方便了。

          然后,這里再順便提一下為什么這次的更新打包,要保留原本的目錄結(jié)構(gòu)來打包?;蛘哒f為什么 element-plus 產(chǎn)物的 es、lib 目錄下是保留了原項目結(jié)構(gòu)的。我們來看看它官網(wǎng)的其中一個介紹:

          image.png

          我個人猜測,如果需要手動引入樣式的話,還是要知道去哪里引用的對吧?總不能打包完代碼就亂成一團,然后用戶需要手動引入的時候不知道去哪里引入了...

          好了,這一小節(jié)說得有點長,我簡單總結(jié)一下:

          1. 解釋了自建組件庫使用 unplugin之類插件[5] 實現(xiàn)自動按需導(dǎo)入的樣式引入問題。
          2. 解釋了為什么要分離 css?核心解決組合組件的樣式問題,順便解決按需加載的體量問題。
          3. 順便分析了為什么組件庫打包完要保留原項目結(jié)構(gòu)。

          編寫打包腳本

          前文鋪墊了這么多,終于輪到本文的重頭戲了。那這一小節(jié),我們主要實現(xiàn)幾個目標(biāo)點:

          1. 使用 gulp 串并聯(lián)工作流完成打包任務(wù)
          2. 打包出 全局dist、es、lib 的產(chǎn)物。其中es、lib目錄要保持原目錄結(jié)構(gòu)
          3. 抽離css,并且編譯打包css(這里我用的是scss)

          這里因為我們要對不同格式根本打包,再配置成 vite.config 并直接通過 vite build 來打包肯定是不夠方便的了,所以我借助 gulp 來完成一個簡單的打包腳本。正式進入打包環(huán)節(jié)前,先來解決工具選擇的問題。目前我個人意向的是 viterollup,我是如何選擇的呢?在此,我先擼了個圖:

          打包工具選擇.png

          從圖中大概可以看出來,vite 雖然生產(chǎn)環(huán)境默認(rèn)使用 rollup 來構(gòu)建,但相比于 rollup,它是更為上層的。它會有更多的集成,比如說集成了對 ts 的支持,對 scss、less 等支持,還有各種基礎(chǔ)的插件集成(如支持直接 require 模塊),當(dāng)然,他還預(yù)置了一些通用的 rollup 配置。

          講這么多,簡單來說就是 vite 更上層,使用方便,適合懶人;**rollup 更底層,使用靈活**,適合有激情愛折騰的大佬!我當(dāng)然是選擇了前者~如果說想使用 rollup 的話,建議大家直接參考 element-plus 的打包吧,它就是基于 rollup 寫的打包腳本。

          我們直接看基于 vite打包腳本的基本格式

          import { build } from 'vite'

          function buildScript ({
            build({
              plugins: [],
              build: {
                outDir'xxx',
                lib: {
                  entry'xxx'
                }
              },
            })
          }

          其實也很簡單,安裝 vite,然后 import 它暴露出來的 build 函數(shù),并對其中做一些配置。這些配置就跟我們平時配置 vite.config 文件是一樣的,一把梭哈,基本沒什么難度。接下來我們看看其中每一步的一些核心點吧。

          1. 打包 es、lib 包并保留原結(jié)構(gòu)

          關(guān)于 es、lib 包,我們依舊是使用 vite 的 lib 模式去構(gòu)建[6](詳細可點擊鏈接去了解)。這里簡單的列一下基礎(chǔ)的配置:

          {
            plugins: [
              vue(),
              vueJsx()
            ],
            build: {
              outDir: join(vcElementPlusRoot, 'dist''es'),
              lib: {
                entry: files,
                formats: ['es'],
              },
              rollupOptions: {
                external: ['element-plus''vue''vue-router''@element-plus/icons-vue']
              }
            }
          }

          其中的 plugins 配置中,因為我們打包的是 vue3 組件庫,所以會用到 vitejs/plugin-vue[7] 等相關(guān)的 vue 插件。

          build 配置中,我們主要看 lib.entry 即可。這里的入口是可以傳數(shù)組(多個)的,如下圖的說明:

          image.png

          這里可以通過一個工具——fast-glob[8]拿到所有的入口,包括 style 目錄中的索引文件的入口(這一點后面會提到)。

          如以下寫法,就能拿到組件庫的全部入口了:

            const files = await glob('**/*.{js,ts,vue}', {
              cwd: vcElementPlusComponentsRoot,
              absolutetrue,
              onlyFilestrue,
            })

          獲取的結(jié)果如下截圖(大家可以自己試著玩玩):

          image.png

          剩下的就是 rollupOptions 配置,當(dāng)我們想實現(xiàn)打包后保留原項目結(jié)構(gòu),必須配置:

          1. preserveModules[9]。此模式將使用原始模塊名稱作為文件名為所有模塊創(chuàng)建單獨的塊,而不是創(chuàng)建盡可能少的塊。(感興趣的點擊進去看看吧,這里我隨便找個翻譯軟件翻譯的)
          2. preserveModulesRoot[10]。當(dāng)output.preserveModules為true時,應(yīng)該從output.dir路徑中剝離的輸入模塊的目錄路徑(同上,感興趣點鏈接了解吧)

          ok,基本上有了這些之后,打包 es、lib 的任務(wù)就完成了。最終腳本代碼如下:

            await build({
              resolve: {
                alias: VcElementAlias()
              },
              plugins: [
                vue(),
                vueJsx()
              ],
              build: {
                outDir: join(vcElementPlusRoot, 'dist''es'),
                lib: {
                  entry: files,
                  formats: ['es'],
                },
                rollupOptions: {
                  external: ['element-plus''vue''vue-router''@element-plus/icons-vue'],
                  output: {
                    preserveModulestrue,
                    preserveModulesRoot: vcElementPlusComponentsRoot,
                    exports'named'
                  }
                }
              }
            })

          接著,我運行一下打包看看打包后的效果:

          image.png

          為了方便大家看產(chǎn)物結(jié)構(gòu),我用不同顏色的框框劃分了打包后的產(chǎn)物結(jié)構(gòu),可以看到基本符合我們的預(yù)期了。(cjs模式的打包跟es類似的,所以我就不展開lib目錄的打包過程和產(chǎn)物了)

          2. 打包 dist 代碼

          這一點相比上一點來說要更加的簡單,其實就是我們改版前的那種。無腦配置個輸出模式為 umdiife 就完成了。簡單看看相比前文的差異的配置:

            build: {
              outDir: join(vcElementPlusRoot, 'dist'),
              lib: {
                entry: file,
                formats: ['umd''iife'],
                name: VC_ELEMENT_PLUS_CAMELCASE_NAME,
                fileNameformat => `${VC_ELEMENT_PLUS}.${format}.js`
              }
              ...
            }  

          其余的基本沒什么不同,不過注意這里要配置 namefileName。當(dāng)然,這些你不配置的話,打包也不會成功,并且會有報錯提示,只要根據(jù)報錯提示完成對應(yīng)的配置后,問題也不大。我打算把這兩個文件放在 dist 的根目錄中,es、lib 目錄同級

          最終打包出來的結(jié)果如下:

          image.png

          也是符合預(yù)期的,我們接著往下走。

          3. 編譯&打包CSS

          這一步,我借助了gulp-sass[11]插件。它的作用是:用于將 Sass 代碼編譯成 CSS 代碼。在正式講打包之前,我給大家看看我抽離的樣式文件大概成什么樣:

          image.png

          我將原本都各自寫在組件內(nèi)的樣式抽離出來,放在一個 theme 的目錄下的。每一個 scss 文件以組件名命名,他們就是一個個組件的原子css。然后我會在組件對應(yīng)的 style 索引文件中這樣引用樣式(直接引用 scss 文件):

          image.png

          大家也可能注意到了,在 theme 目錄下,也有一個 index.scss 文件,它的代碼是這樣的:

          image.png

          沒錯,它其實就是一個總的樣式文件。緊接著,我們直接看看關(guān)于 scss 的編譯、打包腳本如何實現(xiàn)吧:

          import gulpSass from 'gulp-sass'
          import gulp from 'gulp'
          import dartSass from 'sass'
          import { vcElementPlusRoot } from '../../utils/path';

          export async function sassCompiler ({
            const sass = gulpSass(dartSass)
            return await gulp.src(`${vcElementPlusRoot}/theme/*.scss`// 入口
              .pipe(sass.sync()) // 編譯
              .pipe(gulp.dest(`${vcElementPlusRoot}/dist/theme`)) // 輸出目錄
          }

          對于上述代碼,他的作用就是編譯所有的 scss 文件,并且將他們都打包進 index 文件中。因為關(guān)于 gulp-sass 、 gulp 等這些工具,我也是要用的時候才去寫,平時也了解不多,所以我就不多展開他們的一些用法、寫法了,大家感興趣可以自己去研究一下。

          最后也是來看看打包后的效果:

          image.png

          可以看到,所有的 scss 文件都被編譯成了 css 文件,此時,我們打開一下 index.css 文件大概看看成什么樣:

          image.png

          我沒有做代碼壓縮,所以大家可以一目了然,應(yīng)該是所有組件的 css 都被打進來了,沒問題!

          4. 通過 gulp 編排任務(wù)

          其實這就是一個工作流工具,有了解過 CI/CD 的同學(xué)應(yīng)該很清楚它是干嘛的了。當(dāng)然,還是那句話,我并不是常年使用 gulp 的,所以了解得也并不多,這里使用也就是為了解決問題,達成目的,所以我也不會過多的展開對 gulp 的講解。大家感興趣的可以去他的官網(wǎng)[12]詳細看看。

          因為前面我們已經(jīng)把打包任務(wù)都分別實現(xiàn)了,最后通過 gulp 做一個串聯(lián)而已。所以我還是直接上代碼吧:

          import { series, parallel } from 'gulp'
          import { cleanDist, elpBuildModules, elpBuildBundle, sassCompiler } from './tasks'

          export default series(
            cleanDist, // 刪除上次的dist
            parallel(
              elpBuildModules, // 并行執(zhí)行 es、lib 打包
              elpBuildBundle, // 并行執(zhí)行 全局dist 打包
              sassCompiler // 并行執(zhí)行 scss 的編譯打包
            )
          )

          整個 gulpfile 就這么點,簡單來說它做的事情就是刪除上次的 dist,然后進行 es、lib、dist、scss 的編譯打包工作。

          彩蛋——rollup插件改動樣式索引文件

          不知道大家發(fā)現(xiàn)沒,前文兩個地方我都埋了點伏筆:

          1. 入口為什么要包含 style 目錄中的索引文件
          2. 組件 style 目錄中的索引文件直接引用 scss 文件:

          相信已經(jīng)很明顯了,因為直接引用 scss 文件作為樣式文件在瀏覽器中無法直接使用!所以我們需要對其做一些改動。**開發(fā)環(huán)境中,因為 vite 天然支持 scss**(只要安裝了sass的包就行,不用任何插件配置),所以我們在開發(fā)環(huán)境使用樣式時,直接 import 我們的索引文件(再次提醒索引文件是個js)是沒問題的,比如:

          import {vcButton} from '@xxx'
          import '@xxx/button/style/index.js'

          但是如果此時到了瀏覽器環(huán)境直接使用的話就不行了,因為 index.jsimport 的是一個 scss 的文件。所以我們還需要自己寫一個 rollup 插件,在打包的時候?qū)⑺饕募玫穆窂阶鲆稽c改動。

          這個插件的目標(biāo)就是**將 scss 替換成 css**,如:

          • import '@lizhife/vc-element-plus/theme/back.scss'
          • import '@lizhife/vc-element-plus/theme/back.css'

          當(dāng)然,對應(yīng)的import路徑配置那些也要配置好,不然可能在路徑上也要有所改動。這里我就基于 rollupresolveId[13] 鉤子。當(dāng)然,這一段在 vite 官網(wǎng)[14]也能看到。

          簡單說說 resolveId 鉤子的作用,他能拿到你所有 import 的包名、路徑,并在參數(shù)中提供給你。所以基于此,我們可以這樣來寫這個插件:

          export function rollupPluginCompileStyleEntry (): Plugin {
            const themeEntryPrefix = `${PREFIX}/${VC_ELEMENT_PLUS}/${THEME}`

            return {
              name'rollup-plugin-compile-style-entry',

              resolveId (id) {
                // 匹配是否滿足 @xxx/vc-el.. 開頭的字符
                if (!id.startsWith(themeEntryPrefix)) return 
                return {
                  // 將 scss 字符替換成 css
                  id: id.replaceAll('.scss''.css'),
                  external'absolute',
                }
              }
            }
          }

          這個插件的核心就如注釋那樣了,匹配一個固定開頭的字符(比如這里是 @lizhife/vc-element-plus),**將這個字符串的 .scss 替換成 .css**。我們直接看結(jié)果,看看使用了這個插件后的效果如何:

          image.png

          可以看到,import 的最終結(jié)果變成了 xx.css,這也符合我們的期望,完美~當(dāng)然啦,記得把插件配置上,不然就白搞了:

            plugins: [
              rollupPluginCompileStyleEntry(),
              vue(),
              vueJsx()
            ],

          彩蛋——alias配置

          當(dāng)在項目中使用自身依賴時,需要注意配置alias。

          Error: [vite]: Rollup failed to resolve import "@lizhife/vc-element-plus" from '...'

          當(dāng)遇到上述的一些因為包名而導(dǎo)致的無法解析的問題,可以通過配置 alias 來解決,特別是一些開發(fā)環(huán)境和打包完之后有所變動的。相關(guān)的我也在這里說太多了,之前的文章也有提到這一點。

          涉及的插件簡介

          這里我會介紹本次實戰(zhàn)中會用到的各種插件、工具和他們的作用簡介,希望可以幫助大家更清晰地了解本文的內(nèi)容。另外我會附上每個插件的gayhub地址,感興趣的同學(xué)可以戳進去詳細了解。

          1. @esbuild-kit/cjs-loader[15]
          • 支持在 gulpfile 使用 esm(import、export)的模塊化寫法
          • 支持在 gulpfile 使用 ts
          1. fast-glob[16]
          • 提供了一種快速、靈活的方式來匹配文件和目錄。
          1. @vitejs/plugin-vue[17]
          • 支持 vite 解析.vue后綴的單文件組件(SFC),類似 webpack 中我們用的 vue-loader
          1. @vitejs/plugin-vue-jsx[18]
          • 支持 vite 解析 jsx/tsx。同第3點,并且二者是放在同一個倉庫中的
          1. gulp-sass[19]
          • 一個 gulp 插件,用于將 Sass 代碼編譯成 CSS 代碼

          寫在最后

          文章內(nèi)容有點長,大家點贊關(guān)注慢慢看~關(guān)于組件庫打包的內(nèi)容輸出,之前就有小伙伴催更了,但是因為之前沒啥使用上的問題,并且這一塊投入也麻煩,所以一直沒搞。組件庫慢慢發(fā)展到現(xiàn)在,組件數(shù)量慢慢上升,發(fā)展遇到瓶頸了所以需要升級一下組件庫的架構(gòu)和打包。當(dāng)然,后續(xù)有相關(guān)的組件庫實戰(zhàn)我會持續(xù)的輸出文章分享。

          最后,如果本文有哪些寫得不對的地方,大家盡管指出。希望這篇文章在你的工程化道路上有所啟發(fā)。再重申一下,工程化是開放性作文,思路、方案有很多,能解決問題的就是可行的,并沒有標(biāo)準(zhǔn)答案。

          作者:井柏然

          鏈接:https://juejin.cn/user/3544481218962183/posts


          往期回顧

          #

          如何使用 TypeScript 開發(fā) React 函數(shù)式組件?

          #

          11 個需要避免的 React 錯誤用法

          #

          6 個 Vue3 開發(fā)必備的 VSCode 插件

          #

          3 款非常實用的 Node.js 版本管理工具

          #

          6 個你必須明白 Vue3 的 ref 和 reactive 問題

          #

          6 個意想不到的 JavaScript 問題

          #

          試著換個角度理解低代碼平臺設(shè)計的本質(zhì)

          回復(fù)“加群”,一起學(xué)習(xí)進步

          瀏覽 374
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  日韩色网| 波多野结衣无码AⅤ一区t二区三区 | 欧美日韩一区二区黄 | www国产成人免费观看视频 | www日逼com |