本文來自尚妝前端團隊南洋
發表於尚妝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
時,開發同學會看到瀏覽器開啟頁面:
選擇想要除錯的元件,比如說 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,得到兩個資訊:
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
會開啟文件除錯伺服器,方便開發同學編寫文件。
文件伺服器的邏輯放在 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。
版本管理
測試版本的管理
在前文就提到過目前元件庫的開發還是依賴於需求的迭代,小團隊沒有人專門開發元件,元件的開發會跟隨需求的迭代而迭代。
那麼在前期元件變更需求通過評審會後,就會跟隨專案正式進入開發流程。專案開發會區分測試環境和預發全量環境,那麼元件的版本號也需要區分測試環境和全量環境。
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包的形式去維護的實踐過程,不完美還有待時間的考驗,希望其中的一些內容能幫助到大家。