xpine 0.0.20 → 0.0.22
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/TODO +0 -2
- package/dist/index.js +357 -128
- package/dist/src/build/esbuild/addDotJS.d.ts +5 -0
- package/dist/src/build/esbuild/addDotJS.d.ts.map +1 -0
- package/dist/src/build/esbuild/getDataFiles.d.ts +5 -0
- package/dist/src/build/esbuild/getDataFiles.d.ts.map +1 -0
- package/dist/src/build/esbuild/transformTSXFiles.d.ts +6 -0
- package/dist/src/build/esbuild/transformTSXFiles.d.ts.map +1 -0
- package/dist/src/express.d.ts +3 -0
- package/dist/src/express.d.ts.map +1 -1
- package/dist/src/runDevServer.d.ts.map +1 -1
- package/dist/src/scripts/build.d.ts +5 -1
- package/dist/src/scripts/build.d.ts.map +1 -1
- package/dist/src/scripts/xpine-build.js +249 -104
- package/dist/src/scripts/xpine-dev.js +294 -114
- package/dist/src/static/spa.js +2 -1
- package/dist/src/util/config-file.d.ts +6 -0
- package/dist/src/util/config-file.d.ts.map +1 -0
- package/dist/src/util/constants.d.ts +3 -0
- package/dist/src/util/constants.d.ts.map +1 -0
- package/dist/src/util/get-config.d.ts.map +1 -1
- package/dist/src/util/postcss/remove-layers.d.ts +1 -1
- package/dist/src/util/regex.d.ts +11 -0
- package/dist/src/util/regex.d.ts.map +1 -0
- package/dist/src/util/require.d.ts.map +1 -1
- package/dist/types.d.ts +31 -2
- package/dist/types.d.ts.map +1 -1
- package/eslint.config.mjs +27 -0
- package/package.json +18 -5
- package/tests/e2e/alpine.spec.ts +14 -0
- package/tests/e2e/app-build.spec.ts +73 -0
- package/tests/e2e/spa.spec.ts +23 -0
- package/tests/e2e/tests-examples/demo-todo-app.spec.ts +437 -0
- package/tests/eslint.config.mjs +27 -0
- package/tests/package-lock.json +8941 -0
- package/tests/package.json +89 -0
- package/tests/playwright.config.ts +81 -0
- package/tests/tsconfig.json +46 -0
- package/tests/xpine.config.mjs +1 -0
- package/types.ts +34 -2
package/dist/index.js
CHANGED
|
@@ -6,11 +6,10 @@ import EventEmitter from "events";
|
|
|
6
6
|
import chokidar from "chokidar";
|
|
7
7
|
|
|
8
8
|
// src/scripts/build.ts
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import path5 from "path";
|
|
10
|
+
import fs5 from "fs-extra";
|
|
11
11
|
import { build } from "esbuild";
|
|
12
|
-
import
|
|
13
|
-
import ts2 from "typescript";
|
|
12
|
+
import ts3 from "typescript";
|
|
14
13
|
|
|
15
14
|
// src/build/typescript-builder.ts
|
|
16
15
|
import ts from "typescript";
|
|
@@ -114,17 +113,6 @@ function removeClientScriptInTSXFile(pathName, source) {
|
|
|
114
113
|
clientDataStart
|
|
115
114
|
};
|
|
116
115
|
}
|
|
117
|
-
function createStaticFile(pathName, source) {
|
|
118
|
-
source.forEachChild((child) => {
|
|
119
|
-
if (child.kind === ts.SyntaxKind.ExpressionStatement) {
|
|
120
|
-
const text = child.getText(source);
|
|
121
|
-
const cleanedText = text.replace(/["';]/g, "");
|
|
122
|
-
if (cleanedText === "xpine-static") {
|
|
123
|
-
console.log("make this file static", pathName);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
116
|
|
|
129
117
|
// src/scripts/build.ts
|
|
130
118
|
import { globSync } from "glob";
|
|
@@ -140,6 +128,10 @@ var require_default = createRequire(import.meta.url);
|
|
|
140
128
|
function getXPineDistDir() {
|
|
141
129
|
const dir = import.meta.dirname;
|
|
142
130
|
const splitDir = dir.split("/xpine/dist");
|
|
131
|
+
if (splitDir.length === 1) {
|
|
132
|
+
const splitDirSingle = dir.split("/xpine/");
|
|
133
|
+
return splitDirSingle[0] + "/xpine/dist";
|
|
134
|
+
}
|
|
143
135
|
return splitDir[0] + "/xpine/dist";
|
|
144
136
|
}
|
|
145
137
|
|
|
@@ -181,6 +173,9 @@ var configDefaults = {
|
|
|
181
173
|
get pagesDir() {
|
|
182
174
|
return path2.join(this.srcDir, "./pages");
|
|
183
175
|
},
|
|
176
|
+
get distPagesDir() {
|
|
177
|
+
return path2.join(this.distDir, "./pages");
|
|
178
|
+
},
|
|
184
179
|
get publicDir() {
|
|
185
180
|
return path2.join(this.srcDir, "./public");
|
|
186
181
|
},
|
|
@@ -216,21 +211,152 @@ var plugin = (opts = {}) => {
|
|
|
216
211
|
plugin.postcss = true;
|
|
217
212
|
var remove_layers_default = plugin;
|
|
218
213
|
|
|
214
|
+
// src/build/esbuild/transformTSXFiles.ts
|
|
215
|
+
import fs2 from "fs-extra";
|
|
216
|
+
import ts2 from "typescript";
|
|
217
|
+
|
|
218
|
+
// src/util/regex.ts
|
|
219
|
+
var regex_default = {
|
|
220
|
+
dotTsx: /.tsx/,
|
|
221
|
+
hasLetters: /[A-Za-z0-9]/g,
|
|
222
|
+
configFile: /\+config\.[tj]sx?/g,
|
|
223
|
+
dynamicRoutes: /(\[)(.*?)(\])/g,
|
|
224
|
+
isDynamicRoute: /\[(.*)\]/g,
|
|
225
|
+
endsWithTSX: /\.tsx$/,
|
|
226
|
+
endsWithJSX: /\.jsx$/
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// src/util/config-file.ts
|
|
230
|
+
import path3 from "path";
|
|
231
|
+
function sourcePathToDistPath(sourcePath) {
|
|
232
|
+
return sourcePath?.replace(config.srcDir, config.distDir)?.replace(/\.ts$/, ".js")?.replace(/\.tsx$/, ".js");
|
|
233
|
+
}
|
|
234
|
+
function getConfigFiles(pathName, pageConfigFiles, returnDistPaths = true) {
|
|
235
|
+
const configs = [];
|
|
236
|
+
for (const configFile of pageConfigFiles) {
|
|
237
|
+
const result = path3.relative(pathName, configFile)?.replace(regex_default.configFile, "");
|
|
238
|
+
const hasLetters = result.match(regex_default.hasLetters);
|
|
239
|
+
if (!hasLetters) {
|
|
240
|
+
configs.push(returnDistPaths ? sourcePathToDistPath(configFile) : configFile);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (!configs.length) return null;
|
|
244
|
+
return configs.sort((a, b) => b.length - a.length);
|
|
245
|
+
}
|
|
246
|
+
async function getCompleteConfig(configFiles, cacheKey) {
|
|
247
|
+
let config2 = {};
|
|
248
|
+
for (const configFile of configFiles) {
|
|
249
|
+
config2 = {
|
|
250
|
+
...(await import(configFile + `?cache=${cacheKey}`)).default,
|
|
251
|
+
...config2
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return config2;
|
|
255
|
+
}
|
|
256
|
+
function isAConfigFile(pathName) {
|
|
257
|
+
return !!pathName.match(regex_default.configFile);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/build/esbuild/transformTSXFiles.ts
|
|
261
|
+
function transformTSXFiles(componentData, pageConfigFiles) {
|
|
262
|
+
return {
|
|
263
|
+
name: "transform-tsx-files",
|
|
264
|
+
setup(build2) {
|
|
265
|
+
build2.onLoad({ filter: regex_default.dotTsx }, async (args) => {
|
|
266
|
+
const content = fs2.readFileSync(args.path, "utf-8");
|
|
267
|
+
const source = ts2.createSourceFile(
|
|
268
|
+
args.path,
|
|
269
|
+
content,
|
|
270
|
+
ts2.ScriptTarget.Latest
|
|
271
|
+
);
|
|
272
|
+
const cleanedContent = removeClientScriptInTSXFile(args.path, source);
|
|
273
|
+
const htmlImportStart = "import { html } from 'xpine';\n";
|
|
274
|
+
const newContent = `${htmlImportStart}${cleanedContent.content}`;
|
|
275
|
+
componentData.push({
|
|
276
|
+
...args,
|
|
277
|
+
contents: `${htmlImportStart}${cleanedContent.fullContent}`,
|
|
278
|
+
clientContent: cleanedContent.clientContent,
|
|
279
|
+
configFiles: isAConfigFile(args?.path) ? null : getConfigFiles(args.path, pageConfigFiles)
|
|
280
|
+
});
|
|
281
|
+
return {
|
|
282
|
+
contents: newContent,
|
|
283
|
+
loader: "tsx"
|
|
284
|
+
};
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/build/esbuild/addDotJS.ts
|
|
291
|
+
import fs3 from "fs-extra";
|
|
292
|
+
import path4 from "path";
|
|
293
|
+
import builtinModules from "builtin-modules";
|
|
294
|
+
function addDotJS(allPackages2, extensions2, isDev) {
|
|
295
|
+
const allPackagesIncludingNode = allPackages2.concat(builtinModules);
|
|
296
|
+
return {
|
|
297
|
+
name: "add-dot-js",
|
|
298
|
+
setup(build2) {
|
|
299
|
+
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
300
|
+
const hasAtSign = args.path.startsWith("@");
|
|
301
|
+
const isPackage = hasAtSign ? allPackagesIncludingNode.includes(args.path) : allPackagesIncludingNode.includes(args.path.split("/").shift());
|
|
302
|
+
if (args.importer && !isPackage) {
|
|
303
|
+
const calculatedDir = path4.join(args.resolveDir, args.path);
|
|
304
|
+
let existsAsFile = false;
|
|
305
|
+
for (const extension of extensions2) {
|
|
306
|
+
const asFile = calculatedDir + extension;
|
|
307
|
+
const exists = fs3.existsSync(asFile);
|
|
308
|
+
if (exists) existsAsFile = true;
|
|
309
|
+
}
|
|
310
|
+
let outputPath = args.path + (existsAsFile ? "" : "/index") + ".js";
|
|
311
|
+
outputPath = args.path.endsWith(".js") || args.path.endsWith(".mjs") ? args.path : outputPath;
|
|
312
|
+
return { path: outputPath + (isDev ? `?cache=${Date.now()}` : ""), external: true };
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/build/esbuild/getDataFiles.ts
|
|
320
|
+
import fs4 from "fs-extra";
|
|
321
|
+
function getDataFiles(dataFiles) {
|
|
322
|
+
return {
|
|
323
|
+
name: "get-data-files",
|
|
324
|
+
setup(build2) {
|
|
325
|
+
build2.onLoad({ filter: /\.data\.(js|mjs|ts)$/ }, (args) => {
|
|
326
|
+
const contents = fs4.readFileSync(args.path, "utf-8");
|
|
327
|
+
dataFiles.push({
|
|
328
|
+
...args,
|
|
329
|
+
contents
|
|
330
|
+
});
|
|
331
|
+
return {
|
|
332
|
+
contents,
|
|
333
|
+
loader: "ts"
|
|
334
|
+
};
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/util/constants.ts
|
|
341
|
+
var doctypeHTML = "<!DOCTYPE html>";
|
|
342
|
+
var staticComment = "<!-- static -->";
|
|
343
|
+
|
|
219
344
|
// src/scripts/build.ts
|
|
220
345
|
var extensions = [".ts", ".tsx"];
|
|
221
|
-
var packageJson = JSON.parse(
|
|
346
|
+
var packageJson = JSON.parse(fs5.readFileSync(config.packageJsonPath, "utf-8"));
|
|
222
347
|
var allPackages = Object.keys(packageJson.devDependencies).concat(Object.keys(packageJson.dependencies));
|
|
223
|
-
var allPackagesIncludingNode = allPackages.concat(builtinModules);
|
|
224
348
|
var xpineDistDir = getXPineDistDir();
|
|
225
|
-
async function buildApp(isDev = false) {
|
|
349
|
+
async function buildApp(isDev = false, removePreviousBuild = false) {
|
|
226
350
|
try {
|
|
351
|
+
if (removePreviousBuild) fs5.removeSync(config.distDir);
|
|
227
352
|
const srcDirFiles = globSync(config.srcDir + "/**/*.{js,ts,tsx,jsx}");
|
|
228
353
|
const { componentData, dataFiles } = await buildAppFiles(srcDirFiles, isDev);
|
|
229
354
|
const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
|
|
230
355
|
await buildClientSideFiles([alpineDataFile], isDev);
|
|
231
|
-
|
|
356
|
+
fs5.removeSync(config.distTempFolder);
|
|
232
357
|
await buildCSS();
|
|
233
358
|
await buildPublicFolderSymlinks();
|
|
359
|
+
if (!isDev) await buildFilesWithConfigs(componentData);
|
|
234
360
|
} catch (err) {
|
|
235
361
|
console.error("Build failed");
|
|
236
362
|
console.error(err);
|
|
@@ -239,8 +365,12 @@ async function buildApp(isDev = false) {
|
|
|
239
365
|
async function buildAppFiles(files, isDev) {
|
|
240
366
|
const componentData = [];
|
|
241
367
|
const dataFiles = [];
|
|
368
|
+
const pageConfigFiles = files.filter((file) => {
|
|
369
|
+
const fileName = file.split("/").at(-1).split(".").shift();
|
|
370
|
+
return fileName === "+config";
|
|
371
|
+
});
|
|
242
372
|
const backendFiles = files.filter((file) => extensions.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
|
|
243
|
-
|
|
373
|
+
fs5.ensureDirSync(config.distDir);
|
|
244
374
|
await build({
|
|
245
375
|
entryPoints: backendFiles,
|
|
246
376
|
format: "esm",
|
|
@@ -252,69 +382,9 @@ async function buildAppFiles(files, isDev) {
|
|
|
252
382
|
jsx: "transform",
|
|
253
383
|
minify: !isDev,
|
|
254
384
|
plugins: [
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
259
|
-
const hasAtSign = args.path.startsWith("@");
|
|
260
|
-
const isPackage = hasAtSign ? allPackagesIncludingNode.includes(args.path) : allPackagesIncludingNode.includes(args.path.split("/").shift());
|
|
261
|
-
if (args.importer && !isPackage) {
|
|
262
|
-
const calculatedDir = path3.join(args.resolveDir, args.path);
|
|
263
|
-
let existsAsFile = false;
|
|
264
|
-
for (const extension of extensions) {
|
|
265
|
-
const asFile = calculatedDir + extension;
|
|
266
|
-
const exists = fs2.existsSync(asFile);
|
|
267
|
-
if (exists) existsAsFile = true;
|
|
268
|
-
}
|
|
269
|
-
let outputPath = args.path + (existsAsFile ? "" : "/index") + ".js";
|
|
270
|
-
outputPath = args.path.endsWith(".js") || args.path.endsWith(".mjs") ? args.path : outputPath;
|
|
271
|
-
return { path: outputPath + (isDev ? `?cache=${Date.now()}` : ""), external: true };
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
name: "insert-html-banner-and-remove-client-scripts",
|
|
278
|
-
setup(build2) {
|
|
279
|
-
build2.onLoad({ filter: /.tsx/ }, (args) => {
|
|
280
|
-
const content = fs2.readFileSync(args.path, "utf-8");
|
|
281
|
-
const source = ts2.createSourceFile(
|
|
282
|
-
args.path,
|
|
283
|
-
content,
|
|
284
|
-
ts2.ScriptTarget.Latest
|
|
285
|
-
);
|
|
286
|
-
const cleanedContent = removeClientScriptInTSXFile(args.path, source);
|
|
287
|
-
createStaticFile(args.path, source);
|
|
288
|
-
const htmlImportStart = "import { html } from 'xpine';\n";
|
|
289
|
-
const newContent = `${htmlImportStart}${cleanedContent.content}`;
|
|
290
|
-
componentData.push({
|
|
291
|
-
...args,
|
|
292
|
-
contents: `${htmlImportStart}${cleanedContent.fullContent}`,
|
|
293
|
-
clientContent: cleanedContent.clientContent
|
|
294
|
-
});
|
|
295
|
-
return {
|
|
296
|
-
contents: newContent,
|
|
297
|
-
loader: "tsx"
|
|
298
|
-
};
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
name: "get-data-files",
|
|
304
|
-
setup(build2) {
|
|
305
|
-
build2.onLoad({ filter: /\.data\.(js|mjs|ts)$/ }, (args) => {
|
|
306
|
-
const contents = fs2.readFileSync(args.path, "utf-8");
|
|
307
|
-
dataFiles.push({
|
|
308
|
-
...args,
|
|
309
|
-
contents
|
|
310
|
-
});
|
|
311
|
-
return {
|
|
312
|
-
contents,
|
|
313
|
-
loader: "ts"
|
|
314
|
-
};
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
}
|
|
385
|
+
addDotJS(allPackages, extensions, isDev),
|
|
386
|
+
transformTSXFiles(componentData, pageConfigFiles),
|
|
387
|
+
getDataFiles(dataFiles)
|
|
318
388
|
]
|
|
319
389
|
});
|
|
320
390
|
await logSize(config.distDir, "app");
|
|
@@ -324,8 +394,8 @@ async function buildAppFiles(files, isDev) {
|
|
|
324
394
|
};
|
|
325
395
|
}
|
|
326
396
|
async function buildClientSideFiles(alpineDataFiles = [], isDev) {
|
|
327
|
-
const tempFilePath =
|
|
328
|
-
|
|
397
|
+
const tempFilePath = path5.join(config.distTempFolder, "./app.ts");
|
|
398
|
+
fs5.ensureFileSync(tempFilePath);
|
|
329
399
|
const pagesScriptsGlob = config.publicDir + "/scripts/pages/**/*.{js,ts}";
|
|
330
400
|
const clientFiles = globSync(
|
|
331
401
|
config.publicDir + "/**/*.{js,ts}",
|
|
@@ -354,16 +424,14 @@ async function buildClientSideFiles(alpineDataFiles = [], isDev) {
|
|
|
354
424
|
await logSize(config.distPublicDir, "client");
|
|
355
425
|
}
|
|
356
426
|
function writeDevServerClientSideCode(tempFilePath) {
|
|
357
|
-
const devServerPath =
|
|
358
|
-
const content =
|
|
359
|
-
|
|
360
|
-
` + content);
|
|
427
|
+
const devServerPath = path5.join(xpineDistDir, "./src/static/dev-server.js");
|
|
428
|
+
const content = fs5.readFileSync(devServerPath, "utf-8");
|
|
429
|
+
fs5.appendFileSync(tempFilePath, "\n" + content);
|
|
361
430
|
}
|
|
362
431
|
function writeSpaClientSideCode(tempFilePath) {
|
|
363
|
-
const spaPath =
|
|
364
|
-
const content =
|
|
365
|
-
|
|
366
|
-
` + content);
|
|
432
|
+
const spaPath = path5.join(xpineDistDir, "./src/static/spa.js");
|
|
433
|
+
const content = fs5.readFileSync(spaPath, "utf-8");
|
|
434
|
+
fs5.appendFileSync(tempFilePath, "\n" + content);
|
|
367
435
|
}
|
|
368
436
|
async function buildAlpineDataFile(componentData, dataFiles) {
|
|
369
437
|
const output = {
|
|
@@ -382,10 +450,10 @@ async function buildAlpineDataFile(componentData, dataFiles) {
|
|
|
382
450
|
};
|
|
383
451
|
const componentsAndDataFiles = componentData.concat(dataFiles);
|
|
384
452
|
for (const component of componentsAndDataFiles) {
|
|
385
|
-
const sourceFile =
|
|
453
|
+
const sourceFile = ts3.createSourceFile(
|
|
386
454
|
component.path,
|
|
387
455
|
component.contents,
|
|
388
|
-
|
|
456
|
+
ts3.ScriptTarget.Latest
|
|
389
457
|
);
|
|
390
458
|
const dataFunctionResult = findDataAttributesAndFunctions(sourceFile, sourceFile);
|
|
391
459
|
dataFunctionResults.foundDataAttributes.push(...dataFunctionResult.foundDataAttributes);
|
|
@@ -407,18 +475,18 @@ async function buildAlpineDataFile(componentData, dataFiles) {
|
|
|
407
475
|
output.code.push(`Alpine.data('${dataFunction.name}', ${dataFunction.name});`);
|
|
408
476
|
}
|
|
409
477
|
const result = output.imports.join("\n") + "\n" + output.code.join("\n") + "\n" + output.end.join("\n");
|
|
410
|
-
|
|
411
|
-
|
|
478
|
+
fs5.ensureFileSync(config.alpineDataPath);
|
|
479
|
+
fs5.writeFileSync(config.alpineDataPath, result);
|
|
412
480
|
return config.alpineDataPath;
|
|
413
481
|
}
|
|
414
482
|
async function buildCSS() {
|
|
415
483
|
const cssFiles = globSync(config.srcDir + "/**/*.css");
|
|
416
484
|
for (const file of cssFiles) {
|
|
417
|
-
const fileContents =
|
|
485
|
+
const fileContents = fs5.readFileSync(file, "utf-8");
|
|
418
486
|
const result = await postcss([tailwindPostcss(), remove_layers_default()]).process(fileContents, { from: file });
|
|
419
487
|
const newPath = file.replace(config.srcDir, config.distDir);
|
|
420
|
-
|
|
421
|
-
|
|
488
|
+
fs5.ensureFileSync(newPath);
|
|
489
|
+
fs5.writeFileSync(newPath, result.css);
|
|
422
490
|
}
|
|
423
491
|
logSize(config.distPublicDir, "css");
|
|
424
492
|
}
|
|
@@ -432,8 +500,8 @@ async function buildPublicFolderSymlinks() {
|
|
|
432
500
|
const splitNewPath = newPath.split("/");
|
|
433
501
|
splitNewPath.pop();
|
|
434
502
|
const newDir = splitNewPath.join("/");
|
|
435
|
-
|
|
436
|
-
|
|
503
|
+
fs5.ensureDirSync(newDir);
|
|
504
|
+
fs5.symlinkSync(file, newPath);
|
|
437
505
|
} catch {
|
|
438
506
|
}
|
|
439
507
|
}
|
|
@@ -444,7 +512,7 @@ async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
|
|
|
444
512
|
if (!validExtensions.find((ext) => file.endsWith(ext))) return false;
|
|
445
513
|
return {
|
|
446
514
|
file,
|
|
447
|
-
size:
|
|
515
|
+
size: fs5.statSync(file).size / (1024 * 1e3)
|
|
448
516
|
};
|
|
449
517
|
}).filter(Boolean);
|
|
450
518
|
const totalSize = fileSizes.reduce((total, current) => {
|
|
@@ -452,17 +520,91 @@ async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
|
|
|
452
520
|
}, 0);
|
|
453
521
|
console.info(`[${totalSize.toFixed(3)} MB] Built ${type}`);
|
|
454
522
|
}
|
|
523
|
+
async function buildFilesWithConfigs(componentData) {
|
|
524
|
+
const now = Date.now();
|
|
525
|
+
const componentsWithConfigs = componentData.filter((item) => item.configFiles);
|
|
526
|
+
for (const component of componentsWithConfigs) {
|
|
527
|
+
let config2 = await getCompleteConfig(component.configFiles, now);
|
|
528
|
+
const builtComponentPath = sourcePathToDistPath(component.path);
|
|
529
|
+
const componentImport = await import(builtComponentPath + `?cache=${Date.now()}`);
|
|
530
|
+
if (componentImport?.config) {
|
|
531
|
+
config2 = {
|
|
532
|
+
...config2,
|
|
533
|
+
...componentImport.config
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
if (config2?.staticPaths) buildStaticFiles(config2, component, componentImport, builtComponentPath);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async function buildStaticFiles(config2, component, componentImport, builtComponentPath) {
|
|
540
|
+
if (!config2?.staticPaths) return;
|
|
541
|
+
let componentFileName = component.path.split("/").pop().replace(regex_default.endsWithJSX, "").replace(regex_default.endsWithTSX, "");
|
|
542
|
+
const isDynamicRoute = component.path.match(regex_default.isDynamicRoute);
|
|
543
|
+
if (isDynamicRoute) {
|
|
544
|
+
componentFileName = component.path.split("/").filter((dir) => dir.match(regex_default.isDynamicRoute)).join("/").replace(regex_default.endsWithJSX, "").replace(regex_default.endsWithTSX, "");
|
|
545
|
+
}
|
|
546
|
+
const componentDynamicPaths = getComponentDynamicPaths(componentFileName);
|
|
547
|
+
const componentFn = componentImport.default;
|
|
548
|
+
const outputPath = componentDynamicPaths?.length ? componentDynamicPaths.reduce((total, current) => {
|
|
549
|
+
return total.replace(`/[${current}]`, "");
|
|
550
|
+
}, path5.dirname(builtComponentPath)) : path5.dirname(builtComponentPath);
|
|
551
|
+
if (typeof config2?.staticPaths === "boolean") {
|
|
552
|
+
try {
|
|
553
|
+
const req = { params: {} };
|
|
554
|
+
const data = config2?.data ? await config2.data(req) : null;
|
|
555
|
+
const staticComponentOutput = await componentFn({ data });
|
|
556
|
+
fs5.writeFileSync(
|
|
557
|
+
path5.join(outputPath, "./index.html"),
|
|
558
|
+
doctypeHTML + (config2?.wrapper ? await config2.wrapper({ req, children: staticComponentOutput, config: config2, data }) : staticComponentOutput) + staticComment
|
|
559
|
+
);
|
|
560
|
+
} catch (err) {
|
|
561
|
+
console.error(err);
|
|
562
|
+
console.log("Could not build static component", component.path);
|
|
563
|
+
}
|
|
564
|
+
} else if (typeof config2?.staticPaths === "function") {
|
|
565
|
+
const dynamicPaths = await config2.staticPaths();
|
|
566
|
+
for (const dynamicPath of dynamicPaths) {
|
|
567
|
+
try {
|
|
568
|
+
const req = {
|
|
569
|
+
params: {
|
|
570
|
+
...componentDynamicPaths?.length ? dynamicPath : {}
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
const data = config2?.data ? await config2.data(req) : null;
|
|
574
|
+
const staticComponentOutput = await componentFn({ req, data });
|
|
575
|
+
const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
|
|
576
|
+
fs5.ensureDirSync(updatedOutDir);
|
|
577
|
+
fs5.writeFileSync(
|
|
578
|
+
path5.join(updatedOutDir, "./index.html"),
|
|
579
|
+
doctypeHTML + (config2?.wrapper ? await config2.wrapper({ req, children: staticComponentOutput, config: config2, data }) : staticComponentOutput) + staticComment
|
|
580
|
+
);
|
|
581
|
+
} catch (err) {
|
|
582
|
+
console.log("Could not build static component", component.path);
|
|
583
|
+
console.error(err);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function getComponentDynamicPaths(componentPath) {
|
|
589
|
+
const matches = [...componentPath.matchAll(regex_default.dynamicRoutes)];
|
|
590
|
+
if (!matches?.length) return null;
|
|
591
|
+
const output = [];
|
|
592
|
+
for (const match of matches) {
|
|
593
|
+
output.push(match[2]);
|
|
594
|
+
}
|
|
595
|
+
return output;
|
|
596
|
+
}
|
|
455
597
|
|
|
456
598
|
// src/runDevServer.ts
|
|
457
|
-
import
|
|
599
|
+
import path7 from "path";
|
|
458
600
|
|
|
459
601
|
// src/util/env.ts
|
|
460
602
|
import dotenv from "dotenv";
|
|
461
|
-
import
|
|
603
|
+
import path6 from "path";
|
|
462
604
|
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
|
|
463
605
|
async function setupEnv() {
|
|
464
606
|
if (process.env.HAS_SETUP_ENV) return;
|
|
465
|
-
dotenv.config({ path:
|
|
607
|
+
dotenv.config({ path: path6.join(config.rootDir, `./.env.${process.env.STAGE || "dev"}`) });
|
|
466
608
|
await loadSecretsManagerSecrets();
|
|
467
609
|
process.env.HAS_SETUP_ENV = "true";
|
|
468
610
|
}
|
|
@@ -491,26 +633,33 @@ async function loadSecretsManagerSecrets() {
|
|
|
491
633
|
await setupEnv();
|
|
492
634
|
async function runDevServer() {
|
|
493
635
|
process.env.NODE_ENV = "development";
|
|
494
|
-
const
|
|
636
|
+
const rebuildEmitter = createRebuildEmitter();
|
|
495
637
|
await buildApp(true);
|
|
638
|
+
const startServer = (await import(config.serverDistAppPath + `?cache=${Date.now()}`)).default;
|
|
496
639
|
let appServer = await startServer();
|
|
497
640
|
const watcher = chokidar.watch(config.srcDir, {
|
|
498
641
|
ignoreInitial: true,
|
|
499
642
|
// Ignore map and prisma files
|
|
500
|
-
ignored: (pathName) => pathName.endsWith(".map") || pathName.startsWith(
|
|
643
|
+
ignored: (pathName) => pathName.endsWith(".map") || pathName.startsWith(path7.join(config.serverDir, "./prisma"))
|
|
501
644
|
});
|
|
502
|
-
watcher.on("all", async (event,
|
|
503
|
-
const shouldReloadServer =
|
|
645
|
+
watcher.on("all", async (event, path9) => {
|
|
646
|
+
const shouldReloadServer = path9.startsWith(config.serverDir) && !path9.startsWith(config.runDir) || ["add", "unlink"].includes(event) && path9.startsWith(config.pagesDir);
|
|
504
647
|
if (shouldReloadServer) {
|
|
505
|
-
await appServer.server
|
|
506
|
-
|
|
507
|
-
const startServer2 = (await import(config.serverDistAppPath + `?cache=${Date.now()}`)).default;
|
|
508
|
-
appServer = await startServer2();
|
|
648
|
+
await asyncServerClose(appServer.server);
|
|
649
|
+
rebuildEmitter.emit("rebuild-server");
|
|
509
650
|
return;
|
|
510
651
|
}
|
|
511
652
|
await buildApp(true);
|
|
512
653
|
refreshEmitter.emit("refresh");
|
|
513
654
|
});
|
|
655
|
+
rebuildEmitter.on("done", async () => {
|
|
656
|
+
await rebuildServer();
|
|
657
|
+
});
|
|
658
|
+
async function rebuildServer() {
|
|
659
|
+
await buildApp(true);
|
|
660
|
+
const startServer2 = (await import(config.serverDistAppPath + `?cache=${Date.now()}`)).default;
|
|
661
|
+
appServer = await startServer2();
|
|
662
|
+
}
|
|
514
663
|
const wsApp = express();
|
|
515
664
|
const wsServer = http.createServer(wsApp);
|
|
516
665
|
expressWs(wsApp, wsServer);
|
|
@@ -530,6 +679,37 @@ async function runDevServer() {
|
|
|
530
679
|
console.info(`Dev server listening on port ${wsPort}`);
|
|
531
680
|
});
|
|
532
681
|
}
|
|
682
|
+
function asyncServerClose(server) {
|
|
683
|
+
return new Promise((resolve, reject) => {
|
|
684
|
+
server.close();
|
|
685
|
+
resolve(true);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
function createRebuildEmitter(delay = 500) {
|
|
689
|
+
class RebuildEmitter extends EventEmitter {
|
|
690
|
+
}
|
|
691
|
+
;
|
|
692
|
+
const rebuildEmitter = new RebuildEmitter();
|
|
693
|
+
let start = 0;
|
|
694
|
+
let hasEmitted = false;
|
|
695
|
+
rebuildEmitter.on("rebuild-server", () => {
|
|
696
|
+
hasEmitted = false;
|
|
697
|
+
trigger();
|
|
698
|
+
});
|
|
699
|
+
function trigger() {
|
|
700
|
+
start = Date.now();
|
|
701
|
+
const interval = setInterval(() => {
|
|
702
|
+
if (hasEmitted) return;
|
|
703
|
+
const now = Date.now();
|
|
704
|
+
if (now - start >= delay) {
|
|
705
|
+
rebuildEmitter.emit("done");
|
|
706
|
+
clearInterval(interval);
|
|
707
|
+
hasEmitted = true;
|
|
708
|
+
}
|
|
709
|
+
}, 100);
|
|
710
|
+
}
|
|
711
|
+
return rebuildEmitter;
|
|
712
|
+
}
|
|
533
713
|
|
|
534
714
|
// src/express.ts
|
|
535
715
|
import express2 from "express";
|
|
@@ -574,33 +754,48 @@ function getTokenFromRequest(req) {
|
|
|
574
754
|
|
|
575
755
|
// src/express.ts
|
|
576
756
|
import requestIP from "request-ip";
|
|
577
|
-
|
|
757
|
+
import fs6 from "fs-extra";
|
|
758
|
+
import path8 from "path";
|
|
578
759
|
async function createRouter() {
|
|
760
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
579
761
|
const methods = ["get", "post", "put", "patch", "delete"];
|
|
580
762
|
const router = express2.Router();
|
|
581
763
|
const routes = globSync2(config.pagesDir + "/**/*.{tsx,ts}");
|
|
582
764
|
const routeMap = routes.map((route) => {
|
|
765
|
+
const routeFormatted = route.split(config.pagesDir).pop().replace(".tsx", "").replace(".js", "").replace(".ts", "");
|
|
766
|
+
if (routeFormatted.endsWith("+config")) return;
|
|
767
|
+
const routeFormattedWithIndex = routeFormatted.replace(/\/index$/g, "");
|
|
583
768
|
return {
|
|
584
|
-
route:
|
|
769
|
+
route: routeFormattedWithIndex,
|
|
585
770
|
path: route.replace(config.srcDir, config.distDir).replace(".tsx", ".js").replace(".ts", ".js"),
|
|
586
771
|
originalRoute: route
|
|
587
772
|
};
|
|
588
|
-
});
|
|
773
|
+
}).filter(Boolean);
|
|
589
774
|
const routeResults = [];
|
|
775
|
+
const configFiles = globSync2(config.pagesDir + "/**/+config.{tsx,ts}");
|
|
590
776
|
for (const route of routeMap) {
|
|
591
777
|
const isJSX = route.originalRoute.endsWith(".tsx") || route.originalRoute.endsWith(".jsx");
|
|
592
|
-
const
|
|
593
|
-
const slugRoute = route.route.toLowerCase().replace(/[ ]/g, "");
|
|
778
|
+
const slugRoute = route.route.replace(/[ ]/g, "");
|
|
594
779
|
const foundMethod = methods.find((method) => slugRoute.endsWith(`.${method}`));
|
|
595
|
-
const isDynamicRoute = slugRoute.match(
|
|
780
|
+
const isDynamicRoute = slugRoute.match(regex_default.isDynamicRoute);
|
|
596
781
|
let formattedRouteItem = slugRoute;
|
|
597
782
|
if (foundMethod) formattedRouteItem = formattedRouteItem.split(".").shift();
|
|
598
783
|
if (isDynamicRoute) {
|
|
599
|
-
const result = [...formattedRouteItem.matchAll(
|
|
784
|
+
const result = [...formattedRouteItem.matchAll(regex_default.dynamicRoutes)];
|
|
600
785
|
for (const match of result) {
|
|
601
786
|
formattedRouteItem = formattedRouteItem.replace(match[0], ":" + match[2]);
|
|
602
787
|
}
|
|
603
788
|
}
|
|
789
|
+
const componentImport = isDev ? null : await import(route.path);
|
|
790
|
+
const componentFn = componentImport?.default;
|
|
791
|
+
const configFilePaths = getConfigFiles(route.originalRoute, configFiles);
|
|
792
|
+
let config2 = configFilePaths && await getCompleteConfig(configFilePaths, Date.now());
|
|
793
|
+
if (componentImport?.config) {
|
|
794
|
+
config2 = {
|
|
795
|
+
...config2,
|
|
796
|
+
...componentImport.config
|
|
797
|
+
};
|
|
798
|
+
}
|
|
604
799
|
routeResults.push({
|
|
605
800
|
formattedRouteItem,
|
|
606
801
|
foundMethod,
|
|
@@ -608,19 +803,38 @@ async function createRouter() {
|
|
|
608
803
|
});
|
|
609
804
|
router[foundMethod || "get"](formattedRouteItem, async (req, res) => {
|
|
610
805
|
try {
|
|
611
|
-
|
|
806
|
+
const staticPath = routeHasStaticPath(formattedRouteItem, req.params);
|
|
807
|
+
if (staticPath && !isDev) {
|
|
808
|
+
res.sendFile(staticPath);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (componentFn && !isDev) {
|
|
612
812
|
if (isJSX) {
|
|
613
|
-
|
|
813
|
+
const data = config2?.data ? await config2.data(req) : null;
|
|
814
|
+
const originalResult = await componentFn({ req, res, data });
|
|
815
|
+
const output = config2?.wrapper ? await config2.wrapper({ req, children: originalResult, config: config2, data }) : originalResult;
|
|
816
|
+
res.send(doctypeHTML + output);
|
|
614
817
|
} else {
|
|
615
|
-
await
|
|
818
|
+
await componentFn(req, res);
|
|
616
819
|
}
|
|
617
820
|
return;
|
|
618
821
|
}
|
|
619
|
-
const
|
|
822
|
+
const componentImportDev = await import(route.path + `?cache=${Date.now()}`);
|
|
823
|
+
const componentFnDev = componentImportDev.default;
|
|
620
824
|
if (isJSX) {
|
|
621
|
-
|
|
825
|
+
let config3 = configFilePaths && await getCompleteConfig(configFilePaths, Date.now());
|
|
826
|
+
if (componentImportDev?.config) {
|
|
827
|
+
config3 = {
|
|
828
|
+
...config3,
|
|
829
|
+
...componentImportDev.config
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
const data = config3?.data ? await config3.data(req) : null;
|
|
833
|
+
const originalResult = await componentFnDev({ req, res, data });
|
|
834
|
+
const output = config3?.wrapper ? await config3.wrapper({ req, children: originalResult, config: config3, data }) : originalResult;
|
|
835
|
+
res.send(doctypeHTML + output);
|
|
622
836
|
} else {
|
|
623
|
-
await
|
|
837
|
+
await componentFnDev(req, res);
|
|
624
838
|
}
|
|
625
839
|
} catch (err) {
|
|
626
840
|
console.error(err);
|
|
@@ -655,7 +869,7 @@ async function createXPineRouter(app, beforeErrorRoute) {
|
|
|
655
869
|
router(req, res, next);
|
|
656
870
|
});
|
|
657
871
|
const found404 = routeResults?.find((item) => item?.formattedRouteItem === "/404");
|
|
658
|
-
const import404 = process.env.NODE_ENV === "development" ? null : (await import(found404.route.path)).default;
|
|
872
|
+
const import404 = process.env.NODE_ENV === "development" || !found404 ? null : (await import(found404.route.path)).default;
|
|
659
873
|
if (beforeErrorRoute) beforeErrorRoute(app);
|
|
660
874
|
app.use(async function(req, res) {
|
|
661
875
|
res.status(404);
|
|
@@ -669,6 +883,17 @@ async function createXPineRouter(app, beforeErrorRoute) {
|
|
|
669
883
|
}
|
|
670
884
|
});
|
|
671
885
|
}
|
|
886
|
+
function routeHasStaticPath(route, params) {
|
|
887
|
+
const paramEntries = Object.entries(params);
|
|
888
|
+
let routeToStaticPath = route;
|
|
889
|
+
for (const [key, value] of paramEntries) {
|
|
890
|
+
routeToStaticPath = routeToStaticPath.replace(`:${key}`, value);
|
|
891
|
+
}
|
|
892
|
+
routeToStaticPath += "/index.html";
|
|
893
|
+
const outputPath = path8.join(config.distPagesDir, routeToStaticPath);
|
|
894
|
+
if (fs6.existsSync(outputPath)) return outputPath;
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
672
897
|
|
|
673
898
|
// src/util/html.ts
|
|
674
899
|
var html = class {
|
|
@@ -699,14 +924,18 @@ export {
|
|
|
699
924
|
JSXRuntime,
|
|
700
925
|
buildApp,
|
|
701
926
|
buildCSS,
|
|
927
|
+
buildFilesWithConfigs,
|
|
702
928
|
buildPublicFolderSymlinks,
|
|
929
|
+
buildStaticFiles,
|
|
703
930
|
config,
|
|
704
931
|
createRouter,
|
|
705
932
|
createXPineRouter,
|
|
706
933
|
fromRoot,
|
|
934
|
+
getComponentDynamicPaths,
|
|
707
935
|
getTokenFromRequest,
|
|
708
936
|
html,
|
|
709
937
|
logSize,
|
|
938
|
+
routeHasStaticPath,
|
|
710
939
|
runDevServer,
|
|
711
940
|
setupEnv,
|
|
712
941
|
signUser,
|