@lolyjs/core 0.2.0-alpha.20 → 0.2.0-alpha.22
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 +59 -1
- package/dist/cli.cjs +123 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +123 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +131 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +131 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -392,6 +392,64 @@ export default function Dashboard() {
|
|
|
392
392
|
}
|
|
393
393
|
```
|
|
394
394
|
|
|
395
|
+
### 📄 Static Files & Assets
|
|
396
|
+
|
|
397
|
+
Loly serves static files from the `public/` directory at the root of your application. This is perfect for SEO files like `sitemap.xml`, `robots.txt`, favicons, and other static assets.
|
|
398
|
+
|
|
399
|
+
**How it works:**
|
|
400
|
+
- Files in `public/` are served at the root URL (e.g., `public/sitemap.xml` → `/sitemap.xml`)
|
|
401
|
+
- Static files have **priority over dynamic routes** - if a file exists in `public/`, it will be served instead of matching a route
|
|
402
|
+
- Perfect for SEO: Google automatically finds `sitemap.xml` and `robots.txt` at the root
|
|
403
|
+
- Works in both development and production environments
|
|
404
|
+
- Subdirectories are supported: `public/assets/logo.png` → `/assets/logo.png`
|
|
405
|
+
|
|
406
|
+
**Directory Structure:**
|
|
407
|
+
```
|
|
408
|
+
public/
|
|
409
|
+
├── sitemap.xml # Available at /sitemap.xml
|
|
410
|
+
├── robots.txt # Available at /robots.txt
|
|
411
|
+
├── favicon.ico # Available at /favicon.ico
|
|
412
|
+
└── assets/
|
|
413
|
+
├── logo.png # Available at /assets/logo.png
|
|
414
|
+
└── images/ # Available at /assets/images/*
|
|
415
|
+
└── hero.jpg
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**SEO Example:**
|
|
419
|
+
|
|
420
|
+
Create `public/sitemap.xml`:
|
|
421
|
+
```xml
|
|
422
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
423
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
424
|
+
<url>
|
|
425
|
+
<loc>https://example.com/</loc>
|
|
426
|
+
<lastmod>2024-01-01</lastmod>
|
|
427
|
+
<changefreq>daily</changefreq>
|
|
428
|
+
<priority>1.0</priority>
|
|
429
|
+
</url>
|
|
430
|
+
</urlset>
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Create `public/robots.txt`:
|
|
434
|
+
```
|
|
435
|
+
User-agent: *
|
|
436
|
+
Allow: /
|
|
437
|
+
|
|
438
|
+
Sitemap: https://example.com/sitemap.xml
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Both files will be automatically available at `/sitemap.xml` and `/robots.txt` respectively, and search engines will find them at the standard locations.
|
|
442
|
+
|
|
443
|
+
**Configuration:**
|
|
444
|
+
The static directory can be customized in `loly.config.ts`:
|
|
445
|
+
```tsx
|
|
446
|
+
export default {
|
|
447
|
+
directories: {
|
|
448
|
+
static: "public", // Default: "public"
|
|
449
|
+
},
|
|
450
|
+
} satisfies FrameworkConfig;
|
|
451
|
+
```
|
|
452
|
+
|
|
395
453
|
### 🔌 API Routes
|
|
396
454
|
|
|
397
455
|
Create RESTful APIs with flexible middleware support:
|
|
@@ -667,7 +725,7 @@ your-app/
|
|
|
667
725
|
│ └── events.ts # WebSocket namespace /chat
|
|
668
726
|
├── components/ # React components
|
|
669
727
|
├── lib/ # Utilities
|
|
670
|
-
├── public/ # Static files
|
|
728
|
+
├── public/ # Static files (served at root: /sitemap.xml, /robots.txt, etc.)
|
|
671
729
|
├── loly.config.ts # Framework configuration
|
|
672
730
|
├── init.server.ts # Server initialization (DB, services, etc.)
|
|
673
731
|
└── package.json
|
package/dist/cli.cjs
CHANGED
|
@@ -13743,6 +13743,105 @@ function validateRealtimeConfig(config) {
|
|
|
13743
13743
|
// modules/build/bundler/server.ts
|
|
13744
13744
|
init_globals();
|
|
13745
13745
|
var SERVER_FILES = [INIT_FILE_NAME, CONFIG_FILE_NAME];
|
|
13746
|
+
function createPathAliasPlugin(projectRoot, outDir) {
|
|
13747
|
+
const aliases = loadAliasesFromTsconfig(projectRoot);
|
|
13748
|
+
const tsconfigPath = import_path25.default.join(projectRoot, "tsconfig.json");
|
|
13749
|
+
let baseUrl = ".";
|
|
13750
|
+
if (import_fs16.default.existsSync(tsconfigPath)) {
|
|
13751
|
+
try {
|
|
13752
|
+
const tsconfig = JSON.parse(import_fs16.default.readFileSync(tsconfigPath, "utf-8"));
|
|
13753
|
+
baseUrl = tsconfig.compilerOptions?.baseUrl ?? ".";
|
|
13754
|
+
} catch {
|
|
13755
|
+
}
|
|
13756
|
+
}
|
|
13757
|
+
function resolveAliasToRelative(importPath, sourceFile) {
|
|
13758
|
+
if (importPath.startsWith(".") || importPath.startsWith("/") || import_path25.default.isAbsolute(importPath) || importPath.includes("node_modules")) {
|
|
13759
|
+
return null;
|
|
13760
|
+
}
|
|
13761
|
+
for (const [aliasKey, aliasPath] of Object.entries(aliases)) {
|
|
13762
|
+
if (importPath.startsWith(aliasKey + "/") || importPath === aliasKey) {
|
|
13763
|
+
const restPath = importPath.startsWith(aliasKey + "/") ? importPath.slice(aliasKey.length + 1) : "";
|
|
13764
|
+
const resolvedPath = restPath ? import_path25.default.join(aliasPath, restPath) : aliasPath;
|
|
13765
|
+
let actualPath = null;
|
|
13766
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".json"];
|
|
13767
|
+
if (import_fs16.default.existsSync(resolvedPath) && import_fs16.default.statSync(resolvedPath).isDirectory()) {
|
|
13768
|
+
for (const ext of extensions) {
|
|
13769
|
+
const indexPath = import_path25.default.join(resolvedPath, `index${ext}`);
|
|
13770
|
+
if (import_fs16.default.existsSync(indexPath)) {
|
|
13771
|
+
actualPath = indexPath;
|
|
13772
|
+
break;
|
|
13773
|
+
}
|
|
13774
|
+
}
|
|
13775
|
+
} else {
|
|
13776
|
+
for (const ext of extensions) {
|
|
13777
|
+
const filePath = resolvedPath + ext;
|
|
13778
|
+
if (import_fs16.default.existsSync(filePath)) {
|
|
13779
|
+
actualPath = filePath;
|
|
13780
|
+
break;
|
|
13781
|
+
}
|
|
13782
|
+
}
|
|
13783
|
+
if (!actualPath && import_fs16.default.existsSync(resolvedPath)) {
|
|
13784
|
+
actualPath = resolvedPath;
|
|
13785
|
+
}
|
|
13786
|
+
}
|
|
13787
|
+
if (actualPath) {
|
|
13788
|
+
const relativePath = import_path25.default.relative(outDir, actualPath);
|
|
13789
|
+
const normalizedPath = relativePath.replace(/\\/g, "/");
|
|
13790
|
+
const finalPath = normalizedPath.startsWith(".") ? normalizedPath : `./${normalizedPath}`;
|
|
13791
|
+
const ext = import_path25.default.extname(finalPath);
|
|
13792
|
+
const pathWithoutExt = ext === ".json" ? finalPath : finalPath.slice(0, -ext.length);
|
|
13793
|
+
return pathWithoutExt;
|
|
13794
|
+
}
|
|
13795
|
+
}
|
|
13796
|
+
}
|
|
13797
|
+
return null;
|
|
13798
|
+
}
|
|
13799
|
+
return {
|
|
13800
|
+
name: "path-alias-resolver",
|
|
13801
|
+
setup(build) {
|
|
13802
|
+
build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, (args) => {
|
|
13803
|
+
const fileName = import_path25.default.basename(args.path);
|
|
13804
|
+
const isServerFile = SERVER_FILES.some((f) => fileName === `${f}.ts` || fileName === `${f}.tsx` || fileName === `${f}.js` || fileName === `${f}.jsx`);
|
|
13805
|
+
const isInProjectRoot = import_path25.default.dirname(args.path) === projectRoot;
|
|
13806
|
+
if (!isServerFile || !isInProjectRoot) {
|
|
13807
|
+
return null;
|
|
13808
|
+
}
|
|
13809
|
+
const contents = import_fs16.default.readFileSync(args.path, "utf-8");
|
|
13810
|
+
let transformed = contents;
|
|
13811
|
+
const aliasPatterns = Object.keys(aliases).sort((a, b) => b.length - a.length);
|
|
13812
|
+
for (const aliasKey of aliasPatterns) {
|
|
13813
|
+
const escapedAlias = aliasKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13814
|
+
const aliasInQuotesPattern = new RegExp(
|
|
13815
|
+
`(['"\`])${escapedAlias}(/[^'"\`\\s]*)?(['"\`])`,
|
|
13816
|
+
"g"
|
|
13817
|
+
);
|
|
13818
|
+
transformed = transformed.replace(aliasInQuotesPattern, (match, quote1, rest, quote2) => {
|
|
13819
|
+
const fullPath = aliasKey + (rest || "");
|
|
13820
|
+
const resolved = resolveAliasToRelative(fullPath, args.path);
|
|
13821
|
+
if (resolved) {
|
|
13822
|
+
return `${quote1}${resolved}${quote2}`;
|
|
13823
|
+
}
|
|
13824
|
+
return match;
|
|
13825
|
+
});
|
|
13826
|
+
}
|
|
13827
|
+
return {
|
|
13828
|
+
contents: transformed,
|
|
13829
|
+
loader: import_path25.default.extname(args.path).slice(1)
|
|
13830
|
+
};
|
|
13831
|
+
});
|
|
13832
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
13833
|
+
const resolved = resolveAliasToRelative(args.path, args.importer || "");
|
|
13834
|
+
if (resolved) {
|
|
13835
|
+
return {
|
|
13836
|
+
path: resolved,
|
|
13837
|
+
namespace: "file"
|
|
13838
|
+
};
|
|
13839
|
+
}
|
|
13840
|
+
return null;
|
|
13841
|
+
});
|
|
13842
|
+
}
|
|
13843
|
+
};
|
|
13844
|
+
}
|
|
13746
13845
|
function collectAppSources(appDir) {
|
|
13747
13846
|
const entries = [];
|
|
13748
13847
|
function walk(dir) {
|
|
@@ -13786,6 +13885,7 @@ async function buildServerApp(projectRoot, appDir) {
|
|
|
13786
13885
|
tsconfig: import_path25.default.join(projectRoot, "tsconfig.json"),
|
|
13787
13886
|
packages: "external"
|
|
13788
13887
|
});
|
|
13888
|
+
const pathAliasPlugin = createPathAliasPlugin(projectRoot, outDir);
|
|
13789
13889
|
for (const fileName of SERVER_FILES) {
|
|
13790
13890
|
const initTS = import_path25.default.join(projectRoot, `${fileName}.ts`);
|
|
13791
13891
|
const initJS = import_path25.default.join(outDir, `${fileName}.js`);
|
|
@@ -13800,7 +13900,8 @@ async function buildServerApp(projectRoot, appDir) {
|
|
|
13800
13900
|
sourcemap: true,
|
|
13801
13901
|
bundle: false,
|
|
13802
13902
|
logLevel: "info",
|
|
13803
|
-
tsconfig: import_path25.default.join(projectRoot, "tsconfig.json")
|
|
13903
|
+
tsconfig: import_path25.default.join(projectRoot, "tsconfig.json"),
|
|
13904
|
+
plugins: [pathAliasPlugin]
|
|
13804
13905
|
});
|
|
13805
13906
|
}
|
|
13806
13907
|
}
|
|
@@ -14037,6 +14138,9 @@ function getAppDir(projectRoot, config) {
|
|
|
14037
14138
|
function getBuildDir(projectRoot, config) {
|
|
14038
14139
|
return import_path26.default.join(projectRoot, config.directories.build);
|
|
14039
14140
|
}
|
|
14141
|
+
function getStaticDir(projectRoot, config) {
|
|
14142
|
+
return import_path26.default.resolve(projectRoot, config.directories.static);
|
|
14143
|
+
}
|
|
14040
14144
|
|
|
14041
14145
|
// modules/build/index.ts
|
|
14042
14146
|
async function buildApp(options = {}) {
|
|
@@ -14087,12 +14191,13 @@ async function buildApp(options = {}) {
|
|
|
14087
14191
|
}
|
|
14088
14192
|
|
|
14089
14193
|
// src/server.ts
|
|
14090
|
-
var
|
|
14194
|
+
var import_fs21 = __toESM(require("fs"));
|
|
14091
14195
|
var import_path31 = __toESM(require("path"));
|
|
14092
14196
|
|
|
14093
14197
|
// modules/server/setup.ts
|
|
14094
14198
|
var import_express = __toESM(require("express"));
|
|
14095
14199
|
var import_path29 = __toESM(require("path"));
|
|
14200
|
+
var import_fs20 = __toESM(require("fs"));
|
|
14096
14201
|
|
|
14097
14202
|
// ../../node_modules/.pnpm/chokidar@4.0.3/node_modules/chokidar/esm/index.js
|
|
14098
14203
|
var import_fs19 = require("fs");
|
|
@@ -15934,8 +16039,22 @@ function clearAppRequireCache(appDir) {
|
|
|
15934
16039
|
|
|
15935
16040
|
// modules/server/setup.ts
|
|
15936
16041
|
init_globals();
|
|
16042
|
+
function setupStaticFiles(app, projectRoot, config) {
|
|
16043
|
+
if (!config) return;
|
|
16044
|
+
const staticDir = getStaticDir(projectRoot, config);
|
|
16045
|
+
if (import_fs20.default.existsSync(staticDir)) {
|
|
16046
|
+
app.use(
|
|
16047
|
+
import_express.default.static(staticDir, {
|
|
16048
|
+
// In production, add caching headers for better performance
|
|
16049
|
+
maxAge: process.env.NODE_ENV === "production" ? "1y" : 0,
|
|
16050
|
+
immutable: process.env.NODE_ENV === "production"
|
|
16051
|
+
})
|
|
16052
|
+
);
|
|
16053
|
+
}
|
|
16054
|
+
}
|
|
15937
16055
|
function setupServer(app, options) {
|
|
15938
16056
|
const { projectRoot, appDir, isDev, config } = options;
|
|
16057
|
+
setupStaticFiles(app, projectRoot, config);
|
|
15939
16058
|
const routeLoader = isDev ? new FilesystemRouteLoader(appDir, projectRoot) : new ManifestRouteLoader(projectRoot);
|
|
15940
16059
|
if (isDev) {
|
|
15941
16060
|
let getRoutes2 = function() {
|
|
@@ -17498,7 +17617,7 @@ var setupApplication = async ({
|
|
|
17498
17617
|
// src/server.ts
|
|
17499
17618
|
var import_dotenv2 = __toESM(require("dotenv"));
|
|
17500
17619
|
var envPath = import_path31.default.join(process.cwd(), ".env");
|
|
17501
|
-
if (
|
|
17620
|
+
if (import_fs21.default.existsSync(envPath)) {
|
|
17502
17621
|
import_dotenv2.default.config({ path: envPath });
|
|
17503
17622
|
} else {
|
|
17504
17623
|
import_dotenv2.default.config();
|
|
@@ -17520,7 +17639,7 @@ async function startServer(options = {}) {
|
|
|
17520
17639
|
const port = options.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : void 0) ?? config.server.port;
|
|
17521
17640
|
const host = process.env.HOST ?? (!isDev ? "0.0.0.0" : void 0) ?? config.server.host;
|
|
17522
17641
|
const appDir = options.appDir ?? (isDev ? getAppDir(projectRoot, config) : import_path31.default.join(getBuildDir(projectRoot, config), "server"));
|
|
17523
|
-
if (!isDev && !
|
|
17642
|
+
if (!isDev && !import_fs21.default.existsSync(appDir)) {
|
|
17524
17643
|
logger4.error("Compiled directory not found", void 0, {
|
|
17525
17644
|
buildDir: config.directories.build,
|
|
17526
17645
|
appDir,
|