refactor(markdown): konsolidasi polyfill nodeCompat & hapus dependency dompurify

- CLN-01: Verifikasi dan konsolidasi polyfill worker_threads.markAsUncloneable ke helper nodeCompat.ts
- CLN-01: Dokumentasi mekanisme require-time (node-polyfill.cjs) vs import-time (nodeCompat.ts)
- CLN-02: Hapus dependency dompurify dan @types/dompurify yang tidak terpakai dari package.json
- Verifikasi seluruh test suite (13 test files, 165 tests) tetap 100% lulus
This commit is contained in:
a2nr 2026-08-28 11:12:39 +07:00
parent 2d557df061
commit 9087a77096
6 changed files with 43 additions and 34 deletions

View File

@ -1,4 +1,11 @@
// Polyfill markAsUncloneable on node:worker_threads for Node 20 runtime compatibility (undici/jsdom)
/**
* Polyfill worker_threads.markAsUncloneable untuk Node 20 runtime compatibility (undici/jsdom).
*
* Catatan:
* File CJS ini dimuat via NODE_OPTIONS="--require /app/node-polyfill.cjs" di Docker runner
* (require-time sebelum entrypoint Node dievaluasi).
* Untuk penggunaan import-time di TypeScript/ESM, lihat src/lib/services/markdown/nodeCompat.ts.
*/
try {
const wt = require('node:worker_threads');
if (wt && typeof wt.markAsUncloneable === 'undefined') {

View File

@ -12,9 +12,7 @@
"@codemirror/lang-markdown": "^6.3.0",
"@codemirror/lang-python": "^6.1.0",
"@codemirror/theme-one-dark": "^6.1.0",
"@types/dompurify": "^3.0.5",
"codemirror": "^6.0.1",
"dompurify": "^3.4.14",
"highlight.js": "^11.11.0",
"isomorphic-dompurify": "^3.23.0",
"katex": "^0.16.9",
@ -1575,15 +1573,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/dompurify": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
"integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
"license": "MIT",
"dependencies": {
"@types/trusted-types": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
@ -1619,6 +1608,7 @@
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"devOptional": true,
"license": "MIT"
},
"node_modules/@vitest/expect": {

View File

@ -27,9 +27,7 @@
"@codemirror/lang-markdown": "^6.3.0",
"@codemirror/lang-python": "^6.1.0",
"@codemirror/theme-one-dark": "^6.1.0",
"@types/dompurify": "^3.0.5",
"codemirror": "^6.0.1",
"dompurify": "^3.4.14",
"highlight.js": "^11.11.0",
"isomorphic-dompurify": "^3.23.0",
"katex": "^0.16.9",

View File

@ -6,16 +6,8 @@
* elemes-ts network namespace, so the backend lives on localhost.
*/
// Polyfill markAsUncloneable on node:worker_threads for Node 20 runtime compatibility (undici/jsdom)
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const wt = require('node:worker_threads');
if (wt && typeof wt.markAsUncloneable === 'undefined') {
wt.markAsUncloneable = () => {};
}
} catch {
// ignore
}
import { patchWorkerThreadsCompat } from '$lib/services/markdown/nodeCompat';
patchWorkerThreadsCompat();
import type { Handle } from '@sveltejs/kit';

View File

@ -0,0 +1,30 @@
/**
* Polyfill worker_threads.markAsUncloneable untuk kompatibilitas Node 20
* dengan isomorphic-dompurify/jsdom/undici.
*
* Root Cause:
* isomorphic-dompurify@3.23.0 -> jsdom@30.0.1 -> undici@8.10.0 memanggil
* `markAsUncloneable` dari `node:worker_threads`. Pada Node 20 runtime (v20.20.2),
* method ini belum ada (ditambahkan pada Node 22+), menyebabkan
* `TypeError: webidl.util.markAsUncloneable is not a function` saat SSR.
*
* Catatan:
* File ini adalah helper TypeScript/ESM (import-time). Terdapat juga salinan CJS di
* `frontend/node-polyfill.cjs` yang digunakan khusus untuk `NODE_OPTIONS="--require ..."`
* pada Docker container (require-time sebelum entrypoint Node).
*
* TODO: hapus polyfill ini kalau isomorphic-dompurify/jsdom sudah rilis versi
* yang kompatibel penuh dengan Node 20 tanpa markAsUncloneable, atau base image
* Docker sudah di-upgrade ke Node 22+.
*/
export function patchWorkerThreadsCompat(): void {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const wt = require('node:worker_threads');
if (wt && typeof wt.markAsUncloneable === 'undefined') {
wt.markAsUncloneable = () => {};
}
} catch {
// ignore - bukan environment Node (misal di browser runtime)
}
}

View File

@ -1,13 +1,5 @@
// Polyfill markAsUncloneable on node:worker_threads for Node 20 runtime compatibility (undici/jsdom)
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const wt = require('node:worker_threads');
if (wt && typeof wt.markAsUncloneable === 'undefined') {
wt.markAsUncloneable = () => {};
}
} catch {
// ignore
}
import { patchWorkerThreadsCompat } from './nodeCompat';
patchWorkerThreadsCompat();
import DOMPurify from 'isomorphic-dompurify';