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 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 `.litsx` modules through `@litsx/compiler`
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.litsx` and `error.litsx` are resolved from the current route directory up to `app/`.
310
- The nearest file wins and its output is wrapped by the route layouts. A root `app/not-found.litsx`
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.litsx
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.litsx` is intentionally not supported
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.litsx` receives a generic error with an opaque `digest`; the original 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.litsx", "@acme/ui/product-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.0",
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/nextsx.git"
9
+ "url": "git+https://github.com/litsxdev/evolit.git"
10
10
  },
11
11
  "bugs": {
12
- "url": "https://github.com/litsxdev/nextsx/issues"
12
+ "url": "https://github.com/litsxdev/evolit/issues"
13
13
  },
14
- "homepage": "https://github.com/litsxdev/nextsx#readme",
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": "./src/cli.js",
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": ">=20.19.5"
58
+ "node": "^22.18.0 || >=24.11.0"
59
59
  },
60
60
  "dependencies": {
61
61
  "@jridgewell/remapping": "^2.3.5",
62
- "@litsx/compiler": "0.10.0-canary-feat-ssr-20260802205539",
63
- "@litsx/core": "0.17.0-canary-feat-ssr-20260802205539",
64
- "@litsx/ssr": "0.2.0-canary-feat-ssr-20260802205539",
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.litsx");
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 transformLitsx(source, {
942
- filename: sourcePath,
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 transformLitsx(source, { filename: sourcePath, sourceMaps: false });
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
@@ -13,8 +13,6 @@ export const DEPLOY_ASSETS_MANIFEST_FILENAME = "deploy-assets.json";
13
13
  export const DEPLOY_SERVER_MANIFEST_FILENAME = "deploy-server.json";
14
14
 
15
15
  export const MODULE_EXTENSIONS = [
16
- ".litsx",
17
- ".litsx.jsx",
18
16
  ".tsx",
19
17
  ".ts",
20
18
  ".jsx",
@@ -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.litsx");
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.litsx` boundary when available.
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";
@@ -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.litsx` boundary.
204
+ * Stops route execution with a 404 signal for the nearest `not-found.jsx` boundary.
205
205
  *
206
206
  * @returns {never}
207
207
  */
@@ -1,6 +1,6 @@
1
1
  import crypto from "node:crypto";
2
2
  import path from "node:path";
3
- import { html } from "lit";
3
+ import { html } from "@litsx/ssr";
4
4
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
5
5
 
6
6
  const SEGMENT_MARKER_PREFIX = "evolit:segment";
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.17.0-canary-feat-ssr-20260726130435",
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.litsx` boundary when available.
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.litsx";
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.litsx becomes an addressable route, with dynamic segments reserved for the next iteration."
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 .litsx source is compiled through the public @litsx/compiler facade instead of a parallel transform path."
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,7 +6,7 @@
6
6
  "allowJs": true,
7
7
  "checkJs": false,
8
8
  "strict": false,
9
- "jsx": "preserve",
9
+ "jsx": "react-jsx",
10
10
  "jsxImportSource": "@litsx/core",
11
11
  "allowArbitraryExtensions": true,
12
12
  "plugins": [
@@ -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
- }