UNPKG

rou3

Version:

Lightweight and fast router for JavaScript.

133 lines (132 loc) 4.93 kB
//#region src/compiler.ts /** * Compiles the router instance into a faster route-matching function. * * **IMPORTANT:** `compileRouter` requires eval support with `new Function()` in the runtime for JIT compilation. * * @example * import { createRouter, addRoute } from "rou3"; * import { compileRouter } from "rou3/compiler"; * const router = createRouter(); * // [add some routes] * const findRoute = compileRouter(router); * const matchAll = compileRouter(router, { matchAll: true }); * findRoute("GET", "/path/foo/bar"); * * @param router - The router context to compile. */ function compileRouter(router, opts) { const deps = []; const compiled = compileRouteMatch(router, deps, opts?.matchAll); return new Function(...deps.map((_, i) => "d" + (i + 1)), `return(m,p)=>{${compiled}}`)(...deps); } /** * Compile the router instance into a compact runnable code. * * **IMPORTANT:** Route data must be serializable to JSON (i.e., no functions or classes) or implement the `toJSON()` method to render custom code. * * @example * import { createRouter, addRoute } from "rou3"; * import { compileRouterToString } from "rou3/compiler"; * const router = createRouter(); * // [add some routes with serializable data] * const compilerCode = compileRouterToString(router, "findRoute"); * // "const findRoute=(m, p) => {}" */ function compileRouterToString(router, functionName, opts) { const compiled = `(m,p)=>{${compileRouteMatch(router, void 0, opts?.matchAll)}}`; return functionName ? `const ${functionName}=${compiled};` : compiled; } /** * Compile a router to pattern matching statements * @param router * @param deps - Dependencies of the function scope */ function compileRouteMatch(router, deps, matchAll) { let str = `${matchAll ? `let r=[];` : ""}if(p[p.length-1]==='/')p=p.slice(0,-1)||'/';`; const staticNodes = new Set(); for (const key in router.static) { const node = router.static[key]; if (node?.methods) { staticNodes.add(node); str += `if(p===${JSON.stringify(key.replace(/\/$/, "") || "/")}){${compileMethodMatch(node.methods, [], deps, -1, matchAll)[1]}}`; } } const [existsTail, tail] = compileNode(router.root, [], 0, deps, false, staticNodes, matchAll); return str + (existsTail ? "let s=p.split('/'),l=s.length-1;" + tail + (matchAll ? ";return r" : "") : ""); } function compileMethodMatch(methods, params, deps, currentIdx, matchAll) { let str = ""; let exists = false; for (const key in methods) { const data = methods[key]; if (data && data?.length > 0) { exists = true; if (key !== "") str += `if(m==='${key}')`; const dataValue = data[0].data; let res = deps ? `{data:d${deps.push(dataValue)}` : `{data:${typeof dataValue?.toJSON === "function" ? dataValue.toJSON() : JSON.stringify(dataValue)}`; const { paramsMap } = data[0]; if (paramsMap && paramsMap.length > 0) { const required = !paramsMap[paramsMap.length - 1][2] && currentIdx !== -1; if (required) str += `if(l>=${currentIdx})`; res += ",params:{"; for (let i = 0; i < paramsMap.length; i++) { const map = paramsMap[i]; res += typeof map[1] === "string" ? `${JSON.stringify(map[1])}:${params[i]},` : `...(${map[1].toString()}.exec(${params[i]}))?.groups,`; } res += "}"; } str += matchAll ? `r.unshift(${res}});` : `return ${res}};`; } } return [exists, str]; } /** * Compile a node to matcher logic */ function compileNode(node, params, startIdx, deps, isParamNode, staticNodes, matchAll) { let str = ""; let exists = false; if (node.methods && !staticNodes.has(node)) { const [existsChild, match] = compileMethodMatch(node.methods, params, deps, isParamNode ? startIdx : -1, matchAll); if (existsChild) { exists = true; str += `if(l===${startIdx}${isParamNode ? `||l===${startIdx - 1}` : ""}){${match}}`; } } if (node.static) for (const key in node.static) { const [existsChild, match] = compileNode(node.static[key], params, startIdx + 1, deps, false, staticNodes, matchAll); if (existsChild) { exists = true; str += `if(s[${startIdx + 1}]===${JSON.stringify(key)}){${match}}`; } } if (node.param) { const [existsChild, match] = compileNode(node.param, [...params, `s[${startIdx + 1}]`], startIdx + 1, deps, true, staticNodes, matchAll); if (existsChild) { exists = true; str += match; } } if (node.wildcard) { const { wildcard } = node; if (hasChild(wildcard)) throw new Error("Compiler mode does not support patterns after wildcard"); if (wildcard.methods) { const [existsChild, match] = compileMethodMatch(wildcard.methods, [...params, `s.slice(${startIdx + 1}).join('/')`], deps, startIdx, matchAll); if (existsChild) { exists = true; str += match; } } } return [exists, str]; } /** * Whether the current node has children nodes * @param n */ function hasChild(n) { return !!(n.static || n.param || n.wildcard); } //#endregion export { compileRouter, compileRouterToString };