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.
@@ -7,6 +7,8 @@ const fs = require('fs-extra');
7
7
  const vite = require('vite');
8
8
  const JSDOM = require('jsdom');
9
9
  const SiteMetadataDefaults = require('./vite-react-ssg.4ca822c0.cjs');
10
+ const express = require('express');
11
+ const node_fs = require('node:fs');
10
12
  const React = require('react');
11
13
  const reactHelmetAsync = require('react-helmet-async');
12
14
  const server_js = require('react-router-dom/server.js');
@@ -16,6 +18,7 @@ const server = require('react-dom/server');
16
18
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
17
19
 
18
20
  const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
21
+ const express__default = /*#__PURE__*/_interopDefaultCompat(express);
19
22
  const React__default = /*#__PURE__*/_interopDefaultCompat(React);
20
23
 
21
24
  function getDefaultExportFromCjs (x) {
@@ -900,6 +903,39 @@ function routesToPaths(routes) {
900
903
  getPaths(routes);
901
904
  return { paths: Array.from(paths), pathToEntry };
902
905
  }
906
+ function createFetchRequest(req) {
907
+ const origin = `${req.protocol}://${req.get("host")}`;
908
+ const url = new URL(req.originalUrl || req.url, origin);
909
+ const controller = new AbortController();
910
+ req.on("close", () => controller.abort());
911
+ const headers = new Headers();
912
+ for (const [key, values] of Object.entries(req.headers)) {
913
+ if (values) {
914
+ if (Array.isArray(values)) {
915
+ for (const value of values)
916
+ headers.append(key, value);
917
+ } else {
918
+ headers.set(key, values);
919
+ }
920
+ }
921
+ }
922
+ const init = {
923
+ method: req.method,
924
+ headers,
925
+ signal: controller.signal
926
+ };
927
+ if (req.method !== "GET" && req.method !== "HEAD")
928
+ init.body = req.body;
929
+ return new Request(url.href, init);
930
+ }
931
+ async function resolveAlias(config, entry) {
932
+ const resolver = config.createResolver();
933
+ const result = await resolver(entry, config.root);
934
+ return result || node_path.join(config.root, entry);
935
+ }
936
+ const { version } = JSON.parse(
937
+ node_fs.readFileSync(new URL("../../package.json", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (document.currentScript && document.currentScript.src || new URL('shared/vite-react-ssg.823b31c9.cjs', document.baseURI).href)))).toString()
938
+ );
903
939
 
904
940
  async function getCritters(outDir, options = {}) {
905
941
  try {
@@ -1017,7 +1053,7 @@ function renderPreloadLink(document, file) {
1017
1053
  });
1018
1054
  }
1019
1055
  }
