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
|
@@ -8,11 +8,10 @@ import EventEmitter from "events";
|
|
|
8
8
|
import chokidar from "chokidar";
|
|
9
9
|
|
|
10
10
|
// src/scripts/build.ts
|
|
11
|
-
import
|
|
12
|
-
import
|
|
11
|
+
import path5 from "path";
|
|
12
|
+
import fs5 from "fs-extra";
|
|
13
13
|
import { build } from "esbuild";
|
|
14
|
-
import
|
|
15
|
-
import ts2 from "typescript";
|
|
14
|
+
import ts3 from "typescript";
|
|
16
15
|
|
|
17
16
|
// src/build/typescript-builder.ts
|
|
18
17
|
import ts from "typescript";
|
|
@@ -116,17 +115,6 @@ function removeClientScriptInTSXFile(pathName, source) {
|
|
|
116
115
|
clientDataStart
|
|
117
116
|
};
|
|
118
117
|
}
|
|
119
|
-
function createStaticFile(pathName, source) {
|
|
120
|
-
source.forEachChild((child) => {
|
|
121
|
-
if (child.kind === ts.SyntaxKind.ExpressionStatement) {
|
|
122
|
-
const text = child.getText(source);
|
|
123
|
-
const cleanedText = text.replace(/["';]/g, "");
|
|
124
|
-
if (cleanedText === "xpine-static") {
|
|
125
|
-
console.log("make this file static", pathName);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
118
|
|
|
131
119
|
// src/scripts/build.ts
|
|
132
120
|
import { globSync } from "glob";
|
|
@@ -142,6 +130,10 @@ var require_default = createRequire(import.meta.url);
|
|
|
142
130
|
function getXPineDistDir() {
|
|
143
131
|
const dir = import.meta.dirname;
|
|
144
132
|
const splitDir = dir.split("/xpine/dist");
|
|
133
|
+
if (splitDir.length === 1) {
|
|
134
|
+
const splitDirSingle = dir.split("/xpine/");
|
|
135
|
+
return splitDirSingle[0] + "/xpine/dist";
|
|
136
|
+
}
|
|
145
137
|
return splitDir[0] + "/xpine/dist";
|
|
146
138
|
}
|
|
147
139
|
|
|
@@ -183,6 +175,9 @@ var configDefaults = {
|
|
|
183
175
|
get pagesDir() {
|
|
184
176
|
return path2.join(this.srcDir, "./pages");
|
|
185
177
|
},
|
|
178
|
+
get distPagesDir() {
|
|
179
|
+
return path2.join(this.distDir, "./pages");
|
|
180
|
+
},
|
|
186
181
|
get publicDir() {
|
|
187
182
|
return path2.join(this.srcDir, "./public");
|
|
188
183
|
},
|
|
@@ -218,21 +213,152 @@ var plugin = (opts = {}) => {
|
|
|
218
213
|
plugin.postcss = true;
|
|
219
214
|
var remove_layers_default = plugin;
|
|
220
215
|
|
|
216
|
+
// src/build/esbuild/transformTSXFiles.ts
|
|
217
|
+
import fs2 from "fs-extra";
|
|
218
|
+
import ts2 from "typescript";
|
|
219
|
+
|
|
220
|
+
// src/util/regex.ts
|
|
221
|
+
var regex_default = {
|
|
222
|
+
dotTsx: /.tsx/,
|
|
223
|
+
hasLetters: /[A-Za-z0-9]/g,
|
|
224
|
+
configFile: /\+config\.[tj]sx?/g,
|
|
225
|
+
dynamicRoutes: /(\[)(.*?)(\])/g,
|
|
226
|
+
isDynamicRoute: /\[(.*)\]/g,
|
|
227
|
+
endsWithTSX: /\.tsx$/,
|
|
228
|
+
endsWithJSX: /\.jsx$/
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// src/util/config-file.ts
|
|
232
|
+
import path3 from "path";
|
|
233
|
+
function sourcePathToDistPath(sourcePath) {
|
|
234
|
+
return sourcePath?.replace(config.srcDir, config.distDir)?.replace(/\.ts$/, ".js")?.replace(/\.tsx$/, ".js");
|
|
235
|
+
}
|
|
236
|
+
function getConfigFiles(pathName, pageConfigFiles, returnDistPaths = true) {
|
|
237
|
+
const configs = [];
|
|
238
|
+
for (const configFile of pageConfigFiles) {
|
|
239
|
+
const result = path3.relative(pathName, configFile)?.replace(regex_default.configFile, "");
|
|
240
|
+
const hasLetters = result.match(regex_default.hasLetters);
|
|
241
|
+
if (!hasLetters) {
|
|
242
|
+
configs.push(returnDistPaths ? sourcePathToDistPath(configFile) : configFile);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!configs.length) return null;
|
|
246
|
+
return configs.sort((a, b) => b.length - a.length);
|
|
247
|
+
}
|
|
248
|
+
async function getCompleteConfig(configFiles, cacheKey) {
|
|
249
|
+
let config2 = {};
|
|
250
|
+
for (const configFile of configFiles) {
|
|
251
|
+
config2 = {
|
|
252
|
+
...(await import(configFile + `?cache=${cacheKey}`)).default,
|
|
253
|
+
...config2
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return config2;
|
|
257
|
+
}
|
|
258
|
+
function isAConfigFile(pathName) {
|
|
259
|
+
return !!pathName.match(regex_default.configFile);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/build/esbuild/transformTSXFiles.ts
|
|
263
|
+
function transformTSXFiles(componentData, pageConfigFiles) {
|
|
264
|
+
return {
|
|
265
|
+
name: "transform-tsx-files",
|
|
266
|
+
setup(build2) {
|
|
267
|
+
build2.onLoad({ filter: regex_default.dotTsx }, async (args) => {
|
|
268
|
+
const content = fs2.readFileSync(args.path, "utf-8");
|
|
269
|
+
const source = ts2.createSourceFile(
|
|
270
|
+
args.path,
|
|
271
|
+
content,
|
|
272
|
+
ts2.ScriptTarget.Latest
|
|
273
|
+
);
|
|
274
|
+
const cleanedContent = removeClientScriptInTSXFile(args.path, source);
|
|
275
|
+
const htmlImportStart = "import { html } from 'xpine';\n";
|
|
276
|
+
const newContent = `${htmlImportStart}${cleanedContent.content}`;
|
|
277
|
+
componentData.push({
|
|
278
|
+
...args,
|
|
279
|
+
contents: `${htmlImportStart}${cleanedContent.fullContent}`,
|
|
280
|
+
clientContent: cleanedContent.clientContent,
|
|
281
|
+
configFiles: isAConfigFile(args?.path) ? null : getConfigFiles(args.path, pageConfigFiles)
|
|
282
|
+
});
|
|
283
|
+
return {
|
|
284
|
+
contents: newContent,
|
|
285
|
+
loader: "tsx"
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/build/esbuild/addDotJS.ts
|
|
293
|
+
import fs3 from "fs-extra";
|
|
294
|
+
import path4 from "path";
|
|
295
|
+
import builtinModules from "builtin-modules";
|
|
296
|
+
function addDotJS(allPackages2, extensions2, isDev) {
|
|
297
|
+
const allPackagesIncludingNode = allPackages2.concat(builtinModules);
|
|
298
|
+
return {
|
|
299
|
+
name: "add-dot-js",
|
|
300
|
+
setup(build2) {
|
|
301
|
+
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
302
|
+
const hasAtSign = args.path.startsWith("@");
|
|
303
|
+
const isPackage = hasAtSign ? allPackagesIncludingNode.includes(args.path) : allPackagesIncludingNode.includes(args.path.split("/").shift());
|
|
304
|
+
if (args.importer && !isPackage) {
|
|
305
|
+
const calculatedDir = path4.join(args.resolveDir, args.path);
|
|
306
|
+
let existsAsFile = false;
|
|
307
|
+
for (const extension of extensions2) {
|
|
308
|
+
const asFile = calculatedDir + extension;
|
|
309
|
+
const exists = fs3.existsSync(asFile);
|
|
310
|
+
if (exists) existsAsFile = true;
|
|
311
|
+
}
|
|
312
|
+
let outputPath = args.path + (existsAsFile ? "" : "/index") + ".js";
|
|
313
|
+
outputPath = args.path.endsWith(".js") || args.path.endsWith(".mjs") ? args.path : outputPath;
|
|
314
|
+
return { path: outputPath + (isDev ? `?cache=${Date.now()}` : ""), external: true };
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/build/esbuild/getDataFiles.ts
|
|
322
|
+
import fs4 from "fs-extra";
|
|
323
|
+
function getDataFiles(dataFiles) {
|
|
324
|
+
return {
|
|
325
|
+
name: "get-data-files",
|
|
326
|
+
setup(build2) {
|
|
327
|
+
build2.onLoad({ filter: /\.data\.(js|mjs|ts)$/ }, (args) => {
|
|
328
|
+
const contents = fs4.readFileSync(args.path, "utf-8");
|
|
329
|
+
dataFiles.push({
|
|
330
|
+
...args,
|
|
331
|
+
contents
|
|
332
|
+
});
|
|
333
|
+
return {
|
|
334
|
+
contents,
|
|
335
|
+
loader: "ts"
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/util/constants.ts
|
|
343
|
+
var doctypeHTML = "<!DOCTYPE html>";
|
|
344
|
+
var staticComment = "<!-- static -->";
|
|
345
|
+
|
|
221
346
|
// src/scripts/build.ts
|
|
222
347
|
var extensions = [".ts", ".tsx"];
|
|
223
|
-
var packageJson = JSON.parse(
|
|
348
|
+
var packageJson = JSON.parse(fs5.readFileSync(config.packageJsonPath, "utf-8"));
|
|
224
349
|
var allPackages = Object.keys(packageJson.devDependencies).concat(Object.keys(packageJson.dependencies));
|
|
225
|
-
var allPackagesIncludingNode = allPackages.concat(builtinModules);
|
|
226
350
|
var xpineDistDir = getXPineDistDir();
|
|
227
|
-
async function buildApp(isDev = false) {
|
|
351
|
+
async function buildApp(isDev = false, removePreviousBuild = false) {
|
|
228
352
|
try {
|
|
353
|
+
if (removePreviousBuild) fs5.removeSync(config.distDir);
|
|
229
354
|
const srcDirFiles = globSync(config.srcDir + "/**/*.{js,ts,tsx,jsx}");
|
|
230
355
|
const { componentData, dataFiles } = await buildAppFiles(srcDirFiles, isDev);
|
|
231
356
|
const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
|
|
232
357
|
await buildClientSideFiles([alpineDataFile], isDev);
|
|
233
|
-
|
|
358
|
+
fs5.removeSync(config.distTempFolder);
|
|
234
359
|
await buildCSS();
|
|
235
360
|
await buildPublicFolderSymlinks();
|
|
361
|
+
if (!isDev) await buildFilesWithConfigs(componentData);
|
|
236
362
|
} catch (err) {
|
|
237
363
|
console.error("Build failed");
|
|
238
364
|
console.error(err);
|
|
@@ -241,8 +367,12 @@ async function buildApp(isDev = false) {
|
|
|
241
367
|
async function buildAppFiles(files, isDev) {
|
|
242
368
|
const componentData = [];
|
|
243
369
|
const dataFiles = [];
|
|
370
|
+
const pageConfigFiles = files.filter((file) => {
|
|
371
|
+
const fileName = file.split("/").at(-1).split(".").shift();
|
|
372
|
+
return fileName === "+config";
|
|
373
|
+
});
|
|
244
374
|
const backendFiles = files.filter((file) => extensions.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
|
|
245
|
-
|
|
375
|
+
fs5.ensureDirSync(config.distDir);
|
|
246
376
|
await build({
|
|
247
377
|
entryPoints: backendFiles,
|
|
248
378
|
format: "esm",
|
|
@@ -254,69 +384,9 @@ async function buildAppFiles(files, isDev) {
|
|
|
254
384
|
jsx: "transform",
|
|
255
385
|
minify: !isDev,
|
|
256
386
|
plugins: [
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
261
|
-
const hasAtSign = args.path.startsWith("@");
|
|
262
|
-
const isPackage = hasAtSign ? allPackagesIncludingNode.includes(args.path) : allPackagesIncludingNode.includes(args.path.split("/").shift());
|
|
263
|
-
if (args.importer && !isPackage) {
|
|
264
|
-
const calculatedDir = path3.join(args.resolveDir, args.path);
|
|
265
|
-
let existsAsFile = false;
|
|
266
|
-
for (const extension of extensions) {
|
|
267
|
-
const asFile = calculatedDir + extension;
|
|
268
|
-
const exists = fs2.existsSync(asFile);
|
|
269
|
-
if (exists) existsAsFile = true;
|
|
270
|
-
}
|
|
271
|
-
let outputPath = args.path + (existsAsFile ? "" : "/index") + ".js";
|
|
272
|
-
outputPath = args.path.endsWith(".js") || args.path.endsWith(".mjs") ? args.path : outputPath;
|
|
273
|
-
return { path: outputPath + (isDev ? `?cache=${Date.now()}` : ""), external: true };
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
name: "insert-html-banner-and-remove-client-scripts",
|
|
280
|
-
setup(build2) {
|
|
281
|
-
build2.onLoad({ filter: /.tsx/ }, (args) => {
|
|
282
|
-
const content = fs2.readFileSync(args.path, "utf-8");
|
|
283
|
-
const source = ts2.createSourceFile(
|
|
284
|
-
args.path,
|
|
285
|
-
content,
|
|
286
|
-
ts2.ScriptTarget.Latest
|
|
287
|
-
);
|
|
288
|
-
const cleanedContent = removeClientScriptInTSXFile(args.path, source);
|
|
289
|
-
createStaticFile(args.path, source);
|
|
290
|
-
const htmlImportStart = "import { html } from 'xpine';\n";
|
|
291
|
-
const newContent = `${htmlImportStart}${cleanedContent.content}`;
|
|
292
|
-
componentData.push({
|
|
293
|
-
...args,
|
|
294
|
-
contents: `${htmlImportStart}${cleanedContent.fullContent}`,
|
|
295
|
-
clientContent: cleanedContent.clientContent
|
|
296
|
-
});
|
|
297
|
-
return {
|
|
298
|
-
contents: newContent,
|
|
299
|
-
loader: "tsx"
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
name: "get-data-files",
|
|
306
|
-
setup(build2) {
|
|
307
|
-
build2.onLoad({ filter: /\.data\.(js|mjs|ts)$/ }, (args) => {
|
|
308
|
-
const contents = fs2.readFileSync(args.path, "utf-8");
|
|
309
|
-
dataFiles.push({
|
|
310
|
-
...args,
|
|
311
|
-
contents
|
|
312
|
-
});
|
|
313
|
-
return {
|
|
314
|
-
contents,
|
|
315
|
-
loader: "ts"
|
|
316
|
-
};
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
}
|
|
387
|
+
addDotJS(allPackages, extensions, isDev),
|
|
388
|
+
transformTSXFiles(componentData, pageConfigFiles),
|
|
389
|
+
getDataFiles(dataFiles)
|
|
320
390
|
]
|
|
321
391
|
});
|
|
322
392
|
await logSize(config.distDir, "app");
|
|
@@ -326,8 +396,8 @@ async function buildAppFiles(files, isDev) {
|
|
|
326
396
|
};
|
|
327
397
|
}
|
|
328
398
|
async function buildClientSideFiles(alpineDataFiles = [], isDev) {
|
|
329
|
-
const tempFilePath =
|
|
330
|
-
|
|
399
|
+
const tempFilePath = path5.join(config.distTempFolder, "./app.ts");
|
|
400
|
+
fs5.ensureFileSync(tempFilePath);
|
|
331
401
|
const pagesScriptsGlob = config.publicDir + "/scripts/pages/**/*.{js,ts}";
|
|
332
402
|
const clientFiles = globSync(
|
|
333
403
|
config.publicDir + "/**/*.{js,ts}",
|
|
@@ -356,16 +426,14 @@ async function buildClientSideFiles(alpineDataFiles = [], isDev) {
|
|
|
356
426
|
await logSize(config.distPublicDir, "client");
|
|
357
427
|
}
|
|
358
428
|
function writeDevServerClientSideCode(tempFilePath) {
|
|
359
|
-
const devServerPath =
|
|
360
|
-
const content =
|
|
361
|
-
|
|
362
|
-
` + content);
|
|
429
|
+
const devServerPath = path5.join(xpineDistDir, "./src/static/dev-server.js");
|
|
430
|
+
const content = fs5.readFileSync(devServerPath, "utf-8");
|
|
431
|
+
fs5.appendFileSync(tempFilePath, "\n" + content);
|
|
363
432
|
}
|
|
364
433
|
function writeSpaClientSideCode(tempFilePath) {
|
|
365
|
-
const spaPath =
|
|
366
|
-
const content =
|
|
367
|
-
|
|
368
|
-
` + content);
|
|
434
|
+
const spaPath = path5.join(xpineDistDir, "./src/static/spa.js");
|
|
435
|
+
const content = fs5.readFileSync(spaPath, "utf-8");
|
|
436
|
+
fs5.appendFileSync(tempFilePath, "\n" + content);
|
|
369
437
|
}
|
|
370
438
|
async function buildAlpineDataFile(componentData, dataFiles) {
|
|
371
439
|
const output = {
|
|
@@ -384,10 +452,10 @@ async function buildAlpineDataFile(componentData, dataFiles) {
|
|
|
384
452
|
};
|
|
385
453
|
const componentsAndDataFiles = componentData.concat(dataFiles);
|
|
386
454
|
for (const component of componentsAndDataFiles) {
|
|
387
|
-
const sourceFile =
|
|
455
|
+
const sourceFile = ts3.createSourceFile(
|
|
388
456
|
component.path,
|
|
389
457
|
component.contents,
|
|
390
|
-
|
|
458
|
+
ts3.ScriptTarget.Latest
|
|
391
459
|
);
|
|
392
460
|
const dataFunctionResult = findDataAttributesAndFunctions(sourceFile, sourceFile);
|
|
393
461
|
dataFunctionResults.foundDataAttributes.push(...dataFunctionResult.foundDataAttributes);
|
|
@@ -409,18 +477,18 @@ async function buildAlpineDataFile(componentData, dataFiles) {
|
|
|
409
477
|
output.code.push(`Alpine.data('${dataFunction.name}', ${dataFunction.name});`);
|
|
410
478
|
}
|
|
411
479
|
const result = output.imports.join("\n") + "\n" + output.code.join("\n") + "\n" + output.end.join("\n");
|
|
412
|
-
|
|
413
|
-
|
|
480
|
+
fs5.ensureFileSync(config.alpineDataPath);
|
|
481
|
+
fs5.writeFileSync(config.alpineDataPath, result);
|
|
414
482
|
return config.alpineDataPath;
|
|
415
483
|
}
|
|
416
484
|
async function buildCSS() {
|
|
417
485
|
const cssFiles = globSync(config.srcDir + "/**/*.css");
|
|
418
486
|
for (const file of cssFiles) {
|
|
419
|
-
const fileContents =
|
|
487
|
+
const fileContents = fs5.readFileSync(file, "utf-8");
|
|
420
488
|
const result = await postcss([tailwindPostcss(), remove_layers_default()]).process(fileContents, { from: file });
|
|
421
489
|
const newPath = file.replace(config.srcDir, config.distDir);
|
|
422
|
-
|
|
423
|
-
|
|
490
|
+
fs5.ensureFileSync(newPath);
|
|
491
|
+
fs5.writeFileSync(newPath, result.css);
|
|
424
492
|
}
|
|
425
493
|
logSize(config.distPublicDir, "css");
|
|
426
494
|
}
|
|
@@ -434,8 +502,8 @@ async function buildPublicFolderSymlinks() {
|
|
|
434
502
|
const splitNewPath = newPath.split("/");
|
|
435
503
|
splitNewPath.pop();
|
|
436
504
|
const newDir = splitNewPath.join("/");
|
|
437
|
-
|
|
438
|
-
|
|
505
|
+
fs5.ensureDirSync(newDir);
|
|
506
|
+
fs5.symlinkSync(file, newPath);
|
|
439
507
|
} catch {
|
|
440
508
|
}
|
|
441
509
|
}
|
|
@@ -446,7 +514,7 @@ async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
|
|
|
446
514
|
if (!validExtensions.find((ext) => file.endsWith(ext))) return false;
|
|
447
515
|
return {
|
|
448
516
|
file,
|
|
449
|
-
size:
|
|
517
|
+
size: fs5.statSync(file).size / (1024 * 1e3)
|
|
450
518
|
};
|
|
451
519
|
}).filter(Boolean);
|
|
452
520
|
const totalSize = fileSizes.reduce((total, current) => {
|
|
@@ -454,17 +522,91 @@ async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
|
|
|
454
522
|
}, 0);
|
|
455
523
|
console.info(`[${totalSize.toFixed(3)} MB] Built ${type}`);
|
|
456
524
|
}
|
|
525
|
+
async function buildFilesWithConfigs(componentData) {
|
|
526
|
+
const now = Date.now();
|
|
527
|
+
const componentsWithConfigs = componentData.filter((item) => item.configFiles);
|
|
528
|
+
for (const component of componentsWithConfigs) {
|
|
529
|
+
let config2 = await getCompleteConfig(component.configFiles, now);
|
|
530
|
+
const builtComponentPath = sourcePathToDistPath(component.path);
|
|
531
|
+
const componentImport = await import(builtComponentPath + `?cache=${Date.now()}`);
|
|
532
|
+
if (componentImport?.config) {
|
|
533
|
+
config2 = {
|
|
534
|
+
...config2,
|
|
535
|
+
...componentImport.config
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
if (config2?.staticPaths) buildStaticFiles(config2, component, componentImport, builtComponentPath);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async function buildStaticFiles(config2, component, componentImport, builtComponentPath) {
|
|
542
|
+
if (!config2?.staticPaths) return;
|
|
543
|
+
let componentFileName = component.path.split("/").pop().replace(regex_default.endsWithJSX, "").replace(regex_default.endsWithTSX, "");
|
|
544
|
+
const isDynamicRoute = component.path.match(regex_default.isDynamicRoute);
|
|
545
|
+
if (isDynamicRoute) {
|
|
546
|
+
componentFileName = component.path.split("/").filter((dir) => dir.match(regex_default.isDynamicRoute)).join("/").replace(regex_default.endsWithJSX, "").replace(regex_default.endsWithTSX, "");
|
|
547
|
+
}
|
|
548
|
+
const componentDynamicPaths = getComponentDynamicPaths(componentFileName);
|
|
549
|
+
const componentFn = componentImport.default;
|
|
550
|
+
const outputPath = componentDynamicPaths?.length ? componentDynamicPaths.reduce((total, current) => {
|
|
551
|
+
return total.replace(`/[${current}]`, "");
|
|
552
|
+
}, path5.dirname(builtComponentPath)) : path5.dirname(builtComponentPath);
|
|
553
|
+
if (typeof config2?.staticPaths === "boolean") {
|
|
554
|
+
try {
|
|
555
|
+
const req = { params: {} };
|
|
556
|
+
const data = config2?.data ? await config2.data(req) : null;
|
|
557
|
+
const staticComponentOutput = await componentFn({ data });
|
|
558
|
+
fs5.writeFileSync(
|
|
559
|
+
path5.join(outputPath, "./index.html"),
|
|
560
|
+
doctypeHTML + (config2?.wrapper ? await config2.wrapper({ req, children: staticComponentOutput, config: config2, data }) : staticComponentOutput) + staticComment
|
|
561
|
+
);
|
|
562
|
+
} catch (err) {
|
|
563
|
+
console.error(err);
|
|
564
|
+
console.log("Could not build static component", component.path);
|
|
565
|
+
}
|
|
566
|
+
} else if (typeof config2?.staticPaths === "function") {
|
|
567
|
+
const dynamicPaths = await config2.staticPaths();
|
|
568
|
+
for (const dynamicPath of dynamicPaths) {
|
|
569
|
+
try {
|
|
570
|
+
const req = {
|
|
571
|
+
params: {
|
|
572
|
+
...componentDynamicPaths?.length ? dynamicPath : {}
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
const data = config2?.data ? await config2.data(req) : null;
|
|
576
|
+
const staticComponentOutput = await componentFn({ req, data });
|
|
577
|
+
const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
|
|
578
|
+
fs5.ensureDirSync(updatedOutDir);
|
|
579
|
+
fs5.writeFileSync(
|
|
580
|
+
path5.join(updatedOutDir, "./index.html"),
|
|
581
|
+
doctypeHTML + (config2?.wrapper ? await config2.wrapper({ req, children: staticComponentOutput, config: config2, data }) : staticComponentOutput) + staticComment
|
|
582
|
+
);
|
|
583
|
+
} catch (err) {
|
|
584
|
+
console.log("Could not build static component", component.path);
|
|
585
|
+
console.error(err);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function getComponentDynamicPaths(componentPath) {
|
|
591
|
+
const matches = [...componentPath.matchAll(regex_default.dynamicRoutes)];
|
|
592
|
+
if (!matches?.length) return null;
|
|
593
|
+
const output = [];
|
|
594
|
+
for (const match of matches) {
|
|
595
|
+
output.push(match[2]);
|
|
596
|
+
}
|
|
597
|
+
return output;
|
|
598
|
+
}
|
|
457
599
|
|
|
458
600
|
// src/runDevServer.ts
|
|
459
|
-
import
|
|
601
|
+
import path7 from "path";
|
|
460
602
|
|
|
461
603
|
// src/util/env.ts
|
|
462
604
|
import dotenv from "dotenv";
|
|
463
|
-
import
|
|
605
|
+
import path6 from "path";
|
|
464
606
|
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
|
|
465
607
|
async function setupEnv() {
|
|
466
608
|
if (process.env.HAS_SETUP_ENV) return;
|
|
467
|
-
dotenv.config({ path:
|
|
609
|
+
dotenv.config({ path: path6.join(config.rootDir, `./.env.${process.env.STAGE || "dev"}`) });
|
|
468
610
|
await loadSecretsManagerSecrets();
|
|
469
611
|
process.env.HAS_SETUP_ENV = "true";
|
|
470
612
|
}
|
|
@@ -493,26 +635,33 @@ async function loadSecretsManagerSecrets() {
|
|
|
493
635
|
await setupEnv();
|
|
494
636
|
async function runDevServer() {
|
|
495
637
|
process.env.NODE_ENV = "development";
|
|
496
|
-
const
|
|
638
|
+
const rebuildEmitter = createRebuildEmitter();
|
|
497
639
|
await buildApp(true);
|
|
640
|
+
const startServer = (await import(config.serverDistAppPath + `?cache=${Date.now()}`)).default;
|
|
498
641
|
let appServer = await startServer();
|
|
499
642
|
const watcher = chokidar.watch(config.srcDir, {
|
|
500
643
|
ignoreInitial: true,
|
|
501
644
|
// Ignore map and prisma files
|
|
502
|
-
ignored: (pathName) => pathName.endsWith(".map") || pathName.startsWith(
|
|
645
|
+
ignored: (pathName) => pathName.endsWith(".map") || pathName.startsWith(path7.join(config.serverDir, "./prisma"))
|
|
503
646
|
});
|
|
504
|
-
watcher.on("all", async (event,
|
|
505
|
-
const shouldReloadServer =
|
|
647
|
+
watcher.on("all", async (event, path8) => {
|
|
648
|
+
const shouldReloadServer = path8.startsWith(config.serverDir) && !path8.startsWith(config.runDir) || ["add", "unlink"].includes(event) && path8.startsWith(config.pagesDir);
|
|
506
649
|
if (shouldReloadServer) {
|
|
507
|
-
await appServer.server
|
|
508
|
-
|
|
509
|
-
const startServer2 = (await import(config.serverDistAppPath + `?cache=${Date.now()}`)).default;
|
|
510
|
-
appServer = await startServer2();
|
|
650
|
+
await asyncServerClose(appServer.server);
|
|
651
|
+
rebuildEmitter.emit("rebuild-server");
|
|
511
652
|
return;
|
|
512
653
|
}
|
|
513
654
|
await buildApp(true);
|
|
514
655
|
refreshEmitter.emit("refresh");
|
|
515
656
|
});
|
|
657
|
+
rebuildEmitter.on("done", async () => {
|
|
658
|
+
await rebuildServer();
|
|
659
|
+
});
|
|
660
|
+
async function rebuildServer() {
|
|
661
|
+
await buildApp(true);
|
|
662
|
+
const startServer2 = (await import(config.serverDistAppPath + `?cache=${Date.now()}`)).default;
|
|
663
|
+
appServer = await startServer2();
|
|
664
|
+
}
|
|
516
665
|
const wsApp = express();
|
|
517
666
|
const wsServer = http.createServer(wsApp);
|
|
518
667
|
expressWs(wsApp, wsServer);
|
|
@@ -532,6 +681,37 @@ async function runDevServer() {
|
|
|
532
681
|
console.info(`Dev server listening on port ${wsPort}`);
|
|
533
682
|
});
|
|
534
683
|
}
|
|
684
|
+
function asyncServerClose(server) {
|
|
685
|
+
return new Promise((resolve, reject) => {
|
|
686
|
+
server.close();
|
|
687
|
+
resolve(true);
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
function createRebuildEmitter(delay = 500) {
|
|
691
|
+
class RebuildEmitter extends EventEmitter {
|
|
692
|
+
}
|
|
693
|
+
;
|
|
694
|
+
const rebuildEmitter = new RebuildEmitter();
|
|
695
|
+
let start = 0;
|
|
696
|
+
let hasEmitted = false;
|
|
697
|
+
rebuildEmitter.on("rebuild-server", () => {
|
|
698
|
+
hasEmitted = false;
|
|
699
|
+
trigger();
|
|
700
|
+
});
|
|
701
|
+
function trigger() {
|
|
702
|
+
start = Date.now();
|
|
703
|
+
const interval = setInterval(() => {
|
|
704
|
+
if (hasEmitted) return;
|
|
705
|
+
const now = Date.now();
|
|
706
|
+
if (now - start >= delay) {
|
|
707
|
+
rebuildEmitter.emit("done");
|
|
708
|
+
clearInterval(interval);
|
|
709
|
+
hasEmitted = true;
|
|
710
|
+
}
|
|
711
|
+
}, 100);
|
|
712
|
+
}
|
|
713
|
+
return rebuildEmitter;
|
|
714
|
+
}
|
|
535
715
|
|
|
536
716
|
// src/scripts/xpine-dev.ts
|
|
537
717
|
runDevServer();
|
package/dist/src/static/spa.js
CHANGED
|
@@ -6,7 +6,8 @@ async function replaceDocumentContentsWithLinkResponse() {
|
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
async function updatePageOnLinkClick(e) {
|
|
9
|
-
|
|
9
|
+
e.preventDefault();
|
|
10
|
+
const targetHref = e?.target?.closest("a")?.getAttribute("href");
|
|
10
11
|
if (!targetHref) return;
|
|
11
12
|
if (isExternalURL(targetHref) && !e?.target?.getAttribute("data-spa-crossorigin")) return;
|
|
12
13
|
e.preventDefault();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ConfigFile } from '../../types';
|
|
2
|
+
export declare function sourcePathToDistPath(sourcePath: string): string;
|
|
3
|
+
export declare function getConfigFiles(pathName: string, pageConfigFiles: string[], returnDistPaths?: boolean): string[] | null;
|
|
4
|
+
export declare function getCompleteConfig(configFiles: string[], cacheKey: string | number): Promise<ConfigFile>;
|
|
5
|
+
export declare function isAConfigFile(pathName: string): boolean;
|
|
6
|
+
//# sourceMappingURL=config-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-file.d.ts","sourceRoot":"","sources":["../../../src/util/config-file.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,UAEtD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,eAAe,GAAE,OAAc,GAAG,MAAM,EAAE,GAAG,IAAI,CAY5H;AAED,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,uBASvF;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,WAE7C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/util/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,oBAAoB,CAAC;AAC7C,eAAO,MAAM,aAAa,oBAAoB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-config.d.ts","sourceRoot":"","sources":["../../../src/util/get-config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,wBAAgB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,UAGzC;
|
|
1
|
+
{"version":3,"file":"get-config.d.ts","sourceRoot":"","sources":["../../../src/util/get-config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,wBAAgB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,UAGzC;AAuDD,eAAO,MAAM,MAAM,EAAE,WAGpB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"regex.d.ts","sourceRoot":"","sources":["../../../src/util/regex.ts"],"names":[],"mappings":";;;;;;;;;AAAA,wBAQE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"require.d.ts","sourceRoot":"","sources":["../../../src/util/require.ts"],"names":[],"mappings":";AAEA,wBAA8C;AAE9C,wBAAgB,eAAe,
|
|
1
|
+
{"version":3,"file":"require.d.ts","sourceRoot":"","sources":["../../../src/util/require.ts"],"names":[],"mappings":";AAEA,wBAA8C;AAE9C,wBAAgB,eAAe,WAS9B"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Request } from 'express';
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
2
|
export type XPineConfig = {
|
|
3
|
-
[key: string]:
|
|
3
|
+
[key: string]: any;
|
|
4
4
|
};
|
|
5
5
|
export type TokenUser = {
|
|
6
6
|
email?: string;
|
|
@@ -8,5 +8,34 @@ export type TokenUser = {
|
|
|
8
8
|
};
|
|
9
9
|
export type ServerRequest = Request & {
|
|
10
10
|
user?: TokenUser;
|
|
11
|
+
clientIp?: string;
|
|
12
|
+
};
|
|
13
|
+
export type WrapperProps = {
|
|
14
|
+
req: ServerRequest;
|
|
15
|
+
children: any;
|
|
16
|
+
config: ConfigFile;
|
|
17
|
+
data?: any;
|
|
18
|
+
};
|
|
19
|
+
export type ConfigFile = {
|
|
20
|
+
staticPaths?: boolean | (() => Promise<{
|
|
21
|
+
[key: string]: string;
|
|
22
|
+
}[]>);
|
|
23
|
+
wrapper?: (props: WrapperProps) => Promise<any>;
|
|
24
|
+
data?: (req: ServerRequest) => Promise<any>;
|
|
25
|
+
};
|
|
26
|
+
export type PageProps = {
|
|
27
|
+
req: ServerRequest;
|
|
28
|
+
res: Response;
|
|
29
|
+
data: any;
|
|
30
|
+
};
|
|
31
|
+
export type FileItem = {
|
|
32
|
+
file: string;
|
|
33
|
+
size: number;
|
|
34
|
+
};
|
|
35
|
+
export type ComponentData = {
|
|
36
|
+
path: string;
|
|
37
|
+
contents: string;
|
|
38
|
+
clientContent: string;
|
|
39
|
+
configFiles: string[];
|
|
11
40
|
};
|
|
12
41
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG;IACpC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,aAAa,CAAC;IACnB,QAAQ,EAAE,GAAG,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,WAAW,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAC,EAAE,CAAC,CAAC,CAAC;IACpE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC7C,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,aAAa,CAAC;IACnB,GAAG,EAAE,QAAQ,CAAC;IACd,IAAI,EAAE,GAAG,CAAC;CACX,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAA"}
|