From 6d0b2f0ba252fa6c1a3abc291ac78dc755ae5ebf Mon Sep 17 00:00:00 2001 From: OKAN TIKTAS <346542017+OKAN-SOFTWARE@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:06:23 -0400 Subject: [PATCH 1/3] fix(html): exclude bundler from worker configuration Signed-off-by: OKAN TIKTAS <346542017+OKAN-SOFTWARE@users.noreply.github.com> --- .changeset/fix-doc-kit-1201.md | 8 ++++ packages/core/src/generators/types.d.ts | 3 ++ .../src/threading/__tests__/parallel.test.mjs | 45 +++++++++++++++++++ packages/core/src/threading/parallel.mjs | 20 ++++++++- packages/react/src/html/index.mjs | 3 ++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-doc-kit-1201.md diff --git a/.changeset/fix-doc-kit-1201.md b/.changeset/fix-doc-kit-1201.md new file mode 100644 index 000000000..9d53c1b3e --- /dev/null +++ b/.changeset/fix-doc-kit-1201.md @@ -0,0 +1,8 @@ +--- +'@doc-kit/core': patch +'@doc-kit/generator-react': patch +--- + +Fix custom HTML bundler configurations failing with DataCloneError +during parallel page generation by excluding main-thread-only +bundler functions from worker messages. diff --git a/packages/core/src/generators/types.d.ts b/packages/core/src/generators/types.d.ts index 8bee0399e..c1100a9d3 100644 --- a/packages/core/src/generators/types.d.ts +++ b/packages/core/src/generators/types.d.ts @@ -70,6 +70,9 @@ declare global { hasParallelProcessor?: boolean; + /** Configuration keys excluded from worker-thread messages. */ + workerConfigOmit?: string[]; + /** * The unified plugins this generator processes Markdown with, listed as * in the `markdown` option, with paths relative to its module. Each list diff --git a/packages/core/src/threading/__tests__/parallel.test.mjs b/packages/core/src/threading/__tests__/parallel.test.mjs index 4a2c3653b..029b69435 100644 --- a/packages/core/src/threading/__tests__/parallel.test.mjs +++ b/packages/core/src/threading/__tests__/parallel.test.mjs @@ -203,6 +203,51 @@ describe('createParallelWorker', () => { deepStrictEqual(await result, [0, 1, 2, 3, 4]); }); + it('does not clone main-thread-only HTML bundler functions', async () => { + const htmlGenerator = await loadGenerator('html'); + const transferred = []; + const pool = { + run(task) { + const copy = structuredClone(task); + transferred.push(copy); + return Promise.resolve(copy.input); + }, + }; + + const bundler = { + buildServer: async () => 'file:///library.mjs', + buildClient: async () => ({}), + compile: async code => code, + }; + + const configuration = { + threads: 2, + chunkSize: 1, + html: { + title: 'Worker regression test', + bundler, + }, + }; + + const worker = createParallelWorker( + 'html', + htmlGenerator, + pool, + configuration + ); + + const results = await collectStream(worker.stream([1, 2, 3], {})); + + deepStrictEqual(results, [1, 2, 3]); + strictEqual(transferred.length, 3); + for (const task of transferred) { + deepStrictEqual(task.configuration.html, { + title: 'Worker regression test', + }); + } + strictEqual(configuration.html.bundler, bundler); + }); + it('propagates a later worker failure without an unhandled rejection', async () => { const tasks = []; const pool = { diff --git a/packages/core/src/threading/parallel.mjs b/packages/core/src/threading/parallel.mjs index 136bac69f..d53223fd7 100644 --- a/packages/core/src/threading/parallel.mjs +++ b/packages/core/src/threading/parallel.mjs @@ -1,6 +1,7 @@ 'use strict'; import logger from '#logger/index.mjs'; +import { omitKeys } from '#utils/misc.mjs'; const parallelLogger = logger.child('parallel'); @@ -73,7 +74,15 @@ export default function createParallelWorker( ) { const { threads, chunkSize } = configuration; - const { name } = generator; + const { name, workerConfigOmit } = generator; + + // Main-thread-only options may contain functions that cannot be cloned. + const workerConfiguration = workerConfigOmit?.length + ? { + ...configuration, + [name]: omitKeys(configuration[name], workerConfigOmit), + } + : configuration; return { /** @@ -110,7 +119,14 @@ export default function createParallelWorker( // unhandled rejection while earlier chunks are still running. const tasks = chunks.map(indices => { const task = pool.run( - createTask(items, indices, extra, configuration, specifier, name) + createTask( + items, + indices, + extra, + workerConfiguration, + specifier, + name + ) ); return task.then( diff --git a/packages/react/src/html/index.mjs b/packages/react/src/html/index.mjs index 607592a39..c3e48c583 100644 --- a/packages/react/src/html/index.mjs +++ b/packages/react/src/html/index.mjs @@ -96,5 +96,8 @@ export default { // page count, so it is farmed out to the worker pool. hasParallelProcessor: true, + // The bundler runs on the main thread and contains non-cloneable functions. + workerConfigOmit: ['bundler'], + processChunk, }; From 2aba13b386b03910fedbefc396b19706d4b4ad46 Mon Sep 17 00:00:00 2001 From: OKAN TIKTAS <346542017+OKAN-SOFTWARE@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:10:35 -0400 Subject: [PATCH 2/3] refactor(threading): simplify worker configuration filtering --- packages/core/src/threading/parallel.mjs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/core/src/threading/parallel.mjs b/packages/core/src/threading/parallel.mjs index d53223fd7..3845c9036 100644 --- a/packages/core/src/threading/parallel.mjs +++ b/packages/core/src/threading/parallel.mjs @@ -77,12 +77,13 @@ export default function createParallelWorker( const { name, workerConfigOmit } = generator; // Main-thread-only options may contain functions that cannot be cloned. - const workerConfiguration = workerConfigOmit?.length - ? { - ...configuration, - [name]: omitKeys(configuration[name], workerConfigOmit), - } - : configuration; + const workerConfiguration = { ...configuration }; + if (configuration[name]) { + workerConfiguration[name] = omitKeys( + configuration[name], + workerConfigOmit + ); + } return { /** From 4b122c7f3817af09a95e2578d4a53b34bb2bcc9a Mon Sep 17 00:00:00 2001 From: OKAN TIKTAS <346542017+OKAN-SOFTWARE@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:11:07 -0400 Subject: [PATCH 3/3] style(threading): format worker configuration assignment --- packages/core/src/threading/parallel.mjs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core/src/threading/parallel.mjs b/packages/core/src/threading/parallel.mjs index 3845c9036..641b9e111 100644 --- a/packages/core/src/threading/parallel.mjs +++ b/packages/core/src/threading/parallel.mjs @@ -79,10 +79,7 @@ export default function createParallelWorker( // Main-thread-only options may contain functions that cannot be cloned. const workerConfiguration = { ...configuration }; if (configuration[name]) { - workerConfiguration[name] = omitKeys( - configuration[name], - workerConfigOmit - ); + workerConfiguration[name] = omitKeys(configuration[name], workerConfigOmit); } return {