Написано 2017.06.05
ранее опубликованный«Vue-Donut — среда разработки, предназначенная для создания библиотеки компонентов пользовательского интерфейса Vue», является лишь грубым введением в структуру и не описывает реализацию в деталях.
Недавно участвовал в поддержке библиотеки компонентов мобильного интерфейса внутри компании, в которой отсутствует документация и строгая структура организации файлов.Vue-Donut
Функция относительно проста, и создавать документы и превью для библиотеки компонентов мобильного интерфейса неудобно. в отношенииmint-ui
Дождавшись зрелых решений в отрасли, яVue-Donut
На основе расширения был наконец построен очень удобный и автоматизированный фреймворк для разработки.
Поскольку я думаю, что процесс разработки очень интересен, и я хочу записывать свои собственные идеи разработки, я решил написать статью в качестве записи, чтобы поделиться.
адрес проекта:GitHub.com/назвал дождь AU/vu…
1. Функциональный анализ
Во-первых, давайте спланируем, какова конечная цель этого фреймворка:
Как показано на рисунке, с помощью этой платформы можно создать страницу документации. Эта страница разделена на три раздела: Навигация, Документация и Предварительный просмотр.
-
Навигация: навигация по документации и предварительным просмотрам различных компонентов.
-
Документ: документ, соответствующий этому типу компонента, написан в форме уценки.
-
Предварительный просмотр: страница предварительного просмотра, соответствующая этому типу компонента.
Чтобы повысить эффективность разработки компонентов и обслуживания документации, мы хотим, чтобы эта структура была более автоматизированной. Если нам нужно только открыть страницы предварительного просмотра различных компонентов и их соответствующую документациюREADME
, платформа может автоматически помочь нам создать соответствующую навигацию и HTML-контент, разве это не замечательно? Кроме того, когда мы разработали все компоненты пользовательского интерфейса, все они размещены в/components
В каталоге, если вы можете собрать и упаковать одним щелчком мыши через фреймворк и, наконец, создать пакет npm, другим будет очень легко использовать эту библиотеку компонентов пользовательского интерфейса. Имея в виду эту идею, давайте проанализируем ключевые технологии, которые нам могут понадобиться.
2. Технический анализ
-
Используйте webpack2 в качестве ядра фреймворка: простой в использовании и с широкими возможностями настройки. При этом документация по webpack2 достаточно полная, экосистема процветающая, сообщество активное, а встречающиеся ямки в основном можно найти в google и stackoverflow.
-
Предварительный просмотр страницы с
iframe
Вставьте его на страницу документа в виде: при ведении библиотеки компонентов нужно сосредоточиться только на разработке компонента и организации страницы предварительного просмотра, не отвлекая на ведение навигации и документации, добиться развязки. Итак, это означает, что это основано на Vue.jsмногостраничное приложение. -
Автоматически генерировать навигацию: используйте
vue-router
Переключить страницы. Всякий раз, когда создается новая страница предварительного просмотра, соответствующая навигация будет автоматически генерироваться на странице, и связь между навигацией и маршрутизацией будет автоматически поддерживаться. Поэтому нам нужен механизм для отслеживания изменений в файловой структуре. -
Автоматическое создание документа: страница предварительного просмотра соответствует документу, поэтому документ должен начинаться с
README.md
хранятся в соответствующей папке страницы предварительного просмотра. нам нужноREADME.md
Преобразование непосредственно в HTML-контент. -
Режим разработчика: одной командой запустите
webpack-dev-server
, предоставляя функции горячего обновления и автоматического обновления. -
Режим сборки и упаковки: автоматически ставить
/components
Все ресурсы в каталоге упакованы в пакет npm. -
Режим построения страницы: создавайте статические файлы ресурсов, которые можно напрямую развернуть и использовать с помощью одной команды.
Разобравшись с технологией, мы уже имеем представление в своем сознании, и следующий шаг — развивать его шаг за шагом.
3. Разберитесь со структурой каталогов фреймворка
Хорошая структура каталога может сильно понравиться нашей следующей работе.
.
├── index.html // 文档页的入口html
├── view.html // 预览页的入口html
├── package.json // 依赖声明、npm script命令
├── src
│ ├── document // 文档页目录
│ │ ├── doc-app.vue // 文档页入口.vue文件
│ │ ├── doc-entry.js // 文档页入口.js文件
│ │ ├── doc-router.js // 文档页路由配置
│ │ ├── doc_comps // 文档页组件
│ │ └── static // 文档页静态资源
│ └── view // 预览页目录
│ ├── assets // 预览页静态资源
│ ├── components // UI组件库
│ ├── pages // 存放不同的预览页
│ ├── view-app.vue // 预览页入口.vue文件
│ ├── view-entry.js // 预览页入口.js文件
│ └── view-router.js // 预览页路由配置
└── webpack
├── webpack.base.config.js // webpack通用配置
├── webpack.build.config.js // UI库构建打包配置
├── webpack.dev.config.js // 开发模式配置
└── webpack.doc.config.js // 静态资源构建配置
Как видите, структура каталогов несложная.Далее мы сначала настроим webpack, чтобы можно было запустить проект.
4. конфигурация webapck
4.1 Базовая конфигурация
Войти/webpack
каталог, создайте новыйwebpack.base.config.js
файл со следующим содержимым:
const { join } = require('path')
const hljs = require('highlight.js')
// 配置markdown解析、以便高亮显示markdown中的代码块
const markdown = require('markdown-it')({
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return '<pre class="hljs"><code>' +
hljs.highlight(lang, str, true).value +
'</code></pre>';
} catch (__) {}
}
return '<pre class="hljs"><code>' + markdown.utils.escapeHtml(str) + '</code></pre>';
}
})
const resolve = dir => join(__dirname, '..', dir)
module.exports = {
// 只配置输出路径
output: {
filename: 'js/[name].js',
path: resolve('dist'),
publicPath: '/'
},
// 配置不同的loader以便资源加载
// eslint是标配,建议加上
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader'
]
},
{
enforce: 'pre',
test: /\.vue$/,
loader: 'eslint-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'url-loader'
},
{
test: /\.css$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}]
},
{
test: /\.less$/,
use: [{
loader: 'style-loader' // creates style nodes from JS strings
}, {
loader: 'css-loader' // translates CSS into CommonJS
}, {
loader: 'less-loader' // compiles Less to CSS
}]
},
// vue-markdown-loader能够把.md文件直接转化成vue组件
{
test: /\.md$/,
loader: 'vue-markdown-loader',
options: markdown
}
]
},
resolve: {
// 该项配置能够在加载资源的时候省略后缀名
extensions: ['.js', '.vue', '.json', '.css', '.less'],
modules: [resolve('src'), 'node_modules'],
// 配置路径别名
alias: {
'~src': resolve('src'),
'~components': resolve('src/view/components'),
'~pages': resolve('src/view/pages'),
'~assets': resolve('src/view/assets'),
'~store': resolve('src/store'),
'~static': resolve('src/document/static'),
'~docComps': resolve('src/document/doc_comps')
}
}
}
4.2 Конфигурация режима разработки
После завершения базовой настройки мы можем приступить к настройке режима разработки. В текущем каталоге создайте новыйwebpack.dev.config.js
файл и напишите следующее:
const { join } = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const basicConfig = require('./webpack.base.config')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const resolve = dir => join(__dirname, '..', dir)
module.exports = merge(basicConfig, {
// 由于是多页应用,所以应该有2个入口文件
entry: {
app: './src/document/doc-entry.js',
view: './src/view/view-entry.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
devtool: 'inline-source-map',
// webpack-dev-server配置
devServer: {
contentBase: resolve('/'),
compress: true,
hot: true,
inline: true,
publicPath: '/',
stats: 'minimal'
},
plugins: [
// 热更新插件
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
// 把生成的js注入到入口html文件
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
chunks: ['app']
}),
new HtmlWebpackPlugin({
filename: 'view.html',
template: 'view.html',
inject: true,
chunks: ['view']
})
]
})
Очень простая конфигурация, стоит отметить, что из-за многостраничных приложений файл входа иHtmlWebpackPlugin
Придется писать несколько копий.
4.3 Конфигурация упаковки компонентов
Далее следует конфигурация для упаковки сборки библиотеки компонентов пользовательского интерфейса в пакет npm. Создайте новый с именемwebpack.build.config.js
документ:
const { join } = require('path')
const merge = require('webpack-merge')
const basicConfig = require('./webpack.base.config')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const resolve = dir => join(__dirname, '..', dir)
module.exports = merge(basicConfig, {
// 入口文件
entry: {
app: './src/view/components/index.js'
},
devtool: 'source-map',
// 输出位置为dist目录,名字自定义,输出格式为umd格式
output: {
path: resolve('dist'),
filename: 'index.js',
library: 'my-project',
libraryTarget: 'umd'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
// 每一次打包都把上一次的清空
new CleanWebpackPlugin(['dist'], {
root: resolve('./')
}),
// 把静态资源复制出去,以便实现UI换肤等功能
new CopyWebpackPlugin([
{ from: 'src/view/assets', to: 'assets' }
])
]
})
4.4 Генерация конфигурации документа в один клик
Наконец, давайте настроим создание сайта документации одним щелчком мыши.webpack.doc.config.js
:
const { join } = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const basicConfig = require('./webpack.base.config')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const resolve = dir => join(__dirname, '..', dir)
module.exports = merge(basicConfig, {
// 类似开发者模式,两个入口文件,多了一个公共依赖包vendor
// 以`js/`开头能够自动输出到`js`目录下
entry: {
'js/app': './src/document/doc-entry.js',
'js/view': './src/view/view-entry.js',
'js/vendor': [
'vue',
'vue-router'
]
},
devtool: 'source-map',
// 输出文件加hash
output: {
path: resolve('docs'),
filename: '[name].[chunkhash:8].js',
chunkFilename: 'js/[name].[chunkhash:8].js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
css: ExtractTextPlugin.extract({
use: ['css-loader']
}),
less: ExtractTextPlugin.extract({
use: ['css-loader', 'less-loader']
})
}
}
}
]
},
plugins: [
// 提取css文件并指定其输出位置和命名
new ExtractTextPlugin({
filename: 'css/[name].[contenthash:8].css',
allChunks: true
}),
// 抽离公共依赖
new webpack.optimize.CommonsChunkPlugin({
names: ['js/vendor', 'js/manifest']
}),
// 把构建出的静态资源注入到多个入口html中
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunks: ['js/vendor', 'js/manifest', 'js/app'],
chunksSortMode: 'dependency'
}),
new HtmlWebpackPlugin({
filename: 'view.html',
template: 'view.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunks: ['js/vendor', 'js/manifest', 'js/view'],
chunksSortMode: 'dependency'
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new CleanWebpackPlugin(['docs'], {
root: resolve('./')
})
]
})
Благодаря приведенной выше конфигурации он в конечном итоге создастindex.html
с однимview.html
и соответствующие необходимые файлы css и js. Развертывание непосредственно на статическом сервере для доступа.
Еще один момент, настройка webpack на первый взгляд кажется сложной, но на самом деле она довольно проста.Официальная документация webpack2 также достаточно полная и легко читаемая.Рекомендуется друзьям, не знакомым с webpack2, потратить некоторое время внимательно прочитайте документацию.
До сих пор мы ставили/webpack
Соответствующая конфигурация в каталоге была завершена, основной скелет каркаса был построен, а затем начнется разработка бизнес-логики.
5. Разработка бизнес-логики
Создайте два новых файла записи в корневом каталогеindex.html
а такжеview.html
, добавить<div id="app"></div>
а также<div id="view"></div>
Этикетка.
Войти/src
каталог, новый/document
а также/view
Каталог, создайте необходимые каталоги и файлы в соответствии со структурой каталогов, показанной выше.
Конкретный контент можно увидетьздесь, просто инициализироватьvue
Приложение, пожалуйста, игнорируйтеrouter.js
Этот кусок кода в нем:
routeList.forEach((route) => {
routes.splice(1, 0, {
path: `/${route}`,
component: resolve => require([`~pages/${route}/index`], resolve)
});
});
Это функция, связанная с отслеживанием изменений каталога для автоматического управления навигацией, которая будет подробно описана позже.
Логика проста./document
а также/view
принадлежать文档
а также预览
два приложения, где预览
кiframe
встроенный в виде文档
На странице приложения соответствующие операции фактически находятся в文档
осуществляется посередине. Когда навигация нажата,文档
Приложение загрузится автоматически/view/pages/
в соответствующей папке страницы предварительного просмотраREADME.md
файл, при измененииiframe
ссылка для достижения синхронного переключения контента.
Далее давайте изучим, как отслеживать изменения каталога файлов и поддерживать автоматическое обслуживание.router
навигация.
6. Автоматическое обслуживаниеrouter
навигация
если вы использовалиNuxt, он должен автоматически поддерживатьсяrouter
Функция не будет незнакомой. Неважно, если вы еще не использовали его раньше, мы реализуем эту функцию сами!
использоватьvue-router
Многие студенты, возможно, сталкивались с такой болевой точкой: каждый раз, когда создается новая страница, им приходится переходить наrouter.js
добавить объявление внутри массиваrouter.js
Скорее всего, это будет так:
const route = [
{ path: '/a', component: resolve => require(['a'], resolve) },
{ path: '/b', component: resolve => require(['b'], resolve) },
{ path: '/c', component: resolve => require(['c'], resolve) },
{ path: '/d', component: resolve => require(['d'], resolve) },
{ path: '/e', component: resolve => require(['e'], resolve) },
{ path: '/f', component: resolve => require(['f'], resolve) },
...
]
Раздражает, да? Было бы неплохо, если бы это можно было поддерживать автоматически. Прежде всего, мы должны договориться о том, как должны быть организованы разные «страницы».
существует/src/view/pages
В каталоге каждый раз, когда мы создаем новую «страницу», нам нужно создавать новую папку с тем же именем, что и страница, и добавлять в нее документы.README.md
и входindex.vue
, эффект следующий:
└── view
└── pages
├── 页面A
│ ├── index.vue
│ └── README.md
├── 页面B
│ ├── index.vue
│ └── README.md
├── 页面C
│ ├── index.vue
│ └── README.md
└── 页面D
├── index.vue
└── README.md
Организация файлов была согласована, а затем нам нужно использовать инструмент, который будет отвечать за мониторинг и обработку. Здесь мы используемchokidarреализовать.
существует/webpack
Создайте новый в каталогеwatcher.js
документ:
console.log('Watching dirs...');
const { resolve } = require('path')
const chokidar = require('chokidar')
const fs = require('fs')
const routeList = []
const watcher = chokidar.watch(resolve(__dirname, '../src/view/pages'), {
ignored: /(^|[\/\\])\../
})
watcher
// 监听目录添加
.on('addDir', (path) => {
let routeName = path.split('/').pop()
if (routeName !== 'pages' && routeName !== 'index') {
routeList.push(`'${routeName}'`)
fs.writeFileSync(resolve(__dirname, '../src/route-list.js'), `module.exports = [${routeList}]`)
}
})
// 监听目录变化(删除、重命名)
.on('unlinkDir', (path) => {
let routeName = path.split('/').pop()
const itemIndex = routeList.findIndex((val) => {
return val === `'${routeName}'`
})
routeList.splice(itemIndex, 1)
fs.writeFileSync(resolve(__dirname, '../src/route-list.js'), `module.exports = [${routeList}]`)
})
module.exports = watcher
Здесь нужно сделать три основные вещи: отслеживать изменения каталогов, поддерживать список имен каталогов и записывать этот список в файл. при включенииwatcher
После этого вы можете/src
см. один нижеroute-list.js
файл со следующим содержимым:
module.exports = ['页面A','页面B','页面C','页面D']
Тогда мы можем с удовольствием использовать...
// view-router.js
import routeList from '../route-list.js';
const routes = [
{ path: '/', component: resolve => require(['~pages/index'], resolve) },
{ path: '*', component: resolve => require(['~pages/index'], resolve) },
];
routeList.forEach((route) => {
routes.splice(1, 0, {
path: `/${route}`,
component: resolve => require([`~pages/${route}/index`], resolve)
});
});
// doc-router.js
import routeList from '../route-list.js';
const routes = [
{ path: '/', component: resolve => require(['~pages/index/README.md'], resolve) }
];
routeList.forEach((route) => {
routes.push({
path: `/${route}`,
component: resolve => require([`~pages/${route}/README.md`], resolve)
});
});
Точно так же в навигационном компоненте страницы мы также загружаем этотroute-list.js
файл для автоматического обновления содержимого навигации.
Выложите видео, все это пощупают (СФ не разрешает встроенное видео, ненаучно):V.QQ.com/small/afraid/ah 051…
7. Соглашения об организации файлов библиотеки пользовательского интерфейса
Основная цель этого фреймворка на самом деле заключается в разработке библиотек пользовательского интерфейса. Затем мы также должны согласовать файловую организацию UI-библиотеки.
Войти/src/view/components
каталог, где находится вся наша UI-библиотека:
└── components
├── index.js // 入口文件
├── 组件A
│ ├── index.vue
├── 组件B
│ ├── index.vue
├── 组件C
│ ├── index.vue
└── 组件D
└── index.vue
средиindex.js
, будетvue pluginнаписано так:
import MyHeader from './组件A'
import MyContent from './组件B'
import MyFooter from './组件C'
const install = (Vue) => {
Vue.component('my-header', MyHeader)
Vue.component('my-content', MyContent)
Vue.component('my-footer', MyFooter)
}
export {
MyHeader,
MyContent,
MyFooter
}
export default install
Так, на входе.js
в файле сVue.use(UILibrary)
На библиотеку пользовательского интерфейса ссылаются в виде файла .
Расширяясь, учитывая, что пользовательский интерфейс может иметь функцию «скининга», мы можем/src/view
Создайте новый в каталоге/assets
Каталог, предназначенный для хранения файлов, связанных со стилем, этот каталог в конечном итоге будет упакован в/dist
В каталоге вы можете импортировать соответствующий файл стиля при его использовании.
8. Соберите и запустите команду
Так много было сделано прежде, в конце концов мы надеемся, что сможем пройти простойnpm script
Команда запускает весь фреймворк, что мне делать?
помнить в/webpack
три в каталогеconfig.js
файл? Они являются ключом к работе фреймворка, но мы не будем запускать их напрямую, а обернем сверху.
существует/webpack
Создайте новый в каталогеdev.js
файл со следующим содержимым:
require('./watcher.js')
module.exports = require('./webpack.dev.config.js')
Точно так же создайте новыйbuild.js
а такжеdoc.js
файл, импортируемый отдельноwebpack.build.config.js
а такжеwebpack.doc.config.js
Вот и все.
Зачем ты это делаешь? Потому что при запуске webpack он будет читатьconfig.js
файл, если мы хотим сделать некоторую предварительную обработку до того, как вебпак заработает, то этот подход очень удобен, например, здесь добавлена функция мониторинга изменений файла каталога. Если в будущем будет какое-либо расширение, это также можно сделать аналогичным образом.
Далее вpackage.json
что определяет нашnpm script
сейчас:
"dev": "node_modules/.bin/webpack-dev-server --config webpack/dev.js",
"doc": "node_modules/.bin/webpack -p --config webpack/doc.js --progress --profile --colors",
"build": "node_modules/.bin/webpack -p --config webpack/build.js --progress --profile --colors"
Стоит отметить, что в режиме производства вам необходимо добавить-p
Для полноценного запуска webpack2tree-shaking
Функция.
через корневой каталогnpm run 命令
способ проверить, запустилось ли оно?
9. Последующая работа
- Добавьте модульные тесты
- Добавить функцию PWA
10. Эпилог
Эта статья длинная, и немного головокружительно видеть оценки здесь. Давно не писал статьи, и вот наконец-то сохранил большой ход и разослал, что очень круто. Процесс построения фреймворка для разработки — это процесс постоянных попыток, постоянного гугления и stackoverflow. В этом процессе, от большого до проектирования архитектуры, от малого до организации файлов и использования инструментов, у меня есть дальнейшее понимание.
Режим работы этого фреймворка на самом деле относится ко многим решениям в отрасли, и он больше связан с желанием быть «ленивым». Пусть машина делает это автоматически, а не вручную — это движущая сила технического прогресса.
Проект был преобразован вvue-cli
шаблон, черезvue init jrainlau/vue-donut#mobile
Готов к использованию, добро пожаловать на пробу, с нетерпением жду отзывов и PR, спасибо~