diff --git a/.changeset/guard-process-global.md b/.changeset/guard-process-global.md new file mode 100644 index 0000000000..061d84bab9 --- /dev/null +++ b/.changeset/guard-process-global.md @@ -0,0 +1,5 @@ +--- +'@tanstack/table-core': patch +--- + +Guard the dev-only `process.env.NODE_ENV` checks with `typeof process !== 'undefined'` so the published ESM build runs in environments without a `process` global, such as browsers loading the package through an import map. diff --git a/packages/table-core/src/core/columns/constructColumn.ts b/packages/table-core/src/core/columns/constructColumn.ts index b26f42f7d6..8161791832 100644 --- a/packages/table-core/src/core/columns/constructColumn.ts +++ b/packages/table-core/src/core/columns/constructColumn.ts @@ -74,7 +74,11 @@ 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 ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' && + result === undefined + ) { console.warn( `"${key}" in deeply nested key "${accessorKey}" returned undefined.`, ) @@ -90,7 +94,10 @@ export function constructColumn< } if (!id) { - if (process.env.NODE_ENV === 'development') { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' + ) { 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 7a6ea4bed7..54d2fd2cdd 100644 --- a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts +++ b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts @@ -280,7 +280,11 @@ export function table_getColumn< ): Column | undefined { const column = table.getAllFlatColumnsById()[columnId] - if (process.env.NODE_ENV === 'development' && !column) { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' && + !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 f37ce79828..58cb9cd54e 100644 --- a/packages/table-core/src/core/rows/coreRowsFeature.utils.ts +++ b/packages/table-core/src/core/rows/coreRowsFeature.utils.ts @@ -348,7 +348,10 @@ export function table_getRow< if (!row) { row = table.getCoreRowModel().rowsById[rowId] if (!row) { - if (process.env.NODE_ENV === 'development') { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' + ) { 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 7a6519d354..a48e569fa1 100644 --- a/packages/table-core/src/core/table/constructTable.ts +++ b/packages/table-core/src/core/table/constructTable.ts @@ -223,6 +223,7 @@ export function constructTable< } if ( + typeof process !== 'undefined' && process.env.NODE_ENV === 'development' && (tableOptions.debugAll || tableOptions.debugTable) ) { 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 0d4b355b3d..c5a35bd4dc 100644 --- a/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts +++ b/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts @@ -85,7 +85,11 @@ export function column_getAutoFilterFn< const filterFn = filterFns?.[filterFnName] - if (process.env.NODE_ENV === 'development' && !filterFn) { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' && + !filterFn + ) { console.warn( `filterFn '${filterFnName}' (auto) for column '${column.id}' is not registered`, ) @@ -123,6 +127,7 @@ export function column_getFilterFn< : filterFns?.[column.columnDef.filterFn as string] if ( + typeof process !== 'undefined' && process.env.NODE_ENV === 'development' && !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 1e9d396726..da17e14681 100644 --- a/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts +++ b/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts @@ -75,6 +75,7 @@ export function table_getGlobalFilterFn< : filterFns?.[globalFilterFn as string] if ( + typeof process !== 'undefined' && process.env.NODE_ENV === 'development' && !filterFn && globalFilterFn != null 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 1915b5b8ba..bd48969f46 100644 --- a/packages/table-core/src/features/row-aggregation/rowAggregationFeature.utils.ts +++ b/packages/table-core/src/features/row-aggregation/rowAggregationFeature.utils.ts @@ -48,7 +48,10 @@ function isAggregationFnDescriptor( } function warn(message: string) { - if (process.env.NODE_ENV === 'development') { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' + ) { 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 97e4b61a86..dbd44b502f 100644 --- a/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts +++ b/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts @@ -146,7 +146,10 @@ export function column_getAutoSortFn< let sortFn = sortFns?.[sortFnName] if (!sortFn) { - if (process.env.NODE_ENV === 'development') { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' + ) { console.warn( `sortFn '${sortFnName}' (auto) for column '${column.id}' is not registered`, ) @@ -230,7 +233,11 @@ export function column_getSortFn< const sortFn = sortFns?.[column.columnDef.sortFn as string] - if (process.env.NODE_ENV === 'development' && !sortFn) { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' && + !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 47c49c5533..5b7f893b30 100755 --- a/packages/table-core/src/utils.ts +++ b/packages/table-core/src/utils.ts @@ -405,7 +405,10 @@ export function tableMemo< let debug: boolean | undefined let debugCache: boolean | undefined - if (process.env.NODE_ENV === 'development') { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' + ) { const { debugAll } = table.options const { parentName } = getFunctionNameInfo(fnName, '.') @@ -467,7 +470,7 @@ export function tableMemo< } const debugOptions = - process.env.NODE_ENV === 'development' + typeof process !== 'undefined' && process.env.NODE_ENV === 'development' ? { onBeforeCompare: () => { if (debugCache) { diff --git a/packages/table-core/src/worker/createWorkerRowModel.ts b/packages/table-core/src/worker/createWorkerRowModel.ts index 3f34819b5b..3f2f38f63b 100644 --- a/packages/table-core/src/worker/createWorkerRowModel.ts +++ b/packages/table-core/src/worker/createWorkerRowModel.ts @@ -60,7 +60,11 @@ export function createWorkerRowModel( let warned = false const warnOnce = (message: string) => { - if (process.env.NODE_ENV === 'development' && !warned) { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' && + !warned + ) { warned = true console.warn(`[table-worker] ${message}`) } diff --git a/packages/table-core/tests/unit/core/table/processGlobal.test.ts b/packages/table-core/tests/unit/core/table/processGlobal.test.ts new file mode 100644 index 0000000000..2d3518d48b --- /dev/null +++ b/packages/table-core/tests/unit/core/table/processGlobal.test.ts @@ -0,0 +1,218 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { + columnFilteringFeature, + constructTable, + createFilteredRowModel, + createSortedRowModel, + globalFilteringFeature, + rowAggregationFeature, + rowSortingFeature, +} from '../../../../src' +import { testFeatures } from '../../../fixtures/features' +import { + createTableWorker, + createWorkerRowModel, + workerRowModelsFeature, +} from '../../../../src/experimental-worker-plugin' +import type { ColumnDef } from '../../../../src' + +const features = testFeatures({}) + +const processingFeatures = testFeatures({ + columnFilteringFeature, + globalFilteringFeature, + rowAggregationFeature, + rowSortingFeature, + filteredRowModel: createFilteredRowModel(), + sortedRowModel: createSortedRowModel(), +}) + +type Person = { firstName: string; age: number } + +const data: Array = [ + { firstName: 'alice', age: 30 }, + { firstName: 'bob', age: 40 }, +] + +// No `filterFns`/`sortFns`/`aggregationFns` registries are provided, so every +// lookup misses and takes the dev-only warning branch. +const columns: Array> = [ + { accessorKey: 'firstName', id: 'firstName' }, + { + accessorKey: 'age', + id: 'age', + aggregationFn: 'unregistered' as any, + filterFn: 'unregistered' as any, + sortFn: 'unregistered' as any, + }, +] + +afterEach(() => { + vi.unstubAllGlobals() +}) + +describe('dev-only guards without a `process` global', () => { + it('constructs a table when `process` is not defined', () => { + vi.stubGlobal('process', undefined) + + expect(() => + constructTable({ + features, + columns: [], + data: [], + debugTable: true, + }), + ).not.toThrow() + }) + + it('looks up a missing column when `process` is not defined', () => { + const table = constructTable({ + features, + columns: [], + data: [], + }) + + vi.stubGlobal('process', undefined) + + expect(() => table.getColumn('missing')).not.toThrow() + }) + + it('looks up a missing row when `process` is not defined', () => { + const table = constructTable({ features, columns: [], data: [] }) + + vi.stubGlobal('process', undefined) + + expect(() => table.getRow('missing')).not.toThrow(TypeError) + }) + + it('constructs columns with unresolvable accessors when `process` is not defined', () => { + vi.stubGlobal('process', undefined) + + const table = constructTable({ + features, + columns: [{ accessorKey: 'details.missing.deep', id: 'deep' }], + data: [{ details: {} }], + }) + + expect(() => + table.getCoreRowModel().flatRows[0]!.getValue('deep'), + ).not.toThrow() + + expect(() => + constructTable({ + features, + // @ts-expect-error - an accessorFn column needs an explicit id + columns: [{ accessorFn: (row: Person) => row.firstName }], + data, + }).getAllColumns(), + ).not.toThrow(TypeError) + }) + + it('sorts by unregistered sort fns when `process` is not defined', () => { + const table = constructTable({ + features: processingFeatures, + columns, + data, + }) + + vi.stubGlobal('process', undefined) + + table.setSorting([ + { id: 'firstName', desc: false }, + { id: 'age', desc: true }, + ]) + + expect(() => table.getSortedRowModel()).not.toThrow() + }) + + it('applies column filters with unregistered filter fns when `process` is not defined', () => { + const table = constructTable({ + features: processingFeatures, + columns, + data, + }) + + vi.stubGlobal('process', undefined) + + table.setColumnFilters([ + { id: 'firstName', value: 'a' }, + { id: 'age', value: 30 }, + ]) + + expect(() => table.getFilteredRowModel()).not.toThrow() + }) + + it('applies a global filter with an unregistered filter fn when `process` is not defined', () => { + const table = constructTable({ + features: processingFeatures, + columns, + data, + globalFilterFn: 'unregistered' as any, + }) + + vi.stubGlobal('process', undefined) + + table.setGlobalFilter('a') + + expect(() => table.getFilteredRowModel()).not.toThrow() + }) + + it('resolves unregistered aggregation fns when `process` is not defined', () => { + const table = constructTable({ + features: processingFeatures, + columns, + data, + }) + + vi.stubGlobal('process', undefined) + + expect(() => table.getColumn('age')!.getAggregationFns()).not.toThrow() + expect(() => + table.getColumn('firstName')!.getAutoAggregationFn(), + ).not.toThrow() + }) + + it('reads a worker row model when `process` is not defined', () => { + const posted: Array = [] + class FakeWorker { + onmessage: ((event: { data: any }) => void) | null = null + postMessage(message: any) { + posted.push(message) + } + terminate() {} + } + let worker: FakeWorker + const tableWorker = createTableWorker({ + createWorker: () => (worker = new FakeWorker()) as unknown as Worker, + }) + vi.stubGlobal('Worker', FakeWorker) + const table = constructTable({ + features: testFeatures({ + columnFilteringFeature, + workerRowModelsFeature, + filteredRowModel: createWorkerRowModel(tableWorker, 'filtered'), + }), + columns: [{ accessorKey: 'firstName', id: 'firstName' }], + data, + }) + + vi.stubGlobal('process', undefined) + + table.getFilteredRowModel() + const request = posted + .filter((message) => message.type === 'process') + .at(-1) + worker!.onmessage?.({ + data: { + type: 'result', + requestId: request.requestId, + dataVersion: request.dataVersion, + stages: {}, + computeMs: 1, + }, + }) + table.setOptions((prev) => ({ ...prev, data: [...data] })) + + expect(() => table.getFilteredRowModel()).not.toThrow() + }) +})