Десятый раунд серии квалифицированных интерфейсов — раскрытие одной или двух вещей о библиотеке компонентов.

модульный тест Vue.js
Десятый раунд серии квалифицированных интерфейсов — раскрытие одной или двух вещей о библиотеке компонентов.

1. Пишите впереди

1. Источник вдохновения

Я обычно люблю делать какие-то записи и сводки по некоторым вещам, включая некоторые компоненты.Когда накопленная сумма относительно велика, я считаю, что разрозненное накопление не подходит для управления.

Итак, я начал думать, как лучше управлять этими относительно разрозненными вещами более стандартизированным способом? Если вы управляете компонентами в виде библиотеки компонентов, будет ли она более подходящей для вашего собственного накопления и облегчения будущей работы?

Поэтому я начал ссылаться на некоторые отличные библиотеки компонентов пользовательского интерфейса на рынке, такие какelement-ui,vux,vantд., прочитать его исходный код, понять устройство его архитектуры, а затем разобраться с набором собственной библиотеки компонентов мобильного UI.vui.

В свободное время я активен в крупных технических сообществах, и часто некоторые друзья, которые какое-то время работали или все еще ищут стажировку, задают автору несколько вопросов: Как устроиться самому и сделать свой собственный фреймворк, колесо и библиотека? Как сделать библиотеку компонентов? Станет ли изюминкой вашего резюме создание библиотеки компонентов самостоятельно? Можете ли вы написать несколько статей по теме разработки библиотек компонентов? …

В духе ответов на вопросы и обмена ими родился этот пост в блоге.

2. Финальные визуализации

api-1

api-2

3. Обмен проблемами

Если у вас возникнут какие-либо вопросы при чтении статьи, вы можете присоединиться к дискуссионной группе для обсуждения (в дополнение к группе больших парней каждый день в группе также есть группа девушек~)

Фронта HodgePodge: 731175396

гитхаб:github.com/xuqiang521

Без дальнейших церемоний, давайте перейдем непосредственно к настоящей боевой главе~

2. Строительство окружающей среды

1. Создайте среду NODE

Здесь я говорю только об установке NODE под Mac и Windows.

I. Установка под Mac

  • Если у вас не установлен менеджер пакетов MachomebrewПервый шаг — установить его

    /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    
  • использоватьhomebrewУстановитьnode

    brew install node
    

II. Установка под окном

windowнепосредственно в окружающую средуофициальный сайт узлаЗагрузите соответствующую версию и нажмите «Далее», чтобы завершить установку.

После завершения установки проверьтеnodeа такжеnpmВерсия

node -v
# v9.6.1
npm -v
# 5.6.0

на твоем компьютереnodeОкружение настроено, теперь нам нужно установить скаффолдинг, от которого зависит библиотека компонентов.

2. Создайте проект vue

I. Установите vue-кли

# 全局安装
npm i -g vue-cli
# 查看vue-cli用法
vue -h
# 查看版本
vue -V
# 2.9.3

ii. Создайте проект с помощью vue-cli

использоватьvue-cliизinitДиректива инициализируетpersonal-components-libraryпроект

# 项目基于 webpack
vue init webpack personal-components-library

При сборке строительные леса позволят вам заполнить некоторые описания и зависимости проекта.Пожалуйста, обратитесь к содержимому, которое я выбираю ниже, чтобы заполнить

# 项目名称
Project name? personal-components-library
# 项目描述
Project description? A Personal Vue.js components Library project
# 项目作者
Author? qiangdada
# 项目构建 vue 版本(选择默认项)
Vue build? standalone
# 是否下载 vue-router (后期会用到,这里选 Yes)
Install vue-router? Yes
# 是否下载 eslint (为了制定合理的开发规范,这个必填)
Use ESLint to lint your code? Yes
# 安装默认的标准 eslint 规则
Pick an ESLint preset? Standard
# 构建测试案例
Set up unit tests? Yes
# 安装 test 依赖 (选择 karma + mocha)
Pick a test runner? karma
# 构建 e2e 测试案例 (No)
Setup e2e tests with Nightwatch? No
# 项目初始化完是否安装依赖 (npm)
Should we run `npm install` for you after the project has been created? (recom
mended) npm

Как только вы выбрали, вы можете подождать,vue-cliЭто поможет вам собрать проект и установить зависимости.

Структура проекта инициализации следующая:

├── build                     webpack打包以及本地服务的文件都在里面
├── config              	  不同环境的配置都在这里
├── index.html                入口html
├── node_modules              npm安装的依赖包都在这里面
├── package.json              项目配置信息
├── README.md              	  项目介绍
├── src                       我们的源代码
│   ├── App.vue               vue主入口文件
│   ├── assets                资源存放(如图片)
│   ├── components            可以复用的模块放在这里面
│   ├── main.js               入口js
│   ├── router                路由管理
└── webpack.config.js         webpack配置文件
├── static                    被copy的静态资源存放地址
├── test                      测试文档和案例

если вы используетеnpmЕсли зависимость загрузки слишком медленная или некоторые ресурсы заблокированы, рекомендуется использоватьcnpmСделать зависимые загрузки

# 全局安装 cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 使用 cnpm 进行依赖安装
cnpm i

После завершения установки вы можете начатьvueпроект~

npm run dev

затем посетитеhttp://localhost:8080можно успешно получить доступ черезvue-cliСтроить изvueПункт, я уже установил среду разработки этой зависимости библиотеки.

3. Создайте новый каталог

Прежде всего, нам нужно уточнить цель этого раздела, нам нужно изменить каталог, чтобы лучше разработать библиотеку компонентов.

Мы уже построили его в предыдущем разделеvueпроект, но каталог инициализированного проекта не может соответствовать последующей разработке и обслуживанию библиотеки компонентов. Итак, что нам нужно сделать в этой главе, так это преобразовать инициализациюvueКаталог проекта, превратите его в каталог, требуемый библиотекой компонентов, а затем давайте сделаем это.

1. Каталог библиотеки компонентов

  1. build: Этот каталог в основном используется для хранения файлов, связанных со сборкой.
  2. packages: Этот каталог в основном используется для хранения всех компонентов
  3. examples: Этот каталог в основном используется для хранения отображения библиотеки компонентов.demoа также文档все сопутствующие документы
  4. src: Этот каталог в основном используется для управления основной записью регистрации компонентов, инструментов,mixinsи т.д. (для этого нужно преобразовать инициализированныйsrcсодержание)
  5. test: этот каталог используется для хранения тестовых случаев (продолжайте использовать инициализированный каталог)
  6. lib: каталог, в котором упакована библиотека компонентов.
  7. .github: В качестве библиотеки компонентов с открытым исходным кодом, если вы хотите разрабатывать с другими, то этот каталог используется для хранения некоторых правил разработки, которые вы определяете сами, что тоже очень хорошо.

Хорошо, начните преобразовывать каталог вашего инициализированного проекта.

2. Запустите проект снова

I. Измените каталог примеров

Мы заранее знаем, что когда мы запускаем локальную службу, основным файлом записи страницы являетсяindex.html. Теперь наш первый шаг — поговорить о главном входе на страницу.htmlа такжеjsперейти кexamplesниже каталога.examplesКонкретный каталог выглядит следующим образом

├── assets						css,图片等资源都在这
├── pages                     	路由中所有的页面
├── src              	      	
│   ├── components            	demo中可以复用的模块放在这里面
│   ├── index.js              	入口js
│   ├── index.tpl              	页面入口
│   ├── App.vue               	vue主入口文件
│   ├── router.config.js		路由js

Измененный код каждого файла выглядит следующим образом

  • index.js

    import Vue from 'vue'
    import App from './App'
    import router from './router.config'
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app-container',
      router,
      components: { App },
      template: '<App/>'
    })
    
  • index.tpl

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
      <title>My Component Library</title>
    </head>
    <body>
      <div id="app-container">
        <app></app>
      </div>
    </body>
    </html>
    
  • App.vue

    <template>
      <div id="app">
        <router-view/>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App'
    }
    </script>
    
  • router.config.js

    import Vue from 'vue'
    import Router from 'vue-router'
    import hello from '../pages/hello'  // 请自行去pages下面创建一个hello.vue,以方便之后的测试
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          component: hello
        }
      ]
    })
    

