diff --git a/config/jest.setup.js b/config/jest.setup.js index 0166051..0b78006 100644 --- a/config/jest.setup.js +++ b/config/jest.setup.js @@ -4,6 +4,16 @@ import FDBKeyRange from 'fake-indexeddb/build/FDBKeyRange' import { Crypto } from '@peculiar/webcrypto' import { ResizeObserver } from 'd2l-resize-aware/resize-observer-module.js' +if (!global.performance) { + global.performance = {} +} +if (!global.performance.mark) { + global.performance.mark = () => {} +} +if (!global.performance.measure) { + global.performance.measure = () => {} +} + jest.mock('node-fetch', () => require('fetch-mock-jest').sandbox()) jest.setTimeout(60000) @@ -15,6 +25,13 @@ global.ResizeObserver = ResizeObserver process.env.NODE_ENV = 'test' global.IDBKeyRange = FDBKeyRange + +beforeAll(() => { + jest.spyOn(global.console, 'log').mockImplementation() + jest.spyOn(global.console, 'warn').mockImplementation() + jest.spyOn(global.console, 'error').mockImplementation() +}) + beforeEach(() => { global.indexedDB = new FDBFactory() // fresh indexedDB for every test }) diff --git a/package.json b/package.json index 4ba889f..ddb2c84 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-node-resolve": "^10.0.0", "@rollup/plugin-replace": "^2.3.4", + "@rollup/plugin-strip": "^2.0.1", "@testing-library/dom": "^7.29.0", "@testing-library/jest-dom": "^5.11.6", "@testing-library/user-event": "^12.6.0", diff --git a/rollup.config.js b/rollup.config.js index 0e1d396..aa52fd0 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,13 +1,15 @@ import cjs from '@rollup/plugin-commonjs' import resolve from '@rollup/plugin-node-resolve' import replace from '@rollup/plugin-replace' +import strip from '@rollup/plugin-strip' import mainSvelte from 'rollup-plugin-svelte' import hotSvelte from 'rollup-plugin-svelte-hot' import preprocess from 'svelte-preprocess' import analyze from 'rollup-plugin-analyzer' import cssnano from 'cssnano' -const dev = process.env.NODE_ENV !== 'production' +const { NODE_ENV } = process.env +const dev = NODE_ENV !== 'production' const svelte = dev ? hotSvelte : mainSvelte const preprocessConfig = preprocess({ @@ -58,6 +60,13 @@ const baseConfig = { dev, preprocess: preprocessConfig }), + strip({ + include: ['**/*.js', '**/*.svelte'], + functions: [ + (!dev && !process.env.PERF) && 'performance.*', + !dev && 'console.log' + ].filter(Boolean) + }), !dev && analyze({ summaryOnly: true }) ], external: [ diff --git a/src/database/Database.js b/src/database/Database.js index ec3f50c..038bc28 100644 --- a/src/database/Database.js +++ b/src/database/Database.js @@ -18,7 +18,6 @@ import { getEmojiBySearchQuery, getEmojiByShortcode, getEmojiByUnicode, get, set, getTopFavoriteEmoji, incrementFavoriteEmojiCount } from './idbInterface' -import { log } from '../shared/log' import { customEmojiIndex } from './customEmojiIndex' import { cleanEmoji } from './utils/cleanEmoji' import { loadDataForFirstTime, checkForUpdates } from './dataLoading' @@ -135,7 +134,7 @@ export default class Database { // clear references to IDB, e.g. during a close event _clear () { - log('_clear database', this._dbName) + console.log('_clear database', this._dbName) // We don't need to call removeEventListener or remove the manual "close" listeners. // The memory leak tests prove this is unnecessary. It's because: // 1) IDBDatabases that can no longer fire "close" automatically have listeners GCed diff --git a/src/database/dataLoading.js b/src/database/dataLoading.js index 82479fb..579911a 100644 --- a/src/database/dataLoading.js +++ b/src/database/dataLoading.js @@ -1,7 +1,6 @@ import { getETag, getETagAndData } from './utils/ajax' import { jsonChecksum } from './utils/jsonChecksum' import { hasData, loadData } from './idbInterface' -import { log } from '../shared/log' export async function checkForUpdates (db, dataSource) { // just do a simple HEAD request first to see if the eTags match @@ -16,9 +15,9 @@ export async function checkForUpdates (db, dataSource) { } } if (await hasData(db, dataSource, eTag)) { - log('Database already populated') + console.log('Database already populated') } else { - log('Database update available') + console.log('Database update available') if (!emojiData) { const eTagAndData = await getETagAndData(dataSource) emojiData = eTagAndData[1] diff --git a/src/database/databaseLifecycle.js b/src/database/databaseLifecycle.js index 4af5385..38dc5ac 100644 --- a/src/database/databaseLifecycle.js +++ b/src/database/databaseLifecycle.js @@ -1,6 +1,5 @@ import { initialMigration } from './migrations' import { DB_VERSION_INITIAL, DB_VERSION_CURRENT } from './constants' -import { mark, stop } from '../shared/marks' const openReqs = {} const databaseCache = {} @@ -16,7 +15,7 @@ function handleOpenOrDeleteReq (resolve, reject, req) { } async function createDatabase (dbName) { - mark('createDatabase') + performance.mark('createDatabase') const db = await new Promise((resolve, reject) => { const req = indexedDB.open(dbName, DB_VERSION_CURRENT) openReqs[dbName] = req @@ -38,7 +37,7 @@ async function createDatabase (dbName) { // Unfortunately cannot test in fakeIndexedDB: https://github.com/dumbmatter/fakeIndexedDB/issues/50 /* istanbul ignore next */ db.onclose = () => closeDatabase(dbName) - stop('createDatabase') + performance.measure('createDatabase', 'createDatabase') return db } diff --git a/src/database/idbInterface.js b/src/database/idbInterface.js index 56d11b6..6201f22 100644 --- a/src/database/idbInterface.js +++ b/src/database/idbInterface.js @@ -7,7 +7,6 @@ import { STORE_KEYVALUE } from './constants' import { transformEmojiData } from './utils/transformEmojiData' -import { mark, stop } from '../shared/marks' import { extractTokens } from './utils/extractTokens' import { getAllIDB, getAllKeysIDB, getIDB } from './idbUtil' import { findCommonMembers } from './utils/findCommonMembers' @@ -62,7 +61,7 @@ async function doFullDatabaseScanForSingleResult (db, predicate) { } export async function loadData (db, emojiData, url, eTag) { - mark('loadData') + performance.mark('loadData') try { const transformedData = transformEmojiData(emojiData) await dbPromise(db, [STORE_EMOJI, STORE_KEYVALUE], MODE_READWRITE, ([emojiStore, metaStore]) => { @@ -92,7 +91,7 @@ export async function loadData (db, emojiData, url, eTag) { } metaStore.put(eTag, KEY_ETAG) metaStore.put(url, KEY_URL) - mark('commitAllData') + performance.mark('commitAllData') } getIDB(metaStore, KEY_ETAG, result => { @@ -110,9 +109,9 @@ export async function loadData (db, emojiData, url, eTag) { checkFetched() }) }) - stop('commitAllData') + performance.measure('commitAllData', 'commitAllData') } finally { - stop('loadData') + performance.measure('loadData', 'loadData') } } diff --git a/src/database/utils/ajax.js b/src/database/utils/ajax.js index fb8fe22..121b3d7 100644 --- a/src/database/utils/ajax.js +++ b/src/database/utils/ajax.js @@ -1,6 +1,5 @@ import { warnETag } from './warnETag' import { assertEmojiData } from './assertEmojiData' -import { mark, stop } from '../../shared/marks' function assertStatus (response, dataSource) { if (Math.floor(response.status / 100) !== 2) { @@ -9,23 +8,23 @@ function assertStatus (response, dataSource) { } export async function getETag (dataSource) { - mark('getETag') + performance.mark('getETag') const response = await fetch(dataSource, { method: 'HEAD' }) assertStatus(response, dataSource) const eTag = response.headers.get('etag') warnETag(eTag) - stop('getETag') + performance.measure('getETag', 'getETag') return eTag } export async function getETagAndData (dataSource) { - mark('getETagAndData') + performance.mark('getETagAndData') const response = await fetch(dataSource) assertStatus(response, dataSource) const eTag = response.headers.get('etag') warnETag(eTag) const emojiData = await response.json() assertEmojiData(emojiData) - stop('getETagAndData') + performance.measure('getETagAndData', 'getETagAndData') return [eTag, emojiData] } diff --git a/src/database/utils/jsonChecksum.js b/src/database/utils/jsonChecksum.js index 7093c79..bf03f1e 100644 --- a/src/database/utils/jsonChecksum.js +++ b/src/database/utils/jsonChecksum.js @@ -1,15 +1,14 @@ import { binaryStringToArrayBuffer, arrayBufferToBinaryString } from 'blob-util' -import { mark, stop } from '../../shared/marks' // generate a checksum based on the stringified JSON export async function jsonChecksum (object) { - mark('jsonChecksum') + performance.mark('jsonChecksum') const inString = JSON.stringify(object) const inBuffer = binaryStringToArrayBuffer(inString) // this does not need to be cryptographically secure, SHA-1 is fine const outBuffer = await crypto.subtle.digest('SHA-1', inBuffer) const outBinString = arrayBufferToBinaryString(outBuffer) const res = btoa(outBinString) - stop('jsonChecksum') + performance.measure('jsonChecksum', 'jsonChecksum') return res } diff --git a/src/database/utils/transformEmojiData.js b/src/database/utils/transformEmojiData.js index 96fe725..7bb975f 100644 --- a/src/database/utils/transformEmojiData.js +++ b/src/database/utils/transformEmojiData.js @@ -1,10 +1,9 @@ -import { mark, stop } from '../../shared/marks' import { extractTokens } from './extractTokens' import { normalizeTokens } from './normalizeTokens' // Transform emoji data for storage in IDB export function transformEmojiData (emojiData) { - mark('transformEmojiData') + performance.mark('transformEmojiData') const res = emojiData.map(({ annotation, emoticon, group, order, shortcodes, skins, tags, emoji, version }) => { const tokens = [...new Set( normalizeTokens([ @@ -41,6 +40,6 @@ export function transformEmojiData (emojiData) { } return res }) - stop('transformEmojiData') + performance.measure('transformEmojiData', 'transformEmojiData') return res } diff --git a/src/database/utils/warnETag.js b/src/database/utils/warnETag.js index 41a9ae5..ad228ef 100644 --- a/src/database/utils/warnETag.js +++ b/src/database/utils/warnETag.js @@ -1,7 +1,6 @@ -import { warn } from '../../shared/log' export function warnETag (eTag) { if (!eTag) { - warn('emoji-picker-element is more efficient if the dataSource server exposes an ETag header.') + console.warn('emoji-picker-element is more efficient if the dataSource server exposes an ETag header.') } } diff --git a/src/picker/PickerElement.js b/src/picker/PickerElement.js index fc4ae1c..defd1df 100644 --- a/src/picker/PickerElement.js +++ b/src/picker/PickerElement.js @@ -1,10 +1,8 @@ import SveltePicker from './components/Picker/Picker.svelte' -import { mark } from '../shared/marks' -import { log } from '../shared/log' export default class Picker extends SveltePicker { constructor (props) { - mark('initialLoad') + performance.mark('initialLoad') // Make the API simpler, directly pass in the props super({ props }) } @@ -12,7 +10,7 @@ export default class Picker extends SveltePicker { disconnectedCallback () { // Have to explicitly destroy the component to avoid memory leaks. // See https://github.com/sveltejs/svelte/issues/1152 - log('disconnectedCallback') + console.log('disconnectedCallback') this.$destroy() } diff --git a/src/picker/components/Picker/Picker.js b/src/picker/components/Picker/Picker.js index d79b8eb..26bb2a6 100644 --- a/src/picker/components/Picker/Picker.js +++ b/src/picker/components/Picker/Picker.js @@ -8,7 +8,6 @@ import { MIN_SEARCH_TEXT_LENGTH, NUM_SKIN_TONES } from '../../../shared/constant import { requestIdleCallback } from '../../utils/requestIdleCallback' import { hasZwj } from '../../utils/hasZwj' import { emojiSupportLevelPromise, supportedZwjEmojis } from '../../utils/emojiSupport' -import { logError, log } from '../../../shared/log' import { applySkinTone } from '../../utils/applySkinTone' import { halt } from '../../utils/halt' import { incrementOrDecrement } from '../../utils/incrementOrDecrement' @@ -23,7 +22,6 @@ import { summarizeEmojisForUI } from '../../utils/summarizeEmojisForUI' import * as widthCalculator from '../../utils/widthCalculator' import { checkZwjSupport } from '../../utils/checkZwjSupport' import { requestPostAnimationFrame } from '../../utils/requestPostAnimationFrame' -import { stop } from '../../../shared/marks' import { onMount, onDestroy, tick } from 'svelte' import { requestAnimationFrame } from '../../utils/requestAnimationFrame' import { uniq } from '../../../shared/uniq' @@ -128,7 +126,7 @@ $: { await database.ready() databaseLoaded = true // eslint-disable-line no-unused-vars } catch (err) { - logError(err) + console.error(err) message = i18n.networkErrorMessage } finally { clearTimeout(timeoutHandle) @@ -150,24 +148,24 @@ $: { // See https://github.com/sveltejs/svelte/pull/4527 onMount(async () => { await tick() - log('props ready: setting locale and dataSource to default') + console.log('props ready: setting locale and dataSource to default') locale = locale || DEFAULT_LOCALE dataSource = dataSource || DEFAULT_DATA_SOURCE }) $: { if (locale && dataSource && (!database || (database.locale !== locale && database.dataSource !== dataSource))) { - log('creating database', { locale, dataSource }) + console.log('creating database', { locale, dataSource }) database = new Database({ dataSource, locale }) } } onDestroy(async () => { if (database) { - log('closing database') + console.log('closing database') try { await database.close() } catch (err) { - logError(err) // only happens if the database failed to load in the first place, so we don't care + console.error(err) // only happens if the database failed to load in the first place, so we don't care } } }) @@ -190,7 +188,7 @@ $: pickerStyle = ` $: { if (customEmoji && database) { - log('updating custom emoji') + console.log('updating custom emoji') database.customEmoji = customEmoji } } @@ -239,7 +237,7 @@ $: { $: { async function updateFavorites () { - log('updateFavorites') + console.log('updateFavorites') const dbFavorites = await database.getTopFavoriteEmoji(numColumns) const favorites = await summarizeEmojis(uniqBy([ ...dbFavorites, @@ -314,7 +312,7 @@ $: { $: { async function updateEmojis () { - log('updateEmojis') + console.log('updateEmojis') if (!databaseLoaded) { currentEmojis = [] searchMode = false @@ -378,7 +376,7 @@ async function summarizeEmojis (emojis) { } async function getEmojisByGroup (group) { - log('getEmojiByGroup', group) + console.log('getEmojiByGroup', group) if (typeof group === 'undefined') { return [] } @@ -397,7 +395,7 @@ $: { if (process.env.NODE_ENV !== 'production' || process.env.PERF) { if (currentEmojis.length && currentFavorites.length && initialLoad) { initialLoad = false - requestPostAnimationFrame(() => stop('initialLoad')) + requestPostAnimationFrame(() => performance.measure('initialLoad', 'initialLoad')) } } } diff --git a/src/picker/utils/checkZwjSupport.js b/src/picker/utils/checkZwjSupport.js index f7416d4..66919c3 100644 --- a/src/picker/utils/checkZwjSupport.js +++ b/src/picker/utils/checkZwjSupport.js @@ -1,12 +1,10 @@ -import { mark, stop } from '../../shared/marks' import { calculateTextWidth } from './calculateTextWidth' import { supportedZwjEmojis } from './emojiSupport' -import { log } from '../../shared/log' let baselineEmojiWidth export function checkZwjSupport (zwjEmojisToCheck, baselineEmoji, emojiToDomNode) { - mark('checkZwjSupport') + performance.mark('checkZwjSupport') for (const emoji of zwjEmojisToCheck) { const domNode = emojiToDomNode(emoji) const emojiWidth = calculateTextWidth(domNode) @@ -21,10 +19,10 @@ export function checkZwjSupport (zwjEmojisToCheck, baselineEmoji, emojiToDomNode supportedZwjEmojis.set(emoji.unicode, supported) /* istanbul ignore next */ if (!supported) { - log('Filtered unsupported emoji', emoji.unicode, emojiWidth, baselineEmojiWidth) + console.log('Filtered unsupported emoji', emoji.unicode, emojiWidth, baselineEmojiWidth) } else if (emojiWidth !== baselineEmojiWidth) { - log('Allowed borderline emoji', emoji.unicode, emojiWidth, baselineEmojiWidth) + console.log('Allowed borderline emoji', emoji.unicode, emojiWidth, baselineEmojiWidth) } } - stop('checkZwjSupport') + performance.measure('checkZwjSupport', 'checkZwjSupport') } diff --git a/src/picker/utils/determineEmojiSupportLevel.js b/src/picker/utils/determineEmojiSupportLevel.js index e4bf09c..b07ab11 100644 --- a/src/picker/utils/determineEmojiSupportLevel.js +++ b/src/picker/utils/determineEmojiSupportLevel.js @@ -1,11 +1,10 @@ // rather than check every emoji ever, which would be expensive, just check some representatives from the // different emoji releases to determine what the font supports -import { mark, stop } from '../../shared/marks' import { versionsAndTestEmoji } from '../../../bin/versionsAndTestEmoji' import { testColorEmojiSupported } from './testColorEmojiSupported' export function determineEmojiSupportLevel () { - mark('determineEmojiSupportLevel') + performance.mark('determineEmojiSupportLevel') let res for (const [emoji, version] of Object.entries(versionsAndTestEmoji)) { /* istanbul ignore else */ @@ -15,6 +14,6 @@ export function determineEmojiSupportLevel () { break } } - stop('determineEmojiSupportLevel') + performance.measure('determineEmojiSupportLevel', 'determineEmojiSupportLevel') return res } diff --git a/src/picker/utils/emojiSupport.js b/src/picker/utils/emojiSupport.js index 2b91bb9..8bca299 100644 --- a/src/picker/utils/emojiSupport.js +++ b/src/picker/utils/emojiSupport.js @@ -1,5 +1,4 @@ import { determineEmojiSupportLevel } from './determineEmojiSupportLevel' -import { log } from '../../shared/log' import { requestIdleCallback } from './requestIdleCallback' // Check which emojis we know for sure aren't supported, based on Unicode version level export const emojiSupportLevelPromise = new Promise(resolve => ( @@ -14,6 +13,6 @@ export const supportedZwjEmojis = new Map() /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { emojiSupportLevelPromise.then(emojiSupportLevel => { - log('emoji support level', emojiSupportLevel) + console.log('emoji support level', emojiSupportLevel) }) } diff --git a/src/shared/log.js b/src/shared/log.js deleted file mode 100644 index 053d90f..0000000 --- a/src/shared/log.js +++ /dev/null @@ -1,22 +0,0 @@ -// @rollup/plugin-strip doesn't strip console.logs properly - -export function log () { - /* istanbul ignore if */ - if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { - console.log(...arguments) - } -} - -export function warn () { - /* istanbul ignore if */ - if (process.env.NODE_ENV !== 'test') { - console.warn(...arguments) - } -} - -export function logError () { - /* istanbul ignore if */ - if (process.env.NODE_ENV !== 'test') { - console.error(...arguments) - } -} diff --git a/src/shared/marks.js b/src/shared/marks.js deleted file mode 100644 index 339f285..0000000 --- a/src/shared/marks.js +++ /dev/null @@ -1,18 +0,0 @@ -// @rollup/plugin-strip doesn't properly strip performance.mark/measure - -/* istanbul ignore next */ -const hasPerfMarks = typeof performance !== 'undefined' && performance.mark && performance.measure - -export function mark (str) { - /* istanbul ignore next */ - if ((process.env.NODE_ENV !== 'production' || process.env.PERF) && hasPerfMarks) { - performance.mark(str) - } -} - -export function stop (str) { - /* istanbul ignore next */ - if ((process.env.NODE_ENV !== 'production' || process.env.PERF) && hasPerfMarks) { - performance.measure(str, str) - } -} diff --git a/test/spec/picker/attributes.test.js b/test/spec/picker/attributes.test.js index 5e59352..c49dfeb 100644 --- a/test/spec/picker/attributes.test.js +++ b/test/spec/picker/attributes.test.js @@ -24,6 +24,9 @@ describe('attributes tests', () => { expect(picker.dataSource).toEqual(FR_EMOJI) expect(picker.getAttribute('locale')).toEqual('fr') expect(picker.getAttribute('data-source')).toEqual(FR_EMOJI) + + document.body.removeChild(picker) + await tick(20) }) test('can set skintone emoji using an attribute', async () => { @@ -43,5 +46,8 @@ describe('attributes tests', () => { expect(getByRole(picker.shadowRoot, 'button', { name: /Choose a skin tone/ }).innerHTML) .toContain('🏃') expect(picker.skinToneEmoji).toEqual('🏃') + + document.body.removeChild(picker) + await tick(20) }) }) diff --git a/test/spec/picker/custom.test.js b/test/spec/picker/custom.test.js index a8625dc..d902706 100644 --- a/test/spec/picker/custom.test.js +++ b/test/spec/picker/custom.test.js @@ -30,5 +30,8 @@ describe('Custom emojis tests', () => { await tick(50) await waitFor(() => expect(getByRole(container, 'menuitem', { name: 'monkey' })).toBeVisible()) + + document.body.removeChild(picker) + await tick(20) }) }) diff --git a/yarn.lock b/yarn.lock index 9c90d8b..359077e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1540,6 +1540,15 @@ "@rollup/pluginutils" "^3.1.0" magic-string "^0.25.7" +"@rollup/plugin-strip@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-strip/-/plugin-strip-2.0.1.tgz#276e7789a33ae0b10bc8522a20f187d8a6b6b550" + integrity sha512-+JJInHt/90Ta/ofCH+YHrI6nyDKe9jVzwBkmnakjDUMD+2QUTPHy60jep+kaMm4ARKWavtfmPbQp7e+xxAHU7g== + dependencies: + "@rollup/pluginutils" "^3.1.0" + estree-walker "^2.0.1" + magic-string "^0.25.7" + "@rollup/pluginutils@^3.0.8": version "3.0.10" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.0.10.tgz#a659b9025920378494cd8f8c59fbf9b3a50d5f12"