Skip to content

Commit 9b7adcb

Browse files
committed
refactor: simplify compiler logic
1 parent 0ddbdd0 commit 9b7adcb

File tree

2 files changed

+82
-100
lines changed

2 files changed

+82
-100
lines changed

src/compiler.ts

Lines changed: 81 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ export function compileRouter<
3333
) => O["matchAll"] extends true
3434
? MatchedRoute<T>[]
3535
: MatchedRoute<T> | undefined {
36-
const deps: any[] = [];
37-
const compiled = compileRouteMatch(router, deps, opts);
36+
const ctx: CompilerContext = { opts: opts || {}, router, deps: [] };
37+
const compiled = compileRouteMatch(ctx);
3838
return new Function(
39-
...deps.map((_, i) => "d" + (i + 1)),
39+
...ctx.deps!.map((_, i) => "d" + (i + 1)),
4040
`return(m,p)=>{${compiled}}`,
41-
)(...deps);
41+
)(...ctx.deps!);
4242
}
4343

4444
/**
@@ -59,191 +59,173 @@ export function compileRouterToString(
5959
functionName?: string,
6060
opts?: RouterCompilerOptions,
6161
): string {
62-
const compiled = `(m,p)=>{${compileRouteMatch(router, undefined, opts)}}`;
62+
const ctx: CompilerContext = { opts: opts || {}, router, deps: undefined };
63+
const compiled = `(m,p)=>{${compileRouteMatch(ctx)}}`;
6364
return functionName ? `const ${functionName}=${compiled};` : compiled;
6465
}
6566

6667
// ------- internal functions -------
6768

68-
// p: path
69-
// s: path parts
70-
// l: path parts length
71-
// m: method
72-
// r: matchAll matches
69+
interface CompilerContext {
70+
opts: RouterCompilerOptions;
71+
router: RouterContext<any>;
72+
deps: string[] | undefined;
73+
}
7374

74-
/**
75-
* Compile a router to pattern matching statements
76-
* @param router
77-
* @param deps - Dependencies of the function scope
78-
*/
79-
function compileRouteMatch(
80-
router: RouterContext<any>,
81-
deps?: any[],
82-
opts?: RouterCompilerOptions,
83-
): string {
84-
let str = "";
75+
function compileRouteMatch(ctx: CompilerContext): string {
76+
let code = "";
8577
const staticNodes = new Set<Node>();
8678

87-
for (const key in router.static) {
88-
const node = router.static[key];
79+
for (const key in ctx.router.static) {
80+
const node = ctx.router.static[key];
8981
if (node?.methods) {
9082
staticNodes.add(node);
91-
str += `if(p===${JSON.stringify(key.replace(/\/$/, "") || "/")}){${compileMethodMatch(node.methods, [], deps, -1, opts)}}`;
83+
code += `if(p===${JSON.stringify(key.replace(/\/$/, "") || "/")}){${compileMethodMatch(ctx, node.methods, [], -1)}}`;
9284
}
9385
}
9486

95-
const match = compileNode(router.root, [], 0, deps, false, staticNodes, opts);
87+
const match = compileNode(ctx, ctx.router.root, [], 0, staticNodes);
9688
if (match) {
97-
str += "let s=p.split('/'),l=s.length-1;" + match;
89+
code += `let s=p.split("/"),l=s.length-1;${match}`;
9890
}
9991

100-
if (!str) {
101-
return opts?.matchAll ? "return [];" : "";
92+
if (!code) {
93+
return ctx.opts?.matchAll ? `return [];` : "";
10294
}
10395

104-
return `${opts?.matchAll ? `let r=[];` : ""}if(p.charCodeAt(p.length-1)===47)p=p.slice(0,-1)||'/';${str}${opts?.matchAll ? "return r;" : ""}`;
96+
return `${ctx.opts?.matchAll ? `let r=[];` : ""}if(p.charCodeAt(p.length-1)===47)p=p.slice(0,-1)||"/";${code}${ctx.opts?.matchAll ? "return r;" : ""}`;
10597
}
10698

10799
function compileMethodMatch(
100+
ctx: CompilerContext,
108101
methods: Record<string, MethodData<any>[] | undefined>,
109102
params: string[],
110-
deps: any[] | undefined,
111103
currentIdx: number, // Set to -1 for non-param node
112-
opts?: RouterCompilerOptions,
113104
): string {
114-
let str = "";
105+
let code = "";
115106
for (const key in methods) {
116107
const data = methods[key];
117108
if (data && data?.length > 0) {
118109
// Don't check for matchAll method handler
119-
if (key !== "") str += `if(m==='${key}')`;
120-
121-
const dataValue = data[0].data;
122-
let serializedData: string;
123-
if (deps) {
124-
serializedData = `d${deps.push(dataValue)}`;
125-
} else if (opts?.serialize) {
126-
serializedData = opts.serialize(dataValue);
127-
} else if (typeof dataValue?.toJSON === "function") {
128-
serializedData = dataValue.toJSON();
129-
} else {
130-
serializedData = JSON.stringify(dataValue);
131-
}
132-
let res = `{data:${serializedData}`;
133-
134-
// Add param properties
135-
const { paramsMap } = data[0];
136-
if (paramsMap && paramsMap.length > 0) {
137-
// Check for optional end parameters
138-
const required =
139-
!paramsMap[paramsMap.length - 1][2] && currentIdx !== -1;
140-
if (required) str += `if(l>=${currentIdx})`;
141-
142-
// Create the param object based on previous parameters
143-
res += ",params:{";
144-
for (let i = 0; i < paramsMap.length; i++) {
145-
const map = paramsMap[i];
146-
147-
res +=
148-
typeof map[1] === "string"
149-
? `${JSON.stringify(map[1])}:${params[i]},`
150-
: `...(${map[1].toString()}.exec(${params[i]}))?.groups,`;
151-
}
152-
res += "}";
153-
}
110+
if (key !== "") code += `if(m==="${key}")`;
111+
code += compileFinalMatch(ctx, data[0], currentIdx, params);
112+
}
113+
}
114+
return code;
115+
}
154116

155-
str += opts?.matchAll ? `r.unshift(${res}});` : `return ${res}};`;
117+
function compileFinalMatch(
118+
ctx: CompilerContext,
119+
data: MethodData<any>,
120+
currentIdx: number,
121+
params: string[],
122+
): string {
123+
let code = "";
124+
let ret = `{data:${serializeData(ctx, data.data)}`;
125+
126+
// Add param properties
127+
const { paramsMap } = data;
128+
if (paramsMap && paramsMap.length > 0) {
129+
// Check for optional end parameters
130+
const required = !paramsMap[paramsMap.length - 1][2] && currentIdx !== -1;
131+
if (required) code += `if(l>=${currentIdx})`;
132+
// Create the param object based on previous parameters
133+
ret += ",params:{";
134+
for (let i = 0; i < paramsMap.length; i++) {
135+
const map = paramsMap[i];
136+
ret +=
137+
typeof map[1] === "string"
138+
? `${JSON.stringify(map[1])}:${params[i]},`
139+
: `...(${map[1].toString()}.exec(${params[i]}))?.groups,`;
156140
}
141+
ret += "}";
157142
}
158-
return str;
143+
return (
144+
code + (ctx.opts?.matchAll ? `r.unshift(${ret}});` : `return ${ret}};`)
145+
);
159146
}
160147

161-
/**
162-
* Compile a node to matcher logic
163-
*/
164148
function compileNode(
149+
ctx: CompilerContext,
165150
node: Node<any>,
166151
params: string[],
167152
startIdx: number,
168-
deps: any[] | undefined,
169-
isParamNode: boolean,
170153
staticNodes: Set<Node>,
171-
opts?: RouterCompilerOptions,
172154
): string {
173-
let str = "";
155+
let code = "";
174156

175157
if (node.methods && !staticNodes.has(node)) {
176158
const match = compileMethodMatch(
159+
ctx,
177160
node.methods,
178161
params,
179-
deps,
180-
isParamNode ? startIdx : -1,
181-
opts,
162+
node.key === "*" ? startIdx : -1,
182163
);
183164
if (match) {
184-
str += `if(l===${startIdx}${isParamNode ? `||l===${startIdx - 1}` : ""}){${match}}`;
165+
const hasLastOptionalParam = node.key === "*";
166+
code += `if(l===${startIdx}${hasLastOptionalParam ? `||l===${startIdx - 1}` : ""}){${match}}`;
185167
}
186168
}
187169

188170
if (node.static) {
189171
for (const key in node.static) {
190172
const match = compileNode(
173+
ctx,
191174
node.static[key],
192175
params,
193176
startIdx + 1,
194-
deps,
195-
false,
196177
staticNodes,
197-
opts,
198178
);
199179
if (match) {
200-
str += `if(s[${startIdx + 1}]===${JSON.stringify(key)}){${match}}`;
180+
code += `if(s[${startIdx + 1}]===${JSON.stringify(key)}){${match}}`;
201181
}
202182
}
203183
}
204184

205185
if (node.param) {
206186
const match = compileNode(
187+
ctx,
207188
node.param,
208189
[...params, `s[${startIdx + 1}]`],
209190
startIdx + 1,
210-
deps,
211-
true,
212191
staticNodes,
213-
opts,
214192
);
215193
if (match) {
216-
str += match;
194+
code += match;
217195
}
218196
}
219197

220198
if (node.wildcard) {
221199
const { wildcard } = node;
222-
if (hasChild(wildcard)) {
200+
if (wildcard.static || wildcard.param || wildcard.wildcard) {
223201
throw new Error("Compiler mode does not support patterns after wildcard");
224202
}
225203

226204
if (wildcard.methods) {
227205
const match = compileMethodMatch(
206+
ctx,
228207
wildcard.methods,
229208
[...params, `s.slice(${startIdx + 1}).join('/')`],
230-
deps,
231209
startIdx,
232-
opts,
233210
);
234211
if (match) {
235-
str += match;
212+
code += match;
236213
}
237214
}
238215
}
239216

240-
return str;
217+
return code;
241218
}
242219

243-
/**
244-
* Whether the current node has children nodes
245-
* @param n
246-
*/
247-
function hasChild(n: Node<any>): boolean {
248-
return !!(n.static || n.param || n.wildcard);
220+
function serializeData(ctx: CompilerContext, value: any): string {
221+
if (ctx.deps) {
222+
return `d${ctx.deps.push(value)}`;
223+
}
224+
if (ctx.opts?.serialize) {
225+
return ctx.opts.serialize(value);
226+
}
227+
if (typeof value?.toJSON === "function") {
228+
return value.toJSON();
229+
}
230+
return JSON.stringify(value);
249231
}

test/find-all.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const _findAllRoutes = (
1515
const compiled = compileRouter(ctx, { matchAll: true });
1616
const compiledRes = compiled(method, path).map((m) => m.data.path);
1717

18-
expect(res).toEqual(compiledRes);
18+
expect(compiledRes).toEqual(res);
1919

2020
return res;
2121
};

0 commit comments

Comments
 (0)