ii. Измените каталог src

srcКаталог в основном используется для хранения основного файла записи регистрации компонента, метода инструмента,mixinsи другие документы. мы сверхуexamplesКаталог можно узнать, оригиналsrcНекоторые файлы в файле необходимо удалить.Измененный каталог выглядит следующим образом

├── mixins						mixins方法存放在这
├── utils                     	一些常用辅助方法存放在这
├── index.js              	    组件注册主入口

iii. Измените некоторые упакованные файлы в каталоге сборки.

Думая о маленьких друзьях, которые это видят, они тоже должны знать, что нам нужно делать сейчас. Да, это изменить входной файл локальной службы. Если он может только работать, то изменитеentryjs запись в иhtml-webpack-pluginНа запись страницы можно ссылаться. Код такой (ставится только код ключа)

entry: {
  'vendor': ['vue', 'vue-router'],
  'vui': './examples/src/index.js'
},
// ...
plugins: [
  // ...
  // 将入口改成examples/src/index.tpl
  new HtmlWebpackPlugin({
    chunks: ['vendor', 'vui'],
    template: 'examples/src/index.tpl',
    filename: 'index.html',
    inject: true
  })
]

Хорошо, изменено. повторно выполнитьnpm run dev, тогда ваш проект может работать под новым входным файлом

3. Используйте компоненты локально

В этом разделе нам нужно реализовать службу, которую мы запускаем локально, которую можно использоватьpackagesКомпоненты ниже. Ниже мы разрабатываем простейшийhelloкомпоненты для объяснения

я вpackagesсоздать следующийhelloкомпоненты

Чтобы иметь хорошее ограничение, здесь мы ограничиваем: компонент должен иметь указанный каталог и имя файла для унифицированного управления перед началом записи.packagesПод содержаниемhelloФайлы под компонентом следующие

├── hello						
│   ├── hello.vue

hello.vueСодержание выглядит следующим образом

<template>
  <div class="v-hello">
    hello {{ message }}
  </div>
</template>

<script>
export default {
  name: 'v-hello',
  props: {
    message: String
  }
}
</script>

II.src/index.jsзарегистрировать компонент

sec/index.jsФайл также упомянут выше, он в основном используется для управления регистрацией всех компонентов в нашей библиотеке компонентов.

import Hello from '../packages/hello'

const install = function (Vue) {
  if (install.installed) return

  Vue.component(Hello.name, Hello)
}

if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

export default {
  install,
  Hello
}

III.examples/src/index.jsСсылка в файле записи js

Далее мне нужно переделатьexamplesНапишите это в насhelloкомпоненты для ссылки

import vui from 'src/index.js'
// 完整引用
Vue.use(vui)
// 独立引用
const { Hello } = vui
Vue.component(Hello.name, Hello)

IV.examples/pages/hello.vueИспользовать напрямую

существуетexamples/pagesНам нужно создать демонстрационный файл с тем же именем, что и имя компонента, и использовать компонент

<v-hello message="my component library"></v-hello>

hello

Когда результат вашей операции будет таким же, как на картинке выше, поздравляю. Вы успешно сделали еще один шаг к развитию библиотеки компонентов~

Видя это, мне нужно, чтобы читатель сосредоточился на документе в соответствии с моими собственными предпочтениями (конечно, я также могу сослаться на демонстрацию, приведенную выше). Только так мы можем сделать так, чтобы наша библиотека компонентов была плавно разработана.

В следующем разделе мы оптимизируемbuildСледующий файл пакета, и пусть каждый публикует свои разработанные компоненты вnpmОфициальный сайт делает вашу библиотеку компонентов более удобной для использования другими!

В-четвертых, преобразуйте файл пакета и опубликуйте пакет npm.

Как обычно, прежде чем мы начнем основную часть главы, нам нужно знать, что эта глава должна делать и почему.

  1. Поскольку первоначальный проект строительных лесов предназначен дляbuildФайл имеет только один централизованно упакованный файлwebpack.prod.conf.js

  2. Чтобы в будущем лучше использовать нашу библиотеку компонентов, нам нужно извлечь все модули, соответствующие библиотеке компонентов, и упаковать их вvui.jsВ файле (назовите его как хотите), чтобы мы могли обращаться к нашей библиотеке компонентов следующими способами.

    import Vue from 'vue'
    import vui from 'x-vui'
    Vue.use(vui)
    
  3. Нам также необходимоexamplesСвязанные файлы упакованы и управляются, потому что мы должны разработать официальный веб-сайт документации библиотеки компонентов позже, и все соответствующие входы официального веб-сайта документации находятся вexamplesсередина

1. Измените файл пакета сборки

I. Интеграция локальных служебных файлов

Мы можем видеть из проекта инициализации,buildотносящийся к документуwebpackФайлы следующие

├── webpack.base.conf.js					基础配置文件
├── webpack.dev.conf.js                     本地服务配置文件
├── webpack.prod.conf.js             	    打包配置文件
├── webpack.test.conf.js             	    测试配置文件(这里先不做过多描述)

Первоначальная упаковкаoutputВыходной каталогdist, этот каталог является выходным каталогом всего проекта после упаковки, а не каталогом, требуемым нашей библиотекой компонентов. Поскольку это не то, что нам нужно, что нам нужно в нужном каталоге?

  1. Файл библиотеки компонента первичного входа в находу JSlib/vui.js(основной файл библиотеки компонентов js)
  2. CSS-файл основной записи библиотеки компонентовlib/vui-css/index.css(Основной файл css библиотеки компонентов, мы не будем слишком подробно описывать упаковку css в этой главе, и в следующих главах будет объяснено отдельно)
  3. examplesупакованный файлexamples/dist(Главный вход официального сайта более позднего документа)

Теперь, когда цель поставлена, следующее, что нам нужно сделать, это разобраться в соответствующихwebpackфайл пакета, как показано ниже

├── webpack.base.conf.js			基础配置文件(配置方面和webpack.dev.conf.js的配置进行部分整合)
├── webpack.dev.conf.js             本地服务配置文件(将纯配置文件进行对应的删减)
├── webpack.build.js             	组件库入口文件打包配置文件(将webpack.prod.conf.js重命名)
├── webpack.build.min.js            examples展示文件打包配置文件(新增文件)

1. webpack.base.conf.js

начать ремонтwebpack.base.conf.jsПеред файлом нам нужно понять, что должны делать два файла пакета.

  1. webpack.build.js : выходlib/vui.jsОсновной файл библиотеки компонентов js, который будет использоватьсяwebpack.base.conf.jsа такжеwebpack.dev.conf.jsСвязанная конфигурация
  2. webpack.build.min.js: выходexamples/distФайлы, связанные с документом, будут использоватьсяwebpack.base.conf.jsа такжеwebpack.dev.conf.jsСвязанная конфигурация

С двухwebpackбудут использоваться файлы пакетовwebpack.base.conf.jsа такжеwebpack.dev.conf.jsсоответствующей конфигурации, то почему бы нам не интегрировать некоторые из тех же файлов вwebpack.base.conf.jsв файле? Цель ясна, тогда следуй за мной

'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
const webpack = require('webpack')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const createLintingRule = () => ({
  test: /\.(js|vue)$/,
  loader: 'eslint-loader',
  enforce: 'pre',
  include: [resolve('src'), resolve('test')],
  options: {
    formatter: require('eslint-friendly-formatter'),
    emitWarning: !config.dev.showEslintErrorsInOverlay
  }
})
module.exports = {
  context: path.resolve(__dirname, '../'),
  // 文件入口 
  entry: {
    'vendor': ['vue', 'vue-router'],
    'vui': './examples/src/index.js'
  },
  // 输出目录
  output: {
    path: path.join(__dirname, '../examples/dist'),
    publicPath: '/',
    filename: '[name].js'
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    // 此处新增了一些 alias 别名
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      'src': resolve('src'),
      'packages': resolve('packages'),
      'lib': resolve('lib'),
      'components': resolve('examples/src/components')
    }
  },
  // 延用原先的大部分配置
  module: {
    rules: [
      // 原先的配置...
      // 整合webpack.dev.conf.js中css相关配置
      ...utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
    ]
  },
  // 延用原先的配置
  node: {
    // ...
  },
  devtool: config.dev.devtool,
  // 整合webpack.dev.conf.js中的devServer选项
  devServer: {
    clientLogLevel: 'warning',
    historyApiFallback: {
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },
    hot: true,
    contentBase: false, // since we use CopyWebpackPlugin.
    compress: true,
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    open: config.dev.autoOpenBrowser,
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable,
    quiet: true, // necessary for FriendlyErrorsPlugin
    watchOptions: {
      poll: config.dev.poll,
    }
  },
  // 整合webpack.dev.conf.js中的plugins选项
  plugins: [
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    // 页面主入口
    new HtmlWebpackPlugin({
      chunks: ['manifest', 'vendor', 'vui'],
      template: 'examples/src/index.tpl',
      filename: 'index.html',
      inject: true
    })
  ]
}

