create-vike 0.0.1 → 0.0.4

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.
Files changed (63) hide show
  1. package/README.md +7 -5
  2. package/dist/index.js +191 -144
  3. package/{input-files/react/pages/about/index.page.tsx → files/js/react/pages/about/index.page.jsx} +0 -0
  4. package/{input-files/react/pages/index/Counter.tsx → files/js/react/pages/index/Counter.jsx} +0 -0
  5. package/{input-files/react/pages/index/index.page.tsx → files/js/react/pages/index/index.page.jsx} +0 -0
  6. package/files/js/react/renderer/Link.jsx +15 -0
  7. package/files/js/react/renderer/PageWrapper.jsx +89 -0
  8. package/files/js/react/renderer/_default.page.client.jsx +19 -0
  9. package/files/js/react/renderer/_default.page.server.jsx +47 -0
  10. package/files/js/react/renderer/_error.page.jsx +21 -0
  11. package/files/js/react/renderer/usePageContext.jsx +18 -0
  12. package/files/js/react+client-router/renderer/_default.page.client.jsx +43 -0
  13. package/files/js/react+client-router/renderer/types.js +1 -0
  14. package/files/js/vike/server/index.js +40 -0
  15. package/{input-files → files/js}/vue/pages/about/index.page.vue +0 -0
  16. package/files/js/vue/pages/index/Counter.vue +10 -0
  17. package/files/js/vue/pages/index/index.page.vue +12 -0
  18. package/files/js/vue/renderer/Link.vue +17 -0
  19. package/files/js/vue/renderer/PageWrapper.vue +55 -0
  20. package/files/js/vue/renderer/_default.page.client.js +12 -0
  21. package/files/js/vue/renderer/_default.page.server.js +41 -0
  22. package/files/js/vue/renderer/_error.page.vue +14 -0
  23. package/files/js/vue/renderer/app.js +29 -0
  24. package/files/js/vue/renderer/usePageContext.js +18 -0
  25. package/files/js/vue+client-router/renderer/_default.page.client.js +23 -0
  26. package/files/js/vue+client-router/renderer/app.js +36 -0
  27. package/files/js/vue+client-router/renderer/types.js +1 -0
  28. package/{input-files → files/shared}/.prettierrc +0 -0
  29. package/{input-files → files/shared}/react/pages/about/index.css +0 -0
  30. package/{input-files → files/shared}/react/renderer/PageWrapper.css +0 -0
  31. package/files/shared/vike/_gitignore +121 -0
  32. package/{input-files/common → files/shared/vike}/renderer/logo.svg +0 -0
  33. package/files/ts/react/pages/about/index.page.tsx +13 -0
  34. package/files/ts/react/pages/index/Counter.tsx +12 -0
  35. package/files/ts/react/pages/index/index.page.tsx +19 -0
  36. package/{input-files → files/ts}/react/renderer/Link.tsx +0 -0
  37. package/{input-files → files/ts}/react/renderer/PageWrapper.tsx +0 -0
  38. package/{input-files → files/ts}/react/renderer/_default.page.client.tsx +0 -0
  39. package/{input-files → files/ts}/react/renderer/_default.page.server.tsx +0 -0
  40. package/{input-files → files/ts}/react/renderer/_error.page.tsx +0 -0
  41. package/{input-files → files/ts}/react/renderer/types.ts +0 -0
  42. package/{input-files → files/ts}/react/renderer/usePageContext.tsx +0 -0
  43. package/files/ts/react+client-router/renderer/_default.page.client.tsx +44 -0
  44. package/files/ts/react+client-router/renderer/types.ts +12 -0
  45. package/{input-files/common → files/ts/vike}/server/index.ts +0 -5
  46. package/files/ts/vue/pages/about/index.page.vue +11 -0
  47. package/{input-files → files/ts}/vue/pages/index/Counter.vue +0 -0
  48. package/{input-files → files/ts}/vue/pages/index/index.page.vue +0 -0
  49. package/{input-files → files/ts}/vue/renderer/Link.vue +0 -0
  50. package/{input-files → files/ts}/vue/renderer/PageWrapper.vue +0 -0
  51. package/{input-files → files/ts}/vue/renderer/_default.page.client.ts +0 -0
  52. package/{input-files → files/ts}/vue/renderer/_default.page.server.ts +0 -0
  53. package/{input-files → files/ts}/vue/renderer/_error.page.vue +0 -0
  54. package/{input-files → files/ts}/vue/renderer/app.ts +0 -12
  55. package/{input-files → files/ts}/vue/renderer/types.ts +0 -0
  56. package/{input-files → files/ts}/vue/renderer/usePageContext.ts +0 -0
  57. package/{input-files → files/ts}/vue/vue.d.ts +0 -0
  58. package/files/ts/vue+client-router/renderer/_default.page.client.ts +24 -0
  59. package/files/ts/vue+client-router/renderer/app.ts +37 -0
  60. package/files/ts/vue+client-router/renderer/types.ts +11 -0
  61. package/package.json +56 -53
  62. package/input-files/common/_gitignore +0 -4
  63. package/input-files/vue/tsconfig.json +0 -18
