xpine 0.0.48 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -426,4 +426,31 @@ setupEnv also supports AWS secrets manager. Simply add SECRETS_NAME=your_aws_sec
426
426
 
427
427
  1. Add script to src/public/scripts/pages/your_script.ts
428
428
  2. Import script into page HTML (e.g. `<script src="/scripts/pages/your_script.ts">`)
429
- 3. To unload event listeners, use `window.addEventListener('spa-update-page-url', () => { remove event listeners here})` in the code
429
+ 3. To unload event listeners, use `window.addEventListener('spa-update-page-url', () => { remove event listeners here})` in the code
430
+
431
+ ### Separate client side bundles
432
+
433
+ You can create separate client side bundles in xpine.config.mjs like this:
434
+
435
+ ```
436
+ export default {
437
+ bundles: [
438
+ {
439
+ id: 'site',
440
+ excludePaths: [
441
+ // Excludes SecretPageData
442
+ '/**/pages/secret/**/*.{js,ts,tsx,jsx}',
443
+ '/**/pages/secret/*.{js,ts,tsx,jsx}',
444
+ ],
445
+ },
446
+ {
447
+ id: 'secret-page',
448
+ includePaths: [
449
+ // Only uses Alpine data coming from paths in this directory
450
+ '/**/pages/secret/**/*.{js,ts,tsx,jsx}',
451
+ ],
452
+ requireAuthentication: true, // Only authenticated users can access this .js file
453
+ }
454
+ ]
455
+ }
456
+ ```
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@ import chokidar from "chokidar";
9
9
  import path6 from "path";
10
10
  import fs7 from "fs-extra";
11
11
  import { build } from "esbuild";
12
+ import micromatch from "micromatch";
13
+ import minifyXML from "minify-xml";
12
14
 
13
15
  // src/build/typescript-builder.ts
14
16
  import ts from "typescript";
@@ -30,6 +32,7 @@ function fromRoot(pathName) {
30
32
  }
31
33
  var userConfig = require_default(path.join(process.cwd(), "./xpine.config.mjs")).default;
32
34
  var configDefaults = {
35
+ domain: "",
33
36
  rootDir: fromRoot(),
34
37
  srcDir: fromRoot("./src"),
35
38
  distDir: fromRoot("./dist"),
@@ -77,6 +80,9 @@ var configDefaults = {
77
80
  },
78
81
  get globalCSSFile() {
79
82
  return path.join(this.publicDir, "./styles/global.css");
83
+ },
84
+ get sitemapPath() {
85
+ return path.join(this.distPublicDir, "./sitemap.xml");
80
86
  }
81
87
  };
