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

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

發表於尚妝github博客,歡迎訂閱!html

前言

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

以前維護組件的方式是在達人店項目的工程內維護一個 components 文件夾,隨平常開發迭代,並行需求與開發人員的增多,這種維護方式也暴露出一些問題。vue

一、開發人員能夠隨意跟隨需求開發修改 components 內的組件,破壞約定好的規範,或埋入 bug。java

二、定義組件缺乏規範,好比在某個需求開發中, A 開發人員以爲這個功能能夠抽離成組件,就直接在 components 內定義並使用,但實際倒是僞需求,用了一次就再也沒有人使用,形成 components 組件庫的部分冗餘。node

三、組件抽離過程沒法協同使用,好比 A 開發同窗切了個特性分支 feature/A,並根據項目抽了個通用組件 ComponentA,B 開發切了個特性分支 B,也想使用這個 ComponentA 組件,但此時兩人在不一樣分支,代碼並不能共享。android

四、。。。webpack

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

也就是咱們將 spon-ui(內部組件庫名稱)做爲單獨的一個項目去維護,加以約束造成組件庫開發規範,能有效的解決上述問題。github

此文就是這次抽離過程的一些實踐,包含了組件的調試文檔調試npm使用組件 發佈等內容。固然 weex 的語法同 vue,這些實踐也一樣適用於 vue。

一、組件庫的調試

先看下 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 時,開發同窗會看到瀏覽器打開頁面:

選擇想要調試的組件,好比說 weex-dialog ,進入到 weex-dialog 的調試界面。

開發同窗此時修改 packages 目錄中的 weex-dialog 的組件內容,會實時看到修改內容,進行調試。

console 中輸出二維碼

另外咱們開發的組件是基於 weex 的,意味着開發的組件須要支持三端(iOS android H5),因此在 console 中會打印當前組件js的二維碼,用於 native 調試。

如何在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,獲得兩個信息:

一、 目錄爲 ./template 二、匹配規則爲 /^.*\.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 會開啓文檔調試服務器,方便開發同窗編寫文檔。

文檔服務器的邏輯放在 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;
複製代碼

二、基於 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 也提供了 方法專門解決這種問題。

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

二、回到項目目錄,執行 npm link spon-ui ,兩條命令就能將項目中本來引用的spon-ui 映射到本地的spon-ui目錄中去。

三、npm unlink 取消軟鏈。

三、源碼依賴

上節提到的npm 腳本並無提到組件打包的流程,由於若是在組件這層就進行打包,會增長一些webpack的冗餘代碼,增長字節,並且這個組件庫目前徹底屬於內部項目使用,打包環境在項目中就存在,沒有必要提早進行打包。

因此發佈出去的組件包就是packages下的全部組件,項目中所依賴的都是組件的源碼,稱爲源碼依賴

要作到源碼依賴,須要修改業務項目中(非本組件項目)的babel的配置。排除掉 spon-ui 組件

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

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

四、發佈組件

咱們使用了 lerna 來管理組件的發佈,lerna 有兩種發佈方式,一種是一個項目的全部組件做爲一個發佈包,還有一種能夠將一個項目中的多個組件分別發佈。

咱們使用了第一種,即全部組件統一成一個發佈包。這種方式發揮不出 lerna 的威力,可是做爲發佈前的版本號管理仍是不錯的。將來若是要將各個組件單獨發佈,改一下配置就ok。

版本管理

測試版本的管理

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

那麼在前期組件變動需求經過評審會後,就會跟隨項目正式進入開發流程。項目開發會區分測試環境和預發全量環境,那麼組件的版本號也須要區分測試環境和全量環境。

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
複製代碼

五、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的規則發生了三次變化。

一、npm 5.0.x 版本,無論package.json怎麼變,npm i 時都會根據lock文件下載

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

二、5.1.0版本後 npm install 會無視lock文件 去下載最新的npm

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

三、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包的形式去維護的實踐過程,不完美還有待時間的考驗,但願其中的一些內容能幫助到你們。

相關文章
相關標籤/搜索