1020
- function createLink(document) {
1056
+ function createLink$1(document) {
1021
1057
  return document.createElement("link");
1022
1058
  }
1023
1059
  function setAttrs(el, attrs) {
@@ -1029,11 +1065,68 @@ function appendLink(document, attrs) {
1029
1065
  const exits = document.head.querySelector(`link[href='${attrs.file}']`);
1030
1066
  if (exits)
1031
1067
  return;
1032
- const link = createLink(document);
1068
+ const link = createLink$1(document);
1033
1069
  setAttrs(link, attrs);
1034
1070
  document.head.appendChild(link);
1035
1071
  }
1036
1072
 
1073
+ async function renderHTML({
1074
+ rootContainerId,
1075
+ indexHTML,
1076
+ appHTML,
1077
+ metaAttributes,
1078
+ bodyAttributes,
1079
+ htmlAttributes,
1080
+ initialState
1081
+ }) {
1082
+ const stateScript = initialState ? `
1083
+ <script>window.__INITIAL_STATE__=${initialState}<\/script>` : "";
1084
+ const headStartTag = "<head>";
1085
+ const metaTags = metaAttributes.join("");
1086
+ indexHTML = indexHTML.replace(headStartTag, headStartTag + metaTags);
1087
+ const bodyStartTag = "<body";
1088
+ indexHTML = indexHTML.replace(bodyStartTag, `${bodyStartTag} ${bodyAttributes}`);
1089
+ const htmlStartTag = "<html";
1090
+ indexHTML = indexHTML.replace(htmlStartTag, `${htmlStartTag} ${htmlAttributes}`);
1091
+ const container = `<div id="${rootContainerId}"></div>`;
1092
+ if (indexHTML.includes(container)) {
1093
+ return indexHTML.replace(
1094
+ container,
1095
+ `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}`
1096
+ );
1097
+ }
1098
+ const html5Parser = await import('html5parser');
1099
+ const ast = html5Parser.parse(indexHTML);
1100
+ let renderedOutput;
1101
+ html5Parser.walk(ast, {
1102
+ enter: (node) => {
1103
+ 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)) {
1104
+ const attributesStringified = [...node.attributes.map(({ name: { value: name }, value }) => `${name}="${value.value}"`)].join(" ");
1105
+ const indexHTMLBefore = indexHTML.slice(0, node.start);
1106
+ const indexHTMLAfter = indexHTML.slice(node.end);
1107
+ renderedOutput = `${indexHTMLBefore}<${node.name} ${attributesStringified} data-server-rendered="true">${appHTML}</${node.name}>${stateScript}${indexHTMLAfter}`;
1108
+ }
1109
+ }
1110
+ });
1111
+ if (!renderedOutput)
1112
+ throw new Error(`Could not find a tag with id="${rootContainerId}" to replace it with server-side rendered HTML`);
1113
+ return renderedOutput;
1114
+ }
1115
+ async function detectEntry(root) {
1116
+ const scriptSrcReg = /<script(?:.*?)src=["'](.+?)["'](?!<)(?:.*)\>(?:[\n\r\s]*?)(?:<\/script>)/img;
1117
+ const html = await fs__default.readFile(node_path.join(root, "index.html"), "utf-8");
1118
+ const scripts = [...html.matchAll(scriptSrcReg)];
1119
+ const [, entry] = scripts.find((matchResult) => {
1120
+ const [script] = matchResult;
1121
+ const [, scriptType] = script.match(/.*\stype=(?:'|")?([^>'"\s]+)/i) || [];
1122
+ return scriptType === "module";
1123
+ }) || [];
1124
+ return entry || "src/main.ts";
1125
+ }
1126
+ function createLink(href) {
1127
+ return `<link rel="stylesheet" href="${href}">`;
1128
+ }
1129
+
1037
1130
  function DefaultIncludedRoutes(paths, _routes) {
1038
1131
  return paths.filter((i) => !i.includes(":") && !i.includes("*"));
1039
1132
  }
@@ -1104,7 +1197,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1104
1197
  const prefix = format === "esm" && process.platform === "win32" ? "file://" : "";
1105
1198
  const ext = format === "esm" ? ".mjs" : ".cjs";
1106
1199
  const serverEntry = node_path.join(prefix, ssgOut, node_path.parse(ssrEntry).name + ext);
1107
- const _require = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (document.currentScript && document.currentScript.src || new URL('shared/vite-react-ssg.a5dfecac.cjs', document.baseURI).href)));
1200
+ const _require = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (document.currentScript && document.currentScript.src || new URL('shared/vite-react-ssg.823b31c9.cjs', document.baseURI).href)));
1108
1201
  const { createRoot, includedRoutes: serverEntryIncludedRoutes } = format === "esm" ? await import(serverEntry) : _require(serverEntry);
1109
1202
  const includedRoutes = serverEntryIncludedRoutes || configIncludedRoutes;
1110
1203
  const { routes } = await createRoot(false);
