UNPKG

@serwist/turbopack

Version:

A module that integrates Serwist into your Next.js / Turbopack application.

166 lines (165 loc) 5.75 kB
import { r as logger_exports, t as injectManifestOptions } from "./chunks/index.schema-RVDaKxz4.js"; import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { getFileManifestEntries, rebasePath } from "@serwist/build"; import { SerwistConfigError, validationErrorMap } from "@serwist/build/schema"; import { browserslistToEsbuild } from "@serwist/utils"; import browserslist from "browserslist"; import { cyan, dim, yellow } from "kolorist"; import { MODERN_BROWSERSLIST_TARGET } from "next/constants.js"; import { NextResponse } from "next/server.js"; import { z } from "zod"; //#region src/index.ts let esbuildWasm = null; let esbuildNative = null; const logSerwistResult = (filePath, buildResult) => { const { count, size, warnings } = buildResult; const hasWarnings = warnings && warnings.length > 0; if (filePath === "sw.js" && (hasWarnings || count > 0)) logger_exports[hasWarnings ? "warn" : "event"](`${cyan(count)} precache entries ${dim(`(${(size / 1024).toFixed(2)} KiB)`)}${hasWarnings ? `\n${yellow([ "⚠ warnings", ...warnings.map((w) => ` ${w}`), "" ].join("\n"))}` : ""}`); }; const validateGetManifestOptions = async (input) => { const result = await injectManifestOptions.spa(input, { error: validationErrorMap }); if (!result.success) throw new SerwistConfigError({ moduleName: "@serwist/turbopack", message: z.prettifyError(result.error) }); return result.data; }; const isDev = process.env.NODE_ENV === "development"; const contentTypeMap = { ".js": "application/javascript", ".map": "application/json; charset=UTF-8" }; /** * Creates a Route Handler for Serwist files. * @param options Options for {@linkcode getFileManifestEntries}. */ const createSerwistRoute = (options) => { const dynamic = "force-static", dynamicParams = false, revalidate = false; const validation = validateGetManifestOptions(options).then((config) => { return { ...config, disablePrecacheManifest: isDev, additionalPrecacheEntries: isDev ? [] : config.additionalPrecacheEntries, globIgnores: [...config.globIgnores, rebasePath({ file: config.swSrc, baseDirectory: config.globDirectory })], manifestTransforms: [...config.manifestTransforms ?? [], async (manifestEntries) => { return { manifest: manifestEntries.map((m) => { if (m.url.startsWith(config.nextConfig.distDir)) m.url = `${config.nextConfig.assetPrefix}/_next/${m.url.slice(config.nextConfig.distDir.length)}`; if (m.url.startsWith("public/")) m.url = path.posix.join(config.nextConfig.basePath, m.url.slice(7)); return m; }), warnings: [] }; }] }; }); let lastHash = null; let map = null; const loadMap = async (filePath) => { const config = await validation; const { count, size, manifestEntries, warnings } = await getFileManifestEntries(config); const injectionPoint = config.injectionPoint || ""; const manifestString = manifestEntries === void 0 ? "undefined" : JSON.stringify(manifestEntries, null, 2); const log = (type, ...message) => { if (filePath === "sw.js") logger_exports[type](...message); }; let esbuild; if (config.useNativeEsbuild) { log("info", "Using esbuild to bundle the service worker."); if (!esbuildNative) esbuildNative = import( /* webpackIgnore: true */ "esbuild" ); esbuild = await esbuildNative; } else { log("info", "Using esbuild-wasm to bundle the service worker."); if (!esbuildWasm) esbuildWasm = import( /* webpackIgnore: true */ "esbuild-wasm" ); esbuild = await esbuildWasm; } logSerwistResult(filePath, { count, size, warnings }); const result = await esbuild.build({ sourcemap: true, format: "esm", treeShaking: true, minify: !isDev, bundle: true, ...config.esbuildOptions, target: config.esbuildOptions?.target ?? browserslistToEsbuild(browserslist, config.cwd, MODERN_BROWSERSLIST_TARGET), platform: "browser", define: { ...config.esbuildOptions.define, ...injectionPoint ? { [injectionPoint]: manifestString } : {} }, outdir: config.cwd, write: false, entryNames: "[name]", assetNames: "[name]-[hash]", chunkNames: "[name]-[hash]", entryPoints: [{ in: config.swSrc, out: "sw" }] }); if (result.errors.length) { console.error("Failed to build the service worker.", result.errors); throw new Error(); } if (result.warnings.length) console.warn(result.warnings); return new Map(result.outputFiles.map((e) => [e.path, e.text])); }; const generateStaticParams = async () => { const config = await validation; if (!map) map = await loadMap("root"); return [...map.keys()].map((e) => ({ path: path.relative(config.cwd, e) })); }; const GET = async (_, { params }) => { const { path: filePath } = await params; const config = await validation; if (isDev && config.rebuildOnChange) { const swContent = fs.readFileSync(config.swSrc, "utf-8"); const currentHash = crypto.createHash("sha256").update(swContent).digest("hex"); if (!map || lastHash !== currentHash) { map = await loadMap(filePath); lastHash = currentHash; } } else if (!map) map = await loadMap(filePath); return new NextResponse(map.get(path.join(config.cwd, filePath)), { headers: { "Content-Type": contentTypeMap[path.extname(filePath)] || "text/plain", "Service-Worker-Allowed": "/" } }); }; return { dynamic, dynamicParams, revalidate, generateStaticParams, GET }; }; const withSerwist = (nextConfig = {}) => ({ ...nextConfig, serverExternalPackages: [ ...nextConfig.serverExternalPackages ?? [], "esbuild", "esbuild-wasm" ] }); //#endregion export { createSerwistRoute, withSerwist }; //# sourceMappingURL=index.mjs.map