evolit 0.1.0 → 0.1.2
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 +21 -9
- package/package.json +9 -9
- package/src/build.js +1 -1
- package/src/compiler.js +193 -3
- package/src/constants.js +0 -2
- package/src/deployment-runtime.js +1 -1
- package/src/index.js +1 -1
- package/src/render.js +1 -2
- package/src/request-context.js +1 -1
- package/src/route-segments.js +1 -1
- package/src/scaffold.js +4 -1
- package/src/server-api.js +1 -1
- package/templates/default/app/components/feature-card.jsx +37 -0
- package/templates/default/app/{page.litsx → page.jsx} +3 -3
- package/templates/default/jsconfig.json +6 -1
- package/templates/default/app/components/feature-card.litsx +0 -35
- /package/templates/default/app/about/{page.litsx → page.jsx} +0 -0
- /package/templates/default/app/{layout.litsx → layout.jsx} +0 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ This repository now contains the first framework MVP:
|
|
|
8
8
|
- nested `layout` composition
|
|
9
9
|
- server rendering through `@litsx/ssr`
|
|
10
10
|
- a small `evolit` CLI with `init`, `dev`, `build`, and `start`
|
|
11
|
-
- on-demand compilation of authored `.
|
|
11
|
+
- on-demand compilation of authored `.jsx` modules through `@litsx/compiler`
|
|
12
12
|
- a starter template for generating new sites
|
|
13
13
|
|
|
14
14
|
## MVP Scope
|
|
@@ -26,14 +26,26 @@ It focuses on the core contract that matters first:
|
|
|
26
26
|
|
|
27
27
|
Supported authored module extensions:
|
|
28
28
|
|
|
29
|
-
- `.litsx`
|
|
30
|
-
- `.litsx.jsx`
|
|
31
29
|
- `.js`
|
|
32
30
|
- `.jsx`
|
|
33
31
|
- `.ts`
|
|
34
32
|
- `.tsx`
|
|
35
33
|
- `.mjs`
|
|
36
34
|
|
|
35
|
+
### Internal imports
|
|
36
|
+
|
|
37
|
+
Imports beginning with `@/` resolve from the application root in server and browser graphs. This
|
|
38
|
+
keeps internal imports stable when modules move between route directories:
|
|
39
|
+
|
|
40
|
+
```jsx
|
|
41
|
+
import FeatureCard from "@/app/components/feature-card";
|
|
42
|
+
import { formatPrice } from "@/lib/format-price";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Generated applications declare `"@/*": ["./*"]` in `jsconfig.json`, so editors and typechecking
|
|
46
|
+
use the same convention. An explicit `@/*` mapping in `jsconfig.json` or `tsconfig.json` takes
|
|
47
|
+
priority when an application needs a different source root.
|
|
48
|
+
|
|
37
49
|
## Commands
|
|
38
50
|
|
|
39
51
|
```sh
|
|
@@ -306,20 +318,20 @@ code. Request values are isolated with async request context and are discarded w
|
|
|
306
318
|
|
|
307
319
|
## Route Boundaries
|
|
308
320
|
|
|
309
|
-
`not-found.
|
|
310
|
-
The nearest file wins and its output is wrapped by the route layouts. A root `app/not-found.
|
|
321
|
+
`not-found.jsx` and `error.jsx` are resolved from the current route directory up to `app/`.
|
|
322
|
+
The nearest file wins and its output is wrapped by the route layouts. A root `app/not-found.jsx`
|
|
311
323
|
also handles unmatched URLs; without one, evolit returns its minimal built-in 404 document.
|
|
312
324
|
|
|
313
325
|
```js
|
|
314
|
-
// app/blog/error.
|
|
326
|
+
// app/blog/error.jsx
|
|
315
327
|
export default async function BlogError({ error }) {
|
|
316
328
|
return `<p>Could not load this post: ${error.message}</p>`;
|
|
317
329
|
}
|
|
318
330
|
```
|
|
319
331
|
|
|
320
|
-
Boundaries always bypass the route response cache. `loading.
|
|
332
|
+
Boundaries always bypass the route response cache. `loading.jsx` is intentionally not supported
|
|
321
333
|
yet: it requires an end-to-end streaming document transport rather than an HTML-string fallback.
|
|
322
|
-
In production, `error.
|
|
334
|
+
In production, `error.jsx` receives a generic error with an opaque `digest`; the original error
|
|
323
335
|
is reported only on the server.
|
|
324
336
|
|
|
325
337
|
## Route Handlers
|
|
@@ -414,7 +426,7 @@ enumerated at build time, so production applications can declare only those exce
|
|
|
414
426
|
|
|
415
427
|
```js
|
|
416
428
|
export default {
|
|
417
|
-
clientBoundaries: ["./src/components/dynamic-card.
|
|
429
|
+
clientBoundaries: ["./src/components/dynamic-card.jsx", "@acme/ui/product-card"],
|
|
418
430
|
};
|
|
419
431
|
```
|
|
420
432
|
|
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A convention-driven application framework for LitSX and web components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "yarn@4.10.3",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/litsxdev/
|
|
9
|
+
"url": "git+https://github.com/litsxdev/evolit.git"
|
|
10
10
|
},
|
|
11
11
|
"bugs": {
|
|
12
|
-
"url": "https://github.com/litsxdev/
|
|
12
|
+
"url": "https://github.com/litsxdev/evolit/issues"
|
|
13
13
|
},
|
|
14
|
-
"homepage": "https://github.com/litsxdev/
|
|
14
|
+
"homepage": "https://github.com/litsxdev/evolit#readme",
|
|
15
15
|
"files": [
|
|
16
16
|
"src",
|
|
17
17
|
"templates/default/app/**",
|
|
18
18
|
"templates/default/jsconfig.json",
|
|
19
19
|
"templates/default/yarnrc.yml.template"
|
|
20
20
|
],
|
|
21
|
-
"bin": "
|
|
21
|
+
"bin": "src/cli.js",
|
|
22
22
|
"main": "./src/index.js",
|
|
23
23
|
"exports": {
|
|
24
24
|
".": "./src/index.js",
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
"author": "LitSX Team",
|
|
56
56
|
"license": "Apache-2.0",
|
|
57
57
|
"engines": {
|
|
58
|
-
"node": ">=
|
|
58
|
+
"node": "^22.18.0 || >=24.11.0"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@jridgewell/remapping": "^2.3.5",
|
|
62
|
-
"@litsx/compiler": "0.
|
|
63
|
-
"@litsx/core": "0.
|
|
64
|
-
"@litsx/ssr": "0.
|
|
62
|
+
"@litsx/compiler": "1.0.0-next.8",
|
|
63
|
+
"@litsx/core": "1.0.0-next.5",
|
|
64
|
+
"@litsx/ssr": "1.0.0-next.3",
|
|
65
65
|
"@litsx/typescript": "^0.9.0",
|
|
66
66
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
67
67
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
package/src/build.js
CHANGED
|
@@ -344,7 +344,7 @@ export async function buildProject(projectRoot) {
|
|
|
344
344
|
if (assetResolver(moduleId) || clientAssets.byPublicPath?.[moduleId]) continue;
|
|
345
345
|
const importerPath = routeResult.boundaryModule
|
|
346
346
|
?? routeResult.route?.page
|
|
347
|
-
?? path.join(projectRoot, "app", "page.
|
|
347
|
+
?? path.join(projectRoot, "app", "page.jsx");
|
|
348
348
|
let sourcePath = null;
|
|
349
349
|
if (moduleId.startsWith("file:")) {
|
|
350
350
|
try { sourcePath = fileURLToPath(moduleId); } catch {}
|
package/src/compiler.js
CHANGED
|
@@ -52,6 +52,175 @@ function shouldCompileModule(filePath) {
|
|
|
52
52
|
return MODULE_EXTENSIONS.some((extension) => filePath.endsWith(extension));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function containsJsxSyntax(source, filePath) {
|
|
56
|
+
const sourceFile = ts.createSourceFile(
|
|
57
|
+
filePath,
|
|
58
|
+
source,
|
|
59
|
+
ts.ScriptTarget.Latest,
|
|
60
|
+
true,
|
|
61
|
+
ts.ScriptKind.TSX,
|
|
62
|
+
);
|
|
63
|
+
let containsJsx = false;
|
|
64
|
+
|
|
65
|
+
function visit(node) {
|
|
66
|
+
if (
|
|
67
|
+
ts.isJsxElement(node)
|
|
68
|
+
|| ts.isJsxSelfClosingElement(node)
|
|
69
|
+
|| ts.isJsxFragment(node)
|
|
70
|
+
) {
|
|
71
|
+
containsJsx = true;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
ts.forEachChild(node, visit);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
visit(sourceFile);
|
|
78
|
+
return containsJsx;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function containsLitsxComponentMetadata(source, filePath) {
|
|
82
|
+
const sourceFile = ts.createSourceFile(
|
|
83
|
+
filePath,
|
|
84
|
+
source,
|
|
85
|
+
ts.ScriptTarget.Latest,
|
|
86
|
+
true,
|
|
87
|
+
ts.ScriptKind.TSX,
|
|
88
|
+
);
|
|
89
|
+
let containsMetadata = false;
|
|
90
|
+
|
|
91
|
+
function visit(node) {
|
|
92
|
+
if (
|
|
93
|
+
ts.isBinaryExpression(node)
|
|
94
|
+
&& node.operatorToken.kind === ts.SyntaxKind.EqualsToken
|
|
95
|
+
&& ts.isPropertyAccessExpression(node.left)
|
|
96
|
+
&& ts.isIdentifier(node.left.expression)
|
|
97
|
+
&& /^[A-Z]/.test(node.left.expression.text)
|
|
98
|
+
&& ["elements", "styles", "properties", "shadowRootOptions", "expose", "lightDom"].includes(node.left.name.text)
|
|
99
|
+
) {
|
|
100
|
+
containsMetadata = true;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
ts.forEachChild(node, visit);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
visit(sourceFile);
|
|
107
|
+
return containsMetadata;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isLitsxAuthoredModule(filePath, source) {
|
|
111
|
+
return (
|
|
112
|
+
filePath.endsWith(".jsx")
|
|
113
|
+
|| filePath.endsWith(".tsx")
|
|
114
|
+
|| containsJsxSyntax(source, filePath)
|
|
115
|
+
|| containsLitsxComponentMetadata(source, filePath)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createIdentitySourceMap(source, sourcePath) {
|
|
120
|
+
return JSON.parse(new MagicString(source).generateMap({
|
|
121
|
+
hires: true,
|
|
122
|
+
includeContent: true,
|
|
123
|
+
source: sourcePath.split(path.sep).join("/"),
|
|
124
|
+
}).toString());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function collectSideEffectImports(source, sourcePath) {
|
|
128
|
+
const sourceFile = ts.createSourceFile(
|
|
129
|
+
sourcePath,
|
|
130
|
+
source,
|
|
131
|
+
ts.ScriptTarget.Latest,
|
|
132
|
+
true,
|
|
133
|
+
ts.ScriptKind.TSX,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return sourceFile.statements
|
|
137
|
+
.filter((statement) => ts.isImportDeclaration(statement) && statement.importClause == null)
|
|
138
|
+
.map((statement) => ({
|
|
139
|
+
source: statement.moduleSpecifier.text,
|
|
140
|
+
statement: source.slice(statement.getStart(sourceFile), statement.end),
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function restoreSideEffectImports(source, sourcePath, transformed, sourceMaps) {
|
|
145
|
+
const transformedSpecifiers = new Set(
|
|
146
|
+
[...transformed.code.matchAll(MODULE_SPECIFIER_PATTERN)]
|
|
147
|
+
.map((match) => match[1] ?? match[2])
|
|
148
|
+
.filter(Boolean),
|
|
149
|
+
);
|
|
150
|
+
const missingImports = collectSideEffectImports(source, sourcePath)
|
|
151
|
+
.filter((entry) => !transformedSpecifiers.has(entry.source));
|
|
152
|
+
if (missingImports.length === 0) {
|
|
153
|
+
return transformed;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const magicSource = new MagicString(transformed.code);
|
|
157
|
+
magicSource.prepend(`${missingImports.map((entry) => entry.statement).join("\n")}\n`);
|
|
158
|
+
let map = transformed.map ?? null;
|
|
159
|
+
|
|
160
|
+
if (sourceMaps && map) {
|
|
161
|
+
const intermediateSourceId = `${sourcePath.split(path.sep).join("/")}#evolit-side-effects`;
|
|
162
|
+
const restoredMap = magicSource.generateMap({
|
|
163
|
+
hires: true,
|
|
164
|
+
includeContent: false,
|
|
165
|
+
source: intermediateSourceId,
|
|
166
|
+
});
|
|
167
|
+
map = remapping(restoredMap.toString(), (mappedSource) => (
|
|
168
|
+
mappedSource.endsWith("#evolit-side-effects") ? transformed.map : null
|
|
169
|
+
));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
...transformed,
|
|
174
|
+
code: magicSource.toString(),
|
|
175
|
+
map,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function transformModuleSource(source, {
|
|
180
|
+
sourcePath,
|
|
181
|
+
sourceMaps,
|
|
182
|
+
ssr = false,
|
|
183
|
+
}) {
|
|
184
|
+
if (isLitsxAuthoredModule(sourcePath, source)) {
|
|
185
|
+
const transformed = await transformLitsx(source, {
|
|
186
|
+
filename: sourcePath,
|
|
187
|
+
sourceMaps,
|
|
188
|
+
ssr,
|
|
189
|
+
});
|
|
190
|
+
return restoreSideEffectImports(source, sourcePath, transformed, sourceMaps);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (sourcePath.endsWith(".ts")) {
|
|
194
|
+
const result = ts.transpileModule(source, {
|
|
195
|
+
fileName: sourcePath,
|
|
196
|
+
compilerOptions: {
|
|
197
|
+
inlineSources: sourceMaps,
|
|
198
|
+
module: ts.ModuleKind.ESNext,
|
|
199
|
+
sourceMap: sourceMaps,
|
|
200
|
+
target: ts.ScriptTarget.ESNext,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
const map = sourceMaps && result.sourceMapText
|
|
204
|
+
? JSON.parse(result.sourceMapText)
|
|
205
|
+
: null;
|
|
206
|
+
if (map) {
|
|
207
|
+
map.sources = [sourcePath.split(path.sep).join("/")];
|
|
208
|
+
map.sourcesContent = [source];
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
code: result.outputText.replace(/\n?\/\/# sourceMappingURL=.*$/u, ""),
|
|
212
|
+
map,
|
|
213
|
+
metadata: {},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
code: source,
|
|
219
|
+
map: sourceMaps ? createIdentitySourceMap(source, sourcePath) : null,
|
|
220
|
+
metadata: {},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
55
224
|
function isStaticAssetPath(filePath) {
|
|
56
225
|
return STATIC_ASSET_EXTENSIONS.some((extension) => filePath.endsWith(extension));
|
|
57
226
|
}
|
|
@@ -537,8 +706,24 @@ async function resolveProjectPathAlias(projectRoot, specifier) {
|
|
|
537
706
|
return null;
|
|
538
707
|
}
|
|
539
708
|
|
|
709
|
+
async function resolveProjectRootAlias(projectRoot, specifier) {
|
|
710
|
+
if (!specifier.startsWith("@/") || specifier.length === 2) return null;
|
|
711
|
+
|
|
712
|
+
const normalizedProjectRoot = path.resolve(projectRoot);
|
|
713
|
+
const candidatePath = path.resolve(normalizedProjectRoot, specifier.slice(2));
|
|
714
|
+
const relativePath = path.relative(normalizedProjectRoot, candidatePath);
|
|
715
|
+
const isOutsideProject =
|
|
716
|
+
relativePath === ".."
|
|
717
|
+
|| relativePath.startsWith(`..${path.sep}`)
|
|
718
|
+
|| path.isAbsolute(relativePath);
|
|
719
|
+
if (isOutsideProject) return null;
|
|
720
|
+
|
|
721
|
+
return resolveImportPath(path.join(normalizedProjectRoot, "__alias__.js"), candidatePath);
|
|
722
|
+
}
|
|
723
|
+
|
|
540
724
|
async function resolveProjectMappedImport(projectRoot, importerPath, specifier) {
|
|
541
725
|
return await resolveProjectPathAlias(projectRoot, specifier)
|
|
726
|
+
?? await resolveProjectRootAlias(projectRoot, specifier)
|
|
542
727
|
?? await resolveProjectPackageImportMap(importerPath, specifier);
|
|
543
728
|
}
|
|
544
729
|
|
|
@@ -938,8 +1123,9 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
|
|
|
938
1123
|
|
|
939
1124
|
await ensureDirectory(path.dirname(outputPath));
|
|
940
1125
|
const source = await fs.readFile(sourcePath, "utf8");
|
|
941
|
-
const transformed = await
|
|
942
|
-
|
|
1126
|
+
const transformed = await transformModuleSource(source, {
|
|
1127
|
+
projectRoot,
|
|
1128
|
+
sourcePath,
|
|
943
1129
|
sourceMaps,
|
|
944
1130
|
ssr,
|
|
945
1131
|
});
|
|
@@ -1163,7 +1349,11 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
|
|
|
1163
1349
|
if (visited.has(sourcePath)) return;
|
|
1164
1350
|
visited.add(sourcePath);
|
|
1165
1351
|
const source = await fs.readFile(sourcePath, "utf8");
|
|
1166
|
-
const transformed = await
|
|
1352
|
+
const transformed = await transformModuleSource(source, {
|
|
1353
|
+
projectRoot,
|
|
1354
|
+
sourcePath,
|
|
1355
|
+
sourceMaps: false,
|
|
1356
|
+
});
|
|
1167
1357
|
const isServer = isCompiledServerComponentModule(transformed.code);
|
|
1168
1358
|
const isComponent = isCompiledClientBoundaryModule(transformed.code);
|
|
1169
1359
|
if (isComponent && !isServer) {
|
package/src/constants.js
CHANGED
|
@@ -423,7 +423,7 @@ export async function createRequestRenderer({
|
|
|
423
423
|
|
|
424
424
|
const importerPath = routeResult?.boundaryModule
|
|
425
425
|
?? routeResult?.route?.page
|
|
426
|
-
?? path.join(projectRoot, "app", "page.
|
|
426
|
+
?? path.join(projectRoot, "app", "page.jsx");
|
|
427
427
|
if (moduleId.startsWith("/")) {
|
|
428
428
|
const projectSpecifier = `.${moduleId}`;
|
|
429
429
|
return resolveProjectModuleSpecifier(projectRoot, importerPath, projectSpecifier);
|
package/src/index.js
CHANGED
|
@@ -92,7 +92,7 @@ export { cookies } from "./request-context.js";
|
|
|
92
92
|
export { headers } from "./request-context.js";
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* Signals a 404 response and renders the nearest `not-found.
|
|
95
|
+
* Signals a 404 response and renders the nearest `not-found.jsx` boundary when available.
|
|
96
96
|
*
|
|
97
97
|
* @returns {never}
|
|
98
98
|
*/
|
package/src/render.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { html } from "lit";
|
|
4
3
|
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
5
|
-
import { renderToString } from "@litsx/ssr";
|
|
4
|
+
import { html, renderToString } from "@litsx/ssr";
|
|
6
5
|
import { discoverAppRouteHandlers, discoverAppRoutes, matchRoute } from "./app-discovery.js";
|
|
7
6
|
import { importCompiledModule } from "./compiler.js";
|
|
8
7
|
import { APP_DIRECTORY, MODULE_EXTENSIONS } from "./constants.js";
|
package/src/request-context.js
CHANGED
|
@@ -201,7 +201,7 @@ export function permanentRedirect(location) {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
|
-
* Stops route execution with a 404 signal for the nearest `not-found.
|
|
204
|
+
* Stops route execution with a 404 signal for the nearest `not-found.jsx` boundary.
|
|
205
205
|
*
|
|
206
206
|
* @returns {never}
|
|
207
207
|
*/
|
package/src/route-segments.js
CHANGED
package/src/scaffold.js
CHANGED
|
@@ -16,6 +16,9 @@ async function writeSitePackageJson(targetDirectory, siteName) {
|
|
|
16
16
|
version: "0.1.0",
|
|
17
17
|
private: true,
|
|
18
18
|
type: "module",
|
|
19
|
+
engines: {
|
|
20
|
+
node: "^22.18.0 || >=24.11.0",
|
|
21
|
+
},
|
|
19
22
|
scripts: {
|
|
20
23
|
dev: "evolit dev",
|
|
21
24
|
build: "evolit build",
|
|
@@ -23,7 +26,7 @@ async function writeSitePackageJson(targetDirectory, siteName) {
|
|
|
23
26
|
typecheck: "litsx-tsc -p jsconfig.json --noEmit",
|
|
24
27
|
},
|
|
25
28
|
dependencies: {
|
|
26
|
-
"@litsx/core": "0.
|
|
29
|
+
"@litsx/core": "1.0.0-next.5",
|
|
27
30
|
evolit: frameworkVersion,
|
|
28
31
|
},
|
|
29
32
|
devDependencies: {
|
package/src/server-api.js
CHANGED
|
@@ -13,7 +13,7 @@ export { cookies } from "./request-context.js";
|
|
|
13
13
|
export { headers } from "./request-context.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Signals a 404 response and renders the nearest `not-found.
|
|
16
|
+
* Signals a 404 response and renders the nearest `not-found.jsx` boundary when available.
|
|
17
17
|
*
|
|
18
18
|
* @returns {never}
|
|
19
19
|
*/
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
|
|
3
|
+
export default function FeatureCard({ title, body }) {
|
|
4
|
+
return (
|
|
5
|
+
<article class="card">
|
|
6
|
+
<h2 class="title">{title}</h2>
|
|
7
|
+
<p class="body">
|
|
8
|
+
{body}
|
|
9
|
+
</p>
|
|
10
|
+
</article>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
FeatureCard.styles = css`
|
|
15
|
+
:host {
|
|
16
|
+
display: block;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.card {
|
|
20
|
+
padding: 24px;
|
|
21
|
+
border-radius: 18px;
|
|
22
|
+
background: rgba(255, 255, 255, 0.06);
|
|
23
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
24
|
+
backdrop-filter: blur(12px);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.title {
|
|
28
|
+
margin: 0 0 12px;
|
|
29
|
+
font-size: 1.25rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.body {
|
|
33
|
+
margin: 0;
|
|
34
|
+
line-height: 1.7;
|
|
35
|
+
color: rgba(248, 247, 241, 0.78);
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import FeatureCard from "
|
|
1
|
+
import FeatureCard from "@/app/components/feature-card";
|
|
2
2
|
|
|
3
3
|
export default async function HomePage() {
|
|
4
4
|
return (
|
|
@@ -20,7 +20,7 @@ export default async function HomePage() {
|
|
|
20
20
|
<div class="features">
|
|
21
21
|
<FeatureCard
|
|
22
22
|
title="File Routing"
|
|
23
|
-
body="Every app/page.
|
|
23
|
+
body="Every app/page.jsx becomes an addressable route, with dynamic segments reserved for the next iteration."
|
|
24
24
|
/>
|
|
25
25
|
<FeatureCard
|
|
26
26
|
title="SSR Boundary"
|
|
@@ -28,7 +28,7 @@ export default async function HomePage() {
|
|
|
28
28
|
/>
|
|
29
29
|
<FeatureCard
|
|
30
30
|
title="Compiler Reuse"
|
|
31
|
-
body="Authored .
|
|
31
|
+
body="Authored .jsx source is compiled through the public @litsx/compiler facade instead of a parallel transform path."
|
|
32
32
|
/>
|
|
33
33
|
</div>
|
|
34
34
|
</section>
|
|
@@ -6,9 +6,14 @@
|
|
|
6
6
|
"allowJs": true,
|
|
7
7
|
"checkJs": false,
|
|
8
8
|
"strict": false,
|
|
9
|
-
"jsx": "
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
10
|
"jsxImportSource": "@litsx/core",
|
|
11
11
|
"allowArbitraryExtensions": true,
|
|
12
|
+
"paths": {
|
|
13
|
+
"@/*": [
|
|
14
|
+
"./*"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
12
17
|
"plugins": [
|
|
13
18
|
{
|
|
14
19
|
"name": "@litsx/typescript"
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export default function FeatureCard({ title, body }) {
|
|
2
|
-
static styles = `
|
|
3
|
-
:host {
|
|
4
|
-
display: block;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.card {
|
|
8
|
-
padding: 24px;
|
|
9
|
-
border-radius: 18px;
|
|
10
|
-
background: rgba(255, 255, 255, 0.06);
|
|
11
|
-
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
12
|
-
backdrop-filter: blur(12px);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.title {
|
|
16
|
-
margin: 0 0 12px;
|
|
17
|
-
font-size: 1.25rem;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.body {
|
|
21
|
-
margin: 0;
|
|
22
|
-
line-height: 1.7;
|
|
23
|
-
color: rgba(248, 247, 241, 0.78);
|
|
24
|
-
}
|
|
25
|
-
`;
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<article class="card">
|
|
29
|
-
<h2 class="title">{title}</h2>
|
|
30
|
-
<p class="body">
|
|
31
|
-
{body}
|
|
32
|
-
</p>
|
|
33
|
-
</article>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
File without changes
|
|
File without changes
|