@tanstack/router-plugin
Version:
Modern and scalable routing for React applications
238 lines (237 loc) • 9.19 kB
JavaScript
import { getConfig, splitGroupingsSchema } from "./config.js";
import { defaultCodeSplitGroupings, splitRouteIdentNodes, tsrShared, tsrSplit } from "./constants.js";
import { decodeIdentifier } from "./code-splitter/path-ids.js";
import { compileCodeSplitReferenceRoute, compileCodeSplitSharedRoute, compileCodeSplitVirtualRoute, computeSharedBindings, detectCodeSplitGroupingsFromRoute } from "./code-splitter/compilers.js";
import { debug, normalizePath } from "./utils.js";
import { getReferenceRouteCompilerPlugins } from "./code-splitter/plugins/framework-plugins.js";
import { fileURLToPath, pathToFileURL } from "node:url";
import { logDiff } from "@tanstack/router-utils";
//#region src/core/router-code-splitter-plugin.ts
/**
* It is important to familiarize yourself with how the code-splitting works in this plugin.
* https://github.com/TanStack/router/pull/3355
*/
var PLUGIN_NAME = "unplugin:router-code-splitter";
var CODE_SPLITTER_PLUGIN_NAME = "tanstack-router:code-splitter:compile-reference-file";
/**
* JSX transformation plugins grouped by framework.
* These plugins must come AFTER the TanStack Router plugin in the Vite config.
*/
var TRANSFORMATION_PLUGINS_BY_FRAMEWORK = {
react: [
{
pluginNames: ["vite:react-babel", "vite:react-refresh"],
pkg: "@vitejs/plugin-react",
usage: "react()"
},
{
pluginNames: ["vite:react-swc", "vite:react-swc:resolve-runtime"],
pkg: "@vitejs/plugin-react-swc",
usage: "reactSwc()"
},
{
pluginNames: ["vite:react-oxc:config", "vite:react-oxc:refresh-runtime"],
pkg: "@vitejs/plugin-react-oxc",
usage: "reactOxc()"
}
],
solid: [{
pluginNames: ["solid"],
pkg: "vite-plugin-solid",
usage: "solid()"
}]
};
var unpluginRouterCodeSplitterFactory = (options = {}, { framework: _framework }) => {
let ROOT = process.cwd();
let userConfig;
function initUserConfig() {
if (typeof options === "function") userConfig = options();
else userConfig = getConfig(options, ROOT);
}
const isProduction = process.env.NODE_ENV === "production";
const sharedBindingsMap = /* @__PURE__ */ new Map();
const getGlobalCodeSplitGroupings = () => {
return userConfig.codeSplittingOptions?.defaultBehavior || defaultCodeSplitGroupings;
};
const getShouldSplitFn = () => {
return userConfig.codeSplittingOptions?.splitBehavior;
};
const handleCompilingReferenceFile = (code, id, generatorNodeInfo) => {
if (debug) console.info("Compiling Route: ", id);
const fromCode = detectCodeSplitGroupingsFromRoute({ code });
if (fromCode.groupings) {
const res = splitGroupingsSchema.safeParse(fromCode.groupings);
if (!res.success) {
const message = res.error.errors.map((e) => e.message).join(". ");
throw new Error(`The groupings for the route "${id}" are invalid.\n${message}`);
}
}
const pluginSplitBehavior = getShouldSplitFn()?.({ routeId: generatorNodeInfo.routePath });
if (pluginSplitBehavior) {
const res = splitGroupingsSchema.safeParse(pluginSplitBehavior);
if (!res.success) {
const message = res.error.errors.map((e) => e.message).join(". ");
throw new Error(`The groupings returned when using \`splitBehavior\` for the route "${id}" are invalid.\n${message}`);
}
}
const splitGroupings = fromCode.groupings || pluginSplitBehavior || getGlobalCodeSplitGroupings();
const sharedBindings = computeSharedBindings({
code,
codeSplitGroupings: splitGroupings
});
if (sharedBindings.size > 0) sharedBindingsMap.set(id, sharedBindings);
else sharedBindingsMap.delete(id);
const addHmr = (userConfig.codeSplittingOptions?.addHmr ?? true) && !isProduction;
const compiledReferenceRoute = compileCodeSplitReferenceRoute({
code,
codeSplitGroupings: splitGroupings,
targetFramework: userConfig.target,
filename: id,
id,
deleteNodes: userConfig.codeSplittingOptions?.deleteNodes ? new Set(userConfig.codeSplittingOptions.deleteNodes) : void 0,
addHmr,
sharedBindings: sharedBindings.size > 0 ? sharedBindings : void 0,
compilerPlugins: getReferenceRouteCompilerPlugins({
targetFramework: userConfig.target,
addHmr
})
});
if (compiledReferenceRoute === null) {
if (debug) console.info(`No changes made to route "${id}", skipping code-splitting.`);
return null;
}
if (debug) {
logDiff(code, compiledReferenceRoute.code);
console.log("Output:\n", compiledReferenceRoute.code + "\n\n");
}
return compiledReferenceRoute;
};
const handleCompilingVirtualFile = (code, id) => {
if (debug) console.info("Splitting Route: ", id);
const [_, ...pathnameParts] = id.split("?");
const splitValue = new URLSearchParams(pathnameParts.join("?")).get(tsrSplit);
if (!splitValue) throw new Error(`The split value for the virtual route "${id}" was not found.`);
const rawGrouping = decodeIdentifier(splitValue);
const grouping = [...new Set(rawGrouping)].filter((p) => splitRouteIdentNodes.includes(p));
const baseId = id.split("?")[0];
const result = compileCodeSplitVirtualRoute({
code,
filename: id,
splitTargets: grouping,
sharedBindings: sharedBindingsMap.get(baseId)
});
if (debug) {
logDiff(code, result.code);
console.log("Output:\n", result.code + "\n\n");
}
return result;
};
const includedCode = [
"createFileRoute(",
"createRootRoute(",
"createRootRouteWithContext("
];
return [
{
name: "tanstack-router:code-splitter:compile-reference-file",
enforce: "pre",
transform: {
filter: {
id: {
exclude: [tsrSplit, tsrShared],
include: /\.(m|c)?(j|t)sx?$/
},
code: { include: includedCode }
},
handler(code, id) {
const normalizedId = normalizePath(id);
const generatorFileInfo = globalThis.TSR_ROUTES_BY_ID_MAP?.get(normalizedId);
if (generatorFileInfo && includedCode.some((included) => code.includes(included))) return handleCompilingReferenceFile(code, normalizedId, generatorFileInfo);
return null;
}
},
vite: {
configResolved(config) {
ROOT = config.root;
initUserConfig();
const routerPluginIndex = config.plugins.findIndex((p) => p.name === CODE_SPLITTER_PLUGIN_NAME);
if (routerPluginIndex === -1) return;
const frameworkPlugins = TRANSFORMATION_PLUGINS_BY_FRAMEWORK[userConfig.target];
if (!frameworkPlugins) return;
for (const transformPlugin of frameworkPlugins) {
const transformPluginIndex = config.plugins.findIndex((p) => transformPlugin.pluginNames.includes(p.name));
if (transformPluginIndex !== -1 && transformPluginIndex < routerPluginIndex) throw new Error(`Plugin order error: '${transformPlugin.pkg}' is placed before '@tanstack/router-plugin'.\n\nThe TanStack Router plugin must come BEFORE JSX transformation plugins.\n\nPlease update your Vite config:\n\n plugins: [\n tanstackRouter(),\n ${transformPlugin.usage},\n ]\n`);
}
},
applyToEnvironment(environment) {
if (userConfig.plugin?.vite?.environmentName) return userConfig.plugin.vite.environmentName === environment.name;
return true;
}
},
rspack(compiler) {
ROOT = process.cwd();
initUserConfig();
if (compiler.options.mode === "production") compiler.hooks.done.tap(PLUGIN_NAME, () => {
console.info("✅ " + PLUGIN_NAME + ": code-splitting done!");
});
},
webpack(compiler) {
ROOT = process.cwd();
initUserConfig();
if (compiler.options.mode === "production") compiler.hooks.done.tap(PLUGIN_NAME, () => {
console.info("✅ " + PLUGIN_NAME + ": code-splitting done!");
});
}
},
{
name: "tanstack-router:code-splitter:compile-virtual-file",
enforce: "pre",
transform: {
filter: { id: /tsr-split/ },
handler(code, id) {
const url = pathToFileURL(id);
url.searchParams.delete("v");
return handleCompilingVirtualFile(code, normalizePath(fileURLToPath(url)));
}
},
vite: { applyToEnvironment(environment) {
if (userConfig.plugin?.vite?.environmentName) return userConfig.plugin.vite.environmentName === environment.name;
return true;
} }
},
{
name: "tanstack-router:code-splitter:compile-shared-file",
enforce: "pre",
transform: {
filter: { id: /tsr-shared/ },
handler(code, id) {
const url = pathToFileURL(id);
url.searchParams.delete("v");
const normalizedId = normalizePath(fileURLToPath(url));
const [baseId] = normalizedId.split("?");
if (!baseId) return null;
const sharedBindings = sharedBindingsMap.get(baseId);
if (!sharedBindings || sharedBindings.size === 0) return null;
if (debug) console.info("Compiling Shared Module: ", id);
const result = compileCodeSplitSharedRoute({
code,
sharedBindings,
filename: normalizedId
});
if (debug) {
logDiff(code, result.code);
console.log("Output:\n", result.code + "\n\n");
}
return result;
}
},
vite: { applyToEnvironment(environment) {
if (userConfig.plugin?.vite?.environmentName) return userConfig.plugin.vite.environmentName === environment.name;
return true;
} }
}
];
};
//#endregion
export { unpluginRouterCodeSplitterFactory };
//# sourceMappingURL=router-code-splitter-plugin.js.map