@lumeland/core 1.0.0-beta-3 → 1.0.0-beta-5

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/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/)
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## 1.0.0 - Unreleased
9
+
10
+ First version
@@ -48,8 +48,6 @@ export interface ComponentFile {
48
48
  /** Extra default data stored in the component */
49
49
  [key: string]: unknown;
50
50
  }
51
- export declare function compileCSS(filename: string, imports: Map<string, string | Entry>, entries: Map<string, Entry>): Promise<string>;
52
- export declare function compileJS(filename: string, imports: Map<string, string | Entry>, entries: Map<string, Entry>): Promise<string>;
53
51
  export interface ProxyComponents {
54
52
  (props?: Record<string, unknown>): any;
55
53
  [key: string]: ProxyComponents;
@@ -1,6 +1,3 @@
1
- import textLoader from "./loaders/text.js";
2
- import { posix } from "../deps/path.js";
3
- import { log } from "./utils/log.js";
4
1
  /**
5
2
  * Class to load components from the _components folder.
6
3
  */
@@ -109,92 +106,6 @@ function findChild(entry, filter) {
109
106
  }
110
107
  }
111
108
  }
112
- export async function compileCSS(filename, imports, entries) {
113
- const mainCode = Array.from(imports.keys())
114
- .map((path) => `@import "${path}";`)
115
- .join("\n");
116
- const { bundleAsync } = await import("../deps/lightningcss.js");
117
- const { code } = await bundleAsync({
118
- filename,
119
- sourceMap: false,
120
- resolver: {
121
- resolve(id, importer) {
122
- if (id.startsWith(".")) {
123
- id = posix.join(posix.dirname(importer), id);
124
- }
125
- return id;
126
- },
127
- async read(filePath) {
128
- if (filePath === filename) {
129
- return mainCode;
130
- }
131
- const content = await getEntryContent(imports.get(filePath) || entries.get(filePath));
132
- if (!content) {
133
- log.error(`[components] File "${filePath}" not found or is empty`);
134
- }
135
- return content;
136
- },
137
- },
138
- });
139
- const decoder = new TextDecoder();
140
- return decoder.decode(code);
141
- }
142
- export async function compileJS(filename, imports, entries) {
143
- const mainCode = Array.from(imports.keys())
144
- .map((path) => `import "${path}";`)
145
- .join("\n");
146
- const { build, stop } = await import("../deps/esbuild.js");
147
- const { outputFiles } = await build({
148
- bundle: true,
149
- entryPoints: [filename],
150
- write: false,
151
- format: "esm",
152
- minify: false,
153
- target: "esnext",
154
- outfile: filename,
155
- plugins: [
156
- {
157
- name: "components-resolver",
158
- setup(build) {
159
- build.onResolve({ filter: /.*/ }, ({ path, importer }) => {
160
- if (path.startsWith(".")) {
161
- path = posix.join(posix.dirname(importer), path);
162
- }
163
- if (path === filename || imports.has(path) || entries.has(path)) {
164
- return { path, namespace: "fs" };
165
- }
166
- return { path, external: true };
167
- });
168
- build.onLoad({ filter: /.*/, namespace: "fs" }, async ({ path }) => {
169
- const contents = path === filename
170
- ? mainCode
171
- : await getEntryContent(imports.get(path) || entries.get(path));
172
- if (!contents) {
173
- log.error(`[components] File "${path}" not found or is empty`);
174
- }
175
- return {
176
- contents,
177
- loader: path.endsWith(".ts") ? "ts" : "js",
178
- };
179
- });
180
- },
181
- },
182
- ],
183
- });
184
- await stop();
185
- const decoder = new TextDecoder();
186
- return decoder.decode(outputFiles[0].contents);
187
- }
188
- async function getEntryContent(entry) {
189
- if (typeof entry === "string") {
190
- return entry;
191
- }
192
- if (entry) {
193
- const { content } = await entry.getContent(textLoader);
194
- return content || "";
195
- }
196
- return "";
197
- }
198
109
  /**
199
110
  * Create and returns a proxy to use the components
200
111
  * as comp.name() instead of components.get("name").render()
@@ -48,11 +48,11 @@ export interface FileInfo {
48
48
  export declare function statSync(path: string): FileInfo;
49
49
  export declare class FileWatcher {
50
50
  #private;
51
- constructor(paths: string[]);
51
+ constructor(path: string);
52
52
  close(): void;
53
53
  [Symbol.asyncIterator](): AsyncGenerator<string[]>;
54
54
  }
55
- export declare function watchFiles(paths: string[]): FileWatcher;
55
+ export declare function watchFiles(path: string): FileWatcher;
56
56
  export interface FsStream {
57
57
  readable: ReadableStream<Uint8Array<ArrayBuffer>>;
58
58
  [Symbol.dispose]: () => void;
@@ -131,23 +131,25 @@ export function statSync(path) {
131
131
  }
132
132
  export class FileWatcher {
133
133
  #controller = new AbortController();
134
- #paths;
135
- constructor(paths) {
136
- this.#paths = paths;
134
+ #path;
135
+ constructor(path) {
136
+ this.#path = path;
137
137
  }
138
138
  close() {
139
139
  this.#controller.abort();
140
140
  }
141
141
  async *[Symbol.asyncIterator]() {
142
142
  const { signal } = this.#controller;
143
- const iterator = mergeIterators(this.#paths.map((p) => [p, fsPromises.watch(p, { signal, recursive: true })]));
143
+ const iterator = fsPromises.watch(this.#path, { signal, recursive: true });
144
144
  for await (const change of iterator) {
145
- yield change;
145
+ if (change.filename) {
146
+ yield [join(this.#path, change.filename)];
147
+ }
146
148
  }
147
149
  }
148
150
  }
149
- export function watchFiles(paths) {
150
- return new FileWatcher(paths.map(toPath));
151
+ export function watchFiles(path) {
152
+ return new FileWatcher(path);
151
153
  }
152
154
  export function readStream(file, start) {
153
155
  const nodeStream = fs.createReadStream(toPath(file), { start });
@@ -158,41 +160,6 @@ export function readStream(file, start) {
158
160
  },
159
161
  };
160
162
  }
161
- function mergeIterators(iterators) {
162
- let done = false;
163
- return new ReadableStream({
164
- start(controller) {
165
- let active = iterators.length;
166
- for (const [root, it] of iterators) {
167
- (async () => {
168
- try {
169
- for await (const item of it) {
170
- if (item.filename) {
171
- controller.enqueue([join(root, item.filename)]);
172
- }
173
- }
174
- }
175
- catch (error) {
176
- if (error?.name !== "AbortError" && !done) {
177
- done = true;
178
- controller.error(error);
179
- }
180
- }
181
- finally {
182
- active--;
183
- if (active === 0 && !done) {
184
- done = true;
185
- controller.close();
186
- }
187
- }
188
- })();
189
- }
190
- },
191
- cancel() {
192
- done = true;
193
- },
194
- });
195
- }
196
163
  // Ensure file: URLs are converted to paths
197
164
  function toPath(urlOrPath) {
198
165
  return urlOrPath.startsWith("file:") ? fileURLToPath(urlOrPath) : urlOrPath;
@@ -1 +1,2 @@
1
1
  import "temporal-polyfill/global";
2
+ export declare function toBase64(input: ArrayBuffer | Uint8Array): string;
@@ -1 +1,5 @@
1
1
  import "temporal-polyfill/global";
2
+ // Uint8Array.prototype.toBase64() not available in Node 24
3
+ export function toBase64(input) {
4
+ return Buffer.from(input).toString("base64");
5
+ }
@@ -11,3 +11,8 @@ export declare function hmr(): void;
11
11
  export declare function updateDependencies(url: string): Generator<string>;
12
12
  /** Increment the version number */
