one
Version:
One is a new React Framework that makes Vite serve both native and web.
154 lines (153 loc) • 5.38 kB
JavaScript
import { extname, relative } from "node:path";
import BabelGenerate from "@babel/generator";
import { parse } from "@babel/parser";
import BabelTraverse from "@babel/traverse";
import { deadCodeElimination, findReferencedIdentifiers } from "babel-dead-code-elimination";
import { EMPTY_LOADER_STRING, makeLoaderRouteIdStub } from "../constants.mjs";
const traverse = BabelTraverse["default"] || BabelTraverse;
const generate = BabelGenerate["default"] || BabelGenerate;
function collectTypeImports(ast) {
const typeImports = [];
traverse(ast, {
ImportDeclaration(path) {
if (path.node.importKind === "type") {
typeImports.push(path.node);
}
}
});
return typeImports;
}
function restoreTypeImports(ast, typeImports) {
if (typeImports.length === 0) return;
const existingSources = /* @__PURE__ */new Set();
traverse(ast, {
ImportDeclaration(path) {
if (path.node.importKind === "type") {
existingSources.add(path.node.source.value);
}
}
});
for (const typeImport of typeImports) {
if (!existingSources.has(typeImport.source.value)) {
ast.program.body.unshift(typeImport);
}
}
}
const clientTreeShakePlugin = opts => {
const runtime = opts?.runtime ?? "vite";
return {
name: "one-client-tree-shake",
enforce: "pre",
...(runtime === "vite" && {
applyToEnvironment(env) {
return env.name === "client" || env.name === "ios" || env.name === "android";
}
}),
transform: {
order: "pre",
async handler(code, id, settings) {
if (runtime === "vite" && this.environment?.name === "ssr") {
return;
}
if (!/\.(js|jsx|ts|tsx)/.test(extname(id))) {
return;
}
if (/node_modules/.test(id)) {
return;
}
const out = await transformTreeShakeClient(code, id, process.cwd());
return out;
}
}
};
};
async function transformTreeShakeClient(code, id, root) {
if (!/generateStaticParams|loader/.test(code)) {
return;
}
let ast;
try {
ast = parse(code, {
sourceType: "module",
plugins: ["typescript", "jsx"]
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`[one] Skipping tree shaking for ${id} due to syntax error:`, errorMessage);
return;
}
let referenced;
try {
referenced = findReferencedIdentifiers(ast);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`[one] Skipping tree shaking for ${id} due to identifier analysis error:`, errorMessage);
return;
}
const removed = {
loader: false,
generateStaticParams: false
};
try {
traverse(ast, {
ExportNamedDeclaration(path) {
if (path.node.declaration && path.node.declaration.type === "FunctionDeclaration") {
if (!path.node.declaration.id) return;
const functionName = path.node.declaration.id.name;
if (functionName === "loader" || functionName === "generateStaticParams") {
path.remove();
removed[functionName] = true;
}
} else if (path.node.declaration && path.node.declaration.type === "VariableDeclaration") {
path.node.declaration.declarations.forEach((declarator, index) => {
if (declarator.id.type === "Identifier" && (declarator.id.name === "loader" || declarator.id.name === "generateStaticParams")) {
const declaration = path.get("declaration.declarations." + index);
if (!Array.isArray(declaration) && declaration) {
;
declaration.remove();
removed[declarator.id.name] = true;
}
}
});
}
}
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`[one] Skipping tree shaking for ${id} due to traversal error:`, errorMessage);
return;
}
const removedFunctions = Object.keys(removed).filter(key => removed[key]);
if (removedFunctions.length) {
try {
const typeImports = collectTypeImports(ast);
deadCodeElimination(ast, referenced);
restoreTypeImports(ast, typeImports);
const out = generate(ast, {
retainLines: true
});
const codeOut = out.code + "\n\n" + removedFunctions.map(key => {
if (key === "loader") {
if (root) {
const fromRoot = relative(root, id).replace(/\\/g, "/");
const routeId = "./" + fromRoot.replace(/^app\//, "");
return makeLoaderRouteIdStub(routeId);
}
return EMPTY_LOADER_STRING;
}
return `export function generateStaticParams() {};`;
}).join("\n");
console.info(` \u{1F9F9} [one] ${relative(process.cwd(), id)} removed ${removedFunctions.length} server-only exports`);
return {
code: codeOut,
map: out.map
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`[one] Skipping tree shaking for ${id} due to code generation error:`, errorMessage);
return;
}
}
}
export { clientTreeShakePlugin, transformTreeShakeClient };
//# sourceMappingURL=clientTreeShakePlugin.mjs.map