@tanstack/start-plugin-core 1.132.0-alpha.8 → 1.132.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/dist/esm/constants.d.ts +2 -1
- package/dist/esm/constants.js +3 -2
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/create-server-fn-plugin/compiler.d.ts +64 -0
- package/dist/esm/create-server-fn-plugin/compiler.js +364 -0
- package/dist/esm/create-server-fn-plugin/compiler.js.map +1 -0
- package/dist/esm/create-server-fn-plugin/handleCreateMiddleware.d.ts +5 -0
- package/dist/esm/{start-compiler-plugin/middleware.js → create-server-fn-plugin/handleCreateMiddleware.js} +11 -11
- package/dist/esm/create-server-fn-plugin/handleCreateMiddleware.js.map +1 -0
- package/dist/esm/create-server-fn-plugin/handleCreateServerFn.d.ts +6 -0
- package/dist/esm/{start-compiler-plugin/serverFn.js → create-server-fn-plugin/handleCreateServerFn.js} +15 -17
- package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js.map +1 -0
- package/dist/esm/create-server-fn-plugin/plugin.d.ts +3 -0
- package/dist/esm/create-server-fn-plugin/plugin.js +128 -0
- package/dist/esm/create-server-fn-plugin/plugin.js.map +1 -0
- package/dist/esm/dev-server-plugin/plugin.d.ts +4 -2
- package/dist/esm/dev-server-plugin/plugin.js +6 -2
- package/dist/esm/dev-server-plugin/plugin.js.map +1 -1
- package/dist/esm/plugin.d.ts +12 -6
- package/dist/esm/plugin.js +56 -68
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/resolve-entries.d.ts +0 -1
- package/dist/esm/resolve-entries.js +1 -1
- package/dist/esm/resolve-entries.js.map +1 -1
- package/dist/esm/schema.d.ts +375 -308
- package/dist/esm/schema.js +23 -11
- package/dist/esm/schema.js.map +1 -1
- package/dist/esm/start-compiler-plugin/compilers.js +17 -55
- package/dist/esm/start-compiler-plugin/compilers.js.map +1 -1
- package/dist/esm/start-compiler-plugin/constants.d.ts +1 -1
- package/dist/esm/start-compiler-plugin/constants.js +1 -6
- package/dist/esm/start-compiler-plugin/constants.js.map +1 -1
- package/dist/esm/start-compiler-plugin/plugin.d.ts +1 -8
- package/dist/esm/start-compiler-plugin/plugin.js +6 -13
- package/dist/esm/start-compiler-plugin/plugin.js.map +1 -1
- package/dist/esm/start-router-plugin/constants.d.ts +1 -0
- package/dist/esm/start-router-plugin/constants.js +5 -0
- package/dist/esm/start-router-plugin/constants.js.map +1 -0
- package/dist/esm/start-router-plugin/generator-plugins/routes-manifest-plugin.js +3 -9
- package/dist/esm/start-router-plugin/generator-plugins/routes-manifest-plugin.js.map +1 -1
- package/dist/esm/start-router-plugin/plugin.d.ts +3 -2
- package/dist/esm/start-router-plugin/plugin.js +191 -31
- package/dist/esm/start-router-plugin/plugin.js.map +1 -1
- package/dist/esm/start-router-plugin/pruneServerOnlySubtrees.d.ts +8 -0
- package/dist/esm/start-router-plugin/pruneServerOnlySubtrees.js +34 -0
- package/dist/esm/start-router-plugin/pruneServerOnlySubtrees.js.map +1 -0
- package/package.json +8 -8
- package/src/constants.ts +3 -2
- package/src/create-server-fn-plugin/compiler.ts +498 -0
- package/src/{start-compiler-plugin/middleware.ts → create-server-fn-plugin/handleCreateMiddleware.ts} +15 -12
- package/src/{start-compiler-plugin/serverFn.ts → create-server-fn-plugin/handleCreateServerFn.ts} +32 -39
- package/src/create-server-fn-plugin/plugin.ts +153 -0
- package/src/dev-server-plugin/plugin.ts +6 -3
- package/src/plugin.ts +78 -87
- package/src/resolve-entries.ts +1 -2
- package/src/schema.ts +31 -14
- package/src/start-compiler-plugin/compilers.ts +18 -57
- package/src/start-compiler-plugin/constants.ts +0 -5
- package/src/start-compiler-plugin/plugin.ts +7 -22
- package/src/start-router-plugin/constants.ts +1 -0
- package/src/start-router-plugin/generator-plugins/routes-manifest-plugin.ts +3 -9
- package/src/start-router-plugin/plugin.ts +233 -45
- package/src/start-router-plugin/pruneServerOnlySubtrees.ts +51 -0
- package/dist/esm/debug.js +0 -5
- package/dist/esm/debug.js.map +0 -1
- package/dist/esm/start-compiler-plugin/middleware.d.ts +0 -4
- package/dist/esm/start-compiler-plugin/middleware.js.map +0 -1
- package/dist/esm/start-compiler-plugin/serverFileRoute.d.ts +0 -4
- package/dist/esm/start-compiler-plugin/serverFileRoute.js +0 -38
- package/dist/esm/start-compiler-plugin/serverFileRoute.js.map +0 -1
- package/dist/esm/start-compiler-plugin/serverFn.d.ts +0 -4
- package/dist/esm/start-compiler-plugin/serverFn.js.map +0 -1
- package/dist/esm/start-router-plugin/generator-plugins/server-routes-plugin.d.ts +0 -2
- package/dist/esm/start-router-plugin/generator-plugins/server-routes-plugin.js +0 -119
- package/dist/esm/start-router-plugin/generator-plugins/server-routes-plugin.js.map +0 -1
- package/dist/esm/start-router-plugin/route-tree-client-plugin.d.ts +0 -6
- package/dist/esm/start-router-plugin/route-tree-client-plugin.js +0 -56
- package/dist/esm/start-router-plugin/route-tree-client-plugin.js.map +0 -1
- package/dist/esm/start-router-plugin/virtual-route-tree-plugin.d.ts +0 -3
- package/dist/esm/start-router-plugin/virtual-route-tree-plugin.js +0 -29
- package/dist/esm/start-router-plugin/virtual-route-tree-plugin.js.map +0 -1
- package/src/start-compiler-plugin/serverFileRoute.ts +0 -59
- package/src/start-router-plugin/generator-plugins/server-routes-plugin.ts +0 -138
- package/src/start-router-plugin/route-tree-client-plugin.ts +0 -77
- package/src/start-router-plugin/virtual-route-tree-plugin.ts +0 -29
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["../../../src/start-router-plugin/plugin.ts"],"sourcesContent":["/*\nwhat is this plugin doing, especially compared to one already existing in the @tanstack/router-plugin package?\n\nit configures:\n1. the generator to generate both the render-route-tree as well as the server-route-tree\n2. the code-splitter plugin, so it could possibly be enabled per environment (e.g. disable on the server)\n3. the auto import plugin for both environments\n4. the route tree client plugin, which removes the server part from the generated route tree\n5. the virtual route tree plugin, which provides the route tree to the server\n*/\n\nimport {\n tanStackRouterCodeSplitter,\n tanstackRouterAutoImport,\n tanstackRouterGenerator,\n} from '@tanstack/router-plugin/vite'\nimport { VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { routeTreeClientPlugin } from './route-tree-client-plugin'\nimport { virtualRouteTreePlugin } from './virtual-route-tree-plugin'\nimport { routesManifestPlugin } from './generator-plugins/routes-manifest-plugin'\nimport { serverRoutesPlugin } from './generator-plugins/server-routes-plugin'\nimport type { PluginOption } from 'vite'\nimport type { Config } from '@tanstack/router-plugin'\n\nexport function tanStackStartRouter(config: Config): Array<PluginOption> {\n return [\n tanstackRouterGenerator({\n ...config,\n plugins: [serverRoutesPlugin(), routesManifestPlugin()],\n plugin: {\n vite: { environmentName: VITE_ENVIRONMENT_NAMES.client },\n },\n }),\n tanStackRouterCodeSplitter({\n ...config,\n codeSplittingOptions: {\n ...config.codeSplittingOptions,\n deleteNodes: ['ssr'],\n addHmr: true,\n },\n plugin: {\n vite: { environmentName: VITE_ENVIRONMENT_NAMES.client },\n },\n }),\n tanStackRouterCodeSplitter({\n ...config,\n codeSplittingOptions: {\n ...config.codeSplittingOptions,\n addHmr: false,\n },\n plugin: {\n vite: { environmentName: VITE_ENVIRONMENT_NAMES.server },\n },\n }),\n tanstackRouterAutoImport(config),\n routeTreeClientPlugin(config),\n virtualRouteTreePlugin(config),\n ]\n}\n"],"names":[],"mappings":";;;;;;AAwBO,SAAS,oBAAoB,QAAqC;AACvE,SAAO;AAAA,IACL,wBAAwB;AAAA,MACtB,GAAG;AAAA,MACH,SAAS,CAAC,sBAAsB,sBAAsB;AAAA,MACtD,QAAQ;AAAA,QACN,MAAM,EAAE,iBAAiB,uBAAuB,OAAA;AAAA,MAAO;AAAA,IACzD,CACD;AAAA,IACD,2BAA2B;AAAA,MACzB,GAAG;AAAA,MACH,sBAAsB;AAAA,QACpB,GAAG,OAAO;AAAA,QACV,aAAa,CAAC,KAAK;AAAA,QACnB,QAAQ;AAAA,MAAA;AAAA,MAEV,QAAQ;AAAA,QACN,MAAM,EAAE,iBAAiB,uBAAuB,OAAA;AAAA,MAAO;AAAA,IACzD,CACD;AAAA,IACD,2BAA2B;AAAA,MACzB,GAAG;AAAA,MACH,sBAAsB;AAAA,QACpB,GAAG,OAAO;AAAA,QACV,QAAQ;AAAA,MAAA;AAAA,MAEV,QAAQ;AAAA,QACN,MAAM,EAAE,iBAAiB,uBAAuB,OAAA;AAAA,MAAO;AAAA,IACzD,CACD;AAAA,IACD,yBAAyB,MAAM;AAAA,IAC/B,sBAAsB,MAAM;AAAA,IAC5B,uBAAuB,MAAM;AAAA,EAAA;AAEjC;"}
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["../../../src/start-router-plugin/plugin.ts"],"sourcesContent":["import {\n tanStackRouterCodeSplitter,\n tanstackRouterAutoImport,\n tanstackRouterGenerator,\n} from '@tanstack/router-plugin/vite'\nimport { normalizePath } from 'vite'\nimport path from 'pathe'\nimport { VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { routesManifestPlugin } from './generator-plugins/routes-manifest-plugin'\nimport { pruneServerOnlySubtrees } from './pruneServerOnlySubtrees'\nimport { SERVER_PROP } from './constants'\nimport type {\n Generator,\n GeneratorPlugin,\n RouteNode,\n} from '@tanstack/router-generator'\nimport type { DevEnvironment, Plugin, PluginOption } from 'vite'\nimport type { TanStackStartInputConfig } from '../schema'\nimport type { GetConfigFn, TanStackStartVitePluginCoreOptions } from '../plugin'\n\nfunction isServerOnlyNode(node: RouteNode | undefined) {\n if (!node?.createFileRouteProps) {\n return false\n }\n return (\n node.createFileRouteProps.has(SERVER_PROP) &&\n node.createFileRouteProps.size === 1\n )\n}\n\nfunction moduleDeclaration({\n startFilePath,\n routerFilePath,\n corePluginOpts,\n generatedRouteTreePath,\n}: {\n startFilePath: string | undefined\n routerFilePath: string\n corePluginOpts: TanStackStartVitePluginCoreOptions\n generatedRouteTreePath: string\n}): string {\n function getImportPath(absolutePath: string) {\n let relativePath = path.relative(\n path.dirname(generatedRouteTreePath),\n absolutePath,\n )\n\n if (!relativePath.startsWith('.')) {\n relativePath = './' + relativePath\n }\n\n // convert to POSIX-style for ESM imports (important on Windows)\n relativePath = relativePath.split(path.sep).join('/')\n return relativePath\n }\n\n const result: Array<string> = [\n `import type { getRouter } from '${getImportPath(routerFilePath)}'`,\n ]\n if (startFilePath) {\n result.push(\n `import type { startInstance } from '${getImportPath(startFilePath)}'`,\n )\n }\n // make sure we import something from start to get the server route declaration merge\n else {\n result.push(\n `import type { createStart } from '@tanstack/${corePluginOpts.framework}-start'`,\n )\n }\n result.push(\n `declare module '@tanstack/${corePluginOpts.framework}-start' {\n interface Register {\n router: Awaited<ReturnType<typeof getRouter>>`,\n )\n if (startFilePath) {\n result.push(\n ` config: Awaited<ReturnType<typeof startInstance.getOptions>>`,\n )\n }\n result.push(` }\n}`)\n\n return result.join('\\n')\n}\n\nexport function tanStackStartRouter(\n startPluginOpts: TanStackStartInputConfig,\n getConfig: GetConfigFn,\n corePluginOpts: TanStackStartVitePluginCoreOptions,\n): Array<PluginOption> {\n const getGeneratedRouteTreePath = () => {\n const { startConfig } = getConfig()\n return path.resolve(startConfig.router.generatedRouteTree)\n }\n\n let clientEnvironment: DevEnvironment | null = null\n function invalidate() {\n if (!clientEnvironment) {\n return\n }\n\n const mod = clientEnvironment.moduleGraph.getModuleById(\n getGeneratedRouteTreePath(),\n )\n if (mod) {\n clientEnvironment.moduleGraph.invalidateModule(mod)\n }\n clientEnvironment.hot.send({ type: 'full-reload', path: '*' })\n }\n\n let generatorInstance: Generator | null = null\n\n const clientTreeGeneratorPlugin: GeneratorPlugin = {\n name: 'start-client-tree-plugin',\n init({ generator }) {\n generatorInstance = generator\n },\n afterTransform({ node, prevNode }) {\n if (isServerOnlyNode(node) !== isServerOnlyNode(prevNode)) {\n invalidate()\n }\n },\n }\n\n let routeTreeFileFooter: Array<string> | null = null\n\n function getRouteTreeFileFooter() {\n if (routeTreeFileFooter) {\n return routeTreeFileFooter\n }\n const { startConfig, resolvedStartConfig } = getConfig()\n const ogRouteTreeFileFooter = startConfig.router.routeTreeFileFooter\n if (ogRouteTreeFileFooter) {\n if (Array.isArray(ogRouteTreeFileFooter)) {\n routeTreeFileFooter = ogRouteTreeFileFooter\n } else {\n routeTreeFileFooter = ogRouteTreeFileFooter()\n }\n }\n routeTreeFileFooter = [\n moduleDeclaration({\n generatedRouteTreePath: getGeneratedRouteTreePath(),\n corePluginOpts,\n startFilePath: resolvedStartConfig.startFilePath,\n routerFilePath: resolvedStartConfig.routerFilePath,\n }),\n ...(routeTreeFileFooter ?? []),\n ]\n return routeTreeFileFooter\n }\n\n let resolvedGeneratedRouteTreePath: string | null = null\n const clientTreePlugin: Plugin = {\n name: 'tanstack-start:route-tree-client-plugin',\n enforce: 'pre',\n applyToEnvironment: (env) => env.name === VITE_ENVIRONMENT_NAMES.client,\n configureServer(server) {\n clientEnvironment = server.environments[VITE_ENVIRONMENT_NAMES.client]\n },\n config() {\n type LoadObjectHook = Extract<\n typeof clientTreePlugin.load,\n { filter?: unknown }\n >\n resolvedGeneratedRouteTreePath = normalizePath(\n getGeneratedRouteTreePath(),\n )\n ;(clientTreePlugin.load as LoadObjectHook).filter = {\n id: { include: new RegExp(resolvedGeneratedRouteTreePath) },\n }\n },\n\n load: {\n filter: {\n // this will be set in the config hook above since it relies on `config` hook being called first\n },\n async handler() {\n if (!generatorInstance) {\n throw new Error('Generator instance not initialized')\n }\n const crawlingResult = await generatorInstance.getCrawlingResult()\n if (!crawlingResult) {\n throw new Error('Crawling result not available')\n }\n const prunedAcc = pruneServerOnlySubtrees(crawlingResult)\n const acc = {\n ...crawlingResult.acc,\n ...prunedAcc,\n }\n const buildResult = generatorInstance.buildRouteTree({\n ...crawlingResult,\n acc,\n config: {\n // importRoutesUsingAbsolutePaths: true,\n // addExtensions: true,\n disableTypes: true,\n enableRouteTreeFormatting: false,\n routeTreeFileHeader: [],\n routeTreeFileFooter: [],\n },\n })\n return { code: buildResult.routeTreeContent, map: null }\n },\n },\n }\n return [\n clientTreePlugin,\n tanstackRouterGenerator(() => {\n const routerConfig = getConfig().startConfig.router\n return {\n ...routerConfig,\n target: corePluginOpts.framework,\n routeTreeFileFooter: getRouteTreeFileFooter,\n plugins: [clientTreeGeneratorPlugin, routesManifestPlugin()],\n }\n }),\n tanStackRouterCodeSplitter(() => {\n const routerConfig = getConfig().startConfig.router\n return {\n ...routerConfig,\n codeSplittingOptions: {\n ...routerConfig.codeSplittingOptions,\n deleteNodes: ['ssr', 'server'],\n addHmr: true,\n },\n plugin: {\n vite: { environmentName: VITE_ENVIRONMENT_NAMES.client },\n },\n }\n }),\n tanStackRouterCodeSplitter(() => {\n const routerConfig = getConfig().startConfig.router\n return {\n ...routerConfig,\n codeSplittingOptions: {\n ...routerConfig.codeSplittingOptions,\n addHmr: false,\n },\n plugin: {\n vite: { environmentName: VITE_ENVIRONMENT_NAMES.server },\n },\n }\n }),\n tanstackRouterAutoImport(startPluginOpts?.router),\n ]\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { HandleNodeAccumulator, RouteNode } from '@tanstack/router-generator';
|
|
2
|
+
export declare function pruneServerOnlySubtrees({ rootRouteNode, acc, }: {
|
|
3
|
+
rootRouteNode: RouteNode;
|
|
4
|
+
acc: HandleNodeAccumulator;
|
|
5
|
+
}): {
|
|
6
|
+
routeTree: RouteNode[];
|
|
7
|
+
routeNodes: RouteNode[];
|
|
8
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SERVER_PROP } from "./constants.js";
|
|
2
|
+
function pruneServerOnlySubtrees({
|
|
3
|
+
rootRouteNode,
|
|
4
|
+
acc
|
|
5
|
+
}) {
|
|
6
|
+
const routeNodes = [];
|
|
7
|
+
const routeTree = prune({ ...rootRouteNode, children: acc.routeTree }, routeNodes)?.children || [];
|
|
8
|
+
routeNodes.pop();
|
|
9
|
+
return {
|
|
10
|
+
routeTree,
|
|
11
|
+
routeNodes
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function prune(node, collectedRouteNodes) {
|
|
15
|
+
const newChildren = [];
|
|
16
|
+
let allChildrenServerOnly = true;
|
|
17
|
+
for (const child of node.children || []) {
|
|
18
|
+
const newChild = prune(child, collectedRouteNodes);
|
|
19
|
+
if (newChild) {
|
|
20
|
+
newChildren.push(newChild);
|
|
21
|
+
allChildrenServerOnly = false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const allServerOnly = node.createFileRouteProps?.has(SERVER_PROP) && node.createFileRouteProps.size === 1 && allChildrenServerOnly;
|
|
25
|
+
if (allServerOnly) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
collectedRouteNodes.push(node);
|
|
29
|
+
return { ...node, children: newChildren };
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
pruneServerOnlySubtrees
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=pruneServerOnlySubtrees.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pruneServerOnlySubtrees.js","sources":["../../../src/start-router-plugin/pruneServerOnlySubtrees.ts"],"sourcesContent":["import { SERVER_PROP } from './constants'\nimport type {\n HandleNodeAccumulator,\n RouteNode,\n} from '@tanstack/router-generator'\n\nexport function pruneServerOnlySubtrees({\n rootRouteNode,\n acc,\n}: {\n rootRouteNode: RouteNode\n acc: HandleNodeAccumulator\n}) {\n const routeNodes: Array<RouteNode> = []\n const routeTree =\n prune({ ...rootRouteNode, children: acc.routeTree }, routeNodes)\n ?.children || []\n // remove root node from routeNodes\n routeNodes.pop()\n return {\n routeTree,\n routeNodes,\n }\n}\nfunction prune(\n node: RouteNode,\n collectedRouteNodes: Array<RouteNode>,\n): RouteNode | null {\n const newChildren: Array<RouteNode> = []\n let allChildrenServerOnly = true\n\n for (const child of node.children || []) {\n const newChild = prune(child, collectedRouteNodes)\n if (newChild) {\n newChildren.push(newChild)\n // at least one child survived pruning\n allChildrenServerOnly = false\n }\n }\n\n const allServerOnly =\n node.createFileRouteProps?.has(SERVER_PROP) &&\n node.createFileRouteProps.size === 1 &&\n allChildrenServerOnly\n // prune this subtree\n if (allServerOnly) {\n return null\n }\n collectedRouteNodes.push(node)\n return { ...node, children: newChildren }\n}\n"],"names":[],"mappings":";AAMO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AACF,GAGG;AACD,QAAM,aAA+B,CAAA;AACrC,QAAM,YACJ,MAAM,EAAE,GAAG,eAAe,UAAU,IAAI,UAAA,GAAa,UAAU,GAC3D,YAAY,CAAA;AAElB,aAAW,IAAA;AACX,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EAAA;AAEJ;AACA,SAAS,MACP,MACA,qBACkB;AAClB,QAAM,cAAgC,CAAA;AACtC,MAAI,wBAAwB;AAE5B,aAAW,SAAS,KAAK,YAAY,CAAA,GAAI;AACvC,UAAM,WAAW,MAAM,OAAO,mBAAmB;AACjD,QAAI,UAAU;AACZ,kBAAY,KAAK,QAAQ;AAEzB,8BAAwB;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,gBACJ,KAAK,sBAAsB,IAAI,WAAW,KAC1C,KAAK,qBAAqB,SAAS,KACnC;AAEF,MAAI,eAAe;AACjB,WAAO;AAAA,EACT;AACA,sBAAoB,KAAK,IAAI;AAC7B,SAAO,EAAE,GAAG,MAAM,UAAU,YAAA;AAC9B;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-plugin-core",
|
|
3
|
-
"version": "1.132.0
|
|
3
|
+
"version": "1.132.0",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"src"
|
|
43
43
|
],
|
|
44
44
|
"engines": {
|
|
45
|
-
"node": ">=12"
|
|
45
|
+
"node": ">=22.12.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@babel/code-frame": "7.26.2",
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"vitefu": "^1.1.1",
|
|
58
58
|
"xmlbuilder2": "^3.1.1",
|
|
59
59
|
"zod": "^3.24.2",
|
|
60
|
-
"@tanstack/router-core": "1.132.0
|
|
61
|
-
"@tanstack/router-
|
|
62
|
-
"@tanstack/router-
|
|
63
|
-
"@tanstack/
|
|
64
|
-
"@tanstack/
|
|
65
|
-
"@tanstack/start-server-core": "1.132.0
|
|
60
|
+
"@tanstack/router-core": "1.132.0",
|
|
61
|
+
"@tanstack/router-plugin": "1.132.0",
|
|
62
|
+
"@tanstack/router-generator": "1.132.0",
|
|
63
|
+
"@tanstack/server-functions-plugin": "1.132.0",
|
|
64
|
+
"@tanstack/router-utils": "1.132.0",
|
|
65
|
+
"@tanstack/start-server-core": "1.132.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/babel__code-frame": "^7.0.6",
|
package/src/constants.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type ViteEnvironmentNames =
|
|
|
14
14
|
export const ENTRY_POINTS = {
|
|
15
15
|
client: 'virtual:tanstack-start-client-entry',
|
|
16
16
|
server: 'virtual:tanstack-start-server-request-entry',
|
|
17
|
-
// the
|
|
18
|
-
|
|
17
|
+
// the start entry point must always be provided by the user
|
|
18
|
+
start: '#tanstack-start-entry',
|
|
19
|
+
router: '#tanstack-router-entry',
|
|
19
20
|
} as const
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
/* eslint-disable import/no-commonjs */
|
|
2
|
+
import * as t from '@babel/types'
|
|
3
|
+
import { generateFromAst, parseAst } from '@tanstack/router-utils'
|
|
4
|
+
import babel from '@babel/core'
|
|
5
|
+
import {
|
|
6
|
+
deadCodeElimination,
|
|
7
|
+
findReferencedIdentifiers,
|
|
8
|
+
} from 'babel-dead-code-elimination'
|
|
9
|
+
import { handleCreateServerFn } from './handleCreateServerFn'
|
|
10
|
+
import { handleCreateMiddleware } from './handleCreateMiddleware'
|
|
11
|
+
|
|
12
|
+
type Binding =
|
|
13
|
+
| {
|
|
14
|
+
type: 'import'
|
|
15
|
+
source: string
|
|
16
|
+
importedName: string
|
|
17
|
+
resolvedKind?: Kind
|
|
18
|
+
}
|
|
19
|
+
| {
|
|
20
|
+
type: 'var'
|
|
21
|
+
init: t.Expression | null
|
|
22
|
+
resolvedKind?: Kind
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type ExportEntry =
|
|
26
|
+
| { tag: 'Normal'; name: string }
|
|
27
|
+
| { tag: 'Default'; name: string }
|
|
28
|
+
| { tag: 'Namespace'; name: string; targetId: string } // for `export * as ns from './x'`
|
|
29
|
+
|
|
30
|
+
type Kind = 'None' | `Root` | `Builder` | LookupKind
|
|
31
|
+
|
|
32
|
+
type LookupKind = 'ServerFn' | 'Middleware'
|
|
33
|
+
|
|
34
|
+
const validLookupKinds: Array<LookupKind> = ['ServerFn', 'Middleware']
|
|
35
|
+
const candidateCallIdentifier = ['handler', 'server', 'client']
|
|
36
|
+
export type LookupConfig = {
|
|
37
|
+
libName: string
|
|
38
|
+
rootExport: string
|
|
39
|
+
}
|
|
40
|
+
interface ModuleInfo {
|
|
41
|
+
id: string
|
|
42
|
+
code: string
|
|
43
|
+
ast: ReturnType<typeof parseAst>
|
|
44
|
+
bindings: Map<string, Binding>
|
|
45
|
+
exports: Map<string, ExportEntry>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class ServerFnCompiler {
|
|
49
|
+
private moduleCache = new Map<string, ModuleInfo>()
|
|
50
|
+
private initialized = false
|
|
51
|
+
constructor(
|
|
52
|
+
private options: {
|
|
53
|
+
env: 'client' | 'server'
|
|
54
|
+
lookupConfigurations: Array<LookupConfig>
|
|
55
|
+
loadModule: (id: string) => Promise<void>
|
|
56
|
+
resolveId: (id: string, importer?: string) => Promise<string | null>
|
|
57
|
+
},
|
|
58
|
+
) {}
|
|
59
|
+
|
|
60
|
+
private async init(id: string) {
|
|
61
|
+
await Promise.all(
|
|
62
|
+
this.options.lookupConfigurations.map(async (config) => {
|
|
63
|
+
const libId = await this.options.resolveId(config.libName, id)
|
|
64
|
+
if (!libId) {
|
|
65
|
+
throw new Error(`could not resolve "${config.libName}"`)
|
|
66
|
+
}
|
|
67
|
+
let rootModule = this.moduleCache.get(libId)
|
|
68
|
+
if (!rootModule) {
|
|
69
|
+
// insert root binding
|
|
70
|
+
rootModule = {
|
|
71
|
+
ast: null as any,
|
|
72
|
+
bindings: new Map(),
|
|
73
|
+
exports: new Map(),
|
|
74
|
+
code: '',
|
|
75
|
+
id: libId,
|
|
76
|
+
}
|
|
77
|
+
this.moduleCache.set(libId, rootModule)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
rootModule.exports.set(config.rootExport, {
|
|
81
|
+
tag: 'Normal',
|
|
82
|
+
name: config.rootExport,
|
|
83
|
+
})
|
|
84
|
+
rootModule.exports.set('*', {
|
|
85
|
+
tag: 'Namespace',
|
|
86
|
+
name: config.rootExport,
|
|
87
|
+
targetId: libId,
|
|
88
|
+
})
|
|
89
|
+
rootModule.bindings.set(config.rootExport, {
|
|
90
|
+
type: 'var',
|
|
91
|
+
init: t.identifier(config.rootExport),
|
|
92
|
+
resolvedKind: `Root` satisfies Kind,
|
|
93
|
+
})
|
|
94
|
+
this.moduleCache.set(libId, rootModule)
|
|
95
|
+
}),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
this.initialized = true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public ingestModule({ code, id }: { code: string; id: string }) {
|
|
102
|
+
const ast = parseAst({ code })
|
|
103
|
+
|
|
104
|
+
const bindings = new Map<string, Binding>()
|
|
105
|
+
const exports = new Map<string, ExportEntry>()
|
|
106
|
+
|
|
107
|
+
// we are only interested in top-level bindings, hence we don't traverse the AST
|
|
108
|
+
// instead we only iterate over the program body
|
|
109
|
+
for (const node of ast.program.body) {
|
|
110
|
+
if (t.isImportDeclaration(node)) {
|
|
111
|
+
const source = node.source.value
|
|
112
|
+
for (const s of node.specifiers) {
|
|
113
|
+
if (t.isImportSpecifier(s)) {
|
|
114
|
+
const importedName = t.isIdentifier(s.imported)
|
|
115
|
+
? s.imported.name
|
|
116
|
+
: s.imported.value
|
|
117
|
+
bindings.set(s.local.name, { type: 'import', source, importedName })
|
|
118
|
+
} else if (t.isImportDefaultSpecifier(s)) {
|
|
119
|
+
bindings.set(s.local.name, {
|
|
120
|
+
type: 'import',
|
|
121
|
+
source,
|
|
122
|
+
importedName: 'default',
|
|
123
|
+
})
|
|
124
|
+
} else if (t.isImportNamespaceSpecifier(s)) {
|
|
125
|
+
bindings.set(s.local.name, {
|
|
126
|
+
type: 'import',
|
|
127
|
+
source,
|
|
128
|
+
importedName: '*',
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} else if (t.isVariableDeclaration(node)) {
|
|
133
|
+
for (const decl of node.declarations) {
|
|
134
|
+
if (t.isIdentifier(decl.id)) {
|
|
135
|
+
bindings.set(decl.id.name, {
|
|
136
|
+
type: 'var',
|
|
137
|
+
init: decl.init ?? null,
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else if (t.isExportNamedDeclaration(node)) {
|
|
142
|
+
// export const foo = ...
|
|
143
|
+
if (node.declaration) {
|
|
144
|
+
if (t.isVariableDeclaration(node.declaration)) {
|
|
145
|
+
for (const d of node.declaration.declarations) {
|
|
146
|
+
if (t.isIdentifier(d.id)) {
|
|
147
|
+
exports.set(d.id.name, { tag: 'Normal', name: d.id.name })
|
|
148
|
+
bindings.set(d.id.name, { type: 'var', init: d.init ?? null })
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const sp of node.specifiers) {
|
|
154
|
+
if (t.isExportNamespaceSpecifier(sp)) {
|
|
155
|
+
exports.set(sp.exported.name, {
|
|
156
|
+
tag: 'Namespace',
|
|
157
|
+
name: sp.exported.name,
|
|
158
|
+
targetId: node.source?.value || '',
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
// export { local as exported }
|
|
162
|
+
else if (t.isExportSpecifier(sp)) {
|
|
163
|
+
const local = sp.local.name
|
|
164
|
+
const exported = t.isIdentifier(sp.exported)
|
|
165
|
+
? sp.exported.name
|
|
166
|
+
: sp.exported.value
|
|
167
|
+
exports.set(exported, { tag: 'Normal', name: local })
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} else if (t.isExportDefaultDeclaration(node)) {
|
|
171
|
+
const d = node.declaration
|
|
172
|
+
if (t.isIdentifier(d)) {
|
|
173
|
+
exports.set('default', { tag: 'Default', name: d.name })
|
|
174
|
+
} else {
|
|
175
|
+
const synth = '__default_export__'
|
|
176
|
+
bindings.set(synth, { type: 'var', init: d as t.Expression })
|
|
177
|
+
exports.set('default', { tag: 'Default', name: synth })
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const info: ModuleInfo = { code, id, ast, bindings, exports }
|
|
183
|
+
this.moduleCache.set(id, info)
|
|
184
|
+
return info
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public invalidateModule(id: string) {
|
|
188
|
+
return this.moduleCache.delete(id)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public async compile({ code, id }: { code: string; id: string }) {
|
|
192
|
+
if (!this.initialized) {
|
|
193
|
+
await this.init(id)
|
|
194
|
+
}
|
|
195
|
+
const { bindings, ast } = this.ingestModule({ code, id })
|
|
196
|
+
const candidates = this.collectCandidates(bindings)
|
|
197
|
+
if (candidates.length === 0) {
|
|
198
|
+
// this hook will only be invoked if there is `.handler(` | `.server(` | `.client(` in the code,
|
|
199
|
+
// so not discovering a handler candidate is rather unlikely, but maybe possible?
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// let's find out which of the candidates are actually server functions
|
|
204
|
+
const toRewrite: Array<{
|
|
205
|
+
callExpression: t.CallExpression
|
|
206
|
+
kind: LookupKind
|
|
207
|
+
}> = []
|
|
208
|
+
for (const handler of candidates) {
|
|
209
|
+
const kind = await this.resolveExprKind(handler, id)
|
|
210
|
+
if (validLookupKinds.includes(kind as LookupKind)) {
|
|
211
|
+
toRewrite.push({ callExpression: handler, kind: kind as LookupKind })
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (toRewrite.length === 0) {
|
|
215
|
+
return null
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const pathsToRewrite: Array<{
|
|
219
|
+
nodePath: babel.NodePath<t.CallExpression>
|
|
220
|
+
kind: LookupKind
|
|
221
|
+
}> = []
|
|
222
|
+
babel.traverse(ast, {
|
|
223
|
+
CallExpression(path) {
|
|
224
|
+
const found = toRewrite.findIndex((h) => path.node === h.callExpression)
|
|
225
|
+
if (found !== -1) {
|
|
226
|
+
pathsToRewrite.push({ nodePath: path, kind: toRewrite[found]!.kind })
|
|
227
|
+
// delete from toRewrite
|
|
228
|
+
toRewrite.splice(found, 1)
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
if (toRewrite.length > 0) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Internal error: could not find all paths to rewrite. please file an issue`,
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const refIdents = findReferencedIdentifiers(ast)
|
|
240
|
+
|
|
241
|
+
pathsToRewrite.map((p) => {
|
|
242
|
+
if (p.kind === 'ServerFn') {
|
|
243
|
+
handleCreateServerFn(p.nodePath, { env: this.options.env, code })
|
|
244
|
+
} else {
|
|
245
|
+
handleCreateMiddleware(p.nodePath, { env: this.options.env })
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
deadCodeElimination(ast, refIdents)
|
|
250
|
+
|
|
251
|
+
return generateFromAst(ast, {
|
|
252
|
+
sourceMaps: true,
|
|
253
|
+
sourceFileName: id,
|
|
254
|
+
filename: id,
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// collects all candidate CallExpressions at top-level
|
|
259
|
+
private collectCandidates(bindings: Map<string, Binding>) {
|
|
260
|
+
const candidates: Array<t.CallExpression> = []
|
|
261
|
+
|
|
262
|
+
for (const binding of bindings.values()) {
|
|
263
|
+
if (binding.type === 'var') {
|
|
264
|
+
const handler = isCandidateCallExpression(binding.init)
|
|
265
|
+
if (handler) {
|
|
266
|
+
candidates.push(handler)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return candidates
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private async resolveIdentifierKind(
|
|
274
|
+
ident: string,
|
|
275
|
+
id: string,
|
|
276
|
+
visited = new Set<string>(),
|
|
277
|
+
): Promise<Kind> {
|
|
278
|
+
const info = await this.getModuleInfo(id)
|
|
279
|
+
|
|
280
|
+
const binding = info.bindings.get(ident)
|
|
281
|
+
if (!binding) {
|
|
282
|
+
return 'None'
|
|
283
|
+
}
|
|
284
|
+
if (binding.resolvedKind) {
|
|
285
|
+
return binding.resolvedKind
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// TODO improve cycle detection? should we throw here instead of returning 'None'?
|
|
289
|
+
// prevent cycles
|
|
290
|
+
const vKey = `${id}:${ident}`
|
|
291
|
+
if (visited.has(vKey)) {
|
|
292
|
+
return 'None'
|
|
293
|
+
}
|
|
294
|
+
visited.add(vKey)
|
|
295
|
+
|
|
296
|
+
const resolvedKind = await this.resolveBindingKind(binding, id, visited)
|
|
297
|
+
binding.resolvedKind = resolvedKind
|
|
298
|
+
return resolvedKind
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private async resolveBindingKind(
|
|
302
|
+
binding: Binding,
|
|
303
|
+
fileId: string,
|
|
304
|
+
visited = new Set<string>(),
|
|
305
|
+
): Promise<Kind> {
|
|
306
|
+
if (binding.resolvedKind) {
|
|
307
|
+
return binding.resolvedKind
|
|
308
|
+
}
|
|
309
|
+
if (binding.type === 'import') {
|
|
310
|
+
const target = await this.options.resolveId(binding.source, fileId)
|
|
311
|
+
if (!target) {
|
|
312
|
+
return 'None'
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const importedModule = await this.getModuleInfo(target)
|
|
316
|
+
|
|
317
|
+
const moduleExport = importedModule.exports.get(binding.importedName)
|
|
318
|
+
if (!moduleExport) {
|
|
319
|
+
return 'None'
|
|
320
|
+
}
|
|
321
|
+
const importedBinding = importedModule.bindings.get(moduleExport.name)
|
|
322
|
+
if (!importedBinding) {
|
|
323
|
+
return 'None'
|
|
324
|
+
}
|
|
325
|
+
if (importedBinding.resolvedKind) {
|
|
326
|
+
return importedBinding.resolvedKind
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const resolvedKind = await this.resolveBindingKind(
|
|
330
|
+
importedBinding,
|
|
331
|
+
importedModule.id,
|
|
332
|
+
visited,
|
|
333
|
+
)
|
|
334
|
+
importedBinding.resolvedKind = resolvedKind
|
|
335
|
+
return resolvedKind
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const resolvedKind = await this.resolveExprKind(
|
|
339
|
+
binding.init,
|
|
340
|
+
fileId,
|
|
341
|
+
visited,
|
|
342
|
+
)
|
|
343
|
+
binding.resolvedKind = resolvedKind
|
|
344
|
+
return resolvedKind
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private async resolveExprKind(
|
|
348
|
+
expr: t.Expression | null,
|
|
349
|
+
fileId: string,
|
|
350
|
+
visited = new Set<string>(),
|
|
351
|
+
): Promise<Kind> {
|
|
352
|
+
if (!expr) {
|
|
353
|
+
return 'None'
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
let result: Kind = 'None'
|
|
357
|
+
|
|
358
|
+
if (t.isCallExpression(expr)) {
|
|
359
|
+
if (!t.isExpression(expr.callee)) {
|
|
360
|
+
return 'None'
|
|
361
|
+
}
|
|
362
|
+
const calleeKind = await this.resolveCalleeKind(
|
|
363
|
+
expr.callee,
|
|
364
|
+
fileId,
|
|
365
|
+
visited,
|
|
366
|
+
)
|
|
367
|
+
if (calleeKind !== 'None') {
|
|
368
|
+
if (calleeKind === `Root` || calleeKind === `Builder`) {
|
|
369
|
+
return `Builder`
|
|
370
|
+
}
|
|
371
|
+
for (const kind of validLookupKinds) {
|
|
372
|
+
if (calleeKind === kind) {
|
|
373
|
+
return kind
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else if (t.isMemberExpression(expr) && t.isIdentifier(expr.property)) {
|
|
378
|
+
result = await this.resolveCalleeKind(expr.object, fileId, visited)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (result === 'None' && t.isIdentifier(expr)) {
|
|
382
|
+
result = await this.resolveIdentifierKind(expr.name, fileId, visited)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (result === 'None' && t.isTSAsExpression(expr)) {
|
|
386
|
+
result = await this.resolveExprKind(expr.expression, fileId, visited)
|
|
387
|
+
}
|
|
388
|
+
if (result === 'None' && t.isTSNonNullExpression(expr)) {
|
|
389
|
+
result = await this.resolveExprKind(expr.expression, fileId, visited)
|
|
390
|
+
}
|
|
391
|
+
if (result === 'None' && t.isParenthesizedExpression(expr)) {
|
|
392
|
+
result = await this.resolveExprKind(expr.expression, fileId, visited)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return result
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private async resolveCalleeKind(
|
|
399
|
+
callee: t.Expression,
|
|
400
|
+
fileId: string,
|
|
401
|
+
visited = new Set<string>(),
|
|
402
|
+
): Promise<Kind> {
|
|
403
|
+
if (t.isIdentifier(callee)) {
|
|
404
|
+
return this.resolveIdentifierKind(callee.name, fileId, visited)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
|
|
408
|
+
const prop = callee.property.name
|
|
409
|
+
|
|
410
|
+
if (prop === 'handler') {
|
|
411
|
+
const base = await this.resolveExprKind(callee.object, fileId, visited)
|
|
412
|
+
if (base === 'Root' || base === 'Builder') {
|
|
413
|
+
return 'ServerFn'
|
|
414
|
+
}
|
|
415
|
+
return 'None'
|
|
416
|
+
} else if (
|
|
417
|
+
prop === 'client' ||
|
|
418
|
+
prop === 'server' ||
|
|
419
|
+
prop === 'createMiddleware'
|
|
420
|
+
) {
|
|
421
|
+
const base = await this.resolveExprKind(callee.object, fileId, visited)
|
|
422
|
+
if (base === 'Root' || base === 'Builder' || base === 'Middleware') {
|
|
423
|
+
return 'Middleware'
|
|
424
|
+
}
|
|
425
|
+
return 'None'
|
|
426
|
+
}
|
|
427
|
+
// Check if the object is a namespace import
|
|
428
|
+
if (t.isIdentifier(callee.object)) {
|
|
429
|
+
const info = await this.getModuleInfo(fileId)
|
|
430
|
+
const binding = info.bindings.get(callee.object.name)
|
|
431
|
+
if (
|
|
432
|
+
binding &&
|
|
433
|
+
binding.type === 'import' &&
|
|
434
|
+
binding.importedName === '*'
|
|
435
|
+
) {
|
|
436
|
+
// resolve the property from the target module
|
|
437
|
+
const targetModuleId = await this.options.resolveId(
|
|
438
|
+
binding.source,
|
|
439
|
+
fileId,
|
|
440
|
+
)
|
|
441
|
+
if (targetModuleId) {
|
|
442
|
+
const targetModule = await this.getModuleInfo(targetModuleId)
|
|
443
|
+
const exportEntry = targetModule.exports.get(callee.property.name)
|
|
444
|
+
if (exportEntry) {
|
|
445
|
+
const exportedBinding = targetModule.bindings.get(
|
|
446
|
+
exportEntry.name,
|
|
447
|
+
)
|
|
448
|
+
if (exportedBinding) {
|
|
449
|
+
return await this.resolveBindingKind(
|
|
450
|
+
exportedBinding,
|
|
451
|
+
targetModule.id,
|
|
452
|
+
visited,
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
return 'None'
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return this.resolveExprKind(callee.object, fileId, visited)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// handle nested expressions
|
|
465
|
+
return this.resolveExprKind(callee, fileId, visited)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private async getModuleInfo(id: string) {
|
|
469
|
+
let cached = this.moduleCache.get(id)
|
|
470
|
+
if (cached) {
|
|
471
|
+
return cached
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
await this.options.loadModule(id)
|
|
475
|
+
|
|
476
|
+
cached = this.moduleCache.get(id)
|
|
477
|
+
if (!cached) {
|
|
478
|
+
throw new Error(`could not load module info for ${id}`)
|
|
479
|
+
}
|
|
480
|
+
return cached
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function isCandidateCallExpression(
|
|
485
|
+
node: t.Node | null | undefined,
|
|
486
|
+
): undefined | t.CallExpression {
|
|
487
|
+
if (!t.isCallExpression(node)) return undefined
|
|
488
|
+
|
|
489
|
+
const callee = node.callee
|
|
490
|
+
if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.property)) {
|
|
491
|
+
return undefined
|
|
492
|
+
}
|
|
493
|
+
if (!candidateCallIdentifier.includes(callee.property.name)) {
|
|
494
|
+
return undefined
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return node
|
|
498
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as t from '@babel/types'
|
|
2
|
-
import { getRootCallExpression } from '
|
|
2
|
+
import { getRootCallExpression } from '../start-compiler-plugin/utils'
|
|
3
3
|
import type * as babel from '@babel/core'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export function handleCreateMiddlewareCallExpression(
|
|
5
|
+
export function handleCreateMiddleware(
|
|
8
6
|
path: babel.NodePath<t.CallExpression>,
|
|
9
|
-
opts:
|
|
7
|
+
opts: {
|
|
8
|
+
env: 'client' | 'server'
|
|
9
|
+
},
|
|
10
10
|
) {
|
|
11
11
|
const rootCallExpression = getRootCallExpression(path)
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ export function handleCreateMiddlewareCallExpression(
|
|
|
18
18
|
|
|
19
19
|
const callExpressionPaths = {
|
|
20
20
|
middleware: null as babel.NodePath<t.CallExpression> | null,
|
|
21
|
-
|
|
21
|
+
inputValidator: null as babel.NodePath<t.CallExpression> | null,
|
|
22
22
|
client: null as babel.NodePath<t.CallExpression> | null,
|
|
23
23
|
server: null as babel.NodePath<t.CallExpression> | null,
|
|
24
24
|
}
|
|
@@ -41,20 +41,23 @@ export function handleCreateMiddlewareCallExpression(
|
|
|
41
41
|
},
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
if (callExpressionPaths.
|
|
45
|
-
const innerInputExpression =
|
|
44
|
+
if (callExpressionPaths.inputValidator) {
|
|
45
|
+
const innerInputExpression =
|
|
46
|
+
callExpressionPaths.inputValidator.node.arguments[0]
|
|
46
47
|
|
|
47
48
|
if (!innerInputExpression) {
|
|
48
49
|
throw new Error(
|
|
49
|
-
'createMiddleware().
|
|
50
|
+
'createMiddleware().inputValidator() must be called with a validator!',
|
|
50
51
|
)
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
// If we're on the client, remove the validator call expression
|
|
54
55
|
if (opts.env === 'client') {
|
|
55
|
-
if (
|
|
56
|
-
callExpressionPaths.
|
|
57
|
-
|
|
56
|
+
if (
|
|
57
|
+
t.isMemberExpression(callExpressionPaths.inputValidator.node.callee)
|
|
58
|
+
) {
|
|
59
|
+
callExpressionPaths.inputValidator.replaceWith(
|
|
60
|
+
callExpressionPaths.inputValidator.node.callee.object,
|
|
58
61
|
)
|
|
59
62
|
}
|
|
60
63
|
}
|