2. webpack.dev.conf.js

Здесь нужно только интегрироватьwebpack.base.conf.jsКонфигурацию можно удалить, чтобы избежать дублирования кода.

'use strict'
const utils = require('./utils')
const config = require('../config')
const baseWebpackConfig = require('./webpack.base.conf')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')

module.exports = new Promise((resolve, reject) => {
  portfinder.basePort = process.env.PORT || config.dev.port
  portfinder.getPort((err, port) => {
    if (err) {
      reject(err)
    } else {
      process.env.PORT = port
      baseWebpackConfig.devServer.port = port

      baseWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
        compilationSuccessInfo: {
          messages: [`Your application is running here: http://${baseWebpackConfig.devServer.host}:${port}`],
        },
        onErrors: config.dev.notifyOnErrors
        ? utils.createNotifierCallback()
        : undefined
      }))

      resolve(baseWebpackConfig)
    }
  })
})

webpack.base.conf.jsа такжеwebpack.dev.conf.jsПосле того, как оба файла будут скорректированы, повторно выполнитеnpm run dev

run-dev

На приведенном выше рисунке показано, что ваш локальный служебный файл был успешно изменен, как и ожидалось.

II. Файл пакета реконструкции

1. webpack.build.js

Основная цель этого файла — объединить все связанные с компонентами файлы в библиотеке компонентов и вывести их.lib/vui.jsосновной файл

'use strict'
const webpack = require('webpack')
const config = require('./webpack.base.conf')
// 修改入口文件
config.entry = {
  'vui': './src/index.js'
}
// 修改输出目录
config.output = {
  filename: './lib/[name].js',
  library: 'vui',
  libraryTarget: 'umd'
}
// 配置externals选项
config.externals = {
  vue: {
    root: 'Vue',
    commonjs: 'vue',
    commonjs2: 'vue',
    amd: 'vue'
  }
}
// 配置plugins选项
config.plugins = [
  new webpack.DefinePlugin({
    'process.env': require('../config/prod.env')
  })
]
// 删除devtool配置
delete config.devtool

module.exports = config

2. webpack.build.min.js

Основная цель этого файла - открыть единый адрес пакета,examplesСвязанные с выходным файломexamples/distСправочник (т. е. последующие документы, вход на официальный сайт)

const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

const webpackConfig = merge(baseWebpackConfig, {
  output: {
    chunkFilename: '[id].[hash].js',
    filename: '[name].min.[hash].js'
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      },
      output: {
        comments: false
      },
      sourceMap: false
    }),
    // extract css into its own file
    new ExtractTextPlugin({
      filename: '[name].[contenthash].css',
      allChunks: true,
    }),
    // keep module.id stable when vendor modules does not change
    new webpack.HashedModuleIdsPlugin(),
    // enable scope hoisting
    new webpack.optimize.ModuleConcatenationPlugin(),
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks (module) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),
  ]
})

module.exports = webpackConfig

Когда у нас есть все эти файлы, последним шагом будет запись команды упаковки вpackage.jsonизscriptsбинго

"scripts": {
  "build:vui": "webpack --progress --hide-modules --config build/webpack.build.js && rimraf examples/dist && cross-env NODE_ENV=production webpack --progress --hide-modules --config build/webpack.build.min.js"
},

Выполнение заказа,npm run build:vui, иди ты

build

На данный момент локальная служба и два файла пакета были преобразованы.npmИспользовать его ~

2. Опубликовать пакет npm

Обратите внимание, если у вас нет собственногоnpmЕсли у вас есть учетная запись, перейдите наnpmЗарегистрируйте аккаунт на официальном сайте,Нажмите здесь, чтобы войти на официальный сайт, чтобы зарегистрироваться, шаги регистрации относительно просты, я не буду здесь описывать слишком много, если у вас есть какие-либо вопросы, вы можете задать их мне в группе обсуждения

I. Начнем с самой простой демонстрации

mkdir qiangdada520-npm-test
cd qiangdada520-npm-test
# npm 包主入口js文件
touch index.js
# npm 包首页介绍(具体啥内容你自行写入即可)
touch README.md
npm init
# package name: (qiangdada520-npm-test)
# version: (1.0.0)
# description: npm test
# entry point: (index.js) index.js
# test command:
# git repository:
# keywords: npm test
# author: qiangdada
# license: (ISC)

Тогда ОК, он сгенерируетpackage.json,следующим образом

{
  "name": "qiangdada-npm-test",
  "version": "1.0.0",
  "description": "npm test",
  "main": "index.js",  // npm 包主入口js文件
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "npm",
    "test"
  ],
  "author": "qiangdada",
  "license": "MIT"
}

Далее нам нужно подключить наш регистрационный номер локальноnpmучетная запись

npm adduser
# Username: 填写你自己的npm账号
# Password: npm账号密码
# Email: (this IS public) 你npm账号的认证邮箱
# Logged in as xuqiang521 on https://registry.npmjs.org/.  连接成功

воплощать в жизньnpm publishначать публикацию

npm publish
# + qiangdada-npm-test@1.0.0

на этот раз ты идешьnpmОфициальный сайт может искать и видеть, что вы только что выпустили хороший пакет ~

ii. Опубликуйте библиотеку компонентов

На данный момент в библиотеке компонентов мы написали самую простуюhelloкомпонент, но это не мешает нам опубликовать его наnpmОфициальный сайт и этапы публикации так же просты, как в приведенном выше примере.

Исправлятьpackage.jsonЧасть описания в файле

// npm 包js入口文件改为 lib/vui.js
"main": "lib/vui.js",
// npm 发布出去的包包含的文件
"files": [
  "lib",
  "src",
  "packages"
],
// 将包的属性改为公共可发布的
"private": false,

Примечание, тестnpmКогда пакет выпущен, помните каждый разpackage.jsonсерединаversionВерсия выше, чем в прошлый раз.

начать публикацию

# 打包,输出lib/vui.js
npm run build:vui
# 发布
npm publish
# + component-library-test@1.0.1

iii. Использование компонентов, которые мы публикуем в npm

Выберите vue Project локальное присутствие в проекте

npm i component-library-test
# or 
cnpm i component-library-test

Регистрация компонентов в файле ввода проекта

import Vue from 'vue'
import vui from 'component-library-test'
Vue.use(vui)

использовать на странице

<v-hello message="component library"></v-hello>

use-npm

Пока что мы успешно трансформировали локальный служебный файл, реализовали упаковку основного файла библиотеки компонентов и главного входа официального сайта документа и, наконец, научились им пользоваться.npmОпубликовать проект.

В следующей главе яcssУпаковка файла объяснить.

Пять, управление файлами css и упаковка

В предыдущем разделе мы уже упаковали файл js. Но для библиотеки компонентов нам нужно управлять не только файлами js, но и файлами css, чтобы обеспечить последующее использование библиотеки компонентов.

В этом разделе я опишу, какwebpackРазумное использование в проектах на основе сборкиgulpОтдельное управление пакетами для файлов css.

Прежде чем мы начнем, нам нужно определить две цели:

  1. Как управлять файлами css, связанными с компонентами в библиотеке компонентов, куда их поместить для единого управления и как их написать
  2. Как файл css будет упакован и как один компонент будет выводить соответствующий одиночный css

1. Управление файлами CSS

Чтобы облегчить управление, каждый раз, когда мы создаем новый компонент, нам нужно создать соответствующий файл css для управления стилем компонента, чтобы обеспечить единое управление.

я. каталог css

