@lolyjs/core 0.2.0-alpha.22 → 0.2.0-alpha.24
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/README.md +31 -21
- package/dist/cli.cjs +58 -26
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +58 -26
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +58 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +58 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -408,13 +408,21 @@ Loly serves static files from the `public/` directory at the root of your applic
|
|
|
408
408
|
public/
|
|
409
409
|
├── sitemap.xml # Available at /sitemap.xml
|
|
410
410
|
├── robots.txt # Available at /robots.txt
|
|
411
|
-
├── favicon.ico # Available at /favicon.ico
|
|
411
|
+
├── favicon.ico # Available at /favicon.ico (or favicon.png)
|
|
412
|
+
├── favicon.png # Available at /favicon.png (alternative to .ico)
|
|
412
413
|
└── assets/
|
|
413
414
|
├── logo.png # Available at /assets/logo.png
|
|
414
415
|
└── images/ # Available at /assets/images/*
|
|
415
416
|
└── hero.jpg
|
|
416
417
|
```
|
|
417
418
|
|
|
419
|
+
**Favicon:**
|
|
420
|
+
Place your favicon in the `public/` directory as either `favicon.ico` or `favicon.png`. The framework automatically detects and includes it in the HTML head with the correct MIME type:
|
|
421
|
+
- `public/favicon.ico` → `/favicon.ico` (type: `image/x-icon`)
|
|
422
|
+
- `public/favicon.png` → `/favicon.png` (type: `image/png`)
|
|
423
|
+
|
|
424
|
+
If both exist, `favicon.ico` takes priority (checked first).
|
|
425
|
+
|
|
418
426
|
**SEO Example:**
|
|
419
427
|
|
|
420
428
|
Create `public/sitemap.xml`:
|
|
@@ -440,14 +448,22 @@ Sitemap: https://example.com/sitemap.xml
|
|
|
440
448
|
|
|
441
449
|
Both files will be automatically available at `/sitemap.xml` and `/robots.txt` respectively, and search engines will find them at the standard locations.
|
|
442
450
|
|
|
451
|
+
**Important Notes:**
|
|
452
|
+
- **All static files** (including favicons) must be placed in the `public/` directory
|
|
453
|
+
- The framework **only** looks for favicons in `public/` (not in the root or `app/` directory)
|
|
454
|
+
- Favicons are automatically detected and included in the HTML `<head>` with the correct MIME type
|
|
455
|
+
- Static files have **priority over dynamic routes** - perfect for SEO files
|
|
456
|
+
|
|
443
457
|
**Configuration:**
|
|
444
458
|
The static directory can be customized in `loly.config.ts`:
|
|
445
459
|
```tsx
|
|
460
|
+
import type { FrameworkConfig } from "@lolyjs/core";
|
|
461
|
+
|
|
446
462
|
export default {
|
|
447
463
|
directories: {
|
|
448
464
|
static: "public", // Default: "public"
|
|
449
465
|
},
|
|
450
|
-
} satisfies FrameworkConfig
|
|
466
|
+
} satisfies Partial<FrameworkConfig>;
|
|
451
467
|
```
|
|
452
468
|
|
|
453
469
|
### 🔌 API Routes
|
|
@@ -913,30 +929,24 @@ export default function Navigation() {
|
|
|
913
929
|
Create `loly.config.ts` in your project root to configure the framework:
|
|
914
930
|
|
|
915
931
|
```tsx
|
|
916
|
-
import { FrameworkConfig } from "@lolyjs/core";
|
|
932
|
+
import type { FrameworkConfig } from "@lolyjs/core";
|
|
917
933
|
|
|
934
|
+
// Option 1: Partial config (only specify what you want to change)
|
|
918
935
|
export default {
|
|
919
936
|
directories: {
|
|
920
|
-
app: "app",
|
|
921
|
-
build: ".loly",
|
|
922
937
|
static: "public",
|
|
923
938
|
},
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
streaming: true,
|
|
936
|
-
ssr: true,
|
|
937
|
-
ssg: true,
|
|
938
|
-
},
|
|
939
|
-
} satisfies FrameworkConfig;
|
|
939
|
+
} satisfies Partial<FrameworkConfig>;
|
|
940
|
+
|
|
941
|
+
// Option 2: Full config (for strict validation)
|
|
942
|
+
// export default {
|
|
943
|
+
// directories: { app: "app", build: ".loly", static: "public" },
|
|
944
|
+
// conventions: { /* ... */ },
|
|
945
|
+
// routing: { /* ... */ },
|
|
946
|
+
// build: { /* ... */ },
|
|
947
|
+
// server: { adapter: "express", port: 3000, host: "localhost" },
|
|
948
|
+
// rendering: { framework: "react", streaming: true, ssr: true, ssg: true },
|
|
949
|
+
// } satisfies FrameworkConfig;
|
|
940
950
|
```
|
|
941
951
|
|
|
942
952
|
### Server Configuration
|
package/dist/cli.cjs
CHANGED
|
@@ -11459,22 +11459,36 @@ function copyStaticAssets(projectRoot, outDir) {
|
|
|
11459
11459
|
const assetsSrc = import_path12.default.join(projectRoot, "assets");
|
|
11460
11460
|
const assetsDest = import_path12.default.join(outDir, "assets");
|
|
11461
11461
|
copyDirRecursive(assetsSrc, assetsDest);
|
|
11462
|
-
const
|
|
11462
|
+
const publicDir = import_path12.default.join(projectRoot, "public");
|
|
11463
11463
|
const candidates = ["favicon.ico", "favicon.png"];
|
|
11464
11464
|
for (const name of candidates) {
|
|
11465
|
-
const
|
|
11466
|
-
|
|
11467
|
-
let src = null;
|
|
11468
|
-
if (import_fs10.default.existsSync(fromApp)) src = fromApp;
|
|
11469
|
-
else if (import_fs10.default.existsSync(fromRoot)) src = fromRoot;
|
|
11470
|
-
if (src) {
|
|
11465
|
+
const fromPublic = import_path12.default.join(publicDir, name);
|
|
11466
|
+
if (import_fs10.default.existsSync(fromPublic)) {
|
|
11471
11467
|
const dest = import_path12.default.join(outDir, name);
|
|
11472
11468
|
ensureDir(import_path12.default.dirname(dest));
|
|
11473
|
-
import_fs10.default.copyFileSync(
|
|
11469
|
+
import_fs10.default.copyFileSync(fromPublic, dest);
|
|
11474
11470
|
break;
|
|
11475
11471
|
}
|
|
11476
11472
|
}
|
|
11477
11473
|
}
|
|
11474
|
+
function getFaviconInfo(projectRoot, staticDir = "public", isDev = false) {
|
|
11475
|
+
const candidates = [
|
|
11476
|
+
{ name: "favicon.ico", type: "image/x-icon" },
|
|
11477
|
+
{ name: "favicon.png", type: "image/png" }
|
|
11478
|
+
];
|
|
11479
|
+
const publicDir = import_path12.default.join(projectRoot, staticDir);
|
|
11480
|
+
for (const candidate of candidates) {
|
|
11481
|
+
const publicPath = import_path12.default.join(publicDir, candidate.name);
|
|
11482
|
+
if (import_fs10.default.existsSync(publicPath)) {
|
|
11483
|
+
return {
|
|
11484
|
+
path: `/${candidate.name}`,
|
|
11485
|
+
// Served at root from public/
|
|
11486
|
+
type: candidate.type
|
|
11487
|
+
};
|
|
11488
|
+
}
|
|
11489
|
+
}
|
|
11490
|
+
return null;
|
|
11491
|
+
}
|
|
11478
11492
|
function generateAssetManifest(outDir, stats) {
|
|
11479
11493
|
const manifest = {
|
|
11480
11494
|
client: {
|
|
@@ -11959,8 +11973,12 @@ function createDocumentTree(options) {
|
|
|
11959
11973
|
clientJsPath = "/static/client.js",
|
|
11960
11974
|
clientCssPath = "/static/client.css",
|
|
11961
11975
|
nonce,
|
|
11962
|
-
includeInlineScripts = true
|
|
11976
|
+
includeInlineScripts = true,
|
|
11963
11977
|
// Default true - scripts inline in body for both SSR and SSG
|
|
11978
|
+
faviconPath = FAVICON_PATH,
|
|
11979
|
+
// Default to /static/favicon.png for backward compatibility
|
|
11980
|
+
faviconType = "image/png"
|
|
11981
|
+
// Default to PNG for backward compatibility
|
|
11964
11982
|
} = options;
|
|
11965
11983
|
const metaObj = meta ?? null;
|
|
11966
11984
|
const title = metaObj?.title ?? titleFallback ?? "My Framework Dev";
|
|
@@ -12267,10 +12285,11 @@ function createDocumentTree(options) {
|
|
|
12267
12285
|
href: chunkHref,
|
|
12268
12286
|
as: "script"
|
|
12269
12287
|
}),
|
|
12270
|
-
import_react.default.createElement("link", {
|
|
12288
|
+
faviconPath && import_react.default.createElement("link", {
|
|
12289
|
+
key: "favicon",
|
|
12271
12290
|
rel: "icon",
|
|
12272
|
-
href:
|
|
12273
|
-
type: "image/png"
|
|
12291
|
+
href: faviconPath,
|
|
12292
|
+
type: faviconType || (faviconPath.endsWith(".ico") ? "image/x-icon" : "image/png")
|
|
12274
12293
|
}),
|
|
12275
12294
|
import_react.default.createElement("link", {
|
|
12276
12295
|
rel: "stylesheet",
|
|
@@ -12800,7 +12819,7 @@ async function handlePageRequest(options) {
|
|
|
12800
12819
|
const { errorPage, req, res, routeChunks, theme, projectRoot } = options;
|
|
12801
12820
|
const reqLogger = getRequestLogger(req);
|
|
12802
12821
|
if (errorPage) {
|
|
12803
|
-
await renderErrorPageWithStream(errorPage, req, res, error, routeChunks || {}, theme, projectRoot, options.env);
|
|
12822
|
+
await renderErrorPageWithStream(errorPage, req, res, error, routeChunks || {}, theme, projectRoot, options.env, options.config);
|
|
12804
12823
|
} else {
|
|
12805
12824
|
reqLogger.error("Unhandled error in page request", error, {
|
|
12806
12825
|
urlPath: options.urlPath,
|
|
@@ -12826,11 +12845,13 @@ async function handlePageRequestInternal(options) {
|
|
|
12826
12845
|
env = "dev",
|
|
12827
12846
|
ssgOutDir,
|
|
12828
12847
|
theme,
|
|
12829
|
-
projectRoot
|
|
12848
|
+
projectRoot,
|
|
12849
|
+
config
|
|
12830
12850
|
} = options;
|
|
12831
12851
|
const clientJsPath = env === "dev" ? "/static/client.js" : projectRoot ? getClientJsPath(projectRoot) : "/static/client.js";
|
|
12832
12852
|
const clientCssPath = env === "dev" ? "/static/client.css" : projectRoot ? getClientCssPath(projectRoot) : "/static/client.css";
|
|
12833
12853
|
const assetManifest = env === "prod" && projectRoot ? loadAssetManifest(projectRoot) : null;
|
|
12854
|
+
const faviconInfo = projectRoot && config ? getFaviconInfo(projectRoot, config.directories.static, env === "dev") : null;
|
|
12834
12855
|
const isDataReq = isDataRequest(req);
|
|
12835
12856
|
const skipLayoutHooks = isDataReq && req.headers["x-skip-layout-hooks"] === "true";
|
|
12836
12857
|
if (env === "prod" && ssgOutDir) {
|
|
@@ -12945,7 +12966,9 @@ async function handlePageRequestInternal(options) {
|
|
|
12945
12966
|
theme,
|
|
12946
12967
|
clientJsPath,
|
|
12947
12968
|
clientCssPath,
|
|
12948
|
-
nonce: nonce2
|
|
12969
|
+
nonce: nonce2,
|
|
12970
|
+
faviconPath: faviconInfo?.path || null,
|
|
12971
|
+
faviconType: faviconInfo?.type || null
|
|
12949
12972
|
});
|
|
12950
12973
|
let didError2 = false;
|
|
12951
12974
|
const { pipe: pipe2, abort: abort2 } = (0, import_server.renderToPipeableStream)(documentTree2, {
|
|
@@ -12960,7 +12983,7 @@ async function handlePageRequestInternal(options) {
|
|
|
12960
12983
|
const reqLogger2 = getRequestLogger(req);
|
|
12961
12984
|
reqLogger2.error("SSR shell error", err, { route: "not-found" });
|
|
12962
12985
|
if (!res.headersSent && errorPage) {
|
|
12963
|
-
renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
|
|
12986
|
+
renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env, config);
|
|
12964
12987
|
} else if (!res.headersSent) {
|
|
12965
12988
|
res.statusCode = 500;
|
|
12966
12989
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
@@ -13075,7 +13098,7 @@ async function handlePageRequestInternal(options) {
|
|
|
13075
13098
|
return;
|
|
13076
13099
|
} else {
|
|
13077
13100
|
if (errorPage) {
|
|
13078
|
-
await renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env);
|
|
13101
|
+
await renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env, config);
|
|
13079
13102
|
return;
|
|
13080
13103
|
} else {
|
|
13081
13104
|
throw error;
|
|
@@ -13153,7 +13176,9 @@ async function handlePageRequestInternal(options) {
|
|
|
13153
13176
|
theme,
|
|
13154
13177
|
clientJsPath,
|
|
13155
13178
|
clientCssPath,
|
|
13156
|
-
nonce
|
|
13179
|
+
nonce,
|
|
13180
|
+
faviconPath: faviconInfo?.path || null,
|
|
13181
|
+
faviconType: faviconInfo?.type || null
|
|
13157
13182
|
});
|
|
13158
13183
|
let didError = false;
|
|
13159
13184
|
const { pipe, abort } = (0, import_server.renderToPipeableStream)(documentTree, {
|
|
@@ -13201,7 +13226,7 @@ async function handlePageRequestInternal(options) {
|
|
|
13201
13226
|
abort();
|
|
13202
13227
|
});
|
|
13203
13228
|
}
|
|
13204
|
-
async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env = "dev") {
|
|
13229
|
+
async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env = "dev", config) {
|
|
13205
13230
|
try {
|
|
13206
13231
|
const isDataReq = isDataRequest(req);
|
|
13207
13232
|
const skipLayoutHooks = isDataReq && req.headers["x-skip-layout-hooks"] === "true";
|
|
@@ -13212,6 +13237,7 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
13212
13237
|
pathname: req.path,
|
|
13213
13238
|
locals: { error }
|
|
13214
13239
|
};
|
|
13240
|
+
const faviconInfo = projectRoot && config ? getFaviconInfo(projectRoot, config.directories.static, env === "dev") : null;
|
|
13215
13241
|
const layoutProps = {};
|
|
13216
13242
|
const reqLogger = getRequestLogger(req);
|
|
13217
13243
|
if (!skipLayoutHooks && errorPage.layoutServerHooks && errorPage.layoutServerHooks.length > 0) {
|
|
@@ -13317,7 +13343,9 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
13317
13343
|
theme,
|
|
13318
13344
|
clientJsPath,
|
|
13319
13345
|
clientCssPath,
|
|
13320
|
-
nonce
|
|
13346
|
+
nonce,
|
|
13347
|
+
faviconPath: faviconInfo?.path || null,
|
|
13348
|
+
faviconType: faviconInfo?.type || null
|
|
13321
13349
|
});
|
|
13322
13350
|
let didError = false;
|
|
13323
13351
|
const { pipe, abort } = (0, import_server.renderToPipeableStream)(documentTree, {
|
|
@@ -13361,11 +13389,12 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
13361
13389
|
}
|
|
13362
13390
|
|
|
13363
13391
|
// modules/build/ssg/renderer.ts
|
|
13364
|
-
async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params) {
|
|
13392
|
+
async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params, config) {
|
|
13365
13393
|
const routeChunks = loadChunksFromManifest(projectRoot);
|
|
13366
13394
|
const assetManifest = loadAssetManifest(projectRoot);
|
|
13367
13395
|
const clientJsPath = getClientJsPath(projectRoot);
|
|
13368
13396
|
const clientCssPath = getClientCssPath(projectRoot);
|
|
13397
|
+
const faviconInfo = config ? getFaviconInfo(projectRoot, config.directories.static, false) : null;
|
|
13369
13398
|
const chunkName = routeChunks[route.pattern];
|
|
13370
13399
|
let chunkHref = null;
|
|
13371
13400
|
if (chunkName != null) {
|
|
@@ -13473,8 +13502,10 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
13473
13502
|
entrypointFiles,
|
|
13474
13503
|
clientJsPath,
|
|
13475
13504
|
clientCssPath,
|
|
13476
|
-
includeInlineScripts: true
|
|
13505
|
+
includeInlineScripts: true,
|
|
13477
13506
|
// SSG needs inline scripts (renderToString doesn't support bootstrapScripts)
|
|
13507
|
+
faviconPath: faviconInfo?.path || null,
|
|
13508
|
+
faviconType: faviconInfo?.type || null
|
|
13478
13509
|
});
|
|
13479
13510
|
const html = "<!DOCTYPE html>" + (0, import_server2.renderToString)(documentTree);
|
|
13480
13511
|
const dir = pathToOutDir(ssgOutDir, urlPath);
|
|
@@ -13487,7 +13518,7 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
13487
13518
|
|
|
13488
13519
|
// modules/build/ssg/builder.ts
|
|
13489
13520
|
init_globals();
|
|
13490
|
-
async function buildStaticPages(projectRoot, routes) {
|
|
13521
|
+
async function buildStaticPages(projectRoot, routes, config) {
|
|
13491
13522
|
const ssgOutDir = import_path22.default.join(projectRoot, BUILD_FOLDER_NAME, "ssg");
|
|
13492
13523
|
ensureDir(ssgOutDir);
|
|
13493
13524
|
for (const route of routes) {
|
|
@@ -13536,7 +13567,7 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
13536
13567
|
}
|
|
13537
13568
|
for (const params of allParams) {
|
|
13538
13569
|
const urlPath = buildPathFromPattern(route.pattern, params);
|
|
13539
|
-
await renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params);
|
|
13570
|
+
await renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params, config);
|
|
13540
13571
|
}
|
|
13541
13572
|
}
|
|
13542
13573
|
console.log(`\u2705 [framework][ssg] Finished building all static pages`);
|
|
@@ -14185,7 +14216,7 @@ async function buildApp(options = {}) {
|
|
|
14185
14216
|
writeClientBoostrapManifest(projectRoot);
|
|
14186
14217
|
writeClientRoutesManifest(routes, projectRoot);
|
|
14187
14218
|
await buildClientBundle(projectRoot);
|
|
14188
|
-
await buildStaticPages(projectRoot, routes);
|
|
14219
|
+
await buildStaticPages(projectRoot, routes, config);
|
|
14189
14220
|
delete process.env.LOLY_BUILD;
|
|
14190
14221
|
console.log(`[framework][build] Build completed successfully`);
|
|
14191
14222
|
}
|
|
@@ -16427,7 +16458,8 @@ function setupRoutes(options) {
|
|
|
16427
16458
|
env: isDev ? "dev" : "prod",
|
|
16428
16459
|
ssgOutDir,
|
|
16429
16460
|
theme: req.cookies?.theme || "light",
|
|
16430
|
-
projectRoot
|
|
16461
|
+
projectRoot,
|
|
16462
|
+
config
|
|
16431
16463
|
});
|
|
16432
16464
|
});
|
|
16433
16465
|
}
|