UNPKG

@serwist/turbopack

Version:

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

205 lines (200 loc) 6.68 kB
import path from 'node:path'; import { rebasePath, getFileManifestEntries } from '@serwist/build'; import { validationErrorMap, SerwistConfigError } from '@serwist/build/schema'; import { green, bold, white, yellow, red, cyan, dim } from 'kolorist'; import { NextResponse } from 'next/server.js'; import { z } from 'zod'; import { injectManifestOptions } from './index.schema.js'; const mapLoggingMethodToConsole = { wait: "log", error: "error", warn: "warn", info: "log", event: "log" }; const prefixes = { wait: `${white(bold("○"))} (serwist)`, error: `${red(bold("X"))} (serwist)`, warn: `${yellow(bold("⚠"))} (serwist)`, info: `${white(bold("○"))} (serwist)`, event: `${green(bold("✓"))} (serwist)` }; const prefixedLog = (prefixType, ...message)=>{ const consoleMethod = mapLoggingMethodToConsole[prefixType]; const prefix = prefixes[prefixType]; if ((message[0] === "" || message[0] === undefined) && message.length === 1) { message.shift(); } if (message.length === 0) { console[consoleMethod](""); } else { console[consoleMethod](` ${prefix}`, ...message); } }; const wait = (...message)=>{ prefixedLog("wait", ...message); }; const error = (...message)=>{ prefixedLog("error", ...message); }; const warn = (...message)=>{ prefixedLog("warn", ...message); }; const info = (...message)=>{ prefixedLog("info", ...message); }; const event = (...message)=>{ prefixedLog("event", ...message); }; var logger = /*#__PURE__*/Object.freeze({ __proto__: null, error: error, event: event, info: info, wait: wait, warn: warn }); const esbuild = import('esbuild-wasm'); const logSerwistResult = (filePath, buildResult)=>{ const { count, size, warnings } = buildResult; const hasWarnings = warnings && warnings.length > 0; if (filePath === "sw.js" && (hasWarnings || count > 0)) { logger[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" }; 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 ?? [], (manifestEntries)=>{ const manifest = manifestEntries.map((m)=>{ if (m.url.startsWith(".next/")) m.url = `/_next/${m.url.slice(6)}`; if (m.url.startsWith("public/")) m.url = path.posix.join(config.basePath, m.url.slice(7)); return m; }); return { manifest, warnings: [] }; } ] }; }); let map = null; const loadMap = async (filePath)=>{ const config = await validation; const { count, size, manifestEntries, warnings } = await getFileManifestEntries(config); const injectionPoint = config.injectionPoint ? config.injectionPoint : ""; const manifestString = manifestEntries === undefined ? "undefined" : JSON.stringify(manifestEntries, null, 2); logSerwistResult(filePath, { count, size, warnings }); const result = await (await esbuild).build({ sourcemap: true, format: "esm", target: [ "chrome64", "edge79", "firefox67", "opera51", "safari12" ], treeShaking: true, minify: !isDev, bundle: true, ...config.esbuildOptions, 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 (!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 }; }; export { createSerwistRoute };