More articles
preface
Recently, I helped the company build a UI component library. The main considerations are as follows:
- Precipitation of business components to avoid the loss of high quality components
- Document, reduce the focus on the internal code of the component, and use the component through documentation demo and exposed property methods
- Install components through NPM for better management of projects & components
- Improve front-end component packaging capability
technology
At present, the vast majority of the company’s projects are vue2.0 + element2.x, which have been very mature. Most likely, vue3.0 will not be upgraded. The technologies and tools are as follows:
- vue2.x
- markdown-it
- Markdown-it-container: Processes markDown blocks
- Markdown – it – front – matter: processing
front matter
- Highlight.js: Code highlighting
- Standard-version: indicates the version and log
- Rimraf: deletes a file
- Gulp: Packages SCSS
- Cp -cli: copies files
directory
UI presentation platform content is under SRC and component-related content is under Packages, separating the UI document platform from UI components
QJD - UI ├ ─ ─ build │ ├ ─ ─ md_loader markdown file parsing | | - lib. Config. Js component packaging configuration | | -- utils. Js tool ├ ─ ─ packages based & business component │ ├ ─ ─ │ ├─ SRC │ ├─ Components UI Component │ ├─ Demo - Block-vue md Demo template │ ├─ consts │ ├─ Exercises The slider. Js navigation routing information │ ├ ─ ─ docs components document │ │ ├ ─ ─ button. The md │ ├ ─ ─ pages UI platform page │ ├ ─ ─ the test test code │ ├ ─ ─ the router routing │ └ ─ ─ styles │ ├─ Bass Exercises ─ Utils UI │ ├─ Bass Exercises ─ GetTeStroutes.jsCopy the code
Markdown related
The document is written using Markdown, and the corresponding. Md file can be matched according to the routing information, as follows:
{
path: `/main/${e.key}`.name: e.key,
// Log in the root directory
component: resolve= > e.key == 'CHANGELOG' ? require([`.. /.. /CHANGELOG.md`], resolve) : require([`@/docs/${e.key}.md`], resolve)
}
Copy the code
markdown-it
We need to use markdown-it and other tools to help vUE identify.md files:
// index.js
const markdown = require('markdown-it');
const hljs = require('highlight.js');
const md = markdown({
html: true.typographer: true.// Handle code highlighting
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return `<pre class="hljs" v-pre>
<code>
${hljs.highlight(lang, str, true).value}
</code>
</pre>`
} catch (error) {
console.log('error:' + error)
}
}
return `<pre class="hljs">
<code>${md.utils.escapeHtml(str)}</code>
</pre>`}})const html = md.render(src)
return (
`<template>\n
<div class="markdown">
${html}
</div>\n
</template>\n
`
)
Copy the code
markdown-it-container
Parse the demo using markdown-it-Container and insert it through the written slot (SRC /components/demo-block)
// containerjs
const container = require('markdown-it-container')
module.exports = md= >{ md.use(... createDemo('demo'))}function createDemo(kclass) {
return [container, kclass, {
validate: params= > params.trim().match(/^demo\s*(.*)$/),
render: function (tokens, idx) {
const token = tokens[idx]
const info = token.info.replace('demo'.' ').trim()
let desc = info ? `<div class="demo_desc" slot="desc" >${info}</div>` : null
if (token.nesting === 1) {
return `<demo-block>
${desc}
<div slot="code">
`
}
return '</div></demo-block>\n'}}}]// Then introduce it in index.js
const markdown = require('markdown-it');
const containers = require('./container');
constmd = markdown({... }).use(containers)Copy the code
And then you can write your demo like this in your.md file
< div class = "demo_block" > < / div > : I'm code: : demo ` ` ` HTML <! -- I'm code --> ' '::Copy the code
markdown-it-front-matter
Handle front matter with markdown-it-front-matter
use(require('markdown-it-front-matter'), function (fm) {
const data = fm.split('\n')
data.forEach((item, index) = > data[index] = item.split(':'))
data.map(item= > result += `<span>${kinds[item[0]]}: ${item[1]}</span>`)});return (
`<template>\n
<div class="markdown">
<div class="demo-info">
${result}
</div>
${html}
</div>\n
</template>\n
`
)
Copy the code
You can then write front matter like this in the.md file
---
author: xxx
create: xxxx-xx-xx
update: xxxx-xx-xx
---
Copy the code
Pull away script&style
Without CSS and JS, a MarkDown file is only allowed to have one script and one style
/ / pull away
const scriptRe = /^
(?>;
const styleRe = /^
(?>;
md.renderer.rules.html_block = (tokens, idx) = > {
const content = tokens[idx].content
if (scriptRe.test(content.trim())) {
scriptContent = content;
return ' '
} if(styleRe.test(content.trim())) {
styleContent = content;
return ' '
} else {
return content
}
}
return (
`<template>\n
<div class="markdown">
<div class="demo-info">
${result}
</div>
${html}
</div>\n
</template>\n
${scriptContent}
${styleContent}
`
)
Copy the code
Configure the loader
Finally, configure the loader
{
test: /\.md$/,
use: [
{ loader: 'vue-loader' },
{
loader: require.resolve('./md_loader')}}]Copy the code
After the above configuration is complete, you can write the documentation in the.md file
Complete md_loader
// index.js
const markdown = require('markdown-it');
const hljs = require('highlight.js');
const kinds = require('./consts');
const containers = require('./container');
const scriptRe = /^
(?>;
const styleRe = /^
(?>;
module.exports = function (src) {
let result = ' ', scriptContent = ' ', styleContent = ' ';
const md = markdown({
html: true.typographer: true.highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return `<pre class="hljs" v-pre>
<code>
${hljs.highlight(lang, str, true).value}
</code>
</pre>`
} catch (error) {
console.log('error:' + error)
}
}
return `<pre class="hljs">
<code>${md.utils.escapeHtml(str)}</code>
</pre>`
}
})
.use(containers)
.use(require('markdown-it-front-matter'), function (fm) {
const data = fm.split('\n')
data.forEach(item= > {
item = item.split(':')
result += `<span>${kinds[item[0]]}: ${item[1]? item[1] : The '-'}</span>`})}); md.renderer.rules.html_block =(tokens, idx) = > {
const content = tokens[idx].content
if (scriptRe.test(content.trim())) {
scriptContent = content;
return ' '
} if (styleRe.test(content.trim())) {
styleContent = content;
return ' '
} else {
return content
}
}
const html = md.render(src)
return (
`<template>\n
<div class="markdown">
<div class="demo-info">
${result}
</div>
${html}
</div>\n
</template>\n
${scriptContent}
${styleContent}
`)}// container.js
const container = require('markdown-it-container')
module.exports = md= >{ md.use(... createDemo('demo'))}function createDemo(kclass) {
return [container, kclass, {
validate: params= > params.trim().match(/^demo\s*(.*)$/),
render: function (tokens, idx) {
const token = tokens[idx]
const info = token.info.replace('demo'.' ').trim()
let desc = info ? `<div class="demo_desc" slot="desc" >${info}</div>` : null
if (token.nesting === 1) {
return `<demo-block>
${desc}
<div slot="code">
`
}
return '</div></demo-block>\n'}}}]// consts.js
module.exports = {
author: 'the writer'.create: 'Creation time'.update: 'Update time'
}
Copy the code
UI components
Components are written uniformly under Packages, styles are written under theme-default/ SRC, and styles are packaged through gulp
We will dynamically match the.vue component under packages when packaging, so each component must create the corresponding file and create the index.js component and expose the component, otherwise it will lead to the failure of importing on demand, even if the component like ButtonGroup, Although the implementation is under the button folder, we still need to create the button-group folder
entry
// Dynamically match components
exports.getEntries = () = > {
const componentsContext = requireContext('.. /packages'.true./\.vue$/, __dirname).keys();
const defaultCom = { "qjd-ui": "./packages/index.js" }; // Batch packing
let coms = {}; // Store each component for individual packaging
componentsContext.forEach(item= > {
const keys = item ? item.split('\ \') : [];
const key = keys[keys.length - 1]? keys[keys.length -1].split('. ') [0] : ' ';
if (key) {
coms[key] = `./packages/${key}`; }});return{... defaultCom, ... coms } }// Configure entry in WebPack
{
entry: utils.getEntries(),
}
Copy the code
output
Our components will eventually be published to NPM, so unlike normal packaging, our entry will need to configure the library
{
output: {
path: path.resolve(__dirname, '.. /lib'),
publicPath: '/'.filename: '[name].js'.// [Entry name].js
library: '[name]'.// The name exposed
libraryTarget: 'umd'.// Usually choose UMD, which supports various environments
umdNamedDefine: true}}Copy the code
externals
Because it is a component that is twice encapsulated based on the base component of Element-UI, we usually do not package third-party tools when packaging. If we use the element-UI in the project, we need to install the element-UI and introduce the corresponding components in the project
externals: {
vue: {
root: 'Vue'.commonjs: 'vue'.commonjs2: 'vue'.amd: 'vue'
},
'element-ui': 'ELEMENT'
}
Copy the code
Bulk packaging
This is our entry file for bulk packaging components
import Button from './button'
const components = {
Button,
}
const install = function (Vue) {
if (install.installed) return
Object.keys(components).forEach(key= > {
Vue.component(components[key].name, components[key])
})
}
if (typeof window! = ='undefined' && window.Vue) {
install(window.Vue)
}
constAPI = { install, ... components }module.exports = API
Copy the code
A separate package
Here is the entry of each component when we separately package. To facilitate the introduction of components on demand, inject the install method for each group price separately:
// install.js
export default el => el.install = Vue= > Vue.component(el.name, el);
// Take button as an example
import install from '.. /utils/install'
install(Button)
Copy the code
Complete configuration
// lib.config.js
const path = require('path');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const utils = require('./utils')
module.exports = {
entry: utils.getEntries(),
output: {
path: path.resolve(__dirname, '.. /lib'),
publicPath: '/'.filename: '[name].js'.library: '[name]'.libraryTarget: 'umd'.umdNamedDefine: true
},
externals: {
vue: {
root: 'Vue'.commonjs: 'vue'.commonjs2: 'vue'.amd: 'vue'
},
'element-ui': 'ELEMENT'
},
resolve: {
extensions: ['.js'.'.vue']},module: {
loaders: [{
test: /\.vue$/,
loader: 'vue-loader'.options: {
loaders: {
css: 'vue-style-loader! css-loader'.sass: 'vue-style-loader! css-loader! sass-loader'
},
postLoaders: {
html: 'babel-loader'}}}, {test: /\.js$/,
loader: 'babel-loader'.exclude: /node_modules/
}, {
test: /\.css$/,
use: [
'style-loader'.'css-loader'] {},test: /\.(gif|jpg|png|woff|svg|eot|ttf)\?? . * $/,
loader: 'url-loader? limit=8192'}},plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.optimize.UglifyJsPlugin({
uglifyOptions: {
ie8: false.output: {
comments: false.beautify: false,},mangle: {
keep_fnames: true
},
compress: {
warnings: false.drop_console: true}}}),new CopyWebpackPlugin([
{
from: `./packages`.to: `./packages`.ignore: [
'theme-default/**']}]),]}// util.js
exports.getEntries = () = > {
const componentsContext = requireContext('.. /packages'.true./\.vue$/, __dirname).keys();
const defaultCom = { "qjdui": "./packages/index.js" }; // Batch packing
let coms = {}; // Store each component for individual packaging
componentsContext.forEach(item= > {
const keys = item ? item.split('\ \') : [];
const key = keys[keys.length - 1]? keys[keys.length -1].split('. ') [0] : ' ';
if (key) {
coms[key] = `./packages/${key}`; }});return{... defaultCom, ... coms } }Copy the code
Packaging orders
{
"clean": "rimraf lib"."build": "node build/build.js"."build:theme": "gulp build --gulpfile packages/theme-default/gulpfile.js && cp-cli packages/theme-default/lib lib/theme-default"."build:packages": "webpack --config build/lib.config.js"."build:ui": "npm run clean && npm run build:theme && npm run build:packages"
}
Copy the code
UI display platform packaging results in dist folder, command:
npm run build
Copy the code
The package result of the component library is stored in the lib folder.
npm run build:ui
Copy the code
Commit specification
A good commit specification can improve team development efficiency. Using Angular’s Commit specification as a template, Commitizen can help constrain its own commit specification, and log generation will depend on the commit content
Type type
type | describe |
---|---|
feat | New features |
fix | Fix the bug |
refactor | Code refactoring |
docs | The document |
test | The test code |
pref | To optimize the |
chore | Changes to the build process or ancillary tools |
The module
Modules involved in each commit
describe
Description of each change
The sample
A full commit looks like this:
Git commit -m 'feat(button): add disabled effect to a button 'git commit -m 'docs(button): Document the component of a button'Copy the code
Version & Log
Use standard-version to control the version and generate the log. If the commit type is feat and fix, the log will appear, corresponding to the Features and Bug Fixes modules, respectively
- Major: Indicates the major version
- Minor: Indicates a minor version
- Patch: Revised version
- NPM run release — 1.0.0: This command is executed. The custom version is 1.0.0
- NPM run release:100: Run this command to upgrade to 2.0.0 if the current version is 1.0.0
- NPM run release:010: Run this command to upgrade to 1.1.0 if the current version is 1.0.0
- NPM run release:001: If the current version is 1.0.0, the version will be upgraded to 1.0.1
After the preceding command is executed, the system performs three actions: version generation, tag generation, and log generation. The logs are stored in changelog. md
The installation
npm install qjd-ui –save
use
Introduced the CDN
<! -- css --> <link rel="stylesheet" href="https://unpkg.com/qjd-ui/lib/theme-default/index.css">/ <! -- HTML --> <div id="app"> <xw-button> Default button </xw-button> <xw-button type="primary"> Main button </xw-button> <xw-button </xw-button> <xw-button type="info"> Information button </xw-button> <xw-button type="warning"> Warning button </xw-button> < xw - button type = "error" > dangerous button < / xw - button > < / div > <! -- js --> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/qjd-ui/lib/qjdui.js"></script> <script> new Vue({ el: '#app' }) </script>Copy the code
Batch is introduced into
import qjdui from 'qjd-ui'
import 'qjd-ui/lib/theme-default/index.css'
Vue.use(qjdui)
Copy the code
According to the need to introduce
Method 1 (Manually import components & CSS)
import Button from 'qjd-ui/lib/button'
import 'qjd-ui/lib/theme-default/button.css'
Vue.component(Button.name, Button)
Copy the code
Way 2
/ / install Babel - plugin - import
yarn add babel-plugin-import -D
/ / configuration Babel. Config. Js
{
"plugins": [["import",
{
libraryName: 'qjd-ui'.customStyleName: (name) = > {
return `qjd-ui/lib/theme-default/${name}.css`; },},]]}Copy the code
The import mode is as follows after the configuration is complete:
import { Button, Input } from 'qjd-ui';
Vue.component(Button.name, Button)
Vue.component(Input.name, Input)
Copy the code
or
Vue.use(Button)
Vue.use(Input)
Copy the code
conclusion
At present, only the first version is completed, referring to the emelent- UI structure and CSS processing