Merge remote-tracking branch 'origin/master' into vanilla-js

This commit is contained in:
Nolan Lawson 2023-12-10 21:06:00 -08:00
commit 8db0d43139
18 changed files with 220 additions and 231 deletions

View File

@ -40,7 +40,7 @@ jobs:
# install the chromedriver corresponding to whatever version of chrome is installed
yarn add --ignore-scripts chromedriver@^$(google-chrome --version | awk '{print $3}' | cut -d. -f1)
PERF=1 yarn build:rollup
./bin/setup-benchmark.sh
yarn benchmark:runtime:setup
# first-load
- name: Benchmark first-load
@ -85,4 +85,34 @@ jobs:
report-id: emoji-picker-element-database-interactions
path: test/benchmark/database-interactions.results.json
pr-bench-name: this-change
base-bench-name: tip-of-tree
# change-tab
- name: Benchmark change-tab
run: |
./node_modules/.bin/tach \
--config ./test/benchmark/change-tab.tachometer.json \
--json-file ./test/benchmark/change-tab.results.json
- name: Report change-tab
uses: andrewiggins/tachometer-reporter-action@v2
with:
report-id: emoji-picker-element-change-tab
path: test/benchmark/change-tab.results.json
pr-bench-name: this-change
base-bench-name: tip-of-tree
# search
- name: Benchmark search
run: |
./node_modules/.bin/tach \
--config ./test/benchmark/search.tachometer.json \
--json-file ./test/benchmark/search.results.json
- name: Report search
uses: andrewiggins/tachometer-reporter-action@v2
with:
report-id: emoji-picker-element-search
path: test/benchmark/search.results.json
pr-bench-name: this-change
base-bench-name: tip-of-tree

5
.gitignore vendored
View File

