evolit 0.1.0-alpha.0
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/LICENSE +201 -0
- package/README.md +352 -0
- package/package.json +63 -0
- package/src/app-discovery.js +312 -0
- package/src/build.js +417 -0
- package/src/cli.js +83 -0
- package/src/client-assets.js +1881 -0
- package/src/compiler.js +353 -0
- package/src/config.js +20 -0
- package/src/constants.js +49 -0
- package/src/deployment-runtime.js +463 -0
- package/src/fs-utils.js +71 -0
- package/src/index.js +167 -0
- package/src/render.js +516 -0
- package/src/request-context-browser.js +11 -0
- package/src/request-context.js +230 -0
- package/src/response-cache.js +214 -0
- package/src/route-config.js +60 -0
- package/src/scaffold.js +107 -0
- package/src/server-api.js +51 -0
- package/src/server.js +230 -0
- package/src/ssr-adapter.js +189 -0
- package/templates/default/app/about/page.litsx +20 -0
- package/templates/default/app/assets.d.ts +1 -0
- package/templates/default/app/components/feature-card.litsx +35 -0
- package/templates/default/app/global.css +27 -0
- package/templates/default/app/layout.litsx +18 -0
- package/templates/default/app/page.litsx +36 -0
- package/templates/default/jsconfig.json +22 -0
package/src/compiler.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { transformLitsx } from "@litsx/compiler";
|
|
5
|
+
import MagicString from "magic-string";
|
|
6
|
+
import remapping from "@jridgewell/remapping";
|
|
7
|
+
import {
|
|
8
|
+
BUILD_DIRECTORY,
|
|
9
|
+
CLIENT_DIRECTORY,
|
|
10
|
+
DEV_DIRECTORY,
|
|
11
|
+
INTERNAL_DIRECTORY,
|
|
12
|
+
MODULE_EXTENSIONS,
|
|
13
|
+
SERVER_DIRECTORY,
|
|
14
|
+
STATIC_ASSET_EXTENSIONS,
|
|
15
|
+
} from "./constants.js";
|
|
16
|
+
import { ensureDirectory } from "./fs-utils.js";
|
|
17
|
+
|
|
18
|
+
const MODULE_SPECIFIER_PATTERN =
|
|
19
|
+
/\b(?:import|export)\s+(?:[^"']*?\s+from\s+)?["']([^"']+)["']|\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
20
|
+
|
|
21
|
+
function isRelativeSpecifier(specifier) {
|
|
22
|
+
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isBareSpecifier(specifier) {
|
|
26
|
+
return (
|
|
27
|
+
typeof specifier === "string" &&
|
|
28
|
+
specifier.length > 0 &&
|
|
29
|
+
!specifier.startsWith(".") &&
|
|
30
|
+
!specifier.startsWith("/") &&
|
|
31
|
+
!specifier.startsWith("file:") &&
|
|
32
|
+
!specifier.startsWith("node:") &&
|
|
33
|
+
!specifier.startsWith("data:") &&
|
|
34
|
+
!specifier.startsWith("http:") &&
|
|
35
|
+
!specifier.startsWith("https:")
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function shouldCompileModule(filePath) {
|
|
40
|
+
return MODULE_EXTENSIONS.some((extension) => filePath.endsWith(extension));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isStaticAssetPath(filePath) {
|
|
44
|
+
return STATIC_ASSET_EXTENSIONS.some((extension) => filePath.endsWith(extension));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isStyleAssetPath(filePath) {
|
|
48
|
+
return filePath.endsWith(".css");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createStaticAssetStubSource(relativeAssetPath, mode, target = "server", staticAssetPublicUrls = null) {
|
|
52
|
+
const normalizedAssetPath = relativeAssetPath.split(path.sep).join("/");
|
|
53
|
+
if (normalizedAssetPath.endsWith(".css")) {
|
|
54
|
+
return "export default {};\n";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (target === "server" && staticAssetPublicUrls?.has(normalizedAssetPath)) {
|
|
58
|
+
return `export default ${JSON.stringify(staticAssetPublicUrls.get(normalizedAssetPath))};\n`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return `export default "__EVOLIT_ASSET_URL__:${normalizedAssetPath}";\n`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function resolveImportPath(importerPath, specifier) {
|
|
65
|
+
const basePath = path.resolve(path.dirname(importerPath), specifier);
|
|
66
|
+
const candidates = [basePath];
|
|
67
|
+
|
|
68
|
+
if (!path.extname(basePath)) {
|
|
69
|
+
for (const extension of [...MODULE_EXTENSIONS, ...STATIC_ASSET_EXTENSIONS]) {
|
|
70
|
+
candidates.push(`${basePath}${extension}`);
|
|
71
|
+
candidates.push(path.join(basePath, `index${extension}`));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const candidate of candidates) {
|
|
76
|
+
try {
|
|
77
|
+
const stats = await fs.stat(candidate);
|
|
78
|
+
if (stats.isFile()) {
|
|
79
|
+
return candidate;
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Try the next candidate.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getOutputRoot(projectRoot, mode) {
|
|
90
|
+
return getTypedOutputRoot(projectRoot, mode, "server");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getTypedOutputRoot(projectRoot, mode, target = "server") {
|
|
94
|
+
return path.join(
|
|
95
|
+
projectRoot,
|
|
96
|
+
INTERNAL_DIRECTORY,
|
|
97
|
+
mode === "development" ? DEV_DIRECTORY : BUILD_DIRECTORY,
|
|
98
|
+
target === "client" ? CLIENT_DIRECTORY : SERVER_DIRECTORY,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function toOutputPath(projectRoot, outputRoot, sourcePath) {
|
|
103
|
+
const relativePath = path.relative(projectRoot, sourcePath);
|
|
104
|
+
const extension = path.extname(relativePath);
|
|
105
|
+
if (!extension) {
|
|
106
|
+
return path.join(outputRoot, `${relativePath}.mjs`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return path.join(
|
|
110
|
+
outputRoot,
|
|
111
|
+
`${relativePath.slice(0, -extension.length)}.mjs`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function rewriteRelativeSpecifiers({
|
|
116
|
+
projectRoot,
|
|
117
|
+
outputRoot,
|
|
118
|
+
sourcePath,
|
|
119
|
+
code,
|
|
120
|
+
compileModule,
|
|
121
|
+
moduleMetadata,
|
|
122
|
+
mode,
|
|
123
|
+
target,
|
|
124
|
+
staticAssetPublicUrls,
|
|
125
|
+
serverImportQuery,
|
|
126
|
+
sourceMaps,
|
|
127
|
+
outputPath,
|
|
128
|
+
inputSourceMap,
|
|
129
|
+
}) {
|
|
130
|
+
const magicSource = new MagicString(code);
|
|
131
|
+
let didRewrite = false;
|
|
132
|
+
|
|
133
|
+
for (const match of code.matchAll(MODULE_SPECIFIER_PATTERN)) {
|
|
134
|
+
const specifier = match[1] ?? match[2] ?? null;
|
|
135
|
+
if (!specifier) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (target === "client" && isBareSpecifier(specifier)) {
|
|
140
|
+
const sourceMetadata = moduleMetadata.get(sourcePath) ?? {
|
|
141
|
+
moduleImports: new Set(),
|
|
142
|
+
vendorImports: new Set(),
|
|
143
|
+
styleImports: new Set(),
|
|
144
|
+
assetImports: new Set(),
|
|
145
|
+
};
|
|
146
|
+
sourceMetadata.vendorImports.add(specifier);
|
|
147
|
+
moduleMetadata.set(sourcePath, sourceMetadata);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!isRelativeSpecifier(specifier)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const resolvedImportPath = await resolveImportPath(sourcePath, specifier);
|
|
156
|
+
if (!resolvedImportPath) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const importerOutputPath = toOutputPath(projectRoot, outputRoot, sourcePath);
|
|
161
|
+
let compiledImportPath = null;
|
|
162
|
+
|
|
163
|
+
if (shouldCompileModule(resolvedImportPath)) {
|
|
164
|
+
compiledImportPath = await compileModule(resolvedImportPath);
|
|
165
|
+
} else if (isStaticAssetPath(resolvedImportPath)) {
|
|
166
|
+
const relativeAssetPath = path.relative(projectRoot, resolvedImportPath);
|
|
167
|
+
const assetOutputPath = path.join(outputRoot, relativeAssetPath);
|
|
168
|
+
const stubOutputPath = `${assetOutputPath}.mjs`;
|
|
169
|
+
|
|
170
|
+
await ensureDirectory(path.dirname(stubOutputPath));
|
|
171
|
+
await fs.writeFile(
|
|
172
|
+
stubOutputPath,
|
|
173
|
+
createStaticAssetStubSource(relativeAssetPath, mode, target, staticAssetPublicUrls),
|
|
174
|
+
"utf8",
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (target === "client") {
|
|
178
|
+
await ensureDirectory(path.dirname(assetOutputPath));
|
|
179
|
+
await fs.copyFile(resolvedImportPath, assetOutputPath);
|
|
180
|
+
const sourceMetadata = moduleMetadata.get(sourcePath) ?? {
|
|
181
|
+
moduleImports: new Set(),
|
|
182
|
+
vendorImports: new Set(),
|
|
183
|
+
styleImports: new Set(),
|
|
184
|
+
assetImports: new Set(),
|
|
185
|
+
};
|
|
186
|
+
if (isStyleAssetPath(resolvedImportPath)) {
|
|
187
|
+
sourceMetadata.styleImports.add(relativeAssetPath.split(path.sep).join("/"));
|
|
188
|
+
} else {
|
|
189
|
+
sourceMetadata.assetImports.add(relativeAssetPath.split(path.sep).join("/"));
|
|
190
|
+
}
|
|
191
|
+
moduleMetadata.set(sourcePath, sourceMetadata);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
compiledImportPath = stubOutputPath;
|
|
195
|
+
} else {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const replacementPath = path.relative(
|
|
200
|
+
path.dirname(importerOutputPath),
|
|
201
|
+
compiledImportPath,
|
|
202
|
+
);
|
|
203
|
+
if (target === "client" && shouldCompileModule(resolvedImportPath)) {
|
|
204
|
+
const sourceMetadata = moduleMetadata.get(sourcePath) ?? {
|
|
205
|
+
moduleImports: new Set(),
|
|
206
|
+
vendorImports: new Set(),
|
|
207
|
+
styleImports: new Set(),
|
|
208
|
+
assetImports: new Set(),
|
|
209
|
+
};
|
|
210
|
+
sourceMetadata.moduleImports.add(
|
|
211
|
+
path.relative(outputRoot, compiledImportPath).split(path.sep).join("/"),
|
|
212
|
+
);
|
|
213
|
+
moduleMetadata.set(sourcePath, sourceMetadata);
|
|
214
|
+
}
|
|
215
|
+
let normalizedReplacement = replacementPath.startsWith(".")
|
|
216
|
+
? replacementPath
|
|
217
|
+
: `./${replacementPath}`;
|
|
218
|
+
if (
|
|
219
|
+
target === "server"
|
|
220
|
+
&& mode === "development"
|
|
221
|
+
&& typeof serverImportQuery === "string"
|
|
222
|
+
&& serverImportQuery.length > 0
|
|
223
|
+
) {
|
|
224
|
+
normalizedReplacement = `${normalizedReplacement}?${serverImportQuery}`;
|
|
225
|
+
}
|
|
226
|
+
const quotedSpecifierIndex = match.index + match[0].indexOf(specifier);
|
|
227
|
+
const replacementValue = normalizedReplacement.split(path.sep).join("/");
|
|
228
|
+
if (replacementValue !== specifier) {
|
|
229
|
+
didRewrite = true;
|
|
230
|
+
magicSource.update(
|
|
231
|
+
quotedSpecifierIndex,
|
|
232
|
+
quotedSpecifierIndex + specifier.length,
|
|
233
|
+
replacementValue,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const rewrittenCode = magicSource.toString();
|
|
239
|
+
if (!sourceMaps || !didRewrite) {
|
|
240
|
+
return {
|
|
241
|
+
code: rewrittenCode,
|
|
242
|
+
map: sourceMaps ? inputSourceMap ?? null : null,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const intermediateSourceId = `${sourcePath.split(path.sep).join("/")}#evolit-transform`;
|
|
247
|
+
const rewrittenMap = magicSource.generateMap({
|
|
248
|
+
hires: true,
|
|
249
|
+
includeContent: false,
|
|
250
|
+
source: intermediateSourceId,
|
|
251
|
+
file: outputPath.split(path.sep).join("/"),
|
|
252
|
+
});
|
|
253
|
+
const composedMap = inputSourceMap
|
|
254
|
+
? remapping(rewrittenMap.toString(), (source) => (
|
|
255
|
+
// MagicString serializes the source relative to `file`, so compare the
|
|
256
|
+
// stable suffix rather than the absolute path we supplied above.
|
|
257
|
+
source.endsWith("#evolit-transform") ? inputSourceMap : null
|
|
258
|
+
))
|
|
259
|
+
: rewrittenMap;
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
code: rewrittenCode,
|
|
263
|
+
map: composedMap,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function compileModuleGraph(entryPath, options = {}) {
|
|
268
|
+
const {
|
|
269
|
+
projectRoot,
|
|
270
|
+
mode = "development",
|
|
271
|
+
sourceMaps = mode === "development",
|
|
272
|
+
ssr = false,
|
|
273
|
+
target = "server",
|
|
274
|
+
staticAssetPublicUrls = null,
|
|
275
|
+
} = options;
|
|
276
|
+
|
|
277
|
+
const outputRoot = getTypedOutputRoot(projectRoot, mode, target);
|
|
278
|
+
const visited = new Map();
|
|
279
|
+
const moduleMetadata = new Map();
|
|
280
|
+
const serverImportQuery = target === "server" && mode === "development"
|
|
281
|
+
? `t=${Date.now()}`
|
|
282
|
+
: null;
|
|
283
|
+
|
|
284
|
+
async function compileModule(sourcePath) {
|
|
285
|
+
if (visited.has(sourcePath)) {
|
|
286
|
+
return visited.get(sourcePath);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const outputPath = toOutputPath(projectRoot, outputRoot, sourcePath);
|
|
290
|
+
visited.set(sourcePath, outputPath);
|
|
291
|
+
|
|
292
|
+
await ensureDirectory(path.dirname(outputPath));
|
|
293
|
+
const source = await fs.readFile(sourcePath, "utf8");
|
|
294
|
+
const transformed = await transformLitsx(source, {
|
|
295
|
+
filename: sourcePath,
|
|
296
|
+
sourceMaps,
|
|
297
|
+
ssr,
|
|
298
|
+
});
|
|
299
|
+
const rewritten = await rewriteRelativeSpecifiers({
|
|
300
|
+
projectRoot,
|
|
301
|
+
outputRoot,
|
|
302
|
+
sourcePath,
|
|
303
|
+
code: transformed.code,
|
|
304
|
+
compileModule,
|
|
305
|
+
moduleMetadata,
|
|
306
|
+
mode,
|
|
307
|
+
target,
|
|
308
|
+
staticAssetPublicUrls,
|
|
309
|
+
serverImportQuery,
|
|
310
|
+
sourceMaps,
|
|
311
|
+
outputPath,
|
|
312
|
+
inputSourceMap: transformed.map ?? null,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await fs.writeFile(outputPath, rewritten.code, "utf8");
|
|
316
|
+
if (target === "client") {
|
|
317
|
+
const metadata = moduleMetadata.get(sourcePath);
|
|
318
|
+
if (metadata) {
|
|
319
|
+
await fs.writeFile(
|
|
320
|
+
`${outputPath}.meta.json`,
|
|
321
|
+
`${JSON.stringify({
|
|
322
|
+
moduleImports: [...(metadata.moduleImports ?? [])].sort(),
|
|
323
|
+
vendorImports: [...(metadata.vendorImports ?? [])].sort(),
|
|
324
|
+
styleImports: [...(metadata.styleImports ?? [])].sort(),
|
|
325
|
+
assetImports: [...(metadata.assetImports ?? [])].sort(),
|
|
326
|
+
}, null, 2)}\n`,
|
|
327
|
+
"utf8",
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (rewritten.map && sourceMaps) {
|
|
332
|
+
await fs.writeFile(`${outputPath}.map`, JSON.stringify(rewritten.map), "utf8");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return outputPath;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
entrypoint: await compileModule(entryPath),
|
|
340
|
+
outputRoot,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function importCompiledModule(entryPath, options = {}) {
|
|
345
|
+
const { entrypoint } = await compileModuleGraph(entryPath, options);
|
|
346
|
+
const moduleUrl = new URL(pathToFileURL(entrypoint).href);
|
|
347
|
+
|
|
348
|
+
if (options.mode === "development") {
|
|
349
|
+
moduleUrl.searchParams.set("t", String(Date.now()));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return import(moduleUrl.href);
|
|
353
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { pathExists } from "./fs-utils.js";
|
|
4
|
+
|
|
5
|
+
const CONFIG_FILENAME = "evolit.config.js";
|
|
6
|
+
|
|
7
|
+
export async function loadEvolitConfig(projectRoot) {
|
|
8
|
+
const configPath = path.join(projectRoot, CONFIG_FILENAME);
|
|
9
|
+
if (!(await pathExists(configPath))) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const moduleRecord = await import(pathToFileURL(configPath).href);
|
|
14
|
+
const config = moduleRecord.default ?? {};
|
|
15
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
16
|
+
throw new Error("Expected evolit.config.js to export a default object.");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return config;
|
|
20
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const APP_DIRECTORY = "app";
|
|
2
|
+
export const INTERNAL_DIRECTORY = ".evolit";
|
|
3
|
+
export const BUILD_DIRECTORY = "build";
|
|
4
|
+
export const DEV_DIRECTORY = "dev";
|
|
5
|
+
export const SERVER_DIRECTORY = "server";
|
|
6
|
+
export const CLIENT_DIRECTORY = "client";
|
|
7
|
+
export const SHARED_DIRECTORY = "shared";
|
|
8
|
+
export const STATIC_DIRECTORY = "static";
|
|
9
|
+
export const ROUTE_CACHE_DIRECTORY = "route-cache";
|
|
10
|
+
export const MANIFEST_FILENAME = "manifest.json";
|
|
11
|
+
export const DEPLOY_ROUTES_MANIFEST_FILENAME = "deploy-routes.json";
|
|
12
|
+
export const DEPLOY_ASSETS_MANIFEST_FILENAME = "deploy-assets.json";
|
|
13
|
+
export const DEPLOY_SERVER_MANIFEST_FILENAME = "deploy-server.json";
|
|
14
|
+
|
|
15
|
+
export const MODULE_EXTENSIONS = [
|
|
16
|
+
".litsx",
|
|
17
|
+
".litsx.jsx",
|
|
18
|
+
".tsx",
|
|
19
|
+
".ts",
|
|
20
|
+
".jsx",
|
|
21
|
+
".js",
|
|
22
|
+
".mjs",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export const ROUTE_HANDLER_EXTENSIONS = [
|
|
26
|
+
".tsx",
|
|
27
|
+
".ts",
|
|
28
|
+
".jsx",
|
|
29
|
+
".js",
|
|
30
|
+
".mjs",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const STATIC_ASSET_EXTENSIONS = [
|
|
34
|
+
".css",
|
|
35
|
+
".svg",
|
|
36
|
+
".png",
|
|
37
|
+
".jpg",
|
|
38
|
+
".jpeg",
|
|
39
|
+
".gif",
|
|
40
|
+
".webp",
|
|
41
|
+
".avif",
|
|
42
|
+
".ico",
|
|
43
|
+
".woff",
|
|
44
|
+
".woff2",
|
|
45
|
+
".ttf",
|
|
46
|
+
".otf",
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
export const CLIENT_ASSET_MANIFEST_VERSION = 1;
|