「前端」尚妝 UI 元件庫工程實踐(weex vue)

尚妝產品技術刊讀發表於2017-12-10

本文來自尚妝前端團隊南洋

發表於尚妝github部落格,歡迎訂閱!

前言

尚妝大前端團隊使用 weex 進行三端統一開發有一段時間了,截止本文發表「達人店」APP大部分頁面都已經用 weex 進行了重構,在此期間也積累了一些基礎元件和業務元件。

之前維護元件的方式是在達人店專案的工程內維護一個 components 資料夾,隨日常開發迭代,並行需求與開發人員的增多,這種維護方式也暴露出一些問題。

1、開發人員可以隨意跟隨需求開發修改 components 內的元件,破壞約定好的規範,或埋入 bug。

2、定義元件缺少規範,比如在某個需求開發中, A 開發人員覺得這個功能可以抽離成元件,就直接在 components 內定義並使用,但實際卻是偽需求,用了一次就再也沒有人使用,造成 components 元件庫的部分冗餘。

3、元件抽離過程無法協同使用,比如 A 開發同學切了個特性分支 feature/A,並根據專案抽了個通用元件 ComponentA,B 開發切了個特性分支 B,也想使用這個 ComponentA 元件,但此時兩人在不同分支,程式碼並不能共享。

4、。。。

基於上述不便之處,我們嘗試將 components 抽離出來,放到內部私有 npm 倉庫中以 npm 包的形式去維護。

也就是我們將 spon-ui(內部元件庫名稱)作為單獨的一個專案去維護,加以約束形成元件庫開發規範,能有效的解決上述問題。

此文就是此次抽離過程的一些實踐,包含了元件的除錯文件除錯npm使用元件 釋出等內容。當然 weex 的語法同 vue,這些實踐也同樣適用於 vue。

1、元件庫的除錯

先看下 spon-ui 元件庫專案的目錄結構。

|- spon-ui
||-- build
||-- docs
||-- examples
||-- packages
|||--- weex-field
||||---- index.js
||||---- field.vue
||||---- example.vue
||||---- readme.md
||||---- package.json
||-- src
複製程式碼
  • build 中存放一些指令碼執行檔案,用於工程的除錯、釋出。
  • docs 中存放文件除錯的指令碼,生成一個文件除錯伺服器。
  • examples 中存放元件除錯的指令碼,生成一個元件除錯伺服器。(不存放元件例子)
  • packages 存放真實元件,以及元件的文件和例子。
  • src 存放元件可以使用的公共方法。

元件的除錯

examples 資料夾內就是元件除錯的相關指令碼,這個資料夾在組建開發過程中是不需要變動的,只是定義了除錯伺服器的一些邏輯。並不包含真實的元件例子。

而真實的例子存放在相應元件目錄下,example.vue 中引入當前目錄下的 vue 元件,除錯時是針對 example.vue 進行除錯,因為除錯元件需要模擬使用元件的場景(改變傳入值,使用者互動等)。

當執行 npm run dev:components 時,開發同學會看到瀏覽器開啟頁面:

「前端」尚妝 UI 元件庫工程實踐(weex vue)

選擇想要除錯的元件,比如說 weex-dialog ,進入到 weex-dialog 的除錯介面。

「前端」尚妝 UI 元件庫工程實踐(weex vue)

開發同學此時修改 packages 目錄中的 weex-dialog 的元件內容,會實時看到修改內容,進行除錯。

console 中輸出二維碼

另外我們開發的元件是基於 weex 的,意味著開發的元件需要支援三端(iOS android H5),所以在 console 中會列印當前元件js的二維碼,用於 native 除錯。

「前端」尚妝 UI 元件庫工程實踐(weex vue)

如何在console中輸出二維碼也是個小trick,首先利用js的二維碼庫將資源生成二維碼圖,然後利用console輸出背景圖的機制列印二維碼。

console.log("%c", "padding:75px 80px 75px;line-height:160px;background:url(" + base64 + ") no-repeat;background-size:160px");
複製程式碼

整個除錯頁面是通過單頁面的形式展現的,使用 vue-router 進行路由控制,weex 也支援 vue-router ,所以這個單頁面在 native 中也能良好執行。

自動生成元件相關資訊

在每次執行 npm run dev:components 命令時,會根據 packages 目錄下的元件自動生成 nav-list.js 檔案,這個索引檔案用來定義 vue-router 的路由資訊,以及除錯主頁的元件列表。這樣做可以完全將除錯過程抽離成黑盒,開發人員只需關注 packages 目錄下的開發即可。

const routes = navList.map((item) => {
  const path = item.path;
  return {
    path,
    // 需要加vue字尾,不然webpack會將examples下的所有檔案都require一下
    component: require('examples/' + item.exampleRequire + '.vue'),
  };
});
routes.push({
  path: '/',
  component: require('./app.vue'),
})
複製程式碼

// 元件列表也通過 nav-list.js 渲染
<spon-cell-group>
   <spon-cell
     v-for="(page, jndex) in item.list"
     :key="jndex"
     :title="page.title"
     :is-link="true"
     @click="changePage(page)"
   ></spon-cell>
