Колонка инженерной системы всегда будет начинаться с моейGithub, вы можете подписаться и поставить лайк, как правило, более чем за неделю до выхода основных платформ.
Исходный код и адреса видео, задействованные в этой статье:
- исходный код
- видео, потому что изготовление точно займет больше времени, чем текст, поэтому на этой неделе он будет обновлен, вы можете сначала посмотреть видео.
предисловие
Читатели часто спрашивают меня, что такое фронтенд-инжиниринг? С чего начать фронтенд-инжиниринг?
Поболтав, я пришел к некоторым выводам: такие читатели в основном работают в небольших и средних компаниях, с однозначным фронтенд-персоналом, и обычно устают от разработки, внутри команды инфраструктуры почти нет, а инструментов нет. очень дикий. Инженерия очень незнакома этим читателям, они в основном не знают, что это такое, или думают, что Webpack — это все о фронтенд-инжиниринге.
В настоящее время автор работает в команде по инфраструктуре на заводе, предоставляя базовые услуги по созданию внешнего интерфейса Bailaihao, и имеет небольшой опыт в этой области. Поэтому у меня есть некоторые идеи.Фронтенд-инжиниринг будет представлять собой серию работ, которую автор открыл в этом году.Каждая часть контента будет представлена в виде статей+исходников+видео.
Выход этой серии относится к следующим группам:
-
Интерфейс малых и средних заводов, инфраструктура дикая, обычно устали от бизнеса, не знают, как делать что-то вне бизнеса, могут повысить свою конкурентоспособность и обогатить свое резюме.
-
В настоящее время у компании нет плана инфраструктуры, и она может производить недорогие и высокодоходные продукты только в качестве любителя.
-
Хотите знать о фронтенд-инжиниринге
Следует отметить, что на выходе будет только минимально доступный продукт по низкой цене.Вы можете использовать его для добавления функций по мере необходимости, ссылок на идеи или просто получения небольших знаний.
Что такое фронтенд-инжиниринг?
Поскольку это первая статья в этой серии, давайте кратко поговорим о том, что такое фронтенд-инжиниринг.
Насколько я понимаю, фронтенд-инжиниринг обычно можно понимать как проект повышения эффективности, а инжиниринг можно выполнять на каждом этапе, начиная с написания кода. Например, опыт и эффективность написания кода с помощью IDE по сравнению с Блокнотом определенно отличаются, например, такие инструменты, как Webpack, также помогают нам повысить эффективность разработки и построения, а другие инструменты не перечислены один за другим. знает, что это значит.
Конечно, тестирование производительности, о котором мы сегодня поговорим, также является частью разработки. В конце концов, нам нужен инструмент, помогающий выявить недостатки производительности приложения, который может помочь разработчикам быстрее обнаружить проблему, а не позволить пользователям жаловаться на отставание продукта в производственной среде.
Зачем нужно тестирование производительности?
Оптимизация производительности — тема, которую не могут обойти стороной многие фронтенды, не будем говорить о том, нужна ли проекту оптимизация производительности — такие вопросы очень часто встречаются на собеседованиях.
Однако одной оптимизации производительности недостаточно. В конце концов, нам все еще нужно сравнить данные до и после, чтобы отразить ценность этой оптимизации. В конце концов, количественная оценка данных по-прежнему очень важна на рабочем месте. Босс не знает, что вы делаете, и многое нужно видеть из данных.Чем лучше данные, тем лучше ваша способность выполнять работу.
Чтобы получить изменения данных до и после производительности, мы должны использовать некоторые инструменты для тестирования производительности.
Как проверить работоспособность?
Есть много способов проверить работоспособность:
-
Собственные инструменты разработчика Chrome: производительность
-
Инструменты с открытым исходным кодом Lighthouse
-
Собственный API производительности
-
Различные официальные библиотеки и плагины
Каждый из этих методов имеет свои преимущества.Первые два метода просты и быстры, могут визуализировать различные индикаторы, но получить данные на стороне пользователя сложно.В конце концов, вы вряд ли позволите пользователям запускать эти инструменты.Конечно , есть и другие методы.Мелкие недочеты, типа не получение количества редиректов и тд.
Официальные библиотеки и плагины сильно уступают первым двум и предоставляют лишь некоторые основные показатели.
У собственного API-интерфейса производительности есть проблемы с совместимостью, но он может охватывать этапы разработки и производства, а функция также может охватывать встроенный инструмент разработчика: инструмент производительности. Мы можем не только понять показатели эффективности проекта на этапе разработки, но и получить данные со стороны пользователя, которые помогут нам лучше сформулировать планы оптимизации. Кроме того, индикаторы, которые можно получить, также очень полны, поэтому на этот раз это выбор нашего продукта.
Конечно, это не вопрос с несколькими вариантами ответов, нам все еще нужно использовать инструмент производительности и API в сочетании на этапе разработки, ведь у них все еще есть взаимодополняющие роли.
реальный бой
Вот исходный код продукта здесь:адрес.
некоторые показатели эффективности
Прежде чем приступить к реальному бою, нам все еще нужно понять некоторые показатели производительности.С развитием времени некоторые старые статьи по оптимизации производительности на самом деле немного устарели. Google обновляет показатели оптимизации производительности, автор написал статью ранее, чтобы описать текущую ситуацию.Каковы последние показатели эффективности, и заинтересованные читатели могут сначала прочитать его подробно.
Конечно, если вы думаете, что это слишком долго для чтения, вы можете бросить беглый взгляд на следующую ментальную карту:
Конечно, в дополнение к этому индикатору нам также необходимо получить информативные индикаторы, такие как сеть, передача файлов и DOM.
Использование производительности
Performance
Интерфейс может получать информацию о производительности на текущей странице и предоставлять высокоточные временные метки, убивая секунды.Date.now()
. Сначала давайте посмотрим на совместимость этого API:
Фактически, этот процент уже очень совместим, и версии основных браузеров могут хорошо поддерживаться.
Конкретное объяснение API on Performance повторяться в тексте не будет, если интересно, можете прочитать.Документация MDN, автор расскажет только о нескольких важных API, которые будут использоваться позже.
getEntriesByType
этоAPIпозволяет нам пройти вtype
Получите соответствующую информацию:
- кадр: данные времени для кадра в цикле событий.
- ресурс: загрузка подробных данных синхронизации сети для ресурсов приложения.
- отметка:
performance.mark
информация о вызове - мера:
performance.measure
информация о вызове - longtask: информация о длинной задаче (время выполнения более 50 мс). Этот тип устарел (документация не отмечена, но использование его в Chrome покажет, что он устарел), мы можем получить его другими способами
- навигация: методы и свойства для индикаторов событий документа браузера
- Paint: получить показатели FP и FCP
последние дваtype
Это ключевой тип получения индикаторов при тестировании производительности. Конечно, если вы также хотите проанализировать информацию, связанную с загрузкой ресурсов, вы можете добавить большеresource
тип.
PerformanceObserver
PerformanceObserver
Это также API, используемый для получения некоторых показателей производительности.
const perfObserver = new PerformanceObserver((entryList) => {
// 信息处理
})
// 传入需要的 type
perfObserver.observe({ type: 'longtask', buffered: true })
комбинироватьgetEntriesByType
а такжеPerformanceObserver
, мы можем получить все необходимые метрики.
Код включен!
потому что это уже опубликованоАдрес источника, автор не будет выкладывать большой кусок кода, а разберет основной процесс с нуля до единицы.
Прежде всего, мы должны спроектировать, как пользователи вызывают SDK (имея в виду библиотеку определения производительности)? Какие параметры нужно передать? Как получить и сообщить показатели эффективности?
Вообще говоря, вызов SDK в основном предназначен для создания экземпляра, поэтому на этот раз мы выбираемclass
способ написать. Если параметр предварительно передается вtracker
функция для получения различных показателей иlog
Переменная определяет, печатать ли информацию индикатора.Сигнатура следующая:
export interface IPerProps {
tracker?: (type: IPerDataType, data: any, allData: any) => void
log?: boolean
}
export type IPerDataType =
| 'navigationTime'
| 'networkInfo'
| 'paintTime'
| 'lcp'
| 'cls'
| 'fid'
| 'tbt'
Далее пишемclass
Внутренний код, прежде всего, мы знаем, что есть проблема совместимости с Performance API, поэтому нам нужно определить, поддерживает ли его браузер перед вызовом Performance:
export default class Per {
constructor(args: IPerProps) {
// 存储参数
config.tracker = args.tracker
if (typeof args.log === 'boolean') config.log = args.log
// 判断是否兼容
if (!isSupportPerformance) {
log(`This browser doesn't support Performance API`)
return
}
}
export const isSupportPerformance = () => {
const performance = window.performance
return (
performance &&
!!performance.getEntriesByType &&
!!performance.now &&
!!performance.mark
)
}
После того, как вышеуказанная предварительная работа завершена, вы можете начать писать код для получения данных индикатора производительности.
Мы сначала проходимperformance.getEntriesByType('navigation')
чтобы получить метрики о событиях документа
Этот API по-прежнему может получать временные метки многих событий. Если вы хотите узнать конкретное значение этих событий, вы можете прочитатьДокументация, он не будет скопирован сюда, чтобы занять место.
Увидев столько полей, у некоторых читателей может закружиться голова, как я могу считать индикаторы с таким количеством вещей. На самом деле волноваться не стоит, прочитав картинку ниже в сочетании с предыдущейДокументацияПросто сделать:
Нам не нужно использовать все поля, полученные выше, и важную информацию индикатора можно выставить, а код можно получить, нарисовав тыкву по схеме и документу:
export const getNavigationTime = () => {
const navigation = window.performance.getEntriesByType('navigation')
if (navigation.length > 0) {
const timing = navigation[0] as PerformanceNavigationTiming
if (timing) {
// 解构出来的字段,太长不贴
const {...} = timing
return {
redirect: {
count: redirectCount,
time: redirectEnd - redirectStart,
},
appCache: domainLookupStart - fetchStart,
// dns lookup time
dnsTime: domainLookupEnd - domainLookupStart,
// handshake end - handshake start time
TCP: connectEnd - connectStart,
// HTTP head size
headSize: transferSize - encodedBodySize || 0,
responseTime: responseEnd - responseStart,
// Time to First Byte
TTFB: responseStart - requestStart,
// fetch resource time
fetchTime: responseEnd - fetchStart,
// Service work response time
workerTime: workerStart > 0 ? responseEnd - workerStart : 0,
domReady: domContentLoadedEventEnd - fetchStart,
// DOMContentLoaded time
DCL: domContentLoadedEventEnd - domContentLoadedEventStart,
}
}
}
return {}
}
Вы можете обнаружить, что многие из показателей, полученных выше, связаны с сетью.Поэтому нам также необходимо проанализировать сетевую среду.Это очень удобно для получения информации о сетевой среде.Следующий код:
export const getNetworkInfo = () => {
if ('connection' in window.navigator) {
const connection = window.navigator['connection'] || {}
const { effectiveType, downlink, rtt, saveData } = connection
return {
// 网络类型,4g 3g 这些
effectiveType,
// 网络下行速度
downlink,
// 发送数据到接受数据的往返时间
rtt,
// 打开/请求数据保护模式
saveData,
}
}
return {}
}
После взятия вышеуказанных показателей нам нужно использоватьPerformanceObserver
Вот некоторые основные показатели опыта (производительности). Например, FP, FCP, FID и т. д. содержание включено в интеллект-карту, которую мы видели выше:
Прежде чем мы это сделаем, нужно понять один нюанс: страница может грузиться в фоновом режиме, поэтому метрики, полученные в этом случае, будут неточными. Поэтому нам нужно проигнорировать эту ситуацию, сохранить переменную с помощью следующего кода и сравнить метку времени при получении индикатора, чтобы определить, находится ли он в фоновом режиме:
document.addEventListener(
'visibilitychange',
(event) => {
// @ts-ignore
hiddenTime = Math.min(hiddenTime, event.timeStamp)
},
{ once: true }
)
Далее идет код получения индикаторов, т.к. они получаются одинаково, поэтому сначала инкапсулируем метод получения:
// 封装一下 PerformanceObserver,方便后续调用
export const getObserver = (type: string, cb: IPerCallback) => {
const perfObserver = new PerformanceObserver((entryList) => {
cb(entryList.getEntries())
})
perfObserver.observe({ type, buffered: true })
}
Давайте сначала получим показатели FP и FCP:
export const getPaintTime = () => {
const data: { [key: string]: number } = ({} = {})
getObserver('paint', entries => {
entries.forEach(entry => {
data[entry.name] = entry.startTime
if (entry.name === 'first-contentful-paint') {
getLongTask(entry.startTime)
}
})
})
return data
}
Полученная структура данных выглядит так:
Следует отметить, что после получения показателя FCP необходимо синхронизировать время для начала получения longtask, потому что последующий показатель TBT нужно рассчитывать с помощью longtask.
export const getLongTask = (fcp: number) => {
getObserver('longtask', entries => {
entries.forEach(entry => {
// get long task time in fcp -> tti
if (entry.name !== 'self' || entry.startTime < fcp) {
return
}
// long tasks mean time over 50ms
const blockingTime = entry.duration - 50
if (blockingTime > 0) tbt += blockingTime
})
})
}
Далее возьмем индикатор FID, вот код:
export const getFID = () => {
getObserver('first-input', entries => {
entries.forEach(entry => {
if (entry.startTime < hiddenTime) {
logIndicator('FID', entry.processingStart - entry.startTime)
// TBT is in fcp -> tti
// This data may be inaccurate, because fid >= tti
logIndicator('TBT', tbt)
}
})
})
}
Данные индикатора FID настолько длинные, что для запуска требуется взаимодействие с пользователем:
После получения индикатора FID мы также отправились за индикатором TBT, но полученные данные могут быть неточными. Поскольку значение индикатора TBT представляет собой сумму длительного времени блокировки задачи между FCP и индикатором TTI, но, похоже, нет хорошего способа получить данные индикатора TTI, поэтому вместо него временно используется FID.
Наконец, есть индикаторы CLS и LCP, которые похожи друг на друга и склеены вместе:
export const getLCP = () => {
getObserver('largest-contentful-paint', entries => {
entries.forEach(entry => {
if (entry.startTime < hiddenTime) {
const { startTime, renderTime, size } = entry
logIndicator('LCP Update', {
time: renderTime | startTime,
size,
})
}
})
})
}
export const getCLS = () => {
getObserver('layout-shift', entries => {
let cls = 0
entries.forEach(entry => {
if (!entry.hadRecentInput) {
cls += entry.value
}
})
logIndicator('CLS Update', cls)
})
}
Полученная структура данных выглядит так:
Кроме того, эти два показателя не такие, как другие, и они не статичны. Он будет обновлен, как только новые данные будут соответствовать требованиям метрики.
Выше приведены все показатели производительности, которые нам необходимо получить. Конечно, недостаточно получить показатели. Нам также необходимо предоставить пользователю все данные. Для этой унифицированной операции нам необходимо инкапсулировать функцию инструмента:
// 打印数据
export const logIndicator = (type: string, data: IPerData) => {
tracker(type, data)
if (config.log) return
// 让 log 好看点
console.log(
`%cPer%c${type}`,
'background: #606060; color: white; padding: 1px 10px; border-top-left-radius: 3px; border-bottom-left-radius: 3px;',
'background: #1475b2; color: white; padding: 1px 10px; border-top-right-radius: 3px;border-bottom-right-radius: 3px;',
data
)
}
export default (type: string, data: IPerData) => {
const currentType = typeMap[type]
allData[currentType] = data
// 如果用户传了回调函数,那么每次在新获取指标以后就把相关信息暴露出去
config.tracker && config.tracker(currentType, data, allData)
}
После обертывания функции мы можем вызвать ее так:
logIndicator('FID', entry.processingStart - entry.startTime)
На данный момент общее содержание нашего SDK завершено, и мы можем добавить некоторые небольшие функции по мере необходимости, такие как получение показателей показателей.
Показатели индикатора являются официальными рекомендациями, вы можете найти их вОфициальный блогилимоя статьясм. определенные данные в .
Код не сложный, для демонстрации кода возьмем пример получения оценки индикатора FCP:
export const scores: Record<string, number[]> = {
fcp: [2000, 4000],
lcp: [2500, 4500],
fid: [100, 300],
tbt: [300, 600],
cls: [0.1, 0.25],
}
export const scoreLevel = ['good', 'needsImprovement', 'poor']
export const getScore = (type: string, data: number) => {
const score = scores[type]
for (let i = 0; i < score.length; i++) {
if (data <= score[i]) return scoreLevel[i]
}
return scoreLevel[2]
}
Во-первых, получить функцию инструмента, связанную со счетом.В любом случае, эта часть просто смотрит на официальное предложение скопировать, а затем нам просто нужно добавить дополнительную строку кода в то место, где только что был получен индикатор:
export const getPaintTime = () => {
getObserver('paint', (entries) => {
entries.forEach((entry) => {
const time = entry.startTime
const name = entry.name
if (name === 'first-contentful-paint') {
getLongTask(time)
logIndicator('FCP', {
time,
score: getScore('fcp', time),
})
} else {
logIndicator('FP', {
time,
})
}
})
})
}
Все закончилось, можешь зайти, если интересно.здесьЧитайте исходный код, там все равно не так много строк.
Наконец
Статья написана на выходных, немного поспешно, если есть ошибка, исправьте, в то же время приглашаем всех обсудить проблему вместе.
Для получения дополнительных статей, пожалуйста, следуйте за мнойGithubИли присоединитесь к группе, чтобы пообщаться о фронтенд-инжиниринге.