UNPKG

@serwist/next

Version:

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

135 lines (127 loc) 5.11 kB
import fs from "node:fs"; import path from "node:path"; import { rebasePath } from "@serwist/build"; import type { BuildOptions } from "@serwist/cli"; import { browserslistToEsbuild } from "@serwist/utils"; import browserslist from "browserslist"; import { MODERN_BROWSERSLIST_TARGET } from "next/constants.js"; import type { NextConfigComplete } from "next/dist/server/config-shared.js"; import type { SerwistOptions } from "./lib/config/types.js"; import { generateGlobPatterns, loadNextConfig } from "./lib/config/utils.js"; const _cwd = process.cwd(); const _isDev = process.env.NODE_ENV === "development"; /** * Additional build context. */ export interface SerwistContext { /** * The current working directory. */ cwd?: string; /** * Whether Serwist is in development mode. This option determines how Next.js configuration * is resolved. Note that it doesn't change how the service worker is built. */ isDev?: boolean; } export interface Serwist { /** * Integrates Serwist into your Next.js app. * @param options * @returns */ (options: SerwistOptions, nextConfig?: NextConfigComplete, context?: SerwistContext): Promise<BuildOptions>; /** * Integrates Serwist into your Next.js app. Allows reading fully resolved Next.js configuration. * @param optionsFunction * @returns */ withNextConfig: ( optionsFunction: (nextConfig: NextConfigComplete) => Promise<SerwistOptions> | SerwistOptions, context?: SerwistContext, ) => Promise<BuildOptions>; } export const serwist: Serwist = async (options, nextConfig, { cwd = _cwd, isDev = _isDev } = {}) => { if (!nextConfig) nextConfig = await loadNextConfig(cwd, isDev); const basePath = nextConfig.basePath || "/"; let distDir = nextConfig.distDir; if (distDir[0] === "/") distDir = distDir.slice(1); if (distDir[distDir.length - 1] !== "/") distDir += "/"; const distServerDir = `${distDir}server/`; const distAppDir = `${distServerDir}app/`; const distPagesDir = `${distServerDir}pages/`; const { precachePrerendered = true, globDirectory = cwd, ...cliOptions } = options; for (const file of [cliOptions.swDest, `${cliOptions.swDest}.map`]) { fs.rmSync(file, { force: true }); } return { dontCacheBustURLsMatching: new RegExp(`^${distDir}static/`), disablePrecacheManifest: isDev, ...cliOptions, globDirectory, globPatterns: [ ...(cliOptions.globPatterns ?? generateGlobPatterns(distDir)), ...(precachePrerendered ? [`${distServerDir}{app,pages}/**/*.html`] : []), ], globIgnores: [ `${distAppDir}**/_not-found.html`, `${distAppDir}_global-error*`, `${distPagesDir}404.html`, `${distPagesDir}500.html`, ...(cliOptions.globIgnores ?? []), rebasePath({ baseDirectory: globDirectory, file: cliOptions.swSrc }), rebasePath({ baseDirectory: globDirectory, file: cliOptions.swDest }), rebasePath({ baseDirectory: globDirectory, file: `${cliOptions.swDest}.map` }), ], manifestTransforms: [ ...(cliOptions.manifestTransforms ?? []), (manifestEntries) => { const manifest = manifestEntries.map((m) => { if (m.url.startsWith(distAppDir)) { // Keep the prefixing slash. m.url = m.url.slice(distAppDir.length - 1); } if (m.url.startsWith(distPagesDir)) { // Keep the prefixing slash. m.url = m.url.slice(distPagesDir.length - 1); } if (m.url.endsWith(".html")) { // trailingSlash: true && output: 'export' // or root index.html. // https://nextjs.org/docs/app/api-reference/config/next-config-js/trailingSlash // "/abc/index.html" -> "/abc/" // "/index.html" -> "/" if (m.url.endsWith("/index.html")) { m.url = m.url.slice(0, m.url.lastIndexOf("/") + 1); } // "/xxx.html" -> "/xxx" else { m.url = m.url.substring(0, m.url.lastIndexOf(".")); } m.url = path.posix.join(basePath, m.url); } // Replace all references to "$(distDir)" with "$(assetPrefix)/_next/". if (m.url.startsWith(distDir)) { m.url = `${nextConfig.assetPrefix ?? ""}/_next/${m.url.slice(distDir.length)}`; } // Replace all references to public/ with "$(basePath)/". if (m.url.startsWith("public/")) { m.url = path.posix.join(basePath, m.url.slice(7)); } return m; }); return { manifest, warnings: [] }; }, ], esbuildOptions: { ...cliOptions.esbuildOptions, target: cliOptions.esbuildOptions?.target ?? browserslistToEsbuild(browserslist, cwd, MODERN_BROWSERSLIST_TARGET), }, }; }; serwist.withNextConfig = async (optionsFunction, { cwd = _cwd, isDev = _isDev } = {}) => { const nextConfig = await loadNextConfig(cwd, isDev); return serwist(await optionsFunction(nextConfig), nextConfig, { cwd, isDev }); }; export { generateGlobPatterns }; export type { SerwistOptions };