diff --git a/browser/performance.ts b/browser/performance.ts new file mode 100644 index 00000000000..aec23b86950 --- /dev/null +++ b/browser/performance.ts @@ -0,0 +1,10 @@ +const global = + typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : {}; + +export default 'performance' in global + ? performance + : { + now(): 0 { + return 0; + } + }; diff --git a/browser/process.ts b/browser/process.ts new file mode 100644 index 00000000000..a526f24c035 --- /dev/null +++ b/browser/process.ts @@ -0,0 +1,11 @@ +interface MemoryUsage { + heapUsed: 0; +} + +export default { + memoryUsage(): MemoryUsage { + return { + heapUsed: 0 + }; + } +}; diff --git a/build-plugins/replace-browser-modules.ts b/build-plugins/replace-browser-modules.ts index 39c9acafe2a..25ac4b77abb 100644 --- a/build-plugins/replace-browser-modules.ts +++ b/build-plugins/replace-browser-modules.ts @@ -1,29 +1,35 @@ -import path from 'path'; -import { Plugin } from 'rollup'; +import { dirname, join, resolve } from 'path'; +import type { Plugin } from 'rollup'; -const ID_CRYPTO = path.resolve('src/utils/crypto'); -const ID_FS = path.resolve('src/utils/fs'); -const ID_HOOKACTIONS = path.resolve('src/utils/hookActions'); -const ID_PATH = path.resolve('src/utils/path'); -const ID_RESOLVEID = path.resolve('src/utils/resolveId'); +const ID_CRYPTO = resolve('src/utils/crypto'); +const ID_FS = resolve('src/utils/fs'); +const ID_HOOKACTIONS = resolve('src/utils/hookActions'); +const ID_PATH = resolve('src/utils/path'); +const ID_PERFORMANCE = resolve('src/utils/performance'); +const ID_PROCESS = resolve('src/utils/process'); +const ID_RESOLVEID = resolve('src/utils/resolveId'); export default function replaceBrowserModules(): Plugin { return { name: 'replace-browser-modules', - resolveId: (source, importee) => { + resolveId(source, importee) { if (importee && source[0] === '.') { - const resolved = path.join(path.dirname(importee), source); + const resolved = join(dirname(importee), source); switch (resolved) { case ID_CRYPTO: - return path.resolve('browser/crypto.ts'); + return resolve('browser/crypto.ts'); case ID_FS: - return path.resolve('browser/fs.ts'); + return resolve('browser/fs.ts'); case ID_HOOKACTIONS: - return path.resolve('browser/hookActions.ts'); + return resolve('browser/hookActions.ts'); case ID_PATH: - return path.resolve('browser/path.ts'); + return resolve('browser/path.ts'); + case ID_PERFORMANCE: + return resolve('browser/performance.ts'); + case ID_PROCESS: + return resolve('browser/process.ts'); case ID_RESOLVEID: - return path.resolve('browser/resolveId.ts'); + return resolve('browser/resolveId.ts'); } } } diff --git a/rollup.config.ts b/rollup.config.ts index 3273078b6bf..51ce7f40ace 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -1,4 +1,4 @@ -import fs from 'fs'; +import { readFileSync } from 'fs'; import path from 'path'; import alias from '@rollup/plugin-alias'; import commonjs from '@rollup/plugin-commonjs'; @@ -18,7 +18,7 @@ import pkg from './package.json'; const commitHash = (function () { try { - return fs.readFileSync('.commithash', 'utf-8'); + return readFileSync('.commithash', 'utf-8'); } catch { return 'unknown'; } @@ -90,8 +90,10 @@ export default (command: Record): RollupOptions | RollupOptions 'fs', 'fsevents', 'module', - 'path', 'os', + 'path', + 'perf_hooks', + 'process', 'stream', 'url', 'util' @@ -125,8 +127,7 @@ export default (command: Record): RollupOptions | RollupOptions ...nodePlugins, addCliEntry(), esmDynamicImport(), - // TODO this relied on an unpublished type update - (!command.configTest && collectLicenses()) as Plugin + !command.configTest && collectLicenses() ], strictDeprecations: true, treeshake diff --git a/src/utils/collapseSourcemaps.ts b/src/utils/collapseSourcemaps.ts index 5842126e0e8..2d188baaa03 100644 --- a/src/utils/collapseSourcemaps.ts +++ b/src/utils/collapseSourcemaps.ts @@ -49,7 +49,7 @@ class Link { const sources: string[] = []; const sourcesContent: string[] = []; const names: string[] = []; - const nameIndexMap: Map = new Map(); + const nameIndexMap = new Map(); const mappings = []; @@ -147,7 +147,7 @@ class Link { } function getLinkMap(warn: WarningHandler) { - return function linkMap(source: Source | Link, map: DecodedSourceMapOrMissing) { + return function linkMap(source: Source | Link, map: DecodedSourceMapOrMissing): Link { if (map.mappings) { return new Link(map, [source]); } diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts index 7489b3383ea..56752850646 100644 --- a/src/utils/crypto.ts +++ b/src/utils/crypto.ts @@ -1,3 +1,3 @@ -import { createHash as cryptoCreateHash, Hash } from 'crypto'; +import { createHash as cryptoCreateHash, type Hash } from 'crypto'; export const createHash = (): Hash => cryptoCreateHash('sha256'); diff --git a/src/utils/performance.ts b/src/utils/performance.ts new file mode 100644 index 00000000000..ba7aa092b4c --- /dev/null +++ b/src/utils/performance.ts @@ -0,0 +1 @@ +export { performance as default } from 'perf_hooks'; diff --git a/src/utils/process.ts b/src/utils/process.ts new file mode 100644 index 00000000000..768ba7d74fb --- /dev/null +++ b/src/utils/process.ts @@ -0,0 +1 @@ +export { default } from 'process'; diff --git a/src/utils/timers.ts b/src/utils/timers.ts index 6ecb47e94bb..d097f15131f 100644 --- a/src/utils/timers.ts +++ b/src/utils/timers.ts @@ -1,39 +1,18 @@ import { InputOptions, Plugin, SerializedTimings } from '../rollup/types'; - -type StartTime = [number, number]; +import performance from './performance'; +import process from './process'; interface Timer { memory: number; startMemory: number; - startTime: StartTime; + startTime: number; time: number; totalMemory: number; } -interface Timers { - [label: string]: Timer; -} - const NOOP = (): void => {}; -let getStartTime = (): StartTime => [0, 0]; -let getElapsedTime: (previous: StartTime) => number = () => 0; -let getMemory: () => number = () => 0; -let timers: Timers = {}; - -const normalizeHrTime = (time: [number, number]): number => time[0] * 1e3 + time[1] / 1e6; - -function setTimeHelpers(): void { - if (typeof process !== 'undefined' && typeof process.hrtime === 'function') { - getStartTime = process.hrtime.bind(process); - getElapsedTime = previous => normalizeHrTime(process.hrtime(previous)); - } else if (typeof performance !== 'undefined' && typeof performance.now === 'function') { - getStartTime = () => [performance.now(), 0]; - getElapsedTime = previous => performance.now() - previous[0]; - } - if (typeof process !== 'undefined' && typeof process.memoryUsage === 'function') { - getMemory = () => process.memoryUsage().heapUsed; - } -} + +let timers = new Map(); function getPersistedLabel(label: string, level: number): string { switch (level) { @@ -50,53 +29,58 @@ function getPersistedLabel(label: string, level: number): string { function timeStartImpl(label: string, level = 3): void { label = getPersistedLabel(label, level); - if (!timers.hasOwnProperty(label)) { - timers[label] = { + + const startMemory = process.memoryUsage().heapUsed; + const startTime = performance.now(); + + const timer = timers.get(label); + + if (timer === undefined) { + timers.set(label, { memory: 0, - startMemory: undefined as never, - startTime: undefined as never, + startMemory, + startTime, time: 0, totalMemory: 0 - }; + }); + } else { + timer.startMemory = startMemory; + timer.startTime = startTime; } - const currentMemory = getMemory(); - timers[label].startTime = getStartTime(); - timers[label].startMemory = currentMemory; } function timeEndImpl(label: string, level = 3): void { label = getPersistedLabel(label, level); - if (timers.hasOwnProperty(label)) { - const currentMemory = getMemory(); - timers[label].time += getElapsedTime(timers[label].startTime); - timers[label].totalMemory = Math.max(timers[label].totalMemory, currentMemory); - timers[label].memory += currentMemory - timers[label].startMemory; + + const timer = timers.get(label); + + if (timer !== undefined) { + const currentMemory = process.memoryUsage().heapUsed; + timer.memory += currentMemory - timer.startMemory; + timer.time += performance.now() - timer.startTime; + timer.totalMemory = Math.max(timer.totalMemory, currentMemory); } } export function getTimings(): SerializedTimings { const newTimings: SerializedTimings = {}; - for (const [label, { time, memory, totalMemory }] of Object.entries(timers)) { + + for (const [label, { memory, time, totalMemory }] of timers) { newTimings[label] = [time, memory, totalMemory]; } return newTimings; } -export let timeStart: (label: string, level?: number) => void = NOOP, - timeEnd: (label: string, level?: number) => void = NOOP; +export let timeStart: (label: string, level?: number) => void = NOOP; +export let timeEnd: (label: string, level?: number) => void = NOOP; -const TIMED_PLUGIN_HOOKS: { [hook: string]: boolean } = { - load: true, - resolveDynamicImport: true, - resolveId: true, - transform: true -}; +const TIMED_PLUGIN_HOOKS = ['load', 'resolveDynamicImport', 'resolveId', 'transform'] as const; function getPluginWithTimers(plugin: any, index: number): Plugin { - const timedPlugin: { [hook: string]: any } = {}; + const timedPlugin: Pick = {}; - for (const hook of Object.keys(plugin)) { - if (TIMED_PLUGIN_HOOKS[hook] === true) { + for (const hook of TIMED_PLUGIN_HOOKS) { + if (hook in plugin) { let timerLabel = `plugin ${index}`; if (plugin.name) { timerLabel += ` (${plugin.name})`; @@ -104,28 +88,28 @@ function getPluginWithTimers(plugin: any, index: number): Plugin { timerLabel += ` - ${hook}`; timedPlugin[hook] = function (...args: unknown[]) { timeStart(timerLabel, 4); - let result = plugin[hook].apply(this === timedPlugin ? plugin : this, args); + const result = plugin[hook](...args); timeEnd(timerLabel, 4); if (result && typeof result.then === 'function') { timeStart(`${timerLabel} (async)`, 4); - result = result.then((hookResult: unknown) => { + return result.then((hookResult: unknown) => { timeEnd(`${timerLabel} (async)`, 4); return hookResult; }); } return result; }; - } else { - timedPlugin[hook] = plugin[hook]; } } - return timedPlugin as Plugin; + return { + ...plugin, + ...timedPlugin + }; } export function initialiseTimers(inputOptions: InputOptions): void { if (inputOptions.perf) { - timers = {}; - setTimeHelpers(); + timers = new Map(); timeStart = timeStartImpl; timeEnd = timeEndImpl; inputOptions.plugins = inputOptions.plugins!.map(getPluginWithTimers); diff --git a/test/browser/index.js b/test/browser/index.js index e3f05d0620d..a084f320744 100644 --- a/test/browser/index.js +++ b/test/browser/index.js @@ -1,3 +1,8 @@ +// since we don't run the browser tests in an actual browser, we need to make `performance` +// globally accessible same as in the browser. this can be removed once `performance` is +// available globally in all supported platforms. [currently global for node.js v16+]. +global.performance = require('perf_hooks').performance; + const { basename, resolve } = require('path'); const fixturify = require('fixturify'); const { rollup } = require('../../dist/rollup.browser.js');