Как создать собственную библиотеку компонентов Vue

Vue.js

Позвольте мне поделиться с вами процессом создания библиотеки компонентов Vue для ознакомления.Адрес проекта здесь:

GitHub.com/Colgate/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