背景:为了避免重复造轮子,很有必要开发一个通用组件库,方便重复利用。本文是采用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.jsimport 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.jsimport { 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.jsconst 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)}