Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
pcattori committed Jun 4, 2024
1 parent f29d42a commit a02bf93
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 36 deletions.
149 changes: 113 additions & 36 deletions packages/remix-dev/vite/define-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,108 @@ const MACRO = "defineRoute$";
const MACRO_PKG = "react-router";
const SERVER_ONLY_PROPERTIES = ["loader", "action"];

type Fields = {
params?: NodePath<t.ObjectProperty>;
loader?: NodePath<t.ObjectProperty | t.ObjectMethod>;
clientLoader?: NodePath<t.ObjectProperty | t.ObjectMethod>;
action?: NodePath<t.ObjectProperty | t.ObjectMethod>;
clientAction?: NodePath<t.ObjectProperty | t.ObjectMethod>;
Component?: NodePath<t.ObjectProperty | t.ObjectMethod>;
ErrorBoundary?: NodePath<t.ObjectProperty | t.ObjectMethod>;
};

function analyzeRoute(path: NodePath<t.CallExpression>) {
if (path.node.arguments.length !== 1) {
throw path.buildCodeFrameError(`macro must take exactly one argument`);
}
let arg = path.node.arguments[0];
let argPath = path.get("arguments.0") as NodePath<t.Node>;
if (!t.isObjectExpression(arg)) {
throw argPath.buildCodeFrameError(
"macro argument must be a literal object"
);
}

let fields: Fields = {};
for (let [i, property] of arg.properties.entries()) {
if (!t.isObjectProperty(property) && !t.isObjectMethod(property)) {
let propertyPath = argPath.get(`properties.${i}`) as NodePath<t.Node>;
throw propertyPath.buildCodeFrameError(
"macro argument must only have statically analyzable properties"
);
}
let propertyPath = argPath.get(`properties.${i}`) as NodePath<
t.ObjectProperty | t.ObjectMethod
>;
if (property.computed || !t.isIdentifier(property.key)) {
throw propertyPath.buildCodeFrameError(
"macro argument must only have statically analyzable fields"
);
}

let key = property.key.name;
if (key === "params") {
let paramsPath = propertyPath as NodePath<t.ObjectProperty>;
checkRouteParams(paramsPath);
fields["params"] = paramsPath;
continue;
}

if (
key === "loader" ||
key === "clientLoader" ||
key === "action" ||
key === "clientAction" ||
key === "Component" ||
key === "ErrorBoundary"
) {
fields[key] = propertyPath;
}
}
return fields;
}

// analyze route module:
// - using defineRoute$ ?
// - throw if macro not used correctly (validation)
// - return PATHS to the fields (params, loader, clientLoader, action, clientAction, ErrorBoundary, Component)
//
export function getRouteHasFields(source: string) {
let ast = parse(source, { sourceType: "module" });
let foundMacro = false;
let metadata = {
hasLoader: false,
hasClientAction: false,
hasAction: false,
hasClientLoader: false,
hasErrorBoundary: false,
};
traverse(ast, {
CallExpression(path) {
// detect macro BEGIN
if (!t.isIdentifier(path.node.callee)) return;
let macro = path.node.callee.name;
let binding = path.scope.getBinding(macro);

if (!binding) return;
if (!isMacroBinding(binding)) return;
// detect macro END

foundMacro = true;
let fields = analyzeRoute(path);
metadata = {
hasLoader: fields.loader !== undefined,
hasClientLoader: fields.clientLoader !== undefined,
hasAction: fields.action !== undefined,
hasClientAction: fields.clientAction !== undefined,
hasErrorBoundary: fields.ErrorBoundary !== undefined,
};
},
});
if (!foundMacro) return null;
return metadata;
}

export function transform(
source: string,
id: string,
Expand All @@ -31,48 +133,23 @@ export function transform(
);
},
CallExpression(path) {
if (!t.isIdentifier(path.node.callee)) return false;
if (!t.isIdentifier(path.node.callee)) return;
let macro = path.node.callee.name;
let binding = path.scope.getBinding(macro);

if (!binding) return false;
if (!isMacroBinding(binding)) return false;
if (path.node.arguments.length !== 1) {
throw path.buildCodeFrameError(
`'${macro}' macro must take exactly one argument`
);
}
let arg = path.node.arguments[0];
let argPath = path.get("arguments.0") as NodePath<t.Node>;
if (!t.isObjectExpression(arg)) {
throw argPath.buildCodeFrameError(
`'${macro}' macro argument must be a literal object`
);
}

let markedForRemoval: NodePath<t.Node>[] = [];
for (let [i, property] of arg.properties.entries()) {
let propertyPath = argPath.get(`properties.${i}`) as NodePath<t.Node>;
if (!t.isObjectProperty(property) && !t.isObjectMethod(property)) {
throw propertyPath.buildCodeFrameError(
`'${macro}' macro argument must only have statically analyzable properties`
);
}
if (property.computed || !t.isIdentifier(property.key)) {
throw propertyPath.buildCodeFrameError(
`'${macro}' macro argument must only have statically analyzable fields`
);
}

if (property.key.name === "params") {
checkRouteParams(propertyPath as NodePath<t.ObjectProperty>);
}
if (!binding) return;
if (!isMacroBinding(binding)) return;

if (options.ssr && SERVER_ONLY_PROPERTIES.includes(property.key.name)) {
markedForRemoval.push(propertyPath);
let fields = analyzeRoute(path);
if (options.ssr) {
let markedForRemoval: NodePath<t.Node>[] = [];
for (let [key, fieldPath] of Object.entries(fields)) {
if (SERVER_ONLY_PROPERTIES.includes(key)) {
markedForRemoval.push(fieldPath);
}
}
markedForRemoval.forEach((path) => path.remove());
}
markedForRemoval.forEach((path) => path.remove());
},
});
deadCodeElimination(ast, refs);
Expand Down
16 changes: 16 additions & 0 deletions packages/remix-dev/vite/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
resolveEntryFiles,
resolvePublicPath,
} from "../config";
import { getRouteHasFields } from "./define-route";

export async function resolveViteConfig({
configFile,
Expand Down Expand Up @@ -289,6 +290,8 @@ const getRouteManifestModuleExports = async (
): Promise<Record<string, string[]>> => {
let entries = await Promise.all(
Object.entries(ctx.reactRouterConfig.routes).map(async ([key, route]) => {
// do the old thing when `defineRoute$` isn't present
// otherwise use the new AST-based `has*` fields detector
let sourceExports = await getRouteModuleExports(
viteChildCompiler,
ctx,
Expand Down Expand Up @@ -665,6 +668,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
);

for (let [key, route] of Object.entries(ctx.reactRouterConfig.routes)) {
let metadata = await getDefineRouteFields(ctx, route);
let sourceExports = routeManifestExports[key];
routes[key] = {
id: route.id,
Expand Down Expand Up @@ -1877,3 +1881,15 @@ function createPrerenderRoutes(
};
});
}

// TODO precompute the define route fields for all routes in parallel
// for all routes in the route tree!
async function getDefineRouteFields(
ctx: ReactRouterPluginContext,
route: ConfigRoute,
readRouteFile?: () => string | Promise<string>
) {
let routePath = path.resolve(ctx.reactRouterConfig.appDirectory, route.file);
let routeSource = readRouteFile?.() ?? fse.readFile(routePath, "utf-8");
return getRouteHasFields(await routeSource);
}

0 comments on commit a02bf93

Please sign in to comment.