13
13
  export declare function updateVersion(): void;
14
+ /** Files that reload the process when they changed (not HMR available) */
15
+ export declare function addReloadFile(path: string): void;
16
+ /** Reload the current process */
17
+ export declare function reloadProcess(): void;
18
+ export declare function startProcess(mainEntry: string, importer: string): void;
@@ -1,7 +1,10 @@
1
1
  import { registerHooks } from "node:module";
2
2
  import { pathToFileURL } from "node:url";
3
+ import process from "node:process";
3
4
  import { cwd } from "./process.js";
5
+ import { fork } from "node:child_process";
4
6
  let version = 0;
7
+ const reloadFiles = new Set();
5
8
  const root = pathToFileURL(cwd()).href;
6
9
  const files = new Map();
7
10
  const dependencies = new Map();
@@ -51,6 +54,10 @@ export function hmr() {
51
54
  * This updates all modules that import the module that just changed.
52
55
  */
53
56
  export function* updateDependencies(url) {
57
+ if (reloadFiles.has(url)) {
58
+ reloadProcess();
59
+ return;
60
+ }
54
61
  files.delete(url);
55
62
  for (const dep of dependencies.get(url) ?? []) {
56
63
  yield dep;
@@ -61,3 +68,32 @@ export function* updateDependencies(url) {
61
68
  export function updateVersion() {
62
69
  ++version;
63
70
  }
71
+ /** Files that reload the process when they changed (not HMR available) */
72
+ export function addReloadFile(path) {
73
+ reloadFiles.add(path);
74
+ }
75
+ /** Reload the current process */
76
+ export function reloadProcess() {
77
+ if (process.send) {
78
+ process.send({ type: "reload" });
79
+ }
80
+ }
81
+ let child;
82
+ export function startProcess(mainEntry, importer) {
83
+ child = fork(mainEntry, process.argv.slice(2), {
84
+ stdio: ["inherit", "inherit", "inherit", "ipc"],
85
+ execArgv: [...process.execArgv, "--import", importer],
86
+ });
87
+ let relaunch = false;
88
+ child.on("message", (msg) => {
89
+ if (msg.type === "reload") {
90
+ relaunch = true;
91
+ child?.kill("SIGTERM");
92
+ }
93
+ });
94
+ child.on("exit", (code, signal) => {
95
+ if (relaunch)
96
+ return startProcess(mainEntry, importer);
97
+ process.exit(code ?? (signal ? 1 : 0));
98
+ });
99
+ }
@@ -4,6 +4,8 @@ export interface NetworkInterfaceInfo {
4
4
  }
5
5
  /** Returns an array with the network interfaces information */
6
6
  export declare function networkInterfaces(): NetworkInterfaceInfo[];
7
+ /** Check if a port is free */
8
+ export declare function checkPort(port: number): Promise<boolean>;
7
9
  export interface NetAddress {
8
10
  transport: "tcp" | "udp";
9
11
  hostname: string;
@@ -15,6 +15,24 @@ export function networkInterfaces() {
15
15
  }
16
16
  return result;
17
17
  }
18
+ /** Check if a port is free */
19
+ export function checkPort(port) {
20
+ return new Promise((resolve, reject) => {
21
+ const server = createServer(() => { });
22
+ server.once("error", (error) => {
23
+ if (error.code === "EADDRINUSE") {
24
+ resolve(false);
25
+ }
26
+ else {
27
+ reject(error);
28
+ }
29
+ });
30
+ server.once("listening", () => {
31
+ server.close(() => resolve(true));
32
+ });
33
+ server.listen(port, "0.0.0.0");
34
+ });
35
+ }
18
36
  /** Start a new HTTP server */
19
37
  export function serve(options) {
20
38
  const { handler, hostname = "0.0.0.0", port = 8000, signal, onListen, onUpgradeWebSocket, } = options;
@@ -54,7 +54,7 @@ declare const defaults: {
54
54
  watcher: {
55
55
  ignore: never[];
56
56
  debounce: number;
57
- include: never[];
57
+ reload: never[];
58
58
  dependencies: {};
59
59
  };
60
60
  components: {};
@@ -303,8 +303,8 @@ export interface WatcherOptions {
303
303
  ignore?: (string | ((path: string) => boolean))[];
304
304
  /** The interval in milliseconds to check for changes */
305
305
  debounce?: number;
306
- /** Extra files and folders to watch (ouside the src folder) */
307
- include?: string[];
306
+ /** Extra files that cause the process to reload */
307
+ reload?: string[];
308
308
  /** Manual dependencies not detected by the watcher */
309
309
  dependencies?: Record<string, string[]>;
310
310
  }
package/build/src/site.js CHANGED
@@ -8,7 +8,7 @@ import { insertContent } from "./utils/page_content.js";
8
8
  import { getFile, getFiles, isFromCdn } from "./utils/cdn.js";
9
9
  import { setCache } from "./utils/read.js";
10
10
  import FS from "./fs.js";
11
- import { compileCSS, compileJS, ComponentLoader } from "./components.js";
11
+ import { ComponentLoader } from "./components.js";
12
12
  import DataLoader from "./data_loader.js";
13
13
  import Source from "./source.js";
14
14
  import Scopes from "./scopes.js";
@@ -28,6 +28,8 @@ import Server from "./server.js";
28
28
  import Cache from "./cache.js";
29
29
  import DebugBar from "./debugbar.js";
30
30
  import { cwd } from "./services/process.js";
31
+ import { compileComponentsCSS } from "@lumeland/plugin-lightningcss/comp.ts";
32
+ import { compileComponentsJS } from "@lumeland/plugin-esbuild/comp.ts";
31
33
  /** Default options of the site */
32
34
  const defaults = {
33
35
  cwd: cwd(),
@@ -51,7 +53,7 @@ const defaults = {
51
53
  watcher: {
52
54
  ignore: [],
53
55
  debounce: 100,
54
- include: [],
56
+ reload: [],
55
57
  dependencies: {},
56
58
  },
57
59
  components: {},
@@ -424,8 +426,8 @@ export default class Site {
424
426
  }
425
427
  return;
426
428
  }
427
- // Remote files from NPM or GitHub CDN
428
- if (isFromCdn(from)) {
429
+ // Remote files CDN or filesystem
430
+ if (isFromCdn(from) || from.startsWith("file:")) {
429
431
  // It's a pattern
430
432
  if (from.includes("*")) {
431
433
  const pos = from.indexOf("*");
@@ -672,7 +674,7 @@ export default class Site {
672
674
  if (path.endsWith(".css")) {
673
675
  this.debugBar?.startMeasure("components-css");
674
676
  const page = await this.getOrCreatePage(path);
675
- page.text = insertContent(page.text, await compileCSS(path, entries, this.fs.entries), this.options.components.placeholder);
677
+ page.text = insertContent(page.text, await compileComponentsCSS(path, entries, this.fs.entries), this.options.components.placeholder);
676
678
  this.debugBar?.endMeasure("components-css", `[Components] Compiled CSS code into <code>${path}</code>`);
677
679
  continue;
678
680
  }
@@ -681,7 +683,7 @@ export default class Site {
681
683
  // https://github.com/lumeland/lume/issues/659
682
684
  const existingFile = this.search.file(path.replace(/.m?js$/, "{.mjs,.js,.ts}"));
683
685
  const page = await this.getOrCreatePage(existingFile || path);
684
- page.text = insertContent(page.text, await compileJS(path, entries, this.fs.entries), this.options.components.placeholder);
686
+ page.text = insertContent(page.text, await compileComponentsJS(path, entries, this.fs.entries), this.options.components.placeholder);
685
687
  this.debugBar?.endMeasure("components-js", `[Components] Compiled JS code into <code>${path}</code>`);
686
688
  }
687
689
  }
@@ -850,9 +852,7 @@ export default class Site {
850
852
  return this.watcher;
851
853
  }
852
854
  this.watcher = new FSWatcher({
853
- src: this.src(),
854
855
  root: this.root(),
855
- paths: this.options.watcher.include,
856
856
  ignore: this.options.watcher.ignore,
857
857
  debounce: this.options.watcher.debounce,
858
858
  dependencies: this.options.watcher.dependencies,
@@ -58,16 +58,15 @@ export function getFile(specifier) {
58
58
  if (specifier.startsWith("npm:")) {
59
59
  return specifier.replace("npm:", "https://cdn.jsdelivr.net/npm/");
60
60
  }
61
- else if (specifier.startsWith("gh:")) {
61
+ if (specifier.startsWith("gh:")) {
62
62
  return specifier.replace("gh:", "https://cdn.jsdelivr.net/gh/");
63
63
  }
64
- else if (specifier.startsWith("https://cdn.jsdelivr.net/npm/") ||
64
+ if (specifier.startsWith("file://") ||
65
+ specifier.startsWith("https://cdn.jsdelivr.net/npm/") ||
65
66
  specifier.startsWith("https://cdn.jsdelivr.net/gh/")) {
66
67
  return specifier;
67
68
  }
68
- else {
69
- throw new Error(`Invalid specifier: ${specifier}`);
70
- }
69
+ throw new Error(`Invalid specifier: ${specifier}`);
71
70
  }
72
71
  export async function getVersion(type, name, version) {
73
72
  // Check if the version is a hash (commit SHA)
@@ -4,7 +4,7 @@ export declare function setCache(instance: Cache): void;
4
4
  * Read a local or remote file and return its content.
5
5
  * If the file is remote, it will be cached in the `lume_remote_files` cache.
6
6
  */
7
- export declare function read(path: string, isBinary: false, init?: RequestInit): Promise<string>;
7
+ export declare function read(path: string, isBinary?: false, init?: RequestInit): Promise<string>;
8
8
  export declare function read(path: string, isBinary: true, init?: RequestInit): Promise<Uint8Array<ArrayBuffer>>;
9
9
  /** Read a text file like a browser */
10
10
  export declare function readFile(path: string): Promise<string>;
@@ -6,7 +6,7 @@ let cache;
6
6
  export function setCache(instance) {
7
7
  cache = instance;
8
8
  }
9
- export async function read(path, isBinary, init) {
9
+ export async function read(path, isBinary = false, init) {
10
10
  if (!isUrl(path)) {
11
11
  if (path.startsWith("data:")) {
12
12
  const response = await fetch(path);
@@ -5,10 +5,6 @@ import type { Event, EventListener, EventOptions } from "./events.ts";
5
5
  export interface Options {
6
6
  /** The root folder */
7
7
  root: string;
8
- /** The src folder to watch */
9
- src: string;
10
- /** Extra files to watch */
11
- paths?: string[];
12
8
  /** Paths ignored by the watcher */
13
9
  ignore?: (string | ((path: string) => boolean))[];
14
10
  /** The debounce waiting time */
@@ -21,8 +21,8 @@ export default class FSWatcher {
21
21
  }
22
22
  /** Start the file watcher */
23
23
  async start() {
24
- const { root, src, paths, ignore, debounce, dependencies } = this.options;
25
- const watcher = watchFiles([src, ...(paths ?? [])]);
24
+ const { root, ignore, debounce, dependencies } = this.options;
25
+ const watcher = watchFiles(root);
26
26
  const changes = new Set();
27
27
  let timer = undefined;
28
28
  let runningCallback = false;
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@lumeland/core",
3
- "version": "1.0.0-beta-3",
3
+ "version": "1.0.0-beta-5",
4
4
  "description": "The core of Lume SSG",
5
5
  "license": "MIT",
6
+ "author": "Óscar Otero",
6
7
  "files": [
7
- "build"
8
+ "build",
9
+ "CHANGELOG.md"
8
10
  ],
9
11
  "type": "module",
10
12
  "exports": {
@@ -19,15 +21,11 @@
19
21
  },
20
22
  "dependencies": {
21
23
  "@lumeland/dom": "^0.1.56",
22
- "@resvg/resvg-js": "^2.6.2",
23
- "@types/ws": "^8.18.2",
24
+ "@lumeland/plugin-esbuild": "^1.0.0-beta-3",
25
+ "@lumeland/plugin-lightningcss": "^1.0.0-beta-3",
24
26
  "deno-std": "1.5.1",
25
- "esbuild": "0.28.2",
26
- "ico-endec": "^0.1.6",
27
- "lightningcss": "1.33.0",
28
27
  "remove-markdown": "0.8.0",
29
- "sharp": "^0.35.5",
30
- "temporal-polyfill": "^1.0.5",
28
+ "temporal-polyfill": "^1.0.6",
31
29
  "ws": "^8.22.0"
32
30
  }
33
31
  }
@@ -1 +0,0 @@
1
- export * from "esbuild";
@@ -1 +0,0 @@
1
- export * from "esbuild";
@@ -1 +0,0 @@
1
- export * from "lightningcss";
@@ -1 +0,0 @@
1
- export * from "lightningcss";
@@ -1,3 +0,0 @@
1
- import { type ResvgRenderOptions } from "@resvg/resvg-js";
2
- export type { ResvgRenderOptions };
3
- export declare function toPng(svg: string, options?: ResvgRenderOptions): Uint8Array;
@@ -1,6 +0,0 @@
1
- import { Resvg } from "@resvg/resvg-js";
2
- export function toPng(svg, options = { fitTo: { mode: "original" } }) {
3
- const resvg = new Resvg(svg, options);
4
- const pngData = resvg.render();
5
- return pngData.asPng();
6
- }
@@ -1,5 +0,0 @@
1
- export type { FormatEnum, ResizeOptions, Sharp } from "sharp";
2
- import type { Sharp, SharpOptions } from "sharp";
3
- import { type ResvgRenderOptions } from "./resvg.ts";
4
- export declare function sharpsToIco(...images: Sharp[]): Promise<any>;
5
- export declare function create(content: Uint8Array | string, config?: SharpOptions, svgOptions?: ResvgRenderOptions): Sharp;
@@ -1,16 +0,0 @@
1
- import sharp from "sharp";
2
- // @ts-expect-error: no types for ico-endec
3
- import icoEndec from "ico-endec";
4
- import { toPng } from "./resvg.js";
5
- export async function sharpsToIco(...images) {
6
- const buffers = await Promise.all(images.map((image) => image.toFormat("png").toBuffer()));
7
- // deno-lint-ignore no-explicit-any
8
- return icoEndec.encode(buffers.map((buffer) => buffer.buffer));
9
- }
10
- export function create(content, config = {}, svgOptions) {
11
- // It's a SVG
12
- if (typeof content === "string") {
13
- return sharp(toPng(content, svgOptions));
14
- }
15
- return sharp(content, config);
16
- }
@@ -1,3 +0,0 @@
1
- import type { FormatEnum } from "../../deps/sharp.ts";
2
- import type Cache from "../cache.ts";
3
- export declare function buildIcon(content: Uint8Array | string, format: keyof FormatEnum | "ico", size: number[], cache?: Cache): Promise<Uint8Array<ArrayBuffer>>;
@@ -1,28 +0,0 @@
1
- import { create, sharpsToIco } from "../../deps/sharp.js";
2
- export async function buildIcon(content, format, size, cache) {
3
- if (cache) {
4
- const result = cache.getBytes([content, format, size]);
5
- if (result) {
6
- return result;
7
- }
8
- }
9
- const svgOptions = {
10
- fitTo: { mode: "width", value: Math.max(...size) },
11
- };
12
- let image;
13
- if (format === "ico") {
14
- const resizeOptions = { background: { r: 0, g: 0, b: 0, alpha: 0 } };
15
- const img = create(content, undefined, svgOptions);
16
- image = await sharpsToIco(...size.map((size) => img.clone().resize(size, size, resizeOptions)));
17
- }
18
- else {
19
- image = (await create(content, undefined, svgOptions)
20
- .resize(size[0], size[0])
21
- .toFormat(format)
22
- .toBuffer());
23
- }
24
- if (cache) {
25
- cache.set([content, format, size], image);
26
- }
27
- return image;
28
- }