Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/guard-process-env-checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/table-core': patch
---

Guard the `process` global before reading `process.env.NODE_ENV` in development-only debug and validation checks. Raw `process.env.NODE_ENV` reads survived unguarded into the published ESM build, so any environment without a `process` global (e.g. vanilla JS loaded via an import map, or another bundler-less setup) threw `ReferenceError: process is not defined` the first time one of these checks ran. All ~14 call sites now go through a shared `isDevelopmentEnv()` helper that checks `typeof process !== 'undefined'` first.
5 changes: 3 additions & 2 deletions packages/table-core/src/core/columns/constructColumn.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isDevelopmentEnv } from '../../utils'
import type { Table_Internal } from '../../types/Table'
import type { CellData, RowData } from '../../types/type-utils'
import type { TableFeatures } from '../../types/TableFeatures'
Expand Down Expand Up @@ -74,7 +75,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 (isDevelopmentEnv() && result === undefined) {
console.warn(
`"${key}" in deeply nested key "${accessorKey}" returned undefined.`,
)
Expand All @@ -90,7 +91,7 @@ export function constructColumn<
}

if (!id) {
if (process.env.NODE_ENV === 'development') {
if (isDevelopmentEnv()) {
throw new Error(
resolvedColumnDef.accessorFn
? `coreColumnsFeature require an id when using an accessorFn`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { callMemoOrStaticFn, makeObjectMap } from '../../utils'
import {
callMemoOrStaticFn,
isDevelopmentEnv,
makeObjectMap,
} from '../../utils'
import { table_getOrderColumnsFn } from '../../features/column-ordering/columnOrderingFeature.utils'
import { constructColumn } from './constructColumn'
import type { Table_Internal } from '../../types/Table'
Expand Down Expand Up @@ -280,7 +284,7 @@ export function table_getColumn<
): Column<TFeatures, TData, unknown> | undefined {
const column = table.getAllFlatColumnsById()[columnId]

if (process.env.NODE_ENV === 'development' && !column) {
if (isDevelopmentEnv() && !column) {
console.warn(`[Table] Column with id '${columnId}' does not exist.`)
}

Expand Down
4 changes: 2 additions & 2 deletions packages/table-core/src/core/rows/coreRowsFeature.utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { flattenBy, hasOwn, makeObjectMap } from '../../utils'
import { flattenBy, hasOwn, isDevelopmentEnv, makeObjectMap } from '../../utils'
import { constructCell } from '../cells/constructCell'
import type { Table_Internal } from '../../types/Table'
import type { RowData } from '../../types/type-utils'
Expand Down Expand Up @@ -348,7 +348,7 @@ export function table_getRow<
if (!row) {
row = table.getCoreRowModel().rowsById[rowId]
if (!row) {
if (process.env.NODE_ENV === 'development') {
if (isDevelopmentEnv()) {
throw new Error(`getRow could not find row with ID: ${rowId}`)
}
throw new Error()
Expand Down
4 changes: 2 additions & 2 deletions packages/table-core/src/core/table/constructTable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { shallow } from '@tanstack/store'
import { coreFeatures } from '../coreFeatures'
import { cloneState, hasOwn } from '../../utils'
import { cloneState, hasOwn, isDevelopmentEnv } from '../../utils'
import { atomToStore } from '../reactivity/coreReactivityFeature.utils'
import { table_syncExternalStateToBaseAtoms } from './coreTablesFeature.utils'
import type { Atom } from '@tanstack/store'
Expand Down Expand Up @@ -223,7 +223,7 @@ export function constructTable<
}

if (
process.env.NODE_ENV === 'development' &&
isDevelopmentEnv() &&
(tableOptions.debugAll || tableOptions.debugTable)
) {
const features = Object.keys(table._features)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { cloneState, functionalUpdate, isFunction } from '../../utils'
import {
cloneState,
functionalUpdate,
isDevelopmentEnv,
isFunction,
} from '../../utils'
import type { CellData, RowData, Updater } from '../../types/type-utils'
import type { TableFeatures } from '../../types/TableFeatures'
import type { Table_Internal } from '../../types/Table'
Expand Down Expand Up @@ -80,7 +85,7 @@ export function column_getAutoFilterFn<

const filterFn = filterFns?.[filterFnName]

if (process.env.NODE_ENV === 'development' && !filterFn) {
if (isDevelopmentEnv() && !filterFn) {
console.warn(
`filterFn '${filterFnName}' (auto) for column '${column.id}' is not registered`,
)
Expand Down Expand Up @@ -118,7 +123,7 @@ export function column_getFilterFn<
: filterFns?.[column.columnDef.filterFn as string]

if (
process.env.NODE_ENV === 'development' &&
isDevelopmentEnv() &&
!filterFn &&
column.columnDef.filterFn !== 'auto' // the auto picker warns on its own
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { filterFn_includesString } from '../column-filtering/filterFns'
import { cloneState, isFunction } from '../../utils'
import { cloneState, isDevelopmentEnv, isFunction } from '../../utils'
import type { Column_Internal } from '../../types/Column'
import type { FilterFn } from '../column-filtering/columnFilteringFeature.types'
import type { CellData, RowData } from '../../types/type-utils'
Expand Down Expand Up @@ -74,11 +74,7 @@ export function table_getGlobalFilterFn<
? table_getGlobalAutoFilterFn()
: filterFns?.[globalFilterFn as string]

if (
process.env.NODE_ENV === 'development' &&
!filterFn &&
globalFilterFn != null
) {
if (isDevelopmentEnv() && !filterFn && globalFilterFn != null) {
console.warn(`globalFilterFn '${String(globalFilterFn)}' is not registered`)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { hasOwn, makeObjectMap } from '../../utils'
import { hasOwn, isDevelopmentEnv, makeObjectMap } from '../../utils'
import type { Cell } from '../../types/Cell'
import type { Column, Column_Internal } from '../../types/Column'
import type { Row } from '../../types/Row'
Expand Down Expand Up @@ -48,7 +48,7 @@ function isAggregationFnDescriptor(
}

function warn(message: string) {
if (process.env.NODE_ENV === 'development') {
if (isDevelopmentEnv()) {
console.warn(message)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cloneState, isFunction } from '../../utils'
import { cloneState, isDevelopmentEnv, isFunction } from '../../utils'
import { reSplitAlphaNumeric, sortFn_basic } from './sortFns'
import type { CellData, RowData, Updater } from '../../types/type-utils'
import type { TableFeatures } from '../../types/TableFeatures'
Expand Down Expand Up @@ -144,7 +144,7 @@ export function column_getAutoSortFn<
let sortFn = sortFns?.[sortFnName]

if (!sortFn) {
if (process.env.NODE_ENV === 'development') {
if (isDevelopmentEnv()) {
console.warn(
`sortFn '${sortFnName}' (auto) for column '${column.id}' is not registered`,
)
Expand Down Expand Up @@ -228,7 +228,7 @@ export function column_getSortFn<

const sortFn = sortFns?.[column.columnDef.sortFn as string]

if (process.env.NODE_ENV === 'development' && !sortFn) {
if (isDevelopmentEnv() && !sortFn) {
console.warn(
`sortFn '${String(column.columnDef.sortFn)}' for column '${column.id}' is not registered`,
)
Expand Down
90 changes: 52 additions & 38 deletions packages/table-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ export function hasOwn(obj: object, key: PropertyKey): boolean {
return Object.prototype.hasOwnProperty.call(obj, key)
}

/**
* Reports whether the library should run its development-only debug and
* validation logic.
*
* Guards the `process` global so this is safe to call in environments with
* no bundler or Node.js runtime (e.g. an ESM build loaded directly via an
* import map), where a raw `process.env.NODE_ENV` read throws a
* `ReferenceError`.
*/
export function isDevelopmentEnv(): boolean {
return (
typeof process !== 'undefined' && process.env.NODE_ENV === 'development'
)
}

/**
* Creates a table state updater for a single state slice.
*
Expand Down Expand Up @@ -272,7 +287,7 @@ export function tableMemo<
let debug: boolean | undefined
let debugCache: boolean | undefined

if (process.env.NODE_ENV === 'development') {
if (isDevelopmentEnv()) {
const { debugAll } = table.options
const { parentName } = getFunctionNameInfo(fnName, '.')

Expand Down Expand Up @@ -333,44 +348,43 @@ export function tableMemo<
schedule(() => untrack(() => onAfterUpdate()))
}

const debugOptions =
process.env.NODE_ENV === 'development'
? {
onBeforeCompare: () => {
if (debugCache) {
beforeCompareTime = performance.now()
const debugOptions = isDevelopmentEnv()
? {
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)
}
},
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)
}
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,
Expand Down
4 changes: 2 additions & 2 deletions packages/table-core/src/worker/createWorkerRowModel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { tableMemo } from '../utils'
import { isDevelopmentEnv, tableMemo } from '../utils'
import { getTableWorkerBridge, syncTableWorker } from './createTableWorker'
import { rebuildRowModel } from './rebuildRowModel'
import { tableWorkerPipeline } from './tableWorkerProtocol'
Expand Down Expand Up @@ -60,7 +60,7 @@ export function createWorkerRowModel(
let warned = false

const warnOnce = (message: string) => {
if (process.env.NODE_ENV === 'development' && !warned) {
if (isDevelopmentEnv() && !warned) {
warned = true
console.warn(`[table-worker] ${message}`)
}
Expand Down
50 changes: 49 additions & 1 deletion packages/table-core/tests/unit/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { describe, expect, test, vi } from 'vitest'
import { afterEach, describe, expect, test, vi } from 'vitest'
import {
callMemoOrStaticFn,
cloneState,
copyInstancePropertiesWithoutMemos,
flattenBy,
functionalUpdate,
getFunctionNameInfo,
isDevelopmentEnv,
isFunction,
tableMemo,
} from '../../src/utils'
Expand Down Expand Up @@ -52,6 +53,27 @@ describe('tableMemo', () => {
expect(schedule).toHaveBeenCalledTimes(1)
expect(onAfterUpdate).toHaveBeenCalledTimes(1)
})

test('does not throw when the process global is not defined', () => {
vi.stubGlobal('process', undefined)

const memoized = tableMemo({
table: {
options: {},
_reactivity: {
schedule: (fn: () => void) => fn(),
untrack: (fn: () => void) => fn(),
},
} as any,
fnName: 'table.getValue',
fn: (value?: number) => value ?? 0,
memoDeps: (value?: number) => [value],
})

expect(() => memoized(1)).not.toThrow()

vi.unstubAllGlobals()
})
})

describe('functionalUpdate', () => {
Expand Down Expand Up @@ -165,6 +187,32 @@ describe('getFunctionNameInfo', () => {
})
})

describe('isDevelopmentEnv', () => {
afterEach(() => {
vi.unstubAllGlobals()
vi.unstubAllEnvs()
})

test('is true when NODE_ENV is "development"', () => {
vi.stubEnv('NODE_ENV', 'development')

expect(isDevelopmentEnv()).toBe(true)
})

test('is false when NODE_ENV is not "development"', () => {
vi.stubEnv('NODE_ENV', 'production')

expect(isDevelopmentEnv()).toBe(false)
})

test('is false, not throwing, when the process global is not defined', () => {
vi.stubGlobal('process', undefined)

expect(() => isDevelopmentEnv()).not.toThrow()
expect(isDevelopmentEnv()).toBe(false)
})
})

describe('callMemoOrStaticFn', () => {
test('prefers the instance method when present', () => {
const staticFn = vi.fn(() => 'static')
Expand Down