From 44b9644a429c6fe6f04a0c4c67ad1a44e8c8b269 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:30:43 +0100 Subject: [PATCH] perf: compute NODE_ENV up-front & flip opt-in The code published to npm still contains the `NODE_ENV` guards. If you're running this code in an env where that isn't set, or where it is `production`, you pay the cost _per call_ of the string comparison. We can avoid that by computing it up front since the env won't change. Then we instead have `if (IS_DEV)` rather than `if (process.env.NODE_ENV === ...)`. Additionally, many of these guards were previously on `=== 'development'`. This meant only something like vite in dev mode would see useful errors (since most other tools won't set NODE_ENV at all). If we flip this around to `!== 'production'`, it means all dev runtimes would get the more useful error messages. --- .../src/core/columns/constructColumn.ts | 6 +- .../core/columns/coreColumnsFeature.utils.ts | 4 +- .../src/core/rows/coreRowsFeature.utils.ts | 4 +- .../src/core/table/constructTable.ts | 7 +- .../columnFilteringFeature.utils.ts | 6 +- .../globalFilteringFeature.utils.ts | 8 +- .../rowAggregationFeature.utils.ts | 4 +- .../row-sorting/rowSortingFeature.utils.ts | 6 +- packages/table-core/src/utils.ts | 77 ++++++++++--------- .../src/worker/createWorkerRowModel.ts | 4 +- .../createFilteredRowModel.test.ts | 4 +- .../rowAggregationFeature.test.ts | 2 - .../features/fnRegistryResolution.test.ts | 22 +++--- 13 files changed, 80 insertions(+), 74 deletions(-) diff --git a/packages/table-core/src/core/columns/constructColumn.ts b/packages/table-core/src/core/columns/constructColumn.ts index 35815746da..e3725a50e4 100644 --- a/packages/table-core/src/core/columns/constructColumn.ts +++ b/packages/table-core/src/core/columns/constructColumn.ts @@ -10,6 +10,8 @@ import type { import type { Column } from '../../types/Column' import type { Column_CoreProperties } from './coreColumnsFeature.types' +const IS_DEV = process.env.NODE_ENV !== 'production' + /** * Creates or retrieves the column prototype for a table. * The prototype is cached on the table and shared by all column instances. @@ -75,7 +77,7 @@ export function constructColumn< for (let i = 0; i < keys.length; i++) { const key = keys[i]! result = result?.[key] - if (process.env.NODE_ENV === 'development' && result === undefined) { + if (IS_DEV && result === undefined) { console.warn( `"${key}" in deeply nested key "${accessorKey}" returned undefined.`, ) @@ -91,7 +93,7 @@ export function constructColumn< } if (!id) { - if (process.env.NODE_ENV === 'development') { + if (IS_DEV) { throw new Error( resolvedColumnDef.accessorFn ? `coreColumnsFeature require an id when using an accessorFn` diff --git a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts index 2fae8a1fd7..ff2c7ae577 100644 --- a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts +++ b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts @@ -247,6 +247,8 @@ export function table_getAllLeafColumnsById< return result } +const IS_DEV = process.env.NODE_ENV !== 'production' + /** * Looks up a column by id from the flat column map. * @@ -267,7 +269,7 @@ export function table_getColumn< ): Column | undefined { const column = table.getAllFlatColumnsById()[columnId] - if (process.env.NODE_ENV === 'development' && !column) { + if (IS_DEV && !column) { console.warn(`[Table] Column with id '${columnId}' does not exist.`) } diff --git a/packages/table-core/src/core/rows/coreRowsFeature.utils.ts b/packages/table-core/src/core/rows/coreRowsFeature.utils.ts index cf41cf7d67..a4eb3b28d3 100644 --- a/packages/table-core/src/core/rows/coreRowsFeature.utils.ts +++ b/packages/table-core/src/core/rows/coreRowsFeature.utils.ts @@ -7,6 +7,8 @@ import type { Row } from '../../types/Row' import type { Cell } from '../../types/Cell' import type { Row_RowExpanding } from '../../features/row-expanding/rowExpandingFeature.types' +const IS_DEV = process.env.NODE_ENV !== 'production' + /** * Returns this row's zero-based position in the current pre-pagination row * model. Rows outside that model return `-1`. @@ -341,7 +343,7 @@ export function table_getRow< if (!row) { row = table.getCoreRowModel().rowsById[rowId] if (!row) { - if (process.env.NODE_ENV === 'development') { + if (IS_DEV) { throw new Error(`getRow could not find row with ID: ${rowId}`) } throw new Error() diff --git a/packages/table-core/src/core/table/constructTable.ts b/packages/table-core/src/core/table/constructTable.ts index 3a2f648966..fae0c21edc 100644 --- a/packages/table-core/src/core/table/constructTable.ts +++ b/packages/table-core/src/core/table/constructTable.ts @@ -9,6 +9,8 @@ import type { Table, Table_Internal } from '../../types/Table' import type { TableOptions } from '../../types/TableOptions' import type { TableState, TableState_All } from '../../types/TableState' +const IS_DEV = process.env.NODE_ENV === 'development' + /** * Builds the initial table state from registered features and user initial state. * @@ -170,10 +172,7 @@ export function constructTable< featuresList[i]!.initTableInstanceData?.(table) } - if ( - process.env.NODE_ENV === 'development' && - (tableOptions.debugAll || tableOptions.debugTable) - ) { + if (IS_DEV && (tableOptions.debugAll || tableOptions.debugTable)) { const features = Object.keys(table._features) const rowModels = Object.entries({ coreRowModel, diff --git a/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts b/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts index 7770e2c265..c85e86c485 100644 --- a/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts +++ b/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts @@ -8,6 +8,8 @@ import type { FilterFn, } from './columnFilteringFeature.types' +const IS_DEV = process.env.NODE_ENV !== 'production' + /** * Creates the default column filter state. * @@ -71,7 +73,7 @@ export function column_getAutoFilterFn< const filterFn = filterFns?.[filterFnName] - if (process.env.NODE_ENV === 'development' && !filterFn) { + if (IS_DEV && !filterFn) { console.warn( `filterFn '${filterFnName}' (auto) for column '${column.id}' is not registered`, ) @@ -109,7 +111,7 @@ export function column_getFilterFn< : filterFns?.[column.columnDef.filterFn as string] if ( - process.env.NODE_ENV === 'development' && + IS_DEV && !filterFn && column.columnDef.filterFn !== 'auto' // the auto picker warns on its own ) { diff --git a/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts b/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts index 18882cf316..5c262b9827 100644 --- a/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts +++ b/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts @@ -6,6 +6,8 @@ import type { CellData, RowData } from '../../types/type-utils' import type { TableFeatures } from '../../types/TableFeatures' import type { Table_Internal } from '../../types/Table' +const IS_DEV = process.env.NODE_ENV !== 'production' + /** * Checks whether this accessor column participates in global filtering. * @@ -74,11 +76,7 @@ export function table_getGlobalFilterFn< ? table_getGlobalAutoFilterFn() : filterFns?.[globalFilterFn as string] - if ( - process.env.NODE_ENV === 'development' && - !filterFn && - globalFilterFn != null - ) { + if (IS_DEV && !filterFn && globalFilterFn != null) { console.warn(`globalFilterFn '${String(globalFilterFn)}' is not registered`) } diff --git a/packages/table-core/src/features/row-aggregation/rowAggregationFeature.utils.ts b/packages/table-core/src/features/row-aggregation/rowAggregationFeature.utils.ts index 9c28ed6d91..98ed5f0e06 100644 --- a/packages/table-core/src/features/row-aggregation/rowAggregationFeature.utils.ts +++ b/packages/table-core/src/features/row-aggregation/rowAggregationFeature.utils.ts @@ -14,6 +14,8 @@ import type { ResolvedAggregationFn, } from './rowAggregationFeature.types' +const IS_DEV = process.env.NODE_ENV !== 'production' + interface AggregationCacheEntry { aggregationFnOption: unknown dependency: unknown @@ -48,7 +50,7 @@ function isAggregationFnDescriptor( } function warn(message: string) { - if (process.env.NODE_ENV === 'development') { + if (IS_DEV) { console.warn(message) } } diff --git a/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts b/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts index f0aac2d012..925ba50598 100644 --- a/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts +++ b/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts @@ -10,6 +10,8 @@ import type { SortingState, } from './rowSortingFeature.types' +const IS_DEV = process.env.NODE_ENV !== 'production' + // State Utils /** @@ -120,7 +122,7 @@ export function column_getAutoSortFn< let sortFn = sortFns?.[sortFnName] if (!sortFn) { - if (process.env.NODE_ENV === 'development') { + if (IS_DEV) { console.warn( `sortFn '${sortFnName}' (auto) for column '${column.id}' is not registered`, ) @@ -197,7 +199,7 @@ export function column_getSortFn< const sortFn = sortFns?.[column.columnDef.sortFn as string] - if (process.env.NODE_ENV === 'development' && !sortFn) { + if (IS_DEV && !sortFn) { console.warn( `sortFn '${String(column.columnDef.sortFn)}' for column '${column.id}' is not registered`, ) diff --git a/packages/table-core/src/utils.ts b/packages/table-core/src/utils.ts index 81cd79b4f7..44acbadf8e 100755 --- a/packages/table-core/src/utils.ts +++ b/packages/table-core/src/utils.ts @@ -3,6 +3,8 @@ import type { NoInfer, RowData, Updater } from './types/type-utils' import type { TableFeatures } from './types/TableFeatures' import type { TableState, TableState_All } from './types/TableState' +const IS_DEV = process.env.NODE_ENV === 'development' + /** * Applies a TanStack updater to a value. * @@ -253,7 +255,7 @@ export function tableMemo< let debug: boolean | undefined let debugCache: boolean | undefined - if (process.env.NODE_ENV === 'development') { + if (IS_DEV) { const { debugAll } = table.options const { parentName } = getFunctionNameInfo(fnName, '.') @@ -314,44 +316,43 @@ export function tableMemo< schedule(() => untrack(() => onAfterUpdate())) } - const debugOptions = - process.env.NODE_ENV === 'development' - ? { - onBeforeCompare: () => { - if (debugCache) { - beforeCompareTime = performance.now() - } - }, - onAfterCompare: (depsChanged: boolean) => { - if (debugCache) { - afterCompareTime = performance.now() - const compareTime = - Math.round((afterCompareTime - beforeCompareTime) * 100) / 100 - if (!depsChanged) { - logTime(compareTime, depsChanged) - } - } - }, - onBeforeUpdate: () => { - if (debug) { - startCalcTime = performance.now() - } - }, - onAfterUpdate: () => { - if (debug) { - endCalcTime = performance.now() - const executionTime = - Math.round((endCalcTime - startCalcTime) * 100) / 100 - logTime(executionTime, true) + const debugOptions = IS_DEV + ? { + onBeforeCompare: () => { + if (debugCache) { + beforeCompareTime = performance.now() + } + }, + onAfterCompare: (depsChanged: boolean) => { + if (debugCache) { + afterCompareTime = performance.now() + const compareTime = + Math.round((afterCompareTime - beforeCompareTime) * 100) / 100 + if (!depsChanged) { + logTime(compareTime, depsChanged) } - onAfterUpdateHandler() - }, - } - : { - onAfterUpdate: () => { - onAfterUpdateHandler() - }, - } + } + }, + onBeforeUpdate: () => { + if (debug) { + startCalcTime = performance.now() + } + }, + onAfterUpdate: () => { + if (debug) { + endCalcTime = performance.now() + const executionTime = + Math.round((endCalcTime - startCalcTime) * 100) / 100 + logTime(executionTime, true) + } + onAfterUpdateHandler() + }, + } + : { + onAfterUpdate: () => { + onAfterUpdateHandler() + }, + } return memo({ ...memoOptions, diff --git a/packages/table-core/src/worker/createWorkerRowModel.ts b/packages/table-core/src/worker/createWorkerRowModel.ts index 7d2e7395de..e530dc352a 100644 --- a/packages/table-core/src/worker/createWorkerRowModel.ts +++ b/packages/table-core/src/worker/createWorkerRowModel.ts @@ -9,6 +9,8 @@ import type { RowData } from '../types/type-utils' import type { TableWorker } from './createTableWorker' import type { TableWorkerStage } from './tableWorkerProtocol' +const IS_DEV = process.env.NODE_ENV !== 'production' + type AnyTable = Table_Internal function capitalize(stage: string) { @@ -60,7 +62,7 @@ export function createWorkerRowModel( let warned = false const warnOnce = (message: string) => { - if (process.env.NODE_ENV === 'development' && !warned) { + if (IS_DEV && !warned) { warned = true console.warn(`[table-worker] ${message}`) } diff --git a/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts b/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts index 455a34f509..6ce70d8bcc 100644 --- a/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts +++ b/packages/table-core/tests/implementation/features/column-filtering/createFilteredRowModel.test.ts @@ -526,12 +526,10 @@ describe('createFilteredRowModel', () => { describe('unresolvable global filter fn', () => { afterEach(() => { - vi.unstubAllEnvs() vi.restoreAllMocks() }) - it('should apply no global filtering and warn in dev when the globalFilterFn name is not registered', () => { - vi.stubEnv('NODE_ENV', 'development') + it('should apply no global filtering and warn when the globalFilterFn name is not registered', () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) const table = constructTable({ diff --git a/packages/table-core/tests/implementation/features/row-aggregation/rowAggregationFeature.test.ts b/packages/table-core/tests/implementation/features/row-aggregation/rowAggregationFeature.test.ts index adaceb74eb..c4f5e8d356 100644 --- a/packages/table-core/tests/implementation/features/row-aggregation/rowAggregationFeature.test.ts +++ b/packages/table-core/tests/implementation/features/row-aggregation/rowAggregationFeature.test.ts @@ -20,7 +20,6 @@ import type { ColumnDef } from '../../../../src' afterEach(() => { vi.restoreAllMocks() - vi.unstubAllEnvs() }) describe('rowAggregationFeature', () => { @@ -289,7 +288,6 @@ describe('rowAggregationFeature', () => { }) it('warns and preserves undefined keys for invalid multi configurations', () => { - vi.stubEnv('NODE_ENV', 'development') const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) const features = testFeatures({ rowAggregationFeature, aggregationFns }) const table = constructTable({ diff --git a/packages/table-core/tests/unit/features/fnRegistryResolution.test.ts b/packages/table-core/tests/unit/features/fnRegistryResolution.test.ts index 1b8d44cdb9..bf6cdf9891 100644 --- a/packages/table-core/tests/unit/features/fnRegistryResolution.test.ts +++ b/packages/table-core/tests/unit/features/fnRegistryResolution.test.ts @@ -21,14 +21,12 @@ const data: Array = [ { name: 'amy', age: 20 }, ] -// The registry warnings only fire in development builds. -function spyOnDevWarnings() { - vi.stubEnv('NODE_ENV', 'development') +// Registry misconfiguration warnings fire outside production builds. +function spyOnWarnings() { return vi.spyOn(console, 'warn').mockImplementation(() => {}) } afterEach(() => { - vi.unstubAllEnvs() vi.restoreAllMocks() }) @@ -53,7 +51,7 @@ describe('filter fn registry resolution', () => { } it('warns and ignores the filter when the auto filter fn is not registered', () => { - const warn = spyOnDevWarnings() + const warn = spyOnWarnings() const table = makeFilterTable(undefined) expect(table.getFilteredRowModel().rows).toHaveLength(2) @@ -63,7 +61,7 @@ describe('filter fn registry resolution', () => { }) it('warns with the missing name for unregistered string filter fns', () => { - const warn = spyOnDevWarnings() + const warn = spyOnWarnings() const table = makeFilterTable(undefined, 'doesNotExist') expect(table.getFilteredRowModel().rows).toHaveLength(2) @@ -71,7 +69,7 @@ describe('filter fn registry resolution', () => { }) it('resolves individually registered filter fns without warning', () => { - const warn = spyOnDevWarnings() + const warn = spyOnWarnings() const table = makeFilterTable({ includesString: filterFn_includesString }) expect( @@ -102,7 +100,7 @@ describe('sort fn registry resolution', () => { } it('warns and falls back to basic when the auto sort fn is not registered', () => { - const warn = spyOnDevWarnings() + const warn = spyOnWarnings() const table = makeSortTable(undefined) // basic and text agree on plain strings, so sorting still works @@ -113,7 +111,7 @@ describe('sort fn registry resolution', () => { }) it('warns with the missing name for unregistered string sort fns', () => { - const warn = spyOnDevWarnings() + const warn = spyOnWarnings() const table = makeSortTable(undefined, 'doesNotExist') expect( @@ -123,7 +121,7 @@ describe('sort fn registry resolution', () => { }) it('resolves individually registered sort fns without warning', () => { - const warn = spyOnDevWarnings() + const warn = spyOnWarnings() const table = makeSortTable({ text: sortFn_text }) expect( @@ -135,7 +133,7 @@ describe('sort fn registry resolution', () => { describe('aggregation fn registry resolution', () => { it('warns and returns undefined when the auto aggregation fn is not registered', () => { - const warn = spyOnDevWarnings() + const warn = spyOnWarnings() const features = testFeatures({ rowAggregationFeature }) const table = constructTable({ features, @@ -148,7 +146,7 @@ describe('aggregation fn registry resolution', () => { }) it('leaves non-aggregatable value types unspecified without warning', () => { - const warn = spyOnDevWarnings() + const warn = spyOnWarnings() const features = testFeatures({ rowAggregationFeature }) const table = constructTable({ features,