@troyio/vite-file-router 0.0.0-stage → 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/README.md CHANGED
@@ -1,3 +1,74 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # vite-file-router
2
+
3
+ A Vite plugin that generates a route tree from a file-based page directory.
4
+
5
+ ## Install
6
+
7
+ The package is published to GitHub Packages on every push to `main`, under
8
+ `@OWNER/vite-file-router`. Configure npm to use GitHub Packages for that scope
9
+ and authenticate with a GitHub token that has `read:packages` access:
10
+
11
+ ```ini
12
+ @OWNER:registry=https://npm.pkg.github.com
13
+ //npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
14
+ ```
15
+
16
+ Then install it with:
17
+
18
+ ```sh
19
+ npm install @OWNER/vite-file-router
20
+ ```
21
+
22
+ Each main-branch build is published with a unique prerelease version.
23
+
24
+ Vite is a peer dependency and must also be installed in your project.
25
+
26
+ ## Configure
27
+
28
+ ```ts
29
+ import { defineConfig } from "vite";
30
+ import { routes } from "@OWNER/vite-file-router";
31
+
32
+ export default defineConfig({
33
+ plugins: [routes()],
34
+ });
35
+ ```
36
+
37
+ By default, the plugin scans `src/pages`. Pass a path relative to the Vite
38
+ project root to use another directory:
39
+
40
+ ```ts
41
+ plugins: [routes("src/routes")];
42
+ ```
43
+
44
+ Each `page.tsx` becomes a route page, and each `layout.tsx` becomes a lazy
45
+ layout for its directory. Directories become nested routes. For example:
46
+
47
+ ```text
48
+ src/pages/
49
+ layout.tsx
50
+ page.tsx
51
+ users/
52
+ page.tsx
53
+ [id]/
54
+ page.tsx
55
+ ```
56
+
57
+ Dynamic segments such as `[id]` become `:id`; catch-all segments such as
58
+ `[...slug]` become `*`.
59
+
60
+ Import the generated route configuration from the `virtual:routes` module in
61
+ your application:
62
+
63
+ ```ts
64
+ import { routes } from "virtual:routes";
65
+ ```
66
+
67
+ The virtual module exports a `routes` array with lazy-loaded page and layout
68
+ components.
69
+
70
+ ## Build
71
+
72
+ ```sh
73
+ npm run build
74
+ ```
@@ -0,0 +1,2 @@
1
+ import type { Node } from "./scanner";
2
+ export declare function generate(node: Node, root?: boolean): string;
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from "vite";
2
+ export declare function routes(directory?: string): Plugin;
package/dist/index.js ADDED
@@ -0,0 +1,86 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { genArrayFromRaw, genObjectFromRaw } from "knitwork";
4
+ import { normalizePath } from "vite";
5
+ // src/scanner.ts
6
+ function scan(directory) {
7
+ const node = {
8
+ name: path.basename(directory),
9
+ directory,
10
+ children: []
11
+ };
12
+ if (!fs.existsSync(directory)) return node;
13
+ const entries = fs.readdirSync(directory, { withFileTypes: true });
14
+ for (const entry of entries) {
15
+ const file = path.join(directory, entry.name);
16
+ if (entry.isDirectory()) {
17
+ node.children.push(scan(file));
18
+ continue;
19
+ }
20
+ if (entry.name === "page.tsx") node.page = file;
21
+ if (entry.name === "layout.tsx") node.layout = file;
22
+ }
23
+ return node;
24
+ }
25
+ // src/generator.ts
26
+ function getRoutePath(directory) {
27
+ const segment = path.basename(directory);
28
+ if (segment.startsWith("[...") && segment.endsWith("]")) return "*";
29
+ return segment.replace(/^\[(.+)\]$/, ":$1");
30
+ }
31
+ function generateImport(file) {
32
+ return `async ()=>({
33
+ Component: (await import(${JSON.stringify(normalizePath(file))})).default
34
+ })`;
35
+ }
36
+ function generate(node, root = false) {
37
+ const route = {};
38
+ if (!root) route.path = JSON.stringify(getRoutePath(node.directory));
39
+ if (node.layout) route.lazy = generateImport(node.layout);
40
+ const children = [];
41
+ if (node.page) children.push(genObjectFromRaw({
42
+ index: "true",
43
+ lazy: generateImport(node.page)
44
+ }));
45
+ for (const child of node.children) children.push(generate(child));
46
+ if (children.length) route.children = genArrayFromRaw(children);
47
+ return genObjectFromRaw(route);
48
+ }
49
+ // src/index.ts
50
+ var MODULE_ID = "virtual:routes";
51
+ var RESOLVED_ID = `\0${MODULE_ID}`;
52
+ function routes(directory = "src/pages") {
53
+ let root = "";
54
+ function execute() {
55
+ const routes = generate(scan(root), true);
56
+ return `
57
+ export const routes = ${genArrayFromRaw([routes])};
58
+ `;
59
+ }
60
+ return {
61
+ name: "routes",
62
+ configResolved(config) {
63
+ root = path.resolve(config.root, directory);
64
+ },
65
+ resolveId: (id) => id === MODULE_ID ? RESOLVED_ID : void 0,
66
+ load: (id) => id === RESOLVED_ID ? execute() : void 0,
67
+ configureServer: (server) => {
68
+ server.watcher.add(root);
69
+ server.watcher.on("all", (event, file) => {
70
+ if (![
71
+ "add",
72
+ "unlink",
73
+ "addDir",
74
+ "unlinkDir",
75
+ "change"
76
+ ].includes(event)) return;
77
+ const location = path.resolve(file);
78
+ if (location !== root && !location.startsWith(root + path.sep)) return;
79
+ const module = server.moduleGraph.getModuleById(RESOLVED_ID);
80
+ if (module) server.moduleGraph.invalidateModule(module);
81
+ server.ws.send({ type: "full-reload" });
82
+ });
83
+ }
84
+ };
85
+ }
86
+ export { routes };
@@ -0,0 +1,8 @@
1
+ export type Node = {
2
+ name: string;
3
+ directory: string;
4
+ page?: string;
5
+ layout?: string;
6
+ children: Node[];
7
+ };
8
+ export declare function scan(directory: string): Node;
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@troyio/vite-file-router",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "File-system routing plugin for Vite",
5
+ "type": "module",
6
+ "private": false,
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "types"
13
+ ],
14
+ "main": "./dist/index.js",
15
+ "types": "./types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./types/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "vite build && tsc -p tsconfig.build.json",
24
+ "lint": "eslint src vite.config.ts"
25
+ },
26
+ "dependencies": {
27
+ "knitwork": "^1.3.0",
28
+ "react-router": "^8.5.1",
29
+ "vite-file-router": "link:"
30
+ },
31
+ "peerDependencies": {
32
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@eslint/js": "^10.0.1",
36
+ "@types/node": "^24.19.2",
37
+ "eslint": "^10.13.0",
38
+ "globals": "^17.13.0",
39
+ "typescript": "~6.0.3",
40
+ "typescript-eslint": "^8.72.0",
41
+ "vite": "^8.3.5"
42
+ }
43
+ }
@@ -0,0 +1,3 @@
1
+ /// <reference path="./virtual-routes.d.ts" />
2
+
3
+ export { routes } from "../dist/index.js";
@@ -0,0 +1,4 @@
1
+ declare module "virtual:routes" {
2
+ import type { RouteObject } from "react-router";
3
+ export const routes: RouteObject[];
4
+ }