vite-react-ssg 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { b as build } from './shared/vite-react-ssg.524ba00d.mjs';
1
+ export { b as build, d as dev } from './shared/vite-react-ssg.7ee042ba.mjs';
2
2
  import 'node:path';
3
3
  import 'node:module';
4
4
  import 'kolorist';
@@ -8,6 +8,8 @@ import 'jsdom';
8
8
  import './shared/vite-react-ssg.9d005d5e.mjs';
9
9
  import 'react';
10
10
  import 'react-helmet-async';
11
+ import 'express';
12
+ import 'node:fs';
11
13
  import 'react-router-dom/server.js';
12
14
  import 'node:stream';
13
15
  import 'react-dom/server';
@@ -1,10 +1,12 @@
1
1
  import { join, isAbsolute, parse, dirname } from 'node:path';
2
2
  import { createRequire } from 'node:module';
3
- import { gray, yellow, blue, dim, cyan, red, green } from 'kolorist';
3
+ import { gray, yellow, blue, dim, cyan, red, green, bold, reset, bgLightCyan } from 'kolorist';
4
4
  import fs from 'fs-extra';
5
- import { resolveConfig, build as build$1, mergeConfig } from 'vite';
5
+ import { resolveConfig, build as build$1, mergeConfig, createServer, version as version$1 } from 'vite';
6
6
  import { JSDOM } from 'jsdom';
7
7
  import { S as SiteMetadataDefaults, s as serializeState } from './vite-react-ssg.9d005d5e.mjs';
8
+ import express from 'express';
9
+ import { readFileSync } from 'node:fs';
8
10
  import React from 'react';
9
11
  import { HelmetProvider } from 'react-helmet-async';
10
12
  import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router-dom/server.js';
@@ -893,6 +895,39 @@ function routesToPaths(routes) {
893
895
  getPaths(routes);
894
896
  return { paths: Array.from(paths), pathToEntry };
895
897
  }
898
+ function createFetchRequest(req) {
899
+ const origin = `${req.protocol}://${req.get("host")}`;
900
+ const url = new URL(req.originalUrl || req.url, origin);
901
+ const controller = new AbortController();
902
+ req.on("close", () => controller.abort());
903
+ const headers = new Headers();
904
+ for (const [key, values] of Object.entries(req.headers)) {
905
+ if (values) {
906
+ if (Array.isArray(values)) {
907
+ for (const value of values)
908
+ headers.append(key, value);
909
+ } else {
910
+ headers.set(key, values);
911
+ }
912
+ }
913
+ }
914
+ const init = {
915
+ method: req.method,
916
+ headers,
917
+ signal: controller.signal
918
+ };
919
+ if (req.method !== "GET" && req.method !== "HEAD")
920
+ init.body = req.body;
921
+ return new Request(url.href, init);
922
+ }
923
+ async function resolveAlias(config, entry) {
924
+ const resolver = config.createResolver();
925
+ const result = await resolver(entry, config.root);
926
+ return result || join(config.root, entry);
927
+ }
928
+ const { version } = JSON.parse(
929
+ readFileSync(new URL("../../package.json", import.meta.url)).toString()
930
+ );
896
931
 
897
932
  async function getCritters(outDir, options = {}) {
898
933
  try {
@@ -1010,7 +1045,7 @@ function renderPreloadLink(document, file) {
1010
1045
  });
1011
1046
  }
1012
1047
  }