82
88
  var config = {
@@ -322,6 +328,10 @@ var regex_default = {
322
328
  isDynamicRoute: /\[(.*)\]/g,
323
329
  endsWithTSX: /\.tsx$/,
324
330
  endsWithJSX: /\.jsx$/,
331
+ endsWithJs: /\.js$/,
332
+ endsWithPageFileName: /\.(tsx|jsx)$/,
333
+ endsWithIndexNoExtension: /\/index$/,
334
+ endsWithIndex: /\/index\.(html|tsx|jsx|js|ts)$/,
325
335
  endsWithFileName: /\.(html|tsx|jsx|js|ts)$/,
326
336
  indexFile: /index\.(html|tsx|jsx|js|ts)$/g,
327
337
  catchAllRoute: /\/_all_$/g,
@@ -643,9 +653,29 @@ async function verifyUserMiddleware(req, _res, next) {
643
653
  }
644
654
  next();
645
655
  }
656
+ async function verifyAuthenticatedBundleMiddleware(req, res, next) {
657
+ if (!req?.originalUrl?.startsWith("/scripts/")) {
658
+ next();
659
+ return;
660
+ }
661
+ const bundle = req?.originalUrl?.split("/")?.pop()?.split(".js")?.shift();
662
+ const foundBundle = config?.bundles?.find((bundleItem) => bundleItem?.id === bundle);
663
+ if (!foundBundle) {
664
+ next();
665
+ return;
666
+ }
667
+ if (foundBundle?.requireAuthentication && !req?.user) {
668
+ res.status(403).json({
669
+ message: "Unauthenticated"
670
+ });
671
+ return;
672
+ }
673
+ next();
674
+ }
646
675
  async function createXPineRouter(app, beforeErrorRoute) {
647
- app.use(express.static(config.distPublicDir));
648
676
  app.use(verifyUserMiddleware);
677
+ app.use(verifyAuthenticatedBundleMiddleware);
678
+ app.use(express.static(config.distPublicDir));
649
679
  app.use(requestIP.mw());
650
680
  const { router, routeResults } = await createRouter();
651
681
  app.use(function replaceableRouter(req, res, next) {
@@ -754,15 +784,19 @@ import tailwindPostcss from "@tailwindcss/postcss";
754
784
  async function buildCSS(disableTailwind) {
755
785
  const cssFiles = globSync2(config.srcDir + "/**/*.css");
756
786
  for (const file of cssFiles) {
757
- const fileContents = fs6.readFileSync(file, "utf-8");
758
- let result = fileContents;
759
- if (!disableTailwind) {
760
- result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
761
- result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
787
+ try {
788
+ const fileContents = fs6.readFileSync(file, "utf-8");
789
+ let result = fileContents;
790
+ if (!disableTailwind) {
791
+ result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
792
+ result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
793
+ }
794
+ const newPath = file.replace(config.srcDir, config.distDir);
795
+ fs6.ensureFileSync(newPath);
796
+ fs6.writeFileSync(newPath, result.css);
797
+ } catch (err) {
798
+ console.error(err);
762
799
  }
763
- const newPath = file.replace(config.srcDir, config.distDir);
764
- fs6.ensureFileSync(newPath);
765
- fs6.writeFileSync(newPath, result.css);
766
800
  }
767
801
  logSize(config.distPublicDir, "css");
768
802
  }
@@ -780,14 +814,35 @@ async function buildApp(args) {
780
814
  if (removePreviousBuild) fs7.removeSync(config.distDir);
781
815
  const srcDirFiles = globSync3(config.srcDir + "/**/*.{js,ts,tsx,jsx}");
782
816
  const { componentData, dataFiles } = await buildAppFiles(srcDirFiles, isDev2);
783
- const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
784
- await buildClientSideFiles([alpineDataFile], isDev2);
817
+ const clientSideBundles = [];
818
+ if (config?.bundles?.length) {
819
+ for (const bundle of config.bundles) {
820
+ const matchingComponentData = componentData?.filter((item) => {
821
+ return !micromatch([item.path], bundle?.excludePaths || [])?.length;
822
+ })?.filter((item) => {
823
+ if (!bundle?.includePaths?.length) return true;
824
+ return micromatch([item.path], bundle?.includePaths || [])?.length;
825
+ });
826
+ const matchingDataFiles = dataFiles?.filter((item) => {
827
+ return !micromatch([item.path], bundle?.excludePaths || [])?.length;
828
+ })?.filter((item) => {
829
+ if (!bundle?.includePaths?.length) return true;
830
+ return micromatch([item.path], bundle?.includePaths || [])?.length;
831
+ });
832
+ const alpineDataFile = await buildAlpineDataFile(matchingComponentData, matchingDataFiles, bundle.id);
833
+ await buildClientSideFiles([alpineDataFile], isDev2, `./${bundle.id}.ts`, bundle.id);
834
+ }
835
+ } else {
836
+ const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
837
+ await buildClientSideFiles([alpineDataFile, ...clientSideBundles], isDev2);
838
+ }
785
839
  fs7.removeSync(config.distTempFolder);
786
840
  await buildCSS(disableTailwind);
787
841
  await buildPublicFolderSymlinks();
788
842
  await buildOnLoadFile(componentData, isDev2);
789
843
  if (!isDev2) await triggerXPineOnLoad();
790
844
  if (!isDev2) await buildFilesWithConfigs(componentData);
845
+ if (!isDev2) buildSitemap();
791
846
  if (!isDev2) context.clear();
792
847
  } catch (err) {
793
848
  console.error("Build failed");
@@ -825,8 +880,8 @@ async function buildAppFiles(files, isDev2) {
825
880
  dataFiles
826
881
  };
827
882
  }
828
- async function buildClientSideFiles(alpineDataFiles = [], isDev2) {
829
- const tempFilePath = path6.join(config.distTempFolder, "./app.ts");
883
+ async function buildClientSideFiles(alpineDataFiles = [], isDev2, tempClientSidePath, id) {
884
+ const tempFilePath = path6.join(config.distTempFolder, tempClientSidePath || "./app.ts");
830
885
  fs7.ensureFileSync(tempFilePath);
831
886
  const pagesScriptsGlob = config.publicDir + "/scripts/pages/**/*.{js,ts}";
832
887
  const clientFiles = globSync3(
@@ -853,7 +908,7 @@ async function buildClientSideFiles(alpineDataFiles = [], isDev2) {
853
908
  minify: !isDev2,
854
909
  sourcemap: isDev2 ? "inline" : false
855
910
  });
856
- await logSize(config.distPublicDir, "client");
911
+ await logSize(config.distPublicDir, id ? `client bundle: ${id}.js` : "client bundle: app.js");
857
912
  }
858
913
  function writeDevServerClientSideCode(tempFilePath) {
859
914
  const devServerPath = path6.join(xpineDistDir, "./src/static/dev-server.js");
@@ -865,7 +920,7 @@ function writeSpaClientSideCode(tempFilePath) {
865
920
  const content = fs7.readFileSync(spaPath, "utf-8");
866
921
  fs7.appendFileSync(tempFilePath, "\n" + content);
867
922
  }
868
- async function buildAlpineDataFile(componentData, dataFiles) {
923
+ async function buildAlpineDataFile(componentData, dataFiles, bundleID) {
869
924
  const output = {
870
925
  imports: [
871
926
  "import Alpine from 'alpinejs';"
@@ -909,9 +964,16 @@ async function buildAlpineDataFile(componentData, dataFiles) {
909
964
  output.code.push(`Alpine.data('${dataFunction.name}', ${dataFunction.name});`);
910
965
  }
911
966
  const result = output.imports.join("\n") + "\n" + output.code.join("\n") + "\n" + output.end.join("\n");
912
- fs7.ensureFileSync(config.alpineDataPath);
913
- fs7.writeFileSync(config.alpineDataPath, result);
914
- return config.alpineDataPath;
967
+ if (bundleID) {
968
+ const bundlePath = path6.join(config.distTempFolder, `./alpine-data-${bundleID}.ts`);
969
+ fs7.ensureFileSync(bundlePath);
970
+ fs7.writeFileSync(bundlePath, result);
971
+ return bundlePath;
972
+ } else {
973
+ fs7.ensureFileSync(config.alpineDataPath);
974
+ fs7.writeFileSync(config.alpineDataPath, result);
975
+ return config.alpineDataPath;
976
+ }
915
977
  }
916
978
  async function buildPublicFolderSymlinks() {
917
979
  const files = globSync3(config.publicDir + "/**/*.*", {
@@ -975,17 +1037,28 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
975
1037
  const componentDynamicPaths = getComponentDynamicPaths(componentFileName);
976
1038
  const componentFn = componentImport.default;
977
1039
  if (componentImport?.onInit) await componentImport.onInit();
978
- const outputPath = componentDynamicPaths?.length ? componentDynamicPaths.reduce((total, current) => {
979
- return total.replace(`/[${current}]`, "");
980
- }, path6.dirname(builtComponentPath)) : path6.dirname(builtComponentPath);
1040
+ const outputPath = determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths);
1041
+ if (!outputPath) return;
981
1042
  if (typeof config2?.staticPaths === "boolean") {
982
1043
  const urlPath = filePathToURLPath(outputPath);
983
1044
  try {
984
- const req = { params: {} };
985
- const data = config2?.data ? await config2.data(req) : null;
986
- const staticComponentOutput = await componentFn({ data, routePath: urlPath });
1045
+ const req = {
1046
+ params: {},
1047
+ route: {
1048
+ path: urlPath
1049
+ },
1050
+ url: urlPath
1051
+ };
1052
+ let data = config2?.data ? await config2.data(req) : null;
1053
+ data = {
1054
+ ...data,
1055
+ routePath: urlPath
1056
+ };
1057
+ const staticComponentOutput = await componentFn({ data, routePath: urlPath, req });
1058
+ const htmlOutputPath = path6.join(outputPath, "./index.html");
1059
+ fs7.ensureFileSync(htmlOutputPath);
987
1060
  fs7.writeFileSync(
988
- path6.join(outputPath, "./index.html"),
1061
+ htmlOutputPath,
989
1062
  doctypeHTML + (config2?.wrapper ? await config2.wrapper({ req, children: staticComponentOutput, config: config2, data, routePath: urlPath }) : staticComponentOutput) + staticComment
990
1063
  );
991
1064
  } catch (err) {
@@ -996,14 +1069,19 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
996
1069
  const dynamicPaths = await config2.staticPaths();
997
1070
  for (const dynamicPath of dynamicPaths) {
998
1071
  try {
1072
+ const updatedOutDir = path6.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
1073
+ const urlPath = filePathToURLPath(updatedOutDir);
999
1074
  const req = {
1000
1075
  params: {
1001
1076
  ...componentDynamicPaths?.length ? dynamicPath : {}
1002
- }
1077
+ },
1078
+ route: {
1079
+ path: urlPath
1080
+ },
1081
+ url: urlPath
1003
1082
  };
1004
- const updatedOutDir = path6.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
1005
- const urlPath = filePathToURLPath(updatedOutDir);
1006
- const data = config2?.data ? await config2.data(req) : null;
1083
+ let data = config2?.data ? await config2.data(req) : null;
1084
+ data = { ...data, routePath: urlPath };
1007
1085
  const staticComponentOutput = await componentFn({ req, data, routePath: urlPath });
1008
1086
  fs7.ensureDirSync(updatedOutDir);
1009
1087
  fs7.writeFileSync(
@@ -1017,6 +1095,20 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
1017
1095
  }
1018
1096
  }
1019
1097
  }
1098
+ function determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths) {
1099
+ if (componentDynamicPaths?.length) {
1100
+ return componentDynamicPaths.reduce((total, current) => {
1101
+ return total.replace(`/[${current}]`, "");
1102
+ }, path6.dirname(builtComponentPath));
1103
+ } else {
1104
+ if (builtComponentPath?.match(regex_default.endsWithIndex)) {
1105
+ return path6.dirname(builtComponentPath);
1106
+ } else {
1107
+ if (!builtComponentPath.match(regex_default.endsWithJs)) return null;
1108
+ return builtComponentPath.replace(regex_default.endsWithJs, "");
1109
+ }
1110
+ }
1111
+ }
1020
1112
  function getComponentDynamicPaths(componentPath) {
1021
1113
  const matches = [...componentPath.matchAll(regex_default.dynamicRoutes)].concat([...componentPath.matchAll(regex_default.catchAllRoute)]);
1022
1114
  if (!matches?.length) return null;
@@ -1073,6 +1165,47 @@ async function buildOnLoadFile(componentData, isDev2) {
1073
1165
  ]
1074
1166
  });
1075
1167
  }
1168
+ async function buildSitemap() {
1169
+ if (!config?.sitemap) return;
1170
+ const staticPages = globSync3(`${config.distPagesDir}/**/*.html`) || [];
1171
+ const dynamicPages = globSync3(`${config.pagesDir}/**/*.{jsx,tsx}`) || [];
1172
+ const allPages = staticPages.concat(dynamicPages);
1173
+ const filteredPages = allPages.filter((filePath) => {
1174
+ return !micromatch([filePath], config.sitemap?.excludePaths || [])?.length;
1175
+ });
1176
+ const pages = filteredPages.map((filePath) => {
1177
+ const replacedExtensions = filePath.replace(regex_default.endsWithFileName, "");
1178
+ const replacedPagesPath = replacedExtensions.replace(config.pagesDir, "");
1179
+ const replacedDistPath = replacedPagesPath.replace(config.distPagesDir, "");
1180
+ const replacedIndex = replacedDistPath.replace(regex_default.endsWithIndexNoExtension, "");
1181
+ if (replacedIndex === "") return "/";
1182
+ return replacedIndex;
1183
+ }).filter((filePath) => {
1184
+ return !filePath.includes("+config");
1185
+ });
1186
+ const sitemap = `
1187
+ <?xml version="1.0" encoding="UTF-8"?>
1188
+ <urlset
1189
+ xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
1190
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1191
+ xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
1192
+ xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
1193
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
1194
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
1195
+ xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
1196
+ >
1197
+ ${pages.map((page) => {
1198
+ return `
1199
+ <url>
1200
+ <loc>${config?.domain ? `https://${config.domain}${page}` : page}</loc>
1201
+ </url>
1202
+ `;
1203
+ }).join("\n")}
1204
+ </urlset>
1205
+ `;
1206
+ fs7.ensureFileSync(config.sitemapPath);
1207
+ fs7.writeFileSync(config.sitemapPath, minifyXML(sitemap));
1208
+ }
1076
1209
 
1077
1210
  // src/runDevServer.ts
1078
1211
  import path8 from "path";
@@ -1229,12 +1362,14 @@ export {
1229
1362
  buildFilesWithConfigs,
1230
1363
  buildOnLoadFile,
1231
1364
  buildPublicFolderSymlinks,
1365
+ buildSitemap,
1232
1366
  buildStaticFiles,
1233
1367
  config,
1234
1368
  context,
1235
1369
  createContext,
1236
1370
  createRouter,
1237
1371
  createXPineRouter,
1372
+ determineOutputPathForStaticPath,
1238
1373
  filePathToURLPath,
1239
1374
  fromRoot,
1240
1375
  getComponentDynamicPaths,
@@ -1 +1 @@
1
- {"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../../../src/build/css.ts"],"names":[],"mappings":"AAUA,wBAAsB,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,iBAgBvD"}
1
+ {"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../../../src/build/css.ts"],"names":[],"mappings":"AAUA,wBAAsB,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,iBAoBvD"}
@@ -1 +1 @@
1
- {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAgB,OAAO,EAAqB,MAAM,SAAS,CAAC;AAqJ5E,wBAAsB,YAAY;;;GAkCjC;AAiBD,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,iBAyB1F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,KAAK,CAanG;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,UAQzE"}
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAgB,OAAO,EAAqB,MAAM,SAAS,CAAC;AAqJ5E,wBAAsB,YAAY;;;GAkCjC;AAsCD,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,iBA0B1F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,KAAK,CAanG;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,UAQzE"}
@@ -6,14 +6,16 @@ type BuildAppArgs = {
6
6
  };
7
7
  export declare function buildApp(args: BuildAppArgs): Promise<void>;
8
8
  export declare function buildPublicFolderSymlinks(): Promise<void>;
9
- export declare function logSize(pathName: string, type: 'app' | 'client' | 'css', validExtensions?: string[]): Promise<void>;
9
+ export declare function logSize(pathName: string, type: string, validExtensions?: string[]): Promise<void>;
10
10
  export declare function buildFilesWithConfigs(componentData: ComponentData[]): Promise<void>;
11
11
  export declare function buildStaticFiles(config: ConfigFile, component: ComponentData, componentImport: any, builtComponentPath: string): Promise<void>;
12
+ export declare function determineOutputPathForStaticPath(builtComponentPath: string, componentDynamicPaths?: string[]): string;
12
13
  export declare function getComponentDynamicPaths(componentPath: string): string[];
13
14
  export type OnLoadFileResult = {
14
15
  imports: string;
15
16
  fn: string;
16
17
  };
17
18
  export declare function buildOnLoadFile(componentData: ComponentData[], isDev?: boolean): Promise<void>;
19
+ export declare function buildSitemap(): Promise<void>;
18
20
  export {};
19
21
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/scripts/build.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,UAAU,EAA2B,aAAa,EAAE,MAAM,aAAa,CAAC;AAajF,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAA;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,iBAsBhD;AAiJD,wBAAsB,yBAAyB,kBAgB9C;AAED,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,EAAE,eAAe,WAAkB,iBAahH;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,aAAa,EAAE,iBAezE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,GAAG,EAAE,kBAAkB,EAAE,MAAM,iBAyEpI;AAED,wBAAgB,wBAAwB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CAQxE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAA;AAGD,wBAAsB,eAAe,CAAC,aAAa,EAAE,aAAa,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,iBAoDpF"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/scripts/build.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,UAAU,EAA2B,aAAa,EAAE,MAAM,aAAa,CAAC;AAajF,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAA;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,iBAgDhD;AAwJD,wBAAsB,yBAAyB,kBAgB9C;AAED,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,WAAkB,iBAa9F;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,aAAa,EAAE,iBAezE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,GAAG,EAAE,kBAAkB,EAAE,MAAM,iBA2FpI;AAED,wBAAgB,gCAAgC,CAAC,kBAAkB,EAAE,MAAM,EAAE,qBAAqB,CAAC,EAAE,MAAM,EAAE,UAe5G;AAED,wBAAgB,wBAAwB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CAQxE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAA;AAGD,wBAAsB,eAAe,CAAC,aAAa,EAAE,aAAa,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,iBAoDpF;AAED,wBAAsB,YAAY,kBA0CjC"}
@@ -4,6 +4,8 @@
4
4
  import path5 from "path";
5
5
  import fs7 from "fs-extra";
6
6
  import { build } from "esbuild";
7
+ import micromatch from "micromatch";
8
+ import minifyXML from "minify-xml";
7
9
 
8
10
  // src/build/typescript-builder.ts
9
11
  import ts from "typescript";
@@ -25,6 +27,7 @@ function fromRoot(pathName) {
25
27
  }
26
28
  var userConfig = require_default(path.join(process.cwd(), "./xpine.config.mjs")).default;
27
29
  var configDefaults = {
30
+ domain: "",
28
31
  rootDir: fromRoot(),
29
32
  srcDir: fromRoot("./src"),
30
33
  distDir: fromRoot("./dist"),
@@ -72,6 +75,9 @@ var configDefaults = {
72
75
  },
73
76
  get globalCSSFile() {
74
77
  return path.join(this.publicDir, "./styles/global.css");
78
+ },
79
+ get sitemapPath() {
80
+ return path.join(this.distPublicDir, "./sitemap.xml");
75
81
  }
76
82
  };
77
83
  var config = {
@@ -317,6 +323,10 @@ var regex_default = {
317
323
  isDynamicRoute: /\[(.*)\]/g,
318
324
  endsWithTSX: /\.tsx$/,
319
325
  endsWithJSX: /\.jsx$/,
326
+ endsWithJs: /\.js$/,
327
+ endsWithPageFileName: /\.(tsx|jsx)$/,
328
+ endsWithIndexNoExtension: /\/index$/,
329
+ endsWithIndex: /\/index\.(html|tsx|jsx|js|ts)$/,
320
330
  endsWithFileName: /\.(html|tsx|jsx|js|ts)$/,
321
331
  indexFile: /index\.(html|tsx|jsx|js|ts)$/g,
322
332
  catchAllRoute: /\/_all_$/g,
@@ -547,15 +557,19 @@ import tailwindPostcss from "@tailwindcss/postcss";
547
557
  async function buildCSS(disableTailwind2) {
548
558
  const cssFiles = globSync2(config.srcDir + "/**/*.css");
549
559
  for (const file of cssFiles) {
550
- const fileContents = fs6.readFileSync(file, "utf-8");
551
- let result = fileContents;
552
- if (!disableTailwind2) {
553
- result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
554
- result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
560
+ try {
561
+ const fileContents = fs6.readFileSync(file, "utf-8");
562
+ let result = fileContents;
563
+ if (!disableTailwind2) {
564
+ result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
565
+ result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
566
+ }
567
+ const newPath = file.replace(config.srcDir, config.distDir);
568
+ fs6.ensureFileSync(newPath);
569
+ fs6.writeFileSync(newPath, result.css);
570
+ } catch (err) {
571
+ console.error(err);
555
572
  }
556
- const newPath = file.replace(config.srcDir, config.distDir);
557
- fs6.ensureFileSync(newPath);
558
- fs6.writeFileSync(newPath, result.css);
559
573
  }
560
574
  logSize(config.distPublicDir, "css");
561
575
  }
@@ -573,14 +587,35 @@ async function buildApp(args) {
573
587
  if (removePreviousBuild2) fs7.removeSync(config.distDir);
574
588
  const srcDirFiles = globSync3(config.srcDir + "/**/*.{js,ts,tsx,jsx}");
575
589
  const { componentData, dataFiles } = await buildAppFiles(srcDirFiles, isDev3);
576
- const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
577
- await buildClientSideFiles([alpineDataFile], isDev3);
590
+ const clientSideBundles = [];
591
+ if (config?.bundles?.length) {
592
+ for (const bundle of config.bundles) {
593
+ const matchingComponentData = componentData?.filter((item) => {
594
+ return !micromatch([item.path], bundle?.excludePaths || [])?.length;
595
+ })?.filter((item) => {
596
+ if (!bundle?.includePaths?.length) return true;
597
+ return micromatch([item.path], bundle?.includePaths || [])?.length;
598
+ });
599
+ const matchingDataFiles = dataFiles?.filter((item) => {
600
+ return !micromatch([item.path], bundle?.excludePaths || [])?.length;
601
+ })?.filter((item) => {
602
+ if (!bundle?.includePaths?.length) return true;
603
+ return micromatch([item.path], bundle?.includePaths || [])?.length;
604
+ });
605
+ const alpineDataFile = await buildAlpineDataFile(matchingComponentData, matchingDataFiles, bundle.id);
606
+ await buildClientSideFiles([alpineDataFile], isDev3, `./${bundle.id}.ts`, bundle.id);
607
+ }
608
+ } else {
609
+ const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
610
+ await buildClientSideFiles([alpineDataFile, ...clientSideBundles], isDev3);
611
+ }
578
612
  fs7.removeSync(config.distTempFolder);
579
613
  await buildCSS(disableTailwind2);
580
614
  await buildPublicFolderSymlinks();
581
615
  await buildOnLoadFile(componentData, isDev3);
582
616
  if (!isDev3) await triggerXPineOnLoad();
583
617
  if (!isDev3) await buildFilesWithConfigs(componentData);
618
+ if (!isDev3) buildSitemap();
584
619
  if (!isDev3) context.clear();
585
620
  } catch (err) {
586
621
  console.error("Build failed");
@@ -618,8 +653,8 @@ async function buildAppFiles(files, isDev3) {
618
653
  dataFiles
619
654
  };
620
655
  }
621
- async function buildClientSideFiles(alpineDataFiles = [], isDev3) {
622
- const tempFilePath = path5.join(config.distTempFolder, "./app.ts");
656
+ async function buildClientSideFiles(alpineDataFiles = [], isDev3, tempClientSidePath, id) {
657
+ const tempFilePath = path5.join(config.distTempFolder, tempClientSidePath || "./app.ts");
623
658
  fs7.ensureFileSync(tempFilePath);
624
659
  const pagesScriptsGlob = config.publicDir + "/scripts/pages/**/*.{js,ts}";
625
660
  const clientFiles = globSync3(
@@ -646,7 +681,7 @@ async function buildClientSideFiles(alpineDataFiles = [], isDev3) {
646
681
  minify: !isDev3,
647
682
  sourcemap: isDev3 ? "inline" : false
648
683
  });
649
- await logSize(config.distPublicDir, "client");
684
+ await logSize(config.distPublicDir, id ? `client bundle: ${id}.js` : "client bundle: app.js");
650
685
  }
651
686
  function writeDevServerClientSideCode(tempFilePath) {
652
687
  const devServerPath = path5.join(xpineDistDir, "./src/static/dev-server.js");
@@ -658,7 +693,7 @@ function writeSpaClientSideCode(tempFilePath) {
658
693
  const content = fs7.readFileSync(spaPath, "utf-8");
659
694
  fs7.appendFileSync(tempFilePath, "\n" + content);
660
695
  }
661
- async function buildAlpineDataFile(componentData, dataFiles) {
696
+ async function buildAlpineDataFile(componentData, dataFiles, bundleID) {
662
697
  const output = {
663
698
  imports: [
664
699
  "import Alpine from 'alpinejs';"
@@ -702,9 +737,16 @@ async function buildAlpineDataFile(componentData, dataFiles) {
702
737
  output.code.push(`Alpine.data('${dataFunction.name}', ${dataFunction.name});`);
703
738
  }
704
739
  const result = output.imports.join("\n") + "\n" + output.code.join("\n") + "\n" + output.end.join("\n");
705
- fs7.ensureFileSync(config.alpineDataPath);
706
- fs7.writeFileSync(config.alpineDataPath, result);
707
- return config.alpineDataPath;
740
+ if (bundleID) {
741
+ const bundlePath = path5.join(config.distTempFolder, `./alpine-data-${bundleID}.ts`);
742
+ fs7.ensureFileSync(bundlePath);
743
+ fs7.writeFileSync(bundlePath, result);
744
+ return bundlePath;
745
+ } else {
746
+ fs7.ensureFileSync(config.alpineDataPath);
747
+ fs7.writeFileSync(config.alpineDataPath, result);
748
+ return config.alpineDataPath;
749
+ }
708
750
  }
709
751
  async function buildPublicFolderSymlinks() {
710
752
  const files = globSync3(config.publicDir + "/**/*.*", {
@@ -768,17 +810,28 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
768
810
  const componentDynamicPaths = getComponentDynamicPaths(componentFileName);
769
811
  const componentFn = componentImport.default;
770
812
  if (componentImport?.onInit) await componentImport.onInit();
771
- const outputPath = componentDynamicPaths?.length ? componentDynamicPaths.reduce((total, current) => {
772
- return total.replace(`/[${current}]`, "");
773
- }, path5.dirname(builtComponentPath)) : path5.dirname(builtComponentPath);
813
+ const outputPath = determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths);
814
+ if (!outputPath) return;
774
815
  if (typeof config2?.staticPaths === "boolean") {
775
816
  const urlPath = filePathToURLPath(outputPath);
776
817
  try {
777
- const req = { params: {} };
778
- const data = config2?.data ? await config2.data(req) : null;
779
- const staticComponentOutput = await componentFn({ data, routePath: urlPath });
818
+ const req = {
819
+ params: {},
820
+ route: {
821
+ path: urlPath
822
+ },
823
+ url: urlPath
824
+ };
825
+ let data = config2?.data ? await config2.data(req) : null;
826
+ data = {
827
+ ...data,
828
+ routePath: urlPath
829
+ };
830
+ const staticComponentOutput = await componentFn({ data, routePath: urlPath, req });
831
+ const htmlOutputPath = path5.join(outputPath, "./index.html");
832
+ fs7.ensureFileSync(htmlOutputPath);
780
833
  fs7.writeFileSync(
781
- path5.join(outputPath, "./index.html"),
834
+ htmlOutputPath,
782
835
  doctypeHTML + (config2?.wrapper ? await config2.wrapper({ req, children: staticComponentOutput, config: config2, data, routePath: urlPath }) : staticComponentOutput) + staticComment
783
836
  );
784
837
  } catch (err) {
@@ -789,14 +842,19 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
789
842
  const dynamicPaths = await config2.staticPaths();
790
843
  for (const dynamicPath of dynamicPaths) {
791
844
  try {
845
+ const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
846
+ const urlPath = filePathToURLPath(updatedOutDir);
792
847
  const req = {
793
848
  params: {
794
849
  ...componentDynamicPaths?.length ? dynamicPath : {}
795
- }
850
+ },
851
+ route: {
852
+ path: urlPath
853
+ },
854
+ url: urlPath
796
855
  };
797
- const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
798
- const urlPath = filePathToURLPath(updatedOutDir);
799
- const data = config2?.data ? await config2.data(req) : null;
856
+ let data = config2?.data ? await config2.data(req) : null;
857
+ data = { ...data, routePath: urlPath };
800
858
  const staticComponentOutput = await componentFn({ req, data, routePath: urlPath });
801
859
  fs7.ensureDirSync(updatedOutDir);
802
860
  fs7.writeFileSync(
@@ -810,6 +868,20 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
810
868
  }
811
869
  }
812
870
  }
871
+ function determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths) {
872
+ if (componentDynamicPaths?.length) {
873
+ return componentDynamicPaths.reduce((total, current) => {
874
+ return total.replace(`/[${current}]`, "");
875
+ }, path5.dirname(builtComponentPath));
876
+ } else {
877
+ if (builtComponentPath?.match(regex_default.endsWithIndex)) {
878
+ return path5.dirname(builtComponentPath);
879
+ } else {
880
+ if (!builtComponentPath.match(regex_default.endsWithJs)) return null;
881
+ return builtComponentPath.replace(regex_default.endsWithJs, "");
882
+ }
883
+ }
884
+ }
813
885
  function getComponentDynamicPaths(componentPath) {
814
886
  const matches = [...componentPath.matchAll(regex_default.dynamicRoutes)].concat([...componentPath.matchAll(regex_default.catchAllRoute)]);
815
887
  if (!matches?.length) return null;
@@ -866,6 +938,47 @@ async function buildOnLoadFile(componentData, isDev3) {
866
938
  ]
867
939
  });
868
940
  }
941
+ async function buildSitemap() {
942
+ if (!config?.sitemap) return;
943
+ const staticPages = globSync3(`${config.distPagesDir}/**/*.html`) || [];
944
+ const dynamicPages = globSync3(`${config.pagesDir}/**/*.{jsx,tsx}`) || [];
945
+ const allPages = staticPages.concat(dynamicPages);
946
+ const filteredPages = allPages.filter((filePath) => {
947
+ return !micromatch([filePath], config.sitemap?.excludePaths || [])?.length;
948
+ });
949
+ const pages = filteredPages.map((filePath) => {
950
+ const replacedExtensions = filePath.replace(regex_default.endsWithFileName, "");
951
+ const replacedPagesPath = replacedExtensions.replace(config.pagesDir, "");
952
+ const replacedDistPath = replacedPagesPath.replace(config.distPagesDir, "");
953
+ const replacedIndex = replacedDistPath.replace(regex_default.endsWithIndexNoExtension, "");
954
+ if (replacedIndex === "") return "/";
955
+ return replacedIndex;
956
+ }).filter((filePath) => {
957
+ return !filePath.includes("+config");
958
+ });
959
+ const sitemap = `
960
+ <?xml version="1.0" encoding="UTF-8"?>
961
+ <urlset
962
+ xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
963
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
964
+ xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
965
+ xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
966
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
967
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
968
+ xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
969
+ >
970
+ ${pages.map((page) => {
971
+ return `
972
+ <url>
973
+ <loc>${config?.domain ? `https://${config.domain}${page}` : page}</loc>
974
+ </url>
975
+ `;
976
+ }).join("\n")}
977
+ </urlset>
978
+ `;
979
+ fs7.ensureFileSync(config.sitemapPath);
980
+ fs7.writeFileSync(config.sitemapPath, minifyXML(sitemap));
981
+ }
869
982
 
870
983
  // src/scripts/xpine-build.ts
871
984
  import yargs from "yargs";
@@ -11,6 +11,8 @@ import chokidar from "chokidar";
11
11
  import path5 from "path";
12
12
  import fs7 from "fs-extra";
13
13
  import { build } from "esbuild";
14
+ import micromatch from "micromatch";
15
+ import minifyXML from "minify-xml";
14
16
 
15
17
  // src/build/typescript-builder.ts
16
18
  import ts from "typescript";
@@ -32,6 +34,7 @@ function fromRoot(pathName) {
32
34
  }
33
35
  var userConfig = require_default(path.join(process.cwd(), "./xpine.config.mjs")).default;
34
36
  var configDefaults = {
37
+ domain: "",
35
38
  rootDir: fromRoot(),
36
39
  srcDir: fromRoot("./src"),
37
40
  distDir: fromRoot("./dist"),
@@ -79,6 +82,9 @@ var configDefaults = {
79
82
  },
80
83
  get globalCSSFile() {
81
84
  return path.join(this.publicDir, "./styles/global.css");
85
+ },
86
+ get sitemapPath() {
87
+ return path.join(this.distPublicDir, "./sitemap.xml");
82
88
  }
83
89
  };
84
90
  var config = {
@@ -324,6 +330,10 @@ var regex_default = {
324
330
  isDynamicRoute: /\[(.*)\]/g,
325
331
  endsWithTSX: /\.tsx$/,
326
332
  endsWithJSX: /\.jsx$/,
333
+ endsWithJs: /\.js$/,
334
+ endsWithPageFileName: /\.(tsx|jsx)$/,
335
+ endsWithIndexNoExtension: /\/index$/,
336
+ endsWithIndex: /\/index\.(html|tsx|jsx|js|ts)$/,
327
337
  endsWithFileName: /\.(html|tsx|jsx|js|ts)$/,
328
338
  indexFile: /index\.(html|tsx|jsx|js|ts)$/g,
329
339
  catchAllRoute: /\/_all_$/g,
@@ -554,15 +564,19 @@ import tailwindPostcss from "@tailwindcss/postcss";
554
564
  async function buildCSS(disableTailwind) {
555
565
  const cssFiles = globSync2(config.srcDir + "/**/*.css");
556
566
  for (const file of cssFiles) {
557
- const fileContents = fs6.readFileSync(file, "utf-8");
558
- let result = fileContents;
559
- if (!disableTailwind) {
560
- result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
561
- result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
567
+ try {
568
+ const fileContents = fs6.readFileSync(file, "utf-8");
569
+ let result = fileContents;
570
+ if (!disableTailwind) {
571
+ result = await postcss([add_breakpoint_data_default()]).process(fileContents, { from: file });
572
+ result = await postcss([tailwindPostcss(), remove_layers_default()]).process(result.css, { from: file });
573
+ }
574
+ const newPath = file.replace(config.srcDir, config.distDir);
575
+ fs6.ensureFileSync(newPath);
576
+ fs6.writeFileSync(newPath, result.css);
577
+ } catch (err) {
578
+ console.error(err);
562
579
  }
563
- const newPath = file.replace(config.srcDir, config.distDir);
564
- fs6.ensureFileSync(newPath);
565
- fs6.writeFileSync(newPath, result.css);
566
580
  }
567
581
  logSize(config.distPublicDir, "css");
568
582
  }
@@ -580,14 +594,35 @@ async function buildApp(args) {
580
594
  if (removePreviousBuild) fs7.removeSync(config.distDir);
581
595
  const srcDirFiles = globSync3(config.srcDir + "/**/*.{js,ts,tsx,jsx}");
582
596
  const { componentData, dataFiles } = await buildAppFiles(srcDirFiles, isDev2);
583
- const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
584
- await buildClientSideFiles([alpineDataFile], isDev2);
597
+ const clientSideBundles = [];
598
+ if (config?.bundles?.length) {
599
+ for (const bundle of config.bundles) {
600
+ const matchingComponentData = componentData?.filter((item) => {
601
+ return !micromatch([item.path], bundle?.excludePaths || [])?.length;
602
+ })?.filter((item) => {
603
+ if (!bundle?.includePaths?.length) return true;
604
+ return micromatch([item.path], bundle?.includePaths || [])?.length;
605
+ });
606
+ const matchingDataFiles = dataFiles?.filter((item) => {
607
+ return !micromatch([item.path], bundle?.excludePaths || [])?.length;
608
+ })?.filter((item) => {
609
+ if (!bundle?.includePaths?.length) return true;
610
+ return micromatch([item.path], bundle?.includePaths || [])?.length;
611
+ });
612
+ const alpineDataFile = await buildAlpineDataFile(matchingComponentData, matchingDataFiles, bundle.id);
613
+ await buildClientSideFiles([alpineDataFile], isDev2, `./${bundle.id}.ts`, bundle.id);
614
+ }
615
+ } else {
616
+ const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
617
+ await buildClientSideFiles([alpineDataFile, ...clientSideBundles], isDev2);
618
+ }
585
619
  fs7.removeSync(config.distTempFolder);
586
620
  await buildCSS(disableTailwind);
587
621
  await buildPublicFolderSymlinks();
588
622
  await buildOnLoadFile(componentData, isDev2);
589
623
  if (!isDev2) await triggerXPineOnLoad();
590
624
  if (!isDev2) await buildFilesWithConfigs(componentData);
625
+ if (!isDev2) buildSitemap();
591
626
  if (!isDev2) context.clear();
592
627
  } catch (err) {
593
628
  console.error("Build failed");
@@ -625,8 +660,8 @@ async function buildAppFiles(files, isDev2) {
625
660
  dataFiles
626
661
  };
627
662
  }
628
- async function buildClientSideFiles(alpineDataFiles = [], isDev2) {
629
- const tempFilePath = path5.join(config.distTempFolder, "./app.ts");
663
+ async function buildClientSideFiles(alpineDataFiles = [], isDev2, tempClientSidePath, id) {
664
+ const tempFilePath = path5.join(config.distTempFolder, tempClientSidePath || "./app.ts");
630
665
  fs7.ensureFileSync(tempFilePath);
631
666
  const pagesScriptsGlob = config.publicDir + "/scripts/pages/**/*.{js,ts}";
632
667
  const clientFiles = globSync3(
@@ -653,7 +688,7 @@ async function buildClientSideFiles(alpineDataFiles = [], isDev2) {
653
688
  minify: !isDev2,
654
689
  sourcemap: isDev2 ? "inline" : false
655
690
  });
656
- await logSize(config.distPublicDir, "client");
691
+ await logSize(config.distPublicDir, id ? `client bundle: ${id}.js` : "client bundle: app.js");
657
692
  }
658
693
  function writeDevServerClientSideCode(tempFilePath) {
659
694
  const devServerPath = path5.join(xpineDistDir, "./src/static/dev-server.js");
@@ -665,7 +700,7 @@ function writeSpaClientSideCode(tempFilePath) {
665
700
  const content = fs7.readFileSync(spaPath, "utf-8");
666
701
  fs7.appendFileSync(tempFilePath, "\n" + content);
667
702
  }
668
- async function buildAlpineDataFile(componentData, dataFiles) {
703
+ async function buildAlpineDataFile(componentData, dataFiles, bundleID) {
669
704
  const output = {
670
705
  imports: [
671
706
  "import Alpine from 'alpinejs';"
@@ -709,9 +744,16 @@ async function buildAlpineDataFile(componentData, dataFiles) {
709
744
  output.code.push(`Alpine.data('${dataFunction.name}', ${dataFunction.name});`);
710
745
  }
711
746
  const result = output.imports.join("\n") + "\n" + output.code.join("\n") + "\n" + output.end.join("\n");
712
- fs7.ensureFileSync(config.alpineDataPath);
713
- fs7.writeFileSync(config.alpineDataPath, result);
714
- return config.alpineDataPath;
747
+ if (bundleID) {
748
+ const bundlePath = path5.join(config.distTempFolder, `./alpine-data-${bundleID}.ts`);
749
+ fs7.ensureFileSync(bundlePath);
750
+ fs7.writeFileSync(bundlePath, result);
751
+ return bundlePath;
752
+ } else {
753
+ fs7.ensureFileSync(config.alpineDataPath);
754
+ fs7.writeFileSync(config.alpineDataPath, result);
755
+ return config.alpineDataPath;
756
+ }
715
757
  }
716
758
  async function buildPublicFolderSymlinks() {
717
759
  const files = globSync3(config.publicDir + "/**/*.*", {
@@ -775,17 +817,28 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
775
817
  const componentDynamicPaths = getComponentDynamicPaths(componentFileName);
776
818
  const componentFn = componentImport.default;
777
819
  if (componentImport?.onInit) await componentImport.onInit();
778
- const outputPath = componentDynamicPaths?.length ? componentDynamicPaths.reduce((total, current) => {
779
- return total.replace(`/[${current}]`, "");
780
- }, path5.dirname(builtComponentPath)) : path5.dirname(builtComponentPath);
820
+ const outputPath = determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths);
821
+ if (!outputPath) return;
781
822
  if (typeof config2?.staticPaths === "boolean") {
782
823
  const urlPath = filePathToURLPath(outputPath);
783
824
  try {
784
- const req = { params: {} };
785
- const data = config2?.data ? await config2.data(req) : null;
786
- const staticComponentOutput = await componentFn({ data, routePath: urlPath });
825
+ const req = {
826
+ params: {},
827
+ route: {
828
+ path: urlPath
829
+ },
830
+ url: urlPath
831
+ };
832
+ let data = config2?.data ? await config2.data(req) : null;
833
+ data = {
834
+ ...data,
835
+ routePath: urlPath
836
+ };
837
+ const staticComponentOutput = await componentFn({ data, routePath: urlPath, req });
838
+ const htmlOutputPath = path5.join(outputPath, "./index.html");
839
+ fs7.ensureFileSync(htmlOutputPath);
787
840
  fs7.writeFileSync(
788
- path5.join(outputPath, "./index.html"),
841
+ htmlOutputPath,
789
842
  doctypeHTML + (config2?.wrapper ? await config2.wrapper({ req, children: staticComponentOutput, config: config2, data, routePath: urlPath }) : staticComponentOutput) + staticComment
790
843
  );
791
844
  } catch (err) {
@@ -796,14 +849,19 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
796
849
  const dynamicPaths = await config2.staticPaths();
797
850
  for (const dynamicPath of dynamicPaths) {
798
851
  try {
852
+ const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
853
+ const urlPath = filePathToURLPath(updatedOutDir);
799
854
  const req = {
800
855
  params: {
801
856
  ...componentDynamicPaths?.length ? dynamicPath : {}
802
- }
857
+ },
858
+ route: {
859
+ path: urlPath
860
+ },
861
+ url: urlPath
803
862
  };
804
- const updatedOutDir = path5.join(outputPath, `./${componentDynamicPaths.map((key) => dynamicPath[key]).join("/")}`);
805
- const urlPath = filePathToURLPath(updatedOutDir);
806
- const data = config2?.data ? await config2.data(req) : null;
863
+ let data = config2?.data ? await config2.data(req) : null;
864
+ data = { ...data, routePath: urlPath };
807
865
  const staticComponentOutput = await componentFn({ req, data, routePath: urlPath });
808
866
  fs7.ensureDirSync(updatedOutDir);
809
867
  fs7.writeFileSync(
@@ -817,6 +875,20 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
817
875
  }
818
876
  }
819
877
  }
878
+ function determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths) {
879
+ if (componentDynamicPaths?.length) {
880
+ return componentDynamicPaths.reduce((total, current) => {
881
+ return total.replace(`/[${current}]`, "");
882
+ }, path5.dirname(builtComponentPath));
883
+ } else {
884
+ if (builtComponentPath?.match(regex_default.endsWithIndex)) {
885
+ return path5.dirname(builtComponentPath);
886
+ } else {
887
+ if (!builtComponentPath.match(regex_default.endsWithJs)) return null;
888
+ return builtComponentPath.replace(regex_default.endsWithJs, "");
889
+ }
890
+ }
891
+ }
820
892
  function getComponentDynamicPaths(componentPath) {
821
893
  const matches = [...componentPath.matchAll(regex_default.dynamicRoutes)].concat([...componentPath.matchAll(regex_default.catchAllRoute)]);
822
894
  if (!matches?.length) return null;
@@ -873,6 +945,47 @@ async function buildOnLoadFile(componentData, isDev2) {
873
945
  ]
874
946
  });
875
947
  }
948
+ async function buildSitemap() {
949
+ if (!config?.sitemap) return;
950
+ const staticPages = globSync3(`${config.distPagesDir}/**/*.html`) || [];
951
+ const dynamicPages = globSync3(`${config.pagesDir}/**/*.{jsx,tsx}`) || [];
952
+ const allPages = staticPages.concat(dynamicPages);
953
+ const filteredPages = allPages.filter((filePath) => {
954
+ return !micromatch([filePath], config.sitemap?.excludePaths || [])?.length;
955
+ });
956
+ const pages = filteredPages.map((filePath) => {
957
+ const replacedExtensions = filePath.replace(regex_default.endsWithFileName, "");
958
+ const replacedPagesPath = replacedExtensions.replace(config.pagesDir, "");
959
+ const replacedDistPath = replacedPagesPath.replace(config.distPagesDir, "");
960
+ const replacedIndex = replacedDistPath.replace(regex_default.endsWithIndexNoExtension, "");
961
+ if (replacedIndex === "") return "/";
962
+ return replacedIndex;
963
+ }).filter((filePath) => {
964
+ return !filePath.includes("+config");
965
+ });
966
+ const sitemap = `
967
+ <?xml version="1.0" encoding="UTF-8"?>
968
+ <urlset
969
+ xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
970
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
971
+ xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
972
+ xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
973
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
974
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
975
+ xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
976
+ >
977
+ ${pages.map((page) => {
978
+ return `
979
+ <url>
980
+ <loc>${config?.domain ? `https://${config.domain}${page}` : page}</loc>
981
+ </url>
982
+ `;
983
+ }).join("\n")}
984
+ </urlset>
985
+ `;
986
+ fs7.ensureFileSync(config.sitemapPath);
987
+ fs7.writeFileSync(config.sitemapPath, minifyXML(sitemap));
988
+ }
876
989
 
877
990
  // src/runDevServer.ts
878
991
  import path7 from "path";
@@ -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;AAuDD,eAAO,MAAM,MAAM,EAAE,WAGpB,CAAC"}
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;AA2DD,eAAO,MAAM,MAAM,EAAE,WAGpB,CAAC"}
@@ -6,6 +6,10 @@ declare const _default: {
6
6
  isDynamicRoute: RegExp;
7
7
  endsWithTSX: RegExp;
8
8
  endsWithJSX: RegExp;
9
+ endsWithJs: RegExp;
10
+ endsWithPageFileName: RegExp;
11
+ endsWithIndexNoExtension: RegExp;
12
+ endsWithIndex: RegExp;
9
13
  endsWithFileName: RegExp;
10
14
  indexFile: RegExp;
11
15
  catchAllRoute: RegExp;
@@ -1 +1 @@
1
- {"version":3,"file":"regex.d.ts","sourceRoot":"","sources":["../../../src/util/regex.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,wBAaE"}
1
+ {"version":3,"file":"regex.d.ts","sourceRoot":"","sources":["../../../src/util/regex.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,wBAiBE"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "xpine",
3
- "version": "0.0.48",
3
+ "version": "0.0.50",
4
4
  "main": "dist/index.js",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-secrets-manager": "^3.758.0",
7
- "@tailwindcss/postcss": "^4.0.8",
7
+ "@tailwindcss/postcss": "^4.1.12",
8
8
  "builtin-modules": "^4.0.0",
9
9
  "chokidar": "^4.0.3",
10
10
  "dotenv": "^16.4.7",
@@ -14,6 +14,8 @@
14
14
  "fs-extra": "^11.3.0",
15
15
  "glob": "^11.0.1",
16
16
  "jsonwebtoken": "^9.0.2",
17
+ "micromatch": "^4.0.8",
18
+ "minify-xml": "^4.5.2",
17
19
  "postcss": "^8.5.3",
18
20
  "request-ip": "^3.3.0",
19
21
  "shelljs": "^0.9.2",
@@ -48,6 +50,7 @@
48
50
  "@eslint/js": "^9.24.0",
49
51
  "@tailwindcss/oxide-darwin-arm64": "^4.1.3",
50
52
  "@types/express": "^5.0.0",
53
+ "@types/micromatch": "^4.0.9",
51
54
  "@types/node": "^22.13.5",
52
55
  "@types/request-ip": "^0.0.41",
53
56
  "@types/shelljs": "^0.8.15",