Здесь мы будем хранить все файлы css вpackages/vui-cssВ каталоге конкретная структура выглядит следующим образом

├── src              	
│   ├── common         		存放组件公用的css文件
│   ├── mixins				存放一些mixin的css文件
│   ├── index.css			css主入口文件
│   ├── hello.css			对应hello组件的单一css文件
├── gulpfile.js          	css打包配置文件
├── package.json         	相关的版本依赖

II. Как написать файл css

Перед тем, как мы приступим к написанию css компонента, нам необходимо уточнить несколько моментов:

  1. Когда пользователь вводит библиотеку компонентов и использует стиль в компоненте стиля, он не может конфликтовать с проектом разработки пользователя.
  2. В некоторых особых случаях пользователь может переопределить стиль компонента и изменить его более удобным образом.

Чтобы справиться с этими двумя ситуациями, я лично считаю, что лучший способ на рынке — это управлять компонентами с помощью одного CSS и использоватьbemНапишите css. хочу знатьbemстуденты, нажмите на ссылку ниже

Далее у нас есть простойhelloКомпоненты, чтобы дать объяснение, прежде чем начать, наденьте егоhello.vueСодержание

<template>
  <div class="v-hello">
    <p class="v-hello__message">hello {{ message }}</p>
  </div>
</template>

<script>
export default {
  name: 'v-hello',
  props: {
    message: String
  }
}
</script>

существуетpackages/vui-css/srcСоздано в каталогеhello.css

@b v-hello {
  color: #fff;
  transform: scale(1);

  @e message {
    background: #0067ED;
  }
}

потом у главного входаindex.cssвведен вhello.cssдокумент

@import './hello.css';

существуетexamples/src/index.jsПредставьте стиль библиотеки компонентов в

import 'packages/vui-css/src/index.css'

но отhello.cssсодержание мы можем видеть, что это типичноbemМетод записи не может быть проанализирован нормально. Нам нужно ввести соответствующиеpostcssпара плагиновbemРазобрана грамматика. Здесь мы будем использовать饿了么团队Развитыйpostcss-saladпара плагиновbemразбирается грамматика, а во-вторых, этоsass-likestyle, вам также необходимо использовать плагин под названиемprecss, сначала установите зависимости~

npm i postcss-salad precss -D

После установки зависимостей нам нужно создать новую в корневом каталоге проекта.salad.config.jsonиспользуется для настройкиbemПравила, конкретные правила заключаются в следующем

{
  "browsers": ["ie > 8", "last 2 versions"],
  "features": {
    "bem": {
      "shortcuts": {
        "component": "b",
        "modifier": "m",
        "descendent": "e"
      },
      "separators": {
        "descendent": "__",
        "modifier": "--"
      }
    }
  }
}

Далее нам нужно инициализировать проект.postcssrcиспользуется в файлеpostcss-saladа такжеprecssплагин, как показано ниже

module.exports = {
  "plugins": {
    "postcss-import": {},
    "postcss-salad": require('./salad.config.json'),
    "postcss-url": {},
    "precss": {},
    "autoprefixer": {},
  }
}

ОК, в это время снова запустите проект, вы увидите, что css вступает в силу, как показано на рисунке.

bem

2, упаковка файла css

Чтобы лучше управлять файлами css в библиотеке компонентов, пользователи также могут импортировать файлы css, соответствующие компонентам, когда они хотят импортировать только один или несколько компонентов в библиотеке компонентов. Итак, нам нужно упаковать файл css отдельно, здесь нам нужно использоватьgulpЧтобы выполнить соответствующую операцию упаковки, прежде чем вы начнете получать информацию об упаковке, убедитесь, что вы установили ее глобально.gulp, если нет, то установить

npm i gulp -g
# 查看版本
gulp -v
# CLI version 3.9.1

Далее посмотримpackages/vui-css/package.jsonКакие зависимости нужно использовать в файле

{
  "name": "vui-css",
  "version": "1.0.0",
  "description": "vui css.",
  "main": "lib/index.css",
  "style": "lib/index.css",
   // 和组件发布一样,也需要指定目录
  "files": [
    "lib",
    "src"
  ],
  "scripts": {
    "build": "gulp build"
  },
  "license": "MIT",
  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-cssmin": "^0.2.0",
    "gulp-postcss": "^7.0.1",
    "postcss-salad": "^2.0.1"
  },
  "dependencies": {}
}

Мы видим, что это на самом деле то же самое, что и зависимость от файла css в библиотеке компонентов, но она основана наgulpизpostcssплагин. начать настройкуgulpfile.jsПеред этим не забудьте выполнитьnpm iУстановить зависимости.

Далее приступаем к настройкеgulpfile.js, подробности следующим образом

const gulp = require('gulp')
const postcss = require('gulp-postcss')
const cssmin = require('gulp-cssmin')
const salad = require('postcss-salad')(require('../../salad.config.json'))

gulp.task('compile', function () {
  return gulp.src('./src/*.css')
    // 使用postcss-salad
    .pipe(postcss([salad]))
    // 进行css压缩
    .pipe(cssmin())
    // 输出到 './lib' 目录下
    .pipe(gulp.dest('./lib'))
})

gulp.task('build', ['compile'])

Теперь можно приступить к выполнениюgulp buildКоманда упаковывает файл css. Конечно, чтобы облегчить и лучше выполнить команду упаковки, нам теперь понадобитсяpackage.jsonДобавьте к нему команду сборки css, как показано ниже.

"scripts": {
  "build:vui-css": "gulp build --gulpfile packages/vui-css/gulpfile.js && rimraf lib/vui-css && cp-cli packages/vui-css/lib lib/vui-css && rimraf packages/vui-css/lib"
}

воплощать в жизньnpm run build:vui-css, поехали, файлы js и css финальной упакованной библиотеки компонентов показаны на рисунке ниже

build-vui-css

Хорошо, теперь вы можете вводить компоненты и их стили по отдельности. Наконец, чтобы пользователи могли напрямую использовать css вашего компонента, не забудьте опубликовать его вnpmОфициальный сайт ~ Шаги следующие

# 进到vui-css目录
cd packages/vui-css
# 发布
npm publish

На данный момент мы завершили управление и отдельную упаковку файлов css, а также завершили единый вывод файлов css. Таким образом, мы можем лучше разрабатывать и управлять css-файлами библиотеки компонентов, и в то же время мы можем облегчить использование библиотеки компонентов!

6. Модульное тестирование

На данный момент мы построили новый каталог, необходимый для библиотеки компонентов, мы также изменили упаковку файлов js и css, и мы сделали существенную подготовку для разработки библиотеки компонентов, но нам все еще нужно сделать некоторые очень важная предварительная работа для облегчения разработки и обслуживания последующих компонентов библиотеки компонентов.

Для фронтенд-тестирования это важная ветвь фронтенд-инжиниринга, так как же мы можем упустить такой важный угол в нашей библиотеке компонентов? Для модульного тестирования в основном есть два типа

  • TDD (Разработка через тестирование): Разработка через тестирование, ориентированная на выходные результаты.
  • BDD (разработка, управляемая поведением): разработка, управляемая поведением, с упором на логику тестирования.

В этой главе я покажу вам, как использовать встроенную инициализацию на основе проекта.Karma + MochaЭти две платформы модульно тестируют компоненты в нашей библиотеке компонентов.

1. Введение в фреймворк

дляKarma + MochaЯ полагаю, что эти два фреймворка не будут незнакомы большинству людей, подвергшихся модульному тестированию, но здесь я думаю, что необходимо открыть отдельный раздел, чтобы дать краткое введение в эти два фреймворка.

I. Структура кармы

  • KarmaЭто инструмент управления процессом выполнения тестов JavaScript на основе Node.js (Test Runner).
  • Karmaэто инструмент тестирования, который позволяет тестировать ваш код в среде браузера.
  • KarmaСделайте так, чтобы ваш код автоматически запускался в нескольких браузерах, таких как chrome, firefox и т. д.

Чтобы компоненты в нашей библиотеке компонентов могли работать во всех основных веб-браузерах для тестирования, мы выбралиKarma. самое главное этоKarmaдаvue-cliРекомендуемая среда модульного тестирования. Если вы хотите узнать больше оKarmaвведение, см.официальный сайт кармы