</spon-cell-group>
複製程式碼

webpack require 動態的資源

本文使用 webpack 3.x.x

上節提到的 require 動態的模組時,如果不表明檔案型別,webpack會將該目錄下所有資源都 require 一遍,造成的問題是如果目錄下有某型別的檔案,而又沒有使用對應的loader,在編譯過程就會報錯。上節中如果不加 .vue 字尾, webpack會將 examples 目錄下所有資源都require一遍。

所以在定義各路由的component時,需要加上 vue 字尾,查詢vue檔案。

component: require('examples/' + item.exampleRequire + '.vue'),
  };
複製程式碼

webpack的文件說明在 https://webpack.js.org/guides/dependency-management/#require-context

在 webpack 的官方文件裡列出了動態 require 的原理,對於 require("./template/" + name + ".ejs"); 含表示式的引用,webpack 解析此處的 require,得到兩個資訊:

1、 目錄為 ./template 2、匹配規則為 /^.*\.ejs$/

然後 webpack 會根據這兩個資訊得到一個 context module,這個模組包含了 ./template 目錄下所有以 .ejs 為字尾的模組。

{
    "./table.ejs": 42,
    "./table-row.ejs": 43,
    "./directory/folder.ejs": 44
}
複製程式碼

還有一個 require.context() 方法可以自定義動態引用的規則,文件中也有示例,官網給出了一個基於此的demo,引入一個目錄中所有符合規則的模組。

function importAll (r) {
  r.keys().forEach(r);
}

importAll(require.context('../components/', true, /\.js$/));
複製程式碼

文件的除錯

元件開發的差不多了,就要編寫相應的文件,方便同事小夥伴使用,執行 npm run dev:docs 會開啟文件除錯伺服器,方便開發同學編寫文件。

「前端」尚妝 UI 元件庫工程實踐(weex vue)

文件伺服器的邏輯放在 docs 目錄下,同樣與元件程式碼解耦,左側的元件資訊動態取自 packages 目錄下的元件資訊,右側的元件預覽直接使用 examples 目錄下的元件除錯邏輯,中間的部分取自 元件中的 readme.md 檔案。

整個文件應用也是基於 vue + vue-router 開發。

<div class="nav-bar-container">
    <page-nav></page-nav>
</div>

<div class="document-area-container markdown-body">
    <router-view></router-view>
</div>

<div class="mock-phone-container">
    <page-preview :component-name="componentName"></page-preview>
</div>
複製程式碼

<router-view> 就是對應的路由所展示的文件內容,相應的在定義路由資訊時需要確定路由以及路由所對應的 readme.md 路徑。

const routes = navList.map((item) => {
  const path = item.path;
  return {
    path,
    component: require('mds/' + item.mdRequire + '.md'),
  };
});

const router = new VueRouter({
  routes,
});
複製程式碼

markdown 轉換 vue

在引用元件時使用了 .md 字尾,這裡是採用了 vue-markdown-loader 餓了麼出品的loader。這個loader還是藉助vue-loader,首先會將 md 的內容轉換成 html ,然後再轉換成 vue 所需要的單檔案形式給vue-loader。

var renderVueTemplate = function(html, wrapper) {
  // 本文作者注
  // 傳入的html是根據 markdown外掛將md轉換而來
  var $ = cheerio.load(html, {
    decodeEntities: false,
    lowerCaseAttributeNames: false,
    lowerCaseTags: false
  });

  ...
  // 本文作者注
  // 將html轉換成 vue-loader 所需的字串形式
  result =
    `<template><${wrapper}>` +
    $.html() +
    `</${wrapper}></template>\n` +
    output.style +
    '\n' +
    output.script;

  return result;
};
複製程式碼
var result =
    'module.exports = require(' +
    loaderUtils.stringifyRequest(
      this,
      '!!vue-loader!' +
        markdownCompilerPath +
        '?raw!' +
        filePath +
        (this.resourceQuery || '')
    ) +
    ');';
    
  // 本文作者注
  // 將轉換好的字串傳給 vue-loader
  return result;
複製程式碼

2、基於 npm 指令碼實現工程化

"scripts": {
    "bootstrap": "npm i",
    "dev:components": "node build/bin/dev-entry.js",
    "dev:docs": "node build/bin/docs-dev-entry.js",
    "build:docs": "node build/bin/docs-build.js",
    "pub:docs": "npm run bootstrap && npm run clean && node build/bin/release.js",
    "pub:components": "node build/bin/prepublish.js && lerna publish --skip-npm --skip-git && node build/bin/publish.js",
    "clean": "rm -rf docs/dist && rm -rf docs/deploy",
    "add": "node build/bin/add.js"
  },
複製程式碼

本專案中將所有常用的命令都進行了抽離,開發同學使用的命令最後暴露出4個:

npm run dev:components 元件的除錯
npm run dev:docs 文件的除錯
npm run pub:docs 文件的釋出
npm run pub:components 元件的釋出
複製程式碼

