Skip to content

Fix headless loading via. commonJS - #401

Open
lentinj wants to merge 2 commits into
DataTables:masterfrom
lentinj:headless-loader
Open

Fix headless loading via. commonJS#401
lentinj wants to merge 2 commits into
DataTables:masterfrom
lentinj:headless-loader

Conversation

@lentinj

@lentinj lentinj commented Jul 29, 2026

Copy link
Copy Markdown

I've been tinkering with this on and off for a bit with DT 3 as suggested. The following fix #385 to the point that a script like this can run in nodeJS:

import { JSDOM } from 'jsdom';

// Vanilla DT import or bootstrap DT import (both should work now)
import DataTable from './DataTablesSrc/built/DataTables/js/dataTables.js';
//import DataTable from './DataTablesSrc/built/DataTables/js/dataTables.bootstrap5.js';

console.log("--------------------- Creating JSDOM window");
const jsd_window = (new JSDOM('<!DOCTYPE html><html><body></body></html>')).window;
global.window = jsd_window; // NB: Creating a global window object to potentially confuse the factory function

console.log("--------------------- Creating DataTable");
const dt = DataTable(jsd_window);
console.log(dt.ext.oStdClasses.container);

Using EJS however is a bit of a non-starter:

  • Extension-less imports (including the ones in dataTables.bootstrap5.mjs) will load the .js version without type: "module" in package.json. How safe this is to add I'm not sure.
  • A by-product of the CommonJS mechanism is they make the window & document objects available to other code. In EJS-land, this doesn't happen, there's a long string of code (e.g. the line in the second commit) that runs on import that assumes that window or document are defined.

There doesn't seem to be an obvious solution for the latter. Going round and doing globalThis.window ? ... seems sisyphean, and wouldn't be the right window object anyway. Maybe a separate headless wrapper would work, but I've not looked into how to construct one of those yet.

And thanks again for dataTables!

My contribution is offered under and will be made available under the project's existing license (MIT).

lentinj added 2 commits July 29, 2026 10:14
The CommonJS loader, for both dataTables.js and variants that import
it, will vary behaviour depending on whether a root window is defined
or not. If no window is available (read: nodejs), then a factory
function is returned.

Previously, this factory function would require() the base
dataTables.js at run time. However, by then a global window might
exist, and the wrapper doesn't get the factory class it's expecting.

Instead, require() all dependencies whilst the wrapper itself is being
require()d, so they get the same environment.

References: DataTables#385
In windowless environments (read: nodejs), location will not be
defined. However, the CommonJS wrapper makes sure window & document are
defined appropriately in these scenarios.

References: DataTables#385
@AllanJard

Copy link
Copy Markdown
Contributor

Interesting - many thanks for this. Is it specifically CommonJS that you want to use, or is that just a workaround as headless doesn't work with ES Modules (ESM, which is what I presume you mean by EJS)? Looking at the code above, finding some way to have the ESM loaded work for a headless environment looks more desirable (so you can use the styling packages).

@AllanJard

Copy link
Copy Markdown
Contributor

Out of interest, what is the use case for using DataTables headless - server-side rendering of the table?

@AllanJard

Copy link
Copy Markdown
Contributor

I've just been looking a bit more into using DataTables in a windowless environment, and I do have it setup to allow the window (and thus also document) property with the DataTable.use() static function, so you should be able to do something like:

import DataTable from 'datatables.net/js/dataTables.mjs';
import { JSDOM } from 'jsdom';

const jsd_window = new JSDOM(
	`<!DOCTYPE html>
	<html>
		<body>
			<table id="myTable"></table>
		</body>
	</html>`
).window;

DataTable.use(jsd_window);

const dt = new DataTable('#myTable');

However, that very definitely doesn't work. I've got a handful of "pre-init" setup functions that use window properties, such as checking the locale number format.

I'd really like to get this addressed so it does work in native ESM environments (it should also then work for CommonJS since it is loader independent), however while checking this I've also realised that my package.json doesn't use exports so Node.js doesn't see the ESM file, which is another change to be made.

What I think I'll do is set this as a 3.1 target. The changes are a bit too extensive to get into 3.0.1 (or any 3.0 patch). That shouldn't be too far away. I want to get StateRestore and SearchPanes ported to v3, then consider this. Apologies it isn't going to be a super fast fix.

@AllanJard AllanJard added this to the 3.1 milestone Jul 31, 2026
@lentinj

lentinj commented Jul 31, 2026

Copy link
Copy Markdown
Author

Is it specifically CommonJS that you want to use, or is that just a workaround as headless doesn't work with ES Modules (ESM, which is what I presume you mean by EJS)?

I'm not fussed, anything that works (and yes, sorry for getting the names wrong!)

what is the use case for using DataTables headless

Unit-testing modules containing custom column renderer functions that build on DataTable.render. I don't want a full DT instance, just enough that I can get at the utility classes and then exercise my own renderer functions.

The module is still browser code, so I'd like to keep doing import DataTable from 'datatables.net-bs5' at the top and then the tests do whatever they need to make that work. Currently that includes using CommonJS because of nodeJS' type: "module" shenanigans.

Getting a factory back in some cases is something that caused head-scratching, but sounds like this is already something you're picking apart. An aside, jQuery deprecated theirs with 4.0.0 and have a separate factory import.

What I think I'll do is set this as a 3.1 target

Yeah, that's more than fine. I did start looking at detangling the pre-init uses of window/document, but it clearly needed more structural thought.

I've conflated 2 things a bit here, quick fixes needed to improve CommonJS support in nodeJS and the issue of using ESM in nodeJS. If you'd like me to do the issue gardening to separate the latter into it's own issue then can do. If you'd rather not merge the pull request that's also fine, I think I can work around everything at this stage.

Thanks again for your help!

@AllanJard

Copy link
Copy Markdown
Contributor

Hi - many thanks for your reply and additional thoughts on this.

Yes, I think it probably wouldn't be right to pull in this PR at the moment, knowing that a full fix for both issues (which go hand in hand) will be coming in 3.1.

I'll leave this issue open for the moment though, as window-less is currently not going to work. I'll update when its done :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Headless loading of "datatables.net-bs5" fails if global.window defined.

2 participants