@ -129,5 +129,8 @@ dist
/bundle.js
/test/benchmark/node_modules
/test/benchmark/data.json
/test/benchmark/*.js
/test/benchmark/database.js
/test/benchmark/index.js
/test/benchmark/picker.js
/test/benchmark/*.tachometer.json
/i18n

View File

@ -0,0 +1,54 @@
// Write a bunch of *.tachometer.json files since they contain a lot of boilerplate
import fs from 'node:fs'
const benchmarks = fs.readdirSync('./test/benchmark').filter(_ => _.endsWith('.benchmark.js'))
for (const benchmark of benchmarks) {
const benchmarkShortName = benchmark.replace('.benchmark.js', '')
const content = {
$schema: 'https://raw.githubusercontent.com/Polymer/tachometer/master/config.schema.json',
sampleSize: 50,
timeout: 5,
autoSampleConditions: ['10%'],
benchmarks: [
{
url: `./index.html?benchmark=${benchmarkShortName}`,
browser: {
name: 'chrome',
headless: true
},
measurement: [
{
mode: 'performance',
entryName: 'benchmark-total'
}
],
expand: [
{
name: 'this-change'
},
{
name: 'tip-of-tree',
packageVersions: {
label: 'tip-of-tree',
dependencies: {
'emoji-picker-element': {
kind: 'git',
repo: 'https://github.com/nolanlawson/emoji-picker-element.git',
ref: 'master',
setupCommands: [
'yarn --immutable --ignore-scripts',
'PERF=1 yarn build:rollup'
]
}
}
}
}
]
}
]
}
fs.writeFileSync(`./test/benchmark/${benchmarkShortName}.tachometer.json`, JSON.stringify(content, null, 2), 'utf-8')
}

View File

@ -26,7 +26,7 @@
"build:toc": "node ./bin/generateTOC",
"build:i18n": "node ./bin/buildI18n",
"benchmark:runtime": "cross-env PERF=1 run-s build:rollup benchmark:runtime:setup benchmark:runtime:firstload benchmark:runtime:secondload benchmark:runtime:database",
"benchmark:runtime:setup": "./bin/setup-benchmark.sh",
"benchmark:runtime:setup": "./bin/setup-benchmark.sh && node ./bin/setupTachometerConfigs.js",
"benchmark:runtime:firstload": "tach --config ./test/benchmark/first-load.tachometer.json",
"benchmark:runtime:secondload": "tach --config ./test/benchmark/second-load.tachometer.json",
"benchmark:runtime:database": "tach --config ./test/benchmark/database-interactions.tachometer.json",

View File

@ -1,31 +1,48 @@
// hijack the performance.mark/measure API so we can add our own marks/measures just for the benchmark
const { mark, measure } = performance
performance.mark = function (name) {
if (name === 'initialLoad') {
mark.call(performance, 'benchmark-start')
}
}
performance.measure = function (name, start) {
if (name === 'initialLoad' && start === 'initialLoad') {
// test to make sure the picker loaded with no errors
const hasErrors = document.querySelector('emoji-picker') && document.querySelector('emoji-picker')
.shadowRoot.querySelector('.message:not(.gone)')
if (hasErrors) {
console.error('picker is showing an error message')
} else {
measure.call(performance, 'benchmark-total', 'benchmark-start')
function instrumentPickerLoading () {
const observer = new PerformanceObserver(entries => {
for (const { name, startTime, duration } of entries.getEntries()) {
if (name === 'initialLoad') {
// test to make sure the picker loaded with no errors
const hasErrors = document.querySelector('emoji-picker') && document.querySelector('emoji-picker')
.shadowRoot.querySelector('.message:not(.gone)')
if (hasErrors) {
console.error('picker is showing an error message')
} else {
performance.measure('benchmark-total', { start: startTime, duration })
}
}
}
})
observer.observe({ entryTypes: ['measure'] })
}
function useFakeEtag () {
// Fake an eTag on the headers for the emoji-picker data so that we actually reuse the cache.
// Tachometer doesn't serve an eTag by default
const nativeGet = Headers.prototype.get
Headers.prototype.get = function (name) {
if (name.toLowerCase() === 'etag') {
return 'W/fakeEtag'
}
return nativeGet.call(this, name)
}
}
// Fake an eTag on the headers for the emoji-picker data so that we actually reuse the cache.
// Tachometer doesn't serve an eTag by default
const nativeGet = Headers.prototype.get
Headers.prototype.get = function (name) {
if (name.toLowerCase() === 'etag') {
return 'W/fakeEtag'
}
return nativeGet.call(this, name)
const params = new URLSearchParams(window.location.search)
const benchmark = params.get('benchmark') || 'first-load'
if (benchmark === 'first-load') {
instrumentPickerLoading()
await import('./first-load.benchmark.js')
} else if (benchmark === 'second-load') {
instrumentPickerLoading()
useFakeEtag()
await import('./second-load.benchmark.js')
} else if (benchmark === 'database-interactions') {
await import('./database-interactions.benchmark.js')
} else if (benchmark === 'change-tab') {
await import('./change-tab.benchmark.js')
} else if (benchmark === 'search') {
await import('./search.benchmark.js')
}

View File

@ -0,0 +1,16 @@
import Picker from './picker.js'
import { waitForElementWithId, postRaf } from './utils.js'
const picker = new Picker()
document.body.appendChild(picker)
await waitForElementWithId(picker.shadowRoot, 'emo-😀')
await postRaf()
const peopleTabButton = picker.shadowRoot.querySelector('[role="tab"][aria-label="People and body"]')
performance.mark('start-change-tab')
peopleTabButton.click()
await waitForElementWithId(picker.shadowRoot, 'emo-👋')
await postRaf()
performance.measure('benchmark-total', 'start-change-tab')

View File

@ -0,0 +1,19 @@
import Database from './database.js'
performance.mark('start-db-interactions')
const dataSource = './data.json'
const database = new Database({ dataSource })
await database.ready()
for (let i = 0; i < 10; i++) {
await database.getEmojiByUnicodeOrName('💥')
await database.getEmojiBySearchQuery('boom')
await database.getEmojiByShortcode('boom')
await database.getEmojiByGroup(1)
await database.getPreferredSkinTone()
await database.getTopFavoriteEmoji(10)
await database.incrementFavoriteEmojiCount('💥')
await database.setPreferredSkinTone(0)
}
performance.measure('benchmark-total', 'start-db-interactions')

View File

@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script type="module" src="./benchmark.js"></script>
<script type="module">
import Database from './database.js'
performance.mark('initialLoad')
const dataSource = './data.json'
const database = new Database({ dataSource })
await database.ready()
for (let i = 0; i < 10; i++) {
await database.getEmojiByUnicodeOrName('💥')
await database.getEmojiBySearchQuery('boom')
await database.getEmojiByShortcode('boom')
await database.getEmojiByGroup(1)
await database.getPreferredSkinTone()
await database.getTopFavoriteEmoji(10)
await database.incrementFavoriteEmojiCount('💥')
await database.setPreferredSkinTone(0)
}
performance.measure('initialLoad', 'initialLoad')
</script>
</body>
</html>

View File

@ -1,43 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/Polymer/tachometer/master/config.schema.json",
"sampleSize": 50,
"timeout": 5,
"autoSampleConditions": ["10%"],
"benchmarks": [
{
"url": "./database-interactions.html",
"browser": {
"name": "chrome",
"headless": true
},
"measurement": [
{
"mode": "performance",
"entryName": "benchmark-total"
}
],
"expand": [
{
"name": "this-change"
},
{
"name": "tip-of-tree",
"packageVersions": {
"label": "tip-of-tree",
"dependencies": {
"emoji-picker-element": {
"kind": "git",
"repo": "https://github.com/nolanlawson/emoji-picker-element.git",
"ref": "master",
"setupCommands": [
"yarn --immutable --ignore-scripts",
"PERF=1 yarn build:rollup"
]
}
}
}
}
]
}
]
}

View File

@ -0,0 +1,5 @@
import Picker from './picker.js'
const dataSource = './data.json'
document.body.appendChild(new Picker({ dataSource }))

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script type="module" src="./benchmark.js"></script>
<script type="module">
import Picker from './picker.js'
const dataSource = './data.json'
document.body.appendChild(new Picker({ dataSource }))
</script>
</body>
</html>

View File

@ -1,43 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/Polymer/tachometer/master/config.schema.json",
"sampleSize": 50,
"timeout": 5,
"autoSampleConditions": ["10%"],
"benchmarks": [
{
"url": "./first-load.html",
"browser": {
"name": "chrome",
"headless": true
},
"measurement": [
{
"mode": "performance",
"entryName": "benchmark-total"
}
],
"expand": [
{
"name": "this-change"
},
{
"name": "tip-of-tree",
"packageVersions": {
"label": "tip-of-tree",
"dependencies": {
"emoji-picker-element": {
"kind": "git",
"repo": "https://github.com/nolanlawson/emoji-picker-element.git",
"ref": "master",
"setupCommands": [
"yarn --immutable --ignore-scripts",
"PERF=1 yarn build:rollup"
]
}
}
}
}
]
}
]
}

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script type="module" src="./benchmark.js"></script>
</body>
</html>

View File

@ -0,0 +1,17 @@
import Picker from './picker.js'
import { waitForElementWithId, postRaf } from './utils.js'
const picker = new Picker()
document.body.appendChild(picker)
await waitForElementWithId(picker.shadowRoot, 'emo-😀')
await postRaf()
const searchBox = picker.shadowRoot.querySelector('[role="combobox"]')
performance.mark('start-search')
searchBox.value = 'fa' // "face" returns a lot of results, we want a non-trivial benchmark
searchBox.dispatchEvent(new Event('input', { bubbles: true }))
await waitForElementWithId(picker.shadowRoot, 'emo-🐻')
await postRaf()
performance.measure('benchmark-total', 'start-search')

View File

@ -0,0 +1,13 @@
import Database from './database.js'
const dataSource = './data.json'
// populate IndexedDB so the Picker is just reading from the local store
const db = new Database({ dataSource })
await db.ready()
await db.close()
// lazy-load the picker so that its logic to determine emoji support runs during the perf measure
const { default: Picker } = await import('../../picker.js')
document.body.appendChild(new Picker({ dataSource }))

View File

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script type="module" src="./benchmark.js"></script>
<script type="module">
import Database from './database.js'
(async () => {
const dataSource = './data.json'
// populate IndexedDB so the Picker is just reading from the local store
const db = new Database({ dataSource })
await db.ready()
await db.close()
// lazy-load the picker so that its logic to determine emoji support runs during the perf measure
const { default: Picker } = await import('../../picker.js')
document.body.appendChild(new Picker({ dataSource }))
})()
</script>
</body>
</html>

View File

@ -1,43 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/Polymer/tachometer/master/config.schema.json",
"sampleSize": 50,
"timeout": 5,
"autoSampleConditions": ["10%"],
"benchmarks": [
{
"url": "./second-load.html",
"browser": {
"name": "chrome",
"headless": true
},
"measurement": [
{
"mode": "performance",
"entryName": "benchmark-total"
}
],
"expand": [
{
"name": "this-change"
},
{
"name": "tip-of-tree",
"packageVersions": {
"label": "tip-of-tree",
"dependencies": {
"emoji-picker-element": {
"kind": "git",
"repo": "https://github.com/nolanlawson/emoji-picker-element.git",
"ref": "master",
"setupCommands": [
"yarn --immutable --ignore-scripts",
"PERF=1 yarn build:rollup"
]
}
}
}
}
]
}
]
}

8
test/benchmark/utils.js Normal file
View File

@ -0,0 +1,8 @@
export const raf = () => new Promise(resolve => requestAnimationFrame(resolve))
export const postRaf = () => new Promise(resolve => requestAnimationFrame(() => setTimeout(resolve)))
export const waitForElementWithId = async (root, id) => {
while (!root.getElementById(id)) {
await raf()
}
}