To share with you the process of creating Vue component library, for your reference, the project address is here:
Github.com/gaoljie/vue…
The structure of the project is as follows
Build (Webpack configuration) lib (Pack file location) - button (load component location on demand) - index. js-theme (component style) - - base.css (common style) - - button.css(component style) - index.js (global reference entry) SRC (project file) - Assets (public resources) - Components (vue components) - button - button.vue (component logic) - button.scss (Component style) -- button.test.js (component test) -- button.story.js (component use case) -- index.js (component entry) -- directives (vue directive command) -- locale (Internationalization) - mixins (vue mixins) - styles (styles) - variables (index. SCSS (variable entry) - color. SCSS (color variable) - - Vendors (Public styles, style resets) - index.scss (style entry) - utils (public methods) - index.js (Project entry)Copy the code
Initialize the project
Create a project
mkdir vue-uikit
cd vue-uikit
mkdir src
Copy the code
Initialize the git
git init
Copy the code
Create a.gitignore file in the project root directory
node_modules/
Copy the code
Initialize the NPM
yarn init
Copy the code
Install the vue
yarn add vue -D
Copy the code
We install vUE in devDependencies because our component library depends on the vUE package installed by the user, and we do not need to package our own components with vUE.
Install webpack
yarn add webpack webpack-cli webpack-merge -D
Copy the code
formatting
Formatting uses esLint and prettier
yarn add eslint prettier eslint-config-prettier eslint-plugin-jest eslint-plugin-vue husky pretty-quick -D
- Husky (Pre-Commit tool)
- Pretty -quick (formatting git changed with prettier)
- Eslint-plugin-jest, eslint-plugin-vue, eslint-plugin-prettier esLint-plugin-jest, eslint-plugin-vue, eslint-plugin-prettier
Add the appropriate configuration to package.json
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged && eslint --ext .js,.vue src"
}
},
"eslintConfig": {
"env": {
"node": true
},
"extends": [
"eslint:recommended",
"plugin:jest/recommended",
"plugin:vue/recommended",
"plugin:prettier/recommended"
]
},
Copy the code
Add.eslintrc to the root directory
{
"env": {
"node": true
},
"extends": [
"eslint:recommended"."plugin:jest/recommended"."plugin:vue/recommended"."prettier"]}Copy the code
Husky is used to format the code before each commit to ensure consistency.
Initialize the style structure
Create a color. SCSS file in SRC /styles/variables as shown in the top project structure
$color_primary: #ff5722;
$color_disabled: #d1d1d1;
Copy the code
Create index.scss reference color.scss in the sibling directory
@import "color";
Copy the code
Do the same for creating other types of style variables later
SCSS file is created on SRC /styles/vendors to standardize style differences between browsers, and the source code can be viewed on Github.
Then create index.scss under SRC /styles as the style entry
@import "vendors/normalize";
Copy the code
Install the appropriate NPM package
yarn add sass-loader node-sass style-loader css-loader -D
Copy the code
Create components
Create button.vue file in SRC /components/button
<template>
<button class="ml-button" :class="btnClass">
<slot></slot>
</button>
</template>
<script>
const prefix = "ml-button";
export default {
name: "MlButton",
props: {
type: {
type: String,
default: "primary"
},
disabled: {
type: Boolean,
default: false
},
round: {
type: Boolean,
default: false
}
},
computed: {
btnClass() {
return [
`${prefix}--${this.type}`,
this.disabled ? `${prefix}--disabled` : "",
this.round ? `${prefix}--round` : ""
];
}
}
};
</script>
<style lang="scss">
@import "../../styles/variables/index";
.ml-button {
color: white;
font-size: 14px;
padding: 12px 20px;
border-radius: 5px;
&:focus {
cursor: pointer;
}
&--primary {
background-color: $color-primary;
}
&--disabled {
background-color: $color_disabled;
color: rgba(255, 255, 255, 0.8);
}
&--round {
border-radius: 20px;
}
}
</style>
Copy the code
Create index.js in the sibling directory to use as a component entry
import MlButton from "./button.vue";
export default MlButton;
Copy the code
Install the storybook
Storybook provides a good presentation of your components in the production environment. You can write various use cases for your components through storybook. Combined with various plug-ins, your components can interact with documents in real time, listen to component clicks, view source code, write Markdown documents, display components in different Windows and so on.
With the help of Storybook, we do not need to install various NPM packages, saving us time to build component use cases, and can more intuitively show users the various uses of components.
Here is the storybook installation configuration
yarn add @storybook/vue vue-loader vue-template-compiler @babel/core babel-loader babel-preset-vue -D
Copy the code
- @storybook/vue (Storybook Vue Core package)
- Vue-loader (Webpack loader, used by Webpack to parse vUE single file components)
- Vue-template-compiler (Vue compiler used by the Vue loader, which must be consistent with the vUE package version)
- @babel/core
- Babel-loader (webpack loader, let webpack use Babel to parse JS files)
- Babel-preset – Vue (Plugin used by Babel to parse Vue JSX)
Create the storybook config file in.storybook/config.js
import { configure } from "@storybook/vue";
function loadStories() {
const req = require.context(".. /src".true, /\.story\.js$/);
req.keys().forEach(filename= > req(filename));
}
configure(loadStories, module);
Copy the code
This configuration file automatically loads the *.story.js file in the SRC directory
Because the Vue component uses SCSS, the storybook webpack configuration webpack.config.js needs to be created in the.storybook directory
const path = require("path");
module.exports = async ({ config, mode }) => {
config.module.rules.push({
test: /\.scss$/.use: ["vue-style-loader"."css-loader"."sass-loader"].include: path.resolve(__dirname, ".. /")});// Return the altered config
return config;
};
Copy the code
Create the storybook use case button.story.js in SRC/Components /button
import { storiesOf } from "@storybook/vue";
import MlButton from "./button.vue";
storiesOf("Button".module).add("Primary", () = > ({components: { MlButton },
template: '<ml-button type="primary">Button</ml-button>'
}));
Copy the code
Add NPM Script to package.json to make it easy to start and package the Storybook service
"scripts": {
"dev": "start-storybook",
"build:storybook": "build-storybook -c .storybook -o dist",
}
Copy the code
Start the storybook
yarn dev
Copy the code
With Storybook, we don’t have to write component use cases and build webPack configurations anymore
Project package
The storybook is just for us to show the components, and we need to package the components for other projects to use.
Entry.js is first created in the SRC directory to import the component
export { default as MlButton } from "./components/button";
Copy the code
Then create other components that need to be added to this file
Create index.js in the SRC directory as the project entry
// Introduce styles
import "./styles/index.scss";
// Import components
import * as components from "./entry";
// Create an install method that registers all components with vue
const install = function(Vue) {
Object.keys(components).forEach(key= > {
Vue.component(key, components[key]);
});
};
// auto install
if (typeof window! = ="undefined" && window.Vue) {
install(window.Vue);
}
const plugin = {
install
};
export * from "./entry";
export default plugin;
Copy the code
Webpack configuration
Create a webpack.base.js file in the build folder and fill in the common configuration
const VueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
module: {
rules: [{test: /\.js$/.exclude: /(node_modules)/.use: {
loader: "babel-loader"}}, {test: /\.vue$/.use: {
loader: "vue-loader"}}},plugins: [new VueLoaderPlugin()],
externals: {
vue: {
root: "Vue".commonjs: "vue".commonjs2: "vue".amd: "vue"}}};Copy the code
Add vUE to externals so that vUE is not packaged together.
Create webpack.config.js under the same directory as the package entry
const path = require("path");
const merge = require("webpack-merge");
const webpackBaseConfig = require("./webpack.base.js");
module.exports = merge(webpackBaseConfig, {
entry: {
main: path.resolve(__dirname, ".. /src/index")},output: {
path: path.resolve(__dirname, ".. /lib"),
filename: "vue-uikit.js".library: "vue-uikit".libraryTarget: "umd"
},
module: {
rules: [{test: /\.scss$/.use: ["vue-style-loader"."css-loader"."sass-loader"]}]}});Copy the code
Add a package command to package.json
"scripts": {
"build": "webpack --mode production --config build/webpack.config.js"
}
Copy the code
Run yarn Build to package the package
Verify the package file
After packaging, you can see that the main project has a lib directory, which contains a vue-UIkit file. We need to verify whether this file is properly packaged, first of all, we need to change the package.json entry
"main": "lib/vue-uikit.js"
Copy the code
So that when other projects introduce component libraries, they know where the entry is: lib/vue-uikit.js
Before formal packaging, you can run Yarn Link. The following message is displayed
success Registered "vue-uikit".
info You can now run `yarn link "vue-uikit"` in the projects where you want to use this package and it will be used instead.
Copy the code
You can create a new project in your own project, or with vue-CLI, and run YARN Link “vue-UIKit” in it. Your project node_modules will temporarily add your component library, which can then be introduced as normal
<template>
<div id="app">
<ml-button type="primary">button</ml-button>
</div>
</template>
<script>
import UIKit from "vue-uikit";
import Vue from "vue";
Vue.use(UIKit);
export default {
name: "app"
};
</script>
Copy the code
According to the need to load
Sometimes a page only needs a few components and does not want to introduce the entire library, so it is better for the library to implement on-demand functionality. See the example of element-UI, which uses a Babel component: babel-plugin-Component.
After introducing the plugin to your project, code like this
import { Button } from "components";
Copy the code
It’s going to be resolved into
var button = require("components/lib/button");
require("components/lib/button/style.css");
Copy the code
This plugin can be used only in lib/vue-uikit.js. The plugin can be used only in lib/button. Also reference the corresponding style file, so our component library packaging should also be packaged according to the component classification into the corresponding folder, also need to extract the style.
We first classify component packaging, first in the build/webpack.com ponent. Js create the corresponding webpack configuration
const path = require("path");
const merge = require("webpack-merge");
const webpackBaseConfig = require("./webpack.base.js");
module.exports = merge(webpackBaseConfig, {
entry: {
"ml-button": path.resolve(__dirname, ".. /src/components/button/index.js")},output: {
path: path.resolve(__dirname, ".. /lib"),
filename: "[name]/index.js".libraryTarget: "umd"
},
module: {
rules: [{test: /\.scss$/.use: ["vue-style-loader"."css-loader"."sass-loader"]}]}});Copy the code
Then create the corresponding script in package.json
"build:com": "webpack --mode production --config build/webpack.component.js"
Copy the code
Then run YARN Build :com and, once packaged, you can see that the current directory structure looks like this
lib
- ml-button
- index.js
- vue-uikit.js
Copy the code
Modify the introduced code in your main project to see if it works
<template> <div id="app"> <ml-button type="positive">button</ml-button> </div> </template> <script> import MlButton from "vue-uikit/lib/ml-button"; export default { name: "app", components: { MlButton } }; </script>Copy the code
Now there are a few more questions. The entry in webpack.component.js looks like this
entry: {
"ml-button": path.resolve(__dirname, ".. /src/components/button/index.js")}Copy the code
In the future, each time a new component is added, we need to manually add an entry, preferably automatically generated according to the file directory, and according to the requirements of babel-plugin-compoent, we need to extract the style file.
To solve the entry problem, install the glob package to obtain the file that matches the corresponding rule yarn add glob -d
const glob = require("glob");
console.log(glob.sync("./src/components/**/index.js"));
// [ './src/components/button/index.js' ]
Copy the code
By processing the returned array, you can generate the corresponding entry
const entry = Object.assign(
{},
glob
.sync("./src/components/**/index.js")
.map(item= > {
return item.split("/") [3];
})
.reduce((acc, cur) = > {
acc[`ml-${cur}`] = path.resolve(
__dirname,
`.. /src/components/${cur}/index.js`
);
return{... acc }; }, {}));Copy the code
For extracting style files, install the mini-CSs-extract-Plugin package and configure it:
yarn add mini-css-extract-plugin -D
Copy the code
The final webpack.component.js looks like this
const path = require("path");
const merge = require("webpack-merge");
const webpackBaseConfig = require("./webpack.base.js");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const glob = require("glob");
const entry = Object.assign(
{},
glob
.sync("./src/components/**/index.js")
.map(item= > {
return item.split("/") [3];
})
.reduce((acc, cur) = > {
acc[`ml-${cur}`] = path.resolve(
__dirname,
`.. /src/components/${cur}/index.js`
);
return{... acc }; }, {}));module.exports = merge(webpackBaseConfig, {
entry: entry,
output: {
path: path.resolve(__dirname, ".. /lib"),
filename: "[name]/index.js".libraryTarget: "umd"
},
module: {
rules: [{test: /\.scss$/.use: [MiniCssExtractPlugin.loader, "css-loader"."sass-loader"]]}},plugins: [
new MiniCssExtractPlugin({
filename: "theme/[name].css"]}}));Copy the code
The generated style files are placed separately in the lib/theme folder.
The import MlButton from ‘vue-UIkit /lib/ml-button’ does not contain the style and needs to be reintroduced:
<template> <div id="app"> <ml-button type="positive">button</ml-button> </div> </template> <script> import MlButton from "vue-uikit/lib/ml-button"; import "vue-uikit/lib/theme/ml-button.css"; export default { name: "app", components: { MlButton } }; </script>Copy the code
We certainly don’t want to add styles every time we add a new component when loading on demand:
import MlButton from "vue-uikit/lib/ml-button";
import "vue-uikit/lib/theme/ml-button.css";
import MlMessage from "vue-uikit/lib/ml-message";
import "vue-uikit/lib/theme/ml-message.css";
Copy the code
This is where babel-plugin-compnent comes in.
After the main project (not the component library) installs babel-plugin-compnent, add the following code to the Babel configuration file
plugins: [
[
"component",
{
libraryName: "vue-uikit".// This plugin is used when introducing vue-UIKit
styleLibraryName: "theme" // The style file is found in the theme}]].Copy the code
Rerun the main project and you will find an error
* vue-uikit/lib/theme/base.css in./node_modules/cache-loader/dist/cjs.js?? ref--12-0! ./node_modules/babel-loader/lib! ./node_modules/cache-loader/dist/cjs.js?? ref--0-0! ./node_modules/vue-loader/lib?? vue-loader-options! ./src/App.vue? vue&type=script&lang=js&
Copy the code
The theme/base.css file cannot be found, babel-plugin-compnent will introduce common styles for all components by default, so we need to change the Webpack configuration again
const entry = Object.assign(
{},
{
base: path.resolve(__dirname, ".. /src/styles/index.scss")
},
glob
.sync("./src/components/**/index.js")
.map(item= > {
return item.split("/") [3];
})
.reduce((acc, cur) = > {
acc[`ml-${cur}`] = path.resolve(
__dirname,
`.. /src/components/${cur}/index.js`
);
return{... acc }; }, {}));Copy the code
After packing, we found a lib/base folder. This is because webpack generates a js file by default when packing index. SCSS. We can delete it manually or use webpack component
yarn add rimraf -D
Rewrite the NPM script
"scripts": {
"build": "rimraf lib && yarn build:web && yarn build:com && rimraf lib/base",
"build:web": "webpack --mode production --config build/webpack.config.js",
"build:com": "webpack --mode production --config build/webpack.component.js"
},
Copy the code
Run YARN Build, at which point the main project should be running properly
Style optimization
In addition to extracting the style file, we can also make some optimizations, such as compression and adding prefix
Install postCSS and related plug-ins
yarn add postcss autoprefixer cssnano -D
Autoprefix is used to add prefixes and cssnano is used to compress style files
After the installation is complete, add the corresponding configuration postcss.config.js to the root directory
module.exports = {
plugins: {
autoprefixer: {},
cssnano: {}
}
};
Copy the code
Add postCSS-loader to Webpack.
In addition, it is best to extract all styles into a single file, so that when importing a component library globally, you can easily import style files in the HTML header independently, avoiding FOUC problems. The final WebPack configuration looks like this
webpack.component.js
const path = require("path");
const merge = require("webpack-merge");
const webpackBaseConfig = require("./webpack.base.js");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const glob = require("glob");
const entry = Object.assign(
{},
{
base: path.resolve(__dirname, ".. /src/styles/index.scss")
},
glob
.sync("./src/components/**/index.js")
.map(item= > {
return item.split("/") [3];
})
.reduce((acc, cur) = > {
acc[`ml-${cur}`] = path.resolve(
__dirname,
`.. /src/components/${cur}/index.js`
);
return{... acc }; }, {}));module.exports = merge(webpackBaseConfig, {
entry: entry,
output: {
path: path.resolve(__dirname, ".. /lib"),
filename: "[name]/index.js".libraryTarget: "umd"
},
module: {
rules: [{test: /\.scss$/.use: [
MiniCssExtractPlugin.loader,
"css-loader"."postcss-loader"./ / add postcss - loader
"sass-loader"]]}},plugins: [
new MiniCssExtractPlugin({
filename: "theme/[name].css"]}}));Copy the code
webpack.config.js
const path = require("path");
const merge = require("webpack-merge");
const webpackBaseConfig = require("./webpack.base.js");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = merge(webpackBaseConfig, {
entry: {
main: path.resolve(__dirname, ".. /src/index")},output: {
path: path.resolve(__dirname, ".. /lib"),
filename: "vue-uikit.js".library: "vue-uikit".libraryTarget: "umd"
},
module: {
rules: [{test: /\.scss$/.use: [
MiniCssExtractPlugin.loader,
"css-loader"."postcss-loader"."sass-loader"]]}},plugins: [
// Extract all style files to index.css
new MiniCssExtractPlugin({
filename: "index.css"]}}));Copy the code
Repackage yarn Build
Because we’re pulling styles out, global imports require additional styles
import Vue from 'vue' import UIkit from 'vue-uikit' import
'vue-uikit/lib/index.css' Vue.use(UIkit)
Copy the code
Unit testing
For unit tests we’ll use @vue/test-utils
Yarn add Jest @vue/test-utils jest-serializer-vue vue-jest babel-jest @babel/preset-env babel-core@^7.0.0-bridge.0 -d
- Jest-serializer -vue (Snapshot test)
- Babel-core @^7.0.0-bridge.0
Add the jest configuration to the root directory jest.config.js
module.exports = {
moduleFileExtensions: ["js", "json", "vue"],
transform: {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "vue-jest"
},
snapshotSerializers: ["jest-serializer-vue"]
};
Copy the code
Add the Babel configuration to the root directory. Babelrc
{
"presets": [
"@babel/preset-env"]."env": {
"test": {
"presets": [["@babel/preset-env",
{
"targets": {
"node": "current"}}]]}}}Copy the code
Add test command to NPM script: “test”: “jest”
Add test cases to SRC/components/button/button test. Js
import { shallowMount } from "@vue/test-utils";
import MlButton from "./button.vue";
describe("Button", () => {
test("is a Vue instance", () = > {const wrapper = shallowMount(MlButton);
expect(wrapper.isVueInstance()).toBeTruthy();
});
test("positive color", () = > {const wrapper = shallowMount(MlButton, {
propsData: {
type: "positive"}}); expect(wrapper.classes("ml-button--positive")).toBeTruthy();
});
});
Copy the code
Run the yarn test
The next best thing is to put the unit tests into PreCommit and check them every time the code commits
"scripts": {
"test": "jest",
"lint": "pretty-quick --staged && eslint --ext .js,.vue src",
"dev": "start-storybook",
"build:storybook": "build-storybook -c .storybook -o dist",
"build": "rimraf lib && yarn build:web && yarn build:com && rimraf lib/base",
"build:web": "webpack --mode production --config build/webpack.config.js",
"build:com": "webpack --mode production --config build/webpack.component.js"
},
"husky": {
"hooks": {
"pre-commit": "yarn test && yarn lint"
}
},
Copy the code
release
First go to www.npmjs.com/ to register an account, after registration
Log in from the component library
npm login
And then it’s ready to publish
npm publish