前不久發佈了vc-popup組件集, 可是那時候徹底只是展現沒有如何使用的教程, 由於當時急於發佈出來, 實在不妥, 抱歉~javascript
既然是想本身東西可讓別人方便使用, 那就是打包成npm的包咯, 可是考慮vc-popup
僅僅是popup的組件集, 不是完整的組件庫, 因此不少時候用戶僅僅想使用某個popup
, 那麼其餘popup
也打包進去, 就浪費帶寬了, 因此須要一個每一個popup
單獨發佈到npm上去, 可是把依賴分開的時候以後開發就是帶來不便, 好比一個包更新了, 須要在另外一個手動更新, 爲了解決這個不便, 就是Lerna
登場的時候了, 用來方便開發和管理多個package
~css
可是本身實踐的過程中遇到一些問題和還有踩過一些坑, 因此在這裏記錄, 不過在開始以前, 先提一下vc-popup
的更新html
目前知道3種辦法, 若是在使用vscode
同窗, 使用cnpm
時候附帶--by=npm
能夠避免rg.exe
吃CPU的問題, 同理能夠設置爲--by=yarn
, 一些包使用cnpm
安裝有問題的時候, 就可使用讓cnpm
僅僅作下載, 安裝交給npm/yarn
vue
> npm i -g lerna
> cnpm i -g lerna --by=npm
> yarn global add lerna
複製代碼
在平常使用輸入命令的時候經常使用**&&加快效率, 本身輸入的次數多了, 才發現命令行相比於界面的優勢在於能夠串聯多個簡單的任務, 這個學期開始學習操做系統, 發現有個相似的名詞單道批處理系統和CMD批處理腳本**, 因此不言而喻咯~ 摁{enter}
鍵的時候想一想還有什麼命令能夠提早敲進去的java
還有一個優勢是, 命令是基於字符組合的肯定, 而非界面位置, 因此界面須要層疊, 命名不須要, 字符組合容量大node
> mkdir lerna-demo && cd lerna-demo && lerna init
複製代碼
前面由於須要穿插cnpm
因此安裝部分沒有串聯linux
因爲鍵盤右邊
shift
鍵位問題, 其實輸入&&的時候並非那麼順暢, 能夠經過AHK來作轉接, 我通常用筆記本鍵盤的時候按aand{space}
生成&&{space}
, 本身作的鍵盤, 由於調整過shift的位置就仍是按&&webpack
生成的查看生成的文件和目錄git
> ls
lerna.json package.json packages
複製代碼
分別查看文件內容es6
> head lerna.json && head package.json
{
"lerna": "2.5.1",
"packages": [
"packages/*"
],
"version": "0.0.0"
}
{
"devDependencies": {
"lerna": "^2.5.1"
}
}
複製代碼
而後新建目錄s
> cd packages && mkdir module-0 module-1 module-2
複製代碼
初始化package.json
> cd module-0 && npm init -y && cd ../module-1 && npm init -y && cd ../module-2 && npm init -y
Wrote to D:\DEV\Github\demo\lerna-demo\packages\module-0\package.json:
{
"name": "module-0",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
Wrote to D:\DEV\Github\demo\lerna-demo\packages\module-1\package.json:
{
"name": "module-1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Wrote to D:\DEV\Github\demo\lerna-demo\packages\module-2\package.json:
{
"name": "module-2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
複製代碼
初始化每一個module的index.js
> echo export default require('./package.json').name > index.js && cat index.js > ../module-0/index.js && cat index.js > ../module-1/index.js
複製代碼
而後在lerna-demo
新建index.js並編輯, 由於lerna會維護的是packages/*之間的依賴, 這裏的index.js
直接填寫module-2
的路徑
> cd ../.. && code index.js
複製代碼
const msg = require('./packages/module-2')
console.log(msg);
複製代碼
設置module
之間依賴, 如今require
的時候就能夠直接填寫對應的module
了
修改module-1的index.js
export default
require('./package.json').name
+ 'depends on [' + require('module-0').default + ']'
複製代碼
修改module-2的index.js
export default
require('./package.json').name
+ 'depends on [' + require('module-1').default + ']'
複製代碼
正常途徑如何添加npm包的依賴? yarn add modue-name
有什麼結果? 會從npm倉庫下載該包下來, 解壓到node_modules/module-name
, 而後處理packsage.json
依賴
那麼是否意味着Lerna
也會有這個相似的操做? 若是如今在開發module-2, 可是發現是module-1的bug, 把module-1的bug修改了, 須要發佈一下到npm, 而後module-2再更新module-1的依賴, 那麼能夠猜想Leran
經過某種手段讓這個更新同步自動化了
那麼基於猜想能夠進行驗證咯~ 先看手冊, 查查這個相似的操做是什麼~
看Example就很清晰知道的了, 那麼開始生成依賴
> lerna add module-0 --scope=module-1
> lerna add module-1 --scope=module-2
複製代碼
那麼能夠預計操做結果是, module-2的node_modules
有module-1
的文件夾,而且包含了其內容, module-1同理
那麼就能夠猜想如何實現了
是遞歸複製文件? 驗證一下 那麼如今修改一下module-0/index.js
而後,查看module-1/node_modules/module-0/index.js
, module-2
同理
把module-0/index.js
該爲以下
export default
require('./package.json').name + ' edited'
複製代碼
OK, 自動修改是同步更新的, 因此不是, 記得本身看linux的教程的時候有個工具是相關的, ln
, 可是我使用的是, 文件系統是NTFS
> ver
Microsoft Windows [Version 10.0.15063]
複製代碼
> ln --help
用法:ln [選項]... [-T] 目標 連接名 (第一種格式)
或:ln [選項]... 目標 (第二種格式)
或:ln [選項]... 目標... 目錄 (第三種格式)
或:ln [選項]... -t 目錄 目標... (第四種格式)
In the 1st form, create a link to TARGET with the name LINK_NAME.
In the 2nd form, create a link to TARGET in the current directory.
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.
Create hard links by default, symbolic links with --symbolic.
By default, each destination (name of new link) should not already exist.
When creating hard links, each TARGET must exist. Symbolic links
can hold arbitrary text; if later resolved, a relative link is
interpreted in relation to its parent directory.
--more
複製代碼
可是我用的是windows哦, 那麼猜想是經過windows的工具來實現的, 這個時候, 忽然我想到了屢次重裝系統在網上習得的技巧
> mklink --help
The syntax of the command is incorrect.
Creates a symbolic link.
MKLINK [[/D] | [/H] | [/J]] Link Target
/D Creates a directory symbolic link. Default is a file
symbolic link.
/H Creates a hard link instead of a symbolic link.
/J Creates a Directory Junction.
Link Specifies the new symbolic link name.
Target Specifies the path (relative or absolute) that the new link
refers to.
複製代碼
以前重裝系統多了, 會經過mklink把C盤的Users Juction 到D盤去, 以後每次恢復系統的時候一些程序的配置也就不用從新設置的了, 具體能夠參考網上的教程, 須要裝系統的時候操做的(文件解壓出來, 可是還沒重啓, 啓動安裝的時候), 記得好像不能在系統安裝以後操做
來驗證咯, 這時候就不能使用ls -all
來查看了(安裝了cygwin, 而且把bin目錄放在path裏了, 因此能夠用), 而是須要使用dir
因此, lerna在windows下是經過創建Juction來解決依賴包同步更新的問題~ linux的話, 也就不言而喻咯, 使用的應該是相似的工具ln
~
經過webpack
設置babel
轉碼, 而後經過lerna-demo/index.out.js
來驗證結果咯~
> webpack && node index.out.js
Hash: 3378d33b254656002585
Version: webpack 3.10.0
Time: 1031ms
Asset Size Chunks Chunk Names
index.out.js 4.14 kB 0 [emitted] main
[0] ./index.js 83 bytes {0} [built]
[1] ./packages/module-2/index.js 183 bytes {0} [built]
[2] ./packages/module-2/package.json 233 bytes {0} [built]
[3] ./packages/module-1/index.js 183 bytes {0} [built]
[4] ./packages/module-1/package.json 233 bytes {0} [built]
[5] ./packages/module-0/index.js 141 bytes {0} [built]
[6] ./packages/module-0/package.json 196 bytes {0} [built]
module-2 depends on [module-1 depends on [module-0 edited]]
複製代碼
結果就出來了, demo測試經過 再想一下改造vc-popup
的時候會可能出現什麼問題? Lerna解決的是在**packages/***的依賴, 也就是回到了例子的問題了
const msg = require('./packages/module-2')
console.log(msg);
複製代碼
這裏說明的是在不在packages文件夾內就不能享受依賴更新同步的福利了
任何對試驗性的改造, 都推薦新建分支裏面進行~
> git checkout -b split-packages
複製代碼
整體的思路, 大體上和lerna-demo
差很少, 區別在於會根據現有的目錄結構作相應的定製, 因此接下來會簡單講思路, 和遇到的問題.
> tree src
Folder PATH listing for volume Data
Volume serial number is 0007-86B5
D:\DEV\GITHUB\OPENSOURCE\VC-POPUP\SRC
├───components
│ ├───gesture-tile-press
│ ├───picker-view
│ ├───popup-base
│ ├───popup-bottom-menu
│ ├───popup-by-animation
│ ├───popup-calendar
│ ├───popup-center-menu
│ ├───popup-datetime-picker
│ ├───popup-dialog
│ ├───popup-dom-relative
│ ├───popup-img-viewer
│ ├───popup-over
│ ├───popup-picker
│ ├───popup-press-menu
│ ├───pull-down-refresh
│ ├───swipe-item
│ └───swipeplus
├───mixins
│ └───event
└───utils
複製代碼
須要拆成包的是src/components/popup-*
生成的包是vc-popup-*
, 入口是index.js
每一個包的安裝方式都是以下
import Vue from 'vue'
import popup from 'vc-popup-*'
Vue.use(popup)
複製代碼
拆包以後popup-*
包和包之間都是屬於外部依賴
在Vue.use
的時候的install
函數會先安裝依賴的popup
popup-*
目錄和package.json
popup
的entry[install.js]
webpack.pkg.conf.js
, 配置多入口popup-base
popup
經過在package.json
設置private: true
不發佈出去一共須要新建3個文件, 兩個是批處理屬性的, 一個就是webpack的配置, 要點在於多入口的配置, 比較簡單
在webpack打包的時候設置爲外部依賴? 而後popup內部直接使用import Vue from 'vue'
?
仍是應該依賴於執行Vue.use()
時候的Vue?
區別在因而否使用webpack來作項目構建(或者其餘打包工具, 不清楚webpack打包出來的模塊裏面聲明的外部依賴, 再經過其餘工具打包是否能夠兼容)
若是是經過Vue.use()
來注入vue的依賴, 那麼就能夠兼容那些不使用webpack作構建的項目, 通用性更好一些
我是無語線.........................................................................
可是, 若是注意到import popup from 'vc-popup-*'
, 哈哈哈, vue的導入不須要走webpack, 可是vc-popup-*
須要, 因此popup也是須要提供一個script+src
的版本才行, 因此仍是擁抱es6的模塊吧[尬笑]
一開始頭幾回測試都是發佈到npm以後再更新再測試的, 其實,並不須要, 在構建完成以後把更新以後的文件同步過去測試項目的node_modules
文件夾就行了, 效率提升很多, 這裏經過mklink
的junction
的方式同步就行了
不過使用自定義使用juction
的時候最好記錄到一下文檔, 把juction的設置寫到初始化的腳本里面, 最好編寫平臺兼容的, ntfs
使用mklink
, linux系的就使用ln
若是使用文件複製來實現同步的方式也是可行, 不過注意, 不要刪除node_modules/vc-popup-base
文件夾, 再複製該文件夾, 由於開dev server
的時候會由於沒法找到文件夾而中斷, 須要重開那種, 因此直接覆寫文件便可
popup-*
目錄, 和package.jsonvar fs = require('fs')
var path = require('path')
var readlineSync = require('readline-sync');
var deleteFolderRecursive = require('./utils').deleteFolderRecursive;
require('shelljs/global');
// 工具函數
function _path(str){
return path.resolve(__dirname, str)
}
function _package(name){
return `{ "name": "vc-${name}", "version": "0.0.0", "description": "vc-${name}", "main": "index.js", "scripts": { "test": "echo hasn't write test~" }, "author": "deepkolos", "license": "MIT", "dependencies": {} }`;
}
function initpkg(dirname){
var path = _path('../packages/'+dirname);
if( !fs.existsSync(path) ){
fs.mkdirSync(path);
fs.writeFileSync(path+'/package.json', _package(dirname));
}
}
// 開始
var deleteAllDir = readlineSync.question('是否清空packages下全部目錄? (y/n)');
var componentsDir = fs.readdirSync(
_path('../src/components'), {
encoding: "utf8"
});
deleteAllDir.toLowerCase() == 'y' &&
componentsDir.map((dirname) => {
deleteFolderRecursive(_path('../packages/'+dirname))
})
componentsDir.map(dirname => {
if(dirname.indexOf('popup-') === 0)
initpkg(dirname)
});
複製代碼
popup-*
目錄, entery[install.js]var fs = require('fs')
var render = require('json-templater/string')
var uppercamelcase = require('uppercamelcase')
var path = require('path')
var utils = require('./utils')
var p = function (str){
return path.resolve(__dirname, str);
}
var PACKAGE_PATH = p('../packages')
var DEPENDANCE_TEMPLATE = ` Vue.use(require('{{name}}'))`
var MAIN_TEMPLATE = ` const version = '{{version}}' const install = function (Vue, config = {}) { if (install.installed) return {{includeDepend}} require('{{self}}') } // auto install if (typeof window !== 'undefined' && window.Vue) { install(window.Vue) } export default { install, version } `
var BASE_MAIN_TEMPLATE = ` import { popupRegister, importVue } from '{{self}}' const version = '{{version}}' const install = function (Vue, config = {}) { if (install.installed) return {{includeDepend}} importVue(Vue) require('{{self}}').default.init(Vue) } // auto install if (typeof window !== 'undefined' && window.Vue) { install(window.Vue) } export default { install, version, popupRegister } `
function build_install(popupName){
var pkg = require(`${PACKAGE_PATH}/${popupName}/package.json`)
var version = pkg.version
var dependanceList = []
var tpl = popupName === 'popup-base'? BASE_MAIN_TEMPLATE: MAIN_TEMPLATE
pkg.dependencies &&
Object.keys(pkg.dependencies).forEach(function(depName){
dependanceList.push(render(DEPENDANCE_TEMPLATE, {
name: depName
}))
});
var template = render(tpl, {
includeDepend: dependanceList.join('\n'),
version: version,
self: `../../src/components/${popupName}`
})
fs.writeFileSync(p(`../packages/${popupName}/install.js`), template);
}
// 開始
utils.mapPkgList(function(popupName){
build_install(popupName)
})
複製代碼
配置webpack的多入口
const webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
externals: ['vue', 'vc-popup-base'], //設置外部依賴, 目前比較簡單
plugins: [
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
})
]
})
fs.readdirSync(path.resolve(__dirname, '../packages'));
webpackConfig.entry = {}
webpackConfig.output = {
path: path.resolve(__dirname, '../packages/'),
filename: `[name]/index.js`,
libraryExport: "default",
libraryTarget: "umd"
}
utils.mapPkgList(function(popupName){
webpackConfig.entry[popupName] =
path.resolve(__dirname, `../packages/${popupName}/install.js`)
})
module.exports = webpackConfig
複製代碼
剩下的步驟和lerna-demo
的同樣~
> lerna publish
複製代碼
done~
我看了mint-ui
, vant
, we-vue
, weex-ui
, cube-ui
, fish-ui
的大概構建思路
其中只有mint-ui
和weex-ui
從設計開始使用了lerna來拆包, vant
有packages
可是裏面的子目錄不包含package.json
可能還沒引用lerna吧
weex-ui
雖然是使用了lerna來拆包, 可是package.json
直接使用源碼做爲入口
感受mint-ui
能夠說是最標準的組件庫了, 在構建層面來講, 拆出來的包同時是包含源碼的, package.json
的出口是通過編譯的
而個人vc-popup
結構是一個混合體, 一開始沒有考慮作拆包, 後面加上的, 因此...拆出來的包僅僅包含通過編譯的文件...也沒有作js, css的分離
...
至於子組件的包是否有須要再走一遍編譯, cube-ui
滴滴團隊有後編譯的優化建議, 我的感受也合理, 組件在具體的vue項目是會再有一層編譯的, 因此組件發佈的時候僅僅發佈源碼便可, 不過我仍是以爲mint-ui
是最標準的方式~~
寫到後面彷佛有點不夠扣題了[faceplam], 不過也由於, 其實思路理清楚以後, 接下來的事情就是編碼和調試了
主要想問一下, 像一開始那裏穿插的各類小技巧, 和對事物的點滴理解, 不知道你們對這種方式的有什麼評價? 其實本身平時也有一些小理解, 可是不足以成文, 因此就打算後面把這些小知識插到相關的具體實例當中去, 若是你們感受前面部分還不錯的話就點贊, 我打算後面都使用這種小知識分享的風格~
但願你們給個人文章提提建議~ 主要是分享的思路上面, 或者對實踐的總結上面有什麼好的方法或者思路, 指導指導~
vc-popup使用的文檔還沒完善, 這裏給本身寫下篇文章的藉口~