1013
- function createLink(document) {
1048
+ function createLink$1(document) {
1014
1049
  return document.createElement("link");
1015
1050
  }
1016
1051
  function setAttrs(el, attrs) {
@@ -1022,11 +1057,68 @@ function appendLink(document, attrs) {
1022
1057
  const exits = document.head.querySelector(`link[href='${attrs.file}']`);
1023
1058
  if (exits)
1024
1059
  return;
1025
- const link = createLink(document);
1060
+ const link = createLink$1(document);
1026
1061
  setAttrs(link, attrs);
1027
1062
  document.head.appendChild(link);
1028
1063
  }
1029
1064
 
1065
+ async function renderHTML({
1066
+ rootContainerId,
1067
+ indexHTML,
1068
+ appHTML,
1069
+ metaAttributes,
1070
+ bodyAttributes,
1071
+ htmlAttributes,
1072
+ initialState
1073
+ }) {
1074
+ const stateScript = initialState ? `
1075
+ <script>window.__INITIAL_STATE__=${initialState}<\/script>` : "";
1076
+ const headStartTag = "<head>";
1077
+ const metaTags = metaAttributes.join("");
1078
+ indexHTML = indexHTML.replace(headStartTag, headStartTag + metaTags);
1079
+ const bodyStartTag = "<body";
1080
+ indexHTML = indexHTML.replace(bodyStartTag, `${bodyStartTag} ${bodyAttributes}`);
1081
+ const htmlStartTag = "<html";
1082
+ indexHTML = indexHTML.replace(htmlStartTag, `${htmlStartTag} ${htmlAttributes}`);
1083
+ const container = `<div id="${rootContainerId}"></div>`;
1084
+ if (indexHTML.includes(container)) {
1085
+ return indexHTML.replace(
1086
+ container,
1087
+ `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}`
1088
+ );
1089
+ }
1090
+ const html5Parser = await import('html5parser');
1091
+ const ast = html5Parser.parse(indexHTML);
1092
+ let renderedOutput;
1093
+ html5Parser.walk(ast, {
1094
+ enter: (node) => {
1095
+ if (!renderedOutput && node?.type === html5Parser.SyntaxKind.Tag && Array.isArray(node.attributes) && node.attributes.length > 0 && node.attributes.some((attr) => attr.name.value === "id" && attr.value?.value === rootContainerId)) {
1096
+ const attributesStringified = [...node.attributes.map(({ name: { value: name }, value }) => `${name}="${value.value}"`)].join(" ");
1097
+ const indexHTMLBefore = indexHTML.slice(0, node.start);
1098
+ const indexHTMLAfter = indexHTML.slice(node.end);
1099
+ renderedOutput = `${indexHTMLBefore}<${node.name} ${attributesStringified} data-server-rendered="true">${appHTML}</${node.name}>${stateScript}${indexHTMLAfter}`;
1100
+ }
1101
+ }
1102
+ });
1103
+ if (!renderedOutput)
1104
+ throw new Error(`Could not find a tag with id="${rootContainerId}" to replace it with server-side rendered HTML`);
1105
+ return renderedOutput;
1106
+ }
1107
+ async function detectEntry(root) {
1108
+ const scriptSrcReg = /<script(?:.*?)src=["'](.+?)["'](?!<)(?:.*)\>(?:[\n\r\s]*?)(?:<\/script>)/img;
1109
+ const html = await fs.readFile(join(root, "index.html"), "utf-8");
1110
+ const scripts = [...html.matchAll(scriptSrcReg)];
1111
+ const [, entry] = scripts.find((matchResult) => {
1112
+ const [script] = matchResult;
1113
+ const [, scriptType] = script.match(/.*\stype=(?:'|")?([^>'"\s]+)/i) || [];
1114
+ return scriptType === "module";
1115
+ }) || [];
1116
+ return entry || "src/main.ts";
1117
+ }
1118
+ function createLink(href) {
1119
+ return `<link rel="stylesheet" href="${href}">`;
1120
+ }
1121
+
1030
1122
  function DefaultIncludedRoutes(paths, _routes) {
1031
1123
  return paths.filter((i) => !i.includes(":") && !i.includes("*"));
1032
1124
  }
@@ -1118,7 +1210,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1118
1210
  try {
1119
1211
  const appCtx = await createRoot(false, route);
1120
1212
  const { routes: routes2, initialState, triggerOnSSRAppRendered, transformState = serializeState } = appCtx;
1121
- const transformedIndexHTML = await onBeforePageRender?.(route, indexHTML, appCtx);
1213
+ const transformedIndexHTML = await onBeforePageRender?.(route, indexHTML, appCtx) || indexHTML;
1122
1214
  const url = new URL(route, "http://vite-react-ssg.com");
1123
1215
  url.search = "";
1124
1216
  url.hash = "";
@@ -1129,7 +1221,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1129
1221
  const renderedHTML = await renderHTML({
1130
1222
  rootContainerId,
1131
1223
  appHTML,
1132
- indexHTML,
1224
+ indexHTML: transformedIndexHTML,
1133
1225
  metaAttributes,
1134
1226
  bodyAttributes,
1135
1227
  htmlAttributes,
@@ -1173,69 +1265,11 @@ ${gray("[vite-react-ssg]")} ${green("Build finished.")}`);
1173
1265
  }, waitInSeconds * 1e3);
1174
1266
  timeout.unref();
1175
1267
  }
1176
- async function detectEntry(root) {
1177
- const scriptSrcReg = /<script(?:.*?)src=["'](.+?)["'](?!<)(?:.*)\>(?:[\n\r\s]*?)(?:<\/script>)/img;
1178
- const html = await fs.readFile(join(root, "index.html"), "utf-8");
1179
- const scripts = [...html.matchAll(scriptSrcReg)];
1180
- const [, entry] = scripts.find((matchResult) => {
1181
- const [script] = matchResult;
1182
- const [, scriptType] = script.match(/.*\stype=(?:'|")?([^>'"\s]+)/i) || [];
1183
- return scriptType === "module";
1184
- }) || [];
1185
- return entry || "src/main.ts";
1186
- }
1187
- async function resolveAlias(config, entry) {
1188
- const resolver = config.createResolver();
1189
- const result = await resolver(entry, config.root);
1190
- return result || join(config.root, entry);
1191
- }
1192
1268
  function rewriteScripts(indexHTML, mode) {
1193
1269
  if (!mode || mode === "sync")
1194
1270
  return indexHTML;
1195
1271
  return indexHTML.replace(/<script type="module" /g, `<script type="module" ${mode} `);
1196
1272
  }
1197
- async function renderHTML({
1198
- rootContainerId,
1199
- indexHTML,
1200
- appHTML,
1201
- metaAttributes,
1202
- bodyAttributes,
1203
- htmlAttributes,
1204
- initialState
1205
- }) {
1206
- const stateScript = initialState ? `
1207
- <script>window.__INITIAL_STATE__=${initialState}<\/script>` : "";
1208
- const headStartTag = "<head>";
1209
- const metaTags = metaAttributes.join("");
1210
- indexHTML = indexHTML.replace(headStartTag, headStartTag + metaTags);
1211
- const bodyStartTag = "<body";
1212
- indexHTML = indexHTML.replace(bodyStartTag, `${bodyStartTag} ${bodyAttributes}`);
1213
- const htmlStartTag = "<html";
1214
- indexHTML = indexHTML.replace(htmlStartTag, `${htmlStartTag} ${htmlAttributes}`);
1215
- const container = `<div id="${rootContainerId}"></div>`;
1216
- if (indexHTML.includes(container)) {
1217
- return indexHTML.replace(
1218
- container,
1219
- `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}`
1220
- );
1221
- }
1222
- const html5Parser = await import('html5parser');
1223
- const ast = html5Parser.parse(indexHTML);
1224
- let renderedOutput;
1225
- html5Parser.walk(ast, {
1226
- enter: (node) => {
1227
- if (!renderedOutput && node?.type === html5Parser.SyntaxKind.Tag && Array.isArray(node.attributes) && node.attributes.length > 0 && node.attributes.some((attr) => attr.name.value === "id" && attr.value?.value === rootContainerId)) {
1228
- const attributesStringified = [...node.attributes.map(({ name: { value: name }, value }) => `${name}="${value.value}"`)].join(" ");
1229
- const indexHTMLBefore = indexHTML.slice(0, node.start);
1230
- const indexHTMLAfter = indexHTML.slice(node.end);
1231
- renderedOutput = `${indexHTMLBefore}<${node.name} ${attributesStringified} data-server-rendered="true">${appHTML}</${node.name}>${stateScript}${indexHTMLAfter}`;
1232
- }
1233
- }
1234
- });
1235
- if (!renderedOutput)
1236
- throw new Error(`Could not find a tag with id="${rootContainerId}" to replace it with server-side rendered HTML`);
1237
- return renderedOutput;
1238
- }
1239
1273
  async function formatHtml(html, formatting) {
1240
1274
  if (formatting === "minify") {
1241
1275
  const htmlMinifier = await import('html-minifier');
@@ -1271,4 +1305,184 @@ function collectModules(manifest, entry, mods = /* @__PURE__ */ new Set()) {
1271
1305
  return mods;
1272
1306
  }
1273
1307
 
1274
- export { build as b };
1308
+ const SHORTCUTS = [
1309
+ {
1310
+ key: "u",
1311
+ description: "show server url",
1312
+ action(vite, _) {
1313
+ vite.config.logger.info("");
1314
+ printServerInfo(vite, true);
1315
+ }
1316
+ },
1317
+ {
1318
+ key: "c",
1319
+ description: "clear console",
1320
+ action(vite, _) {
1321
+ vite.config.logger.clearScreen("error");
1322
+ }
1323
+ },
1324
+ {
1325
+ key: "q",
1326
+ description: "quit",
1327
+ action(_, server) {
1328
+ server.close(() => process.exit());
1329
+ }
1330
+ }
1331
+ ];
1332
+ function bindShortcuts(viteServer, server) {
1333
+ if (!process.stdin.isTTY || process.env.CI)
1334
+ return;
1335
+ viteServer.config.logger.info(
1336
+ dim(green(" \u279C")) + dim(" press ") + bold("h") + dim(" to show help")
1337
+ );
1338
+ const shortcuts = SHORTCUTS;
1339
+ let actionRunning = false;
1340
+ const onInput = async (input) => {
1341
+ if (input === "" || input === "") {
1342
+ try {
1343
+ await server.close();
1344
+ } finally {
1345
+ process.exit(1);
1346
+ }
1347
+ return;
1348
+ }
1349
+ if (actionRunning)
1350
+ return;
1351
+ if (input === "h") {
1352
+ viteServer.config.logger.info(
1353
+ [
1354
+ "",
1355
+ bold(" Shortcuts"),
1356
+ ...shortcuts.map(
1357
+ (shortcut2) => dim(" press ") + bold(shortcut2.key) + dim(` to ${shortcut2.description}`)
1358
+ )
1359
+ ].join("\n")
1360
+ );
1361
+ }
1362
+ const shortcut = shortcuts.find((shortcut2) => shortcut2.key === input);
1363
+ if (!shortcut)
1364
+ return;
1365
+ actionRunning = true;
1366
+ await shortcut.action(viteServer, server);
1367
+ actionRunning = false;
1368
+ };
1369
+ process.stdin.setRawMode(true);
1370
+ process.stdin.on("data", onInput).setEncoding("utf8").resume();
1371
+ server.on("close", () => {
1372
+ process.stdin.off("data", onInput).pause();
1373
+ });
1374
+ }
1375
+
1376
+ async function dev(ssgOptions = {}, viteConfig = {}) {
1377
+ const mode = process.env.MODE || process.env.NODE_ENV || ssgOptions.mode || "development";
1378
+ const config = await resolveConfig(viteConfig, "serve", mode, mode);
1379
+ const cwd = process.cwd();
1380
+ const root = config.root || cwd;
1381
+ const {
1382
+ entry = await detectEntry(root),
1383
+ onBeforePageRender,
1384
+ onPageRendered,
1385
+ rootContainerId = "root"
1386
+ } = Object.assign({}, config.ssgOptions || {}, ssgOptions);
1387
+ const ssrEntry = await resolveAlias(config, entry);
1388
+ const template = await fs.readFile(join(root, "index.html"), "utf-8");
1389
+ let viteServer;
1390
+ globalThis.__ssr_start_time = performance.now();
1391
+ createServer$1().then((app) => {
1392
+ const port = viteServer.config.server.port || 5173;
1393
+ const server = app.listen(port, () => {
1394
+ printServerInfo(viteServer);
1395
+ bindShortcuts(viteServer, server);
1396
+ });
1397
+ });
1398
+ async function createServer$1() {
1399
+ process.env.__DEV_MODE_SSR = "true";
1400
+ const app = express();
1401
+ viteServer = await createServer({
1402
+ // ...options,
1403
+ server: { middlewareMode: true },
1404
+ appType: "custom"
1405
+ });
1406
+ app.use(viteServer.middlewares);
1407
+ app.use("*", async (req, res) => {
1408
+ try {
1409
+ const url = req.originalUrl;
1410
+ const indexHTML = await viteServer.transformIndexHtml(url, template);
1411
+ const createRoot = await viteServer.ssrLoadModule(ssrEntry).then((m) => m.createRoot);
1412
+ const appCtx = await createRoot(false, url);
1413
+ const { routes } = appCtx;
1414
+ const transformedIndexHTML = await onBeforePageRender?.(url, indexHTML, appCtx) || indexHTML;
1415
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes } = await render([...routes], createFetchRequest(req));
1416
+ const mod = await viteServer.moduleGraph.getModuleByUrl(entry);
1417
+ const assetsUrls = /* @__PURE__ */ new Set();
1418
+ const collectAssets = async (mod2) => {
1419
+ if (!mod2?.ssrTransformResult)
1420
+ return;
1421
+ const { deps = [], dynamicDeps = [] } = mod2?.ssrTransformResult;
1422
+ const allDeps = [...deps, ...dynamicDeps];
1423
+ for (const dep of allDeps) {
1424
+ if (dep.endsWith(".css")) {
1425
+ assetsUrls.add(dep);
1426
+ } else if (dep.endsWith(".ts") || dep.endsWith(".tsx")) {
1427
+ const depModule = await viteServer.moduleGraph.getModuleByUrl(dep);
1428
+ depModule && await collectAssets(depModule);
1429
+ }
1430
+ }
1431
+ };
1432
+ await collectAssets(mod);
1433
+ const preloadLink = [...assetsUrls].map((item) => createLink(item));
1434
+ metaAttributes.push(...preloadLink);
1435
+ const renderedHTML = await renderHTML({
1436
+ rootContainerId,
1437
+ appHTML,
1438
+ indexHTML: transformedIndexHTML,
1439
+ metaAttributes,
1440
+ bodyAttributes,
1441
+ htmlAttributes,
1442
+ initialState: null
1443
+ });
1444
+ const transformed = await onPageRendered?.(url, renderedHTML, appCtx) || renderedHTML;
1445
+ res.status(200).set({ "Content-Type": "text/html" }).end(transformed);
1446
+ } catch (e) {
1447
+ viteServer.ssrFixStacktrace(e);
1448
+ console.error(`[vite-react-ssg] error: ${e.stack}`);
1449
+ res.status(500).end(e.stack);
1450
+ }
1451
+ });
1452
+ return app;
1453
+ }
1454
+ }
1455
+ async function printServerInfo(server, onlyUrl = false) {
1456
+ const info = server.config.logger.info;
1457
+ const port = server.config.server.port || 5173;
1458
+ const url = `http://localhost:${port}/`;
1459
+ if (!onlyUrl) {
1460
+ let ssrReadyMessage = " -- SSR";
1461
+ if (globalThis.__ssr_start_time) {
1462
+ ssrReadyMessage += ` ready in ${reset(bold(`${Math.round(
1463
+ // @ts-expect-error global var
1464
+ performance.now() - globalThis.__ssr_start_time
1465
+ )}ms`))}`;
1466
+ }
1467
+ info(
1468
+ `
1469
+ ${bgLightCyan(` VITE-REACT-SSG v${version} `)}`,
1470
+ { clear: !server.config.logger.hasWarned }
1471
+ );
1472
+ info(
1473
+ `${cyan(`
1474
+ VITE v${version$1}`) + dim(ssrReadyMessage)}
1475
+ `
1476
+ );
1477
+ }
1478
+ info(
1479
+ green(" dev server running at:")
1480
+ );
1481
+ printUrls(url, info);
1482
+ }
1483
+ function printUrls(url, info) {
1484
+ const colorUrl = (url2) => cyan(url2.replace(/:(\d+)\//, (_, port) => `:${bold(port)}/`));
1485
+ info(` ${green("\u279C")} ${bold("Local")}: ${colorUrl(url)}`);
1486
+ }
1487
+
1488
+ export { build as b, dev as d };