在前端項目的開發中,每每會根據業務需求,沉澱出一些項目內的UI組件/功能模塊(如下通稱組件) 等;這些組件初期只在同一個項目中被維護,並被該項目中的不一樣頁面或模塊複用,此時的組件逐步被完善,是一個只聚焦於功能和健壯性的成長期。javascript
隨着業務的發展,原來的項目可能不得不產生裂變,變成幾個類似但各有不一樣的項目 -- 好比在初始項目中積累經驗後,須要推廣到類似的業態上或根據不一樣大客戶的需求進行定製,這種狀況下每每很難理想化的保持各項目大版本或者後續發展進度的同步,只能逐漸各自發展。這時那些在一開始顯得八面玲瓏的「可複用組件」,每每就須要手忙腳亂的在各個項目中分頭維護,或是出現了意想不到的問題,須要從新規劃了。css
本文以 Vue 技術棧的前端項目爲例,嘗試簡單的探討一種抽象提取跨項目可複用組件的方法。html
關於同一組件在不一樣項目中的區別方面,以一個二次封裝 element-ui 中 el-date-picker 的 DateRange.vue
組件舉例:前端
所在項目 | 基礎組件庫 | 發現的表明性問題 |
---|---|---|
A | element-ui@v1.x |
|
B | ||
C | ||
D | ||
E | element-ui@v2.x |
|
F |
因爲種種緣由,幾個項目依賴的 UI 庫類似但並不相同;且項目體量過大、維護的團隊不一樣等等,都讓統一基礎組件庫變得🐔乎不可能~ 🐔 你太美,這就很尷尬了嘛~vue
$t
、$router
等和項目環境有關的依賴在某一個具體項目內,對組件只需引用其源碼便可;java
對於跨項目的通用組件庫,一種方法是在各項目內部維護一個指向組件庫源碼的子模塊(git 的 subtree 或 submodule),但這種方法維護比較麻煩,故不經常使用。node
另外一種咱們比較習慣的方式是經過 npm 安裝後直接引用組件的註冊名稱(package.json
中的 name
)。webpack
固然若是本身的組件多少仍是關乎業務邏輯、對外部的項目其實也沒那麼通用,而公司內部又維護有 npm 的鏡像,那麼選擇將其發佈到這個內部環境中也是能夠的。git
在 npmjs.com 上註冊用戶,或經過命令行:web
npm adduser
複製代碼
發佈前確認登陸:
npm login
複製代碼
發佈前手動更改 package.json ,或用命令行更新項目版本號,注意每次發佈的版本號不能相同:
npm version x.x.x
複製代碼
執行發佈:
npm publish
複製代碼
直接在命令行中打開項目主頁查看:
npm home [name]
複製代碼
更多的命令參見官方的完整文檔: docs.npmjs.com/cli-documen…
另外須要注意的是,正確配置 package.json 裏的 repository
字段,能夠在組件的 npm 主頁上顯示代碼倉庫的連接。
本例中選擇了 rollup 做爲打包工具:
假設組件庫結構規劃以下:
├─.babelrc
├─.eslintignore
├─.eslintrc.js
├─.gitignore
├─CHANGELOG.md
├─jest.config.js
├─package.json
├─README.md
├─postcss.config.js
├─rollup.config.js
├─dist/
├─example/
├─node_modules/
├─src/
├─__mocks__/
└─__tests__/
複製代碼
最小化的 npm scripts 以下:
// package.json
"scripts": {
"build": "rollup --config"
},
複製代碼
較基礎的 rollup 配置以下:
// rollup.config.js
import path from 'path';
import json from 'rollup-plugin-json';
import { uglify } from 'rollup-plugin-uglify';
import alias from 'rollup-plugin-alias';
import vue from 'rollup-plugin-vue';
import babel from 'rollup-plugin-babel';
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';
import nodeGlobals from 'rollup-plugin-node-globals';
import bundleSize from 'rollup-plugin-filesize';
import { eslint } from 'rollup-plugin-eslint';
import pkg from './package.json';
const pathResolve = p => path.resolve(__dirname, p);
const extensions = ['.js', '.vue'];
module.exports = {
input: 'src/index.js',
output: {
file: 'dist/bundle.min.js',
format: 'umd',
name: 'MyComponents',
globals: {
vue: 'Vue',
echarts: 'echarts',
lodash: 'lodash'
},
sourcemap: true
},
external: Object.keys(pkg.dependencies),
plugins: [
resolve({
extensions,
browser: true
}),
eslint({
extensions,
exclude: ['**/*.json'],
cache: true,
throwOnError: true
}),
bundleSize(),
commonjs(),
nodeGlobals(),
vue({
template: {
isProduction: !process.env.ROLLUP_WATCH,
compilerOptions: { preserveWhitespace: false }
},
css: true
}),
babel({
exclude: 'node_modules/**'
}),
alias({
'@': pathResolve('src')
}),
json(),
uglify()
]
};
複製代碼
關於該配置,簡要說明以下:
相關的語法轉換和語法檢查配置:
// .babelrc
{
"presets": [["env", { "modules": false }]],
"env": {
"test": {
"presets": [["env", { "targets": { "node": "current" } }]]
}
}
}
複製代碼
// .eslintignore
__tests__/*
*.css
複製代碼
// .eslintrc.js
module.exports = {
extends: [
"airbnb-base",
'plugin:vue/essential'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': 'error',
'space-before-function-paren': 'off',
'no-underscore-dangle': 'off',
'no-param-reassign': 'off',
'func-names': 'off',
'no-bitwise': 'off',
'prefer-rest-params': 'off',
'no-trailing-spaces': 'off',
'comma-dangle': 'off',
'quote-props': 'off',
'consistent-return': 'off',
'no-plusplus': 'off',
'prefer-spread': 'warn',
semi: 'warn',
indent: 'warn',
'no-tabs': 'warn',
'no-unused-vars': 'warn',
quotes: 'warn',
'no-void': 'off',
'no-nested-ternary': 'off',
'import/no-unresolved': 'off',
'no-return-assign': 'warn',
'linebreak-style': 'off',
'prefer-destructuring': 'off',
'no-restricted-syntax': 'warn'
},
parserOptions: {
parser: 'babel-eslint'
}
}
複製代碼
維護點收斂到了一個庫中,須要注意的是,相應的風險也高度集中了,可謂一損俱損一榮俱榮🐶。
因此單元測試也愈發重要起來,庫裏的組件或模塊,凡有條件的(好比 Vue 中的 directives 就沒那麼好作單元測試,但 filters 純函數很容易),想要讓各個項目的開發者小夥伴們放心大膽的統一引用,就應該無條件的買一送一,搭配完善的單元測試。
這裏以 jest 爲例,列舉其主要配置:
// jest.config.js
module.exports = {
modulePaths: [
'<rootDir>/src/'
],
moduleFileExtensions: [
'js',
'json',
'vue'
],
transform: {
'^.+\\.vue$': 'vue-jest',
'^.+\\.jsx?$': 'babel-jest'
},
transformIgnorePatterns: [
'/node_modules/'
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss)$': '<rootDir>/__mocks__/emptyMock.js'
},
snapshotSerializers: [
'jest-serializer-vue'
],
collectCoverage: true,
collectCoverageFrom: [
'<rootDir>/src/**/*.{js,vue}',
'!**/node_modules/**'
],
coveragePathIgnorePatterns: [
'<rootDir>/__tests__/'
],
coverageReporters: [
'text'
]
};
複製代碼
其中 emptyMock.js
是用來在用例中忽略對樣式的引用的:
// __mocks__/emptyMock.js
module.exports = {};
複製代碼
對應的 npm scripts:
"scripts": {
// ...
"test": "jest"
},
"pre-commit": [
"test"
],
複製代碼
這裏用 pre-commit 包實現了提交前先進行單元測試的鉤子功能。
關於 Vue 單元測試的更多內容請參考這篇文章。
光說不練假把式,雖然靜態語法也檢查了、單元測試也跑通了,仍是眼見爲實比較踏實,對其餘開發者也比較直觀;藉助 rollup-plugin-serve 等插件,能夠運行起一個最小配置的瀏覽器運行環境,人肉看看組件的實際表現。
在 npm scripts 中設置環境參數,分別對徹底通用的組件,及適用於特定類型項目的組件啓動 demo 頁面服務:
"scripts": {
// ...
"dev:common": "rollup --watch --config rollup.config.dev.js --environment PROJ_ENV:common",
"dev:A": "rollup --watch --config rollup.config.dev.js --environment PROJ_ENV:A",
"dev:B": "rollup --watch --config rollup.config.dev.js --environment PROJ_ENV:B",
},
複製代碼
適當修改 rollup 原配置,增長單獨的 rollup.config.dev.js,根據環境參數啓動服務:
import serve from 'rollup-plugin-serve';
import postcss from 'rollup-plugin-postcss';
import baseConfig, {
pathResolve,
browserGlobals
} from './rollup.config.base';
...
const PORT = 3001;
const PROJECT = process.env.PROJ_ENV;
export default {
input: pathResolve(`example/${PROJECT}/main.js`),
output: {
file: pathResolve(`example/${PROJECT}/dist/example.bundle.${PROJECT}.js`),
format: 'umd',
name: 'exampleApp',
globals: browserGlobals,
sourcemap: false
},
plugins: [
postcss(),
...baseConfig.plugins,
serve({
port: PORT,
contentBase: [
pathResolve(`example/${PROJECT}`)
]
})
]
};
複製代碼
這裏假設簡單粗暴的都把組件引用到 App.vue 中,暫不考慮分路由等狀況,對應的 example 目錄的結構可能以下:
+---A
| | App.vue
| | index.html
| | main.js
| |
| \---dist
| example.bundle.A.js
|
+---B
| | App.vue
| | index.html
| | foo.css
| | main.js
| |
| \---dist
| example.bundle.B.js
|
\---common
| App.vue
| index.html
| main.js
|
+---dist
| example.bundle.common.js
|
\---fonts
複製代碼
同時維護幾個同質化的前端項目時,不可避免的涉及到一些較通用的 UI組件/功能模塊 的狀況,將其集結後發佈到 npm 上,並輔以完善的單元測試和可運行的 demo 展現、必要的文檔,就能將維護組件的工做量大大減輕。
搜索 fewelife 關注公衆號
轉載請註明出處