ts-file-router 5.0.0 → 6.0.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 +1 -2
- package/dist/generator.d.ts +1 -1
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +15 -18
- package/dist/helpers/fileHelper.d.ts +6 -0
- package/dist/helpers/fileHelper.d.ts.map +1 -0
- package/dist/helpers/fileHelper.js +10 -0
- package/dist/helpers/index.d.ts +3 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +2 -0
- package/dist/helpers/serializeHelper.d.ts +7 -0
- package/dist/helpers/serializeHelper.d.ts.map +1 -0
- package/dist/helpers/serializeHelper.js +81 -0
- package/package.json +1 -1
- package/dist/serialize.d.ts +0 -3
- package/dist/serialize.d.ts.map +0 -1
- package/dist/serialize.js +0 -51
package/README.md
CHANGED
|
@@ -39,7 +39,6 @@ import { generateRoutes } from 'ts-file-router';
|
|
|
39
39
|
generateRoutes({
|
|
40
40
|
baseFolder: 'src/screens',
|
|
41
41
|
outputFile: 'src/screens/routes.ts',
|
|
42
|
-
routeFileName: 'page.tsx',
|
|
43
42
|
});
|
|
44
43
|
```
|
|
45
44
|
|
|
@@ -100,7 +99,7 @@ or setup generateRoutesPlugin in vite.config.ts
|
|
|
100
99
|
export const routes = {
|
|
101
100
|
page: {
|
|
102
101
|
path: '/',
|
|
103
|
-
import:
|
|
102
|
+
import: import('./page'),
|
|
104
103
|
},
|
|
105
104
|
} as const;
|
|
106
105
|
```
|
package/dist/generator.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { TGenerateRoutesConfig } from './types.js';
|
|
2
|
-
export declare const generateRoutes: ({ baseFolder, outputFile,
|
|
2
|
+
export declare const generateRoutes: ({ baseFolder, outputFile, options, }: TGenerateRoutesConfig) => void;
|
|
3
3
|
//# sourceMappingURL=generator.d.ts.map
|
package/dist/generator.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,qBAAqB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAKrE,eAAO,MAAM,cAAc,GAAI,sCAI5B,qBAAqB,SA6IvB,CAAC"}
|
package/dist/generator.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FileHelper, SerializeHelper } from './helpers/index.js';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
const
|
|
5
|
-
const getIgnoredFiles = (file, output) => file.includes('index') ||
|
|
6
|
-
file.startsWith('_') ||
|
|
7
|
-
getIgnoredOutputFile(file, output);
|
|
8
|
-
const cleanPaths = (path) => path.replaceAll(/\\/gi, '/').replaceAll(/.(tsx|ts|jsx|js)/gi, '');
|
|
9
|
-
export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.tsx', options = { exitCodeOnResolution: true }, }) => {
|
|
4
|
+
export const generateRoutes = ({ baseFolder, outputFile, options = { exitCodeOnResolution: true }, }) => {
|
|
10
5
|
// Get the pages dir to resolve routes
|
|
11
6
|
const basePath = path.resolve(process.cwd(), baseFolder);
|
|
12
7
|
// Output file for routes
|
|
@@ -14,12 +9,12 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
|
|
|
14
9
|
const mapRoutes = async (dir) => {
|
|
15
10
|
const routes = {};
|
|
16
11
|
const directory = await fs.readdir(dir);
|
|
17
|
-
if (!directory.includes(routeFileName)) {
|
|
18
|
-
throw new Error(`Invalid pages structure: The folder "${dir}" must contain a ${routeFileName} file.`);
|
|
19
|
-
}
|
|
20
12
|
for (const file of directory) {
|
|
13
|
+
if (!directory.length || directory.length === 0) {
|
|
14
|
+
throw new Error(`Invalid pages structure: The folder "${dir}" must contain at least one valid file.`);
|
|
15
|
+
}
|
|
21
16
|
// ignore index files, underscore marked, or route file generated
|
|
22
|
-
if (getIgnoredFiles(file, outputFile)) {
|
|
17
|
+
if (FileHelper.getIgnoredFiles(file, outputFile)) {
|
|
23
18
|
continue;
|
|
24
19
|
}
|
|
25
20
|
const fullPath = path.join(dir, file);
|
|
@@ -27,7 +22,6 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
|
|
|
27
22
|
const dirInfo = await fs.stat(fullPath);
|
|
28
23
|
// Path to browser sync if necessary
|
|
29
24
|
const relativePath = '/' + path.relative(basePath, dir);
|
|
30
|
-
// Normalize import path to esm pattern
|
|
31
25
|
const importPath = './' + path.relative(basePath, fullPath);
|
|
32
26
|
// Remove extension from file to naming the route
|
|
33
27
|
const key = path.basename(file, path.extname(file));
|
|
@@ -39,10 +33,12 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
|
|
|
39
33
|
continue;
|
|
40
34
|
}
|
|
41
35
|
// Mount the route object with path like "/folder" and import
|
|
42
|
-
// import will be like "(./baseFolder/file or ./baseFolder/folders).extension"
|
|
36
|
+
// import will be like "import((./baseFolder/file or ./baseFolder/folders).extension)"
|
|
43
37
|
routes[key] = {
|
|
44
|
-
path
|
|
45
|
-
|
|
38
|
+
// Normalize path
|
|
39
|
+
path: FileHelper.cleanPaths(relativePath),
|
|
40
|
+
// Normalize import path to esm pattern
|
|
41
|
+
import: FileHelper.cleanPaths(importPath),
|
|
46
42
|
};
|
|
47
43
|
}
|
|
48
44
|
return routes;
|
|
@@ -52,7 +48,7 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
|
|
|
52
48
|
// Create routes
|
|
53
49
|
const routes = await mapRoutes(basePath);
|
|
54
50
|
// Create ts file
|
|
55
|
-
await
|
|
51
|
+
await SerializeHelper.serializeOutputFile(routes, output);
|
|
56
52
|
// Promise writeFile was successfully resolved
|
|
57
53
|
console.log('🚀 Routes generated successfully!\n');
|
|
58
54
|
if (options.exitCodeOnResolution) {
|
|
@@ -85,7 +81,7 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
|
|
|
85
81
|
}, debounce);
|
|
86
82
|
};
|
|
87
83
|
watcher.on('all', (ev, file) => {
|
|
88
|
-
const ignoredOuput = getIgnoredOutputFile(file, outputFile);
|
|
84
|
+
const ignoredOuput = FileHelper.getIgnoredOutputFile(file, outputFile);
|
|
89
85
|
// If output file as deleted regenerate it
|
|
90
86
|
if (ignoredOuput && ev === 'unlink') {
|
|
91
87
|
console.log(`🚨 ${outputFile} was deleted, regenerating...`);
|
|
@@ -94,11 +90,12 @@ export const generateRoutes = ({ baseFolder, outputFile, routeFileName = 'page.t
|
|
|
94
90
|
}
|
|
95
91
|
const watchOnEvents = [
|
|
96
92
|
'add',
|
|
93
|
+
'addDir',
|
|
97
94
|
'change',
|
|
98
95
|
'unlink',
|
|
99
96
|
'unlinkDir',
|
|
100
97
|
];
|
|
101
|
-
const ignoredGeneralFiles = getIgnoredFiles(file, outputFile);
|
|
98
|
+
const ignoredGeneralFiles = FileHelper.getIgnoredFiles(file, outputFile);
|
|
102
99
|
// If added, or change (renamed) or deleted, update routes
|
|
103
100
|
if (watchOnEvents.includes(ev) && !ignoredGeneralFiles) {
|
|
104
101
|
console.log(`🔎 Watching files from path: "${file}" for changes...`);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const FileHelper: {
|
|
2
|
+
readonly cleanPaths: (path: string) => string;
|
|
3
|
+
readonly getIgnoredFiles: (file: string, output: string) => boolean;
|
|
4
|
+
readonly getIgnoredOutputFile: (file: string, output: string) => boolean;
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=fileHelper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileHelper.d.ts","sourceRoot":"","sources":["../../src/helpers/fileHelper.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,UAAU;gCAHG,MAAM;qCALD,MAAM,UAAU,MAAM;0CAHjB,MAAM,UAAU,MAAM;CAehD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const getIgnoredOutputFile = (file, output) => file.includes(output);
|
|
2
|
+
const getIgnoredFiles = (file, output) => file.includes('index') ||
|
|
3
|
+
file.startsWith('_') ||
|
|
4
|
+
getIgnoredOutputFile(file, output);
|
|
5
|
+
const cleanPaths = (path) => path.replaceAll(/\\/gi, '/').replaceAll(/.(tsx|ts|jsx|js)/gi, '');
|
|
6
|
+
export const FileHelper = {
|
|
7
|
+
cleanPaths,
|
|
8
|
+
getIgnoredFiles,
|
|
9
|
+
getIgnoredOutputFile,
|
|
10
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TRouteLeaf, TRoutesTree } from '../types.js';
|
|
2
|
+
export declare const SerializeHelper: {
|
|
3
|
+
readonly isRouteLeaf: (value: unknown) => value is TRouteLeaf;
|
|
4
|
+
readonly serializeOutputFile: (routes: TRoutesTree, outputPath: string) => Promise<void>;
|
|
5
|
+
readonly formatAndWriteOutputFile: (filePath: string, code: string) => Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=serializeHelper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializeHelper.d.ts","sourceRoot":"","sources":["../../src/helpers/serializeHelper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAqJtD,eAAO,MAAM,eAAe;kCAnHA,OAAO,KAAG,KAAK,IAAI,UAAU;2CAoGd,WAAW,cAAc,MAAM;kDAlBxB,MAAM,QAAQ,MAAM;CAqC5D,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Biome, Distribution } from '@biomejs/js-api';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
const biomeInstance = {
|
|
6
|
+
biome: null,
|
|
7
|
+
project: null,
|
|
8
|
+
};
|
|
9
|
+
const getBiomeSingleton = async () => {
|
|
10
|
+
if (!biomeInstance.biome) {
|
|
11
|
+
biomeInstance.biome = await Biome.create({
|
|
12
|
+
distribution: Distribution.NODE,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (!biomeInstance.project) {
|
|
16
|
+
biomeInstance.project = biomeInstance.biome.openProject();
|
|
17
|
+
biomeInstance.biome.applyConfiguration(biomeInstance.project.projectKey, {
|
|
18
|
+
formatter: { enabled: true, indentStyle: 'space', lineWidth: 100 },
|
|
19
|
+
javascript: { formatter: { quoteStyle: 'single' } },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
projectKey: biomeInstance.project.projectKey,
|
|
24
|
+
formatContent: biomeInstance.biome.formatContent,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
// Type guard
|
|
28
|
+
const isRouteLeaf = (value) => {
|
|
29
|
+
return (typeof value === 'object' &&
|
|
30
|
+
value !== null &&
|
|
31
|
+
'path' in value &&
|
|
32
|
+
'import' in value);
|
|
33
|
+
};
|
|
34
|
+
const createRouteObject = (obj) => {
|
|
35
|
+
const properties = Object.entries(obj).map(([key, value]) => {
|
|
36
|
+
if (isRouteLeaf(value)) {
|
|
37
|
+
return ts.factory.createPropertyAssignment(ts.factory.createIdentifier(key), ts.factory.createObjectLiteralExpression([
|
|
38
|
+
ts.factory.createPropertyAssignment('path', ts.factory.createStringLiteral(value.path)),
|
|
39
|
+
ts.factory.createPropertyAssignment('import', ts.factory.createCallExpression(ts.factory.createIdentifier('import'), undefined, [ts.factory.createStringLiteral(value.import)])),
|
|
40
|
+
], true));
|
|
41
|
+
}
|
|
42
|
+
// Recursive object
|
|
43
|
+
return ts.factory.createPropertyAssignment(ts.factory.createIdentifier(key), createRouteObject(value));
|
|
44
|
+
});
|
|
45
|
+
return ts.factory.createObjectLiteralExpression(properties, true);
|
|
46
|
+
};
|
|
47
|
+
const createRoutesFile = (obj) => {
|
|
48
|
+
const routesObject = createRouteObject(obj);
|
|
49
|
+
// Create AST from routes variable
|
|
50
|
+
const exportStatement = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
51
|
+
ts.factory.createVariableDeclaration('routes', undefined, undefined, ts.factory.createAsExpression(routesObject, ts.factory.createTypeReferenceNode('const'))),
|
|
52
|
+
], ts.NodeFlags.Const));
|
|
53
|
+
// Add the comment
|
|
54
|
+
ts.addSyntheticLeadingComment(exportStatement, ts.SyntaxKind.SingleLineCommentTrivia, ' GENERATED WITH TS-FILE-ROUTER DO NOT EDIT', true);
|
|
55
|
+
// Create the source file
|
|
56
|
+
return ts.factory.createSourceFile([exportStatement], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
57
|
+
};
|
|
58
|
+
const formatAndWriteOutputFile = async (filePath, code) => {
|
|
59
|
+
const biomeInstance = await getBiomeSingleton();
|
|
60
|
+
const formatted = biomeInstance.formatContent(biomeInstance.projectKey, code, {
|
|
61
|
+
filePath: path.basename(filePath),
|
|
62
|
+
});
|
|
63
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
64
|
+
fs.writeFileSync(filePath, formatted.content, 'utf8');
|
|
65
|
+
};
|
|
66
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
67
|
+
const serializeOutputFile = async (routes, outputPath) => {
|
|
68
|
+
const code = createRoutesFile(routes);
|
|
69
|
+
try {
|
|
70
|
+
await formatAndWriteOutputFile(path.resolve(outputPath), printer.printFile(code));
|
|
71
|
+
console.log('✨ File was parsed succesfully');
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.error('❌ Error parsing file:\n', err);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
export const SerializeHelper = {
|
|
78
|
+
isRouteLeaf,
|
|
79
|
+
serializeOutputFile,
|
|
80
|
+
formatAndWriteOutputFile,
|
|
81
|
+
};
|
package/package.json
CHANGED
package/dist/serialize.d.ts
DELETED
package/dist/serialize.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,WAAW,EAAE,MAAM,YAAY,CAAC;AAmD1D,eAAO,MAAM,SAAS,GAAU,QAAQ,WAAW,EAAE,YAAY,MAAM,kBActE,CAAC"}
|
package/dist/serialize.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { Biome, Distribution } from '@biomejs/js-api';
|
|
2
|
-
import * as fs from 'node:fs';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
const isRouteLeaf = (value) => {
|
|
5
|
-
return (typeof value === 'object' &&
|
|
6
|
-
value !== null &&
|
|
7
|
-
'path' in value &&
|
|
8
|
-
'import' in value);
|
|
9
|
-
};
|
|
10
|
-
const stringifyRoutes = (obj) => {
|
|
11
|
-
const entries = Object.entries(obj).map(([key, value]) => {
|
|
12
|
-
if (isRouteLeaf(value)) {
|
|
13
|
-
return `'${key}': {
|
|
14
|
-
path: '${value.path}',
|
|
15
|
-
import: import('${value.import}')
|
|
16
|
-
}`;
|
|
17
|
-
}
|
|
18
|
-
return `'${key}': ${stringifyRoutes(value)}`;
|
|
19
|
-
});
|
|
20
|
-
return `{ ${entries.join(',\n')} }`;
|
|
21
|
-
};
|
|
22
|
-
let biome = null;
|
|
23
|
-
const format = async (filePath, content) => {
|
|
24
|
-
if (biome === null) {
|
|
25
|
-
biome = await Biome.create({ distribution: Distribution.NODE });
|
|
26
|
-
}
|
|
27
|
-
const project = biome.openProject();
|
|
28
|
-
biome.applyConfiguration(project.projectKey, {
|
|
29
|
-
formatter: { enabled: true, indentStyle: 'space', lineWidth: 100 },
|
|
30
|
-
javascript: { formatter: { quoteStyle: 'single' } },
|
|
31
|
-
});
|
|
32
|
-
const formatted = biome.formatContent(project.projectKey, content, {
|
|
33
|
-
filePath: path.basename(filePath),
|
|
34
|
-
});
|
|
35
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
36
|
-
fs.writeFileSync(filePath, formatted.content, 'utf8');
|
|
37
|
-
};
|
|
38
|
-
export const serialize = async (routes, outputPath) => {
|
|
39
|
-
const rawCode = `
|
|
40
|
-
// GENERATED WITH TS-FILE-ROUTER DO NOT EDIT
|
|
41
|
-
|
|
42
|
-
export const routes = ${stringifyRoutes(routes)} as const;
|
|
43
|
-
\n`;
|
|
44
|
-
try {
|
|
45
|
-
await format(path.resolve(outputPath), rawCode);
|
|
46
|
-
console.log('✨ File was parsed succesfully');
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
console.error('❌ Error parsing file:\n', err);
|
|
50
|
-
}
|
|
51
|
-
};
|