chore: wire vendored foliate-js into the build
$foliate is a Vite-only alias, deliberately absent from kit.alias and tsconfig paths: TypeScript cannot resolve it, so the ambient declaration in src/lib/reader/foliate.d.ts is the only candidate and svelte-check never walks the ~11k lines of untyped vendored JS. The generated tsconfig includes src/**/*.js and checkJs is on, so src/lib/vendor is excluded as well — `exclude` replaces rather than merges, hence the repeated service-worker entries. optimizeDeps.include for construct-style-sheets-polyfill: it sits behind a dynamic import in fixed-layout.js, so Vite would otherwise discover it mid-session and force a page reload on the first fixed-layout EPUB. Verified with a temporary import that the graph resolves: view.js and paginator.js are emitted as chunks, and the bundled pdf.js is our stub rather than upstream's bare `import '@pdfjs/pdf.min.mjs'`.
This commit is contained in:
Vendored
+90
@@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* Hand-written types for the vendored foliate-js (src/lib/vendor/foliate-js).
|
||||||
|
*
|
||||||
|
* `$foliate` is a Vite-only alias, so TypeScript cannot resolve it and this
|
||||||
|
* ambient declaration is the only candidate — which is the point: svelte-check
|
||||||
|
* never walks the untyped vendored JS. Only the surface Chitai calls is typed.
|
||||||
|
* When you start using a new method, add it here rather than casting to `any`.
|
||||||
|
*/
|
||||||
|
declare module '$foliate/view.js' {
|
||||||
|
/** A TOC entry. `id` is assigned by foliate's `assignIDs`, not by the book. */
|
||||||
|
export interface FoliateTocItem {
|
||||||
|
id: number;
|
||||||
|
label: string;
|
||||||
|
href: string;
|
||||||
|
subitems?: FoliateTocItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `event.detail` of the `relocate` event. Shape from SectionProgress.getProgress. */
|
||||||
|
export interface FoliateRelocateDetail {
|
||||||
|
/** Overall progress through the book, 0–1. Chitai stores this as `percentage`. */
|
||||||
|
fraction: number;
|
||||||
|
section: { current: number; total: number };
|
||||||
|
location: { current: number; next: number; total: number };
|
||||||
|
/** Estimated remaining reading time, in minutes. */
|
||||||
|
time: { section: number; total: number };
|
||||||
|
/** Null when the book has no TOC entry covering this position. */
|
||||||
|
tocItem?: FoliateTocItem | null;
|
||||||
|
pageItem?: { label: string; href: string } | null;
|
||||||
|
cfi: string;
|
||||||
|
range?: Range;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `event.detail` of the `load` event. `doc` is the section's iframe document. */
|
||||||
|
export interface FoliateLoadDetail {
|
||||||
|
doc: Document;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FoliateRenderer extends HTMLElement {
|
||||||
|
/**
|
||||||
|
* A single string sets the stylesheet appended to <head>, which wins over the
|
||||||
|
* book's own CSS on equal specificity. A [before, after] tuple also sets one
|
||||||
|
* prepended to <head>, which the book's CSS overrides. Re-applied by the
|
||||||
|
* paginator on every section load. Absent on the fixed-layout renderer.
|
||||||
|
*/
|
||||||
|
setStyles?(styles: string | [before: string, after: string]): void;
|
||||||
|
getContents(): { doc: Document; index: number }[];
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FoliateBook {
|
||||||
|
toc?: FoliateTocItem[];
|
||||||
|
/** 'rtl' for right-to-left books; drives goLeft/goRight. */
|
||||||
|
dir?: string;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
rendition?: { layout?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Accepted by goTo/select/init: a CFI or href, a spine index, or a fraction. */
|
||||||
|
export type FoliateTarget = string | number | { fraction: number };
|
||||||
|
|
||||||
|
export class View extends HTMLElement {
|
||||||
|
book: FoliateBook;
|
||||||
|
renderer: FoliateRenderer;
|
||||||
|
/** True when the book is pre-paginated; the renderer is then foliate-fxl. */
|
||||||
|
isFixedLayout: boolean;
|
||||||
|
lastLocation: FoliateRelocateDetail | null;
|
||||||
|
|
||||||
|
open(book: File | string | FoliateBook): Promise<void>;
|
||||||
|
close(): void;
|
||||||
|
init(opts: { lastLocation?: FoliateTarget | null; showTextStart?: boolean }): Promise<void>;
|
||||||
|
|
||||||
|
/** Returns undefined on failure — it logs and swallows. Check before relying on it. */
|
||||||
|
resolveNavigation(target: FoliateTarget): { index: number; anchor?: unknown } | undefined;
|
||||||
|
goTo(target: FoliateTarget): Promise<{ index: number } | undefined>;
|
||||||
|
goToFraction(fraction: number): Promise<void>;
|
||||||
|
|
||||||
|
prev(distance?: number): Promise<void>;
|
||||||
|
next(distance?: number): Promise<void>;
|
||||||
|
/** Direction-aware: inverts against prev/next for rtl books. */
|
||||||
|
goLeft(): Promise<void>;
|
||||||
|
goRight(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeBook(file: File | string): Promise<FoliateBook>;
|
||||||
|
|
||||||
|
export class ResponseError extends Error {}
|
||||||
|
export class NotFoundError extends Error {}
|
||||||
|
export class UnsupportedTypeError extends Error {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { View } from '$foliate/view.js';
|
||||||
|
|
||||||
|
let pending: Promise<void> | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads foliate-js and registers its custom elements.
|
||||||
|
*
|
||||||
|
* view.js calls customElements.define() at module scope and subclasses
|
||||||
|
* HTMLElement, and its class fields construct DOM helpers eagerly — so importing
|
||||||
|
* it on the server throws. This module is safe to import anywhere because it
|
||||||
|
* touches nothing at module scope; the vendored code is only pulled in when
|
||||||
|
* loadFoliate() is called, which must be from onMount or a browser guard.
|
||||||
|
*
|
||||||
|
* The promise is cached so concurrent callers share one load. ESM already dedupes
|
||||||
|
* module evaluation; this mainly makes the "define runs once" contract explicit.
|
||||||
|
* Editing a file under src/lib/vendor invalidates the module and re-runs
|
||||||
|
* customElements.define, which throws — hard-refresh after re-vendoring.
|
||||||
|
*/
|
||||||
|
export function loadFoliate(): Promise<void> {
|
||||||
|
return (pending ??= import('$foliate/view.js').then(() => undefined));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <foliate-view>. Call only after loadFoliate() has resolved.
|
||||||
|
*
|
||||||
|
* Built imperatively rather than written in markup so SSR never emits an unknown
|
||||||
|
* element for Svelte to hydrate, and so svelte-check has no unknown attributes to
|
||||||
|
* complain about.
|
||||||
|
*/
|
||||||
|
export function createFoliateView(): View {
|
||||||
|
return document.createElement('foliate-view') as View;
|
||||||
|
}
|
||||||
+15
-1
@@ -10,7 +10,21 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"moduleResolution": "bundler"
|
"moduleResolution": "bundler"
|
||||||
}
|
},
|
||||||
|
// `exclude` replaces the generated list rather than merging with it, so the
|
||||||
|
// service-worker entries from .svelte-kit/tsconfig.json are repeated here.
|
||||||
|
// src/lib/vendor is added: it is vendored third-party JS and checkJs is on,
|
||||||
|
// so without this svelte-check reports thousands of errors from foliate-js.
|
||||||
|
"exclude": [
|
||||||
|
"node_modules/**",
|
||||||
|
"src/service-worker.js",
|
||||||
|
"src/service-worker/**/*.js",
|
||||||
|
"src/service-worker.ts",
|
||||||
|
"src/service-worker/**/*.ts",
|
||||||
|
"src/service-worker.d.ts",
|
||||||
|
"src/service-worker/**/*.d.ts",
|
||||||
|
"src/lib/vendor/**"
|
||||||
|
]
|
||||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||||
//
|
//
|
||||||
|
|||||||
+17
-1
@@ -1,7 +1,23 @@
|
|||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tailwindcss(), sveltekit()]
|
plugins: [tailwindcss(), sveltekit()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
// Deliberately a Vite-only alias, and deliberately not in kit.alias or
|
||||||
|
// tsconfig paths. TypeScript cannot resolve `$foliate/*`, so the ambient
|
||||||
|
// declaration in src/lib/reader/foliate.d.ts is the only candidate and
|
||||||
|
// svelte-check never walks the ~11k lines of untyped vendored JS.
|
||||||
|
$foliate: fileURLToPath(new URL('./src/lib/vendor/foliate-js', import.meta.url))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
// Behind a dynamic import in foliate's fixed-layout.js, so Vite would
|
||||||
|
// otherwise discover it mid-session and force a full page reload the first
|
||||||
|
// time someone opens a fixed-layout EPUB.
|
||||||
|
include: ['construct-style-sheets-polyfill']
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user