one
Version:
One is a new React Framework that makes Vite serve both native and web.
607 lines (604 loc) • 28.5 kB
JavaScript
import path from "path";
import { Readable } from "stream";
import { debounce } from "perfect-debounce";
import colors from "picocolors";
import { createServerModuleRunner } from "vite";
import { getSpaHeaderElements } from "../../constants.native.js";
import { createHandleRequest } from "../../createHandleRequest.native.js";
import { getPageExport } from "../../utils/getPageExport.native.js";
import { getRouterRootFromOneOptions } from "../../utils/getRouterRootFromOneOptions.native.js";
import { isResponse } from "../../utils/isResponse.native.js";
import { isStatusRedirect } from "../../utils/isStatus.native.js";
import { promiseWithResolvers } from "../../utils/promiseWithResolvers.native.js";
import { trackLoaderDependencies } from "../../utils/trackLoaderDependencies.native.js";
import { LoaderDataCache } from "../../vite/constants.native.js";
import { replaceLoader } from "../../vite/replaceLoader.native.js";
import { setServerContext } from "../one-server-only.native.js";
import { virtalEntryIdClient, virtualEntryId } from "./virtualEntryConstants.native.js";
function _instanceof(left, right) {
return right != null && typeof Symbol < "u" && right[Symbol.hasInstance] ? !!right[Symbol.hasInstance](left) : left instanceof right;
}
function _type_of(obj) {
"@swc/helpers - typeof";
return obj && typeof Symbol < "u" && obj.constructor === Symbol ? "symbol" : typeof obj;
}
var debugRouter = process.env.ONE_DEBUG_ROUTER,
debugLoaderDeps = process.env.ONE_DEBUG_LOADER_DEPS,
routeTypeColors = {
ssg: colors.green,
ssr: colors.blue,
spa: colors.yellow,
api: colors.magenta
},
USE_SERVER_ENV = !1;
//!!process.env.USE_SERVER_ENV
function createFileSystemRouterPlugin(options) {
var preloads = ["/@vite/client", virtalEntryIdClient],
runner,
server,
loaderFileDependencies = /* @__PURE__ */new Map(),
handleRequest = createRequestHandler(),
renderPromise = null;
function createRequestHandler() {
var routerRoot = getRouterRootFromOneOptions(options);
async function findNearestNotFoundPath(routeFile2) {
for (var routeDir2 = routeFile2.replace(/\/[^/]+$/, ""), searchDir2 = routeDir2;;) {
for (var _i2 = 0, _iter2 = [".tsx", ".ts", ".jsx", ".js"]; _i2 < _iter2.length; _i2++) {
var ext2 = _iter2[_i2],
candidate2 = path.join(routerRoot, searchDir2, `+not-found${ext2}`);
try {
var mod = await runner.import(candidate2);
if (mod?.default) return searchDir2 ? `/${searchDir2}/+not-found` : "/+not-found";
} catch {}
}
if (!searchDir2) break;
var parent2 = searchDir2.replace(/\/[^/]+$/, "");
parent2 === searchDir2 ? searchDir2 = "" : searchDir2 = parent2;
}
return "/+not-found";
}
return createHandleRequest({
async handlePage(param) {
var {
route,
url,
loaderProps
} = param,
_options_server;
if (((_options_server = options.server) === null || _options_server === void 0 ? void 0 : _options_server.loggingEnabled) !== !1) {
var colorType = routeTypeColors[route.type] || colors.white,
pathname = typeof url == "string" ? new URL(url).pathname : url.pathname,
file = route.isNotFound ? colors.red("404") : colors.dim(`app/${route.file.slice(2)}`);
console.info(` \u24F5 ${colorType(`[${route.type}]`)} ${pathname} ${colors.dim("\u2192")} ${file}`);
}
var layouts = route.layouts || [],
isSpaShell = route.type === "spa" && layouts.some(function (layout) {
return layout.layoutRenderMode === "ssg" || layout.layoutRenderMode === "ssr";
});
if (route.type === "spa" && !isSpaShell) return `<!DOCTYPE html><html><head>
${getSpaHeaderElements({
serverContext: {
mode: "spa"
}
})}
<script type="module" src="/@one/dev.js"></script>
<script type="module" src="/@vite/client" async=""></script>
<script type="module" src="/@id/__x00__virtual:one-entry" async=""></script>
</head></html>`;
renderPromise && (await renderPromise);
var {
promise,
resolve: resolveRender
} = promiseWithResolvers();
renderPromise = promise;
try {
var _globalThis___vxrnresetState,
_globalThis,
routeFile = path.join(routerRoot, route.file);
runner.clearCache(), (_globalThis___vxrnresetState = (_globalThis = globalThis).__vxrnresetState) === null || _globalThis___vxrnresetState === void 0 || _globalThis___vxrnresetState.call(_globalThis);
var exported = routeFile === "" ? {} : await runner.import(routeFile);
async function runLoaderWithTracking(routeNode, loaderFn) {
var routeId = routeNode.contextKey;
if (!loaderFn) return {
loaderData: void 0,
routeId
};
try {
var tracked = await trackLoaderDependencies(function () {
return loaderFn(loaderProps);
}),
routePath = loaderProps?.path || "/",
_iteratorNormalCompletion = !0,
_didIteratorError = !1,
_iteratorError = void 0;
try {
for (var _iterator = tracked.dependencies[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = !0) {
var dep = _step.value,
absoluteDep = path.resolve(dep);
loaderFileDependencies.has(absoluteDep) || (loaderFileDependencies.set(absoluteDep, /* @__PURE__ */new Set()), server?.watcher.add(absoluteDep), debugLoaderDeps && console.info(` \u24F5 [loader-dep] watching: ${absoluteDep}`)), loaderFileDependencies.get(absoluteDep).add(routePath);
}
} catch (err) {
_didIteratorError = !0, _iteratorError = err;
} finally {
try {
!_iteratorNormalCompletion && _iterator.return != null && _iterator.return();
} finally {
if (_didIteratorError) throw _iteratorError;
}
}
return {
loaderData: tracked.result,
routeId
};
} catch (err) {
if (isResponse(err)) throw err;
return console.error(`[one] Error running loader for ${routeId}:`, err), {
loaderData: void 0,
routeId
};
}
}
var loaderData, matches;
if (isSpaShell) {
var layoutLoaderPromises = layouts.map(async function (layout) {
var layoutFile = path.join(routerRoot, layout.contextKey),
layoutExported = await runner.import(layoutFile);
return runLoaderWithTracking(layout, layoutExported.loader);
}),
layoutResults = await Promise.all(layoutLoaderPromises);
matches = layoutResults.map(function (result) {
return {
routeId: result.routeId,
pathname: loaderProps?.path || "/",
params: loaderProps?.params || {},
loaderData: result.loaderData
};
}), loaderData = void 0;
} else {
var layoutRoutes = route.layouts || [],
pageRoute = {
contextKey: route.file,
file: route.file
},
layoutLoaderPromises = layoutRoutes.map(async function (layout) {
var layoutFile = path.join(routerRoot, layout.contextKey),
layoutExported = await runner.import(layoutFile);
return runLoaderWithTracking(layout, layoutExported.loader);
}),
pageLoaderPromise = runLoaderWithTracking(pageRoute, exported.loader),
[layoutResults, pageResult] = await Promise.all([Promise.all(layoutLoaderPromises), pageLoaderPromise]);
matches = [...layoutResults.map(function (result) {
return {
routeId: result.routeId,
pathname: loaderProps?.path || "/",
params: loaderProps?.params || {},
loaderData: result.loaderData
};
}), {
routeId: pageResult.routeId,
pathname: loaderProps?.path || "/",
params: loaderProps?.params || {},
loaderData: pageResult.loaderData
}], loaderData = pageResult.loaderData;
}
eval("process.env.TAMAGUI_IS_SERVER = '1'");
var entry = await runner.import(virtualEntryId),
render = entry.default.render;
setServerContext({
loaderData,
loaderProps,
matches
}), LoaderDataCache[route.file] = loaderData;
var isDynamicRoute = Object.keys(route.routeKeys || {}).length > 0,
isMissingSsgSlug = !1;
if (route.type === "ssg" && isDynamicRoute && exported.generateStaticParams) {
var staticParams = await exported.generateStaticParams({
params: loaderProps?.params
}),
currentParams = loaderProps?.params || {};
isMissingSsgSlug = !staticParams.some(function (sp) {
return Object.keys(sp).every(function (key) {
return sp[key] === currentParams[key];
});
});
}
var is404 = route.isNotFound || !getPageExport(exported) || isMissingSsgSlug || route.type === "ssg" && isDynamicRoute && loaderData === void 0;
if (isMissingSsgSlug || route.type === "ssg" && isDynamicRoute && loaderData === void 0) {
for (var notFoundExported = {}, notFoundRoutePath = "/+not-found", routeDir = route.file.replace(/\/[^/]+$/, ""), searchDir = routeDir;;) {
for (var _i = 0, _iter = [".tsx", ".ts", ".jsx", ".js"]; _i < _iter.length; _i++) {
var ext = _iter[_i],
candidate = path.join(routerRoot, searchDir, `+not-found${ext}`);
try {
if (notFoundExported = await runner.import(candidate), notFoundExported?.default) {
notFoundRoutePath = searchDir ? `/${searchDir}/+not-found` : "/+not-found";
break;
}
} catch {}
}
if (notFoundExported?.default || !searchDir) break;
var parent = searchDir.replace(/\/[^/]+$/, "");
parent === searchDir ? searchDir = "" : searchDir = parent;
}
if (notFoundExported.default) {
setServerContext({
loaderData: void 0,
loaderProps,
matches: []
});
var notFoundHtml = await render({
mode: "ssg",
loaderData: void 0,
loaderProps,
path: notFoundRoutePath,
preloads,
matches: []
});
return new Response(notFoundHtml, {
status: 404,
headers: {
"Content-Type": "text/html"
}
});
}
return new Response("<html><body><h1>404 - Not Found</h1></body></html>", {
status: 404,
headers: {
"Content-Type": "text/html"
}
});
}
var html = await render({
mode: isSpaShell ? "spa-shell" : route.type === "ssg" ? "ssg" : route.type === "ssr" ? "ssr" : "spa",
loaderData,
loaderProps,
path: loaderProps?.path || "/",
preloads,
matches
});
return is404 ? new Response(html, {
status: 404,
headers: {
"Content-Type": "text/html"
}
}) : html;
} catch (err) {
if (isResponse(err)) return err;
console.error(`SSR error while loading file ${route.file} from URL ${url.href}
`, err);
var title = `Error rendering ${url.pathname} on server`,
message = _instanceof(err, Error) ? err.message : `${err}`,
stack = _instanceof(err, Error) && err.stack || "",
isDuplicateReactError = /at (useEffect|useState|useReducer|useContext|useLayoutEffect)\s*\(.*?react\.development\.js/g.test(stack),
subMessage = isDuplicateReactError ? `
<h2>Duplicate React Error</h2>
<p style="font-size: 18px; line-height: 24px; max-width: 850px;">Note: These types of errors happen during SSR because One needs all dependencies that use React to be optimized. Find the dependency on the line after the react.development.js line below to find the failing dependency. So long as that dependency has "react" as a sub-dependency, you can add it to your package.json and One will optimize it automatically. If it doesn't list it properly, you can fix this manually by changing your vite.config.ts One plugin to add "one({ deps: { depName: true })" so One optimizes depName.</p>
` : "";
return console.error(`${title}
${message}
${stack}
`), `
<html>
<body style="background: #000; color: #fff; padding: 5%; font-family: monospace; line-height: 2rem;">
<h1 style="display: inline-flex; background: red; color: white; padding: 5px; margin: -5px;">${title}</h1>
<h2>${message}</h2>
${subMessage}
${stack ? `<pre style="font-size: 15px; line-height: 24px; white-space: pre;">
${stack}
</pre>` : ""}
</body>
</html>
`;
} finally {
resolveRender();
}
},
async handleLoader(param2) {
var {
request,
route: route2,
url: url2,
loaderProps: loaderProps2
} = param2,
_this,
routeFile2 = path.join(routerRoot, route2.file),
transformedJS = (_this = await server.transformRequest(routeFile2)) === null || _this === void 0 ? void 0 : _this.code;
if (!transformedJS) throw new Error("No transformed js returned");
var exported2 = await runner.import(routeFile2),
isDynamicRoute2 = Object.keys(route2.routeKeys || {}).length > 0;
if (route2.type === "ssg" && isDynamicRoute2 && exported2.generateStaticParams) {
var staticParams2 = await exported2.generateStaticParams({
params: loaderProps2?.params
}),
currentParams2 = loaderProps2?.params || {},
isValidSlug = staticParams2.some(function (sp) {
return Object.keys(sp).every(function (key) {
return sp[key] === currentParams2[key];
});
});
if (!isValidSlug) {
var nfPath = await findNearestNotFoundPath(route2.file);
return `export function loader(){return{__oneError:404,__oneErrorMessage:'Not Found',__oneNotFoundPath:${JSON.stringify(nfPath)}}}`;
}
}
var loaderData2;
if (exported2.loader) try {
var tracked = await trackLoaderDependencies(function () {
return exported2.loader(loaderProps2);
});
if (loaderData2 = tracked.result, isResponse(loaderData2)) throw loaderData2;
var routePath = loaderProps2?.path || "/",
_iteratorNormalCompletion = !0,
_didIteratorError = !1,
_iteratorError = void 0;
try {
for (var _iterator = tracked.dependencies[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = !0) {
var dep = _step.value,
absoluteDep = path.resolve(dep);
loaderFileDependencies.has(absoluteDep) || (loaderFileDependencies.set(absoluteDep, /* @__PURE__ */new Set()), server?.watcher.add(absoluteDep), debugLoaderDeps && console.info(` \u24F5 [loader-dep] watching: ${absoluteDep}`)), loaderFileDependencies.get(absoluteDep).add(routePath);
}
} catch (err) {
_didIteratorError = !0, _iteratorError = err;
} finally {
try {
!_iteratorNormalCompletion && _iterator.return != null && _iterator.return();
} finally {
if (_didIteratorError) throw _iteratorError;
}
}
} catch (err) {
if (isResponse(err)) throw err;
if (err?.code === "ENOENT") {
var nfPath1 = await findNearestNotFoundPath(route2.file);
return `export function loader(){return{__oneError:404,__oneErrorMessage:'Not Found',__oneNotFoundPath:${JSON.stringify(nfPath1)}}}`;
}
throw err;
}
loaderData2 && (transformedJS = replaceLoader({
code: transformedJS,
loaderData: loaderData2
}));
var platform = url2.searchParams.get("platform");
if (platform === "ios" || platform === "android") {
var environment = server.environments[platform || ""];
if (!environment) throw new Error(`[handleLoader] No Vite environment found for platform '${platform}'`);
var nativeTransformedJS = `exports.loader = () => (${JSON.stringify(loaderData2)});`;
return nativeTransformedJS;
}
return transformedJS;
},
async handleAPI(param2) {
var {
route: route2
} = param2;
return await runner.import(path.join(routerRoot, route2.file));
},
async loadMiddleware(route2) {
return await runner.import(path.join(routerRoot, route2.contextKey));
}
}, {
routerRoot
});
}
return {
name: "one-router-fs",
enforce: "post",
apply: "serve",
async config(userConfig) {
var _ref,
_options_optimization,
setting = (_ref = (_options_optimization = options.optimization) === null || _options_optimization === void 0 ? void 0 : _options_optimization.autoEntriesScanning) !== null && _ref !== void 0 ? _ref : "flat";
if (setting !== !1 && handleRequest.manifest.pageRoutes) {
var routesAndLayouts = [...new Set(handleRequest.manifest.pageRoutes.flatMap(function (route2) {
var _route_layouts;
return route2.isNotFound ? [] : route2.file ? setting === "flat" && route2.file.split("/").filter(function (x) {
return !x.startsWith("(");
}).length > 3 ? [] : [path.join("./app", route2.file), ...(((_route_layouts = route2.layouts) === null || _route_layouts === void 0 ? void 0 : _route_layouts.flatMap(function (layout) {
return layout.contextKey ? [path.join("./app", layout.contextKey)] : [];
})) || [])] : [];
}))];
return {
optimizeDeps: {
/**
* This adds all our routes and layouts as entries which fixes initial load making
* optimizeDeps be triggered which causes hard refreshes (also on initial navigations)
*
* see: https://vitejs.dev/config/dep-optimization-options.html#optimizedeps-entries
* and: https://github.com/remix-run/remix/pull/9921
*/
entries: routesAndLayouts
}
};
}
},
// if (USE_SERVER_ENV) {
// return {
// appType: 'custom',
// environments: {
// server: {
// resolve: {
// dedupe: optimizeDeps.include,
// external: [],
// noExternal: optimizeDeps.include,
// conditions: ['vxrn-web'],
// alias: {
// react: '@vxrn/vendor/react-19',
// 'react-dom': '@vxrn/vendor/react-dom-19',
// },
// },
// // webCompatible: true,
// nodeCompatible: true,
// dev: {
// optimizeDeps,
// createEnvironment(name, config) {
// const worker = new Worker(path.join(import.meta.dirname, 'server.js'))
// // const hot = new
// return new DevEnvironment(name, config, {
// hot: false,
// runner: {
// transport: new RemoteEnvironmentTransport({
// send: (data) => worker.postMessage(data),
// onMessage: (listener) => worker.on('message', listener),
// }),
// },
// })
// },
// },
// },
// },
// }
// }
configureServer(serverIn) {
server = serverIn, runner = createServerModuleRunner(USE_SERVER_ENV ? server.environments.server : server.environments.ssr);
var appDir = path.join(process.cwd(), getRouterRootFromOneOptions(options)),
fileWatcherChangeListener = debounce(async function (type, changedPath) {
if (type === "add" || type === "delete") {
var absolutePath = path.resolve(changedPath);
absolutePath.startsWith(appDir) && (handleRequest = createRequestHandler());
}
}, 100);
server.watcher.addListener("all", fileWatcherChangeListener);
var loaderDepChangeListener = debounce(function (changedPath) {
var absolutePath = path.resolve(changedPath),
routePaths = loaderFileDependencies.get(absolutePath);
routePaths && routePaths.size > 0 && (debugLoaderDeps && console.info(` \u24F5 [loader-dep] changed: ${absolutePath}, triggering loader refetch for routes:`, [...routePaths]), server.hot.send({
type: "custom",
event: "one:loader-data-update",
data: {
routePaths: [...routePaths]
}
}));
}, 100);
return server.watcher.on("change", loaderDepChangeListener), function () {
server.middlewares.use(async function (req, res, next) {
res.setHeader("Cache-Control", "no-store");
try {
var _options_web,
redirects = (_options_web = options.web) === null || _options_web === void 0 ? void 0 : _options_web.redirects;
if (redirects) {
var url2 = new URL(req.url || "", `http://${req.headers.host}`),
_iteratorNormalCompletion = !0,
_didIteratorError = !1,
_iteratorError = void 0;
try {
for (var _loop = function () {
var redirect = _step.value,
regexStr = `^${redirect.source.replace(/:\w+/g, "([^/]+)")}$`,
match = url2.pathname.match(new RegExp(regexStr));
if (match) {
var destination = redirect.destination,
params = redirect.source.match(/:\w+/g);
return params && params.forEach(function (param2, index) {
destination = destination.replace(param2, match[index + 1] || "");
}), debugRouter && console.info(`[one] \u21AA redirect ${url2.pathname} \u2192 ${destination}`), res.writeHead(redirect.permanent ? 301 : 302, {
Location: destination
}), res.end(), {
v: void 0
};
}
}, _iterator = redirects[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = !0) {
var _ret = _loop();
if (_type_of(_ret) === "object") return _ret.v;
}
} catch (err) {
_didIteratorError = !0, _iteratorError = err;
} finally {
try {
!_iteratorNormalCompletion && _iterator.return != null && _iterator.return();
} finally {
if (_didIteratorError) throw _iteratorError;
}
}
}
var reply = await handleRequest.handler(convertIncomingMessageToRequest(req));
if (!reply) return next();
if (typeof reply != "string" && isResponse(reply)) {
if (debugRouter) {
var headers = {};
reply.headers.forEach(function (v, k) {
headers[k] = v;
}), console.info(`[one] \u{1F4E4} response ${reply.status}`, headers);
}
if (reply.headers.forEach(function (value, key) {
if (key === "set-cookie") {
var cookies = value.split(", "),
_iteratorNormalCompletion2 = !0,
_didIteratorError2 = !1,
_iteratorError2 = void 0;
try {
for (var _iterator2 = cookies[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = !0) {
var cookie = _step2.value;
res.appendHeader("Set-Cookie", cookie);
}
} catch (err) {
_didIteratorError2 = !0, _iteratorError2 = err;
} finally {
try {
!_iteratorNormalCompletion2 && _iterator2.return != null && _iterator2.return();
} finally {
if (_didIteratorError2) throw _iteratorError2;
}
}
} else res.setHeader(key, value);
}), isStatusRedirect(reply.status)) {
var location = `${reply.headers.get("location") || ""}`;
if (debugRouter && console.info(`[one] \u21AA response redirect \u2192 ${location}`), location) {
res.writeHead(reply.status, {
Location: location
}), res.end();
return;
}
console.error("No location provided to redirected status reply", reply);
}
if (res.statusCode = reply.status, res.statusMessage = reply.statusText, reply.body && reply.body.locked) {
console.warn("Body is locked??", req.url), res.write(""), res.end();
return;
}
if (reply.body) {
if (reply.body.locked) {
console.warn("Body is locked??", req.url), res.end();
return;
}
try {
Readable.fromWeb(reply.body).pipe(res);
} catch (err) {
console.warn("Error piping reply body to response:", err), res.end();
}
return;
}
res.end();
return;
}
if (reply && (typeof reply > "u" ? "undefined" : _type_of(reply)) === "object") {
res.setHeader("Content-Type", "application/json"), res.write(JSON.stringify(reply)), res.end();
return;
}
res.write(reply), res.end();
return;
} catch (error) {
console.error(`[one] routing error ${req.url}: ${error}`), next(error);
}
console.warn(`SSR handler didn't send a response for url: ${req.url}`);
});
};
}
};
}
var convertIncomingMessageToRequest = function (req) {
if (!req.originalUrl) throw new Error("Can't convert: originalUrl is missing");
var urlBase = `http://${req.headers.host}`,
urlString = req.originalUrl,
url2 = new URL(urlString, urlBase),
headers = new Headers();
for (var key in req.headers) req.headers[key] && headers.append(key, req.headers[key]);
var hasBody = ["POST", "PUT", "PATCH", "DELETE"].includes(req.method || ""),
body = hasBody ? Readable.toWeb(req) : null;
return new Request(url2, {
method: req.method,
headers,
body,
// Required for streaming bodies in Node's experimental fetch:
duplex: "half"
});
};
export { createFileSystemRouterPlugin };
//# sourceMappingURL=fileSystemRouterPlugin.native.js.map