背景:爲了不重複造輪子,頗有必要開發一個通用組件庫,方便重複利用。
本文是採用vue-lic3.0腳手改造而成的,使用vuepress做爲演示環境。css
而後咱們須要修改一下:刪除public目錄,添加packages,utils和lib文件,見以下:html
packages是用於放控件庫, utils是用於放一些通用工具(方法等),src用於整個控件的引用,lib用於放打包後的文件:
src下放以下內容vue
index.js是將全部控件引入,做爲打包的入口node
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.jswebpack
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: '翻譯次'}}git
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中加入一個使用多語言的方法,主要是這個方法是同樣的,因此提取到mixins
mixins/locale.jses6
import { t } from 'hui-pro/src/locale'; export default { methods: { t(...args) { return t.apply(this, args); } } };
以後控件庫中能夠直接引用mixis,t(), this.t()的形式使用了github
import Locale from 'hui-pro/src/mixins/locale'; mixins: [Locale] 使用 html中 {{t('aa.bb')}} js中使用 test () { this.t('aa.bb') }
接下來咱們還須要修改一些配置:
babel.config.jsweb
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
增長配置文件vue-router
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" }
在該環境下建立一個docs文件
這邊主要config.js和enhanceApp.js
config.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) }