推薦看阮一峰的部落格 npm scripts 使用指南 ,將npm 指令碼很細緻的介紹了一遍。

自動生成腳手架

npm run add 會自動新增一個元件所需的腳手架資訊,方便開發同學新增新元件。

這裡推薦使用 json-templater/string 模組處理 string 模板的問題。

腳手架檔案中的某些值會根據元件名的不同而不同,根據元件名自動生成對應的腳手架內容,更加方便開發。

npm link

元件在本地開發完成了,例子和文件都編寫完畢,但不知在真實專案中使用會不會出現奇怪bug。

最原始的方法可以將元件複製到專案中的npm包中進行真實除錯。

當然 npm 也提供了 方法專門解決這種問題。

1、首先在 spon-ui 元件庫的根目錄執行 npm link

2、回到專案目錄,執行 npm link spon-ui ,兩條命令就能將專案中原本引用的spon-ui 對映到本地的spon-ui目錄中去。

3、npm unlink 取消軟鏈。

3、原始碼依賴

上節提到的npm 指令碼並沒有提到元件打包的流程,因為如果在元件這層就進行打包,會增加一些webpack的冗餘程式碼,增加位元組,而且這個元件庫目前完全屬於內部專案使用,打包環境在專案中就存在,沒有必要提前進行打包。

所以釋出出去的元件包就是packages下的所有元件,專案中所依賴的都是元件的原始碼,稱為原始碼依賴

要做到原始碼依賴,需要修改業務專案中(非本元件專案)的babel的配置。排除掉 spon-ui 元件

 module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules(?!\/.*(spon-ui).*)/,
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      ],
    },
複製程式碼

滴滴有篇webpack 應用編譯優化之路有講到原始碼依賴所帶來的好處。

4、釋出元件

我們使用了 lerna 來管理元件的釋出,lerna 有兩種釋出方式,一種是一個專案的所有元件作為一個釋出包,還有一種可以將一個專案中的多個元件分別釋出。

我們使用了第一種,即所有元件統一成一個釋出包。這種方式發揮不出 lerna 的威力,但是作為釋出前的版本號管理還是不錯的。未來如果要將各個元件單獨釋出,改一下配置就ok。

「前端」尚妝 UI 元件庫工程實踐(weex vue)

版本管理

測試版本的管理

在前文就提到過目前元件庫的開發還是依賴於需求的迭代,小團隊沒有人專門開發元件,元件的開發會跟隨需求的迭代而迭代。

那麼在前期元件變更需求通過評審會後,就會跟隨專案正式進入開發流程。專案開發會區分測試環境和預發全量環境,那麼元件的版本號也需要區分測試環境和全量環境。

npm publish --tag

介紹一下 publish 的 tag,釋出的 npm 包預設會有一個 latest 標籤,每次執行 npm publish 都會自動將 tag 設定為 latest,也可以理解為穩定版,所以我們要做的是再新增一個 tag

npm publish --tag dev

這個命令代表新增一個名為 dev 的tag,並將此次釋出的版本號貼上 dev 標籤。

執行 npm dist-tag ls spon-ui 可以檢視當前的標籤所對應的版本號資訊。

➜  spon-ui git:(master) npm dist-tag ls spon-ui
dev: 0.1.0-12
latest: 0.1.0
複製程式碼

在專案中安裝spon-ui的時候,根據情況分別執行

npm i spon-ui@dev
npm i spon-ui@latest
複製程式碼

5、npm5 package-lock.json

元件釋出完成了,就可以在專案中使用了,我們從npm3.x更新到了npm5,但是發現執行 npm i 時的現象跟網路上的科普文不太一致。

有提到不管怎麼修改package.json檔案,重複執行npm i,npm都會根據lock檔案描述的版本資訊進行下載。

也有提到重複npm i時,npm會不顧lock的資訊,根據package.json中的包Semantic versioning 版本資訊下載更新模組(lock貌似沒啥用了)。

查閱資料得知,自npm 5.0版本釋出以來,npm i的規則發生了三次變化。

1、npm 5.0.x 版本,不管package.json怎麼變,npm i 時都會根據lock檔案下載

https://github.com/npm/npm/issues/16866 這個 issue 控訴了這個問題,明明手動改了package.json,為啥不給我升級包!然後就導致了5.1.0的問題...

2、5.1.0版本後 npm install 會無視lock檔案 去下載最新的npm

然後有人提了這個issue https://github.com/npm/npm/issues/17979 控訴這個問題,最後演變成5.4.2版本後的規則。

3、5.4.2版本後 https://github.com/npm/npm/issues/17979

大致意思是,如果改了package.json,且package.json和lock檔案不同,那麼執行npm i時npm會根據package中的版本號以及語義含義去下載最新的包,並更新至lock。

如果兩者是同一狀態,那麼執行npm i都會根據lock下載,不會理會package實際包的版本是否有新。

總結

以上就是我們將UI元件從專案中遷移出來單獨以npm包的形式去維護的實踐過程,不完美還有待時間的考驗,希望其中的一些內容能幫助到大家。

相關文章