Skip to content

Commit 515577c

Browse files
committed
refactor: add axe-core polyfill
1 parent ce7e644 commit 515577c

File tree

11 files changed

+134
-6
lines changed

11 files changed

+134
-6
lines changed

package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"@types/benchmark": "^2.1.5",
7575
"@types/debug": "^4.1.12",
7676
"@types/eslint": "^8.44.2",
77+
"@types/jsdom": "^27.0.0",
7778
"@types/node": "^22.13.4",
7879
"@types/react": "18.3.1",
7980
"@types/react-dom": "18.3.0",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Axe-core Polyfilled Import
3+
*
4+
* This file ensures the jsdom polyfill runs BEFORE axe-core is imported.
5+
* Due to ES module import hoisting, we must import the polyfill explicitly
6+
* at the top of this file, then import axe-core. This guarantees the correct
7+
* execution order: polyfill setup → axe-core import.
8+
*
9+
* IMPORT CHAIN:
10+
* 1. jsdom.polyfill.ts (sets globalThis.window and globalThis.document)
11+
* 2. This file (imports polyfill, then imports axe-core)
12+
* 3. safe-axe-core-import.ts (re-exports for clean imports)
13+
*
14+
* USAGE:
15+
* Do NOT import from this file directly. Use safe-axe-core-import.ts instead.
16+
*/
17+
// Import polyfill FIRST to ensure globals are set before axe-core loads
18+
// eslint-disable-next-line import/no-unassigned-import
19+
// Now safe to import axe-core - globals exist due to polyfill import above
20+
import axe from 'axe-core';
21+
import './jsdom.polyfill.js';
22+
23+
// Re-export axe default and all types used throughout the codebase
24+
export default axe;
25+
26+
export type {
27+
AxeResults,
28+
NodeResult,
29+
Result,
30+
IncompleteResult,
31+
RuleMetadata,
32+
ImpactValue,
33+
CrossTreeSelector,
34+
} from 'axe-core';

packages/plugin-axe/src/lib/groups.int.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import axe from 'axe-core';
21
import { axeCategoryGroupSlugSchema, axeWcagTagSchema } from './groups.js';
2+
import axe from './safe-axe-core-import.js';
33

44
describe('axeCategoryGroupSlugSchema', () => {
55
const axeCategoryTags = axe
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* JSDOM Polyfill Setup
3+
*
4+
* WHY THIS EXISTS:
5+
* axe-core has side effects on import - it expects global `window` and `document` objects
6+
* to be available when the module is loaded. In Node.js environments, these don't exist
7+
* by default. This polyfill creates a virtual DOM using JSDOM and sets these globals
8+
* before axe-core is imported.
9+
*
10+
* HOW IT WORKS:
11+
* - Creates a minimal JSDOM instance with a basic HTML document
12+
* - Sets globalThis.window and globalThis.document to the JSDOM window/document
13+
* - This must be imported BEFORE any axe-core imports to ensure globals exist
14+
*
15+
* IMPORT CHAIN:
16+
* This file is imported first by axe-core-polyfilled.ts, which then safely imports
17+
* axe-core. All other files should import from safe-axe-core-import.ts, not directly
18+
* from this file or from 'axe-core'.
19+
*
20+
* @see https://github.com/dequelabs/axe-core/issues/3962
21+
*/
22+
import { JSDOM } from 'jsdom';
23+
24+
const html = `<!DOCTYPE html>\n<html></html>`;
25+
const { window: jsdomWindow } = new JSDOM(html);
26+
27+
// Set globals for axe-core compatibility
28+
// eslint-disable-next-line functional/immutable-data
29+
globalThis.window = jsdomWindow;
30+
// eslint-disable-next-line functional/immutable-data
31+
globalThis.document = jsdomWindow.document;

packages/plugin-axe/src/lib/meta/transform.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import axe from 'axe-core';
21
import type { Audit, Group } from '@code-pushup/models';
32
import { objectToEntries, wrapTags } from '@code-pushup/utils';
43
import type { AxePreset } from '../config.js';
@@ -7,6 +6,7 @@ import {
76
CATEGORY_GROUPS,
87
getWcagPresetTags,
98
} from '../groups.js';
9+
import axe from '../safe-axe-core-import.js';
1010

1111
/** Loads Axe rules filtered by the specified preset. */
1212
export function loadAxeRules(preset: AxePreset): axe.RuleMetadata[] {

packages/plugin-axe/src/lib/runner/run-axe.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { AxeBuilder } from '@axe-core/playwright';
22
import ansis from 'ansis';
3-
import type { AxeResults } from 'axe-core';
43
import { createRequire } from 'node:module';
54
import path from 'node:path';
65
import {
@@ -17,6 +16,7 @@ import {
1716
logger,
1817
pluralizeToken,
1918
} from '@code-pushup/utils';
19+
import type { AxeResults } from '../safe-axe-core-import.js';
2020
import { type SetupFunction, runSetup } from './setup.js';
2121
import { toAuditOutputs } from './transform.js';
2222

packages/plugin-axe/src/lib/runner/runner.unit.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import type { AxeResults, IncompleteResult, Result } from 'axe-core';
21
import { type AuditOutput, DEFAULT_PERSIST_CONFIG } from '@code-pushup/models';
2+
import type {
3+
AxeResults,
4+
IncompleteResult,
5+
Result,
6+
} from '../safe-axe-core-import.js';
37
import type { AxeUrlResult } from './run-axe.js';
48
import { createRunnerFunction } from './runner.js';
59
import * as setup from './setup.js';

packages/plugin-axe/src/lib/runner/transform.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import axe from 'axe-core';
21
import type {
32
AuditOutput,
43
AuditOutputs,
@@ -11,6 +10,7 @@ import {
1110
pluralizeToken,
1211
truncateIssueMessage,
1312
} from '@code-pushup/utils';
13+
import axe from '../safe-axe-core-import.js';
1414

1515
/**
1616
* Transforms Axe results into audit outputs.

packages/plugin-axe/src/lib/runner/transform.unit.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import type { AxeResults, NodeResult, Result } from 'axe-core';
21
import type { AuditOutput } from '@code-pushup/models';
2+
import type {
3+
AxeResults,
4+
NodeResult,
5+
Result,
6+
} from '../safe-axe-core-import.js';
37
import { toAuditOutputs } from './transform.js';
48

59
function createMockNode(overrides: Partial<NodeResult> = {}): NodeResult {

0 commit comments

Comments
 (0)