package/README.md CHANGED
@@ -17,12 +17,14 @@ Options:
17
17
  ```
18
18
 
19
19
  ## How it works
20
- - Copies files from `input-files/common`, `input-files/react`, and `input-files/vue` renaming and transforming them as necessary.
21
- - Generates plain JS files from the TS files via `detype`.
20
+ - Generates TypeScript and JavaScript files from the templates in `input-files` via `detype`.
21
+ - Copies files from `files/<language>/<feature>` where:
22
+ - `<language>` is `shared`, `ts`, or `js`
23
+ - `<feature>` is any combination of `vike` (default), `react`, `vue`, and `client-router` combined with a plus sign
22
24
  - Configuration files (`package.json`, `tsconfig.json`, and `vite.config.{js,ts}`) are generated programmatically.
23
- - Boilerplate package versions are kept in `src/config-generators/package-versions.json`.
25
+ - Boilerplate package versions are kept in `src/config-generators/package-versions.json`, `check-deps.mjs` can be used to review package updates.
24
26
 
25
- ## Roadmap
27
+ ## TODO
28
+ - Add integration tests
26
29
  - Create an interactive frontend
27
- - Apply TS > JS transform in build-time to speed up generation
28
30
  - Resolve user's default prettier config if any and use it instead of the default
package/dist/index.js CHANGED
@@ -21,104 +21,42 @@ var __spreadValues = (a, b) => {
21
21
  return a;
22
22
  };
23
23
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
24
- var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
25
- var __reExport = (target, module2, desc) => {
26
- if (module2 && typeof module2 === "object" || typeof module2 === "function") {
27
- for (let key of __getOwnPropNames(module2))
28
- if (!__hasOwnProp.call(target, key) && key !== "default")
29
- __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
24
+ var __copyProps = (to, from, except, desc) => {
25
+ if (from && typeof from === "object" || typeof from === "function") {
26
+ for (let key of __getOwnPropNames(from))
27
+ if (!__hasOwnProp.call(to, key) && key !== except)
28
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
30
29
  }
31
- return target;
32
- };
33
- var __toModule = (module2) => {
34
- return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
30
+ return to;
35
31
  };
32
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
36
33
 
37
34
  // src/index.ts
38
- var import_commander = __toModule(require("commander"));
35
+ var import_commander = require("commander");
39
36
 
40
37
  // src/generate.ts
41
- var import_path2 = __toModule(require("path"));
42
-
43
- // src/copy-files.ts
44
- var import_fs = __toModule(require("fs"));
45
- var import_path = __toModule(require("path"));
46
- var import_walk = __toModule(require("walk"));
47
- async function copyFiles(src, dest, filter) {
48
- return new Promise((resolve) => {
49
- const walker = (0, import_walk.walk)(src, {});
50
- walker.on("file", async (root, fileStats, next) => {
51
- var _a;
52
- const srcPath = import_path.default.join(root, fileStats.name);
53
- const relativePath = import_path.default.relative(src, srcPath);
54
- const destPath = import_path.default.join(dest, relativePath);
55
- const filterResult = await filter({
56
- path: relativePath,
57
- readContent: () => import_fs.default.promises.readFile(srcPath, "utf-8"),
58
- srcPath,
59
- destPath
60
- });
61
- switch (filterResult.operation) {
62
- case "skip":
63
- break;
64
- case "copy":
65
- {
66
- const outFileDir = import_path.default.dirname(destPath);
67
- await import_fs.default.promises.mkdir(outFileDir, { recursive: true });
68
- await import_fs.default.promises.copyFile(srcPath, destPath);
69
- }
70
- break;
71
- case "rename":
72
- {
73
- const outFileName = import_path.default.join(dest, filterResult.newPath);
74
- const outFileDir = import_path.default.dirname(outFileName);
75
- await import_fs.default.promises.mkdir(outFileDir, { recursive: true });
76
- await import_fs.default.promises.copyFile(srcPath, outFileName);
77
- }
78
- break;
79
- case "transform":
80
- {
81
- const outFileName = import_path.default.join(dest, (_a = filterResult.newPath) != null ? _a : relativePath);
82
- const outFileDir = import_path.default.dirname(outFileName);
83
- await import_fs.default.promises.mkdir(outFileDir, { recursive: true });
84
- await import_fs.default.promises.writeFile(outFileName, filterResult.newContent, "utf-8");
85
- }
86
- break;
87
- default:
88
- assertNever(filterResult);
89
- }
90
- next();
91
- });
92
- walker.on("end", resolve);
93
- });
94
- }
95
- function assertNever(arg) {
96
- throw new Error("Unexpected type");
97
- }
98
-
99
- // src/generate.ts
100
- var import_detype = __toModule(require("detype"));
101
- var import_promises = __toModule(require("fs/promises"));
102
- var import_prettier = __toModule(require("prettier"));
38
+ var import_path = __toESM(require("path"));
39
+ var import_promises = __toESM(require("fs/promises"));
40
+ var import_prettier = require("prettier");
103
41
 
104
42
  // src/config-generators/package-versions.json
105
- var _types_express = "^4.17.13";
106
- var _types_node = "^16.10.2";
107
- var _types_react_dom = "^17.0.9";
108
- var _types_react = "^17.0.27";
109
- var _vitejs_plugin_react_refresh = "^1.3.6";
110
- var _vitejs_plugin_vue = "^1.9.2";
111
- var _vue_compiler_sfc = "^3.2.19";
112
- var _vue_server_renderer = "^3.2.19";
113
- var cross_env = "^7.0.3";
114
- var express = "^4.17.1";
115
- var react_dom = "^17.0.2";
116
- var react = "^17.0.2";
117
- var ts_node = "^10.2.1";
118
- var typescript = "^4.4.3";
119
- var vite_plugin_ssr = "^0.3.8";
120
- var vite = "2.6.2";
121
- var vue = "^3.2.19";
43
+ var _types_express = "4.17.13";
44
+ var _types_node = "17.0.36";
45
+ var _types_react_dom = "18.0.5";
46
+ var _types_react = "18.0.9";
47
+ var _vitejs_plugin_react_refresh = "1.3.6";
48
+ var _vitejs_plugin_vue = "2.3.3";
49
+ var _vue_compiler_sfc = "3.2.36";
50
+ var _vue_server_renderer = "3.2.36";
51
+ var cross_env = "7.0.3";
52
+ var express = "4.18.1";
53
+ var react_dom = "18.1.0";
54
+ var react = "18.1.0";
55
+ var ts_node = "10.8.0";
56
+ var typescript = "4.7.2";
57
+ var vite_plugin_ssr = "0.3.64";
58
+ var vite = "2.9.9";
59
+ var vue = "3.2.36";
122
60
  var package_versions_default = {
123
61
  "@types/express": _types_express,
124
62
  "@types/node": _types_node,
@@ -168,7 +106,7 @@ var ConfigGenerator = class {
168
106
  return [dep, package_versions_default[dep]];
169
107
  }))
170
108
  });
171
- return JSON.stringify(output, void 0, 2);
109
+ return output;
172
110
  }
173
111
  generateViteConfig() {
174
112
  const config = this.getViteConfig();
@@ -279,77 +217,186 @@ var VUE_DEPENDENCIES = [
279
217
  "vue"
280
218
  ];
281
219
 
220
+ // src/detect-package-manager.ts
221
+ function detectPackageManager() {
222
+ const agent = process.env.npm_config_user_agent;
223
+ if (agent) {
224
+ const [program2] = agent.split("/");
225
+ if (program2 === "yarn")
226
+ return "yarn";
227
+ if (program2 === "pnpm")
228
+ return "pnpm";
229
+ if (program2 === "npm")
230
+ return "npm";
231
+ }
232
+ if (process.platform !== "win32") {
233
+ const parent = process.env._;
234
+ if (parent) {
235
+ if (parent.endsWith("pnpx") || parent.endsWith("pnpm"))
236
+ return "pnpm";
237
+ if (parent.endsWith("yarn"))
238
+ return "yarn";
239
+ if (parent.endsWith("npx") || parent.endsWith("npm"))
240
+ return "npm";
241
+ }
242
+ }
243
+ return null;
244
+ }
245
+
246
+ // src/run-command.ts
247
+ var import_child_process = require("child_process");
248
+ async function runCommand(command, ...args) {
249
+ return new Promise((resolve, reject) => {
250
+ const child = (0, import_child_process.spawn)(command, args, { stdio: "inherit" });
251
+ child.on("close", (code) => {
252
+ if (code === 0) {
253
+ resolve();
254
+ } else {
255
+ reject();
256
+ }
257
+ });
258
+ });
259
+ }
260
+
282
261
  // src/generate.ts
283
- var INPUT_FILES_DIR = import_path2.default.resolve(__dirname, "../input-files");
262
+ var import_walk = require("walk");
263
+ var FILES_DIR = import_path.default.resolve(__dirname, "../files");
284
264
  async function generate({
285
- framework: frontendFramework,
286
- language,
287
- outputDir
265
+ outputDir,
266
+ typescript: typescript2,
267
+ react: react2,
268
+ vue: vue2,
269
+ clientRouter,
270
+ skipDependencies,
271
+ npm,
272
+ yarn,
273
+ pnpm,
274
+ initGitRepo,
275
+ createInitialCommit
288
276
  }) {
289
- const prettierConfig = {};
290
- const filter = async ({ path: path3, readContent, srcPath }) => {
291
- if (path3 === "_gitignore") {
292
- return { operation: "rename", newPath: ".gitignore" };
293
- }
294
- if (language !== "ts" && (["tsconfig.json", "renderer/types.ts"].includes(path3) || path3.endsWith(".d.ts")))
295
- return { operation: "skip" };
296
- if (path3 === "vue.d.ts") {
297
- return frontendFramework === "vue" ? { operation: "copy" } : { operation: "skip" };
298
- }
299
- if (!path3.endsWith(".ts") && !path3.endsWith(".tsx") && !path3.endsWith(".vue")) {
300
- return { operation: "copy" };
301
- }
302
- if (language === "ts") {
303
- return {
304
- operation: "transform",
305
- newContent: (0, import_prettier.format)((0, import_detype.removeMagicComments)(await readContent()), __spreadProps(__spreadValues({}, prettierConfig), {
306
- filepath: srcPath
307
- }))
308
- };
309
- }
310
- const newPath = path3.endsWith(".ts") ? path3.slice(0, -2) + "js" : path3.endsWith(".tsx") ? path3.slice(0, -3) + "jsx" : path3;
311
- return {
312
- operation: "transform",
313
- newPath,
314
- newContent: await (0, import_detype.transform)(await readContent(), srcPath, prettierConfig)
315
- };
316
- };
317
- await copyFiles(import_path2.default.join(INPUT_FILES_DIR, "common"), outputDir, filter);
318
- await copyFiles(import_path2.default.join(INPUT_FILES_DIR, frontendFramework), outputDir, filter);
277
+ let error = false;
278
+ if (Number(npm) + Number(pnpm) + Number(yarn) > 1) {
279
+ process.stderr.write("Only one of npm, pnpm, or yarn can be specified\n");
280
+ error = true;
281
+ }
282
+ let packageManager = npm ? "npm" : pnpm ? "pnpm" : yarn ? "yarn" : void 0;
283
+ if (!packageManager) {
284
+ const detected = detectPackageManager();
285
+ packageManager = detected || "npm";
286
+ }
287
+ if (vue2 && react2) {
288
+ process.stderr.write("Cannot use both react and vue\n");
289
+ error = true;
290
+ } else if (!vue2 && !react2) {
291
+ process.stderr.write("Please specify either --react or --vue\n");
292
+ error = true;
293
+ }
294
+ if (error) {
295
+ process.exit(1);
296
+ }
297
+ const framework = vue2 ? "vue" : "react";
298
+ const features = ["vike", framework];
299
+ if (clientRouter)
300
+ features.push("client-router");
301
+ const language = typescript2 ? "ts" : "js";
302
+ async function findDirs(dir) {
303
+ return (await import_promises.default.readdir(import_path.default.join(FILES_DIR, dir), {
304
+ withFileTypes: true
305
+ })).filter((x) => x.isDirectory()).map((x) => ({
306
+ name: import_path.default.join(dir, x.name),
307
+ features: x.name.split("+")
308
+ })).sort((a, b) => a.features.length - b.features.length);
309
+ }
310
+ const dirs = [...await findDirs("shared"), ...await findDirs(language)].filter((x) => x.features.every((f) => features.includes(f))).map((x) => x.name);
311
+ console.log("Copying files");
312
+ const toBeCopied = {};
313
+ for (const dir of dirs) {
314
+ const files = await getFiles(dir);
315
+ files.forEach((x) => {
316
+ let targetName = x.slice(dir.length + 1);
317
+ if (targetName === "_gitignore")
318
+ targetName = ".gitignore";
319
+ toBeCopied[targetName] = x;
320
+ });
321
+ }
322
+ for (const [targetName, sourcePath] of Object.entries(toBeCopied)) {
323
+ const targetPath = import_path.default.join(outputDir, targetName);
324
+ const dirName = import_path.default.dirname(targetPath);
325
+ await import_promises.default.mkdir(dirName, { recursive: true });
326
+ await import_promises.default.copyFile(import_path.default.join(FILES_DIR, sourcePath), targetPath);
327
+ }
319
328
  const generators = {
320
329
  react: ReactConfigGenerator,
321
330
  vue: VueConfigGenerator
322
331
  };
323
- const generator = new generators[frontendFramework](language);
324
- const packageJsonfileName = import_path2.default.join(outputDir, "package.json");
325
- await import_promises.default.writeFile(packageJsonfileName, (0, import_prettier.format)(generator.generatePackageJson(), __spreadProps(__spreadValues({}, prettierConfig), {
332
+ console.log("Generating configutation files");
333
+ const generator = new generators[framework](language);
334
+ const prettierConfig = {};
335
+ const packageJson = generator.generatePackageJson();
336
+ if (yarn) {
337
+ packageJson.scripts.dev = "yarn server";
338
+ packageJson.scripts.prod = "yarn build && yarn server:prod";
339
+ } else if (pnpm) {
340
+ packageJson.scripts.dev = "pnpm run server";
341
+ packageJson.scripts.prod = "pnpm build && pnpm server:prod";
342
+ }
343
+ const packageJsonfileName = import_path.default.join(outputDir, "package.json");
344
+ await import_promises.default.writeFile(packageJsonfileName, (0, import_prettier.format)(JSON.stringify(packageJson), __spreadProps(__spreadValues({}, prettierConfig), {
326
345
  filepath: packageJsonfileName
327
346
  })));
328
347
  if (language === "ts") {
329
- const fn = import_path2.default.join(outputDir, "tsconfig.json");
348
+ const fn = import_path.default.join(outputDir, "tsconfig.json");
330
349
  await import_promises.default.writeFile(fn, (0, import_prettier.format)(generator.generateTsConfig(), __spreadProps(__spreadValues({}, prettierConfig), { filepath: fn })));
331
350
  }
332
- const viteConfigFileName = import_path2.default.join(outputDir, "vite.config." + language);
351
+ const viteConfigFileName = import_path.default.join(outputDir, "vite.config." + language);
333
352
  await import_promises.default.writeFile(viteConfigFileName, (0, import_prettier.format)(generator.generateViteConfig(), __spreadProps(__spreadValues({}, prettierConfig), {
334
353
  filepath: viteConfigFileName
335
354
  })));
355
+ process.chdir(outputDir);
356
+ if (!skipDependencies) {
357
+ console.log("Installing dependencies with", packageManager);
358
+ await runCommand(packageManager, "install");
359
+ }
360
+ if (initGitRepo) {
361
+ console.log("Initializing git repository");
362
+ await runCommand("git", "init");
363
+ if (initGitRepo === true)
364
+ initGitRepo = "main";
365
+ if (initGitRepo !== "master") {
366
+ await runCommand("git", "checkout", "-b", initGitRepo);
367
+ }
368
+ if (createInitialCommit) {
369
+ console.log("Creating initial commit");
370
+ if (createInitialCommit === true)
371
+ createInitialCommit = `Initialized Vike ${typescript2 ? "TypeScript" : "vanilla JavaScript"} boilerplate for ${react2 ? "React" : "Vue"}`;
372
+ await runCommand("git", "add", ".");
373
+ await runCommand("git", "commit", "-m", createInitialCommit);
374
+ }
375
+ } else if (createInitialCommit) {
376
+ console.warn("Ignoring initial commit because no git repo was initialized");
377
+ }
378
+ }
379
+ async function getFiles(dir) {
380
+ const files = [];
381
+ const walker = (0, import_walk.walk)(import_path.default.join(FILES_DIR, dir));
382
+ walker.on("file", (root, fileStats, next) => {
383
+ files.push(import_path.default.relative(FILES_DIR, import_path.default.join(root, fileStats.name)));
384
+ next();
385
+ });
386
+ await new Promise((resolve, reject) => {
387
+ walker.on("end", resolve);
388
+ walker.on("errors", (_, statsArr) => {
389
+ const errors = statsArr.map((stat) => stat.error);
390
+ reject(new Error(`Failed to walk ${dir}: ${errors.join(", ")}`));
391
+ });
392
+ });
393
+ return files;
336
394
  }
337
395
 
338
396
  // package.json
339
- var version = "0.0.1";
397
+ var version = "0.0.4";
340
398
 
341
399
  // src/index.ts
342
- import_commander.program.description("Generates Vike application boilerplate").version(version);
343
- import_commander.program.argument("<output-dir>", "Output directory");
344
- import_commander.program.addOption(new import_commander.Option("-f, --framework <framework>", "Frontend framework").choices([
345
- "react",
346
- "vue"
347
- ]));
348
- import_commander.program.addOption(new import_commander.Option("-l, --language <language>", "Programming language").choices([
349
- "js",
350
- "ts"
351
- ]));
352
- import_commander.program.action((outputDir, options) => {
400
+ import_commander.program.description("Generates Vike application boilerplate").version(version).argument("<output-dir>", "Output directory").option("-t, --typescript", "use TypeScript", false).option("-n, --npm", "use npm package manager", false).option("-y, --yarn", "use yarn package manager", false).option("-p, --pnpm", "use pnpm package manager", false).option("-v, --vue", "use Vue", false).option("-r, --react", "use React", false).option("-c, --client-router", "use client router", false).option("-s, --skip-dependencies", "skip installing dependencies", false).option("-i, --init-git-repo [branch]", "initialize git repo", false).option("-m, --create-initial-commit [message]", "create initial commit", false).action((outputDir, options) => {
353
401
  generate(__spreadValues({ outputDir }, options));
354
- });
355
- import_commander.program.parse();
402
+ }).parse();
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import { usePageContext } from "./usePageContext";
3
+
4
+ export { Link };
5
+
6
+ function Link(props) {
7
+ const pageContext = usePageContext();
8
+ const className = [
9
+ props.className,
10
+ pageContext.urlPathname === props.href && "is-active",
11
+ ]
12
+ .filter(Boolean)
13
+ .join(" ");
14
+ return <a {...props} className={className} />;
15
+ }
@@ -0,0 +1,89 @@
1
+ import React from "react";
2
+ import logo from "./logo.svg";
3
+ import { PageContextProvider } from "./usePageContext";
4
+ import "./PageWrapper.css";
5
+ import { Link } from "./Link";
6
+
7
+ export { PageWrapper };
8
+
9
+ function PageWrapper({ children, pageContext }) {
10
+ return (
11
+ <React.StrictMode>
12
+ <PageContextProvider pageContext={pageContext}>
13
+ <Layout>
14
+ <Sidebar>
15
+ <Logo />
16
+ <Link className="navitem" href="/">
17
+ Home
18
+ </Link>
19
+ <Link className="navitem" href="/about">
20
+ About
21
+ </Link>
22
+ </Sidebar>
23
+ <Content>{children}</Content>
24
+ </Layout>
25
+ </PageContextProvider>
26
+ </React.StrictMode>
27
+ );
28
+ }
29
+
30
+ function Layout({ children }) {
31
+ return (
32
+ <div
33
+ style={{
34
+ display: "flex",
35
+ maxWidth: 900,
36
+ margin: "auto",
37
+ }}
38
+ >
39
+ {children}
40
+ </div>
41
+ );
42
+ }
43
+
44
+ function Sidebar({ children }) {
45
+ return (
46
+ <div
47
+ style={{
48
+ padding: 20,
49
+ flexShrink: 0,
50
+ display: "flex",
51
+ flexDirection: "column",
52
+ alignItems: "center",
53
+ lineHeight: "1.8em",
54
+ }}
55
+ >
56
+ {children}
57
+ </div>
58
+ );
59
+ }
60
+
61
+ function Content({ children }) {
62
+ return (
63
+ <div
64
+ style={{
65
+ padding: 20,
66
+ paddingBottom: 50,
67
+ borderLeft: "2px solid #eee",
68
+ minHeight: "100vh",
69
+ }}
70
+ >
71
+ {children}
72
+ </div>
73
+ );
74
+ }
75
+
76
+ function Logo() {
77
+ return (
78
+ <div
79
+ style={{
80
+ marginTop: 20,
81
+ marginBottom: 10,
82
+ }}
83
+ >
84
+ <a href="/">
85
+ <img src={logo} height={64} width={64} alt="logo" />
86
+ </a>
87
+ </div>
88
+ );
89
+ }
@@ -0,0 +1,19 @@
1
+ import ReactDOM from "react-dom";
2
+ import React from "react";
3
+ import { getPage } from "vite-plugin-ssr/client";
4
+ import { PageWrapper } from "./PageWrapper";
5
+
6
+ hydrate();
7
+
8
+ async function hydrate() {
9
+ // We do Server Routing, but we can also do Client Routing by using `useClientRouter()`
10
+ // instead of `getPage()`, see https://vite-plugin-ssr.com/useClientRouter
11
+ const pageContext = await getPage();
12
+ const { Page, pageProps } = pageContext;
13
+ ReactDOM.hydrate(
14
+ <PageWrapper pageContext={pageContext}>
15
+ <Page {...pageProps} />
16
+ </PageWrapper>,
17
+ document.getElementById("page-view")
18
+ );
19
+ }
@@ -0,0 +1,47 @@
1
+ import ReactDOMServer from "react-dom/server";
2
+ import React from "react";
3
+ import { PageWrapper } from "./PageWrapper";
4
+ import { escapeInject, dangerouslySkipEscape } from "vite-plugin-ssr";
5
+ import logoUrl from "./logo.svg";
6
+
7
+ export { render };
8
+
9
+ // See https://vite-plugin-ssr.com/data-fetching
10
+ export const passToClient = ["pageProps", "urlPathname"];
11
+
12
+ async function render(pageContext) {
13
+ const { Page, pageProps } = pageContext;
14
+ const pageHtml = ReactDOMServer.renderToString(
15
+ <PageWrapper pageContext={pageContext}>
16
+ <Page {...pageProps} />
17
+ </PageWrapper>
18
+ );
19
+
20
+ // See https://vite-plugin-ssr.com/html-head
21
+ const { documentProps } = pageContext;
22
+ const title = (documentProps && documentProps.title) || "Vite SSR app";
23
+ const desc =
24
+ (documentProps && documentProps.description) ||
25
+ "App using Vite + vite-plugin-ssr";
26
+
27
+ const documentHtml = escapeInject`<!DOCTYPE html>
28
+ <html lang="en">
29
+ <head>
30
+ <meta charset="UTF-8" />
31
+ <link rel="icon" href="${logoUrl}" />
32
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
33
+ <meta name="description" content="${desc}" />
34
+ <title>${title}</title>
35
+ </head>
36
+ <body>
37
+ <div id="page-view">${dangerouslySkipEscape(pageHtml)}</div>
38
+ </body>
39
+ </html>`;
40
+
41
+ return {
42
+ documentHtml,
43
+ pageContext: {
44
+ // We can add some `pageContext` here, which is useful if we want to do page redirection https://vite-plugin-ssr.com/page-redirection
45
+ },
46
+ };
47
+ }
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+
3
+ export { Page };
4
+
5
+ function Page({ is404 }) {
6
+ if (is404) {
7
+ return (
8
+ <>
9
+ <h1>404 Page Not Found</h1>
10
+ <p>This page could not be found.</p>
11
+ </>
12
+ );
13
+ } else {
14
+ return (
15
+ <>
16
+ <h1>500 Internal Server Error</h1>
17
+ <p>Something went wrong.</p>
18
+ </>
19
+ );
20
+ }
21
+ }
@@ -0,0 +1,18 @@
1
+ // `usePageContext` allows us to access `pageContext` in any React component.
2
+ // More infos: https://vite-plugin-ssr.com/pageContext-anywhere
3
+
4
+ import React, { useContext } from "react";
5
+
6
+ export { PageContextProvider };
7
+ export { usePageContext };
8
+
9
+ const Context = React.createContext(undefined);
10
+
11
+ function PageContextProvider({ pageContext, children }) {
12
+ return <Context.Provider value={pageContext}>{children}</Context.Provider>;
13
+ }
14
+
15
+ function usePageContext() {
16
+ const pageContext = useContext(Context);
17
+ return pageContext;
18
+ }
@@ -0,0 +1,43 @@
1
+ import ReactDOM from "react-dom";
2
+ import React from "react";
3
+ import { useClientRouter } from "vite-plugin-ssr/client/router";
4
+ import { PageWrapper } from "./PageWrapper";
5
+
6
+ useClientRouter({
7
+ async render(pageContext) {
8
+ if (pageContext.isHydration) {
9
+ // When we render the first page. (Since we do SSR, the first page is already
10
+ // rendered to HTML and we merely have to hydrate it.)
11
+ const { Page, pageProps } = pageContext;
12
+ ReactDOM.hydrate(
13
+ <PageWrapper pageContext={pageContext}>
14
+ <Page {...pageProps} />
15
+ </PageWrapper>,
16
+ document.getElementById("page-view")
17
+ );
18
+ } else {
19
+ // When the user navigates to a new page.
20
+ const { Page, pageProps } = pageContext;
21
+ ReactDOM.render(
22
+ <PageWrapper pageContext={pageContext}>
23
+ <Page {...pageProps} />
24
+ </PageWrapper>,
25
+ document.getElementById("page-view")
26
+ );
27
+ }
28
+ },
29
+
30
+ ensureHydration: true,
31
+
32
+ prefetchLinks: true,
33
+
34
+ onTransitionStart() {
35
+ // Page transition started
36
+ },
37
+
38
+ onTransitionEnd() {
39
+ // Page transition ended
40
+ },
41
+ }).hydrationPromise.then(() => {
42
+ // Hydration finished; page is now interactive
43
+ });