UNPKG

@expo/cli

Version:
349 lines (348 loc) 12.7 kB
/** * Copyright © 2022 650 Industries. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { getFilesToExportFromServerAsync: ()=>getFilesToExportFromServerAsync, exportFromServerAsync: ()=>exportFromServerAsync, getHtmlFiles: ()=>getHtmlFiles, getPathVariations: ()=>getPathVariations }); function _chalk() { const data = /*#__PURE__*/ _interopRequireDefault(require("chalk")); _chalk = function() { return data; }; return data; } function _matchers() { const data = require("expo-router/build/matchers"); _matchers = function() { return data; }; return data; } function _path() { const data = /*#__PURE__*/ _interopRequireDefault(require("path")); _path = function() { return data; }; return data; } function _resolveFrom() { const data = /*#__PURE__*/ _interopRequireDefault(require("resolve-from")); _resolveFrom = function() { return data; }; return data; } function _util() { const data = require("util"); _util = function() { return data; }; return data; } const _favicon = require("./favicon"); const _persistMetroAssets = require("./persistMetroAssets"); const _saveAssets = require("./saveAssets"); const _log = require("../log"); const _metroErrorInterface = require("../start/server/metro/metroErrorInterface"); const _router = require("../start/server/metro/router"); const _serializeHtml = require("../start/server/metro/serializeHtml"); const _link = require("../utils/link"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const debug = require("debug")("expo:export:generateStaticRoutes"); /** Match `(page)` -> `page` */ function matchGroupName(name) { var ref; return (ref = name.match(/^\(([^/]+?)\)$/)) == null ? void 0 : ref[1]; } async function getFilesToExportFromServerAsync(projectRoot, { manifest , renderAsync , // Servers can handle group routes automatically and therefore // don't require the build-time generation of every possible group // variation. exportServer , // name : contents files =new Map() }) { await Promise.all(getHtmlFiles({ manifest, includeGroupVariations: !exportServer }).map(async ({ route , filePath , pathname })=>{ try { const targetDomain = exportServer ? "server" : "client"; files.set(filePath, { contents: "", targetDomain }); const data = await renderAsync({ route, filePath, pathname }); files.set(filePath, { contents: data, routeId: pathname, targetDomain }); } catch (e) { await (0, _metroErrorInterface.logMetroErrorAsync)({ error: e, projectRoot }); throw new Error("Failed to statically export route: " + pathname); } })); return files; } function modifyRouteNodeInRuntimeManifest(manifest, callback) { const iterateScreens = (screens)=>{ Object.values(screens).map((value)=>{ if (typeof value !== "string") { if (value._route) callback(value._route); iterateScreens(value.screens); } }); }; iterateScreens(manifest.screens); } // TODO: Do this earlier in the process. function makeRuntimeEntryPointsAbsolute(manifest, appDir) { modifyRouteNodeInRuntimeManifest(manifest, (route)=>{ if (Array.isArray(route.entryPoints)) { route.entryPoints = route.entryPoints.map((entryPoint)=>{ if (entryPoint.startsWith(".")) { return _path().default.resolve(appDir, entryPoint); } else if (!_path().default.isAbsolute(entryPoint)) { return (0, _resolveFrom().default)(appDir, entryPoint); } return entryPoint; }); } }); } async function exportFromServerAsync(projectRoot, devServer, { outputDir , baseUrl , exportServer , includeSourceMaps , routerRoot , files =new Map() , exp }) { _log.Log.log(`Static rendering is enabled. ` + (0, _link.learnMore)("https://docs.expo.dev/router/reference/static-rendering/")); const platform = "web"; const isExporting = true; const appDir = _path().default.join(projectRoot, routerRoot); const injectFaviconTag = await (0, _favicon.getVirtualFaviconAssetsAsync)(projectRoot, { outputDir, baseUrl, files, exp }); const [resources, { manifest , serverManifest , renderAsync }] = await Promise.all([ devServer.getStaticResourcesAsync({ includeSourceMaps }), devServer.getStaticRenderFunctionAsync(), ]); makeRuntimeEntryPointsAbsolute(manifest, appDir); debug("Routes:\n", (0, _util().inspect)(manifest, { colors: true, depth: null })); await getFilesToExportFromServerAsync(projectRoot, { files, manifest, exportServer, async renderAsync ({ pathname , route }) { const template = await renderAsync(pathname); let html = await (0, _serializeHtml.serializeHtmlWithAssets)({ isExporting, resources: resources.artifacts, template, baseUrl, route }); if (injectFaviconTag) { html = injectFaviconTag(html); } return html; } }); (0, _saveAssets.getFilesFromSerialAssets)(resources.artifacts, { platform, includeSourceMaps, files }); if (resources.assets) { // TODO: Collect files without writing to disk. // NOTE(kitten): Re. above, this is now using `files` except for iOS catalog output, which isn't used here await (0, _persistMetroAssets.persistMetroAssetsAsync)(resources.assets, { files, platform, outputDirectory: outputDir, baseUrl }); } if (exportServer) { const apiRoutes = await exportApiRoutesAsync({ outputDir, server: devServer, manifest: serverManifest, // NOTE(kitten): For now, we always output source maps for API route exports includeSourceMaps: true }); // Add the api routes to the files to export. for (const [route, contents] of apiRoutes){ files.set(route, contents); } } else { warnPossibleInvalidExportType(appDir); } return files; } function getHtmlFiles({ manifest , includeGroupVariations }) { const htmlFiles = new Set(); function traverseScreens(screens, route, baseUrl = "") { for (const value of Object.values(screens)){ let leaf = null; if (typeof value === "string") { leaf = value; } else if (Object.keys(value.screens).length === 0) { leaf = value.path; var __route; route = (__route = value._route) != null ? __route : null; } if (leaf != null) { let filePath = baseUrl + leaf; if (leaf === "") { filePath = baseUrl === "" ? "index" : baseUrl.endsWith("/") ? baseUrl + "index" : baseUrl.slice(0, -1); } else if (// If the path is a collection of group segments leading to an index route, append `/index`. (0, _matchers().stripGroupSegmentsFromPath)(filePath) === "") { filePath += "/index"; } // This should never happen, the type of `string | object` originally comes from React Navigation. if (!route) { throw new Error(`Internal error: Route not found for "${filePath}" while collecting static export paths.`); } if (includeGroupVariations) { // TODO: Dedupe requests for alias routes. addOptionalGroups(filePath, route); } else { htmlFiles.add({ filePath, route }); } } else if (typeof value === "object" && (value == null ? void 0 : value.screens)) { const newPath = baseUrl + value.path + "/"; var __route1; traverseScreens(value.screens, (__route1 = value._route) != null ? __route1 : null, newPath); } } } function addOptionalGroups(path, route) { const variations = getPathVariations(path); for (const variation of variations){ htmlFiles.add({ filePath: variation, route }); } } traverseScreens(manifest.screens, null); return uniqueBy(Array.from(htmlFiles), (value)=>value.filePath).map((value)=>{ const parts = value.filePath.split("/"); // Replace `:foo` with `[foo]` and `*foo` with `[...foo]` const partsWithGroups = parts.map((part)=>{ if (part === "*not-found") { return `+not-found`; } else if (part.startsWith(":")) { return `[${part.slice(1)}]`; } else if (part.startsWith("*")) { return `[...${part.slice(1)}]`; } return part; }); const filePathLocation = partsWithGroups.join("/"); const filePath = filePathLocation + ".html"; return { ...value, filePath, pathname: filePathLocation.replace(/(\/?index)?$/, "") }; }); } function uniqueBy(array, key) { const seen = new Set(); const result = []; for (const value of array){ const id = key(value); if (!seen.has(id)) { seen.add(id); result.push(value); } } return result; } function getPathVariations(routePath) { const variations = new Set(); const segments = routePath.split("/"); function generateVariations(segments, current = "") { if (segments.length === 0) { if (current) variations.add(current); return; } const [head, ...rest] = segments; if (matchGroupName(head)) { const groups = head.slice(1, -1).split(","); if (groups.length > 1) { for (const group of groups){ // If there are multiple groups, recurse on each group. generateVariations([ `(${group.trim()})`, ...rest ], current); } return; } else { // Start a fork where this group is included generateVariations(rest, current ? `${current}/(${groups[0]})` : `(${groups[0]})`); // This code will continue and add paths without this group included` } } else if (current) { current = `${current}/${head}`; } else { current = head; } generateVariations(rest, current); } generateVariations(segments); return Array.from(variations); } async function exportApiRoutesAsync({ includeSourceMaps , outputDir , server , ...props }) { const { manifest , files } = await server.exportExpoRouterApiRoutesAsync({ outputDir: "_expo/functions", prerenderManifest: props.manifest, includeSourceMaps }); _log.Log.log(_chalk().default.bold`Exporting ${files.size} API Routes.`); files.set("_expo/routes.json", { contents: JSON.stringify(manifest, null, 2), targetDomain: "server" }); return files; } function warnPossibleInvalidExportType(appDir) { const apiRoutes = (0, _router.getApiRoutesForDirectory)(appDir); if (apiRoutes.length) { // TODO: Allow API Routes for native-only. _log.Log.warn(_chalk().default.yellow`Skipping export for API routes because \`web.output\` is not "server". You may want to remove the routes: ${apiRoutes.map((v)=>_path().default.relative(appDir, v)).join(", ")}`); } } //# sourceMappingURL=exportStaticAsync.js.map