2. Мокко Фреймворк

  • MochaЯвляетсяsimple,flexible,funтестовая среда
  • MochaПоддерживает асинхронные тестовые случаи, такие какPromise
  • MochaПокрытие кода поддержкиcoverageотчет об испытаниях
  • Mochaпозволяет вам использовать любую библиотеку утверждений, которую вы хотите, напримерchai,should.js(стиль BDD),expect.jsтак далее
  • Mochaпри условииbefore(), after(), beforeEach(), так же какafterEach()Четыре функции хука, которые нам удобны для установки разных операций на разных этапах, чтобы лучше завершить наши тесты.

Здесь я представляюmochaТри основных использования , иdescribeЧетыре функции крючка (жизненный цикл)

  1. описать (имя модуля, функция): describeявляется вложенным, описываяпрецедентэто правильно или нет

    describe('测试模块的描述', () => {
      // ....
    });
    
  2. **it(информация, функция):** aitСоответствует модульному тесту

    it('单元测试用例的描述', () => {
      // ....
    })
    
  3. Использование библиотеки утверждений

    expect(1 + 1).to.be.equal(2)
    
  4. describeжизненный цикл

    describe('Test Hooks', function() {
    
      before(function() {
        // 在本区块的所有测试用例之前执行
      });
    
      after(function() {
        // 在本区块的所有测试用例之后执行
      });
    
      beforeEach(function() {
        // 在本区块的每个测试用例之前执行
      });
    
      afterEach(function() {
        // 在本区块的每个测试用例之后执行
      });
    
      // test cases
    });
    

хочу узнать большеmochaСтуденты, которые работают, могут щелкнуть ссылку ниже, чтобы просмотреть

  1. официальный сайт мокко
  2. Учебное пособие по тестовому фреймворку Mocha

2. Практика модульного тестирования

В приведенном выше разделе я дал вам краткое введение в среду тестирования, официально рекомендованную Vue.Karmaа такжеMocha, я также надеюсь, что, увидев это, вы сможете получить простое представление о модульном тестировании и общих средах тестирования.

I. Модульное тестирование компонента приветствия

Прежде чем начнется фактическое модульное тестирование, давайте посмотримKarmaконфигурация, здесь мы смотрим непосредственноvue-cliЛеса инициализированыkarma.conf.jsКонфигурация в файле (я сделал комментарий для конкретного использования)

var webpackConfig = require('../../build/webpack.test.conf')

module.exports = function karmaConfig (config) {
  config.set({
    // 浏览器
    browsers: ['PhantomJS'],
    // 测试框架
    frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
    // 测试报告
    reporters: ['spec', 'coverage'],
    // 测试入口文件
    files: ['./index.js'],
    // 预处理器 karma-webpack
    preprocessors: {
      './index.js': ['webpack', 'sourcemap']
    },
    // webpack配置
    webpack: webpackConfig,
    // webpack中间件
    webpackMiddleware: {
      noInfo: true
    },
    // 测试覆盖率报告
    coverageReporter: {
      dir: './coverage',
      reporters: [
        { type: 'lcov', subdir: '.' },
        { type: 'text-summary' }
      ]
    }
  })
}

Далее мы приходим к своимhelloКомпонент просто тестируется (пишется только один тест-кейс), вtest/unit/specsновыйhello.spec.jsфайл и напишите следующий код

import Vue from 'vue' // 导入Vue用于生成Vue实例
import Hello from 'packages/hello' // 导入组件
// 测试脚本里面应该包括一个或多个describe块,称为测试套件(test suite)
describe('Hello.vue', () => {
  // 每个describe块应该包括一个或多个it块,称为测试用例(test case)
  it('render default classList in hello', () => {
    const Constructor = Vue.extend(Hello) // 获得Hello组件实例
    const vm = new Constructor().$mount() // 将组件挂在到DOM上
    // 断言:DOM中包含class为v-hello的元素
    expect(vm.$el.classList.contains('v-hello')).to.be.true
    const message = vm.$el.querySelector('.v-hello__message')
    // 断言:DOM中包含class为v-hello__message的元素
    expect(message.classList.contains('v-hello__message')).to.be.true
  })
})

После того, как тестовый пример написан, следующим шагом является тестирование. воплощать в жизньnpm run test, иди ты ~ , выводи результат

hello.vue
    ✓ render default classList in hello

2. Оптимизируйте модульные тесты

сверхуhelloКак видно из тестового экземпляра компонента, нам нужно создать экземпляр компонента как экземпляр Vue и иногда монтировать его в DOM.

const Constructor = Vue.extend(Hello)
const vm = new Constructor({
  propsData: {
    message: 'component'
  }
}).$mount()

Если в будущем каждый компонент будет иметь несколько экземпляров модульного теста, этот метод написания приведет к раздуванию нашего окончательного теста.Здесь мы можем обратиться кelementупакованныйИнструмент модульного тестирования util.js. Нам нужно инкапсулировать некоторые из методов, обычно используемых Vue в модульном тестировании.Ниже я перечислю некоторые из методов, предоставляемых инструментом.

/**
 * 回收 vm,一般在每个测试脚本测试完成后执行回收vm。
 * @param  {Object} vm
 */
exports.destroyVM = function (vm) {}

/**
 * 创建一个 Vue 的实例对象
 * @param  {Object|String}  Compo     - 组件配置,可直接传 template
 * @param  {Boolean=false}  mounted   - 是否添加到 DOM 上
 * @return {Object} vm
 */
exports.createVue = function (Compo, mounted = false) {}

/**
 * 创建一个测试组件实例
 * @param  {Object}  Compo          - 组件对象
 * @param  {Object}  propsData      - props 数据
 * @param  {Boolean=false} mounted  - 是否添加到 DOM 上
 * @return {Object} vm
 */
exports.createTest = function (Compo, propsData = {}, mounted = false) {}

/**
 * 触发一个事件
 * 注: 一般在触发事件后使用 vm.$nextTick 方法确定事件触发完成。
 * mouseenter, mouseleave, mouseover, keyup, change, click 等
 * @param  {Element} elm      - 元素
 * @param  {String} name      - 事件名称
 * @param  {*} opts           - 配置项
 */
exports.triggerEvent = function (elm, name, ...opts) {}

/**
 * 触发 “mouseup” 和 “mousedown” 事件,既触发点击事件。
 * @param {Element} elm     - 元素
 * @param {*} opts          - 配置选项
 */
exports.triggerClick = function (elm, ...opts) {}

Далее мы будем использовать определенный метод инструмента тестирования, преобразованиеhelloТестовый экземпляр компонента, который будетhello.spec.jsфайл для трансформации

import { destroyVM, createTest } from '../util'
import Hello from 'packages/hello'

describe('hello.vue', () => {
  let vm
  // 测试用例执行之后销毁实例
  afterEach(() => {
    destroyVM(vm)
  })
  it('render default classList in hello', () => {
    vm = createTest(Hello)
    expect(vm.$el.classList.contains('v-hello')).to.be.true
    const message = vm.$el.querySelector('.v-hello__message')
    expect(message.classList.contains('v-hello__message')).to.be.true
  })
})

сделай это сноваnpm run test, выведите результат

hello.vue
    ✓ render default classList in hello

3. Более широкое использование модульных тестов

Выше мы представили использование статических суждений в модульном тестировании, а затем протестируем некоторые асинхронные варианты использования и некоторые интерактивные события. Перед тестированием нам нужно изменить нашhelloКод компонента выглядит следующим образом

<template>
  <div class="v-hello" @click="handleClick">
    <p class="v-hello__message">hello {{ message }}</p>
  </div>
</template>

<script>
export default {
  name: 'v-hello',
  props: {
    message: String
  },
  methods: {
    handleClick () {
      return new Promise((resolve) => {
        resolve()
      }).then(() => {
        this.$emit('click', 'this is click emit')
      })
    }
  }
}
</script>

Дальше будем тестироватьhelloМожет ли компонент успешно передать информацию через PromiseemitВыходите, тестовый пример выглядит следующим образом

