19年目標:消滅英語!我新開了一個公衆號記錄一個程序員學英語的歷程javascript
有提高英語訴求的小夥伴能夠關注公衆號:csenglish 程序員學英語,天天花10分鐘交做業,跟我一塊兒學英語吧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(內部組件庫名稱)
做爲單獨的一個項目去維護,加以約束造成組件庫開發規範,能有效的解決上述問題。程序員
此文就是這次抽離過程的一些實踐,包含了組件的調試、文檔調試、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
複製代碼
examples 文件夾內就是組件調試的相關腳本,這個文件夾在組建開發過程當中是不須要變更的,只是定義了調試服務器的一些邏輯。並不包含真實的組件例子。
而真實的例子存放在相應組件目錄下,example.vue
中引入當前目錄下的 vue 組件,調試時是針對 example.vue
進行調試,由於調試組件須要模擬使用組件的場景(改變傳入值,用戶交互等)。
當執行 npm run dev:components
時,開發同窗會看到瀏覽器打開頁面:
選擇想要調試的組件,好比說 weex-dialog ,進入到 weex-dialog 的調試界面。
開發同窗此時修改 packages 目錄中的 weex-dialog 的組件內容,會實時看到修改內容,進行調試。
另外咱們開發的組件是基於 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 3.x.x
上節提到的 require 動態的模塊時,若是不代表文件類型,webpack會將該目錄下全部資源都 require 一遍,形成的問題是若是目錄下有某類型的文件,而又沒有使用對應的loader,在編譯過程就會報錯。上節中若是不加 .vue
後綴, webpack會將 examples 目錄下全部資源都require一遍。
因此在定義各路由的component時,須要加上 vue 後綴,查找vue文件。
component: require('examples/' + item.exampleRequire + '.vue'),
};
複製代碼
webpack的文檔說明在 webpack.js.org/guides/depe…
在 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,
});
複製代碼
在引用組件時使用了 .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;
複製代碼
"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 模板的問題。
腳手架文件中的某些值會根據組件名的不一樣而不一樣,根據組件名自動生成對應的腳手架內容,更加方便開發。
組件在本地開發完成了,例子和文檔都編寫完畢,但不知在真實項目中使用會不會出現奇怪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
複製代碼
組件發佈完成了,就能夠在項目中使用了,咱們從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文件下載
github.com/npm/npm/iss… 這個 issue 控訴了這個問題,明明手動改了package.json,爲啥不給我升級包!而後就致使了5.1.0的問題...
二、5.1.0版本後 npm install 會無視lock文件 去下載最新的npm
而後有人提了這個issue github.com/npm/npm/iss… 控訴這個問題,最後演變成5.4.2版本後的規則。
三、5.4.2版本後 github.com/npm/npm/iss…
大體意思是,若是改了package.json,且package.json和lock文件不一樣,那麼執行npm i
時npm會根據package中的版本號以及語義含義去下載最新的包,並更新至lock。
若是二者是同一狀態,那麼執行npm i
都會根據lock下載,不會理會package實際包的版本是否有新。
以上就是咱們將UI組件從項目中遷移出來單獨以npm包的形式去維護的實踐過程,不完美還有待時間的考驗,但願其中的一些內容能幫助到你們。
19年目標:消滅英語!我新開了一個公衆號記錄一個程序員學英語的歷程
有提高英語訴求的小夥伴能夠關注公衆號:csenglish 程序員學英語,天天花10分鐘交做業,跟我一塊兒學英語吧