evolit 0.1.0 → 0.1.1
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 +7 -9
- package/package.json +9 -9
- package/src/build.js +1 -1
- package/src/compiler.js +177 -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 +1 -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,8 +26,6 @@ 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`
|
|
@@ -306,20 +304,20 @@ code. Request values are isolated with async request context and are discarded w
|
|
|
306
304
|
|
|
307
305
|
## Route Boundaries
|
|
308
306
|
|
|
309
|
-
`not-found.
|
|
310
|
-
The nearest file wins and its output is wrapped by the route layouts. A root `app/not-found.
|
|
307
|
+
`not-found.jsx` and `error.jsx` are resolved from the current route directory up to `app/`.
|
|
308
|
+
The nearest file wins and its output is wrapped by the route layouts. A root `app/not-found.jsx`
|
|
311
309
|
also handles unmatched URLs; without one, evolit returns its minimal built-in 404 document.
|
|
312
310
|
|
|
313
311
|
```js
|
|
314
|
-
// app/blog/error.
|
|
312
|
+
// app/blog/error.jsx
|
|
315
313
|
export default async function BlogError({ error }) {
|
|
316
314
|
return `<p>Could not load this post: ${error.message}</p>`;
|
|
317
315
|
}
|
|
318
316
|
```
|
|
319
317
|
|
|
320
|
-
Boundaries always bypass the route response cache. `loading.
|
|
318
|
+
Boundaries always bypass the route response cache. `loading.jsx` is intentionally not supported
|
|
321
319
|
yet: it requires an end-to-end streaming document transport rather than an HTML-string fallback.
|
|
322
|
-
In production, `error.
|
|
320
|
+
In production, `error.jsx` receives a generic error with an opaque `digest`; the original error
|
|
323
321
|
is reported only on the server.
|
|
324
322
|
|
|
325
323
|
## Route Handlers
|
|
@@ -414,7 +412,7 @@ enumerated at build time, so production applications can declare only those exce
|
|
|
414
412
|
|
|
415
413
|
```js
|
|
416
414
|
export default {
|
|
417
|
-
clientBoundaries: ["./src/components/dynamic-card.
|
|
415
|
+
clientBoundaries: ["./src/components/dynamic-card.jsx", "@acme/ui/product-card"],
|
|
418
416
|
};
|
|
419
417
|
```
|
|
420
418
|
|
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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
|
}
|
|
@@ -938,8 +1107,9 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
|
|
|
938
1107
|
|
|
939
1108
|
await ensureDirectory(path.dirname(outputPath));
|
|
940
1109
|
const source = await fs.readFile(sourcePath, "utf8");
|
|
941
|
-
const transformed = await
|
|
942
|
-
|
|
1110
|
+
const transformed = await transformModuleSource(source, {
|
|
1111
|
+
projectRoot,
|
|
1112
|
+
sourcePath,
|
|
943
1113
|
sourceMaps,
|
|
944
1114
|
ssr,
|
|
945
1115
|
});
|
|
@@ -1163,7 +1333,11 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
|
|
|
1163
1333
|
if (visited.has(sourcePath)) return;
|
|
1164
1334
|
visited.add(sourcePath);
|
|
1165
1335
|
const source = await fs.readFile(sourcePath, "utf8");
|
|
1166
|
-
const transformed = await
|
|
1336
|
+
const transformed = await transformModuleSource(source, {
|
|
1337
|
+
projectRoot,
|
|
1338
|
+
sourcePath,
|
|
1339
|
+
sourceMaps: false,
|
|
1340
|
+
});
|
|
1167
1341
|
const isServer = isCompiledServerComponentModule(transformed.code);
|
|
1168
1342
|
const isComponent = isCompiledClientBoundaryModule(transformed.code);
|
|
1169
1343
|
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 "./components/feature-card.
|
|
1
|
+
import FeatureCard from "./components/feature-card.jsx";
|
|
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>
|
|
@@ -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
|