@serwist/turbopack 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +210 -0
- package/dist/index.react.d.ts +16 -0
- package/dist/index.react.d.ts.map +1 -0
- package/dist/index.react.js +49 -0
- package/dist/index.schema.d.ts +165 -0
- package/dist/index.schema.d.ts.map +1 -0
- package/dist/index.schema.js +107 -0
- package/dist/index.worker.d.ts +14 -0
- package/dist/index.worker.d.ts.map +1 -0
- package/dist/index.worker.js +261 -0
- package/dist/lib/constants.d.ts +2 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/context.d.ts +7 -0
- package/dist/lib/context.d.ts.map +1 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/logger.d.ts +7 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +106 -0
- package/src/index.react.tsx +44 -0
- package/src/index.schema.ts +48 -0
- package/src/index.ts +152 -0
- package/src/index.worker.ts +274 -0
- package/src/lib/constants.ts +61 -0
- package/src/lib/context.ts +16 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/logger.ts +58 -0
- package/src/lib/utils.ts +4 -0
- package/src/types.ts +79 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Workaround for Next.js + Turbopack, while plugins are still
|
|
2
|
+
// not supported. This relies on Next.js Route Handlers and file
|
|
3
|
+
// name determinism.
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { type BuildResult, getFileManifestEntries, rebasePath } from "@serwist/build";
|
|
6
|
+
import { SerwistConfigError, validationErrorMap } from "@serwist/build/schema";
|
|
7
|
+
import { cyan, dim, yellow } from "kolorist";
|
|
8
|
+
import { NextResponse } from "next/server.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { injectManifestOptions } from "./index.schema.js";
|
|
11
|
+
import { logger } from "./lib/index.js";
|
|
12
|
+
import type { InjectManifestOptions, InjectManifestOptionsComplete } from "./types.js";
|
|
13
|
+
|
|
14
|
+
// TODO(workarond): `esbuild` doesn't load when in Turbopack production
|
|
15
|
+
const esbuild = import("esbuild-wasm");
|
|
16
|
+
|
|
17
|
+
const logSerwistResult = (filePath: string, buildResult: Pick<BuildResult, "count" | "size" | "warnings">) => {
|
|
18
|
+
const { count, size, warnings } = buildResult;
|
|
19
|
+
const hasWarnings = warnings && warnings.length > 0;
|
|
20
|
+
// The route is reinitiated for each `path` param, so we only log results
|
|
21
|
+
// if we're prerendering for sw.js.
|
|
22
|
+
if (filePath === "sw.js" && (hasWarnings || count > 0)) {
|
|
23
|
+
logger[hasWarnings ? "warn" : "event"](
|
|
24
|
+
`${cyan(count)} precache entries ${dim(`(${(size / 1024).toFixed(2)} KiB)`)}${
|
|
25
|
+
hasWarnings ? `\n${yellow(["⚠ warnings", ...warnings.map((w) => ` ${w}`), ""].join("\n"))}` : ""
|
|
26
|
+
}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const validateGetManifestOptions = async (input: unknown): Promise<InjectManifestOptionsComplete> => {
|
|
32
|
+
const result = await injectManifestOptions.spa(input, {
|
|
33
|
+
error: validationErrorMap,
|
|
34
|
+
});
|
|
35
|
+
if (!result.success) {
|
|
36
|
+
throw new SerwistConfigError({
|
|
37
|
+
moduleName: "@serwist/turbopack",
|
|
38
|
+
message: z.prettifyError(result.error),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return result.data;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
45
|
+
|
|
46
|
+
const contentTypeMap: Record<string, string> = {
|
|
47
|
+
".js": "application/javascript",
|
|
48
|
+
".map": "application/json; charset=UTF-8",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a Route Handler for Serwist files.
|
|
53
|
+
* @param options Options for {@linkcode getFileManifestEntries}.
|
|
54
|
+
*/
|
|
55
|
+
export const createSerwistRoute = (options: InjectManifestOptions) => {
|
|
56
|
+
const dynamic = "force-static" as const,
|
|
57
|
+
dynamicParams = false as const,
|
|
58
|
+
revalidate = false as const;
|
|
59
|
+
const validation = validateGetManifestOptions(options).then((config) => {
|
|
60
|
+
return {
|
|
61
|
+
...config,
|
|
62
|
+
disablePrecacheManifest: isDev,
|
|
63
|
+
additionalPrecacheEntries: isDev ? [] : config.additionalPrecacheEntries,
|
|
64
|
+
globIgnores: [
|
|
65
|
+
...config.globIgnores,
|
|
66
|
+
// Make sure we leave swSrc out of the precache manifest.
|
|
67
|
+
rebasePath({
|
|
68
|
+
file: config.swSrc,
|
|
69
|
+
baseDirectory: config.globDirectory,
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
manifestTransforms: [
|
|
73
|
+
...(config.manifestTransforms ?? []),
|
|
74
|
+
(manifestEntries) => {
|
|
75
|
+
const manifest = manifestEntries.map((m) => {
|
|
76
|
+
// Replace all references to "$(distDir)" with "$(assetPrefix)/_next/".
|
|
77
|
+
if (m.url.startsWith(config.nextConfig.distDir)) {
|
|
78
|
+
m.url = `${config.nextConfig.assetPrefix ?? ""}/_next/${m.url.slice(config.nextConfig.distDir.length)}`;
|
|
79
|
+
}
|
|
80
|
+
// Replace all references to public/ with "$(basePath)/".
|
|
81
|
+
if (m.url.startsWith("public/")) {
|
|
82
|
+
m.url = path.posix.join(config.nextConfig.basePath, m.url.slice(7));
|
|
83
|
+
}
|
|
84
|
+
return m;
|
|
85
|
+
});
|
|
86
|
+
return { manifest, warnings: [] };
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
let map: Map<string, string> | null = null;
|
|
92
|
+
// NOTE: ALL FILES MUST HAVE DETERMINISTIC NAMES. THIS IS BECAUSE
|
|
93
|
+
// THE FOLLOWING MAP IS LOADED SEPARATELY FOR `generateStaticParams`
|
|
94
|
+
// AND EVERY `GET` REQUEST TO EACH OF THE FILES.
|
|
95
|
+
const loadMap = async (filePath: string) => {
|
|
96
|
+
const config = await validation;
|
|
97
|
+
const { count, size, manifestEntries, warnings } = await getFileManifestEntries(config);
|
|
98
|
+
// See https://github.com/GoogleChrome/workbox/issues/2230
|
|
99
|
+
const injectionPoint = config.injectionPoint || "";
|
|
100
|
+
const manifestString = manifestEntries === undefined ? "undefined" : JSON.stringify(manifestEntries, null, 2);
|
|
101
|
+
logSerwistResult(filePath, { count, size, warnings });
|
|
102
|
+
const result = await (await esbuild).build({
|
|
103
|
+
sourcemap: true,
|
|
104
|
+
format: "esm",
|
|
105
|
+
target: ["chrome64", "edge79", "firefox67", "opera51", "safari12"],
|
|
106
|
+
treeShaking: true,
|
|
107
|
+
minify: !isDev,
|
|
108
|
+
bundle: true,
|
|
109
|
+
...config.esbuildOptions,
|
|
110
|
+
platform: "browser",
|
|
111
|
+
define: {
|
|
112
|
+
...config.esbuildOptions.define,
|
|
113
|
+
...(injectionPoint ? { [injectionPoint]: manifestString } : {}),
|
|
114
|
+
},
|
|
115
|
+
outdir: config.cwd,
|
|
116
|
+
write: false,
|
|
117
|
+
entryNames: "[name]",
|
|
118
|
+
// Asset and chunk names must be at the top, as our path is `/serwist/[path]`,
|
|
119
|
+
// not `/serwist/[...path]`, meaning that we can't resolve paths deeper
|
|
120
|
+
// than one level.
|
|
121
|
+
assetNames: "[name]-[hash]",
|
|
122
|
+
chunkNames: "[name]-[hash]",
|
|
123
|
+
entryPoints: [{ in: config.swSrc, out: "sw" }],
|
|
124
|
+
});
|
|
125
|
+
if (result.errors.length) {
|
|
126
|
+
console.error("Failed to build the service worker.", result.errors);
|
|
127
|
+
throw new Error();
|
|
128
|
+
}
|
|
129
|
+
if (result.warnings.length) {
|
|
130
|
+
console.warn(result.warnings);
|
|
131
|
+
}
|
|
132
|
+
return new Map(result.outputFiles.map((e) => [e.path, e.text]));
|
|
133
|
+
};
|
|
134
|
+
const generateStaticParams = async () => {
|
|
135
|
+
const config = await validation;
|
|
136
|
+
if (!map) map = await loadMap("root");
|
|
137
|
+
return [...map.keys().map((e) => ({ path: path.relative(config.cwd, e) }))];
|
|
138
|
+
};
|
|
139
|
+
const GET = async (_: Request, { params }: { params: Promise<{ path: string }> }) => {
|
|
140
|
+
// TODO: obviously, files get stale in development when we pull this off.
|
|
141
|
+
const { path: filePath } = await params;
|
|
142
|
+
const config = await validation;
|
|
143
|
+
if (!map) map = await loadMap(filePath);
|
|
144
|
+
return new NextResponse(map.get(path.join(config.cwd, filePath)), {
|
|
145
|
+
headers: {
|
|
146
|
+
"Content-Type": contentTypeMap[path.extname(filePath)] || "text/plain",
|
|
147
|
+
"Service-Worker-Allowed": "/",
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
return { dynamic, dynamicParams, revalidate, generateStaticParams, GET };
|
|
152
|
+
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import type { RuntimeCaching } from "serwist";
|
|
2
|
+
import { CacheFirst, ExpirationPlugin, NetworkFirst, NetworkOnly, RangeRequestsPlugin, StaleWhileRevalidate } from "serwist";
|
|
3
|
+
|
|
4
|
+
export const PAGES_CACHE_NAME = {
|
|
5
|
+
rscPrefetch: "pages-rsc-prefetch",
|
|
6
|
+
rsc: "pages-rsc",
|
|
7
|
+
html: "pages",
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The default, recommended list of caching strategies for applications
|
|
12
|
+
* built with Next.js.
|
|
13
|
+
*
|
|
14
|
+
* @see https://serwist.pages.dev/docs/next/worker-exports#default-cache
|
|
15
|
+
*/
|
|
16
|
+
export const defaultCache: RuntimeCaching[] =
|
|
17
|
+
process.env.NODE_ENV !== "production"
|
|
18
|
+
? [
|
|
19
|
+
{
|
|
20
|
+
matcher: /.*/i,
|
|
21
|
+
handler: new NetworkOnly(),
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
: [
|
|
25
|
+
{
|
|
26
|
+
matcher: /^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,
|
|
27
|
+
handler: new CacheFirst({
|
|
28
|
+
cacheName: "google-fonts-webfonts",
|
|
29
|
+
plugins: [
|
|
30
|
+
new ExpirationPlugin({
|
|
31
|
+
maxEntries: 4,
|
|
32
|
+
maxAgeSeconds: 365 * 24 * 60 * 60, // 365 days
|
|
33
|
+
maxAgeFrom: "last-used",
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
matcher: /^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,
|
|
40
|
+
handler: new StaleWhileRevalidate({
|
|
41
|
+
cacheName: "google-fonts-stylesheets",
|
|
42
|
+
plugins: [
|
|
43
|
+
new ExpirationPlugin({
|
|
44
|
+
maxEntries: 4,
|
|
45
|
+
maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days
|
|
46
|
+
maxAgeFrom: "last-used",
|
|
47
|
+
}),
|
|
48
|
+
],
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
matcher: /\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,
|
|
53
|
+
handler: new StaleWhileRevalidate({
|
|
54
|
+
cacheName: "static-font-assets",
|
|
55
|
+
plugins: [
|
|
56
|
+
new ExpirationPlugin({
|
|
57
|
+
maxEntries: 4,
|
|
58
|
+
maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days
|
|
59
|
+
maxAgeFrom: "last-used",
|
|
60
|
+
}),
|
|
61
|
+
],
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
matcher: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
|
|
66
|
+
handler: new StaleWhileRevalidate({
|
|
67
|
+
cacheName: "static-image-assets",
|
|
68
|
+
plugins: [
|
|
69
|
+
new ExpirationPlugin({
|
|
70
|
+
maxEntries: 64,
|
|
71
|
+
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
|
|
72
|
+
maxAgeFrom: "last-used",
|
|
73
|
+
}),
|
|
74
|
+
],
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
matcher: /\/_next\/static.+\.js$/i,
|
|
79
|
+
handler: new CacheFirst({
|
|
80
|
+
cacheName: "next-static-js-assets",
|
|
81
|
+
plugins: [
|
|
82
|
+
new ExpirationPlugin({
|
|
83
|
+
maxEntries: 64,
|
|
84
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
85
|
+
maxAgeFrom: "last-used",
|
|
86
|
+
}),
|
|
87
|
+
],
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
matcher: /\/_next\/image\?url=.+$/i,
|
|
92
|
+
handler: new StaleWhileRevalidate({
|
|
93
|
+
cacheName: "next-image",
|
|
94
|
+
plugins: [
|
|
95
|
+
new ExpirationPlugin({
|
|
96
|
+
maxEntries: 64,
|
|
97
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
98
|
+
maxAgeFrom: "last-used",
|
|
99
|
+
}),
|
|
100
|
+
],
|
|
101
|
+
}),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
matcher: /\.(?:mp3|wav|ogg)$/i,
|
|
105
|
+
handler: new CacheFirst({
|
|
106
|
+
cacheName: "static-audio-assets",
|
|
107
|
+
plugins: [
|
|
108
|
+
new ExpirationPlugin({
|
|
109
|
+
maxEntries: 32,
|
|
110
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
111
|
+
maxAgeFrom: "last-used",
|
|
112
|
+
}),
|
|
113
|
+
new RangeRequestsPlugin(),
|
|
114
|
+
],
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
matcher: /\.(?:mp4|webm)$/i,
|
|
119
|
+
handler: new CacheFirst({
|
|
120
|
+
cacheName: "static-video-assets",
|
|
121
|
+
plugins: [
|
|
122
|
+
new ExpirationPlugin({
|
|
123
|
+
maxEntries: 32,
|
|
124
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
125
|
+
maxAgeFrom: "last-used",
|
|
126
|
+
}),
|
|
127
|
+
new RangeRequestsPlugin(),
|
|
128
|
+
],
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
matcher: /\.(?:js)$/i,
|
|
133
|
+
handler: new StaleWhileRevalidate({
|
|
134
|
+
cacheName: "static-js-assets",
|
|
135
|
+
plugins: [
|
|
136
|
+
new ExpirationPlugin({
|
|
137
|
+
maxEntries: 48,
|
|
138
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
139
|
+
maxAgeFrom: "last-used",
|
|
140
|
+
}),
|
|
141
|
+
],
|
|
142
|
+
}),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
matcher: /\.(?:css|less)$/i,
|
|
146
|
+
handler: new StaleWhileRevalidate({
|
|
147
|
+
cacheName: "static-style-assets",
|
|
148
|
+
plugins: [
|
|
149
|
+
new ExpirationPlugin({
|
|
150
|
+
maxEntries: 32,
|
|
151
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
152
|
+
maxAgeFrom: "last-used",
|
|
153
|
+
}),
|
|
154
|
+
],
|
|
155
|
+
}),
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
matcher: /\/_next\/data\/.+\/.+\.json$/i,
|
|
159
|
+
handler: new NetworkFirst({
|
|
160
|
+
cacheName: "next-data",
|
|
161
|
+
plugins: [
|
|
162
|
+
new ExpirationPlugin({
|
|
163
|
+
maxEntries: 32,
|
|
164
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
165
|
+
maxAgeFrom: "last-used",
|
|
166
|
+
}),
|
|
167
|
+
],
|
|
168
|
+
}),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
matcher: /\.(?:json|xml|csv)$/i,
|
|
172
|
+
handler: new NetworkFirst({
|
|
173
|
+
cacheName: "static-data-assets",
|
|
174
|
+
plugins: [
|
|
175
|
+
new ExpirationPlugin({
|
|
176
|
+
maxEntries: 32,
|
|
177
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
178
|
+
maxAgeFrom: "last-used",
|
|
179
|
+
}),
|
|
180
|
+
],
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
// Exclude /api/auth/* to fix auth callback
|
|
185
|
+
// https://github.com/serwist/serwist/discussions/28
|
|
186
|
+
matcher: /\/api\/auth\/.*/,
|
|
187
|
+
handler: new NetworkOnly({
|
|
188
|
+
networkTimeoutSeconds: 10, // fallback to cache if API does not response within 10 seconds
|
|
189
|
+
}),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
matcher: ({ sameOrigin, url: { pathname } }) => sameOrigin && pathname.startsWith("/api/"),
|
|
193
|
+
method: "GET",
|
|
194
|
+
handler: new NetworkFirst({
|
|
195
|
+
cacheName: "apis",
|
|
196
|
+
plugins: [
|
|
197
|
+
new ExpirationPlugin({
|
|
198
|
+
maxEntries: 16,
|
|
199
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
200
|
+
maxAgeFrom: "last-used",
|
|
201
|
+
}),
|
|
202
|
+
],
|
|
203
|
+
networkTimeoutSeconds: 10, // fallback to cache if API does not response within 10 seconds
|
|
204
|
+
}),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
matcher: ({ request, url: { pathname }, sameOrigin }) =>
|
|
208
|
+
request.headers.get("RSC") === "1" && request.headers.get("Next-Router-Prefetch") === "1" && sameOrigin && !pathname.startsWith("/api/"),
|
|
209
|
+
handler: new NetworkFirst({
|
|
210
|
+
cacheName: PAGES_CACHE_NAME.rscPrefetch,
|
|
211
|
+
plugins: [
|
|
212
|
+
new ExpirationPlugin({
|
|
213
|
+
maxEntries: 32,
|
|
214
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
215
|
+
}),
|
|
216
|
+
],
|
|
217
|
+
}),
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
matcher: ({ request, url: { pathname }, sameOrigin }) => request.headers.get("RSC") === "1" && sameOrigin && !pathname.startsWith("/api/"),
|
|
221
|
+
handler: new NetworkFirst({
|
|
222
|
+
cacheName: PAGES_CACHE_NAME.rsc,
|
|
223
|
+
plugins: [
|
|
224
|
+
new ExpirationPlugin({
|
|
225
|
+
maxEntries: 32,
|
|
226
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
227
|
+
}),
|
|
228
|
+
],
|
|
229
|
+
}),
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
matcher: ({ request, url: { pathname }, sameOrigin }) =>
|
|
233
|
+
request.headers.get("Content-Type")?.includes("text/html") && sameOrigin && !pathname.startsWith("/api/"),
|
|
234
|
+
handler: new NetworkFirst({
|
|
235
|
+
cacheName: PAGES_CACHE_NAME.html,
|
|
236
|
+
plugins: [
|
|
237
|
+
new ExpirationPlugin({
|
|
238
|
+
maxEntries: 32,
|
|
239
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
240
|
+
}),
|
|
241
|
+
],
|
|
242
|
+
}),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
matcher: ({ url: { pathname }, sameOrigin }) => sameOrigin && !pathname.startsWith("/api/"),
|
|
246
|
+
handler: new NetworkFirst({
|
|
247
|
+
cacheName: "others",
|
|
248
|
+
plugins: [
|
|
249
|
+
new ExpirationPlugin({
|
|
250
|
+
maxEntries: 32,
|
|
251
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
252
|
+
}),
|
|
253
|
+
],
|
|
254
|
+
}),
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
matcher: ({ sameOrigin }) => !sameOrigin,
|
|
258
|
+
handler: new NetworkFirst({
|
|
259
|
+
cacheName: "cross-origin",
|
|
260
|
+
plugins: [
|
|
261
|
+
new ExpirationPlugin({
|
|
262
|
+
maxEntries: 32,
|
|
263
|
+
maxAgeSeconds: 60 * 60, // 1 hour
|
|
264
|
+
}),
|
|
265
|
+
],
|
|
266
|
+
networkTimeoutSeconds: 10,
|
|
267
|
+
}),
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
matcher: /.*/i,
|
|
271
|
+
method: "GET",
|
|
272
|
+
handler: new NetworkOnly(),
|
|
273
|
+
},
|
|
274
|
+
];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { BuildOptions } from "esbuild-wasm";
|
|
2
|
+
|
|
3
|
+
export const SUPPORTED_ESBUILD_OPTIONS = [
|
|
4
|
+
// CommonOptions
|
|
5
|
+
"sourcemap",
|
|
6
|
+
"legalComments",
|
|
7
|
+
"sourceRoot",
|
|
8
|
+
"sourcesContent",
|
|
9
|
+
"format",
|
|
10
|
+
"globalName",
|
|
11
|
+
"target",
|
|
12
|
+
"supported",
|
|
13
|
+
"define",
|
|
14
|
+
"treeShaking",
|
|
15
|
+
"minify",
|
|
16
|
+
"mangleProps",
|
|
17
|
+
"reserveProps",
|
|
18
|
+
"mangleQuoted",
|
|
19
|
+
"mangleCache",
|
|
20
|
+
"drop",
|
|
21
|
+
"dropLabels",
|
|
22
|
+
"minifyWhitespace",
|
|
23
|
+
"minifyIdentifiers",
|
|
24
|
+
"minifySyntax",
|
|
25
|
+
"lineLimit",
|
|
26
|
+
"charset",
|
|
27
|
+
"ignoreAnnotations",
|
|
28
|
+
"jsx",
|
|
29
|
+
"jsxFactory",
|
|
30
|
+
"jsxFragment",
|
|
31
|
+
"jsxImportSource",
|
|
32
|
+
"jsxDev",
|
|
33
|
+
"jsxSideEffects",
|
|
34
|
+
"pure",
|
|
35
|
+
"keepNames",
|
|
36
|
+
"absPaths",
|
|
37
|
+
"color",
|
|
38
|
+
"logLevel",
|
|
39
|
+
"logLimit",
|
|
40
|
+
"logOverride",
|
|
41
|
+
"tsconfigRaw",
|
|
42
|
+
// BuildOptions
|
|
43
|
+
"bundle",
|
|
44
|
+
"splitting",
|
|
45
|
+
"preserveSymlinks",
|
|
46
|
+
"external",
|
|
47
|
+
"packages",
|
|
48
|
+
"alias",
|
|
49
|
+
"loader",
|
|
50
|
+
"resolveExtensions",
|
|
51
|
+
"mainFields",
|
|
52
|
+
"conditions",
|
|
53
|
+
"allowOverwrite",
|
|
54
|
+
"tsconfig",
|
|
55
|
+
"outExtension",
|
|
56
|
+
"publicPath",
|
|
57
|
+
"inject",
|
|
58
|
+
"banner",
|
|
59
|
+
"footer",
|
|
60
|
+
"plugins",
|
|
61
|
+
] as const satisfies readonly (keyof BuildOptions)[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Serwist } from "@serwist/window";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
|
|
4
|
+
export interface SerwistContextValues {
|
|
5
|
+
serwist: Serwist | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const SerwistContext = createContext<SerwistContextValues>(null!);
|
|
9
|
+
|
|
10
|
+
export const useSerwist = () => {
|
|
11
|
+
const context = useContext(SerwistContext);
|
|
12
|
+
if (!context) {
|
|
13
|
+
throw new Error("[useSerwist]: 'SerwistContext' is not available.");
|
|
14
|
+
}
|
|
15
|
+
return context;
|
|
16
|
+
};
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { bold, green, red, white, yellow } from "kolorist";
|
|
3
|
+
import semver from "semver";
|
|
4
|
+
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
|
|
7
|
+
const LOGGING_SPACE_PREFIX = semver.gte(require("next/package.json").version, "16.0.0") ? "" : " ";
|
|
8
|
+
|
|
9
|
+
export type LoggingMethods = "wait" | "error" | "warn" | "info" | "event";
|
|
10
|
+
|
|
11
|
+
const prefixedLog = (prefixType: LoggingMethods, ...message: any[]) => {
|
|
12
|
+
let prefix: string;
|
|
13
|
+
let consoleMethod: keyof Console;
|
|
14
|
+
|
|
15
|
+
switch (prefixType) {
|
|
16
|
+
case "wait":
|
|
17
|
+
prefix = `${white(bold("○"))} (serwist)`;
|
|
18
|
+
consoleMethod = "log";
|
|
19
|
+
break;
|
|
20
|
+
case "error":
|
|
21
|
+
prefix = `${red(bold("X"))} (serwist)`;
|
|
22
|
+
consoleMethod = "error";
|
|
23
|
+
break;
|
|
24
|
+
case "warn":
|
|
25
|
+
prefix = `${yellow(bold("⚠"))} (serwist)`;
|
|
26
|
+
consoleMethod = "warn";
|
|
27
|
+
break;
|
|
28
|
+
case "info":
|
|
29
|
+
prefix = `${white(bold("○"))} (serwist)`;
|
|
30
|
+
consoleMethod = "log";
|
|
31
|
+
break;
|
|
32
|
+
case "event":
|
|
33
|
+
prefix = `${green(bold("✓"))} (serwist)`;
|
|
34
|
+
consoleMethod = "log";
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if ((message[0] === "" || message[0] === undefined) && message.length === 1) {
|
|
39
|
+
message.shift();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If there's no message, don't print the prefix but a new line
|
|
43
|
+
if (message.length === 0) {
|
|
44
|
+
console[consoleMethod]("");
|
|
45
|
+
} else {
|
|
46
|
+
console[consoleMethod](`${LOGGING_SPACE_PREFIX}${prefix}`, ...message);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const wait = (...message: any[]) => prefixedLog("wait", ...message);
|
|
51
|
+
|
|
52
|
+
export const error = (...message: any[]) => prefixedLog("error", ...message);
|
|
53
|
+
|
|
54
|
+
export const warn = (...message: any[]) => prefixedLog("warn", ...message);
|
|
55
|
+
|
|
56
|
+
export const info = (...message: any[]) => prefixedLog("info", ...message);
|
|
57
|
+
|
|
58
|
+
export const event = (...message: any[]) => prefixedLog("event", ...message);
|
package/src/lib/utils.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BasePartial,
|
|
3
|
+
BaseResolved,
|
|
4
|
+
GlobPartial,
|
|
5
|
+
GlobResolved,
|
|
6
|
+
InjectPartial,
|
|
7
|
+
InjectResolved,
|
|
8
|
+
OptionalGlobDirectoryPartial,
|
|
9
|
+
RequiredGlobDirectoryResolved,
|
|
10
|
+
} from "@serwist/build";
|
|
11
|
+
import type { Prettify, Require } from "@serwist/utils";
|
|
12
|
+
import type { BuildOptions } from "esbuild-wasm";
|
|
13
|
+
import type { SUPPORTED_ESBUILD_OPTIONS } from "./lib/constants.js";
|
|
14
|
+
import type { NextConfig as CompleteNextConfig } from "next";
|
|
15
|
+
|
|
16
|
+
export type EsbuildSupportedOptions = (typeof SUPPORTED_ESBUILD_OPTIONS)[number];
|
|
17
|
+
|
|
18
|
+
export type EsbuildOptions = Pick<BuildOptions, EsbuildSupportedOptions>;
|
|
19
|
+
|
|
20
|
+
export interface NextConfig extends Pick<CompleteNextConfig, "basePath" | "distDir"> {
|
|
21
|
+
/**
|
|
22
|
+
* The Next.js `assetPrefix` config option.
|
|
23
|
+
*
|
|
24
|
+
* @see https://nextjs.org/docs/app/api-reference/config/next-config-js/assetPrefix
|
|
25
|
+
*/
|
|
26
|
+
assetPrefix?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The Next.js `basePath` config option.
|
|
29
|
+
*
|
|
30
|
+
* @default "/"
|
|
31
|
+
* @see https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath
|
|
32
|
+
*/
|
|
33
|
+
basePath?: string;
|
|
34
|
+
/**
|
|
35
|
+
* The Next.js `distDir` config option.
|
|
36
|
+
*
|
|
37
|
+
* @default ".next"
|
|
38
|
+
* @see https://nextjs.org/docs/app/api-reference/config/next-config-js/distDir
|
|
39
|
+
*/
|
|
40
|
+
distDir?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface TurboPartial {
|
|
44
|
+
/**
|
|
45
|
+
* The path to your working directory.
|
|
46
|
+
*
|
|
47
|
+
* @default process.cwd()
|
|
48
|
+
*/
|
|
49
|
+
cwd?: string;
|
|
50
|
+
/**
|
|
51
|
+
* A copy of your Next.js configuration. You must check
|
|
52
|
+
* if any option you've configured is needed by Serwist
|
|
53
|
+
* to ensure expected behavior.
|
|
54
|
+
*
|
|
55
|
+
* The following options are currently needed: `assetPrefix`,
|
|
56
|
+
* `basePath`, `distDir`.
|
|
57
|
+
*/
|
|
58
|
+
nextConfig: Prettify<NextConfig>;
|
|
59
|
+
/**
|
|
60
|
+
* Options to configure the esbuild instance used to bundle
|
|
61
|
+
* the service worker.
|
|
62
|
+
*/
|
|
63
|
+
esbuildOptions?: EsbuildOptions;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface TurboResolved extends Require<TurboPartial, "cwd" | "esbuildOptions"> {
|
|
67
|
+
nextConfig: Require<NextConfig, "basePath" | "distDir">;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type InjectManifestOptions = Prettify<
|
|
71
|
+
Omit<BasePartial & GlobPartial & InjectPartial & OptionalGlobDirectoryPartial & TurboPartial, "disablePrecacheManifest">
|
|
72
|
+
>;
|
|
73
|
+
|
|
74
|
+
export type InjectManifestOptionsComplete = Prettify<
|
|
75
|
+
Omit<
|
|
76
|
+
Require<BaseResolved, "dontCacheBustURLsMatching"> & GlobResolved & InjectResolved & RequiredGlobDirectoryResolved & TurboResolved,
|
|
77
|
+
"disablePrecacheManifest"
|
|
78
|
+
>
|
|
79
|
+
>;
|