在前端项目的开发中,每每会根据业务需求,沉淀出一些项目内的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 关注公众号
转载请注明出处