@epubook/core 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,550 @@
1
- export { B as BundleError, E as Epub, d as EsbuildOptions, F as Fragment, H as Html, c as Image, I as Item, a as ManifestItem, b as ManifestItemRef, P as PackageDocument, R as Rollup, S as Style, X as XHTMLBuilder, h } from './shared/core.c365bf12.mjs';
2
- import 'fast-xml-parser';
3
- import 'pathe';
4
- import 'node:fs';
5
- import 'node:crypto';
6
- import 'defu';
7
- import 'fflate';
1
+ import * as path from 'node:path';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { createDefu } from 'defu';
4
+ import * as path$1 from 'pathe';
5
+ import { strToU8 } from 'fflate';
6
+ import { XMLBuilder } from 'fast-xml-parser';
7
+ import { promises, existsSync, mkdirSync } from 'node:fs';
8
+
9
+ const Fragment = "Fragment";
10
+ function h(tag, attrs = {}, ...children) {
11
+ const sub = children.flatMap(
12
+ (c) => typeof c === "object" && !Array.isArray(c) && c.tag === Fragment ? c.children ?? [] : c
13
+ ).filter((c) => c !== void 0 && c !== null && c !== false);
14
+ const o = {
15
+ tag,
16
+ attrs: attrs ?? {},
17
+ children: sub
18
+ };
19
+ return o;
20
+ }
21
+
22
+ const MIMETYPE = "application/epub+zip";
23
+ const ImageGif = "image/gif";
24
+ const ImageJpeg = "image/jpeg";
25
+ const ImagePng = "image/png";
26
+ const ImageSvg = "image/svg+xml";
27
+ const ImageWebp = "image/webp";
28
+ const TextCSS = "text/css";
29
+ const TextXHTML = "application/xhtml+xml";
30
+ function getImageMediaType(file) {
31
+ const ext = path.extname(file);
32
+ switch (ext) {
33
+ case ".gif":
34
+ return ImageGif;
35
+ case ".jpg":
36
+ case ".jpeg":
37
+ return ImageJpeg;
38
+ case ".png":
39
+ return ImagePng;
40
+ case ".svg":
41
+ return ImageSvg;
42
+ case ".webp":
43
+ return ImageWebp;
44
+ default:
45
+ return void 0;
46
+ }
47
+ }
48
+
49
+ class ManifestItem {
50
+ constructor(href, id) {
51
+ this.optional = {};
52
+ this._href = href;
53
+ this._id = id;
54
+ }
55
+ update(info) {
56
+ for (const [key, value] of Object.entries(info)) {
57
+ if (!!value) {
58
+ this.optional[key] = value;
59
+ }
60
+ }
61
+ return this;
62
+ }
63
+ href() {
64
+ return this._href;
65
+ }
66
+ id() {
67
+ return this._id;
68
+ }
69
+ fallback() {
70
+ return this.optional.fallback;
71
+ }
72
+ mediaOverlay() {
73
+ return this.optional.mediaOverlay;
74
+ }
75
+ mediaType() {
76
+ return this.optional.mediaType;
77
+ }
78
+ properties() {
79
+ return this.optional.properties;
80
+ }
81
+ ref() {
82
+ return new ManifestItemRef(this._id);
83
+ }
84
+ }
85
+ class ManifestItemRef {
86
+ constructor(idref) {
87
+ this.optional = {};
88
+ this._idref = idref;
89
+ }
90
+ update(info) {
91
+ for (const [key, value] of Object.entries(info)) {
92
+ if (!!value) {
93
+ this.optional[key] = value;
94
+ }
95
+ }
96
+ return this;
97
+ }
98
+ idref() {
99
+ return this._idref;
100
+ }
101
+ id() {
102
+ return this.optional.id;
103
+ }
104
+ linear() {
105
+ return this.optional.linear;
106
+ }
107
+ properties() {
108
+ return this.optional.properties;
109
+ }
110
+ }
111
+
112
+ class Item {
113
+ constructor(file, mediaType) {
114
+ this.file = file;
115
+ this.mediaType = mediaType;
116
+ }
117
+ filename() {
118
+ return this.file;
119
+ }
120
+ relative(from) {
121
+ return path$1.relative(path$1.dirname(from), this.file);
122
+ }
123
+ update(info) {
124
+ if (info.properties) {
125
+ this._properties = info.properties;
126
+ }
127
+ return this;
128
+ }
129
+ id() {
130
+ return this.file.replace(/\/|\\/g, "_").replace(/\.[\w]+$/, "");
131
+ }
132
+ manifest() {
133
+ return new ManifestItem(this.file, this.id()).update({
134
+ mediaType: this.mediaType,
135
+ properties: this._properties
136
+ });
137
+ }
138
+ itemref() {
139
+ return this.manifest().ref();
140
+ }
141
+ }
142
+ class Style extends Item {
143
+ constructor(file, content) {
144
+ super(file, TextCSS);
145
+ this.content = content;
146
+ }
147
+ static async read(src, dst) {
148
+ if (!src.endsWith(".css")) {
149
+ return void 0;
150
+ }
151
+ const content = await promises.readFile(src, "utf-8");
152
+ return new Style(dst, content);
153
+ }
154
+ async bundle() {
155
+ return strToU8(this.content);
156
+ }
157
+ }
158
+ class Image extends Item {
159
+ constructor(file, type, data) {
160
+ super(file, type);
161
+ this.data = data;
162
+ }
163
+ static async read(file, src) {
164
+ const content = await promises.readFile(src);
165
+ const media = getImageMediaType(src);
166
+ if (media) {
167
+ return new Image(file, media, content);
168
+ } else {
169
+ return void 0;
170
+ }
171
+ }
172
+ async bundle() {
173
+ return this.data;
174
+ }
175
+ }
176
+ class HTML extends Item {
177
+ constructor(file, content) {
178
+ super(file, TextXHTML);
179
+ this.content = content;
180
+ }
181
+ static async read(src, dst) {
182
+ if (!src.endsWith(".xhtml")) {
183
+ return void 0;
184
+ }
185
+ const content = await promises.readFile(src, "utf-8");
186
+ return new HTML(dst, content);
187
+ }
188
+ async bundle() {
189
+ return strToU8(this.content);
190
+ }
191
+ }
192
+
193
+ const builder = new XMLBuilder({
194
+ format: true,
195
+ ignoreAttributes: false,
196
+ suppressUnpairedNode: false,
197
+ unpairedTags: ["link"]
198
+ });
199
+ class XHTML extends Item {
200
+ constructor(file, meta, content) {
201
+ super(file, TextXHTML);
202
+ this._meta = meta;
203
+ this._content = content;
204
+ }
205
+ meta() {
206
+ return this._meta;
207
+ }
208
+ title() {
209
+ return this._meta.title;
210
+ }
211
+ language() {
212
+ return this._meta.language;
213
+ }
214
+ content() {
215
+ return this._content;
216
+ }
217
+ async bundle() {
218
+ return strToU8(this._content);
219
+ }
220
+ }
221
+ class XHTMLBuilder {
222
+ constructor(filename) {
223
+ this._meta = {
224
+ language: "en",
225
+ title: ""
226
+ };
227
+ this._head = [];
228
+ this._body = [];
229
+ this._filename = filename;
230
+ this._meta.title = path$1.basename(filename);
231
+ }
232
+ language(value) {
233
+ this._meta.language = value;
234
+ return this;
235
+ }
236
+ title(value) {
237
+ this._meta.title = value;
238
+ return this;
239
+ }
240
+ style(...list) {
241
+ for (const href of list) {
242
+ if (typeof href === "string") {
243
+ this._head.push({
244
+ tag: "link",
245
+ attrs: {
246
+ href,
247
+ rel: "stylesheet",
248
+ type: TextCSS
249
+ },
250
+ children: [""]
251
+ });
252
+ } else {
253
+ this._head.push({
254
+ tag: "link",
255
+ attrs: {
256
+ href: href.relative(this._filename),
257
+ rel: "stylesheet",
258
+ type: TextCSS
259
+ },
260
+ children: [""]
261
+ });
262
+ }
263
+ }
264
+ return this;
265
+ }
266
+ head(...node) {
267
+ this._head.push(...node);
268
+ return this;
269
+ }
270
+ body(...node) {
271
+ this._body.push(...node);
272
+ return this;
273
+ }
274
+ build() {
275
+ const content = builder.build({
276
+ html: {
277
+ "@_xmlns": "http://www.w3.org/1999/xhtml",
278
+ "@_xmlns:epub": "http://www.idpf.org/2007/ops",
279
+ "@_lang": this._meta.language,
280
+ "@_xml:lang": this._meta.language,
281
+ head: {
282
+ title: this._meta.title,
283
+ ...list(this._head)
284
+ },
285
+ body: list(this._body)
286
+ }
287
+ });
288
+ return new XHTML(this._filename, this._meta, content);
289
+ function build(node) {
290
+ const attrs = Object.fromEntries(
291
+ Object.entries(node.attrs ?? {}).map(([key, value]) => ["@_" + key, value])
292
+ );
293
+ const obj = {
294
+ ...attrs
295
+ };
296
+ if (Array.isArray(node.children)) {
297
+ const text = node.children.filter((c) => typeof c === "string");
298
+ const nodes = node.children.filter((c) => typeof c !== "string");
299
+ if (text.length > 0) {
300
+ obj["#text"] = text[0];
301
+ }
302
+ Object.assign(obj, list(nodes));
303
+ }
304
+ return obj;
305
+ }
306
+ function list(list2) {
307
+ const obj = {};
308
+ const nodes = list2.flatMap((n) => n.tag === Fragment ? n.children ?? [] : [n]);
309
+ for (const c of nodes) {
310
+ if (typeof c === "string") {
311
+ if (obj["#text"]) {
312
+ obj["#text"] += c;
313
+ } else {
314
+ obj["#text"] = c;
315
+ }
316
+ } else if (c.tag in obj) {
317
+ obj[c.tag].push(build(c));
318
+ } else {
319
+ obj[c.tag] = [build(c)];
320
+ }
321
+ }
322
+ return obj;
323
+ }
324
+ }
325
+ }
326
+
327
+ class Toc extends XHTML {
328
+ constructor(file, meta, content) {
329
+ super(file, meta, content);
330
+ this.update({ properties: "nav" });
331
+ }
332
+ static from(xhtml) {
333
+ return new Toc(xhtml.filename(), xhtml.meta(), xhtml.content());
334
+ }
335
+ static generate(file, nav, { title = "Nav", heading = 1, titleAttrs = {}, builder } = {}) {
336
+ if (!builder) {
337
+ builder = new XHTMLBuilder(file);
338
+ }
339
+ const root = {
340
+ tag: "nav",
341
+ attrs: {
342
+ "epub:type": "toc"
343
+ },
344
+ children: []
345
+ };
346
+ root.children.push(h("h" + heading, titleAttrs, title));
347
+ root.children.push(/* @__PURE__ */ h("ol", null, list(nav)));
348
+ return builder.body(root);
349
+ function list(items) {
350
+ return items.map(
351
+ (i) => "page" in i ? /* @__PURE__ */ h("li", { ...i.attrs }, /* @__PURE__ */ h("a", { href: i.page.filename() }, i.title)) : "list" in i ? /* @__PURE__ */ h("li", { ...i.attrs }, /* @__PURE__ */ h("span", null, i.title), /* @__PURE__ */ h("ol", null, list(i.list))) : false
352
+ );
353
+ }
354
+ }
355
+ }
356
+
357
+ const defu = createDefu((obj, key, value) => {
358
+ if (obj[key] instanceof Date && value instanceof Date) {
359
+ obj[key] = value;
360
+ return true;
361
+ }
362
+ });
363
+ class PackageDocument {
364
+ constructor(file) {
365
+ this.SpecVersion = "3.0";
366
+ this._uniqueIdentifier = "uuid";
367
+ this._identifier = randomUUID();
368
+ this._metadata = {
369
+ title: "",
370
+ language: "zh-CN",
371
+ contributor: [],
372
+ coverage: "",
373
+ creator: {
374
+ name: "unknown",
375
+ uid: "creator"
376
+ },
377
+ date: /* @__PURE__ */ new Date(),
378
+ description: "",
379
+ format: "",
380
+ publisher: "",
381
+ relation: "",
382
+ rights: "",
383
+ source: "",
384
+ subject: "",
385
+ type: "",
386
+ lastModified: /* @__PURE__ */ new Date()
387
+ };
388
+ this._items = [];
389
+ this._spine = [];
390
+ this.file = file;
391
+ }
392
+ filename() {
393
+ return this.file;
394
+ }
395
+ version() {
396
+ return this.SpecVersion;
397
+ }
398
+ // --- metadata ---
399
+ update(info) {
400
+ this._metadata = defu(info, this._metadata);
401
+ return this;
402
+ }
403
+ title() {
404
+ return this._metadata.title;
405
+ }
406
+ language() {
407
+ return this._metadata.language;
408
+ }
409
+ creator() {
410
+ return this._metadata.creator;
411
+ }
412
+ metadata() {
413
+ return this._metadata;
414
+ }
415
+ // --- manifest ---
416
+ addItem(item) {
417
+ this._items.push(item);
418
+ return this;
419
+ }
420
+ items() {
421
+ const l = [...this._items];
422
+ if (this._toc) {
423
+ l.push(this._toc);
424
+ }
425
+ return l;
426
+ }
427
+ manifest() {
428
+ return this.items().map((i) => i.manifest());
429
+ }
430
+ // --- navigation ---
431
+ spine() {
432
+ return this._spine;
433
+ }
434
+ setSpine(items) {
435
+ this._spine.splice(0, this._spine.length, ...items.map((i) => i.itemref()));
436
+ return this;
437
+ }
438
+ toc() {
439
+ return this._toc;
440
+ }
441
+ setToc(nav, option = {}) {
442
+ const toc = Toc.generate("nav.xhtml", nav, option);
443
+ if (!option.builder) {
444
+ toc.title(option.title ?? "Nav").language(this._metadata.language);
445
+ }
446
+ this._toc = Toc.from(toc.build());
447
+ return this;
448
+ }
449
+ // --- identifier ---
450
+ uniqueIdentifier() {
451
+ return this._uniqueIdentifier;
452
+ }
453
+ identifier() {
454
+ return this._identifier;
455
+ }
456
+ setIdentifier(identifier, uniqueIdentifier = "uuid") {
457
+ this._identifier = identifier;
458
+ this._uniqueIdentifier = uniqueIdentifier;
459
+ }
460
+ }
461
+
462
+ class Epub {
463
+ constructor(meta = {}) {
464
+ /**
465
+ * See: https://www.w3.org/TR/epub-33/#sec-package-doc
466
+ *
467
+ * Now, it only supports single opf (OEBPS/content.opf)
468
+ *
469
+ * @returns list of package documents
470
+ */
471
+ this.opfs = [new PackageDocument("OEBPS/content.opf")];
472
+ this.opfs[0].update(meta);
473
+ }
474
+ packages() {
475
+ return this.opfs;
476
+ }
477
+ main() {
478
+ return this.opfs[0];
479
+ }
480
+ item(...items) {
481
+ for (const item of items) {
482
+ this.opfs[0].addItem(item);
483
+ }
484
+ return this;
485
+ }
486
+ toc(nav, option = {}) {
487
+ this.opfs[0].setToc(nav, option);
488
+ return this;
489
+ }
490
+ spine(...items) {
491
+ this.opfs[0].setSpine(items);
492
+ return this;
493
+ }
494
+ async bundle() {
495
+ const { bundle } = await import('./chunks/index.mjs');
496
+ return await bundle(this);
497
+ }
498
+ async writeFile(file) {
499
+ const buffer = await this.bundle();
500
+ const dir = path$1.dirname(file);
501
+ if (!existsSync(dir)) {
502
+ mkdirSync(dir, { recursive: true });
503
+ }
504
+ await promises.writeFile(file, buffer);
505
+ }
506
+ }
507
+
508
+ class BundleError extends Error {
509
+ constructor(msg) {
510
+ super(msg);
511
+ }
512
+ }
513
+
514
+ const UnbuildPreset = ({
515
+ inject = true
516
+ } = {}) => ({
517
+ rollup: {
518
+ esbuild: {
519
+ jsxFactory: inject ? "__epubook_core.h" : "h",
520
+ jsxFragment: inject ? "__epubook_core.Fragment" : "Fragment",
521
+ loaders: {
522
+ ".js": "js",
523
+ ".ts": "ts",
524
+ ".jsx": "jsx",
525
+ ".tsx": "tsx"
526
+ }
527
+ }
528
+ },
529
+ hooks: {
530
+ "rollup:options"(_options, config) {
531
+ const plugins = config.plugins;
532
+ if (inject && Array.isArray(plugins)) {
533
+ plugins.push(Rollup());
534
+ }
535
+ }
536
+ }
537
+ });
538
+ function Rollup() {
539
+ return {
540
+ name: "epubook:inject-tsx",
541
+ transform(code, id) {
542
+ if (id.endsWith(".tsx")) {
543
+ return `import * as __epubook_core from '@epubook/core';
544
+ ` + code;
545
+ }
546
+ }
547
+ };
548
+ }
549
+
550
+ export { BundleError, Epub, Fragment, HTML, Image, ImageGif, ImageJpeg, ImagePng, ImageSvg, ImageWebp, Item, MIMETYPE, ManifestItem, ManifestItemRef, PackageDocument, Rollup, Style, TextCSS, TextXHTML, Toc, UnbuildPreset, XHTML, XHTMLBuilder, getImageMediaType, h };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epubook/core",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "ebook",
@@ -39,15 +39,15 @@
39
39
  "pathe": "^1.1.0"
40
40
  },
41
41
  "devDependencies": {
42
- "rollup": "^3.17.3",
42
+ "rollup": "^3.18.0",
43
43
  "vitest": "^0.29.2"
44
44
  },
45
45
  "engines": {
46
- "node": ">=v16.19.0"
46
+ "node": ">=v18.14.2"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "unbuild",
50
- "format": "prettier --write src/**/*.ts test/**/*.ts",
50
+ "format": "prettier --write src/**/*.{ts,tsx} test/**/*.{ts,tsx}",
51
51
  "test": "vitest",
52
52
  "test:ci": "vitest --run",
53
53
  "typecheck": "tsc --noEmit"