UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

431 lines (418 loc) 14.7 kB
import { mkdirSync, writeFileSync } from "fs"; import { join, resolve } from "path"; import { pathToFileURL } from "url"; import { resolvePath } from "@vxrn/resolve"; import { cloudflare } from "@cloudflare/vite-plugin"; import { getPlatformEnvDefine } from "@vxrn/vite-plugin-metro"; import { exactRegex } from "rolldown/filter"; import { CACHE_KEY } from "../../constants.native.js"; import { virtalEntryIdClient, virtualEntryId } from "./virtualEntryConstants.native.js"; import { getManifest } from "../getManifest.native.js"; import { getRouterRootFromOneOptions } from "../../utils/getRouterRootFromOneOptions.native.js"; import { createCloudflareWranglerConfig, getCloudflareProjectNameSync, loadUserWranglerConfigSync, shouldEnableWorkerdDev } from "../cloudflareWranglerConfig.native.js"; var workerdDevEntryId = "virtual:one-workerd-dev-entry"; var resolvedWorkerdDevEntryId = `\0${workerdDevEntryId}`; var WORKERD_DEV_DIR = "node_modules/.one-workerd-dev"; function serveOnly(plugin) { var existing = plugin.apply; return { ...plugin, apply(config, env) { if (env.command !== "serve") return false; if (typeof existing === "function") return existing(config, env); if (existing === "build") return false; return true; } }; } function toImportHref(root, routerRoot, routeFile) { var rel = routeFile.replace(/^\.\//, ""); return pathToFileURL(resolve(root, routerRoot, rel)).href; } function serializeImportMap(entries) { return entries.map(function (param) { var [key, href] = param; return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(href)})`; }).join(",\n"); } function stripRouteForWorker(route) { var { layouts, middlewares, ...rest } = route; return { ...rest, // live-render SSG in dev the same way the node middleware does type: route.type === "ssg" ? "ssr" : route.type, layouts: layouts === null || layouts === void 0 ? void 0 : layouts.map(function (layout) { return { contextKey: layout.contextKey }; }), middlewares: middlewares === null || middlewares === void 0 ? void 0 : middlewares.map(function (middleware) { return { contextKey: middleware.contextKey }; }) }; } function stubRouteBuildInfo(route, preloads) { var _ref; var _route_middlewares; var pageType = route.type === "ssg" ? "ssr" : route.type; return { type: pageType, path: route.page, routeFile: route.file, middlewares: (_ref = (_route_middlewares = route.middlewares) === null || _route_middlewares === void 0 ? void 0 : _route_middlewares.map(function (middleware) { return middleware.contextKey; })) !== null && _ref !== void 0 ? _ref : [], preloadPath: "", cssPreloadPath: "", loaderPath: "", cleanPath: route.urlCleanPath || route.page, htmlPath: "", clientJsPath: "", serverJsPath: "", params: {}, preloads, css: [], layoutCSS: [] }; } function generateWorkerdDevEntryModule(args) { var _options_router; var { root, options, routerRoot, routePaths, preloads } = args; var manifest = getManifest({ routerRoot: resolve(root, routerRoot), ignoredRouteFiles: (_options_router = options.router) === null || _options_router === void 0 ? void 0 : _options_router.ignoredRouteFiles, routePaths }); if (!manifest) { throw new Error("[one] workerd dev: no routes manifest"); } var apiImports = /* @__PURE__ */new Map(); var middlewareImports = /* @__PURE__ */new Map(); var routeToBuildInfo = {}; var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = void 0; try { for (var _iterator = manifest.pageRoutes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var route = _step.value; var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = void 0; try { for (var _iterator1 = (route.middlewares || [])[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true) { var middleware = _step1.value; if (!middleware.contextKey || middlewareImports.has(middleware.contextKey)) continue; middlewareImports.set(middleware.contextKey, toImportHref(root, routerRoot, middleware.contextKey)); } } catch (err) { _didIteratorError1 = true; _iteratorError1 = err; } finally { try { if (!_iteratorNormalCompletion1 && _iterator1.return != null) { _iterator1.return(); } } finally { if (_didIteratorError1) { throw _iteratorError1; } } } if (route.file) { routeToBuildInfo[route.file] = stubRouteBuildInfo(route, preloads); } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = void 0; try { for (var _iterator2 = manifest.apiRoutes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var route1 = _step2.value; if (route1.file) { apiImports.set(route1.page, toImportHref(root, routerRoot, route1.file)); } var _iteratorNormalCompletion3 = true, _didIteratorError3 = false, _iteratorError3 = void 0; try { for (var _iterator3 = (route1.middlewares || [])[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var middleware1 = _step3.value; if (!middleware1.contextKey || middlewareImports.has(middleware1.contextKey)) continue; middlewareImports.set(middleware1.contextKey, toImportHref(root, routerRoot, middleware1.contextKey)); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return != null) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return != null) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } var pageRoutes = manifest.pageRoutes.map(stripRouteForWorker); var apiRoutes = manifest.apiRoutes.map(stripRouteForWorker); var buildInfo = { constants: { CACHE_KEY }, oneOptions: options, routeToBuildInfo, pathToRoute: {}, routeMap: {}, manifest: { pageRoutes, apiRoutes, allRoutes: [...pageRoutes, ...apiRoutes] }, preloads: Object.fromEntries(preloads.map(function (preload) { return [preload, true]; })), cssPreloads: {}, loaders: {} }; var spaPreloadScripts = preloads.map(function (preload) { return `<script type="module" src="${preload}"></script>`; }).join(""); return `if (typeof MessageChannel === 'undefined') { globalThis.MessageChannel = class MessageChannel { constructor() { this.port1 = { postMessage: () => {}, onmessage: null, close: () => {} } this.port2 = { postMessage: () => {}, onmessage: null, close: () => {} } } } } import { serve, setFetchStaticHtml } from 'one/serve-worker' const lazyRoutes = { serverEntry: () => import(${JSON.stringify(virtualEntryId)}), api: { ${serializeImportMap([...apiImports])} }, middlewares: { ${serializeImportMap([...middlewareImports])} } } const buildInfo = ${JSON.stringify(buildInfo)} let server export default { async fetch(request, env, ctx) { const url = new URL(request.url) const pathname = url.pathname if (pathname === '/status' || pathname.startsWith('/status?')) { return new Response('packager-status:running', { headers: { 'content-type': 'text/plain' }, }) } if (!server) { server = await serve(buildInfo, lazyRoutes, { disableModuleCache: true }) } if (env && env.ASSETS) { setFetchStaticHtml(async (path) => { try { const assetUrl = new URL(request.url) assetUrl.pathname = path const assetResponse = await env.ASSETS.fetch(new Request(assetUrl)) if (assetResponse && assetResponse.ok) { return await assetResponse.text() } } catch {} return null }) } try { const response = await server.fetch(request, env, ctx) if (!response || response.status === 404) { if (env && env.ASSETS) { try { const assetResponse = await env.ASSETS.fetch(request) if (assetResponse && assetResponse.status !== 404) { return assetResponse } } catch {} } } if (!response) { if (request.method === 'GET' || request.method === 'HEAD') { return new Response( '<!DOCTYPE html><html><head>' + ${JSON.stringify(spaPreloadScripts)} + '</head></html>', { headers: { 'content-type': 'text/html' } } ) } return new Response('Not Found', { status: 404 }) } return response } finally { setFetchStaticHtml(null) } } } `; } function createWorkerdDevPlugins(options, root, routeIndex) { var _options_web, _options_web1; if (!shouldEnableWorkerdDev((_options_web = options.web) === null || _options_web === void 0 ? void 0 : _options_web.deploy, root)) { return []; } var cacheDir = join(root, WORKERD_DEV_DIR); var wranglerInputPath = join(cacheDir, "_wrangler.input.jsonc"); var workerEntryPath = join(cacheDir, "worker-entry.js"); mkdirSync(cacheDir, { recursive: true }); writeFileSync(workerEntryPath, `export { default } from ${JSON.stringify(workerdDevEntryId)} `); var userWranglerConfig = loadUserWranglerConfigSync(root); var projectName = getCloudflareProjectNameSync(root); var wranglerInputConfig = createCloudflareWranglerConfig(projectName, userWranglerConfig === null || userWranglerConfig === void 0 ? void 0 : userWranglerConfig.config); wranglerInputConfig.main = "worker-entry.js"; writeFileSync(wranglerInputPath, `${JSON.stringify(wranglerInputConfig, null, 2)} `); var preloads = ((_options_web1 = options.web) === null || _options_web1 === void 0 ? void 0 : _options_web1.experimentalBundledDev) ? ["/bundledDevClient.mjs", "/assets/_virtual_one-entry.js"] : ["/@vite/client", virtalEntryIdClient]; var empty = resolvePath("@vxrn/vite-plugin-metro/empty", root); var rnWebPkg = resolvePath("react-native-web/package.json", root); var rnWeb = resolvePath("react-native-web", root); var safeArea = resolvePath("@vxrn/safe-area", root); var preparePlugin = { name: "one:workerd-dev-prepare", apply: "serve", enforce: "pre", config() { return { environments: { worker: { define: { ...getPlatformEnvDefine("ssr"), "process.env.TAMAGUI_IS_SERVER": JSON.stringify("1"), "process.env.TAMAGUI_KEEP_THEMES": JSON.stringify("1"), "process.env.ONE_CACHE_KEY": JSON.stringify(CACHE_KEY) }, optimizeDeps: { include: ["react", "react-dom", "react-dom/client", "react-dom/server.browser", "react/jsx-runtime", "react/jsx-dev-runtime"] }, build: { rolldownOptions: { shimMissingExports: true } } } } }; }, // environment resolve options have no alias field; match the production // worker aliases with a worker-only resolveId (rolldown cannot parse RN Flow) resolveId: { filter: { id: /^(react-native(\/|$)|react-native-safe-area-context$)/ }, handler(source) { if (this.environment.name !== "worker") return; if (/^react-native\/Libraries\//.test(source)) return empty; if (source === "react-native/package.json") return rnWebPkg; if (source === "react-native") return rnWeb; if (source === "react-native-safe-area-context") return safeArea; } }, configureServer(server) { if (!server.environments.worker) { throw new Error(`[one] ONE_EXPERIMENTAL_WORKER_DEV=1 is set but @cloudflare/vite-plugin did not register a "worker" environment. Expected wrangler config at ${wranglerInputPath}.`); } console.info("[one] experimental workerd dev enabled (ONE_EXPERIMENTAL_WORKER_DEV=1)"); } }; var virtualPlugin = { name: "one:workerd-dev-entry", apply: "serve", resolveId: { filter: { id: exactRegex(workerdDevEntryId) }, handler(id) { if (this.environment.name !== "worker") return; if (id === workerdDevEntryId) return resolvedWorkerdDevEntryId; } }, load: { filter: { id: exactRegex(resolvedWorkerdDevEntryId) }, handler() { if (this.environment.name !== "worker") return; return generateWorkerdDevEntryModule({ root, options, routerRoot: getRouterRootFromOneOptions(options), routePaths: routeIndex.getPaths(), preloads }); } }, hotUpdate(param) { var { file } = param; if (this.environment.name !== "worker") return; var graph = this.environment.moduleGraph; var entry = graph.getModuleById(resolvedWorkerdDevEntryId); if (!entry) return; var modules = graph.getModulesByFile(file); if (!(modules === null || modules === void 0 ? void 0 : modules.size)) return; graph.invalidateModule(entry); } }; var cloudflarePlugins = cloudflare({ configPath: wranglerInputPath, viteEnvironment: { name: "worker" } }).map(serveOnly); return [preparePlugin, virtualPlugin, ...cloudflarePlugins]; } export { createWorkerdDevPlugins, generateWorkerdDevEntryModule, shouldEnableWorkerdDev, workerdDevEntryId }; //# sourceMappingURL=workerdDevPlugin.native.js.map