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