it('create a hello for click with promise', (done) => {
  let result
  vm = createVue({
    template: `<v-hello @click="handleClick"></v-hello>`,
    methods: {
      handleClick (msg) {
        result = msg
      }
    }
  }, true)
  vm.$el.click()
  // 断言消息是异步emit出去的
  expect(result).to.not.exist
  setTimeout(_ => {
    expect(result).to.exist
    expect(result).to.equal('this is click emit')
    done()
  }, 20)
})

перезапустить тест, выполнитьnpm run test, выведите результат

hello.vue
    ✓ render default classList in hello
    ✓ create a hello for click with promise

До сих пор мы изучили конфигурацию модульного тестирования и некоторые общие способы использования. Если вам нужно узнать больше о модульном тестировании, перейдите по ссылке, которую я предоставил ранее, для более глубокого погружения.

Семь, разработка официального веб-сайта документа (1)

Друзья, следуйте за мной, чтобы применить первые 5 глав на практике и создать базовую основу для разработки нашего компонента. Далее я возьму вас всех, чтобы закончить официальный сайт документов с высокими важными компонентами в библиотеке компонентов.

Каждый должен знать, что у хорошего проекта с открытым исходным кодом должен быть официальный веб-сайт документа, поэтому, чтобы сделать нашу библиотеку пользовательского интерфейса также частью превосходного, у нас также должен быть собственный официальный веб-сайт документа.

Хороший официальный сайт документации должен делать две вещи.

  1. Разберите API собственного проекта с открытым исходным кодом, чтобы пользователям было удобнее его использовать
  2. Есть демо-пример, чтобы пользователи могли увидеть эффект онлайн

Поскольку в этом сообщении в блоге библиотека компонентов, которую я предлагаю вам разработать, адаптирована для мобильного терминала, как мы можем сделать так, чтобы на нашем официальном веб-сайте документации было как описание документа API, так и демонстрация примера мобильного терминала. Это требует от нас разработки двух наборов страниц для адаптации. Нам нужно сделать следующее:

  • Документация по API компонента дисплея ПК
  • Демонстрация компонента мобильного дисплея
  • Маршруты генерируются динамически

Прежде чем приступить к реальному бою, давайте взглянем на структуру каталогов, которую нам нужно использовать в этой главе.

├── assets						css,图片等资源都在这
├── dist                     	打包好的文件都在这
├── docs                     	PC端需要展示的markdown文件都在这
├── pages                     	移动端所有的demo都在这
├── src              	      	
│   ├── components            	demo中可以复用的模块放在这里面
│   ├── index.tpl              	页面入口
│   ├── is-mobile.js            判断设备
│   ├── index.js              	PC端主入口js
│   ├── App.vue               	PC端入口文件
│   ├── mobile.js              	移动端端主入口js
│   ├── MobileApp.vue           移动端入口文件
│   ├── nav.config.json			路由控制文件
│   ├── router.config.js		动态注册路由

В этой главе вы в основном познакомитесь с преобразованием файлов уценки и адаптацией маршрутизации для различных устройств.

После того, как идея ясна, давайте продолжим нашу практику разработки официального веб-сайта документа!

1. Преобразование файлов уценки

Как вы можете видеть из каталога, который я указал выше, все файлы уценки хранятся в папке docs, и каждый файл уценки соответствует документации API компонента. В результате мы хотим преобразовать каждый файл уценки в документации, чтобы сделать его компонентом Vue, и зарегистрировать преобразованный компонент Vue в маршруте, чтобы он мог получить доступ к каждому файлу уценки через маршрут.

Для разбора файлов уценки в компоненты Vue на рынке есть много трех сторон.webpackплагин, конечно если ты правwebpackЕсли вы более опытны, вы также можете попробовать дать пощечину самостоятельно. Здесь я использую его напрямую饿了么团队развитыйvue-markdown-loader.

I. Используйте загрузчик vue-markdown

Первый шаг — установка зависимостей

npm i vue-markdown-loader -D

Второй шаг, вwebpack.base.conf.jsиспользуется в файлеvue-markdown-loader

{
  test: /\.md$/,
  loader: 'vue-markdown-loader',
  options: {
    // 阻止提取脚本和样式标签
    preventExtract: true
  }
}

Третий шаг, попробуйте попробовать. доdocsдобавить внутрьhello.mdфайл, а затем написатьhelloИнструкции по использованию компонентов

## Hello
**Hello 组件,Hello 组件,Hello 组件,Hello 组件**
### 基本用法
​```html
<template>
  <div class="hello-page">
    <v-hello message="my component library" @click="handleClick"></v-hello>
    <p>{{ msg }}</p>
  </div>
</template>
<script>
export default {
  name: 'hello',
  data () {
    return {
      msg: ''
    }
  },
  methods: {
    handleClick (msg) {
      this.msg = msg
    }
  }
}
</script>
​```
### Attributes
| 参数      | 说明    | 类型      | 可选值       | 默认值   |
|---------- |-------- |---------- |-------------  |-------- |
| message  | 文本信息    | string   | — | — |
### Events
| 事件名称      | 说明    | 回调参数      |
|---------- |-------- |---------- |
| click  | 点击操作    | — |

Четвертый шаг, будетhello.mdзарегистрироваться на маршрут

route.push({
  path: '/component/hello',
  component: require('../docs/hello.md')
})

Наконец, посетите страницу. В это время можно найтиhello.mdКонтент был преобразован в компоненты Vue, и к нему можно получить доступ через загрузку маршрута, но страница уродлива и уродлива ~ вот так

markdown

2. Добавьте тему и стиль выделения в md.

Конечно, мне нет нужды объяснять эту ситуацию, и все это могут знать. Да, проанализированный файл уценки такой уродливый, только потому, что мы не добавили тему выделения в наш файл уценки и не установили базовый стиль страницы документа. Итак, далее нам нужно добавить красивую тему выделения и чистые базовые стили в наши файлы уценки.

Для тем здесь мы будем использоватьhighlight.jsвнутриatom-one-darkтема.

Первый шаг, установкаhighlight.js

npm i highlight -D

Второй шаг, вexamples/src/App.vueПредставьте тему, и для того, чтобы установить основной стиль документа, нам также нужно изменить макет App.vue.

<template>
  <div class="app">
    <div class="main-content">
      <div class="page-container clearfix">
        <div class="page-content">
          <router-view></router-view>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import 'highlight.js/styles/atom-one-dark.css'
export default {
  name: 'App'
}
</script>

Третий шаг — установить основной стиль документа. существуетassetsЧжунсинdocs.css, напишите начальный стиль, так как объем кода слишком велик, он не будет размещен здесь. Каждый может копироватьdocs.cssВнутри кода для местногоdocs.cssфайл, затем вexamples/src/index.jsимпортировать в

import '../assets/docs.css'

Наконец, преобразуйте правила синтаксического анализа уценки,vue-markdown-loaderпредоставилpreprocessИнтерфейс дает нам свободу действий.Далее давайте определим структуру анализируемого файла уценки.webpack.base.conf.jsзаписать в файл

// 定义辅助函数wrap,将<code>标签都加上名为'hljs'的class
function wrap (render) {
  return function() {
    return render.apply(this, arguments)
      .replace('<code v-pre class="', '<code class="hljs ')
      .replace('<code>', '<code class="hljs">')
  }
}
// ...
{
  test: /\.md$/,
  loader: 'vue-markdown-loader',
  options: {
    preventExtract: true,
    preprocess: function(MarkdownIt, source) {
      // 为table标签加上名为'table'的class
      MarkdownIt.renderer.rules.table_open = function() {
        return '<table class="table">'
      };
      MarkdownIt.renderer.rules.fence = wrap(MarkdownIt.renderer.rules.fence);
      return source;
    }
  }
}

Затем снова посетитеlocalhost:8080/#/component/hello

markdown

Итак, наш md-файл был успешно разобран на компоненты Vue, и у нас есть красивая тема выделения и простые базовые стили~

2. Адаптация маршрутизации в различных средах устройств

Как я уже говорил ранее, библиотека компонентов, разработанная в этой статье, адаптирована для мобильного терминала, поэтому нам нужно отобразить документ на стороне ПК, а демонстрацию — на мобильном терминале.

В этом разделе я проведу вас через адаптацию различных конечных маршрутов. Конечно, это не сложно, в основном с помощью веб-пакета для создания многостраничных функций, так как это сделать конкретно? Ладно, без лишних слов, приступим

I. Регистрация входного файла

