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

          前端基建處理之組件庫優(yōu)化方案

          共 19607字,需瀏覽 40分鐘

           ·

          2024-03-21 04:00


                

          前端基建開發(fā)一直被認為是前端開發(fā)中的 “高階技能”。而內(nèi)部組件庫的開發(fā)則算是基建中比較“容易”入手的一個方向。所以咱們今天就利用這篇文章,來看一看 組件庫開發(fā) 的優(yōu)化方案。

          原文:https://juejin.cn/post/7302255044879400998

          背景

          前段時間入職了新公司后,做一些內(nèi)部前端基建的工作,其中一個工作就是優(yōu)化現(xiàn)有的frontend-common公共組件庫。之前的組件庫一直是以源碼依賴的形式存在,即各個項目通過git submodule的方式將該倉庫引入到各個項目中,作為一個目錄,然后打包的時候?qū)rontend-common的源碼以及項目本身的代碼一起打包到產(chǎn)物中。公共組件的運行依賴于宿主,要求引入frontend-common的項目(宿主)本身要安裝依賴的包,否則無法運行,例如公共組件依賴element這個庫,所以引入公共組件的項目也要求要安裝element才可以運行。

          分析

          當前這種使用方式以及實際的落地方式上存在一些問題,這里簡單羅列下

          • 分支管理不規(guī)范(每個引用frontend-common的子項目都單獨維護了一個分支,沒有合入到主分支,導(dǎo)致各自的差異越來越大)
          • 代碼風(fēng)格不統(tǒng)一(不同的開發(fā)的編輯器配置不一樣,導(dǎo)致大家提交上來的代碼五花八門)
          • 組件沒有文檔和預(yù)覽(寫公共組件的開發(fā)實現(xiàn)之后就沒有花更多時間在文檔和預(yù)覽上,導(dǎo)致其他開發(fā)要使用組件的時候有上手成本,而且不方便熟悉這些公共組件的功能和使用)
          • 沒有提交規(guī)范(因歷史原因,不少提交的commit message上面都是隨便寫,沒有什么規(guī)范,也不方便根據(jù)commit信息判斷改動的內(nèi)容)
          • 無法保證改動不影響之前的一些功能(即無法保證能向下兼容,改動需要更多靠人工的方式來驗證功能是否不影響之類的)
          • 使用submodule的方式引入的方式不是很優(yōu)雅(個人偏向于用npm包的方式,或者用monorepo的方式)

          優(yōu)化思路

          根據(jù)上面存在的一些問題我們有針對性的做出一些調(diào)整和策略

          • 分支管理規(guī)范,先讓團隊成員把各自的分支合并,如果只是單獨自己項目用的組件,就遷移到自己項目的代碼倉庫中維護,不寫在公共組件中。后續(xù)都從主分支拉新的分支進行開發(fā),本地調(diào)試可以用自己的分支拉取代碼調(diào)試,開發(fā)完之后合并到測試分支,線上環(huán)境和預(yù)發(fā)布環(huán)境必須用指定的分支來拉取公共組件庫的代碼。
          • 用eslint + prettier + husky + lint-stage來保證代碼風(fēng)格統(tǒng)一
          • 接入storybook,用于做組件預(yù)覽和文檔的功能
          • 增加commitlint commitizen等工具,用于命令式生成commit,保證commit信息的規(guī)范
          • 增加單元測試,新增一個組件要寫單元測試,后續(xù)修改之后要保證之前的單元測試都運行通過才可以合并代碼
          • 因為內(nèi)部基建的原因,暫時還沒有搭建內(nèi)部的npm源,monorepo的方式改動也比較大,暫時不做調(diào)整

          改造步驟

          倉庫初始化npm

          因為原先是作為當成一個組件來使用,所以frontend-common這個代碼倉庫里面是沒有package.json node_module等配置,我們?yōu)榱私尤氲囊?guī)范肯定要增加包來處理這些,所以第一步要初始化npm

                
                npm init

          直接按提示輸入即可,這里就不再贅述

          增加代碼規(guī)范的包

          eslint + prettier + lint-stage + husky + 對應(yīng)的eslint包
          根據(jù)自己項目的實際情況增加對應(yīng)的包,比如筆者這個倉庫是用vue2的,就用vue相關(guān)的eslint包
          這里筆者列一下自己安裝的包和創(chuàng)建的配置文件

          新增包和命令

          在package.json中新增對應(yīng)的包和命令、配置

                
                "scripts": {
              ...
              "lint-staged""lint-staged",
              "prepare"" husky install",
          },
          "lint-staged": {
              "*.{js,ts,vue,jsx,tsx}": [
                  "eslint --cache --fix"
              ]
          },
          "devDependencies": {
              ...
              "@commitlint/cli""^17.6.5",
              "@commitlint/config-conventional""^17.6.5",
              "eslint""^7.32.0",
              "eslint-config-prettier""^8.3.0",
              "eslint-plugin-prettier""^4.0.0",
              "eslint-plugin-vue""^8.0.3",
              "husky""^8.0.0",
              "lint-staged""^13.2.2",
              "prettier""^2.4.1",
          }
              

          新增配置文件

          • .eslintrc.js (eslint的配置文件)
          • .eslintignore(eslint的忽略配置文件)
          • .prettier.js (prettier的配置文件)
          • commitlint.config.js (commitlint的配置文件)

          commitlint.config.js

                
                module.exports = {
          extends: ["@commitlint/config-conventional"],
          };

          eslintrc.js

                
                // .eslintrc.js
          module.exports = {
              root: true,
              // 指定代碼的運行環(huán)境
              env: {
                  browser: true,
                  node: true,
                  es6: true
              },
              plugins: ['vue''prettier'],
              extends: [
                  // 繼承 vue 的標準特性
                  'plugin:vue/essential',
                  'eslint:recommended',
                  // 避免與 prettier 沖突
                  'plugin:prettier/recommended'
              ],
              parserOptions: {
                  // 定義ESLint的解析器
                  parser: '@babel/eslint-parser',
                  sourceType: 'module'
              }
          };

          處理husky

          配置完成之后記得npm i,這時候會添加.husky目錄,給這個目錄添加文件
          commit-msg

                
                #!/usr/bin/env sh
          "$(dirname -- "$0")/_/husky.sh"

          npx commitlint -e $GIT_PARAMS

          pre-commit

                
                #!/usr/bin/env sh
          "$(dirname -- "$0")/_/husky.sh"

          npm run lint-staged

          參考目錄如下:

          663d845434d6ffd46b0d8b3a9050b7a0.webp

          運行

          這樣配置完后續(xù)正常commit就會觸發(fā)eslint和commitlint,保證提交的代碼和commit的規(guī)范。如果報錯要修復(fù)完問題才可以正常提交,而且代碼都會進行格式化,保證每個人提交的風(fēng)格都一致。其他的不展開贅述。

          接入storybook

          初始化storybook

          在原先的項目中執(zhí)行命令初始化storybook的相關(guān)配置和依賴

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

          選擇webpack5和安裝依賴

          f9e9ec8799503f9038baf98cacd0ad17.webp

          自動運行storybook

          8581655ba5d10b651a1d37e23530d92f.webp

          打開瀏覽器,我們可以看到storybook的界面

          b04fd26f68d88c3cc0db1d09bb5bb644.webp

          來走讀一下創(chuàng)建出來的storybook demo文件,我們以Button.stories.js這個文件為例

                
                import MyButton from './Button.vue';

          // More on how to set up stories athttps://storybook.js.org/docs/vue/writing-stories/introduction
          export default {
            title'Example/Button',
            component: MyButton,
            tags: ['autodocs'],
            render: (args, { argTypes }) => ({
              props: Object.keys(argTypes),
              components: { MyButton },
              template: '<my-button @onClick="onClick" v-bind="$props" />',
            }),
            argTypes: {
              backgroundColor: { control: '
          color' },
              size: {
                control: { type: '
          select' },
                options: ['
          small', 'medium', 'large'],
              },
            },
          };

          // More on writing stories with args: https://storybook.js.org/docs/vue/writing-stories/args
          export const Primary = {
            args: {
              primary: true,
              label: '
          Button',
            },
          };

          export const Secondary = {
            args: {
              label: '
          Button',
            },
          };

          export const Large = {
            args: {
              size: '
          large',
              label: '
          Button',
            },
          };

          export const Small = {
            args: {
              size: '
          small',
              label: '
          Button',
            },
          };

          走讀下default這個配置

          • title: 該故事在 Storybook 應(yīng)用的側(cè)邊欄中的名稱。路徑式的名稱表示故事的層級結(jié)構(gòu)。在這個例子中,"Example" 是一個文件夾,"Button" 是這個文件夾下的一個故事。

          • component: 這是你想要展示的組件,Storybook 將使用它來自動生成文檔頁(如果你啟用了這個功能)。

          • tags: 這是一個標簽數(shù)組,你可以添加任何你喜歡的標簽來幫助你組織和查找你的故事。

          • render: 這是一個函數(shù),返回一個 Vue 組件的配置對象,用于定義如何渲染故事。在這個例子中,所有的 args 和 argTypes 都被傳遞給 MyButton 組件,你可以在 Storybook 的 UI 中調(diào)整它們的值。

          • argTypes: 這個對象定義了每個 arg 的控件和其他配置。在這個例子中,backgroundColor 的控件是一個顏色選擇器,size 的控件是一個下拉列表,選項包括 'small'、'medium' 和 'large'。

            • control: 用于指定參數(shù)控件的類型,例如:'color'、'select'、'range' 等。
            • options: 用于指定 'select' 類型控件的選項。

          新建story

          新建一個story,用于編寫我們自己的組件的story,如下,這個是我們新創(chuàng)建的stories文件,我們引入自己的vue2組件

          56ef16e4995aa079dba75d7836a14529.webp

          先照貓畫虎寫一個配置

                
                import CommonNoFound from "../../../components/commonPage/FcommonNoFound/index.vue";

          export default {
              title"components/FcommonNoFound",
              component: CommonNoFound,
              tags: ["autodocs"],
          };

          export const NoFound = {};

          看下效果

          8a3c266df69a3eba8941165c51355320.webp 4221ad0e66c2fe11f3f3621f60a06f2e.webp

          這是因為我們的組件里面用了vue-i18n,用$t然后storybook識別不到,這里我們就需要解決這個vue-i18n的問題

          解決vue-i18n

          我們需要在.storybook/preview.js中設(shè)置vue-i18n相關(guān)的配置 看下原先的文件

                
                /** @type { import('@storybook/vue').Preview } */
          const preview = {
              parameters: {
                  actions: { argTypesRegex: "^on[A-Z].*" },
                  controls: {
                      matchers: {
                          color: /(background|color)$/i,
                          date: /Date$/,
                      },
                  },
              },
          };

          export default preview;

          我們在這個文件的基礎(chǔ)上增加vue-i18n的配置 要預(yù)先安裝好vue vue-i18n,然后同i18n初始化一致實例化i18n實例然后設(shè)置到storybook中 看下代碼

                
                import Vue from "vue";
          import VueI18n from "vue-i18n";
          import en from "../lang/en_us";
          import zh from "../lang/zh_cn";

          Vue.use(VueI18n);

          const i18n = new VueI18n({
              locale"en",
              messages: {
                  zh: {
                      language"簡體中文",
                      ...zh,
                  },
                  en: {
                      language"English",
                      ...en,
                  },
              },
          });

          /** @type { import('@storybook/vue').Preview } */
          const preview = {
              parameters: {
                  actions: { argTypesRegex"^on[A-Z].*" },
                  controls: {
                      matchers: {
                          color/(background|color)$/i,
                          date/Date$/,
                      },
                  },
              },
              decorators: [
                  (Story) => ({
                      components: { Story },
                      template'<story v-bind="$props" />',
                      i18n,
                  }),
              ],
          };

          export default preview;

          在這個例子中,decorators 數(shù)組中的函數(shù)接收一個 Story 參數(shù),這個參數(shù)表示當前的故事組件。然后,我們創(chuàng)建了一個模板,這個模板包含一個 story 組件,并且使用 v-bind 來綁定故事的屬性。最后,我們在 components 對象中指定了 Story 組件。

          這樣,你的故事組件就會接收到 i18n 實例,并且會正確地被渲染。

          解決環(huán)境變量問題

          vue代碼里面會有環(huán)境變量,但是在storybook的環(huán)境中這個環(huán)境變量是沒有的,所以我們需要手動設(shè)置這個環(huán)境變量,保證我們的代碼可以正常運行 這時候我們需要一個包,我們安裝dotEnv這個包

                
                npm i dotenv --save-dev

          然后我們新建一個.env的文件,在這個文件中我們設(shè)置我們需要的環(huán)境變量,例如我的這個

                
                VUE_APP_WEB_ENV=dev

          然后就是在我們的storybook的mainjs或者preview.js配置中引入dotEnv的配置即可

                
                require("dotenv").config();
          // 其他配置

          解決請求代理問題

          我們在常見的vue項目中,本地開發(fā)會經(jīng)常用proxy的配置來解決跨域問題,轉(zhuǎn)發(fā)接口,當我們的組件中依賴了接口的話,這時候我們可以同樣模擬一下這個proxy的過程 我們需要安裝proxy的包

                
                npm install http-proxy-middleware --save-dev

          然后我們在.storybook目錄下新建一個middleware.js,然后同我們的webpack配置一樣補充我們需要的proxy,這里補充下筆者接手的這個項目的配置

                
                const { createProxyMiddleware } = require("http-proxy-middleware");

          module.exports = function expressMiddleware(router{
              let env = process.env.VUE_APP_WEB_ENV || "dev";
              const comonApi = require(`../config/api/${env}.js`);

              if (comonApi && Object.keys(comonApi).length) {
                  Object.keys(comonApi).forEach((e) => {
                      const apiBase = comonApi[e].apiBase;
                      const apiRoot = comonApi[e].apiRoot;
                      if (apiBase && apiRoot) {
                          router.use(
                              apiBase,
                              createProxyMiddleware({
                                  target: apiRoot,
                                  changeOrigintrue,
                                  pathRewrite: {
                                      [`^${apiBase}`]: "",
                                  },
                              })
                          );
                      }
                  });
              }
          };

          引入組件庫

          組件會依賴一些UI庫的組件,比如筆者用到的element ui,在storybook中需要引入這些element的組件,這里我們在.storybook/preview.js中引入element,參考如下

                
                import ElementUI from "element-ui";
          import "element-ui/lib/theme-chalk/index.css";

          Vue.use(ElementUI);

          解決樣式問題

          引入組件會有一些樣式,所以我們也需要處理下引入的css,類似webpack一樣增加對應(yīng)的loader,我們安裝對應(yīng)的loader

                
                npm install --save-dev sass-loader style-loader css-loader

          然后在.storybook/main.js文件中補充對應(yīng)的webpack配置

                
                const config = {
          webpackFinal: async (config, { configType }) => {
          // 處理 SCSS 文件
          config.module.rules.push({
          test: /\.scss$/,
          use: ["style-loader", "css-loader", "sass-loader"],
          });
          return config;
          },
          }

          接入commitizen

          組件庫之前的各種commit都是五花八門,這里為了規(guī)范commit信息,然后方便后面生成changelog,我們這里需要一個命令式的commit提交工具,筆者選擇了用commitizen,先安裝好這個包

                
                npm install --save-dev commitizen cz-conventional-changelog 

          然后在我們的package.json中增加對應(yīng)的script或者配置

                
                "scripts": {
                  "commit""git-cz",
                  ...
          },
          "config": {
                  "commitizen": {
                      "path""cz-conventional-changelog"
                  }
          },

          我們正常運行git add git commit就會觸發(fā)下面這個,然后根據(jù)實際情況填寫內(nèi)容

          785168d046307b13e0788f53829dcbf7.webp

          全部填寫完成之后就會生成對應(yīng)的commit記錄

          0f5148556ab1be907fa64e5dc0307035.webp

          生成changelog(可忽略)

          下面的自動升級版本的命令會自動生成changelog,實際接入中可以不用看這一部分 changelog就是根據(jù)我們的commit生成變更的日志,嘗試效果的話我們需要引入新的包

                
                npm install --save-dev conventional-changelog-cli

          在package.json中增加一個生成changelog的腳本,通過這個命令我們可以手動生成changelog

                
                {
            "scripts": {
              "changelog""conventional-changelog -p angular -i CHANGELOG.md -s"
            }
          }

          自動升級版本

          我們需要在準備發(fā)版的時候,更新package.json中的版本號,生成changelog文件,提交更改和創(chuàng)建標簽,這里我們需要用到第三方的工具包,這里用了standard-version

                
                npm install --save-dev standard-version

          增加腳本

                
                "scripts": {
            "release""standard-version"
          }

          當我們準備發(fā)新版本的時候,就跑一下這個命令npm run release,這時候就會幫我們自動增加一個commit做上面說的事情,比如這樣的commit

          339905226cd2a65238e45bd47fcafcb8.webp

          因為standard-version這個包內(nèi)置了生成changelog的包,所以我們不需要額外引入上面部分提到的conventional-changelog-cli

          接入單元測試

          單元測試的作用

          組件庫會被多個項目引用,每個項目的情況不一樣,可能需要根據(jù)本身項目的需求對組件進行修改或者增加一些改動,原則上改動都是要向下兼容的,每次組件庫更新理論上引用的項目都要跟著更新,驗證下改動是否沒問題,但是考慮到每次都要讓各個項目來驗證這種成本比較高,所以引入單元測試,組件的創(chuàng)建人在寫完組件之后,順便根據(jù)自己的場景補充好單元測試。下一個修改的人如果要修改這個組件,修改完成之后,需要保證原先的單元測試都跑通過才可以,另外需要補充單元測試。

          編寫單元測試

          我們在編寫好vue組件之后,如果要對當前這個組件編寫單元測試,可以在組件當前的目錄(初定是和組件放在同一個目錄下)創(chuàng)建對應(yīng)的一個 xx.spec.js文件,然后在文件中編寫對應(yīng)的單元測試,可以參考項目中已有的單元測試文件,如下。

                
                import { shallowMount } from "@vue/test-utils";
          import CommonNoFound from "./index";
          import { i18n, localVue } from "../../../jest.setup";

          describe("FcommonNoFound.vue", () => {
              it("can find list text", () => {
                  const wrapper = shallowMount(CommonNoFound, { localVue, i18n });
                  expect(wrapper.text()).toContain("Lost...");
              });

              it("can find pageNotFound text", () => {
                  const wrapper = shallowMount(CommonNoFound, { localVue, i18n });
                  expect(wrapper.text()).toContain("頁面已飛到太空外");
              });
          });

          運行與調(diào)試單元測試

          我們在package.json中增加一個命令,用于運行單元測試

                
                {
              "scripts": {
                  "test""jest"
              }
          }

          運行單個單測文件,可以單獨驗證單測文件是否運行通過,可以在命令后面補充對應(yīng)的單測文件路徑

                
                npm run test components/commonPage/FcommonNoFound/commonNoFound.spec.js
          a050afc40fbe9035f1fe62e7694333f7.webp

          運行結(jié)果,可以看到哪些通過哪些不通過,如果不通過會有報錯信息,根據(jù)報錯信息調(diào)整單測

          全量運行,結(jié)果展示同上

                
                npm run test
          4a515617268ee2d37bc9c19b7e9e6d09.webp

          單元測試卡點

          有了單元測試之后,我們需要在每次提交合并的時候保證所有的單元測試都跑通過,否則就不給合并代碼,相當于對每次合碼都做一次卡點,減少一些改動無法向下兼容,導(dǎo)致引用組件的項目出現(xiàn)問題。

          • 可以考慮使用自動化測試在每次PR或者MR的時候做運行所有的單元測試,檢查測試覆蓋率之類的
          • 如果無法做自動化測試的話,可以考慮每次PR或者MR的時候要求提交人補充本地運行所有單元測試的結(jié)果,這里就可以通過配置一些MR或者PR提交的模板,要求代碼提交人按這種格式來提交,補充好單元測試的截圖之類的

          合并代碼策略

          指定分支合并到對應(yīng)的分支,例如合并到release或者master分支,這時候會有預(yù)置的模板,按照模板補充說明然后提交PR進行審核
          以下是筆者的搞的一個合碼的模板,要求提交人按這種格式去填寫

          9acd03b4fbfc3c9c70d5b3a501dd5d95.webp

          組件預(yù)覽部署

          在上面的步驟中我們已經(jīng)接入了storybook,可以在本地預(yù)覽,如果我們要單獨把storybook單獨部署一個到一個站點,其他開發(fā)可以直接打開去看

          增加構(gòu)建命令

          在package.json中增加命令,構(gòu)建出storybook的產(chǎn)物

                
                "scripts": {
              "build-dev""storybook build -o dist",
          }

          項目部署

          配合運維,綁定好分支,然后當指定分支有merge或者Push的時候,觸發(fā)構(gòu)建,這個根據(jù)自己團隊的情況去部署即可。
          筆者部署完的大概樣子如下:

          d9e0d0e67ac34f7df45a427d50c8fd45.webp

          總結(jié)

          當前這版優(yōu)化對現(xiàn)有的組件庫做了一次大的調(diào)整,本身不涉及具體組件的改動,只是規(guī)范和優(yōu)化整個流程,方便前端開發(fā)接入和使用等,但是還存在不少的優(yōu)化空間,比如以submodule接入的方式,筆者覺得不是很好,還是偏向于用npm包的方式,但是由于內(nèi)部還沒有搞自建的npm源,加上不少項目都已經(jīng)在用submodule的方式了,所以暫時不做這種處理。


          瀏覽 83
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  天天射天天日天天操 | 成人区精品一区二区婷婷 | 最新版中文官网资源 | 欧美日韩一级电影 | 午夜色婷婷 |