анализ спроса
Когда мы реализуем библиотеку компонентов, мы не просто кодируем ее, как только она появляется, но относимся к ней как к продукту и думаем о потребностях нашей библиотеки компонентов. тогда дляelement-ui
, помимо разработки компонентов на основе стека технологий Vue.js, какие еще у него требования?
- Богатая функция: богатый набор компонентов, настраиваемые темы, международные.
- Документация и демонстрация: предоставьте удобную документацию и демонстрацию с низкими затратами на обслуживание и поддержкой нескольких языков.
- Установка и импорт: поддержка методов npm и cdn, а также поддержка импорта по запросу.
- Инжиниринг: разработка, тестирование, сборка, развертывание, непрерывная интеграция.
Раз есть спрос, то нужно думать, как его удовлетворить.Эта статья будет основана наelement-ui
Исходный код версии 2.11.1 для анализа реализации этих требований. Конечно,element-ui
Этого не должно было быть в первые дни.Проанализированная нами версия была оптимизирована для многих итераций.Если вы хотите понять процесс ее разработки, вы можете найти ее историческую версию на GitHub.
богатые возможности
богатые компоненты
Ядром библиотеки компонентов является компонент, давайте сначала посмотримelement-ui
Принципы проектирования компонентов: согласованность, обратная связь, эффективность, управляемость. Конкретное объяснение доступно на официальном сайте, поэтому я не буду публиковать больше.element-ui
За командой разработчиков стоит сильная команда дизайнеров, которая также выигрывает отelement-ui
Основательsofish
В компании голос и статус, чтобы получить такой хороший ресурс. такelement-ui
Внешний вид, соответствие цветов и взаимодействие компонентов — все сделано очень хорошо.
Другим важным аспектом базовой библиотеки компонентов является большое разнообразие компонентов.element-ui
В настоящее время официальное приложение имеет 55 компонентов, которые разделены на 6 категорий, а именно: базовые компоненты, компоненты форм, компоненты данных, компоненты подсказок, компоненты навигации и другие типы компонентов. Эти богатые базовые компоненты вполне могут удовлетворить большинство потребностей в развитии бизнеса от ПК до B.
Разработка такого количества компонентов требует много времени и усилий, так что спасибо здесь
element-ui
Команда предоставила нам эти базовые компоненты, а мы на их основе сделали вторичную разработку, что сэкономило много времени.
element-ui
Исходный код компонента находится вpackages
храниться в каталоге, а не вsrc
в каталоге. Это не для использования монорепозитория, и я не нашел инструмент управления пакетами lerna.Я предполагаю, что цель этого состоит в том, чтобы позволить каждому компоненту быть упакованным отдельно для поддержки введения по требованию. Но на самом деле для достижения этой цели не обязательно так организовывать и поддерживать код, я предпочитаю размещать код компонента в библиотеке компонентов вsrc/components
Он хранится в каталоге, а затем, изменив сценарий конфигурации веб-пакета, каждый компонент может быть упакован отдельно и может быть импортирован по запросу.Исходный код помещается вsrc
Каталоги всегда разумнее.
пользовательская тема
element-ui
Одной из основных функций является то, что он поддерживает пользовательские темы, вы можете использовать онлайн-редактор тем, вы можете изменять токены дизайна всех глобальных переменных и компонентов пользовательского элемента, и вы можете легко просматривать визуальные эффекты после изменения стиля в режиме реального времени. В то же время он также может генерировать полный пакет файлов стилей на основе нового пользовательского стиля для прямой загрузки, так как же он это делает?
element-ui
Стили и публичные стили компонентов все естьpackages/theme-chalk
файл, и его можно распространять независимо.element-ui
Цвета, шрифты, линии и т. д. в стилях компонентов вводятся переменными,packages/theme-chalk/src/common/var.scss
Мы можем видеть определения этих переменных в , что обеспечивает удобство для создания нескольких тем, потому что пока я изменяю эти переменные, тема компонента может быть изменена.
После понимания основных принципов заменить тему онлайн несложно.Я не буду подробно рассказывать о интерфейсной части интерактивной пользовательской онлайн-темы.Заинтересованные студенты могут сами посмотреть исходный код.examples
В каталоге я говорю только об основных принципах.
Для онлайн-обработки скинов и предварительного просмотра в реальном времени вам потребуется помощь сервера. Например, тему можно поддерживать с помощью конфигурации. После того, как пользователь выполнит ряд операций, будет сгенерирована новая конфигурация темы, а конфигурация будет отправлено через интерфейсный сервер, а затем сервер вернется для создания нового CSS в соответствии с этой конфигурацией (конкретная схема реализации не является открытым исходным кодом, и грубо будет выполнять некоторую замену переменных, а затем компилировать), новый стиль CSS будет перезаписать стиль по умолчанию и достичь цели переключения темы.
Мы можем открыть сетевую панель на странице редактирования темы, и мы видим, что есть 2 запроса xhr, как показано на рисунке:
в,updateVarible
Это запрос POST, он отправит вашу измененную конфигурацию темы на внутренний сервер, и вы сможете просмотреть его полезные данные запроса для отправленных данных.Этот запрос POST вернет фрагмент текста CSS, который затем будет динамически вставлен в тег головы. Внизу, чтобы переопределить стиль по умолчанию, вы можете увидеть, проверив элемент, что нижняя часть головы будет динамически вставлять идентификаторchalk-style
Тег.
Следующее изображение представляет собой текст стиля, возвращаемый запросом:
Соответствующий код находится вexamples/components/theme/loader/index.vue
середина.
onAction() {
this.triggertProgressBar(true);
const time = +new Date();
updateVars(this.userConfig)
.then(res => {
this.applyStyle(res, time);
})
.catch(err => {
this.onError(err);
})
.then(() => {
this.triggertProgressBar(false);
});
},
applyStyle(res, time) {
if (time < this.lastApply) return;
this.updateDocs(() => {
updateDomHeadStyle('chalk-style', res);
});
this.lastApply = time;
}
onAction
в функцииupdateVars
отправить запрос POST иapplyStyle
Функция состоит в том, чтобы изменить и переопределить стиль по умолчанию,updateDocs
Функция обновит цвет темы по умолчанию,updateDomHeadStyle
Стиль добавит или изменит идентификатор наchalk-style
Назначение тега стиля — переопределить стиль по умолчанию и применить новый стиль темы.
updateVars
Запрос выполняется при загрузке страницы, а также после изменения конфигурации темы.
посмотри сноваgetVarible
запрос, это запрос GET, возвращаемый контент является источником данных панели конфигурации в правой части страницы конфигурации темы, как показано на следующем рисунке:
Панель конфигурации темы создается на основе этого источника данных, и когда вы переходите к редактированию одного из элементов, она снова срабатывает.updateVars
Запрос на почту, отправьте обновленную конфигурацию, а затем бэкэн вернет новые CSS и вступит в силу на Frontend.
Кроме того, конфигурация, измененная пользователем, также сохраняется локально с помощью localStorage, так что пользователь может сохранять тему каждый раз, когда он редактирует, и может продолжить редактирование на основе темы в следующий раз.
Однако реализация такого количества тем не идеальна, поэтому для ускорения компиляцииelement-ui
Стилевая часть извлекается из отдельного файла, что доставляет большие неудобства учащимся, разрабатывающим компонент: при написании стиля компонента нужно переключаться между несколькими файлами, а это не соответствует ближайший компонент принципы управления. Но если стили написаны в компоненте, время компиляции и генерации отдельного файла стилей на стороне сервера увеличится (CSS нужно извлекать из компонента), так что это вопрос компромисса.
глобализация
Когда дело доходит до решения интернационализации Vue, его легко представить.vue-i18n
строить планы,element-ui
не представленvue-i18n
, но он хорошо работает сvue-i18n
Совместимый.
Все схемы интернационализации используют языковые пакеты, которые обычно возвращают данные в формате JSON.element-ui
Языковой пакет библиотеки компонентов находится вsrc/locale/lang
каталоге, в качестве примера возьмем английский языковой пакет:
export default {
el: {
colorpicker: {
confirm: 'OK',
clear: 'Clear'
}
// ...
}
}
существуетpackages/color-picker/src/components/picker-dropdown.vue
, мы можем увидеть использование этого языкового пакета в разделе шаблонов:
<el-button
size="mini"
type="text"
class="el-color-dropdown__link-btn"
@click="$emit('clear')">
{{ t('el.colorpicker.clear') }}
</el-button>
<el-button
plain
size="mini"
class="el-color-dropdown__btn"
@click="confirmValue">
{{ t('el.colorpicker.confirm') }}
</el-button>
используется в шаблонеt
функция, которая определена вsrc/mixins/locale.js
середина:
import { t } from 'element-ui/src/locale';
export default {
methods: {
t(...args) {
return t.apply(this, args);
}
}
};
по фактуsrc/locale/index.js
определено вt
функция:
export const t = function(path, options) {
let value = i18nHandler.apply(this, arguments);
if (value !== null && value !== undefined) return value;
const array = path.split('.');
let current = lang;
for (let i = 0, j = array.length; i < j; i++) {
const property = array[i];
value = current[property];
if (i === j - 1) return format(value, options);
if (!value) return '';
current = value;
}
return '';
};
Эта функция основана на входящихpath
путь, как в нашем примереel.colorpicker.confirm
, найдите соответствующую копию из языкового пакета. вi18nHandler
Являетсяi18n
Функция обработки, эта логика используется для совместимости с внешнимиi18n
Схема такая какvue-i18n
.
let i18nHandler = function() {
const vuei18n = Object.getPrototypeOf(this || Vue).$t;
if (typeof vuei18n === 'function' && !!Vue.locale) {
if (!merged) {
merged = true;
Vue.locale(
Vue.config.lang,
deepmerge(lang, Vue.locale(Vue.config.lang) || {}, { clone: true })
);
}
return vuei18n.apply(this, arguments);
}
};
export const i18n = function(fn) {
i18nHandler = fn || i18nHandler;
};
export const use = function(l) {
lang = l || lang;
};
можно увидетьi18nHandler
По умолчанию он попытается найти прототип Vue.$t
функция, котораяvue-i18@5.x
Реализация , будет смонтирована на прототипе Vue$t
метод.
Кроме того, он также раскрываетi18n
метод, который может быть передан извне другимi18n
метод, переопределитьi18nHandler
.
Если нет снаружи снаружиi18n
метод, а затем непосредственно найти текущий языковой пакетlet current = lang;
, следующая логика состоит в том, чтобы прочитать соответствующее строковое значение из этого объекта языкового пакета, конечно, если строку необходимо отформатировать, вызовитеformat
Функция, учащиеся, интересующиеся этой частью логики, могут прочитать ее самостоятельно.
Поэтому необходимо зарегистрироваться при использовании соответствующего языкового пакета:
import lang from 'element-ui/lib/locale/lang/en'
import locale from 'element-ui/lib/locale'
// 设置语言
locale.use(lang)
Таким образом, английский языковой пакет регистрируется, и вы можете нормально использовать его в шаблоне и найти соответствующий язык.
Если вы хотите разработать проект интернационализации, вы можете знать язык пользователя только во время выполнения. Вы можете рассмотреть возможность использования асинхронной динамической загрузки для получения языкового пакета перед отрисовкой страницы. Вы также можете рассмотреть возможность оптимизации кеша, но эта тема расширена. Это уже слишком.В будущем я могу открыть отдельную тему, чтобы поделиться тем, как сделать интернационализацию бизнеса.
Документация и демонстрация
Как отличная библиотека компонентов с открытым исходным кодом, дружественная документация и демонстрации необходимы, и это также может помочь вам привлечь много пользователей. Как разработчик и сопровождающий библиотеку компонентов, я также хочу поддерживать документацию и демонстрации с минимальными затратами.
element-ui
Документация и демонстрация интегрированы.Когда мы открываем его документацию, мы видим, что документация не только знакомит с тем, как используется каждый компонент, но также показывает различные примеры компонента, и вы можете четко видеть детали каждого примера. код очень удобный. Такelement-ui
Как написать эти демо и документы внутри? На самом деле документация и демонстрации для каждого компонента доступны через отдельный.md
файл, так как же это сделать?
element-ui
Исходный код демо находится вexamples
Техническое обслуживание каталогов, когда мы находимся вelement-ui
работать в рамках проектаnpm run dev
, он начнет режим разработки и отладки, а также запустит официальные документы и демоверсии.
посмотриnpm scripts
:
"scripts": {
"bootstrap": "yarn || npm i",
"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
"dev": "npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js",
}
Остальные скрипты опускаем, сосредоточимся наdev
и связанные несколько команд, которыеbootstrap
Роль заключается в установке зависимостей,build:file
это бежатьbuild
Несколько команд в каталоге, в том числеicon
,entry
,i18n
,version
Дождитесь инициализации. после выполненияbootstrap
а такжеbuild:file
После этого черезwebpack-dev-server
бегатьbuild/webpack.demo.js
, это ключевой момент, давайте посмотрим на конфигурационный файл этого вебпака.
const webpackConfig = {
mode: process.env.NODE_ENV,
entry: isProd ? {
docs: './examples/entry.js',
'element-ui': './src/index.js'
} : (isPlay ? './examples/play.js' : './examples/entry.js'),
output: {
path: path.resolve(process.cwd(), './examples/element-ui/'),
publicPath: process.env.CI_ENV || '',
filename: '[name].[hash:7].js',
chunkFilename: isProd ? '[name].[hash:7].js' : '[name].js'
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: config.alias,
modules: ['node_modules']
},
devServer: {
host: '0.0.0.0',
port: 8085,
publicPath: '/',
hot: true
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
},
{
test: /\.md$/,
use: [
{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
},
{
loader: path.resolve(__dirname, './md-loader/index.js')
}
]
}
]
}
};
Поскольку содержимое всего файла конфигурации относительно длинное, я оставлю только ключевые части и рассмотрю ключевые моменты.entry
а такжеmodule
внизrules
.
element-ui
Официальный сайт по сути являетсяvue
разработанное приложение, когда мы запускаемnpm run dev
Когда входной файлexamples
в каталогеentry.js
:
import Vue from 'vue';
import entry from './app';
import VueRouter from 'vue-router';
import Element from 'main/index.js';
import hljs from 'highlight.js';
import routes from './route.config';
import demoBlock from './components/demo-block';
import MainFooter from './components/footer';
import MainHeader from './components/header';
import SideNav from './components/side-nav';
import FooterNav from './components/footer-nav';
import title from './i18n/title';
import 'packages/theme-chalk/src/index.scss';
import './demo-styles/index.scss';
import './assets/styles/common.css';
import './assets/styles/fonts/style.css';
import icon from './icon.json';
Vue.use(Element);
Vue.use(VueRouter);
Vue.component('demo-block', demoBlock);
Vue.component('main-footer', MainFooter);
Vue.component('main-header', MainHeader);
Vue.component('side-nav', SideNav);
Vue.component('footer-nav', FooterNav);
const globalEle = new Vue({
data: { $isEle: false } // 是否 ele 用户
});
Vue.mixin({
computed: {
$isEle: {
get: () => (globalEle.$data.$isEle),
set: (data) => {globalEle.$data.$isEle = data;}
}
}
});
Vue.prototype.$icon = icon; // Icon 列表页用
const router = new VueRouter({
mode: 'hash',
base: __dirname,
routes
});
router.afterEach(route => {
// https://github.com/highlightjs/highlight.js/issues/909#issuecomment-131686186
Vue.nextTick(() => {
const blocks = document.querySelectorAll('pre code:not(.hljs)');
Array.prototype.forEach.call(blocks, hljs.highlightBlock);
});
const data = title[route.meta.lang];
for (let val in data) {
if (new RegExp('^' + val, 'g').test(route.name)) {
document.title = data[val];
return;
}
}
document.title = 'Element';
ga('send', 'event', 'PageView', route.name);
});
new Vue({ // eslint-disable-line
...entry,
router
}).$mount('#app');
То, что делает входной файл, очень просто, полное введение регистрируетсяelement-ui
Библиотека компонентов, в которой регистрируются некоторые компоненты, используемые на официальном веб-сайте, а также регистрируются функции маршрутизации и глобальных перехватчиков маршрутизации.
Здесь мы хотим сосредоточиться на части маршрутизации, конфигурация маршрутизации находится вexamples/route.config.js
середина:
import navConfig from './nav.config';
import langs from './i18n/route';
const LOAD_MAP = {
'zh-CN': name => {
return r => require.ensure([], () =>
r(require(`./pages/zh-CN/${name}.vue`)),
'zh-CN');
},
'en-US': name => {
return r => require.ensure([], () =>
r(require(`./pages/en-US/${name}.vue`)),
'en-US');
},
'es': name => {
return r => require.ensure([], () =>
r(require(`./pages/es/${name}.vue`)),
'es');
},
'fr-FR': name => {
return r => require.ensure([], () =>
r(require(`./pages/fr-FR/${name}.vue`)),
'fr-FR');
}
};
const load = function(lang, path) {
return LOAD_MAP[lang](path);
};
const LOAD_DOCS_MAP = {
'zh-CN': path => {
return r => require.ensure([], () =>
r(require(`./docs/zh-CN${path}.md`)),
'zh-CN');
},
'en-US': path => {
return r => require.ensure([], () =>
r(require(`./docs/en-US${path}.md`)),
'en-US');
},
'es': path => {
return r => require.ensure([], () =>
r(require(`./docs/es${path}.md`)),
'es');
},
'fr-FR': path => {
return r => require.ensure([], () =>
r(require(`./docs/fr-FR${path}.md`)),
'fr-FR');
}
};
const loadDocs = function(lang, path) {
return LOAD_DOCS_MAP[lang](path);
};
const registerRoute = (navConfig) => {
let route = [];
Object.keys(navConfig).forEach((lang, index) => {
let navs = navConfig[lang];
route.push({
path: `/${ lang }/component`,
redirect: `/${ lang }/component/installation`,
component: load(lang, 'component'),
children: []
});
navs.forEach(nav => {
if (nav.href) return;
if (nav.groups) {
nav.groups.forEach(group => {
group.list.forEach(nav => {
addRoute(nav, lang, index);
});
});
} else if (nav.children) {
nav.children.forEach(nav => {
addRoute(nav, lang, index);
});
} else {
addRoute(nav, lang, index);
}
});
});
function addRoute(page, lang, index) {
const component = page.path === '/changelog'
? load(lang, 'changelog')
: loadDocs(lang, page.path);
let child = {
path: page.path.slice(1),
meta: {
title: page.title || page.name,
description: page.description,
lang
},
name: 'component-' + lang + (page.title || page.name),
component: component.default || component
};
route[index].children.push(child);
}
return route;
};
let route = registerRoute(navConfig);
const generateMiscRoutes = function(lang) {
let guideRoute = {
path: `/${ lang }/guide`, // 指南
redirect: `/${ lang }/guide/design`,
component: load(lang, 'guide'),
children: [{
path: 'design', // 设计原则
name: 'guide-design' + lang,
meta: { lang },
component: load(lang, 'design')
}, {
path: 'nav', // 导航
name: 'guide-nav' + lang,
meta: { lang },
component: load(lang, 'nav')
}]
};
let themeRoute = {
path: `/${ lang }/theme`,
component: load(lang, 'theme-nav'),
children: [
{
path: '/', // 主题管理
name: 'theme' + lang,
meta: { lang },
component: load(lang, 'theme')
},
{
path: 'preview', // 主题预览编辑
name: 'theme-preview-' + lang,
meta: { lang },
component: load(lang, 'theme-preview')
}]
};
let resourceRoute = {
path: `/${ lang }/resource`, // 资源
meta: { lang },
name: 'resource' + lang,
component: load(lang, 'resource')
};
let indexRoute = {
path: `/${ lang }`, // 首页
meta: { lang },
name: 'home' + lang,
component: load(lang, 'index')
};
return [guideRoute, resourceRoute, themeRoute, indexRoute];
};
langs.forEach(lang => {
route = route.concat(generateMiscRoutes(lang.lang));
});
route.push({
path: '/play',
name: 'play',
component: require('./play/index.vue')
});
let userLanguage = localStorage.getItem('ELEMENT_LANGUAGE') || window.navigator.language || 'en-US';
let defaultPath = '/en-US';
if (userLanguage.indexOf('zh-') !== -1) {
defaultPath = '/zh-CN';
} else if (userLanguage.indexOf('es') !== -1) {
defaultPath = '/es';
} else if (userLanguage.indexOf('fr') !== -1) {
defaultPath = '/fr-FR';
}
route = route.concat([{
path: '/',
redirect: defaultPath
}, {
path: '*',
redirect: defaultPath
}]);
export default route;
Этот файл конфигурации маршрутизации обеспечивает настройку нескольких страниц маршрутизации, таких как руководства, компоненты, темы, ресурсы и т. д., и поддерживает несколько языков. Мы сосредоточимся на том, как генерируется маршрутизация компонентов.registerRoute(navConfig)
метод генерации.
вnavConfig
читатьexamples/nav.config.json
файл, этот файл конфигурации слишком длинный, и я не буду его публиковать.Он включает в себя конфигурацию нескольких языков и поддерживает отношение сопоставления путей меню навигации левого компонента.
registerRoute
Внутри функция должна пройтиnavConfig
, генерируйте конфигурацию маршрутизации на основе структуры данных его внутренних элементов, если данные имеютchildren
Создается подмаршрут.
Мы знаем, что сущность Vue Router основана на разных путях URL,<router-view>
Компоненты сопоставляются с соответствующими компонентами маршрутизации.Для маршрутизации каждого компонента он проходит черезaddRoute(nav, lang, index)
генерируется методом, который снова вызывается внутри методаloadDocs(lang, page.path)
Получите соответствующий компонент маршрутизации.
const loadDocs = function(lang, path) {
return LOAD_DOCS_MAP[lang](path);
};
const LOAD_DOCS_MAP = {
'zh-CN': path => {
return r => require.ensure([], () =>
r(require(`./docs/zh-CN${path}.md`)),
'zh-CN');
},
'en-US': path => {
return r => require.ensure([], () =>
r(require(`./docs/en-US${path}.md`)),
'en-US');
},
'es': path => {
return r => require.ensure([], () =>
r(require(`./docs/es${path}.md`)),
'es');
},
'fr-FR': path => {
return r => require.ensure([], () =>
r(require(`./docs/fr-FR${path}.md`)),
'fr-FR');
}
};
Взяв в качестве примера китайский язык, мы получаем определенноеpath
Компонент маршрутизации ниже является фабричной функцией, и соответствующий путь загруженного компонентаexmaples/docs/zh-CN/${path}.md
. Здесь следует отметить, что, в отличие от нашего обычного асинхронного метода загрузки компонентов, здесь загружается фактически.md
файл, а не.vue
файл, но может.vue
Файл также можно преобразовать в компонент Vue, как это сделать?
Мы знаем, что философия webpack заключается в том, что все ресурсы могут бытьrequire
, если соответствующий загрузчик настроен. назадbuild/webpack.demo.js
, находим, что для.md
файле настраиваем соответствующий загрузчик:
{
test: /\.md$/,
use: [
{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
},
{
loader: path.resolve(__dirname, './md-loader/index.js')
}
]
}
для.md
файл, здесь настраиваются 2 элемента в массиве использования, и порядок их выполнения обратный, то есть они выполняются первымиmd-loader
, а затем выполнитьvue-loader
,md-loader
Код находится вbuild/md-loader/index.js
середина:
const {
stripScript,
stripTemplate,
genInlineComponentText
} = require('./util');
const md = require('./config');
module.exports = function(source) {
const content = md.render(source);
const startTag = '<!--element-demo:';
const startTagLen = startTag.length;
const endTag = ':element-demo-->';
const endTagLen = endTag.length;
let componenetsString = '';
let id = 0; // demo 的 id
let output = []; // 输出的内容
let start = 0; // 字符串开始位置
let commentStart = content.indexOf(startTag);
let commentEnd = content.indexOf(endTag, commentStart + startTagLen);
while (commentStart !== -1 && commentEnd !== -1) {
output.push(content.slice(start, commentStart));
const commentContent = content.slice(commentStart + startTagLen, commentEnd);
const html = stripTemplate(commentContent);
const script = stripScript(commentContent);
let demoComponentContent = genInlineComponentText(html, script);
const demoComponentName = `element-demo${id}`;
output.push(`<template slot="source"><${demoComponentName} /></template>`);
componenetsString += `${JSON.stringify(demoComponentName)}: ${demoComponentContent},`;
// 重新计算下一次的位置
id++;
start = commentEnd + endTagLen;
commentStart = content.indexOf(startTag, start);
commentEnd = content.indexOf(endTag, commentStart + startTagLen);
}
// 仅允许在 demo 不存在时,才可以在 Markdown 中写 script 标签
// todo: 优化这段逻辑
let pageScript = '';
if (componenetsString) {
pageScript = `<script>
export default {
name: 'component-doc',
components: {
${componenetsString}
}
}
</script>`;
} else if (content.indexOf('<script>') === 0) { // 硬编码,有待改善
start = content.indexOf('</script>') + '</script>'.length;
pageScript = content.slice(0, start);
}
output.push(content.slice(start));
return `
<template>
<section class="content element-doc">
${output.join('')}
</section>
</template>
${pageScript}
`;
};
Принцип работы загрузчика webpack очень прост: на вход поступает исходное содержимое файла, а возвращаемое содержимое — это содержимое, обработанное загрузчиком. дляmd-loader
, ввод.md
документ, вывод представляет собой строку в формате Vue SFC, поэтому его вывод можно использовать в качестве следующегоvue-loader
Ввод обрабатывается.
Давайте кратко рассмотримmd-loader
Промежуточная обработка. первый казненныйmd.render(source)
правильноmd
Парсинг документов, извлечение документов из:::demo {content} :::
Контент, сгенерируйте несколько строк шаблона Vue, затем выполните цикл из этой строки шаблона.<!--element-demo:
а также:element-demo-->
Содержимое пакета, из которого извлекается строка шаблона дляoutput
, извлеките скрипт вcomponenetsString
, а затем построитьpageScript
, окончательный возвращаемый контент:
return `
<template>
<section class="content element-doc">
${output.join('')}
</section>
</template>
${pageScript}
`;
Окончательная сгенерированная строка удовлетворяет тому, что мы обычно пишем.vue
Формат SFC, он будет использоваться в качестве следующегоvue-loader
ввода, так что таким образом мы эквивалентны загрузке.md
Компонент Vue загружается в виде файла в формате.
Есть еще много и.md
детали парсинга файла, если выoutput
а такжеpageScript
Какой код интересует, предлагаю отладить самостоятельно.
element-ui
этот документ иdemo
Метод реализации очень оригинален, что значительно снижает стоимость обслуживания демо и документации, а также очень удобен для пользователя.Если вы также создаете документацию для своей собственной библиотеки, вы можете сослаться на ее реализацию.
установить и импортировать
Обычно библиотеки JS поддерживают два метода установки npm и CDN,element-ui
не исключено.
Давайте сначала поговорим о методе установки CDN, на самом делеelement-ui
Все компоненты будут упакованы для создания CSS и JS, официальный также предоставляет примеры:
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
У метода установки CDN есть свои плюсы, он не требует инструментов для сборки и может использоваться из коробки, но и недостатки очевидны, все компоненты вводятся в полном объеме, а объем очень большой.
Поскольку большинство людей разрабатывают проекты Vue на основеvue-cli
Скаффолдинг инициализирует проект, поэтому для установки рекомендуется использовать метод npm.
npm i element-ui -S
Говоря об установке npm, я должен упомянутьelement-ui
Предусмотрено 2 типа методов импорта компонентов: полный импорт и частичный импорт.
Очень легко поддерживать полный импорт, упаковывать все компоненты в CSS и JS иpackage.json
Средняя конфигурация:
"main": "lib/element-ui.common.js"
Это выполняетсяimport ElementUI from 'element-ui'
Когда можно полностью внедрить JS-код компонента. Как мы уже говорили,element-ui
CSS публикуется отдельно, поэтому вам также понадобитсяimport 'element-ui/lib/theme-chalk/index.css'
.
Преимуществом полного внедрения является удобство, его можно использовать полностью всего с 2 строками кода.element-ui
Все компоненты, но недостатки тоже очевидны.Пакет импортируемых компонентов очень большой.Обычно проект не использует все компоненты,и будет трата ресурсов.
Поэтому лучше всего импортировать по требованию:
import Vue from 'vue'
import { Button } from 'element-ui'
Vue.component(Button.name, Button)
Большинство людей воспринимают это как должное, когда они используют его таким образом.Интересно, задумывались ли вы когда-нибудь об этом: почему этот способ введения может быть введен по требованию? Чтобы понять эту проблему, мы должны понятьimport { Button } from 'element-ui'
Что происходит за этим.
На самом деле на официальном сайте уже есть ответ, при использовании импорта по требованию нужно использоватьbabel-plugin-component
плагин webpack и настройте.babelrc
:
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
На самом деле этоimport { Button } from 'element-ui'
преобразуется в:
var button = require('element-ui/lib/button')
require('element-ui/lib/theme-chalk/button.css')
Таким образом, мы точно вводим соответствующую библиотеку подButton
Код JS и CSS компонента уже импортирован, поэтому его можно импортировать по запросу.Button
компоненты.
element-ui
Хотя этот метод внедрения по требованию удобен, за ним стоит несколько проблем, которые необходимо решить: поскольку мы поддерживаем возможность импорта каждого компонента отдельно, как решить проблему избыточности кода, если зависимости компонентов генерируются и импортируются по требованию в то же время. Например, вelement-ui
середина,Table
компонент зависит отCheckBox
компонент, то когда я также представлюTable
компоненты иCheckBox
Когда компонент будет временем, будет ли он генерировать избыточность кода?
import { Table, CheckBox } from 'element-ui'
Если вы ничего не делаете, ответ — да, пакет, который вы в конечном итоге импортируете, будет иметь 2 копии.CheckBox
код. Такelement-ui
Как решить эту проблему? На самом деле решается только частично, настраивается в конфигурационном файле вебпакаexternals
,существуетbuild/config.js
Мы можем увидеть эти конкретные конфигурации в:
var externals = {};
Object.keys(Components).forEach(function(key) {
externals[`element-ui/packages/${key}`] = `element-ui/lib/${key}`;
});
externals['element-ui/src/locale'] = 'element-ui/lib/locale';
utilsList.forEach(function(file) {
file = path.basename(file, '.js');
externals[`element-ui/src/utils/${file}`] = `element-ui/lib/utils/${file}`;
});
mixinsList.forEach(function(file) {
file = path.basename(file, '.js');
externals[`element-ui/src/mixins/${file}`] = `element-ui/lib/mixins/${file}`;
});
transitionList.forEach(function(file) {
file = path.basename(file, '.js');
externals[`element-ui/src/transitions/${file}`] = `element-ui/lib/transitions/${file}`;
});
externals = [Object.assign({
vue: 'vue'
}, externals), nodeExternals()];
externals
Это может предотвратить упаковку этих импортированных пакетов в пакеты, а затем получение этих зависимостей расширения извне во время выполнения.
Давайте посмотрим на упаковкуlib/table.js
Позже мы можем увидеть компиляциюtable.js
правильноCheckBox
Введение зависимости компонента:
module.exports = require("element-ui/lib/checkbox");
Таким образом, он не будет упакован для создания 2 копий.CheckBox
JS часть кода, но для части CSS,element-ui
Избыточность не обрабатывается, как видитеlib/theme-chalk/checkbox.css
а такжеlib/theme-chalk/table.css
БудутCheckBox
Стили CSS для компонента.
На самом деле решить проблему избыточности JS и CSS, вводимых по требованию, несложно.Можно использовать идею пост-компиляции, то есть полагаться на пакеты для предоставления исходного кода, и компилировать в приложение для обработки , так что не только не будет избыточного кода компонентов, но даже не будет избыточного кода для компиляции, ведь мы основываемся наelement-ui
библиотека компонентов форкаzoom-ui
Применяется технология посткомпиляции и библиотека компонентов с открытым исходным кодом, ранее разработанная Didi.cube-ui
Библиотеки компонентов делают то же самое. Для получения дополнительных сведений, связанных с компиляцией, см.эта статья.
Инжиниринг
Передняя часть предъявляет все более высокие требования к инженерии,element-ui
Как библиотека компонентов, что она делает с точки зрения разработки?
Первый — этап разработки.Чтобы обеспечить единообразие стиля кода у всех, используется ESLint, и даже специально написанныйeslint-config-elemefe
В качестве расширения правило конфигурации ESint; для облегчения локальной разработки и отладки используется webpack и настраивается Hot Reload; используя идею модульной разработки, некоторые общие модули, от которых зависят компоненты, помещаются вsrc
каталог и разделение в соответствии с функциейdirectives
,locale
,mixins
,transitions
,utils
и другие модули.
Во-вторых, это аспект тестирования с использованием среды тестирования кармы, написания модульных тестов для каждого компонента и интеграции тестов с Travis CI.
Затем идет строительный аспект,element-ui
Написал много скриптов npm дляdist
Пример этого скрипта:
"dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme"
Он последовательно выполнит несколько команд и в конечном итоге сгенерируетlib
Каталоги и упакованные файлы. Я не собираюсь знакомить со всеми командами, желающие могут заниматься самостоятельно, здесь я хотел бы представитьbuild:file
Что делает этот скрипт:
"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
Здесь будет выполняться последовательноbuild/bin
Некоторые скрипты Node в каталоге, даicon
,entry
,i18n
,version
После выполнения ряда работ по инициализации их содержание состоит в том, чтобы выполнить файловый ввод-вывод в соответствии с некоторыми правилами.Преимущество этого заключается в том, что файл создается автоматически с помощью инструментов, что надежнее и эффективнее, чем ручная работа.Эта волна работы очень стоит.Мы учимся и применяем.
Наконец, разверните черезpub
Этот скрипт npm завершает:
"pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js && sh build/deploy-faas.sh"
В основном за счет запуска серии сценариев bash он реализует отправку кода, слияние, управление версиями, выпуск npm, выпуск официального веб-сайта и т. Д., Так что весь процесс выпуска автоматизирован.Студенты, которые заинтересованы в конкретном содержании сценария, могут просмотреть его самостоятельно.
Суммировать
Слишком далеко,element-ui
После введения общего дизайна библиотеки компонентов Много технических деталей, чтобы копаться.
Если вы узнаете то, чего не знаете, вы добьетесь прогресса Если вы найдете этот тип статей полезным, вы можете порекомендовать его своим друзьям.
Следующее превью: Element-UI Technology Reveal (3) — Дизайн и реализация компонентов макета макета.
Кроме того, я только недавно открыл публичный аккаунт "Фронтенд-частная кухня Лао Хуанга". Серия статей "Раскрытая технология Element-UI" будет обновлена и опубликована в публичном аккаунте как можно скорее. Кроме того, я буду часто делитесь передовыми передовыми знаниями, галантерейными товарами и иногда делитесь некоторыми навыками мягкого качества, приглашаем всех обратить внимание ~