@@ -1125,7 +1218,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1125
1218
  try {
1126
1219
  const appCtx = await createRoot(false, route);
1127
1220
  const { routes: routes2, initialState, triggerOnSSRAppRendered, transformState = SiteMetadataDefaults.serializeState } = appCtx;
1128
- const transformedIndexHTML = await onBeforePageRender?.(route, indexHTML, appCtx);
1221
+ const transformedIndexHTML = await onBeforePageRender?.(route, indexHTML, appCtx) || indexHTML;
1129
1222
  const url = new URL(route, "http://vite-react-ssg.com");
1130
1223
  url.search = "";
1131
1224
  url.hash = "";
@@ -1136,7 +1229,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1136
1229
  const renderedHTML = await renderHTML({
1137
1230
  rootContainerId,
1138
1231
  appHTML,
1139
- indexHTML,
1232
+ indexHTML: transformedIndexHTML,
1140
1233
  metaAttributes,
1141
1234
  bodyAttributes,
1142
1235
  htmlAttributes,
@@ -1180,69 +1273,11 @@ ${kolorist.gray("[vite-react-ssg]")} ${kolorist.green("Build finished.")}`);
1180
1273
  }, waitInSeconds * 1e3);
1181
1274
  timeout.unref();
1182
1275
  }
1183
- async function detectEntry(root) {
1184
- const scriptSrcReg = /<script(?:.*?)src=["'](.+?)["'](?!<)(?:.*)\>(?:[\n\r\s]*?)(?:<\/script>)/img;
1185
- const html = await fs__default.readFile(node_path.join(root, "index.html"), "utf-8");
1186
- const scripts = [...html.matchAll(scriptSrcReg)];
1187
- const [, entry] = scripts.find((matchResult) => {
1188
- const [script] = matchResult;
1189
- const [, scriptType] = script.match(/.*\stype=(?:'|")?([^>'"\s]+)/i) || [];
1190
- return scriptType === "module";
1191
- }) || [];
1192
- return entry || "src/main.ts";
1193
- }
1194
- async function resolveAlias(config, entry) {
1195
- const resolver = config.createResolver();
1196
- const result = await resolver(entry, config.root);
1197
- return result || node_path.join(config.root, entry);
1198
- }
1199
1276
  function rewriteScripts(indexHTML, mode) {
1200
1277
  if (!mode || mode === "sync")
1201
1278
  return indexHTML;
1202
1279
  return indexHTML.replace(/<script type="module" /g, `<script type="module" ${mode} `);
1203
1280
  }
1204
- async function renderHTML({
1205
- rootContainerId,
1206
- indexHTML,
1207
- appHTML,
1208
- metaAttributes,
1209
- bodyAttributes,
1210
- htmlAttributes,
1211
- initialState
1212
- }) {
1213
- const stateScript = initialState ? `
1214
- <script>window.__INITIAL_STATE__=${initialState}<\/script>` : "";
1215
- const headStartTag = "<head>";
1216
- const metaTags = metaAttributes.join("");
1217
- indexHTML = indexHTML.replace(headStartTag, headStartTag + metaTags);
1218
- const bodyStartTag = "<body";
1219
- indexHTML = indexHTML.replace(bodyStartTag, `${bodyStartTag} ${bodyAttributes}`);
1220
- const htmlStartTag = "<html";
1221
- indexHTML = indexHTML.replace(htmlStartTag, `${htmlStartTag} ${htmlAttributes}`);
1222
- const container = `<div id="${rootContainerId}"></div>`;
1223
- if (indexHTML.includes(container)) {
1224
- return indexHTML.replace(
1225
- container,
1226
- `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}`
1227
- );
1228
- }
1229
- const html5Parser = await import('html5parser');
1230
- const ast = html5Parser.parse(indexHTML);
1231
- let renderedOutput;
1232
- html5Parser.walk(ast, {
1233
- enter: (node) => {
1234
- 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)) {
1235
- const attributesStringified = [...node.attributes.map(({ name: { value: name }, value }) => `${name}="${value.value}"`)].join(" ");
1236
- const indexHTMLBefore = indexHTML.slice(0, node.start);
1237
- const indexHTMLAfter = indexHTML.slice(node.end);
1238
- renderedOutput = `${indexHTMLBefore}<${node.name} ${attributesStringified} data-server-rendered="true">${appHTML}</${node.name}>${stateScript}${indexHTMLAfter}`;
1239
- }
1240
- }
1241
- });
1242
- if (!renderedOutput)
1243
- throw new Error(`Could not find a tag with id="${rootContainerId}" to replace it with server-side rendered HTML`);
1244
- return renderedOutput;
1245
- }
1246
1281
  async function formatHtml(html, formatting) {
1247
1282
  if (formatting === "minify") {
1248
1283
  const htmlMinifier = await import('html-minifier');
@@ -1278,4 +1313,185 @@ function collectModules(manifest, entry, mods = /* @__PURE__ */ new Set()) {
1278
1313
  return mods;
1279
1314
  }
1280
1315
 
1316
+ const SHORTCUTS = [
1317
+ {
1318
+ key: "u",
1319
+ description: "show server url",
1320
+ action(vite, _) {
1321
+ vite.config.logger.info("");
1322
+ printServerInfo(vite, true);
1323
+ }
1324
+ },
1325
+ {
1326
+ key: "c",
1327
+ description: "clear console",
1328
+ action(vite, _) {
1329
+ vite.config.logger.clearScreen("error");
1330
+ }
1331
+ },
1332
+ {
1333
+ key: "q",
1334
+ description: "quit",
1335
+ action(_, server) {
1336
+ server.close(() => process.exit());
1337
+ }
1338
+ }
1339
+ ];
1340
+ function bindShortcuts(viteServer, server) {
1341
+ if (!process.stdin.isTTY || process.env.CI)
1342
+ return;
1343
+ viteServer.config.logger.info(
1344
+ kolorist.dim(kolorist.green(" \u279C")) + kolorist.dim(" press ") + kolorist.bold("h") + kolorist.dim(" to show help")
1345
+ );
1346
+ const shortcuts = SHORTCUTS;
1347
+ let actionRunning = false;
1348
+ const onInput = async (input) => {
1349
+ if (input === "" || input === "") {
1350
+ try {
1351
+ await server.close();
1352
+ } finally {
1353
+ process.exit(1);
1354
+ }
1355
+ return;
1356
+ }
1357
+ if (actionRunning)
1358
+ return;
1359
+ if (input === "h") {
1360
+ viteServer.config.logger.info(
1361
+ [
1362
+ "",
1363
+ kolorist.bold(" Shortcuts"),
1364
+ ...shortcuts.map(
1365
+ (shortcut2) => kolorist.dim(" press ") + kolorist.bold(shortcut2.key) + kolorist.dim(` to ${shortcut2.description}`)
1366
+ )
1367
+ ].join("\n")
1368
+ );
1369
+ }
1370
+ const shortcut = shortcuts.find((shortcut2) => shortcut2.key === input);
1371
+ if (!shortcut)
1372
+ return;
1373
+ actionRunning = true;
1374
+ await shortcut.action(viteServer, server);
1375
+ actionRunning = false;
1376
+ };
1377
+ process.stdin.setRawMode(true);
1378
+ process.stdin.on("data", onInput).setEncoding("utf8").resume();
1379
+ server.on("close", () => {
1380
+ process.stdin.off("data", onInput).pause();
1381
+ });
1382
+ }
1383
+
1384
+ async function dev(ssgOptions = {}, viteConfig = {}) {
1385
+ const mode = process.env.MODE || process.env.NODE_ENV || ssgOptions.mode || "development";
1386
+ const config = await vite.resolveConfig(viteConfig, "serve", mode, mode);
1387
+ const cwd = process.cwd();
1388
+ const root = config.root || cwd;
1389
+ const {
1390
+ entry = await detectEntry(root),
1391
+ onBeforePageRender,
1392
+ onPageRendered,
1393
+ rootContainerId = "root"
1394
+ } = Object.assign({}, config.ssgOptions || {}, ssgOptions);
1395
+ const ssrEntry = await resolveAlias(config, entry);
1396
+ const template = await fs__default.readFile(node_path.join(root, "index.html"), "utf-8");
1397
+ let viteServer;
1398
+ globalThis.__ssr_start_time = performance.now();
1399
+ createServer().then((app) => {
1400
+ const port = viteServer.config.server.port || 5173;
1401
+ const server = app.listen(port, () => {
1402
+ printServerInfo(viteServer);
1403
+ bindShortcuts(viteServer, server);
1404
+ });
1405
+ });
1406
+ async function createServer() {
1407
+ process.env.__DEV_MODE_SSR = "true";
1408
+ const app = express__default();
1409
+ viteServer = await vite.createServer({
1410
+ // ...options,
1411
+ server: { middlewareMode: true },
1412
+ appType: "custom"
1413
+ });
1414
+ app.use(viteServer.middlewares);
1415
+ app.use("*", async (req, res) => {
1416
+ try {
1417
+ const url = req.originalUrl;
1418
+ const indexHTML = await viteServer.transformIndexHtml(url, template);
1419
+ const createRoot = await viteServer.ssrLoadModule(ssrEntry).then((m) => m.createRoot);
1420
+ const appCtx = await createRoot(false, url);
1421
+ const { routes } = appCtx;
1422
+ const transformedIndexHTML = await onBeforePageRender?.(url, indexHTML, appCtx) || indexHTML;
1423
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes } = await render([...routes], createFetchRequest(req));
1424
+ const mod = await viteServer.moduleGraph.getModuleByUrl(entry);
1425
+ const assetsUrls = /* @__PURE__ */ new Set();
1426
+ const collectAssets = async (mod2) => {
1427
+ if (!mod2?.ssrTransformResult)
1428
+ return;
1429
+ const { deps = [], dynamicDeps = [] } = mod2?.ssrTransformResult;
1430
+ const allDeps = [...deps, ...dynamicDeps];
1431
+ for (const dep of allDeps) {
1432
+ if (dep.endsWith(".css")) {
1433
+ assetsUrls.add(dep);
1434
+ } else if (dep.endsWith(".ts") || dep.endsWith(".tsx")) {
1435
+ const depModule = await viteServer.moduleGraph.getModuleByUrl(dep);
1436
+ depModule && await collectAssets(depModule);
1437
+ }
1438
+ }
1439
+ };
1440
+ await collectAssets(mod);
1441
+ const preloadLink = [...assetsUrls].map((item) => createLink(item));
1442
+ metaAttributes.push(...preloadLink);
1443
+ const renderedHTML = await renderHTML({
1444
+ rootContainerId,
1445
+ appHTML,
1446
+ indexHTML: transformedIndexHTML,
1447
+ metaAttributes,
1448
+ bodyAttributes,
1449
+ htmlAttributes,
1450
+ initialState: null
1451
+ });
1452
+ const transformed = await onPageRendered?.(url, renderedHTML, appCtx) || renderedHTML;
1453
+ res.status(200).set({ "Content-Type": "text/html" }).end(transformed);
1454
+ } catch (e) {
1455
+ viteServer.ssrFixStacktrace(e);
1456
+ console.error(`[vite-react-ssg] error: ${e.stack}`);
1457
+ res.status(500).end(e.stack);
1458
+ }
1459
+ });
1460
+ return app;
1461
+ }
1462
+ }
1463
+ async function printServerInfo(server, onlyUrl = false) {
1464
+ const info = server.config.logger.info;
1465
+ const port = server.config.server.port || 5173;
1466
+ const url = `http://localhost:${port}/`;
1467
+ if (!onlyUrl) {
1468
+ let ssrReadyMessage = " -- SSR";
1469
+ if (globalThis.__ssr_start_time) {
1470
+ ssrReadyMessage += ` ready in ${kolorist.reset(kolorist.bold(`${Math.round(
1471
+ // @ts-expect-error global var
1472
+ performance.now() - globalThis.__ssr_start_time
1473
+ )}ms`))}`;
1474
+ }
1475
+ info(
1476
+ `
1477
+ ${kolorist.bgLightCyan(` VITE-REACT-SSG v${version} `)}`,
1478
+ { clear: !server.config.logger.hasWarned }
1479
+ );
1480
+ info(
1481
+ `${kolorist.cyan(`
1482
+ VITE v${vite.version}`) + kolorist.dim(ssrReadyMessage)}
1483
+ `
1484
+ );
1485
+ }
1486
+ info(
1487
+ kolorist.green(" dev server running at:")
1488
+ );
1489
+ printUrls(url, info);
1490
+ }
1491
+ function printUrls(url, info) {
1492
+ const colorUrl = (url2) => kolorist.cyan(url2.replace(/:(\d+)\//, (_, port) => `:${kolorist.bold(port)}/`));
1493
+ info(` ${kolorist.green("\u279C")} ${kolorist.bold("Local")}: ${colorUrl(url)}`);
1494
+ }
1495
+
1281
1496
  exports.build = build;
1497
+ exports.dev = dev;
@@ -126,6 +126,14 @@ interface ViteReactSSGClientOptions {
126
126
  * @default `#root`
127
127
  */
128
128
  rootContainer?: string | Element;
129
+ /**
130
+ * Use SSR during development.
131
+ *
132
+ * If false, the 'dev' script in 'package.json' needs to be set to `"vite"`.
133
+ *
134
+ * @default true
135
+ */
136
+ ssrWhenDev?: boolean;
129
137
  }
130
138
  interface CommonRouteOptions {
131
139
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vite-react-ssg",
3
3
  "type": "module",
4
- "version": "0.0.3",
4
+ "version": "0.1.0",
5
5
  "packageManager": "pnpm@8.6.6",
6
6
  "description": "",
7
7
  "author": "Riri <Daydreamerriri@outlook.com>",
@@ -75,6 +75,7 @@
75
75
  }
76
76
  },
77
77
  "dependencies": {
78
+ "express": "^4.18.2",
78
79
  "fs-extra": "^11.1.1",
79
80
  "html-minifier": "^4.0.0",
80
81
  "html5parser": "^2.0.2",
@@ -86,6 +87,7 @@
86
87
  },
87
88
  "devDependencies": {
88
89
  "@ririd/eslint-config": "0.6.0",
90
+ "@types/express": "^4.17.17",
89
91
  "@types/fs-extra": "^11.0.1",
90
92
  "@types/html-minifier": "^4.0.2",
91
93
  "@types/jsdom": "^21.1.1",
@@ -110,4 +112,4 @@
110
112
  "vite-plugin-pwa": "^0.16.4",
111
113
  "vitest": "0.33.0"
112
114
  }
113
- }
115
+ }