@kuratchi/js 0.0.8 → 0.0.9
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 +35 -1
- package/dist/compiler/index.js +18 -3
- package/dist/compiler/parser.d.ts +2 -0
- package/dist/compiler/parser.js +33 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -46,6 +46,18 @@ src/routes/blog/[slug]/page.html → /blog/:slug
|
|
|
46
46
|
src/routes/layout.html → shared layout wrapping all routes
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
### Execution model
|
|
50
|
+
|
|
51
|
+
Kuratchi routes are server-first.
|
|
52
|
+
|
|
53
|
+
- `src/routes` defines server-rendered route modules.
|
|
54
|
+
- Top-level route `<script>` blocks run on the server.
|
|
55
|
+
- Template expressions, `if`, and `for` blocks render on the server.
|
|
56
|
+
- `src/server` is for private server-only modules and reusable backend logic.
|
|
57
|
+
- Reactive `$:` code is the browser-only escape hatch.
|
|
58
|
+
|
|
59
|
+
Route files are not client files. They are server-rendered routes that can opt into small browser-side reactive behavior when needed.
|
|
60
|
+
|
|
49
61
|
### Route file structure
|
|
50
62
|
|
|
51
63
|
```html
|
|
@@ -64,6 +76,7 @@ src/routes/layout.html → shared layout wrapping all routes
|
|
|
64
76
|
```
|
|
65
77
|
|
|
66
78
|
The `$database/` alias resolves to `src/database/`. You can use any path alias configured in your tsconfig.
|
|
79
|
+
Private server logic should live in `src/server/` and be imported into routes explicitly.
|
|
67
80
|
|
|
68
81
|
### Layout file
|
|
69
82
|
|
|
@@ -161,9 +174,11 @@ Block form is also supported:
|
|
|
161
174
|
```
|
|
162
175
|
|
|
163
176
|
Notes:
|
|
164
|
-
-
|
|
177
|
+
- Route files are server-rendered by default. `$:` is the only browser-side execution primitive in a route template.
|
|
178
|
+
- This reactivity runs in browser scripts rendered in the template markup, not in the top server route `<script>` block.
|
|
165
179
|
- Object/array `let` bindings are proxy-backed automatically when `$:` is used.
|
|
166
180
|
- `$: name = expr` works; when replacing proxy-backed values, the compiler preserves reactivity under the hood.
|
|
181
|
+
- You should not need `if (browser)` style guards in normal Kuratchi route code. If browser checks become necessary outside `$:`, the boundary is likely in the wrong place.
|
|
167
182
|
|
|
168
183
|
## Form actions
|
|
169
184
|
|
|
@@ -360,6 +375,7 @@ For Durable Objects, RPC is file-driven and automatic.
|
|
|
360
375
|
- Put handler logic in a `.do.ts` file.
|
|
361
376
|
- Exported functions in that file become RPC methods.
|
|
362
377
|
- Import RPC methods from `$durable-objects/<file-name-without-.do>`.
|
|
378
|
+
- RPC methods are still server-side code. They are exposed intentionally by the framework runtime, not because route files are client-side.
|
|
363
379
|
|
|
364
380
|
```html
|
|
365
381
|
<script>
|
|
@@ -497,6 +513,24 @@ import { env } from 'cloudflare:workers';
|
|
|
497
513
|
const result = await env.DB.prepare('SELECT 1').run();
|
|
498
514
|
```
|
|
499
515
|
|
|
516
|
+
## Framework environment
|
|
517
|
+
|
|
518
|
+
Kuratchi also exposes a framework build-mode flag:
|
|
519
|
+
|
|
520
|
+
```html
|
|
521
|
+
<script>
|
|
522
|
+
import { dev } from '@kuratchi/js/environment';
|
|
523
|
+
import { env } from 'cloudflare:workers';
|
|
524
|
+
|
|
525
|
+
const turnstileSiteKey = dev ? '' : (env.TURNSTILE_SITE_KEY || '');
|
|
526
|
+
</script>
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
- `dev` is `true` for Kuratchi development builds
|
|
530
|
+
- `dev` is `false` for production builds
|
|
531
|
+
- `dev` is compile-time framework state, not a generic process env var
|
|
532
|
+
- `@kuratchi/js/environment` is intended for server route code, not client `$:` scripts
|
|
533
|
+
|
|
500
534
|
## `kuratchi.config.ts`
|
|
501
535
|
|
|
502
536
|
Optional. Required only when using framework integrations or Durable Objects.
|
package/dist/compiler/index.js
CHANGED
|
@@ -52,6 +52,11 @@ function rewriteWorkerEnvAliases(source, aliases) {
|
|
|
52
52
|
}
|
|
53
53
|
return out;
|
|
54
54
|
}
|
|
55
|
+
function buildDevAliasDeclarations(aliases, isDev) {
|
|
56
|
+
if (!aliases || aliases.length === 0)
|
|
57
|
+
return '';
|
|
58
|
+
return aliases.map((alias) => `const ${alias} = ${isDev ? 'true' : 'false'};`).join('\n');
|
|
59
|
+
}
|
|
55
60
|
function parseNamedImportBindings(line) {
|
|
56
61
|
const namesMatch = line.match(/import\s*\{([^}]+)\}/);
|
|
57
62
|
if (!namesMatch)
|
|
@@ -141,9 +146,13 @@ export function compile(options) {
|
|
|
141
146
|
.replace(/^\s*import[\s\S]*?from\s+['"][^'"]+['"]\s*;?/gm, '')
|
|
142
147
|
.trim()
|
|
143
148
|
: '';
|
|
149
|
+
const devDecls = buildDevAliasDeclarations(compParsed.devAliases, !!options.isDev);
|
|
150
|
+
const effectivePropsCode = [devDecls, propsCode].filter(Boolean).join('\n');
|
|
144
151
|
const transpiledPropsCode = propsCode
|
|
145
|
-
? transpileTypeScript(
|
|
146
|
-
:
|
|
152
|
+
? transpileTypeScript(effectivePropsCode, `component-script:${fileName}.ts`)
|
|
153
|
+
: devDecls
|
|
154
|
+
? transpileTypeScript(devDecls, `component-script:${fileName}.ts`)
|
|
155
|
+
: '';
|
|
147
156
|
// template source (parseFile already removes the <script> block)
|
|
148
157
|
let source = compParsed.template;
|
|
149
158
|
// Extract optional <style> block â€" CSS is scoped and injected once per route at compile time
|
|
@@ -651,6 +660,8 @@ export function compile(options) {
|
|
|
651
660
|
}
|
|
652
661
|
// Build the layout script body (data vars, etc.)
|
|
653
662
|
let layoutScriptBody = layoutParsed.script.replace(/^\s*import[\s\S]*?from\s+['"][^'"]+['"]\s*;?/gm, '').trim();
|
|
663
|
+
const layoutDevDecls = buildDevAliasDeclarations(layoutParsed.devAliases, !!options.isDev);
|
|
664
|
+
layoutScriptBody = [layoutDevDecls, layoutScriptBody].filter(Boolean).join('\n');
|
|
654
665
|
compiledLayout = `function __layout(__content) {
|
|
655
666
|
const __esc = (v) => { if (v == null) return ''; return String(v).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); };
|
|
656
667
|
${layoutScriptBody ? layoutScriptBody + '\n ' : ''}${finalLayoutBody}
|
|
@@ -1068,6 +1079,7 @@ export function compile(options) {
|
|
|
1068
1079
|
index: i,
|
|
1069
1080
|
pattern,
|
|
1070
1081
|
renderBody,
|
|
1082
|
+
isDev: !!options.isDev,
|
|
1071
1083
|
parsed,
|
|
1072
1084
|
fnToModule,
|
|
1073
1085
|
rpcNameMap,
|
|
@@ -1373,7 +1385,7 @@ function discoverRoutes(routesDir) {
|
|
|
1373
1385
|
return results;
|
|
1374
1386
|
}
|
|
1375
1387
|
function buildRouteObject(opts) {
|
|
1376
|
-
const { pattern, renderBody, parsed, fnToModule, rpcNameMap, componentStyles } = opts;
|
|
1388
|
+
const { pattern, renderBody, isDev, parsed, fnToModule, rpcNameMap, componentStyles } = opts;
|
|
1377
1389
|
const hasFns = Object.keys(fnToModule).length > 0;
|
|
1378
1390
|
const parts = [];
|
|
1379
1391
|
parts.push(` pattern: '${pattern}'`);
|
|
@@ -1381,12 +1393,15 @@ function buildRouteObject(opts) {
|
|
|
1381
1393
|
let scriptBody = parsed.script
|
|
1382
1394
|
? parsed.script.replace(/^\s*import[\s\S]*?from\s+['"][^'"]+['"]\s*;?/gm, '').trim()
|
|
1383
1395
|
: '';
|
|
1396
|
+
const routeDevDecls = buildDevAliasDeclarations(parsed.devAliases, isDev);
|
|
1397
|
+
scriptBody = [routeDevDecls, scriptBody].filter(Boolean).join('\n');
|
|
1384
1398
|
scriptBody = rewriteImportedFunctionCalls(scriptBody, fnToModule);
|
|
1385
1399
|
scriptBody = rewriteWorkerEnvAliases(scriptBody, parsed.workerEnvAliases);
|
|
1386
1400
|
let explicitLoadFunction = parsed.loadFunction
|
|
1387
1401
|
? parsed.loadFunction.replace(/^export\s+/, '').trim()
|
|
1388
1402
|
: '';
|
|
1389
1403
|
if (explicitLoadFunction) {
|
|
1404
|
+
explicitLoadFunction = [routeDevDecls, explicitLoadFunction].filter(Boolean).join('\n');
|
|
1390
1405
|
explicitLoadFunction = rewriteImportedFunctionCalls(explicitLoadFunction, fnToModule);
|
|
1391
1406
|
explicitLoadFunction = rewriteWorkerEnvAliases(explicitLoadFunction, parsed.workerEnvAliases);
|
|
1392
1407
|
}
|
|
@@ -40,6 +40,8 @@ export interface ParsedFile {
|
|
|
40
40
|
loadReturnVars: string[];
|
|
41
41
|
/** Local aliases for Cloudflare Workers env imported from cloudflare:workers */
|
|
42
42
|
workerEnvAliases: string[];
|
|
43
|
+
/** Local aliases for dev imported from @kuratchi/js/environment */
|
|
44
|
+
devAliases: string[];
|
|
43
45
|
}
|
|
44
46
|
interface ParseFileOptions {
|
|
45
47
|
kind?: 'route' | 'layout' | 'component';
|
package/dist/compiler/parser.js
CHANGED
|
@@ -158,6 +158,26 @@ function extractCloudflareEnvAliases(importLine) {
|
|
|
158
158
|
}
|
|
159
159
|
return aliases;
|
|
160
160
|
}
|
|
161
|
+
function extractKuratchiEnvironmentDevAliases(importLine) {
|
|
162
|
+
if (!/from\s+['"]@kuratchi\/js\/environment['"]/.test(importLine))
|
|
163
|
+
return [];
|
|
164
|
+
const namedMatch = importLine.match(/import\s*\{([\s\S]*?)\}\s*from\s+['"]@kuratchi\/js\/environment['"]/);
|
|
165
|
+
if (!namedMatch) {
|
|
166
|
+
throw new Error('[kuratchi compiler] @kuratchi/js/environment only supports named imports.');
|
|
167
|
+
}
|
|
168
|
+
const aliases = [];
|
|
169
|
+
for (const rawPart of splitTopLevel(namedMatch[1], ',')) {
|
|
170
|
+
const part = rawPart.trim();
|
|
171
|
+
if (!part)
|
|
172
|
+
continue;
|
|
173
|
+
const devMatch = part.match(/^dev(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
|
|
174
|
+
if (!devMatch) {
|
|
175
|
+
throw new Error('[kuratchi compiler] @kuratchi/js/environment currently only exports `dev`.');
|
|
176
|
+
}
|
|
177
|
+
aliases.push(devMatch[1] || 'dev');
|
|
178
|
+
}
|
|
179
|
+
return aliases;
|
|
180
|
+
}
|
|
161
181
|
function extractReturnObjectKeys(body) {
|
|
162
182
|
const keys = [];
|
|
163
183
|
let i = 0;
|
|
@@ -648,6 +668,7 @@ export function parseFile(source, options = {}) {
|
|
|
648
668
|
const clientImports = [];
|
|
649
669
|
const componentImports = {};
|
|
650
670
|
const workerEnvAliases = [];
|
|
671
|
+
const devAliases = [];
|
|
651
672
|
if (script) {
|
|
652
673
|
// Support both single-line and multiline static imports.
|
|
653
674
|
const importRegex = /^\s*import[\s\S]*?from\s+['"][^'"]+['"]\s*;?/gm;
|
|
@@ -669,6 +690,14 @@ export function parseFile(source, options = {}) {
|
|
|
669
690
|
componentImports[componentName] = `${pkg}:${fileName}`; // e.g. "@kuratchi/ui:badge"
|
|
670
691
|
}
|
|
671
692
|
else {
|
|
693
|
+
const devImportAliases = extractKuratchiEnvironmentDevAliases(line);
|
|
694
|
+
if (devImportAliases.length > 0) {
|
|
695
|
+
for (const alias of devImportAliases) {
|
|
696
|
+
if (!devAliases.includes(alias))
|
|
697
|
+
devAliases.push(alias);
|
|
698
|
+
}
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
672
701
|
serverImports.push(line);
|
|
673
702
|
}
|
|
674
703
|
for (const alias of extractCloudflareEnvAliases(line)) {
|
|
@@ -683,6 +712,9 @@ export function parseFile(source, options = {}) {
|
|
|
683
712
|
while ((m = importRegex.exec(clientScript)) !== null) {
|
|
684
713
|
const line = m[0].trim();
|
|
685
714
|
clientImports.push(line);
|
|
715
|
+
if (extractKuratchiEnvironmentDevAliases(line).length > 0) {
|
|
716
|
+
throw new Error(`[kuratchi compiler] ${options.filePath || kind}\nClient <script> blocks cannot import from @kuratchi/js/environment.\nUse route server script code and pass values into the template explicitly.`);
|
|
717
|
+
}
|
|
686
718
|
if (extractCloudflareEnvAliases(line).length > 0) {
|
|
687
719
|
throw buildEnvAccessError(kind, options.filePath, 'Client <script> blocks cannot import env from cloudflare:workers.');
|
|
688
720
|
}
|
|
@@ -813,6 +845,7 @@ export function parseFile(source, options = {}) {
|
|
|
813
845
|
clientImports,
|
|
814
846
|
loadReturnVars,
|
|
815
847
|
workerEnvAliases,
|
|
848
|
+
devAliases,
|
|
816
849
|
};
|
|
817
850
|
}
|
|
818
851
|
import { transpileTypeScript } from './transpile.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kuratchi/js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "A thin, Cloudflare Workers-native web framework with Svelte-inspired syntax",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
"types": "./dist/compiler/index.d.ts",
|
|
37
37
|
"import": "./dist/compiler/index.js"
|
|
38
38
|
},
|
|
39
|
+
"./environment": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"import": "./dist/index.js"
|
|
42
|
+
},
|
|
39
43
|
"./package.json": "./package.json"
|
|
40
44
|
},
|
|
41
45
|
"engines": {
|