xpine 0.0.20 → 0.0.21

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.
@@ -8,11 +8,10 @@ import EventEmitter from "events";
8
8
  import chokidar from "chokidar";
9
9
 
10
10
  // src/scripts/build.ts
11
- import path3 from "path";
12
- import fs2 from "fs-extra";
11
+ import path5 from "path";
12
+ import fs5 from "fs-extra";
13
13
  import { build } from "esbuild";
14
- import builtinModules from "builtin-modules";
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";
@@ -183,6 +171,9 @@ var configDefaults = {
183
171
  get pagesDir() {
184
172
  return path2.join(this.srcDir, "./pages");
185
173
  },
174
+ get distPagesDir() {
175
+ return path2.join(this.distDir, "./pages");
176
+ },
186
177
  get publicDir() {
187
178
  return path2.join(this.srcDir, "./public");
188
179
  },
@@ -218,11 +209,117 @@ var plugin = (opts = {}) => {
218
209
  plugin.postcss = true;
219
210
  var remove_layers_default = plugin;
220
211
 
212
+ // src/build/esbuild/transformTSXFiles.ts
213
+ import fs2 from "fs-extra";
214
+ import ts2 from "typescript";
215
+ import path3 from "path";
216
+
217
+ // src/util/regex.ts
218
+ var regex_default = {
219
+ dotTsx: /.tsx/,
220
+ hasLetters: /[A-Za-z0-9]/g,
221
+ configFile: /\+config\.[tj]s/g,
222
+ dynamicRoutes: /(\[)(.*?)(\])/g,
223
+ isDynamicRoute: /\[(.*)\]/g,
224
+ endsWithTSX: /\.tsx$/,
225
+ endsWithJSX: /\.jsx$/
226
+ };
227
+
228
+ // src/build/esbuild/transformTSXFiles.ts
229
+ function transformTSXFiles(componentData, pageConfigFiles) {
230
+ return {
231
+ name: "transform-tsx-files",
232
+ setup(build2) {
233
+ build2.onLoad({ filter: regex_default.dotTsx }, (args) => {
234
+ const configFile = getConfigFile(args.path, pageConfigFiles);
235
+ const content = fs2.readFileSync(args.path, "utf-8");
236
+ const source = ts2.createSourceFile(
237
+ args.path,
238
+ content,
239
+ ts2.ScriptTarget.Latest
240
+ );
241
+ const cleanedContent = removeClientScriptInTSXFile(args.path, source);
242
+ const htmlImportStart = "import { html } from 'xpine';\n";
243
+ const newContent = `${htmlImportStart}${cleanedContent.content}`;
244
+ componentData.push({
245
+ ...args,
246
+ contents: `${htmlImportStart}${cleanedContent.fullContent}`,
247
+ clientContent: cleanedContent.clientContent,
248
+ configFile
249
+ });
250
+ return {
251
+ contents: newContent,
252
+ loader: "tsx"
253
+ };
254
+ });
255
+ }
256
+ };
257
+ }
258
+ function getConfigFile(pathName, pageConfigFiles) {
259
+ const configs = [];
260
+ for (const configFile of pageConfigFiles) {
261
+ const result = path3.relative(pathName, configFile)?.replace(regex_default.configFile, "");
262
+ const hasLetters = result.match(regex_default.hasLetters);
263
+ if (!hasLetters) configs.push(configFile);
264
+ }
265
+ const configToUse = configs.sort((a, b) => b.length - a.length)?.[0];
266
+ return configToUse;
267
+ }
268
+
269
+ // src/build/esbuild/addDotJS.ts
270
+ import fs3 from "fs-extra";
271
+ import path4 from "path";
272
+ import builtinModules from "builtin-modules";
273
+ function addDotJS(allPackages2, extensions2, isDev) {
274
+ const allPackagesIncludingNode = allPackages2.concat(builtinModules);
275
+ return {
276
+ name: "add-dot-js",
277
+ setup(build2) {
278
+ build2.onResolve({ filter: /.*/ }, (args) => {
279
+ const hasAtSign = args.path.startsWith("@");
280
+ const isPackage = hasAtSign ? allPackagesIncludingNode.includes(args.path) : allPackagesIncludingNode.includes(args.path.split("/").shift());
281
+ if (args.importer && !isPackage) {
282
+ const calculatedDir = path4.join(args.resolveDir, args.path);
283
+ let existsAsFile = false;
284
+ for (const extension of extensions2) {
285
+ const asFile = calculatedDir + extension;
286
+ const exists = fs3.existsSync(asFile);
287
+ if (exists) existsAsFile = true;
288
+ }
289
+ let outputPath = args.path + (existsAsFile ? "" : "/index") + ".js";
290
+ outputPath = args.path.endsWith(".js") || args.path.endsWith(".mjs") ? args.path : outputPath;
291
+ return { path: outputPath + (isDev ? `?cache=${Date.now()}` : ""), external: true };
292
+ }
293
+ });
294
+ }
295
+ };
296
+ }
297
+
298
+ // src/build/esbuild/getDataFiles.ts
299
+ import fs4 from "fs-extra";
300
+ function getDataFiles(dataFiles) {
301
+ return {
302
+ name: "get-data-files",
303
+ setup(build2) {
304
+ build2.onLoad({ filter: /\.data\.(js|mjs|ts)$/ }, (args) => {
305
+ const contents = fs4.readFileSync(args.path, "utf-8");
306
+ dataFiles.push({
307
+ ...args,
308
+ contents
309
+ });
310
+ return {
311
+ contents,
312
+ loader: "ts"
313
+ };
314
+ });
315
+ }
316
+ };
317
+ }
318
+
221
319
  // src/scripts/build.ts
222
320
  var extensions = [".ts", ".tsx"];
223
- var packageJson = JSON.parse(fs2.readFileSync(config.packageJsonPath, "utf-8"));
321
+ var packageJson = JSON.parse(fs5.readFileSync(config.packageJsonPath, "utf-8"));
224
322
  var allPackages = Object.keys(packageJson.devDependencies).concat(Object.keys(packageJson.dependencies));
225
- var allPackagesIncludingNode = allPackages.concat(builtinModules);
226
323
  var xpineDistDir = getXPineDistDir();
227
324
  async function buildApp(isDev = false) {
228
325
  try {
@@ -230,9 +327,10 @@ async function buildApp(isDev = false) {
230
327
  const { componentData, dataFiles } = await buildAppFiles(srcDirFiles, isDev);
231
328
  const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
232
329
  await buildClientSideFiles([alpineDataFile], isDev);
233
- fs2.removeSync(config.distTempFolder);
330
+ fs5.removeSync(config.distTempFolder);
234
331
  await buildCSS();
235
332
  await buildPublicFolderSymlinks();
333
+ if (!isDev) await buildStaticFiles(componentData);
236
334
  } catch (err) {
237
335
  console.error("Build failed");
238
336
  console.error(err);
@@ -241,8 +339,12 @@ async function buildApp(isDev = false) {
241
339
  async function buildAppFiles(files, isDev) {
242
340
  const componentData = [];
243
341
  const dataFiles = [];
342
+ const pageConfigFiles = files.filter((file) => {
343
+ const fileName = file.split("/").at(-1).split(".").shift();
344
+ return fileName === "+config";
345
+ });
244
346
  const backendFiles = files.filter((file) => extensions.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
245
- fs2.ensureDirSync(config.distDir);
347
+ fs5.ensureDirSync(config.distDir);
246
348
  await build({
247
349
  entryPoints: backendFiles,
248
350
  format: "esm",
@@ -254,69 +356,9 @@ async function buildAppFiles(files, isDev) {
254
356
  jsx: "transform",
255
357
  minify: !isDev,
256
358
  plugins: [
257
- {
258
- name: "add-dot-js",
259
- setup(build2) {
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
- }
359
+ addDotJS(allPackages, extensions, isDev),
360
+ transformTSXFiles(componentData, pageConfigFiles),
361
+ getDataFiles(dataFiles)
320
362
  ]
321
363
  });
322
364
  await logSize(config.distDir, "app");
@@ -326,8 +368,8 @@ async function buildAppFiles(files, isDev) {
326
368
  };
327
369
  }
328
370
  async function buildClientSideFiles(alpineDataFiles = [], isDev) {
329
- const tempFilePath = path3.join(config.distTempFolder, "./app.ts");
330
- fs2.ensureFileSync(tempFilePath);
371
+ const tempFilePath = path5.join(config.distTempFolder, "./app.ts");
372
+ fs5.ensureFileSync(tempFilePath);
331
373
  const pagesScriptsGlob = config.publicDir + "/scripts/pages/**/*.{js,ts}";
332
374
  const clientFiles = globSync(
333
375
  config.publicDir + "/**/*.{js,ts}",
@@ -356,15 +398,15 @@ async function buildClientSideFiles(alpineDataFiles = [], isDev) {
356
398
  await logSize(config.distPublicDir, "client");
357
399
  }
358
400
  function writeDevServerClientSideCode(tempFilePath) {
359
- const devServerPath = path3.join(xpineDistDir, "./src/static/dev-server.js");
360
- const content = fs2.readFileSync(devServerPath, "utf-8");
361
- fs2.appendFileSync(tempFilePath, `
401
+ const devServerPath = path5.join(xpineDistDir, "./src/static/dev-server.js");
402
+ const content = fs5.readFileSync(devServerPath, "utf-8");
403
+ fs5.appendFileSync(tempFilePath, `
362
404
  ` + content);
363
405
  }
364
406
  function writeSpaClientSideCode(tempFilePath) {
365
- const spaPath = path3.join(xpineDistDir, "./src/static/spa.js");
366
- const content = fs2.readFileSync(spaPath, "utf-8");
367
- fs2.appendFileSync(tempFilePath, `
407
+ const spaPath = path5.join(xpineDistDir, "./src/static/spa.js");
408
+ const content = fs5.readFileSync(spaPath, "utf-8");
409
+ fs5.appendFileSync(tempFilePath, `
368
410
  ` + content);
369
411
  }
370
412
  async function buildAlpineDataFile(componentData, dataFiles) {
@@ -384,10 +426,10 @@ async function buildAlpineDataFile(componentData, dataFiles) {
384
426
  };
385
427
  const componentsAndDataFiles = componentData.concat(dataFiles);
386
428
  for (const component of componentsAndDataFiles) {
387
- const sourceFile = ts2.createSourceFile(
429
+ const sourceFile = ts3.createSourceFile(
388
430
  component.path,
389
431
  component.contents,
390
- ts2.ScriptTarget.Latest
432
+ ts3.ScriptTarget.Latest
391
433
  );
392
434
  const dataFunctionResult = findDataAttributesAndFunctions(sourceFile, sourceFile);
393
435
  dataFunctionResults.foundDataAttributes.push(...dataFunctionResult.foundDataAttributes);
@@ -409,18 +451,18 @@ async function buildAlpineDataFile(componentData, dataFiles) {
409
451
  output.code.push(`Alpine.data('${dataFunction.name}', ${dataFunction.name});`);
410
452
  }
411
453
  const result = output.imports.join("\n") + "\n" + output.code.join("\n") + "\n" + output.end.join("\n");
412
- fs2.ensureFileSync(config.alpineDataPath);
413
- fs2.writeFileSync(config.alpineDataPath, result);
454
+ fs5.ensureFileSync(config.alpineDataPath);
455
+ fs5.writeFileSync(config.alpineDataPath, result);
414
456
  return config.alpineDataPath;
415
457
  }
416
458
  async function buildCSS() {
417
459
  const cssFiles = globSync(config.srcDir + "/**/*.css");
418
460
  for (const file of cssFiles) {
419
- const fileContents = fs2.readFileSync(file, "utf-8");
461
+ const fileContents = fs5.readFileSync(file, "utf-8");
420
462
  const result = await postcss([tailwindPostcss(), remove_layers_default()]).process(fileContents, { from: file });
421
463
  const newPath = file.replace(config.srcDir, config.distDir);
422
- fs2.ensureFileSync(newPath);
423
- fs2.writeFileSync(newPath, result.css);
464
+ fs5.ensureFileSync(newPath);
465
+ fs5.writeFileSync(newPath, result.css);
424
466
  }
425
467
  logSize(config.distPublicDir, "css");
426
468
  }
@@ -434,8 +476,8 @@ async function buildPublicFolderSymlinks() {
434
476
  const splitNewPath = newPath.split("/");
435
477
  splitNewPath.pop();
436
478
  const newDir = splitNewPath.join("/");
437
- fs2.ensureDirSync(newDir);
438
- fs2.symlinkSync(file, newPath);
479
+ fs5.ensureDirSync(newDir);
480
+ fs5.symlinkSync(file, newPath);
439
481
  } catch {
440
482
  }
441
483
  }
@@ -446,7 +488,7 @@ async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
446
488
  if (!validExtensions.find((ext) => file.endsWith(ext))) return false;
447
489
  return {
448
490
  file,
449
- size: fs2.statSync(file).size / (1024 * 1e3)
491
+ size: fs5.statSync(file).size / (1024 * 1e3)
450
492
  };
451
493
  }).filter(Boolean);
452
494
  const totalSize = fileSizes.reduce((total, current) => {
@@ -454,17 +496,75 @@ async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
454
496
  }, 0);
455
497
  console.info(`[${totalSize.toFixed(3)} MB] Built ${type}`);
456
498
  }
499
+ async function buildStaticFiles(componentData) {
500
+ const componentsWithConfigs = componentData.filter((item) => item.configFile);
501
+ for (const component of componentsWithConfigs) {
502
+ const config2 = (await import(sourcePathToDistPath(component.configFile) + `?cache=${Date.now()}`)).default;
503
+ const shouldBeStatic = config2?.staticPaths;
504
+ if (shouldBeStatic) {
505
+ let componentFileName = component.path.split("/").pop().replace(regex_default.endsWithJSX, "").replace(regex_default.endsWithTSX, "");
506
+ const isDynamicRoute = component.path.match(regex_default.isDynamicRoute);
507
+ if (isDynamicRoute) {
508
+ componentFileName = component.path.split("/").filter((dir) => dir.match(regex_default.isDynamicRoute)).join("/").replace(regex_default.endsWithJSX, "").replace(regex_default.endsWithTSX, "");
509
+ }
510
+ const builtComponentPath = sourcePathToDistPath(component.path);
511
+ const componentDynamicPaths = getComponentDynamicPaths(componentFileName);
512
+ const componentFn = (await import(builtComponentPath + `?cache=${Date.now()}`)).default;
513
+ const outputPath = componentDynamicPaths?.length ? componentDynamicPaths.reduce((total, current) => {
514
+ return total.replace(`/[${current}]`, "");
515
+ }, path5.dirname(builtComponentPath)) : path5.dirname(builtComponentPath);
516
+ if (typeof shouldBeStatic === "boolean") {
517
+ try {
518
+ const staticComponentOutput = await componentFn();
519
+ fs5.writeFileSync(path5.join(outputPath, "./index.html"), staticComponentOutput);
520
+ } catch (err) {
521
+ console.error(err);
522
+ console.log("Could not build static component", component.path);
523
+ }
524
+ } else if (typeof shouldBeStatic === "function") {
525
+ const dynamicPaths = await shouldBeStatic();
526
+ for (const dynamicPath of dynamicPaths) {
527
+ try {
528
+ const staticComponentOutput = await componentFn({
529
+ params: {
530
+ ...componentDynamicPaths?.length ? dynamicPath : {}
531
+ }
532
+ });
533
+ const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
534
+ fs5.ensureDirSync(updatedOutDir);
535
+ fs5.writeFileSync(path5.join(updatedOutDir, `./index.html`), staticComponentOutput);
536
+ } catch (err) {
537
+ console.log("Could not build static component", component.path);
538
+ console.error(err);
539
+ }
540
+ }
541
+ }
542
+ }
543
+ }
544
+ }
545
+ function sourcePathToDistPath(sourcePath) {
546
+ return sourcePath.replace(config.srcDir, config.distDir).replace(/\.ts$/, ".js").replace(/\.tsx$/, ".js");
547
+ }
548
+ function getComponentDynamicPaths(componentPath) {
549
+ const matches = [...componentPath.matchAll(regex_default.dynamicRoutes)];
550
+ if (!matches?.length) return null;
551
+ const output = [];
552
+ for (const match of matches) {
553
+ output.push(match[2]);
554
+ }
555
+ return output;
556
+ }
457
557
 
458
558
  // src/runDevServer.ts
459
- import path5 from "path";
559
+ import path7 from "path";
460
560
 
461
561
  // src/util/env.ts
462
562
  import dotenv from "dotenv";
463
- import path4 from "path";
563
+ import path6 from "path";
464
564
  import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
465
565
  async function setupEnv() {
466
566
  if (process.env.HAS_SETUP_ENV) return;
467
- dotenv.config({ path: path4.join(config.rootDir, `./.env.${process.env.STAGE || "dev"}`) });
567
+ dotenv.config({ path: path6.join(config.rootDir, `./.env.${process.env.STAGE || "dev"}`) });
468
568
  await loadSecretsManagerSecrets();
469
569
  process.env.HAS_SETUP_ENV = "true";
470
570
  }
@@ -493,16 +593,16 @@ async function loadSecretsManagerSecrets() {
493
593
  await setupEnv();
494
594
  async function runDevServer() {
495
595
  process.env.NODE_ENV = "development";
496
- const startServer = (await import(config.serverDistAppPath + `?cache=${Date.now()}`)).default;
497
596
  await buildApp(true);
597
+ const startServer = (await import(config.serverDistAppPath + `?cache=${Date.now()}`)).default;
498
598
  let appServer = await startServer();
499
599
  const watcher = chokidar.watch(config.srcDir, {
500
600
  ignoreInitial: true,
501
601
  // Ignore map and prisma files
502
- ignored: (pathName) => pathName.endsWith(".map") || pathName.startsWith(path5.join(config.serverDir, "./prisma"))
602
+ ignored: (pathName) => pathName.endsWith(".map") || pathName.startsWith(path7.join(config.serverDir, "./prisma"))
503
603
  });
504
- watcher.on("all", async (event, path6) => {
505
- const shouldReloadServer = path6.startsWith(config.serverDir) && !path6.startsWith(config.runDir) || ["add", "unlink"].includes(event) && path6.startsWith(config.pagesDir);
604
+ watcher.on("all", async (event, path8) => {
605
+ const shouldReloadServer = path8.startsWith(config.serverDir) && !path8.startsWith(config.runDir) || ["add", "unlink"].includes(event) && path8.startsWith(config.pagesDir);
506
606
  if (shouldReloadServer) {
507
607
  await appServer.server.close();
508
608
  await buildApp(true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xpine",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "main": "dist/index.js",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-secrets-manager": "^3.758.0",
@@ -13,7 +13,6 @@
13
13
  "express-ws": "^5.0.2",
14
14
  "fs-extra": "^11.3.0",
15
15
  "glob": "^11.0.1",
16
- "html": "file:./dist/src/util/html.js",
17
16
  "jsonwebtoken": "^9.0.2",
18
17
  "postcss": "^8.5.3",
19
18
  "request-ip": "^3.3.0",