Первый шаг — зарегистрировать файл записи js, вwebpack.base.conf.jsзаписать в файл

entry: {
  // ...
  'vui': './examples/src/index.js',  // PC端入口js
  'vui-mobile': './examples/src/mobile.js'  // 移动端入口js
}

Второй шаг, вход на страницу регистрации, вwebpack.base.conf.jsзаписать в файл

plugins: [
  // ...
  // PC端页面入口
  new HtmlWebpackPlugin({
    chunks: ['manifest', 'vendor', 'vui'],
    template: 'examples/src/index.tpl',
    filename: 'index.html',
    inject: true
  }),
  // 移动端页面入口
  new HtmlWebpackPlugin({
    chunks: ['manifest', 'vendor', 'vui-mobile'],
    template: 'examples/src/index.tpl',
    filename: 'mobile.html',
    inject: true
  })
]

ii. Оценка окружающей среды оборудования

Регистрация входного файла завершена, следующее, что нам нужно сделать, это определить среду устройства. Здесь я буду использоватьnavigator.userAgentС помощью регулярных выражений мы можем определить, принадлежит ли среда, в которой работает наша библиотека компонентов, терминалу ПК или мобильному терминалу?

Первый шаг, вexamples/src/is-mobile.jsНапишите следующий код в файле

/* eslint-disable */
const isMobile = (function () {
  var platform = navigator.userAgent.toLowerCase()
  return (/(android|bb\d+|meego).+mobile|kdtunion|weibo|m2oapp|micromessenger|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i).test(platform) ||
  (/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i).test(platform.substr(0, 4));
})()
// 返回设备所处环境是否为移动端,值为boolean类型
export default isMobile

Второй шаг, файл входа js на стороне ПКexamples/src/index.jsЗапишите следующие правила суждения в

import isMobile from './is-mobile'
// 是否为生产环境
const isProduction = process.env.NODE_ENV === 'production'
router.beforeEach((route, redirect, next) => {
  if (route.path !== '/') {
    window.scrollTo(0, 0)
  }
  // 获取不同环境下,移动端Demo对应的地址
  const pathname = isProduction ? '/vui/mobile' : '/mobile.html'
  // 如果设备环境为移动端,则直接加载移动端Demo的地址
  if (isMobile) {
    window.location.replace(pathname)
    return
  }
  document.title = route.meta.title || document.title
  next()
})

Третий шаг — ввести файл ввода js на мобильной стороне.examples/src/mobile.jsНапишите правило принятия решений аналогично предыдущему шагу в

import isMobile from './is-mobile'
const isProduction = process.env.NODE_ENV === 'production'
router.beforeEach((route, redirect, next) => {
  if (route.path !== '/') {
    window.scrollTo(0, 0)
  }
  // 获取不同环境下,PC端对应的地址
  const pathname = isProduction ? '/vui/mobile' : '/mobile.html'
  // 如果设备环境不是移动端,则直接加载PC端的地址
  if (!isMobile) {
    window.location.replace(pathname)
    return
  }
  document.title = route.meta.title || document.title
  next()
})

Наконец-то идеальноexamples/src/mobile.jsФайл и вход на мобильную страницуMobileApp.vueдокумент

существуетexamples/src/mobile.jsНапишите следующий код в

import Vue from 'vue'
import VueRouter from 'vue-router'
import MobileApp from './MobileApp'
import Vui from 'src/index'
import isMobile from './is-mobile.js'
import Hello from '../pages/hello.vue'

import 'packages/vui-css/src/index.css'

Vue.use(Vui)
Vue.use(VueRouter)

const isProduction = process.env.NODE_ENV === 'production'
const router = new VueRouter({
  base: isProduction ? '/vui/' : __dirname,
  routes: [{
    path: '/component/hello',
    component: Hello
  }]
})
router.beforeEach((route, redirect, next) => {
  if (route.path !== '/') {
    window.scrollTo(0, 0)
  }
  const pathname = isProduction ? '/vui/' : '/'
  if (!isMobile) {
    window.location.replace(pathname)
    return
  }
  document.title = route.meta.title || document.title
  next()
})

new Vue({
  el: '#app-container',
  router,
  components: { MobileApp },
  template: '<MobileApp/>'
})

существуетMobileApp.vueнаписать в

<template>
  <div class="mobile-container">
      <router-view></router-view>
  </div>
</template>

Затем вы можете перейти в браузер, чтобы попробовать эффект и посмотреть, могут ли различные среды устройств отображать соответствующий контент~

На этом план, который мы составили в этой главе, выполнен. «Идеальное» преобразование файлов md и адаптация маршрутизации в различных средах устройств. На этом разработка официального сайта документа (Часть 1) подходит к концу.В следующей главе мы продолжим завершать оставшиеся работы по разработке официального сайта документа!

Восемь, задокументируйте разработку официального веб-сайта (ниже)

В предыдущей главе мы выполнили:

  1. Преобразование файлов уценки с красивыми темами и стилями выделения
  2. Адаптация официального веб-сайта документа в различных средах устройств

В этой главе мы улучшим детали официального веб-сайта документа и разработаем полный официальный веб-сайт документа.

1. Управление маршрутизацией

Из каталога, приведенного в предыдущей главе, мы можем узнать, что каталог docs используется для хранения файлов md, которые необходимо отобразить на ПК, а каталог pages используется для хранения демо-файлов для мобильных устройств. Итак, как заставить компонент отображать соответствующие файлы в разных средах устройств (файл md, соответствующий компоненту дисплея ПК, и файл vue, соответствующий компоненту мобильного дисплея)? В таком случае, как мы можем разумно управлять маршрутизацией нашей библиотеки компонентов? Далее мы продолжаем следующие разработки по этим вопросам. обязательно пригодится здесьis-mobile.jsПерейдите, чтобы определить среду оборудования, следуйте за мной, чтобы медленно выполнять конкретную работу.

Первый шаг, вexamples/srcновый файл подnav.config.jsonфайл, напишите следующее

{
  // 为了之后组件文档多语言化
  "zh-CN": [
    {
      "name": "Vui 组件",
      "showInMobile": true,
      "groups": [
        {
		  // 管理相同类型下的所有组件
          "groupName": "基础组件",
		  "list": [
		    {
			  // 访问组件的相对路径
              "path": "/hello",
              // 组件描述
			  "title": "Hello"
			}
          ]
        }
      ]
    }
  ]
}

Второй шаг – улучшитьrouter.config.jsфайл, измените его на вспомогательную функцию для регистрации маршрута

const registerRoute = (navConfig, isMobile) => {
  let route = []
  // 目前只有中文版的文档
  let navs = navConfig['zh-CN']
  // 遍历路由文件,逐一进行路由注册
  navs.forEach(nav => {
    if (isMobile && !nav.showInMobile) {
      return
    }

    if (nav.groups) {
      nav.groups.forEach(group => {
        group.list.forEach(nav => {
          addRoute(nav)
        })
      })
    } else if (nav.children) {
      nav.children.forEach(nav => {
        addRoute(nav)
      })
    } else {
      addRoute(nav)
    }
  })
  // 进行路由注册
  function addRoute (page) {
    // 不同的设备环境引入对应的路由文件
    const component = isMobile
      ? require(`../pages${page.path}.vue`)
      : require(`../docs${page.path}.md`)
    route.push({
      path: '/component' + page.path,
      component: component.default || component
    })
  }

  return route
}

export default registerRoute

Третий шаг, основная запись js файла на стороне ПКexamples/src/index.jsи JS-файл мобильной основной записиexamples/src/mobile.jsВнутри зарегистрированного маршрута напишите следующий код

import registerRoute from './router.config'
import navConfig from './nav.config'

const routesConfig = registerRoute(navConfig)
const router = new VueRouter({
  routes: routesConfig
})

Тогда посетите официальный сайт нашей текущей библиотеки компонентов.

2. Отображение API ПК

Из окончательных визуализаций в предыдущей главе мы видим, что сторона ПК разделена на три части, а именно:

  1. Шапка, краткое описание библиотеки компонентов и ссылка на гитхаб проекта
  2. Левая колонка, маршрутизация компонентов и отображение заголовка
  3. Правая колонка, отображение документации API компонента

Далее приступим к завершению отображения API на стороне ПК

