@troyio/vite-file-router 0.2.0 → 0.2.1

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
@@ -65,3 +65,9 @@ components.
65
65
  ```sh
66
66
  npm run build
67
67
  ```
68
+
69
+ ## Test
70
+
71
+ ```sh
72
+ npm test
73
+ ```
@@ -1,6 +1,7 @@
1
1
  import type { Node } from "./scanner";
2
2
  export type GeneratedRoutes = {
3
3
  imports: string[];
4
+ helpers: string[];
4
5
  routes: string;
5
6
  };
6
- export declare function generate(node: Node, root?: boolean): GeneratedRoutes;
7
+ export declare function generate(node: Node, root?: boolean, projectRoot?: string): GeneratedRoutes;
package/dist/index.js CHANGED
@@ -1,43 +1,24 @@
1
- import fs from "node:fs";
2
1
  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
- const match = /^(page|layout|loading)\.(jsx|tsx)$/.exec(entry.name);
21
- if (!match) continue;
22
- const [, name, extension] = match;
23
- const key = name;
24
- if (!node[key] || extension === "tsx") node[key] = file;
25
- }
26
- return node;
27
- }
2
+ import { genArrayFromRaw, genObjectFromRaw, genSafeVariableName } from "knitwork";
3
+ import { format } from "prettier";
4
+ import fs from "node:fs";
28
5
  // src/generator.ts
6
+ function getImportPath(file, projectRoot) {
7
+ const relativePath = path.relative(projectRoot, file).split(path.sep).join("/");
8
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
9
+ }
29
10
  function getRoutePath(directory) {
30
11
  const segment = path.basename(directory);
31
12
  if (segment.startsWith("[...") && segment.endsWith("]")) return "*";
32
13
  return segment.replace(/^\[(.+)\]$/, ":$1");
33
14
  }
