背景:为了避免重复造轮子,很有必要开发一个通用组件库,方便重复利用。本文是采用 vue-lic3.0 脚手改造而成的,使用 vuepress 作为演示环境。
首先通过 vue 脚手架生产目录如下:
然后我们需要修改一下:删除 public 目录,添加 packages,utils 和 lib 文件,见如下:
packages 是用于放控件库,utils 是用于放一些通用工具(方法等),src 用于整个控件的引用,lib 用于放打包后的文件:src 下放如下内容
index.js 是将所有控件引入,作为打包的入口
import locale from ‘hui-pro/src/locale’; // 语言包
import ‘hui-pro/packages/theme/index.scss’; // 皮肤包,将主题抽离出来
const components = [] // 放控件库
const install = function(Vue, opts = {}) {
/* istanbul ignore if */
if (install.installed) return;
locale.use(opts.locale);
locale.i18n(opts.i18n);
components.map(component => {
Vue.component(component.name, component);
});
Vue.use(EllipsisDirective);
};
/* istanbul ignore if */
if (typeof window !== ‘undefined’ && window.Vue) {
install(window.Vue);
}
export default {
version: ‘0.1.0’,
locale: locale.use,
i18n: locale.i18n,
install,
… // 控件
}
src 下 locale 是用于管理多语言的,我们在开发控件的时候是不能讲中文写死,所以需要做一层多语言处理,而单纯的控件库有是没有引入 vue 实例,那就更不用说 i18n 了,所以这边就是通过自己翻译,引用项目工程中的 i18n 或者直接自己做切割翻译。如下:index.js
import defaultLang from ‘hui-pro/src/locale/lang/zh-CN’;
import Vue from ‘vue’;
import deepmerge from ‘deepmerge’;
import Format from ‘./format’;
const format = Format(Vue);
let lang = defaultLang;
let merged = false;
let i18nHandler = function() {
const vuei18n = Object.getPrototypeOf(this || Vue).$t;
if (typeof vuei18n === ‘function’ && !!Vue.locale) {
if (!merged) {
merged = true;
Vue.locale(
Vue.config.lang,
deepmerge(lang, Vue.locale(Vue.config.lang) || {}, { clone: true})
);
}
return vuei18n.apply(this, arguments);
}
};
export const t = function(path, options) {
let value = i18nHandler.apply(this, arguments);
if (value !== null && value !== undefined) return value;
const array = path.split(‘.’);
let current = lang;
for (let i = 0, j = array.length; i < j; i++) {
const property = array[i];
value = current[property];
if (i === j – 1) return format(value, options);
if (!value) return ”;
current = value;
}
return ”;
};
export const use = function(l) {
lang = l || lang;
};
export const i18n = function(fn) {
i18nHandler = fn || i18nHandler;
};
export default {use, t, i18n};
format.js 这个是通过自己本地切割字符串来找对应的翻译:‘xxx.yyy.ccc’ => xxx: {yyy: {ccc: ‘ 翻译次 ’}}
const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g;
/**
* String format template
* – Inspired:
* https://github.com/Matt-Esch/string-template/index.js
*/
export default function() {
/**
* template
*
* @param {String} string
* @param {Array} …args
* @return {String}
*/
function template(string, …args) {
if (args.length === 1 && typeof args[0] === ‘object’) {
args = args[0];
}
if (!args || !args.hasOwnProperty) {
args = {};
}
return string.replace(RE_NARGS, (match, prefix, i, index) => {
let result;
if (string[index – 1] === ‘{‘ && string[index + match.length] === ‘}’) {
return i;
} else {
result = Object.prototype.hasOwnProperty.call(args, i) ? args[i] : null;
if (result === null || result === undefined) {
return ”;
}
return result;
}
});
}
return template;
}
然后在 mixins 中加入一个使用多语言的方法,主要是这个方法是一样的,所以提取到 mixinsmixins/locale.js
import {t} from ‘hui-pro/src/locale’;
export default {
methods: {
t(…args) {
return t.apply(this, args);
}
}
};
之后控件库中可以直接引用 mixis,t(), this.t() 的形式使用了
import Locale from ‘hui-pro/src/mixins/locale’;
mixins: [Locale]
使用
html 中
{{t(‘aa.bb’)}}
js 中使用
test () {
this.t(‘aa.bb’)
}
接下来我们还需要修改一些配置:babel.config.js
const utilTypeList = []// 对外暴露工具库的列表
module.exports = function(api) {
let presets = [‘@vue/app’];
let plugins = [];
if (api.env(‘js’)) {
presets = [[‘@babel/preset-env’, { loose: true}]];
plugins = [
[
‘module-resolver’,
{
root: [‘hui-pro’],
alias: {
‘hui-pro/src’: ‘hui-pro/lib’
}
}
]
];
} else if (api.env(‘node’)) {
presets = [
[
‘@babel/preset-env’,
{
targets: {
node: true
}
}
]
];
}
// 按需加载 utils
for (let type of utilTypeList) {
plugins.push([
‘import’,
{
libraryName: `hui-pro/packages/utils/${type}`,
libraryDirectory: ”
},
`${type}`
]);
}
return {
presets,
plugins
};
};
需要安装 eslintrc.js 增加配置文件
var isDev = process.env.NODE_ENV === ‘development’;
module.exports = {
root: true,
env: {
mocha: true,
es6: true,
node: true,
browser: true
},
parserOptions: {
parser: ‘babel-eslint’
},
plugins: [‘vue’],
extends: [‘plugin:vue/strongly-recommended’, ‘@vue/prettier’],
rules: {
‘vue/html-indent’: 1,
‘no-console’: isDev
? 0
: [
‘error’,
{
allow: [‘warn’, ‘error’]
}
],
‘no-debugger’: isDev ? 0 : 2
},
globals: {
expect: true,
sinon: true
}
};
还需要增加 stylelintrc 效验
{
“plugins”: [“stylelint-prettier”, “stylelint-scss”],
“extends”: [
“stylelint-config-idiomatic-order”,
“stylelint-config-standard”,
“stylelint-config-prettier”
],
“rules”: {
“at-rule-no-unknown”: null,
“scss/at-rule-no-unknown”: true,
“prettier/prettier”: true
}
}
最后增加一下 vue 的配置
const path = require(‘path’);
const nodeExternals = require(‘webpack-node-externals’);
module.exports = {
publicPath: ”,
outputDir: ‘lib’,
assetsDir: ”,
filenameHashing: false,
css: {
extract: true,
sourceMap: false
},
productionSourceMap: false,
pages: {
index: {
// page 的入口
entry: ‘preview/main.js’,
// 模板来源
template: ‘preview/index.html’
}
},
devServer: {
port: 8999
},
configureWebpack() {
if (process.env.LIB_TYPE === ‘common’) {
return {
externals: [
{
vue: ‘vue’,
‘hui-pro/src/locale’: ‘hui-pro/lib/locale’
},
nodeExternals()
]
};
}
},
chainWebpack(webpackConfig) {
webpackConfig.when(process.env.LIB_TYPE === ‘umd’, config => {
config.output.umdNamedDefine(true);
});
webpackConfig.resolve.alias.set(‘hui-pro’, path.resolve(__dirname));
//svg
const svgRule = webpackConfig.module.rule(‘svg’);
svgRule.uses.clear();
svgRule
.oneOf(‘svg’)
.resourceQuery(/svg/)
.use(‘vue-svg-loader’)
.loader(‘vue-svg-loader’)
.end()
.end()
.oneOf(‘img’)
.resourceQuery(/img/)
.use(‘url-loader’)
.loader(‘url-loader’)
.options({
name: ‘img/[name].[hash:8].[ext]’
})
.end()
.end()
.oneOf()
.use(‘file-loader’)
.loader(‘file-loader’)
.options({
name: ‘fonts/[name].[ext]’
});
webpackConfig.module
.rule(‘fonts’)
.use(‘url-loader’)
.tap(options => Object.assign(options, { limit: 10}));
// map-picker 控件中,map/ 下的代码是 openlayer 相关代码,无需 babel 编译。
webpackConfig.module
.rule(‘js’)
.test(/\.js$/)
.exclude.add(path.resolve(__dirname) + ‘packages/map-picker/src/map’)
.end()
.use(‘babel-loader’);
//stylelint
webpackConfig
.plugin(‘stylelint’)
.use(‘stylelint-webpack-plugin’)
.tap(() => {
return [
{
configFile: ‘.stylelintrc’,
files: [‘packages/**/*.scss’],
emitErrors: true
}
];
});
}
};
当然如果使用 git 的话,使用 commitlint 来效验提交的代码
{
“printWidth”: 80,
“tabWidth”: 2,
“singleQuote”: true,
“trailingComma”: “none”,
“bracketSpacing”: true,
“semi”: true,
“useTabs”: false,
“proseWrap”: “never”,
“overrides”: [
{
“files”: [
“*.json”,
“.eslintrc”,
“.babelrc”,
“.stylelintrc”,
“.prettierrc”
],
“options”: {
“parser”: “json”,
“tabWidth”: 2
}
}
]
}
最后,需要修改 package.json
{
“name”: “hui-pro”,
“version”: “0.1.0-alpha.1”,
“scripts”: {
“lint”: “vue-cli-service lint packages src && stylelint packages/**/*.scss –fix”,
“cz:changelog”: “conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md”,
“dev”: “vue-cli-service serve”,
“lib:all”: “npm run lib:clean && npm run lib:i18n && npm run lib:common && npm run lib:umd && npm run lib:utils”,
“lib:clean”: “rimraf lib”,
“lib:common”: “cross-env LIB_TYPE=common vue-cli-service build –no-clean –target lib –formats commonjs –name hui-pro src/index.js”,
“lib:i18n”: “cross-env NODE_ENV=js babel src/locale –out-dir lib/locale”,
“lib:utils”: “cross-env NODE_ENV=node babel-node bin/generateIndex && cross-env NODE_ENV=js babel packages/utils –out-dir utils”,
“lib:umd”: “cross-env LIB_TYPE=umd vue-cli-service build –no-clean –target lib –formats umd-min –name hui-pro src/index.js”,
“vuepress:dev”: “vuepress dev docs”
},
“dependencies”: {
“jsencrypt”: “^2.3.1”,
“hui”: “^2.0.0-alpha.4”,
“moment”: “^2.24.0”,
“qs”: “^6.5.2”
},
“devDependencies”: {
“@babel/cli”: “^7.2.3”,
“@babel/node”: “^7.2.2”,
“@commitlint/cli”: “^7.2.0”,
“@commitlint/config-conventional”: “^7.5.0”,
“@vue/cli-plugin-babel”: “^3.3.0”,
“@vue/cli-plugin-eslint”: “^3.3.0”,
“@vue/cli-service”: “^3.3.0”,
“@vue/eslint-config-prettier”: “^4.0.1”,
“babel-eslint”: “^10.0.1”,
“babel-plugin-import”: “^1.11.0”,
“babel-plugin-module-resolver”: “^3.1.3”,
“commitizen”: “^3.0.5”,
“conventional-changelog”: “^3.0.5”,
“cross-env”: “^5.2.0”,
“cz-customizable”: “^5.2.0”,
“eslint”: “^5.8.0”,
“eslint-plugin-vue”: “^5.0.0”,
“highlightjs”: “^9.12.0”,
“husky”: “^1.1.1”,
“ip”: “^1.1.5”,
“lint-staged”: “^8.1.3”,
“node-sass”: “^4.11.0”,
“prettier-eslint”: “^8.8.2”,
“prettier-stylelint”: “^0.4.2”,
“sass-loader”: “^7.1.0”,
“stylelint”: “^9.10.1”,
“stylelint-config-idiomatic-order”: “^6.2.0”,
“stylelint-config-prettier”: “^5.0.0”,
“stylelint-config-standard”: “^18.2.0”,
“stylelint-prettier”: “^1.0.6”,
“stylelint-scss”: “^3.5.1”,
“stylelint-webpack-plugin”: “^0.10.5”,
“vue”: “^2.5.21”,
“vue-cli-plugin-changelog”: “^1.1.9”,
“vue-cli-plugin-lint-staged”: “^0.1.1”,
“vue-router”: “^3.0.1”,
“vue-svg-loader”: “^0.12.0”,
“vue-template-compiler”: “^2.5.21”,
“webpack-node-externals”: “^1.7.2”
},
“postcss”: {
“plugins”: {
“autoprefixer”: {}
}
},
“browserslist”: [
“Chrome > 48”,
“Edge > 16”,
“Firefox > 62”,
“IE > 9”,
“Safari > 11”
],
“commitlint”: {
“extends”: [
“@commitlint/config-conventional”
]
},
“config”: {
“commitizen”: {
“path”: “node_modules/cz-customizable”
}
},
“files”: [
“lib”,
“src”,
“packages”,
“utils”
],
“husky”: {
“hooks”: {
“commit-msg”: “commitlint -E HUSKY_GIT_PARAMS”,
“post-merge”: “npm install”,
“pre-commit”: “lint-staged”
}
},
“main”: “lib/hui-pro.common.js”
}
vuepress 演示环境
在该环境下创建一个 docs 文件
这边主要 config.js 和 enhanceApp.jsconfig.js:
var path = require(‘path’);
var ip = require(‘ip’);
var enNav = require(‘./links/en.nav.json’);
var zhNav = require(‘./links/zh.nav.json’);
var enSidebar = require(‘./links/en.sidebar.json’); // 英文版
var zhSidebar = require(‘./links/zh.sidebar.json’); // 中文版
var webpackConfig = {
module: {
rules: [
{
test: /\.js$/,
loader: ‘babel-loader’,
include: [path.resolve(__dirname, ‘../../packages’)]
}
]
}
};
module.exports = {
base: ‘/lib/’,
host: ip.address(),
port: ‘8099’,
title: ‘lib’,
description: ‘ 框架库 ’,
locales: {
‘/zh/’: {
lang: ‘zh-CN’,
title: ‘test-lib’,
description: ‘ 框架库 ’
},
‘/en/’: {
lang: ‘en-US’,
title: ‘test-lib’,
description: ‘Library’
}
},
head: [
[
‘link’,
{
rel: ‘icon’,
href: `favicon.ico`
}
]
],
themeConfig: {
editLinks: true,
docsDir: ‘docs’,
locales: {
‘/zh/’: {
selectText: ‘ 选择语言 ’,
label: ‘ 简体中文 ’,
nav: zhNav,
sidebar: zhSidebar
},
‘/en/’: {
selectText: ‘Languages’,
label: ‘English’,
nav: enNav,
sidebar: enSidebar
}
}
},
dest: ‘./docs/.vuepress/dist’,
demo: {
menu: [
{
title: ‘ 主页面 ’,
router: ‘/layout/page.html’,
icon: ‘h-icon-menu_app’
}
]
},
scss: {
sourceMap: true
},
sass: {
indentedSyntax: true
},
configureWebpack: webpackConfig,
overlay: {
warnings: true,
errors: true
},
chainWebpack: (webpackConfig, isServer) => {
webpackConfig.resolve.alias.set(
‘name1’,
path.resolve(__dirname, ‘../../’)
); // 配置别名路径
webpackConfig.resolve.alias.set(
‘name2’,
path.resolve(__dirname, ‘../../src’)
); // 配置别名路径
webpackConfig.module
.rule(‘eslint’)
.pre()
.exclude.add(/node_modules/)
.end()
.include.add(path.resolve(__dirname, ‘../../src’))
.add(path.resolve(__dirname, ‘../../packages’))
.end()
.test(/\.(vue|(j|t)sx?)$/)
.use(‘eslint-loader’)
.loader(‘eslint-loader’)
.options({
extensions: [‘.js’, ‘.jsx’, ‘.vue’, ‘.ts’, ‘.tsx’],
cache: true,
emitWarning: true,
emitError: true,
formatter: require(‘eslint/lib/formatters/codeframe’)
});
//svg
const svgRule = webpackConfig.module.rule(‘svg’);
svgRule.uses.clear();
svgRule
.oneOf(‘svg’)
.resourceQuery(/svg/)
.use(‘vue-svg-loader’)
.loader(‘vue-svg-loader’)
.end()
.end()
.oneOf(‘img’)
.resourceQuery(/img/)
.use(‘url-loader’)
.loader(‘url-loader’)
.options({
name: ‘img/[name].[hash:8].[ext]’
})
.end()
.end()
.oneOf()
.use(‘file-loader’)
.loader(‘file-loader’)
.options({
name: ‘fonts/[name].[ext]’
});
//stylelint
webpackConfig
.plugin(‘stylelint’)
.use(require.resolve(‘stylelint-webpack-plugin’))
.tap(() => {
return [
{
configFile: ‘.stylelintrc’,
files: [‘packages/**/*.scss’],
emitErrors: true
}
];
});
}
};
enhanceApp.js:
import name from ‘name2’
import routerGuard from ‘./router’
import *.css’
export default ({
Vue,
router
}) => {
routerGuard.use(router)
Vue.use(name)
}