Позвольте мне поделиться с вами процессом создания библиотеки компонентов Vue для ознакомления.Адрес проекта здесь:
Структура проекта следующая
build (webpack配置)
lib (打包文件位置)
- button (按需加载组件位置)
- - index.js
- theme (组件样式)
- - base.css (公用样式)
- - button.css(组件样式)
- index.js (全局引用入口)
src (项目文件)
- assets (公共资源)
- components (vue组件)
- button
- - button.vue (组件逻辑)
- - button.scss (组件样式)
- - button.test.js (组件测试)
- - button.story.js (组件用例)
- - index.js (组件入口)
- directives (vue directive 命令)
- locale (国际化)
- mixins (vue mixins)
- styles (样式)
- - variables (样式变量)
- - - index.scss (变量入口)
- - - color.scss (颜色变量)
- - vendors (公共样式, 样式重置)
- - index.scss (样式入口)
- utils (公用方法)
- index.js (项目入口)
Инициализировать проект
Создать проект
mkdir vue-uikit
cd vue-uikit
mkdir src
инициализировать git
git init
Создан в корневом каталоге проекта.gitignoreдокумент
node_modules/
инициализировать нпм
yarn init
установить вью
yarn add vue -D
зачем ставитьvueустановлен вdevDependencies, потому что наша библиотека компонентов зависит от установки пользователяvuepackage, нам не нужно упаковывать собственные компонентыvueУпаковано вместе.
Установить веб-пакет
yarn add webpack webpack-cli webpack-merge -D
формат
используемый форматeslintа такжеprettier
yarn add eslint prettier eslint-config-prettier eslint-plugin-jest eslint-plugin-vue husky pretty-quick -D
- хаски (инструмент для предварительной фиксации)
- довольно быстро (формат git изменил файлы с более красивым)
- eslint-plugin-jest, eslint-plugin-vue, eslint-plugin-prettier (плагины, связанные с eslint, для модульного тестирования с помощью jest, файлы vue, совместимые с prettier)
Добавьте соответствующую конфигурацию в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"
]
},
Добавьте .eslintrc в корневой каталог
{
"env": {
"node": true
},
"extends": [
"eslint:recommended",
"plugin:jest/recommended",
"plugin:vue/recommended",
"prettier"
]
}
Будет использоваться перед каждым кодом фиксацииhuskyОтформатируйте код, чтобы обеспечить единообразие стиля кода.
Инициализировать структуру стиля
Согласно верхней структуре проекта вsrc/styles/variablesСоздайтеcolor.scssдокумент
$color_primary: #ff5722;
$color_disabled: #d1d1d1;
Создано в том же каталогеindex.scssЦитироватьcolor.scss
@import "color";
Впоследствии, если вы создаете другие типы переменных стиля, используются те же шаги.
существуетsrc/styles/vendorsСоздайтеnormalize.scssФайл, используемый для стандартизации различий в стилях между браузерами, исходный код можно посмотреть на github.
позжеsrc/stylesСоздать подindex.scssкак запись стиля
@import "vendors/normalize";
Установите соответствующий пакет npm
yarn add sass-loader node-sass style-loader css-loader -D
Создание компонентов
существуетsrc/components/buttonСоздайтеbutton.vueдокумент
<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>
Создано в том же каталогеindex.jsИспользовать как запись компонента
import MlButton from "./button.vue";
export default MlButton;
установить сборник рассказов
storybookОбеспечить хорошее отображение производственной среды для ваших компонентов, вы можете использовать его для написания различных вариантов использования компонентов, в сочетании с различными подключаемыми модулями, которые он предоставляет, ваши компоненты могут использовать документы для достижения взаимодействия в реальном времени, мониторинга щелчков компонентов, просмотра исходного кода код, запись документов уценки, отображение компонентов в разных окнах и другие функции.
С помощью сборника рассказов нам не нужно устанавливать различные пакеты npm, что экономит время на создание сценариев использования компонентов и может более интуитивно показывать пользователям различные варианты использования компонентов.
Ниже приведена конфигурация установки сборника рассказов.
yarn add @storybook/vue vue-loader vue-template-compiler @babel/core babel-loader babel-preset-vue -D
- @storybook/vue (базовый пакет Storybook vue)
- vue-loader (загрузчик веб-пакетов, для анализа однофайловых компонентов веб-пакета)
- vue-template-compiler (компилятор vue, используемый загрузчиком vue, должен соответствовать версии пакета vue)
- @babel/core (основной пакет Babel)
- babel-loader (загрузчик webpack, пусть webpack использует babel для разбора файлов js)
- babel-preset-vue (плагин babel для парсинга vue jsx)
существует.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);
Этот файл конфигурации будет загружен автоматическиsrcв каталоге*.story.jsдокумент
Поскольку компонент vue используетscss, должно быть.storybookСоздайте конфигурацию веб-пакета сборника рассказов в каталогеwebpack.config.js
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;
};
существуетsrc/components/buttonСоздайте сценарий использования сборника рассказовbutton.story.js
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>'
}));
существуетpackage.jsonДобавьте скрипт npm для запуска и упаковки сервиса сборника рассказов.
"scripts": {
"dev": "start-storybook",
"build:storybook": "build-storybook -c .storybook -o dist",
}
начать сборник рассказов
yarn dev
Благодаря сборнику рассказов нам не нужно много работать над написанием сценариев использования компонентов, и нам не нужно создавать конфигурацию веб-пакета ~
Упаковка проекта
Storybook просто помогает нам отображать компоненты, нам также нужно упаковывать компоненты для использования в других проектах.
первый вsrcСоздано в каталогеentry.jsиспользуется для импорта компонентов
export { default as MlButton } from "./components/button";
После создания других компонентов их также необходимо добавить в этот файл
существуетsrcСоздано в каталогеindex.jsкак вход в проект
// 引入样式
import "./styles/index.scss";
// 引入组件
import * as components from "./entry";
//创建 install 方法, 方法里面将所有组件注册到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;
конфигурация веб-пакета
существуетbuildсоздать папкуwebpack.base.jsфайл, заполните некоторые общие конфигурации
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"
}
}
};
Добавьте vue к внешним, чтобы он неvueУпакованы вместе.
Создано в том же каталогеwebpack.config.jsкак запись пакета
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"]
}
]
}
});
существуетpackage.jsonдобавить команду упаковки
"scripts": {
"build": "webpack --mode production --config build/webpack.config.js"
}
бегатьyarn buildупаковать
Проверить файл пакета
После упаковки видно, что есть еще один основной проектlibкаталог, в котором естьvue-uikitфайл, мы должны сначала проверить, нормально ли упакован этот файл, сначала нам нужно изменитьpackage.jsonВход
"main": "lib/vue-uikit.js"
Таким образом, когда другие проекты вводят библиотеки компонентов, они могут знать, где находится запись:lib/vue-uikit.js
До официальной упаковки можно запуститьyarn link, будет следующая подсказка
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.
Вы можете создать новый проект в своем собственном проекте или использовать vue-cli для запускаyarn link "vue-uikit", ваш проект node_modules временно добавит вашу библиотеку компонентов, после чего вы сможете нормально импортировать библиотеку компонентов
<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>
нагрузка по требованию
Иногда на странице нужно использовать только несколько компонентов и не требуется вводить всю библиотеку компонентов, поэтому библиотека компонентов лучше всего подходит для выполнения функции загрузки по запросу, см.element-uiпример, в котором используется компонент babel:babel-plugin-component.
После внедрения этого плагина в ваш проект код, подобный этому
import { Button } from "components";
будет проанализирован как
var button = require("components/lib/button");
require("components/lib/button/style.css");
Но мы упаковали только один, когда упаковалиlib/vue-uikit.jsфайл, библиотеку компонентов нельзя использовать напрямую без соответствующей конфигурации.В приведенном выше коде мы видим, что на нее ссылаются по мере необходимости.lib/buttonв папкеindex.jsфайл, а также ссылку на соответствующий файл стиля, поэтому упаковка нашей библиотеки компонентов также должна быть упакована в соответствующую папку в соответствии с классификацией компонентов, и стиль необходимо извлечь.
Сначала мы классифицируем и упаковываем компоненты, сначала вbuild/webpack.component.jsСоздайте соответствующую конфигурацию веб-пакета
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"]
}
]
}
});
затем вpackage.jsonСоздайте соответствующий скрипт
"build:com": "webpack --mode production --config build/webpack.component.js"
бежать заyarn build:comПосле упаковки вы можете увидеть текущую структуру каталогов - это так.
lib
- ml-button
- index.js
- vue-uikit.js
Измените код, представленный в вашем мастер-проекте, посмотрите, сможете ли вы его использовать в обычном режиме.
<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>
Есть еще несколько вопросов,webpack.component.jsвнутриentryтакое, что
entry: {
"ml-button": path.resolve(__dirname, "../src/components/button/index.js")
}
Каждый раз, когда вы добавляете новый компонент в будущем, вам нужно будет добавлять его вручнуюentry, лучше иметь возможность автоматически генерировать в соответствии с каталогом файлов и в соответствии сbabel-plugin-compoentТребования, нам нужно извлечь файл стиля.
решить сначалаentryПроблема, которую мы можем установитьglobpackage для получения файлов, соответствующих соответствующим правиламyarn add glob -D
const glob = require("glob");
console.log(glob.sync("./src/components/**/index.js"));
// [ './src/components/button/index.js' ]
Обрабатывая возвращенный массив, соответствующий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 };
}, {})
);
Что касается извлечения файлов стилей, вы можете установитьmini-css-extract-pluginупаковать и настроить:
yarn add mini-css-extract-plugin -D
Прошлой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(
{},
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"
})
]
});
Сгенерированные файлы стилей будут помещены отдельно вlib/themeпапка.
После упаковки вы обнаружите, что стиль основного проекта (проект, ссылающийся на библиотеку компонентов) исчез, потому что стиль был извлечен,import MlButton from 'vue-uikit/lib/ml-button'не будет содержать стилей и должен быть повторно введен:
<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>
Мы, конечно, не хотим добавлять стиль каждый раз, когда добавляется новый компонент, когда он загружается по запросу:
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";
тогда нужноbabel-plugin-compnentпомощь.
основной проект(не библиотека компонентов) установленbabel-plugin-compnentПосле этого добавьте следующий код в конфигурационный файл babel
plugins: [
[
"component",
{
libraryName: "vue-uikit", //引入vue-uikit的时候会用此插件
styleLibraryName: "theme" //样式文件在theme里面找
}
]
];
Перезапустите основной проект, вы обнаружите ошибку
* 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&
Не могу найтиtheme/base.cssдокумент,babel-plugin-compnentОбщедоступные стили, необходимые для всех компонентов, будут введены по умолчанию, поэтому нам нужно повторно изменить конфигурацию веб-пакета.
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 };
}, {})
);
После упаковки я нашел еще одинlib/baseпапка, это потому, что пакеты webpackindex.scssКогда придет время, по умолчанию генерируется файл js, мы можем удалить его вручную или использовать компонент веб-пакета, здесь я установил одинrimrafМешок
yarn add rimraf -D
Снова измените скрипт npm
"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"
},
бегатьyarn build, в это время основной проект должен нормально работать
оптимизация стиля
В дополнение к извлечению файлов стилей мы также можем выполнить некоторые оптимизации, такие как сжатие и добавление префиксов.
Установитьpostcssи сопутствующие плагины
yarn add postcss autoprefixer cssnano -D
autoprefixдобавить префиксы,cssnanoИспользуется для сжатия файлов стилей
После завершения установки добавьте соответствующую конфигурацию в корневую директорию.postcss.config.js
module.exports = {
plugins: {
autoprefixer: {},
cssnano: {}
}
};
Добавить кpostcss-loaderв вебпак.
Кроме того, лучше всего извлекать все стили в один файл, чтобы при глобальном импорте библиотеки компонентов было удобно внедрить файлы стилей в html-заголовок независимо друг от друга, чтобы избежать проблем с FOUC.Окончательная конфигурация веб-пакета выглядит так
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", //添加postcss-loader
"sass-loader"
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "theme/[name].css"
})
]
});
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: [
//把所有样式文件提取到index.css
new MiniCssExtractPlugin({
filename: "index.css"
})
]
});
переупаковыватьyarn build
Поскольку мы добавили стиль, для глобального введения необходимо ввести дополнительные стили.
import Vue from 'vue' import UIkit from 'vue-uikit' import
'vue-uikit/lib/index.css' Vue.use(UIkit)
модульный тест
Модульные тесты, которые мы будем использовать@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 (моментальный тест)
- babel-core@^7.0.0-bridge.0 (Jest не использует babel 7, вам необходимо загрузить этот пакет для совместимости)
Добавьте конфигурацию jest в корневой каталогjest.config.js
module.exports = {
moduleFileExtensions: ["js", "json", "vue"],
transform: {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "vue-jest"
},
snapshotSerializers: ["jest-serializer-vue"]
};
Добавьте конфигурацию babel в корневой каталог.babelrc
{
"presets": [
"@babel/preset-env"
],
"env": {
"test": {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}
}
}
Добавьте тестовые команды в скрипт npm:"test": "jest"
Добавьте тестовые случаи в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();
});
});
бегатьyarn test
Далее лучше поместить юнит-тесты в прекоммит и проверять их один раз перед каждым коммитом кода.
"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"
}
},
выпускать
первый пришелwww.npmjs.com/Зарегистрируйте аккаунт, после регистрации
Вход в библиотеку компонентов
npm login
можно опубликовать позже
npm publish