я голова

Голова относительно проста, нам нужно толькоexamples/src/componentsпод новымpage-header.vueфайл, напишите следующее

<template>
  <div class="page-header">
    <div class="page-header__top">
      <h1 class="page-header__logo">
        <a href="#">Vui.js</a>
      </h1>
      <ul class="page-header__navs">
        <li class="page-header__item">
          <a href="/" class="page-header__link">组件</a>
        </li>
        <li class="page-header__item">
          <a href="https://github.com/Brickies/vui" class="page-header__github" target="_blank"></a>
        </li>
        <li class="page-header__item">
          <span class="page-header__link"></span>
        </li>
      </ul>
    </div>
  </div>
</template>

Для конкретных стилей, пожалуйста, посетите непосредственноpage-header.vueсмотреть

2. Левая колонка

В левом столбце мы отображаем маршрутизацию компонентов и заголовки. На самом деле это правильноexamples/src/nav.config.jsonАнализировать и показывать.

мы вexamples/src/componentsпод новымside-nav.vueфайл, нормальная структура файла выглядит следующим образом

<li class="nav-item">
  <a href="javascript:void(0)">Vui 组件</a>
  <div class="nav-group">
    <div class="nav-group__title">基础组件</div>
    <ul class="pure-menu-list">
      <li class="nav-item">
        <router-link
           active-class="active"
           :to="/component/hello"
           v-text="navItem.title">Hello
        </router-link>
      </li>
    </ul>
  </div>
</li>

Но мы сейчас собираемсяexamples/src/nav.config.jsonДля анализа улучшенный код выглядит следующим образом

<li class="nav-item" v-for="item in data">
  <a href="javascript:void(0)" @click="handleTitleClick(item)">{{ item.name }}</a>
  <template v-if="item.groups">
    <div class="nav-group" v-for="group in item.groups">
      <div class="nav-group__title">{{ group.groupName }}</div>
      <ul class="pure-menu-list">
        <template v-for="navItem in group.list">
          <li class="nav-item" v-if="!navItem.disabled">
            <router-link
              active-class="active"
              :to="base + navItem.path"
              v-text="navItem.title" />
          </li>
        </template>
      </ul>
    </div>
  </template>
</li>

Полный код нажмите здесьside-nav.vue

iii. App.vue

мы пишем нашpage-header.vueа такжеside-nav.vueДва файла находятся вApp.vueиспользуется в

<template>
  <div class="app">
    <page-header></page-header>
    <div class="main-content">
      <div class="page-container clearfix">
        <side-nav :data="navConfig['zh-CN']" base="/component"></side-nav>
        <div class="page-content">
          <router-view></router-view>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import 'highlight.js/styles/atom-one-dark.css'
import navConfig from './nav.config.json'
import PageHeader from './components/page-header'
import SideNav from './components/side-nav'

export default {
  name: 'App',
  components: { PageHeader, SideNav },
  data () {
    return {
      navConfig: navConfig
    }
  }
}
</script>

Затем снова зайдите на страницу, результат будет таким, как показано на рисунке.

api-7

3. Мобильная демонстрация

Принципы демо-версии для мобильных устройств и версии для ПК схожи, и их необходимо анализировать.nav.config.jsonфайл для отображения

I. Компоненты мобильной домашней страницы

В настоящее время помимо главной страницы входа нашего мобильного терминалаMobileApp.vueКроме того, нет зависимости от компонента корневого каталога.Далее мы сначала завершим разработку компонента корневого каталога.examples/src/componentsпод новымdemo-list.vueфайл, написать что-нибудь

<template>
  <div class="side-nav">
    <h1 class="vui-title"></h1>
    <h2 class="vui-desc">VUI 移动组件库</h2>
  </div>
</template>

Затем нам нужно сослаться на него в маршруте, вmobile.jsзаписать в файл

import DemoList from './components/demo-list.vue'
routesConfig.push({
  path: '/',
  component: DemoList
})

Затем приступайте к совершенствованиюdemo-list.vueдокумент

<template>
  <div class="side-nav">
    <h1 class="vui-title"></h1>
    <h2 class="vui-desc">VUI 移动组件库</h2>
    <div class="mobile-navs">
      <div v-for="(item, index) in data" :key="index">
        <div class="mobile-nav-item" v-if="item.showInMobile">
          <mobile-nav v-for="(group, s) in item.groups" :group="group" :base="base" :key="s"></mobile-nav>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import navConfig from '../nav.config.json';
import MobileNav from './mobile-nav';

export default {
  data() {
    return {
      data: navConfig['zh-CN'],
      base: '/component'
    };
  },

  components: {
    MobileNav
  }
};
</script>

<style lang="postcss">
.side-nav {
  width: 100%;
  box-sizing: border-box;
  padding: 90px 15px 20px;
  position: relative;
  z-index: 1;

  .vui-title,
  .vui-desc {
    text-align: center;
    font-weight: normal;
    user-select: none;
  }

  .vui-title {
    padding-top: 40px;
    height: 0;
    overflow: hidden;
    background: url(https://raw.githubusercontent.com/xuqiang521/vui/master/src/assets/logo.png) center center no-repeat;
    background-size: 40px 40px;
    margin-bottom: 10px;
  }

  .vui-desc {
    font-size: 14px;
    color: #666;
    margin-bottom: 50px;
  }
}
</style>

Здесь мы цитируемmobile-nav.vueфайл, который также является компонентом отображения мобильного демонстрационного списка, который мы завершим далее.

ii. навигационный список

существуетexamples/src/componentsпод новымmobile-nav.vueфайл, разборnav.config.jsonфайл для отображения демонстрационного списка.

<template>
  <div class="mobile-nav-group">
    <div
      class="mobile-nav-group__title mobile-nav-group__basetitle"
      :class="{
        'mobile-nav-group__title--open': isOpen
      }"
      @click="isOpen = !isOpen">
      {{group.groupName}}
    </div>
    <div class="mobile-nav-group__list-wrapper" :class="{ 'mobile-nav-group__list-wrapper--open': isOpen }">
      <ul class="mobile-nav-group__list" :class="{ 'mobile-nav-group__list--open': isOpen }">
        <template v-for="navItem in group.list">
          <li
            class="mobile-nav-group__title"
            v-if="!navItem.disabled">
            <router-link
              active-class="active"
              :to="base + navItem.path">
              <p>
                {{ navItem.title }}
              </p>
            </router-link>
          </li>
        </template>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    group: {
      type: Object,
      default: () => {
        return [];
      }
    },
    base: String
  },
  data() {
    return {
      isOpen: false
    };
  }
};
</script>

Затем напишите стиль списка

<style lang="postcss">
@component-namespace mobile {
  @b nav-group {
    border-radius: 2px;
    margin-bottom: 15px;
    background-color: #fff;
    box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);

    @e basetitle {
      padding-left: 20px;
    }

    @e title {
      font-size: 16px;
      color: #333;
      line-height: 56px;
      position: relative;
      user-select: none;

      @m open {
        color: #38f;
      }

      a {
        color: #333;
        display: block;
        user-select: none;
        padding-left: 20px;
        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);

        &:active {
          background: #ECECEC;
        }

        > p {
          border-top: 1px solid #e5e5e5;
        }
      }
    }

    @e list-wrapper {
      height: 0;
      overflow: hidden;

      @m open {
        height: auto;
      }
    }

    @e list {
      transform: translateY(-50%);
      transition: transform .2s ease-out;

      @m open {
        transform: translateY(0);
      }
    }

    li {
      list-style: none;
    }

    ul {
      padding: 0;
      margin: 0;
      overflow: hidden;
    }
  }
}
</style>

Далее, повторно посетитеhttp://localhost:8080/mobile.html, вы можете получить ожидаемые результаты без каких-либо сюрпризов

Итак, наша «грубая» полка библиотеки компонентов построена.

Сообщение в блоге почти закончено, и весь код в статье размещен.githubВыше я напишу статью в будущем, чтобы помочь вам постепенно улучшить некоторые детали в нашей библиотеке компонентов, чтобы наша библиотека компонентов могла быть более совершенной.

адрес гитхаба:GitHub.com/Сюй Цян521/…

Очередная волна рекламы в конце статьи ~~~

Группа внешней связи: 731175396