UNPKG

@tanstack/router-plugin

Version:

Modern and scalable routing for React applications

924 lines (923 loc) 43.7 kB
const require_runtime = require("../../_virtual/_rolldown/runtime.cjs"); const require_constants = require("../constants.cjs"); const require_route_hmr_statement = require("../route-hmr-statement.cjs"); const require_path_ids = require("./path-ids.cjs"); const require_framework_options = require("./framework-options.cjs"); let _tanstack_router_utils = require("@tanstack/router-utils"); let _babel_types = require("@babel/types"); _babel_types = require_runtime.__toESM(_babel_types); let _babel_core = require("@babel/core"); _babel_core = require_runtime.__toESM(_babel_core); let _babel_template = require("@babel/template"); _babel_template = require_runtime.__toESM(_babel_template); //#region src/core/code-splitter/compilers.ts var SPLIT_NODES_CONFIG = new Map([ ["loader", { routeIdent: "loader", localImporterIdent: "$$splitLoaderImporter", splitStrategy: "lazyFn", localExporterIdent: "SplitLoader", exporterIdent: "loader" }], ["component", { routeIdent: "component", localImporterIdent: "$$splitComponentImporter", splitStrategy: "lazyRouteComponent", localExporterIdent: "SplitComponent", exporterIdent: "component" }], ["pendingComponent", { routeIdent: "pendingComponent", localImporterIdent: "$$splitPendingComponentImporter", splitStrategy: "lazyRouteComponent", localExporterIdent: "SplitPendingComponent", exporterIdent: "pendingComponent" }], ["errorComponent", { routeIdent: "errorComponent", localImporterIdent: "$$splitErrorComponentImporter", splitStrategy: "lazyRouteComponent", localExporterIdent: "SplitErrorComponent", exporterIdent: "errorComponent" }], ["notFoundComponent", { routeIdent: "notFoundComponent", localImporterIdent: "$$splitNotFoundComponentImporter", splitStrategy: "lazyRouteComponent", localExporterIdent: "SplitNotFoundComponent", exporterIdent: "notFoundComponent" }] ]); var KNOWN_SPLIT_ROUTE_IDENTS = [...SPLIT_NODES_CONFIG.keys()]; function addSplitSearchParamToFilename(filename, grouping) { const [bareFilename] = filename.split("?"); const params = new URLSearchParams(); params.append(require_constants.tsrSplit, require_path_ids.createIdentifier(grouping)); return `${bareFilename}?${params.toString()}`; } function removeSplitSearchParamFromFilename(filename) { const [bareFilename] = filename.split("?"); return bareFilename; } function addSharedSearchParamToFilename(filename) { const [bareFilename] = filename.split("?"); return `${bareFilename}?${require_constants.tsrShared}=1`; } var splittableCreateRouteFns = ["createFileRoute"]; var unsplittableCreateRouteFns = ["createRootRoute", "createRootRouteWithContext"]; var allCreateRouteFns = [...splittableCreateRouteFns, ...unsplittableCreateRouteFns]; /** * Recursively walk an AST node and collect referenced identifier-like names. * Much cheaper than babel.traverse — no path/scope overhead. * * Notes: * - Uses @babel/types `isReferenced` to avoid collecting non-references like * object keys, member expression properties, or binding identifiers. * - Also handles JSX identifiers for component references. */ function collectIdentifiersFromNode(node) { const ids = /* @__PURE__ */ new Set(); (function walk(n, parent, grandparent, parentKey) { if (!n) return; if (_babel_types.isIdentifier(n)) { if (!parent || _babel_types.isReferenced(n, parent, grandparent)) ids.add(n.name); return; } if (_babel_types.isJSXIdentifier(n)) { if (parent && _babel_types.isJSXAttribute(parent) && parentKey === "name") return; if (parent && _babel_types.isJSXMemberExpression(parent) && parentKey === "property") return; const first = n.name[0]; if (first && first === first.toLowerCase()) return; ids.add(n.name); return; } for (const key of _babel_types.VISITOR_KEYS[n.type] || []) { const child = n[key]; if (Array.isArray(child)) { for (const c of child) if (c && typeof c.type === "string") walk(c, n, parent, key); } else if (child && typeof child.type === "string") walk(child, n, parent, key); } })(node); return ids; } /** * Build a map from binding name → declaration AST node for all * locally-declared module-level bindings. Built once, O(1) lookup. */ function buildDeclarationMap(ast) { const map = /* @__PURE__ */ new Map(); for (const stmt of ast.program.body) { const decl = _babel_types.isExportNamedDeclaration(stmt) && stmt.declaration ? stmt.declaration : stmt; if (_babel_types.isVariableDeclaration(decl)) for (const declarator of decl.declarations) for (const name of collectIdentifiersFromPattern(declarator.id)) map.set(name, declarator); else if (_babel_types.isFunctionDeclaration(decl) && decl.id) map.set(decl.id.name, decl); else if (_babel_types.isClassDeclaration(decl) && decl.id) map.set(decl.id.name, decl); } return map; } /** * Build a dependency graph: for each local binding, the set of other local * bindings its declaration references. Built once via simple node walking. */ function buildDependencyGraph(declMap, localBindings) { const graph = /* @__PURE__ */ new Map(); for (const [name, declNode] of declMap) { if (!localBindings.has(name)) continue; const allIds = collectIdentifiersFromNode(declNode); const deps = /* @__PURE__ */ new Set(); for (const id of allIds) if (id !== name && localBindings.has(id)) deps.add(id); graph.set(name, deps); } return graph; } /** * Computes module-level bindings that are shared between split and non-split * route properties. These bindings need to be extracted into a shared virtual * module to avoid double-initialization. * * A binding is "shared" if it is referenced by at least one split property * AND at least one non-split property. Only locally-declared module-level * bindings are candidates (not imports — bundlers dedupe those). */ function computeSharedBindings(opts) { const ast = (0, _tanstack_router_utils.parseAst)(opts); const localModuleLevelBindings = /* @__PURE__ */ new Set(); for (const node of ast.program.body) collectLocalBindingsFromStatement(node, localModuleLevelBindings); localModuleLevelBindings.delete("Route"); if (localModuleLevelBindings.size === 0) return /* @__PURE__ */ new Set(); function findIndexForSplitNode(str) { return opts.codeSplitGroupings.findIndex((group) => group.includes(str)); } let routeOptions; _babel_core.traverse(ast, { CallExpression(path) { if (!_babel_types.isIdentifier(path.node.callee)) return; if (!splittableCreateRouteFns.includes(path.node.callee.name)) return; if (_babel_types.isCallExpression(path.parentPath.node)) { const opts = resolveIdentifier(path, path.parentPath.node.arguments[0]); if (_babel_types.isObjectExpression(opts)) routeOptions = opts; } else if (_babel_types.isVariableDeclarator(path.parentPath.node)) { const caller = resolveIdentifier(path, path.parentPath.node.init); if (_babel_types.isCallExpression(caller)) { const opts = resolveIdentifier(path, caller.arguments[0]); if (_babel_types.isObjectExpression(opts)) routeOptions = opts; } } } }); if (!routeOptions) return /* @__PURE__ */ new Set(); const splitGroupsPresent = /* @__PURE__ */ new Set(); let hasNonSplit = false; for (const prop of routeOptions.properties) { if (!_babel_types.isObjectProperty(prop) || !_babel_types.isIdentifier(prop.key)) continue; if (prop.key.name === "codeSplitGroupings") continue; if (_babel_types.isIdentifier(prop.value) && prop.value.name === "undefined") continue; const groupIndex = findIndexForSplitNode(prop.key.name); if (groupIndex === -1) hasNonSplit = true; else splitGroupsPresent.add(groupIndex); } if (!hasNonSplit && splitGroupsPresent.size < 2) return /* @__PURE__ */ new Set(); const declMap = buildDeclarationMap(ast); const depGraph = buildDependencyGraph(declMap, localModuleLevelBindings); const allLocalBindings = new Set(localModuleLevelBindings); allLocalBindings.add("Route"); const fullDepGraph = buildDependencyGraph(declMap, allLocalBindings); const refsByGroup = /* @__PURE__ */ new Map(); for (const prop of routeOptions.properties) { if (!_babel_types.isObjectProperty(prop) || !_babel_types.isIdentifier(prop.key)) continue; const key = prop.key.name; if (key === "codeSplitGroupings") continue; const groupIndex = findIndexForSplitNode(key); const directRefs = collectModuleLevelRefsFromNode(prop.value, localModuleLevelBindings); const allRefs = new Set(directRefs); expandTransitively(allRefs, depGraph); for (const ref of allRefs) { let groups = refsByGroup.get(ref); if (!groups) { groups = /* @__PURE__ */ new Set(); refsByGroup.set(ref, groups); } groups.add(groupIndex); } } const shared = /* @__PURE__ */ new Set(); for (const [name, groups] of refsByGroup) if (groups.size >= 2) shared.add(name); expandSharedDestructuredDeclarators(ast, refsByGroup, shared); if (shared.size === 0) return shared; expandDestructuredDeclarations(ast, shared); removeBindingsDependingOnRoute(shared, fullDepGraph); return shared; } /** * If bindings from the same destructured declarator are referenced by * different groups, mark all bindings from that declarator as shared. */ function expandSharedDestructuredDeclarators(ast, refsByGroup, shared) { for (const stmt of ast.program.body) { const decl = _babel_types.isExportNamedDeclaration(stmt) && stmt.declaration ? stmt.declaration : stmt; if (!_babel_types.isVariableDeclaration(decl)) continue; for (const declarator of decl.declarations) { if (!_babel_types.isObjectPattern(declarator.id) && !_babel_types.isArrayPattern(declarator.id)) continue; const names = collectIdentifiersFromPattern(declarator.id); const usedGroups = /* @__PURE__ */ new Set(); for (const name of names) { const groups = refsByGroup.get(name); if (!groups) continue; for (const g of groups) usedGroups.add(g); } if (usedGroups.size >= 2) for (const name of names) shared.add(name); } } } /** * Collect locally-declared module-level binding names from a statement. * Pure node inspection, no traversal. */ function collectLocalBindingsFromStatement(node, bindings) { const decl = _babel_types.isExportNamedDeclaration(node) && node.declaration ? node.declaration : node; if (_babel_types.isVariableDeclaration(decl)) for (const declarator of decl.declarations) for (const name of collectIdentifiersFromPattern(declarator.id)) bindings.add(name); else if (_babel_types.isFunctionDeclaration(decl) && decl.id) bindings.add(decl.id.name); else if (_babel_types.isClassDeclaration(decl) && decl.id) bindings.add(decl.id.name); } /** * Collect direct module-level binding names referenced from a given AST node. * Uses a simple recursive walk instead of babel.traverse. */ function collectModuleLevelRefsFromNode(node, localModuleLevelBindings) { const allIds = collectIdentifiersFromNode(node); const refs = /* @__PURE__ */ new Set(); for (const name of allIds) if (localModuleLevelBindings.has(name)) refs.add(name); return refs; } /** * Expand the shared set transitively using a prebuilt dependency graph. * No AST traversals — pure graph BFS. */ function expandTransitively(shared, depGraph) { const queue = [...shared]; const visited = /* @__PURE__ */ new Set(); while (queue.length > 0) { const name = queue.pop(); if (visited.has(name)) continue; visited.add(name); const deps = depGraph.get(name); if (!deps) continue; for (const dep of deps) if (!shared.has(dep)) { shared.add(dep); queue.push(dep); } } } /** * Remove any bindings from `shared` that transitively depend on `Route`. * The Route singleton must remain in the reference file; if a shared binding * references it (directly or transitively), extracting that binding would * duplicate Route in the shared module. * * Uses `depGraph` which must include `Route` as a node so the dependency * chain is visible. */ function removeBindingsDependingOnRoute(shared, depGraph) { const reverseGraph = /* @__PURE__ */ new Map(); for (const [name, deps] of depGraph) for (const dep of deps) { let parents = reverseGraph.get(dep); if (!parents) { parents = /* @__PURE__ */ new Set(); reverseGraph.set(dep, parents); } parents.add(name); } const visited = /* @__PURE__ */ new Set(); const queue = ["Route"]; while (queue.length > 0) { const cur = queue.pop(); if (visited.has(cur)) continue; visited.add(cur); const parents = reverseGraph.get(cur); if (!parents) continue; for (const parent of parents) if (!visited.has(parent)) queue.push(parent); } for (const name of [...shared]) if (visited.has(name)) shared.delete(name); } /** * If any binding from a destructured declaration is shared, * ensure all bindings from that same declaration are also shared. * Pure node inspection of program.body, no traversal. */ function expandDestructuredDeclarations(ast, shared) { for (const stmt of ast.program.body) { const decl = _babel_types.isExportNamedDeclaration(stmt) && stmt.declaration ? stmt.declaration : stmt; if (!_babel_types.isVariableDeclaration(decl)) continue; for (const declarator of decl.declarations) { if (!_babel_types.isObjectPattern(declarator.id) && !_babel_types.isArrayPattern(declarator.id)) continue; const names = collectIdentifiersFromPattern(declarator.id); if (names.some((n) => shared.has(n))) for (const n of names) shared.add(n); } } } /** * Find which shared bindings are user-exported in the original source. * These need to be re-exported from the shared module. */ function findExportedSharedBindings(ast, sharedBindings) { const exported = /* @__PURE__ */ new Set(); for (const stmt of ast.program.body) { if (!_babel_types.isExportNamedDeclaration(stmt) || !stmt.declaration) continue; if (_babel_types.isVariableDeclaration(stmt.declaration)) { for (const decl of stmt.declaration.declarations) for (const name of collectIdentifiersFromPattern(decl.id)) if (sharedBindings.has(name)) exported.add(name); } else if (_babel_types.isFunctionDeclaration(stmt.declaration) && stmt.declaration.id) { if (sharedBindings.has(stmt.declaration.id.name)) exported.add(stmt.declaration.id.name); } else if (_babel_types.isClassDeclaration(stmt.declaration) && stmt.declaration.id) { if (sharedBindings.has(stmt.declaration.id.name)) exported.add(stmt.declaration.id.name); } } return exported; } /** * Remove declarations of shared bindings from the AST. * Handles both plain and exported declarations, including destructured patterns. * Removes the entire statement if all bindings in it are shared. */ function removeSharedDeclarations(ast, sharedBindings) { ast.program.body = ast.program.body.filter((stmt) => { const decl = _babel_types.isExportNamedDeclaration(stmt) && stmt.declaration ? stmt.declaration : stmt; if (_babel_types.isVariableDeclaration(decl)) { decl.declarations = decl.declarations.filter((declarator) => { return !collectIdentifiersFromPattern(declarator.id).every((n) => sharedBindings.has(n)); }); if (decl.declarations.length === 0) return false; } else if (_babel_types.isFunctionDeclaration(decl) && decl.id) { if (sharedBindings.has(decl.id.name)) return false; } else if (_babel_types.isClassDeclaration(decl) && decl.id) { if (sharedBindings.has(decl.id.name)) return false; } return true; }); } function compileCodeSplitReferenceRoute(opts) { const ast = (0, _tanstack_router_utils.parseAst)(opts); const refIdents = (0, _tanstack_router_utils.findReferencedIdentifiers)(ast); const knownExportedIdents = /* @__PURE__ */ new Set(); function findIndexForSplitNode(str) { return opts.codeSplitGroupings.findIndex((group) => group.includes(str)); } const frameworkOptions = require_framework_options.getFrameworkOptions(opts.targetFramework); const PACKAGE = frameworkOptions.package; const LAZY_ROUTE_COMPONENT_IDENT = frameworkOptions.idents.lazyRouteComponent; const LAZY_FN_IDENT = frameworkOptions.idents.lazyFn; let createRouteFn; let modified = false; let hmrAdded = false; let sharedExportedNames; _babel_core.traverse(ast, { Program: { enter(programPath) { /** * If the component for the route is being imported from * another file, this is to track the path to that file * the path itself doesn't matter, we just need to keep * track of it so that we can remove it from the imports * list if it's not being used like: * * `import '../shared/imported'` */ const removableImportPaths = /* @__PURE__ */ new Set([]); programPath.traverse({ CallExpression: (path) => { if (!_babel_types.isIdentifier(path.node.callee)) return; if (!allCreateRouteFns.includes(path.node.callee.name)) return; createRouteFn = path.node.callee.name; function babelHandleReference(routeOptions) { const hasImportedOrDefinedIdentifier = (name) => { return programPath.scope.hasBinding(name); }; if (_babel_types.isObjectExpression(routeOptions)) { if (opts.deleteNodes && opts.deleteNodes.size > 0) routeOptions.properties = routeOptions.properties.filter((prop) => { if (_babel_types.isObjectProperty(prop)) { if (_babel_types.isIdentifier(prop.key)) { if (opts.deleteNodes.has(prop.key.name)) { modified = true; return false; } } } return true; }); if (!splittableCreateRouteFns.includes(createRouteFn)) { const insertionPath = path.getStatementParent() ?? path; opts.compilerPlugins?.forEach((plugin) => { if ((plugin.onUnsplittableRoute?.({ programPath, callExpressionPath: path, insertionPath, routeOptions, createRouteFn, opts }))?.modified) modified = true; }); if (opts.addHmr && !hmrAdded) { programPath.pushContainer("body", require_route_hmr_statement.routeHmrStatement); modified = true; hmrAdded = true; } return programPath.stop(); } routeOptions.properties.forEach((prop) => { if (_babel_types.isObjectProperty(prop)) { if (_babel_types.isIdentifier(prop.key)) { const key = prop.key.name; const codeSplitGroupingByKey = findIndexForSplitNode(key); if (codeSplitGroupingByKey === -1) return; const codeSplitGroup = [...new Set(opts.codeSplitGroupings[codeSplitGroupingByKey])]; if (!SPLIT_NODES_CONFIG.has(key)) return; if (_babel_types.isBooleanLiteral(prop.value) || _babel_types.isNullLiteral(prop.value) || _babel_types.isIdentifier(prop.value) && prop.value.name === "undefined") return; const splitNodeMeta = SPLIT_NODES_CONFIG.get(key); const splitUrl = addSplitSearchParamToFilename(opts.filename, codeSplitGroup); if (splitNodeMeta.splitStrategy === "lazyRouteComponent") { const value = prop.value; let shouldSplit = true; if (_babel_types.isIdentifier(value)) { const existingImportPath = getImportSpecifierAndPathFromLocalName(programPath, value.name).path; if (existingImportPath) removableImportPaths.add(existingImportPath); const isExported = hasExport(ast, value); if (isExported) knownExportedIdents.add(value.name); shouldSplit = !isExported; if (shouldSplit) removeIdentifierLiteral(path, value); } if (!shouldSplit) return; modified = true; if (!hasImportedOrDefinedIdentifier(LAZY_ROUTE_COMPONENT_IDENT)) programPath.unshiftContainer("body", [_babel_template.statement(`import { ${LAZY_ROUTE_COMPONENT_IDENT} } from '${PACKAGE}'`)()]); if (!hasImportedOrDefinedIdentifier(splitNodeMeta.localImporterIdent)) programPath.unshiftContainer("body", [_babel_template.statement(`const ${splitNodeMeta.localImporterIdent} = () => import('${splitUrl}')`)()]); prop.value = _babel_template.expression(`${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`)(); if (opts.addHmr && !hmrAdded) { programPath.pushContainer("body", require_route_hmr_statement.routeHmrStatement); modified = true; hmrAdded = true; } } else { const value = prop.value; let shouldSplit = true; if (_babel_types.isIdentifier(value)) { const existingImportPath = getImportSpecifierAndPathFromLocalName(programPath, value.name).path; if (existingImportPath) removableImportPaths.add(existingImportPath); const isExported = hasExport(ast, value); if (isExported) knownExportedIdents.add(value.name); shouldSplit = !isExported; if (shouldSplit) removeIdentifierLiteral(path, value); } if (!shouldSplit) return; modified = true; if (!hasImportedOrDefinedIdentifier(LAZY_FN_IDENT)) programPath.unshiftContainer("body", _babel_template.smart(`import { ${LAZY_FN_IDENT} } from '${PACKAGE}'`)()); if (!hasImportedOrDefinedIdentifier(splitNodeMeta.localImporterIdent)) programPath.unshiftContainer("body", [_babel_template.statement(`const ${splitNodeMeta.localImporterIdent} = () => import('${splitUrl}')`)()]); prop.value = _babel_template.expression(`${LAZY_FN_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`)(); } } } programPath.scope.crawl(); }); } } if (_babel_types.isCallExpression(path.parentPath.node)) babelHandleReference(resolveIdentifier(path, path.parentPath.node.arguments[0])); else if (_babel_types.isVariableDeclarator(path.parentPath.node)) { const caller = resolveIdentifier(path, path.parentPath.node.init); if (_babel_types.isCallExpression(caller)) babelHandleReference(resolveIdentifier(path, caller.arguments[0])); } } }); /** * If the component for the route is being imported, * and it's not being used, remove the import statement * from the program, by checking that the import has no * specifiers */ if (removableImportPaths.size > 0) { modified = true; programPath.traverse({ ImportDeclaration(path) { if (path.node.specifiers.length > 0) return; if (removableImportPaths.has(path.node.source.value)) path.remove(); } }); } if (opts.sharedBindings && opts.sharedBindings.size > 0) { sharedExportedNames = findExportedSharedBindings(ast, opts.sharedBindings); removeSharedDeclarations(ast, opts.sharedBindings); const sharedModuleUrl = addSharedSearchParamToFilename(opts.filename); const sharedImportSpecifiers = [...opts.sharedBindings].map((name) => _babel_types.importSpecifier(_babel_types.identifier(name), _babel_types.identifier(name))); const [sharedImportPath] = programPath.unshiftContainer("body", _babel_types.importDeclaration(sharedImportSpecifiers, _babel_types.stringLiteral(sharedModuleUrl))); sharedImportPath.traverse({ Identifier(identPath) { if (identPath.parentPath.isImportSpecifier() && identPath.key === "local") refIdents.add(identPath); } }); if (sharedExportedNames.size > 0) { const reExportSpecifiers = [...sharedExportedNames].map((name) => _babel_types.exportSpecifier(_babel_types.identifier(name), _babel_types.identifier(name))); programPath.pushContainer("body", _babel_types.exportNamedDeclaration(null, reExportSpecifiers, _babel_types.stringLiteral(sharedModuleUrl))); } } } } }); if (!modified) return null; (0, _tanstack_router_utils.deadCodeElimination)(ast, refIdents); if (knownExportedIdents.size > 0) { const warningMessage = createNotExportableMessage(opts.filename, knownExportedIdents); console.warn(warningMessage); if (process.env.NODE_ENV !== "production") { const warningTemplate = _babel_template.statement(`console.warn(${JSON.stringify(warningMessage)})`)(); ast.program.body.unshift(warningTemplate); } } const result = (0, _tanstack_router_utils.generateFromAst)(ast, { sourceMaps: true, sourceFileName: opts.filename, filename: opts.filename }); if (result.map) result.map.sourcesContent = [opts.code]; return result; } function compileCodeSplitVirtualRoute(opts) { const ast = (0, _tanstack_router_utils.parseAst)(opts); const refIdents = (0, _tanstack_router_utils.findReferencedIdentifiers)(ast); if (opts.sharedBindings && opts.sharedBindings.size > 0) removeSharedDeclarations(ast, opts.sharedBindings); const intendedSplitNodes = new Set(opts.splitTargets); const knownExportedIdents = /* @__PURE__ */ new Set(); _babel_core.traverse(ast, { Program: { enter(programPath) { const trackedNodesToSplitByType = { component: void 0, loader: void 0, pendingComponent: void 0, errorComponent: void 0, notFoundComponent: void 0 }; programPath.traverse({ CallExpression: (path) => { if (!_babel_types.isIdentifier(path.node.callee)) return; if (!splittableCreateRouteFns.includes(path.node.callee.name)) return; function babelHandleVirtual(options) { if (_babel_types.isObjectExpression(options)) { options.properties.forEach((prop) => { if (_babel_types.isObjectProperty(prop)) KNOWN_SPLIT_ROUTE_IDENTS.forEach((splitType) => { if (!_babel_types.isIdentifier(prop.key) || prop.key.name !== splitType) return; const value = prop.value; if (_babel_types.isIdentifier(value) && value.name === "undefined") return; let isExported = false; if (_babel_types.isIdentifier(value)) { isExported = hasExport(ast, value); if (isExported) knownExportedIdents.add(value.name); } if (isExported && _babel_types.isIdentifier(value)) removeExports(ast, value); else { const meta = SPLIT_NODES_CONFIG.get(splitType); trackedNodesToSplitByType[splitType] = { node: prop.value, meta }; } }); }); options.properties = []; } } if (_babel_types.isCallExpression(path.parentPath.node)) babelHandleVirtual(resolveIdentifier(path, path.parentPath.node.arguments[0])); else if (_babel_types.isVariableDeclarator(path.parentPath.node)) { const caller = resolveIdentifier(path, path.parentPath.node.init); if (_babel_types.isCallExpression(caller)) babelHandleVirtual(resolveIdentifier(path, caller.arguments[0])); } } }); intendedSplitNodes.forEach((SPLIT_TYPE) => { const splitKey = trackedNodesToSplitByType[SPLIT_TYPE]; if (!splitKey) return; let splitNode = splitKey.node; const splitMeta = { ...splitKey.meta, shouldRemoveNode: true }; let originalIdentName; if (_babel_types.isIdentifier(splitNode)) originalIdentName = splitNode.name; while (_babel_types.isIdentifier(splitNode)) splitNode = programPath.scope.getBinding(splitNode.name)?.path.node; if (splitNode) if (_babel_types.isFunctionDeclaration(splitNode)) { if (!splitNode.id) throw new Error(`Function declaration for "${SPLIT_TYPE}" must have an identifier.`); splitMeta.shouldRemoveNode = false; splitMeta.localExporterIdent = splitNode.id.name; } else if (_babel_types.isFunctionExpression(splitNode) || _babel_types.isArrowFunctionExpression(splitNode)) programPath.pushContainer("body", _babel_types.variableDeclaration("const", [_babel_types.variableDeclarator(_babel_types.identifier(splitMeta.localExporterIdent), splitNode)])); else if (_babel_types.isImportSpecifier(splitNode) || _babel_types.isImportDefaultSpecifier(splitNode)) programPath.pushContainer("body", _babel_types.variableDeclaration("const", [_babel_types.variableDeclarator(_babel_types.identifier(splitMeta.localExporterIdent), splitNode.local)])); else if (_babel_types.isVariableDeclarator(splitNode)) if (_babel_types.isIdentifier(splitNode.id)) { splitMeta.localExporterIdent = splitNode.id.name; splitMeta.shouldRemoveNode = false; } else if (_babel_types.isObjectPattern(splitNode.id)) { if (originalIdentName) splitMeta.localExporterIdent = originalIdentName; splitMeta.shouldRemoveNode = false; } else throw new Error(`Unexpected splitNode type ☝️: ${splitNode.type}`); else if (_babel_types.isCallExpression(splitNode)) { const outputSplitNodeCode = (0, _tanstack_router_utils.generateFromAst)(splitNode).code; const splitNodeAst = _babel_core.parse(outputSplitNodeCode); if (!splitNodeAst) throw new Error(`Failed to parse the generated code for "${SPLIT_TYPE}" in the node type "${splitNode.type}"`); const statement = splitNodeAst.program.body[0]; if (!statement) throw new Error(`Failed to parse the generated code for "${SPLIT_TYPE}" in the node type "${splitNode.type}" as no statement was found in the program body`); if (_babel_types.isExpressionStatement(statement)) { const expression = statement.expression; programPath.pushContainer("body", _babel_types.variableDeclaration("const", [_babel_types.variableDeclarator(_babel_types.identifier(splitMeta.localExporterIdent), expression)])); } else throw new Error(`Unexpected expression type encounter for "${SPLIT_TYPE}" in the node type "${splitNode.type}"`); } else if (_babel_types.isConditionalExpression(splitNode)) programPath.pushContainer("body", _babel_types.variableDeclaration("const", [_babel_types.variableDeclarator(_babel_types.identifier(splitMeta.localExporterIdent), splitNode)])); else if (_babel_types.isTSAsExpression(splitNode)) { splitNode = splitNode.expression; programPath.pushContainer("body", _babel_types.variableDeclaration("const", [_babel_types.variableDeclarator(_babel_types.identifier(splitMeta.localExporterIdent), splitNode)])); } else if (_babel_types.isBooleanLiteral(splitNode)) return; else if (_babel_types.isNullLiteral(splitNode)) return; else { console.info("Unexpected splitNode type:", splitNode); throw new Error(`Unexpected splitNode type ☝️: ${splitNode.type}`); } if (splitMeta.shouldRemoveNode) programPath.node.body = programPath.node.body.filter((node) => { return node !== splitNode; }); programPath.pushContainer("body", [_babel_types.exportNamedDeclaration(null, [_babel_types.exportSpecifier(_babel_types.identifier(splitMeta.localExporterIdent), _babel_types.identifier(splitMeta.exporterIdent))])]); }); programPath.traverse({ ExportNamedDeclaration(path) { if (path.node.declaration) { if (_babel_types.isVariableDeclaration(path.node.declaration)) { const specifiers = path.node.declaration.declarations.flatMap((decl) => { if (_babel_types.isIdentifier(decl.id)) return [_babel_types.importSpecifier(_babel_types.identifier(decl.id.name), _babel_types.identifier(decl.id.name))]; if (_babel_types.isObjectPattern(decl.id)) return collectIdentifiersFromPattern(decl.id).map((name) => _babel_types.importSpecifier(_babel_types.identifier(name), _babel_types.identifier(name))); if (_babel_types.isArrayPattern(decl.id)) return collectIdentifiersFromPattern(decl.id).map((name) => _babel_types.importSpecifier(_babel_types.identifier(name), _babel_types.identifier(name))); return []; }); if (specifiers.length === 0) { path.remove(); return; } const importDecl = _babel_types.importDeclaration(specifiers, _babel_types.stringLiteral(removeSplitSearchParamFromFilename(opts.filename))); path.replaceWith(importDecl); path.traverse({ Identifier(identPath) { if (identPath.parentPath.isImportSpecifier() && identPath.key === "local") refIdents.add(identPath); } }); } } } }); if (opts.sharedBindings && opts.sharedBindings.size > 0) { const sharedImportSpecifiers = [...opts.sharedBindings].map((name) => _babel_types.importSpecifier(_babel_types.identifier(name), _babel_types.identifier(name))); const sharedModuleUrl = addSharedSearchParamToFilename(removeSplitSearchParamFromFilename(opts.filename)); const [sharedImportPath] = programPath.unshiftContainer("body", _babel_types.importDeclaration(sharedImportSpecifiers, _babel_types.stringLiteral(sharedModuleUrl))); sharedImportPath.traverse({ Identifier(identPath) { if (identPath.parentPath.isImportSpecifier() && identPath.key === "local") refIdents.add(identPath); } }); } } } }); (0, _tanstack_router_utils.deadCodeElimination)(ast, refIdents); { const locallyBound = /* @__PURE__ */ new Set(); for (const stmt of ast.program.body) collectLocalBindingsFromStatement(stmt, locallyBound); ast.program.body = ast.program.body.filter((stmt) => { if (!_babel_types.isExpressionStatement(stmt)) return true; return [...collectIdentifiersFromNode(stmt)].some((name) => locallyBound.has(name)); }); } if (ast.program.body.length === 0) ast.program.directives = []; const result = (0, _tanstack_router_utils.generateFromAst)(ast, { sourceMaps: true, sourceFileName: opts.filename, filename: opts.filename }); if (result.map) result.map.sourcesContent = [opts.code]; return result; } /** * Compile the shared virtual module (`?tsr-shared=1`). * Keeps only shared binding declarations, their transitive dependencies, * and imports they need. Exports all shared bindings. */ function compileCodeSplitSharedRoute(opts) { const ast = (0, _tanstack_router_utils.parseAst)(opts); const refIdents = (0, _tanstack_router_utils.findReferencedIdentifiers)(ast); const localBindings = /* @__PURE__ */ new Set(); for (const node of ast.program.body) collectLocalBindingsFromStatement(node, localBindings); localBindings.delete("Route"); const depGraph = buildDependencyGraph(buildDeclarationMap(ast), localBindings); const keepBindings = new Set(opts.sharedBindings); keepBindings.delete("Route"); expandTransitively(keepBindings, depGraph); ast.program.body = ast.program.body.filter((stmt) => { if (_babel_types.isImportDeclaration(stmt)) return true; const decl = _babel_types.isExportNamedDeclaration(stmt) && stmt.declaration ? stmt.declaration : stmt; if (_babel_types.isVariableDeclaration(decl)) { decl.declarations = decl.declarations.filter((declarator) => { return collectIdentifiersFromPattern(declarator.id).some((n) => keepBindings.has(n)); }); if (decl.declarations.length === 0) return false; if (_babel_types.isExportNamedDeclaration(stmt) && stmt.declaration) return true; return true; } else if (_babel_types.isFunctionDeclaration(decl) && decl.id) return keepBindings.has(decl.id.name); else if (_babel_types.isClassDeclaration(decl) && decl.id) return keepBindings.has(decl.id.name); return false; }); ast.program.body = ast.program.body.map((stmt) => { if (_babel_types.isExportNamedDeclaration(stmt) && stmt.declaration) return stmt.declaration; return stmt; }); const exportSpecifiers = [...opts.sharedBindings].sort((a, b) => a.localeCompare(b)).map((name) => _babel_types.exportSpecifier(_babel_types.identifier(name), _babel_types.identifier(name))); if (exportSpecifiers.length > 0) { const exportDecl = _babel_types.exportNamedDeclaration(null, exportSpecifiers); ast.program.body.push(exportDecl); _babel_core.traverse(ast, { Program(programPath) { const bodyPaths = programPath.get("body"); const last = bodyPaths[bodyPaths.length - 1]; if (last && last.isExportNamedDeclaration()) last.traverse({ Identifier(identPath) { if (identPath.parentPath.isExportSpecifier() && identPath.key === "local") refIdents.add(identPath); } }); programPath.stop(); } }); } (0, _tanstack_router_utils.deadCodeElimination)(ast, refIdents); if (ast.program.body.length === 0) ast.program.directives = []; const result = (0, _tanstack_router_utils.generateFromAst)(ast, { sourceMaps: true, sourceFileName: opts.filename, filename: opts.filename }); if (result.map) result.map.sourcesContent = [opts.code]; return result; } /** * This function should read get the options from by searching for the key `codeSplitGroupings` * on createFileRoute and return it's values if it exists, else return undefined */ function detectCodeSplitGroupingsFromRoute(opts) { const ast = (0, _tanstack_router_utils.parseAst)(opts); let codeSplitGroupings = void 0; _babel_core.traverse(ast, { Program: { enter(programPath) { programPath.traverse({ CallExpression(path) { if (!_babel_types.isIdentifier(path.node.callee)) return; if (!(path.node.callee.name === "createRoute" || path.node.callee.name === "createFileRoute")) return; function babelHandleSplittingGroups(routeOptions) { if (_babel_types.isObjectExpression(routeOptions)) routeOptions.properties.forEach((prop) => { if (_babel_types.isObjectProperty(prop)) { if (_babel_types.isIdentifier(prop.key)) { if (prop.key.name === "codeSplitGroupings") { const value = prop.value; if (_babel_types.isArrayExpression(value)) codeSplitGroupings = value.elements.map((group) => { if (_babel_types.isArrayExpression(group)) return group.elements.map((node) => { if (!_babel_types.isStringLiteral(node)) throw new Error("You must provide a string literal for the codeSplitGroupings"); return node.value; }); throw new Error("You must provide arrays with codeSplitGroupings options."); }); else throw new Error("You must provide an array of arrays for the codeSplitGroupings."); } } } }); } if (_babel_types.isCallExpression(path.parentPath.node)) babelHandleSplittingGroups(resolveIdentifier(path, path.parentPath.node.arguments[0])); else if (_babel_types.isVariableDeclarator(path.parentPath.node)) { const caller = resolveIdentifier(path, path.parentPath.node.init); if (_babel_types.isCallExpression(caller)) babelHandleSplittingGroups(resolveIdentifier(path, caller.arguments[0])); } } }); } } }); return { groupings: codeSplitGroupings }; } function createNotExportableMessage(filename, idents) { const list = Array.from(idents).map((d) => `- ${d}`); return [ `[tanstack-router] These exports from "${filename}" will not be code-split and will increase your bundle size:`, ...list, "For the best optimization, these items should either have their export statements removed, or be imported from another location that is not a route file." ].join("\n"); } function getImportSpecifierAndPathFromLocalName(programPath, name) { let specifier = null; let path = null; programPath.traverse({ ImportDeclaration(importPath) { const found = importPath.node.specifiers.find((targetSpecifier) => targetSpecifier.local.name === name); if (found) { specifier = found; path = importPath.node.source.value; } } }); return { specifier, path }; } /** * Recursively collects all identifier names from a destructuring pattern * (ObjectPattern, ArrayPattern, AssignmentPattern, RestElement). */ function collectIdentifiersFromPattern(node) { if (!node) return []; if (_babel_types.isIdentifier(node)) return [node.name]; if (_babel_types.isAssignmentPattern(node)) return collectIdentifiersFromPattern(node.left); if (_babel_types.isRestElement(node)) return collectIdentifiersFromPattern(node.argument); if (_babel_types.isObjectPattern(node)) return node.properties.flatMap((prop) => { if (_babel_types.isObjectProperty(prop)) return collectIdentifiersFromPattern(prop.value); if (_babel_types.isRestElement(prop)) return collectIdentifiersFromPattern(prop.argument); return []; }); if (_babel_types.isArrayPattern(node)) return node.elements.flatMap((element) => collectIdentifiersFromPattern(element)); return []; } function resolveIdentifier(path, node) { if (_babel_types.isIdentifier(node)) { const binding = path.scope.getBinding(node.name); if (binding) { const declarator = binding.path.node; if (_babel_types.isObjectExpression(declarator.init)) return declarator.init; else if (_babel_types.isFunctionDeclaration(declarator.init)) return declarator.init; } return; } return node; } function removeIdentifierLiteral(path, node) { const binding = path.scope.getBinding(node.name); if (binding) { if (_babel_types.isVariableDeclarator(binding.path.node) && _babel_types.isObjectPattern(binding.path.node.id)) { const objectPattern = binding.path.node.id; objectPattern.properties = objectPattern.properties.filter((prop) => { if (!_babel_types.isObjectProperty(prop)) return true; if (_babel_types.isIdentifier(prop.value) && prop.value.name === node.name) return false; if (_babel_types.isAssignmentPattern(prop.value) && _babel_types.isIdentifier(prop.value.left) && prop.value.left.name === node.name) return false; return true; }); if (objectPattern.properties.length === 0) binding.path.remove(); return; } binding.path.remove(); } } function hasExport(ast, node) { let found = false; _babel_core.traverse(ast, { ExportNamedDeclaration(path) { if (path.node.declaration) { if (_babel_types.isVariableDeclaration(path.node.declaration)) path.node.declaration.declarations.forEach((decl) => { if (_babel_types.isVariableDeclarator(decl)) { if (_babel_types.isIdentifier(decl.id)) { if (decl.id.name === node.name) found = true; } else if (_babel_types.isObjectPattern(decl.id) || _babel_types.isArrayPattern(decl.id)) { if (collectIdentifiersFromPattern(decl.id).includes(node.name)) found = true; } } }); if (_babel_types.isFunctionDeclaration(path.node.declaration)) { if (_babel_types.isIdentifier(path.node.declaration.id)) { if (path.node.declaration.id.name === node.name) found = true; } } } }, ExportDefaultDeclaration(path) { if (_babel_types.isIdentifier(path.node.declaration)) { if (path.node.declaration.name === node.name) found = true; } if (_babel_types.isFunctionDeclaration(path.node.declaration)) { if (_babel_types.isIdentifier(path.node.declaration.id)) { if (path.node.declaration.id.name === node.name) found = true; } } } }); return found; } function removeExports(ast, node) { let removed = false; _babel_core.traverse(ast, { ExportNamedDeclaration(path) { if (path.node.declaration) { if (_babel_types.isVariableDeclaration(path.node.declaration)) path.node.declaration.declarations.forEach((decl) => { if (_babel_types.isVariableDeclarator(decl)) { if (_babel_types.isIdentifier(decl.id)) { if (decl.id.name === node.name) { path.remove(); removed = true; } } else if (_babel_types.isObjectPattern(decl.id) || _babel_types.isArrayPattern(decl.id)) { if (collectIdentifiersFromPattern(decl.id).includes(node.name)) { path.remove(); removed = true; } } } }); else if (_babel_types.isFunctionDeclaration(path.node.declaration)) { if (_babel_types.isIdentifier(path.node.declaration.id)) { if (path.node.declaration.id.name === node.name) { path.remove(); removed = true; } } } } }, ExportDefaultDeclaration(path) { if (_babel_types.isIdentifier(path.node.declaration)) { if (path.node.declaration.name === node.name) { path.remove(); removed = true; } } else if (_babel_types.isFunctionDeclaration(path.node.declaration)) { if (_babel_types.isIdentifier(path.node.declaration.id)) { if (path.node.declaration.id.name === node.name) { path.remove(); removed = true; } } } } }); return removed; } //#endregion exports.compileCodeSplitReferenceRoute = compileCodeSplitReferenceRoute; exports.compileCodeSplitSharedRoute = compileCodeSplitSharedRoute; exports.compileCodeSplitVirtualRoute = compileCodeSplitVirtualRoute; exports.computeSharedBindings = computeSharedBindings; exports.detectCodeSplitGroupingsFromRoute = detectCodeSplitGroupingsFromRoute; //# sourceMappingURL=compilers.cjs.map