@serwist/turbopack
Version:
A module that integrates Serwist into your Next.js / Turbopack application.
205 lines (200 loc) • 6.68 kB
JavaScript
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 };