34
- function generateImport(file) {
15
+ function generateImport(file, projectRoot) {
35
16
  return `async () => ({
36
- Component: (await import(${JSON.stringify(normalizePath(file))})).default
17
+ Component: (await import(${JSON.stringify(getImportPath(file, projectRoot))})).default
37
18
  })`;
38
19
  }
39
- function generateLazyComponent(file) {
40
- return `lazy(() => import(${JSON.stringify(normalizePath(file))}))`;
20
+ function generateLazyComponent(file, projectRoot) {
21
+ return `lazy(() => import(${JSON.stringify(getImportPath(file, projectRoot))}))`;
41
22
  }
42
23
  function generateNode(node, root, context, inheritedBoundary) {
43
24
  const route = {};
@@ -45,25 +26,25 @@ function generateNode(node, root, context, inheritedBoundary) {
45
26
  if (!root) route.path = JSON.stringify(getRoutePath(node.directory));
46
27
  let loadingComponent;
47
28
  if (node.loading) {
48
- loadingComponent = `Loading${context.nextLoadingId++}`;
49
- context.loadingImports.push(`import ${loadingComponent} from ${JSON.stringify(normalizePath(node.loading))};`);
29
+ loadingComponent = genSafeVariableName(`Loading${context.nextLoadingId++}`);
30
+ context.loadingImports.push(`import ${loadingComponent} from ${JSON.stringify(getImportPath(node.loading, context.projectRoot))};`);
50
31
  context.hasBoundaries = true;
51
32
  }
52
33
  if (loadingComponent) {
53
- const child = node.layout ? generateLazyComponent(node.layout) : "Outlet";
34
+ const child = node.layout ? generateLazyComponent(node.layout, context.projectRoot) : "Outlet";
54
35
  route.Component = `createBoundary(${loadingComponent}, ${child})`;
55
36
  } else if (node.layout) {
56
- if (hasBoundary) route.Component = generateLazyComponent(node.layout);
57
- else route.lazy = generateImport(node.layout);
37
+ if (hasBoundary) route.Component = generateLazyComponent(node.layout, context.projectRoot);
38
+ else route.lazy = generateImport(node.layout, context.projectRoot);
58
39
  }
59
40
  const children = [];
60
41
  if (node.page) {
61
42
  const page = hasBoundary ? {
62
43
  index: "true",
63
- Component: generateLazyComponent(node.page)
44
+ Component: generateLazyComponent(node.page, context.projectRoot)
64
45
  } : {
65
46
  index: "true",
66
- lazy: generateImport(node.page)
47
+ lazy: generateImport(node.page, context.projectRoot)
67
48
  };
68
49
  children.push(genObjectFromRaw(page));
69
50
  }
@@ -71,15 +52,19 @@ function generateNode(node, root, context, inheritedBoundary) {
71
52
  if (children.length) route.children = genArrayFromRaw(children);
72
53
  return genObjectFromRaw(route);
73
54
  }
74
- function generate(node, root = false) {
55
+ function generate(node, root = false, projectRoot = process.cwd()) {
75
56
  const context = {
57
+ projectRoot,
76
58
  loadingImports: [],
77
59
  nextLoadingId: 0,
78
60
  hasBoundaries: false
79
61
  };
80
62
  const routes = generateNode(node, root, context, false);
81
63
  const imports = [...context.loadingImports];
82
- if (context.hasBoundaries) imports.unshift(`import { createElement, lazy, Suspense } from "react";`, `import { Outlet } from "react-router";`, `function createBoundary(Fallback, Component) {
64
+ const helpers = [];
65
+ if (context.hasBoundaries) {
66
+ imports.unshift(`import { createElement, lazy, Suspense } from "react";`, `import { Outlet } from "react-router";`);
67
+ helpers.push(`function createBoundary(Fallback, Component) {
83
68
  return function RouteBoundary() {
84
69
  return createElement(
85
70
  Suspense,
@@ -88,27 +73,62 @@ function generate(node, root = false) {
88
73
  );
89
74
  };
90
75
  }`);
76
+ }
91
77
  return {
92
78
  imports,
79
+ helpers,
93
80
  routes
94
81
  };
95
82
  }
83
+ // src/scanner.ts
84
+ function scan(directory) {
85
+ const node = {
86
+ name: path.basename(directory),
87
+ directory,
88
+ children: []
89
+ };
90
+ if (!fs.existsSync(directory)) return node;
91
+ const entries = fs.readdirSync(directory, { withFileTypes: true });
92
+ for (const entry of entries) {
93
+ const file = path.join(directory, entry.name);
94
+ if (entry.isDirectory()) {
95
+ node.children.push(scan(file));
96
+ continue;
97
+ }
98
+ const match = /^(page|layout|loading)\.(jsx|tsx)$/.exec(entry.name);
99
+ if (!match) continue;
100
+ const [, name, extension] = match;
101
+ const key = name;
102
+ if (!node[key] || extension === "tsx") node[key] = file;
103
+ }
104
+ return node;
105
+ }
96
106
  // src/index.ts
97
107
  var MODULE_ID = "virtual:routes";
98
108
  var RESOLVED_ID = `\0${MODULE_ID}`;
99
109
  function routes(directory = "src/pages") {
110
+ let projectRoot = "";
100
111
  let root = "";
101
- function execute() {
102
- const generated = generate(scan(root), true);
103
- return `
104
- ${generated.imports.join("\n")}
105
- export const routes = ${genArrayFromRaw([generated.routes])};
106
- `;
112
+ async function execute() {
113
+ const generated = generate(scan(root), true, projectRoot);
114
+ const sections = [
115
+ generated.imports.sort((left, right) => {
116
+ const leftSource = left.match(/from ["'](.+?)["']/)?.[1] ?? left;
117
+ const rightSource = right.match(/from ["'](.+?)["']/)?.[1] ?? right;
118
+ const leftIsRelative = leftSource.startsWith(".");
119
+ if (leftIsRelative !== rightSource.startsWith(".")) return leftIsRelative ? 1 : -1;
120
+ return leftSource.localeCompare(rightSource);
121
+ }).join("\n"),
122
+ generated.helpers.join("\n"),
123
+ `export const routes = ${genArrayFromRaw([generated.routes])};`
124
+ ].filter(Boolean);
125
+ return format(sections.join("\n\n"), { parser: "typescript" });
107
126
  }
108
127
  return {
109
128
  name: "routes",
110
129
  configResolved(config) {
111
- root = path.resolve(config.root, directory);
130
+ projectRoot = config.root;
131
+ root = path.resolve(projectRoot, directory);
112
132
  },
113
133
  resolveId: (id) => id === MODULE_ID ? RESOLVED_ID : void 0,
114
134
  load: (id) => id === RESOLVED_ID ? execute() : void 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@troyio/vite-file-router",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "File-system routing plugin for Vite",
5
5
  "type": "module",
6
6
  "private": false,
@@ -26,27 +26,26 @@
26
26
  "import": "./dist/index.js"
27
27
  }
28
28
  },
29
- "scripts": {
30
- "build": "vite build && tsc -p tsconfig.build.json",
31
- "lint": "eslint src vite.config.ts",
32
- "prepublish": "pnpm build"
33
- },
34
29
  "dependencies": {
35
30
  "knitwork": "^1.3.0",
36
- "react-router": "^8.5.1",
37
- "vite-file-router": "link:"
31
+ "prettier": "^3.9.10"
38
32
  },
39
33
  "peerDependencies": {
40
34
  "react": ">=19.2.7",
35
+ "react-router": "^8.5.1",
41
36
  "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
42
37
  },
43
38
  "devDependencies": {
44
- "@eslint/js": "^10.0.1",
39
+ "@biomejs/biome": "^2.6.4",
45
40
  "@types/node": "^24.19.2",
46
- "eslint": "^10.14.0",
47
- "globals": "^17.14.0",
48
41
  "typescript": "~6.0.3",
49
- "typescript-eslint": "^8.72.1",
50
42
  "vite": "^8.3.5"
43
+ },
44
+ "scripts": {
45
+ "build": "vite build && tsc -p tsconfig.build.json",
46
+ "test": "pnpm build && node --test",
47
+ "lint": "biome check",
48
+ "format": "biome format --write",
49
+ "prepublish": "pnpm audit & pnpm build"
51
50
  }
52
- }
51
+ }