✨ Add Node.js parsing-only entry point#5
Conversation
Add `@likecoin/epub-ts/node` subpath export that shims DOMParser, XMLSerializer, and document via linkedom, enabling EPUB parsing (metadata, spine, navigation, section rendering) in Node.js without a browser. - Add linkedom as optional peerDependency - Create src/node.ts with linkedom shims and parsing-safe re-exports - Add vite.config.node.ts for ESM + CJS node bundles - Guard window references in archive.ts, url.ts, replacements.ts - Replace window.decodeURIComponent with global decodeURIComponent - Add try/catch for CSS namespace selectors in querySelectorByType - Add getElementsByTagName fallback for parsers without NS support - Add 10 Node.js integration tests (191 total)
- Fix typo: decodededUrl → decodedUrl in archive.ts (6 occurrences) - Remove `any` escape: use typed assertion for globalThis.document in node.ts - Fix README example: slice Buffer to exact ArrayBuffer to avoid corruption
There was a problem hiding this comment.
Pull request overview
Adds a Node.js-specific, parsing-only entry point to epub.ts so consumers can parse EPUBs (metadata/spine/navigation/section HTML) in Node without a browser DOM, while keeping the existing browser-focused API intact.
Changes:
- Added
@likecoin/epub-ts/nodeentry point withlinkedom-based DOM shims (DOMParser,XMLSerializer,document). - Added a dedicated Node build (
vite.config.node.ts) and package subpath export (./node) producingepub.node.js/epub.node.cjs+node.d.ts. - Improved Node/SSR compatibility by guarding
windowusage and adding parser fallbacks for selector / namespace limitations; added Vitest Node coverage + docs updates.
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| vite.config.node.ts | Adds a separate Vite library build to emit Node ESM/CJS bundles and Node entry d.ts. |
| src/node.ts | Implements Node entry point with linkedom DOM shims and re-exports parsing-related classes. |
| package.json | Adds ./node export map entry, runs Node build in npm run build, and updates sideEffects + deps/peers for linkedom. |
| package-lock.json | Locks linkedom and its transitive dependencies. |
| test/node.test.ts | Adds Node-environment Vitest coverage for opening/parsing an EPUB via the Node entry. |
| src/utils/url.ts | Guards window access for Node/SSR import safety. |
| src/utils/replacements.ts | Guards window access for Node/SSR import safety. |
| src/utils/core.ts | Makes querySelectorByType resilient to DOM engines lacking namespaced selectors. |
| src/packaging.ts | Adds fallback lookup for DC metadata when XML namespace APIs aren’t supported. |
| src/archive.ts | Removes direct window.* usage (decodeURIComponent / URL selection) for Node compatibility. |
| README.md | Documents Node parsing-only usage and new import path. |
| PROJECT_STATUS.md | Updates status/outputs to reflect Node entry point completion and new test totals. |
| CHANGELOG.md | Documents new Node entry point and related compatibility fixes. |
| AGENTS.md | Documents new src/node.ts entry point file. |
src/archive.ts
Outdated
| const _URL = window.URL || window.webkitURL || window.mozURL; | ||
| const _URL = typeof window !== "undefined" ? (window.URL || window.webkitURL || window.mozURL) : URL; | ||
| for (const fromCache in this.urlCache) { | ||
| _URL.revokeObjectURL(fromCache); |
There was a problem hiding this comment.
In destroy(), the loop revokes object URLs using the cache key (fromCache) rather than the cached object URL value. Since urlCache maps original archive paths to generated object URLs, this likely leaves the created object URLs unreleased. Consider revoking this.urlCache[fromCache] (and checking it’s defined) instead of the key string.
| _URL.revokeObjectURL(fromCache); | |
| const cachedUrl = this.urlCache[fromCache]; | |
| if (cachedUrl) { | |
| _URL.revokeObjectURL(cachedUrl); | |
| } |
The for...in loop iterated urlCache keys (archive paths) and passed them to revokeObjectURL, leaving the actual blob URLs unreleased. Now revokes the cached URL values instead.
| console.log(book.navigation.toc.map(item => item.label)); | ||
|
|
||
| const section = book.spine.first(); | ||
| const html = await section.render(book.archive.request.bind(book.archive)); |
There was a problem hiding this comment.
The Node.js usage snippet won’t typecheck as written: book.archive is typed as possibly undefined, so book.archive.request.bind(book.archive) will error in TypeScript. Consider using a non-null assertion (matching the test) or add a small guard before calling section.render(...) so the snippet compiles cleanly.
| const html = await section.render(book.archive.request.bind(book.archive)); | |
| const html = await section.render(book.archive!.request.bind(book.archive!)); |
No description provided.