@serwist/next 9.3.0 → 9.3.1

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.
@@ -0,0 +1,7 @@
1
+ import type { BuildOptions } from "@serwist/cli";
2
+ import type { SerwistOptions } from "./lib/config/types.js";
3
+ import { generateGlobPatterns } from "./lib/config/utils.js";
4
+ export declare const serwist: (options: SerwistOptions) => Promise<BuildOptions>;
5
+ export { generateGlobPatterns };
6
+ export type { SerwistOptions };
7
+ //# sourceMappingURL=index.config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.config.d.ts","sourceRoot":"","sources":["../src/index.config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAI7D,eAAO,MAAM,OAAO,GAAU,SAAS,cAAc,KAAG,OAAO,CAAC,YAAY,CA4E3E,CAAC;AAEF,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAEhC,YAAY,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,100 @@
1
+ import { createRequire } from 'module';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { rebasePath } from '@serwist/build';
5
+ import { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } from 'next/constants.js';
6
+
7
+ const generateGlobPatterns = (distDir)=>[
8
+ `${distDir}static/**/*.{js,css,html,ico,apng,png,avif,jpg,jpeg,jfif,pjpeg,pjp,gif,svg,webp,json,webmanifest}`,
9
+ "public/**/*"
10
+ ];
11
+
12
+ const __require = createRequire(import.meta.url);
13
+ const loadNextConfig = __require("next/dist/server/config.js");
14
+ const serwist = async (options)=>{
15
+ const isWatch = process.env.SERWIST_ENV === "watch";
16
+ const nextPhase = isWatch ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_BUILD;
17
+ const config = await loadNextConfig.default(nextPhase, process.cwd(), {
18
+ silent: false
19
+ });
20
+ const basePath = config.basePath || "/";
21
+ let distDir = config.distDir;
22
+ if (distDir[0] === "/") distDir = distDir.slice(1);
23
+ if (distDir[distDir.length - 1] !== "/") distDir += "/";
24
+ const distServerDir = `${distDir}server/`;
25
+ const distAppDir = `${distServerDir}app/`;
26
+ const distPagesDir = `${distServerDir}pages/`;
27
+ const { precachePrerendered = true, globDirectory = process.cwd(), ...cliOptions } = options;
28
+ for (const file of [
29
+ cliOptions.swDest,
30
+ `${cliOptions.swDest}.map`
31
+ ]){
32
+ fs.rmSync(file, {
33
+ force: true
34
+ });
35
+ }
36
+ return {
37
+ dontCacheBustURLsMatching: new RegExp(`^${distDir}static/`),
38
+ disablePrecacheManifest: isWatch,
39
+ ...cliOptions,
40
+ globDirectory,
41
+ globPatterns: [
42
+ ...cliOptions.globPatterns ?? generateGlobPatterns(distDir),
43
+ ...precachePrerendered ? [
44
+ `${distServerDir}{app,pages}/**/*.html`
45
+ ] : []
46
+ ],
47
+ globIgnores: [
48
+ `${distAppDir}**/_not-found.html`,
49
+ `${distPagesDir}404.html`,
50
+ `${distPagesDir}500.html`,
51
+ ...cliOptions.globIgnores ?? [],
52
+ rebasePath({
53
+ baseDirectory: globDirectory,
54
+ file: cliOptions.swSrc
55
+ }),
56
+ rebasePath({
57
+ baseDirectory: globDirectory,
58
+ file: cliOptions.swDest
59
+ }),
60
+ rebasePath({
61
+ baseDirectory: globDirectory,
62
+ file: `${cliOptions.swDest}.map`
63
+ })
64
+ ],
65
+ manifestTransforms: [
66
+ ...cliOptions.manifestTransforms ?? [],
67
+ (manifestEntries)=>{
68
+ const manifest = manifestEntries.map((m)=>{
69
+ if (m.url.startsWith(distAppDir)) {
70
+ m.url = m.url.slice(distAppDir.length - 1);
71
+ }
72
+ if (m.url.startsWith(distPagesDir)) {
73
+ m.url = m.url.slice(distPagesDir.length - 1);
74
+ }
75
+ if (m.url.endsWith(".html")) {
76
+ if (m.url.endsWith("/index.html")) {
77
+ m.url = m.url.slice(0, m.url.lastIndexOf("/") + 1);
78
+ } else {
79
+ m.url = m.url.substring(0, m.url.lastIndexOf("."));
80
+ }
81
+ m.url = path.posix.join(basePath, m.url);
82
+ }
83
+ if (m.url.startsWith(distDir)) {
84
+ m.url = `${config.assetPrefix ?? ""}/_next/${m.url.slice(distDir.length)}`;
85
+ }
86
+ if (m.url.startsWith("public/")) {
87
+ m.url = path.posix.join(basePath, m.url.slice(7));
88
+ }
89
+ return m;
90
+ });
91
+ return {
92
+ manifest,
93
+ warnings: []
94
+ };
95
+ }
96
+ ]
97
+ };
98
+ };
99
+
100
+ export { generateGlobPatterns, serwist };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAIvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAMnE;;;;GAIG;AACH,QAAA,MAAM,eAAe,GAAI,aAAa,qBAAqB,KAAG,CAAC,CAAC,UAAU,CAAC,EAAE,UAAU,KAAK,UAAU,CAgOrG,CAAC;AAEF,eAAe,eAAe,CAAC;AAC/B,OAAO,EAAE,6BAA6B,EAAE,CAAC;AACzC,YAAY,EAAE,qBAAqB,IAAI,aAAa,EAAE,6BAA6B,IAAI,qBAAqB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAIvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAMnE;;;;GAIG;AACH,QAAA,MAAM,eAAe,GAAI,aAAa,qBAAqB,KAAG,CAAC,CAAC,UAAU,CAAC,EAAE,UAAU,KAAK,UAAU,CA6NrG,CAAC;AAEF,eAAe,eAAe,CAAC;AAC/B,OAAO,EAAE,6BAA6B,EAAE,CAAC;AACzC,YAAY,EAAE,qBAAqB,IAAI,aAAa,EAAE,6BAA6B,IAAI,qBAAqB,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -194,10 +194,8 @@ const withSerwistInit = (userOptions)=>{
194
194
  cwd: destDir
195
195
  });
196
196
  for (const file of cleanUpList){
197
- fs.rm(file, {
197
+ fs.rmSync(file, {
198
198
  force: true
199
- }, (err)=>{
200
- if (err) throw err;
201
199
  });
202
200
  }
203
201
  const shouldBuildSWEntryWorker = cacheOnNavigation;
@@ -0,0 +1,19 @@
1
+ import { Serwist } from "@serwist/window";
2
+ import { type ReactNode } from "react";
3
+ import { useSerwist } from "./lib/context.js";
4
+ export interface SerwistProviderProps {
5
+ swUrl: string;
6
+ register?: boolean;
7
+ cacheOnNavigation?: boolean;
8
+ reloadOnOnline?: boolean;
9
+ options?: RegistrationOptions;
10
+ children?: ReactNode;
11
+ }
12
+ declare global {
13
+ interface Window {
14
+ serwist: Serwist;
15
+ }
16
+ }
17
+ export declare function SerwistProvider({ swUrl, register, cacheOnNavigation, reloadOnOnline, options, children, }: SerwistProviderProps): import("react").JSX.Element;
18
+ export { useSerwist };
19
+ //# sourceMappingURL=index.react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.react.d.ts","sourceRoot":"","sources":["../src/index.react.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,OAAO,EAAE,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAkB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9D,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,OAAO,EAAE,OAAO,CAAC;KAClB;CACF;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAe,EACf,iBAAwB,EACxB,cAAqB,EACrB,OAAO,EACP,QAAQ,GACT,EAAE,oBAAoB,+BAwDtB;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -0,0 +1,93 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { Serwist } from '@serwist/window';
3
+ import { isCurrentPageOutOfScope } from '@serwist/window/internal';
4
+ import { createContext, useContext, useState, useEffect } from 'react';
5
+
6
+ const SerwistContext = createContext(null);
7
+ const useSerwist = ()=>{
8
+ const context = useContext(SerwistContext);
9
+ if (!context) {
10
+ throw new Error("[useSerwist]: 'SerwistContext' is not available.");
11
+ }
12
+ return context;
13
+ };
14
+
15
+ function SerwistProvider({ swUrl, register = true, cacheOnNavigation = true, reloadOnOnline = true, options, children }) {
16
+ const [serwist] = useState(()=>{
17
+ if (typeof window === "undefined") return null;
18
+ if (!(window.serwist && window.serwist instanceof Serwist) && "serviceWorker" in navigator) {
19
+ window.serwist = new Serwist(swUrl, {
20
+ ...options,
21
+ type: options?.type || "module",
22
+ scope: options?.scope || "/"
23
+ });
24
+ }
25
+ return window.serwist ?? null;
26
+ });
27
+ useEffect(()=>{
28
+ const scope = options?.scope || "/";
29
+ if (register && !isCurrentPageOutOfScope(scope)) {
30
+ void serwist?.register();
31
+ }
32
+ }, [
33
+ register,
34
+ options?.scope,
35
+ serwist?.register
36
+ ]);
37
+ useEffect(()=>{
38
+ const cacheUrls = async (url)=>{
39
+ if (!window.navigator.onLine || !url) {
40
+ return;
41
+ }
42
+ serwist?.messageSW({
43
+ type: "CACHE_URLS",
44
+ payload: {
45
+ urlsToCache: [
46
+ url
47
+ ]
48
+ }
49
+ });
50
+ };
51
+ const cacheCurrentPathname = ()=>cacheUrls(window.location.pathname);
52
+ const pushState = history.pushState;
53
+ const replaceState = history.replaceState;
54
+ if (cacheOnNavigation) {
55
+ history.pushState = (...args)=>{
56
+ pushState.apply(history, args);
57
+ cacheUrls(args[2]);
58
+ };
59
+ history.replaceState = (...args)=>{
60
+ replaceState.apply(history, args);
61
+ cacheUrls(args[2]);
62
+ };
63
+ window.addEventListener("online", cacheCurrentPathname);
64
+ }
65
+ return ()=>{
66
+ history.pushState = pushState;
67
+ history.replaceState = replaceState;
68
+ window.removeEventListener("online", cacheCurrentPathname);
69
+ };
70
+ }, [
71
+ serwist?.messageSW,
72
+ cacheOnNavigation
73
+ ]);
74
+ useEffect(()=>{
75
+ const reload = ()=>location.reload();
76
+ if (reloadOnOnline) {
77
+ window.addEventListener("online", reload);
78
+ }
79
+ return ()=>{
80
+ window.removeEventListener("online", reload);
81
+ };
82
+ }, [
83
+ reloadOnOnline
84
+ ]);
85
+ return jsx(SerwistContext.Provider, {
86
+ value: {
87
+ serwist
88
+ },
89
+ children: children
90
+ });
91
+ }
92
+
93
+ export { SerwistProvider, useSerwist };
@@ -0,0 +1,11 @@
1
+ import type { BuildOptions } from "@serwist/cli";
2
+ import type { Optional } from "@serwist/utils";
3
+ export interface SerwistOptions extends Optional<BuildOptions, "globDirectory"> {
4
+ /**
5
+ * Whether Serwist should precache prerendered routes.
6
+ *
7
+ * @default true
8
+ */
9
+ precachePrerendered?: boolean;
10
+ }
11
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAAC,YAAY,EAAE,eAAe,CAAC;IAC7E;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B"}
@@ -0,0 +1,2 @@
1
+ export declare const generateGlobPatterns: (distDir: string) => string[];
2
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/config/utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,aAGnD,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Serwist } from "@serwist/window";
2
+ export interface SerwistContextValues {
3
+ serwist: Serwist | null;
4
+ }
5
+ export declare const SerwistContext: import("react").Context<SerwistContextValues>;
6
+ export declare const useSerwist: () => SerwistContextValues;
7
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/lib/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB;AAED,eAAO,MAAM,cAAc,+CAA6C,CAAC;AAEzE,eAAO,MAAM,UAAU,4BAMtB,CAAC"}
@@ -9,22 +9,6 @@ export declare const injectPartial: z.ZodObject<{
9
9
  globPublicPatterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
10
10
  }, z.core.$strict>;
11
11
  export declare const injectManifestOptions: z.ZodObject<{
12
- cacheOnNavigation: z.ZodDefault<z.ZodBoolean>;
13
- disable: z.ZodDefault<z.ZodBoolean>;
14
- register: z.ZodDefault<z.ZodBoolean>;
15
- reloadOnOnline: z.ZodDefault<z.ZodBoolean>;
16
- scope: z.ZodOptional<z.ZodString>;
17
- swUrl: z.ZodDefault<z.ZodString>;
18
- globPublicPatterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
19
- swDest: z.ZodString;
20
- compileSrc: z.ZodDefault<z.ZodBoolean>;
21
- webpackCompilationPlugins: z.ZodOptional<z.ZodArray<z.ZodAny>>;
22
- injectionPoint: z.ZodDefault<z.ZodString>;
23
- swSrc: z.ZodString;
24
- chunks: z.ZodOptional<z.ZodArray<z.ZodString>>;
25
- exclude: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<RegExp, RegExp>, z.ZodPipe<z.ZodCustom<z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>, z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>>, z.ZodTransform<(args_0: any) => boolean, z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>>>]>>>;
26
- excludeChunks: z.ZodOptional<z.ZodArray<z.ZodString>>;
27
- include: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<RegExp, RegExp>, z.ZodPipe<z.ZodCustom<z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>, z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>>, z.ZodTransform<(args_0: any) => boolean, z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>>>]>>>;
28
12
  additionalPrecacheEntries: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
29
13
  integrity: z.ZodOptional<z.ZodString>;
30
14
  revision: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -86,5 +70,21 @@ export declare const injectManifestOptions: z.ZodObject<{
86
70
  }, z.core.$strict>>>>>>;
87
71
  maximumFileSizeToCacheInBytes: z.ZodDefault<z.ZodNumber>;
88
72
  modifyURLPrefix: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
73
+ injectionPoint: z.ZodDefault<z.ZodString>;
74
+ swSrc: z.ZodString;
75
+ swDest: z.ZodString;
76
+ cacheOnNavigation: z.ZodDefault<z.ZodBoolean>;
77
+ disable: z.ZodDefault<z.ZodBoolean>;
78
+ register: z.ZodDefault<z.ZodBoolean>;
79
+ reloadOnOnline: z.ZodDefault<z.ZodBoolean>;
80
+ scope: z.ZodOptional<z.ZodString>;
81
+ swUrl: z.ZodDefault<z.ZodString>;
82
+ globPublicPatterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
83
+ compileSrc: z.ZodDefault<z.ZodBoolean>;
84
+ webpackCompilationPlugins: z.ZodOptional<z.ZodArray<z.ZodAny>>;
85
+ chunks: z.ZodOptional<z.ZodArray<z.ZodString>>;
86
+ exclude: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<RegExp, RegExp>, z.ZodPipe<z.ZodCustom<z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>, z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>>, z.ZodTransform<(args_0: any) => boolean, z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>>>]>>>;
87
+ excludeChunks: z.ZodOptional<z.ZodArray<z.ZodString>>;
88
+ include: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<RegExp, RegExp>, z.ZodPipe<z.ZodCustom<z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>, z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>>, z.ZodTransform<(args_0: any) => boolean, z.core.$InferInnerFunctionType<z.ZodTuple<[z.ZodAny], null>, z.ZodBoolean>>>]>>>;
89
89
  }, z.core.$strict>;
90
90
  //# sourceMappingURL=schema.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serwist/next",
3
- "version": "9.3.0",
3
+ "version": "9.3.1",
4
4
  "type": "module",
5
5
  "description": "A module that integrates Serwist into your Next.js application.",
6
6
  "files": [
@@ -32,14 +32,20 @@
32
32
  "types": "./dist/index.d.ts",
33
33
  "typesVersions": {
34
34
  "*": {
35
- "worker": [
36
- "./dist/index.worker.d.ts"
35
+ "config": [
36
+ "./dist/index.config.d.ts"
37
37
  ],
38
- "typings": [
39
- "./dist/sw-entry.d.ts"
38
+ "react": [
39
+ "./dist/index.react.d.ts"
40
40
  ],
41
41
  "schema": [
42
42
  "./dist/index.schema.d.ts"
43
+ ],
44
+ "typings": [
45
+ "./dist/sw-entry.d.ts"
46
+ ],
47
+ "worker": [
48
+ "./dist/index.worker.d.ts"
43
49
  ]
44
50
  }
45
51
  },
@@ -48,17 +54,25 @@
48
54
  "types": "./dist/index.d.ts",
49
55
  "default": "./dist/index.js"
50
56
  },
51
- "./worker": {
52
- "types": "./dist/index.worker.d.ts",
53
- "default": "./dist/index.worker.js"
57
+ "./config": {
58
+ "types": "./dist/index.config.d.ts",
59
+ "default": "./dist/index.config.js"
54
60
  },
55
- "./typings": {
56
- "types": "./dist/sw-entry.d.ts"
61
+ "./react": {
62
+ "types": "./dist/index.react.d.ts",
63
+ "default": "./dist/index.react.js"
57
64
  },
58
65
  "./schema": {
59
66
  "types": "./dist/index.schema.d.ts",
60
67
  "default": "./dist/index.schema.js"
61
68
  },
69
+ "./typings": {
70
+ "types": "./dist/sw-entry.d.ts"
71
+ },
72
+ "./worker": {
73
+ "types": "./dist/index.worker.d.ts",
74
+ "default": "./dist/index.worker.js"
75
+ },
62
76
  "./package.json": "./package.json"
63
77
  },
64
78
  "dependencies": {
@@ -66,13 +80,14 @@
66
80
  "kolorist": "1.8.0",
67
81
  "semver": "7.7.3",
68
82
  "zod": "4.2.1",
69
- "@serwist/build": "9.3.0",
70
- "@serwist/webpack-plugin": "9.3.0",
71
- "@serwist/window": "9.3.0",
72
- "serwist": "9.3.0"
83
+ "@serwist/build": "9.3.1",
84
+ "@serwist/webpack-plugin": "9.3.1",
85
+ "@serwist/window": "9.3.1",
86
+ "serwist": "9.3.1"
73
87
  },
74
88
  "devDependencies": {
75
89
  "@types/node": "25.0.3",
90
+ "@types/react": "19.2.7",
76
91
  "@types/semver": "7.7.1",
77
92
  "next": "16.1.0",
78
93
  "react": "19.2.3",
@@ -81,14 +96,20 @@
81
96
  "type-fest": "5.3.1",
82
97
  "typescript": "5.9.3",
83
98
  "webpack": "5.104.1",
84
- "@serwist/configs": "9.3.0",
85
- "@serwist/utils": "9.3.0"
99
+ "@serwist/cli": "9.3.1",
100
+ "@serwist/configs": "9.3.1",
101
+ "@serwist/utils": "9.3.1"
86
102
  },
87
103
  "peerDependencies": {
88
104
  "next": ">=14.0.0",
89
- "typescript": ">=5.0.0"
105
+ "react": ">=18.0.0",
106
+ "typescript": ">=5.0.0",
107
+ "@serwist/cli": "9.3.1"
90
108
  },
91
109
  "peerDependenciesMeta": {
110
+ "@serwist/cli": {
111
+ "optional": true
112
+ },
92
113
  "typescript": {
93
114
  "optional": true
94
115
  }
@@ -0,0 +1,91 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { rebasePath } from "@serwist/build";
4
+ import type { BuildOptions } from "@serwist/cli";
5
+ import { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } from "next/constants.js";
6
+ import type { SerwistOptions } from "./lib/config/types.js";
7
+ import { generateGlobPatterns } from "./lib/config/utils.js";
8
+
9
+ import loadNextConfig = require("next/dist/server/config.js");
10
+
11
+ export const serwist = async (options: SerwistOptions): Promise<BuildOptions> => {
12
+ const isWatch = process.env.SERWIST_ENV === "watch";
13
+ const nextPhase = isWatch ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_BUILD;
14
+ const config = await loadNextConfig.default(nextPhase, process.cwd(), {
15
+ silent: false,
16
+ });
17
+ const basePath = config.basePath || "/";
18
+ let distDir = config.distDir;
19
+ if (distDir[0] === "/") distDir = distDir.slice(1);
20
+ if (distDir[distDir.length - 1] !== "/") distDir += "/";
21
+ const distServerDir = `${distDir}server/`;
22
+ const distAppDir = `${distServerDir}app/`;
23
+ const distPagesDir = `${distServerDir}pages/`;
24
+ const { precachePrerendered = true, globDirectory = process.cwd(), ...cliOptions } = options;
25
+ for (const file of [cliOptions.swDest, `${cliOptions.swDest}.map`]) {
26
+ fs.rmSync(file, { force: true });
27
+ }
28
+ return {
29
+ dontCacheBustURLsMatching: new RegExp(`^${distDir}static/`),
30
+ disablePrecacheManifest: isWatch,
31
+ ...cliOptions,
32
+ globDirectory,
33
+ globPatterns: [
34
+ ...(cliOptions.globPatterns ?? generateGlobPatterns(distDir)),
35
+ ...(precachePrerendered ? [`${distServerDir}{app,pages}/**/*.html`] : []),
36
+ ],
37
+ globIgnores: [
38
+ `${distAppDir}**/_not-found.html`,
39
+ `${distPagesDir}404.html`,
40
+ `${distPagesDir}500.html`,
41
+ ...(cliOptions.globIgnores ?? []),
42
+ rebasePath({ baseDirectory: globDirectory, file: cliOptions.swSrc }),
43
+ rebasePath({ baseDirectory: globDirectory, file: cliOptions.swDest }),
44
+ rebasePath({ baseDirectory: globDirectory, file: `${cliOptions.swDest}.map` }),
45
+ ],
46
+ manifestTransforms: [
47
+ ...(cliOptions.manifestTransforms ?? []),
48
+ (manifestEntries) => {
49
+ const manifest = manifestEntries.map((m) => {
50
+ if (m.url.startsWith(distAppDir)) {
51
+ // Keep the prefixing slash.
52
+ m.url = m.url.slice(distAppDir.length - 1);
53
+ }
54
+ if (m.url.startsWith(distPagesDir)) {
55
+ // Keep the prefixing slash.
56
+ m.url = m.url.slice(distPagesDir.length - 1);
57
+ }
58
+ if (m.url.endsWith(".html")) {
59
+ // trailingSlash: true && output: 'export'
60
+ // or root index.html.
61
+ // https://nextjs.org/docs/app/api-reference/config/next-config-js/trailingSlash
62
+ // "/abc/index.html" -> "/abc/"
63
+ // "/index.html" -> "/"
64
+ if (m.url.endsWith("/index.html")) {
65
+ m.url = m.url.slice(0, m.url.lastIndexOf("/") + 1);
66
+ }
67
+ // "/xxx.html" -> "/xxx"
68
+ else {
69
+ m.url = m.url.substring(0, m.url.lastIndexOf("."));
70
+ }
71
+ m.url = path.posix.join(basePath, m.url);
72
+ }
73
+ // Replace all references to "$(distDir)" with "$(assetPrefix)/_next/".
74
+ if (m.url.startsWith(distDir)) {
75
+ m.url = `${config.assetPrefix ?? ""}/_next/${m.url.slice(distDir.length)}`;
76
+ }
77
+ // Replace all references to public/ with "$(basePath)/".
78
+ if (m.url.startsWith("public/")) {
79
+ m.url = path.posix.join(basePath, m.url.slice(7));
80
+ }
81
+ return m;
82
+ });
83
+ return { manifest, warnings: [] };
84
+ },
85
+ ],
86
+ };
87
+ };
88
+
89
+ export { generateGlobPatterns };
90
+
91
+ export type { SerwistOptions };
@@ -0,0 +1,86 @@
1
+ import { Serwist } from "@serwist/window";
2
+ import { isCurrentPageOutOfScope } from "@serwist/window/internal";
3
+ import { type ReactNode, useEffect, useState } from "react";
4
+ import { SerwistContext, useSerwist } from "./lib/context.js";
5
+
6
+ export interface SerwistProviderProps {
7
+ swUrl: string;
8
+ register?: boolean;
9
+ cacheOnNavigation?: boolean;
10
+ reloadOnOnline?: boolean;
11
+ options?: RegistrationOptions;
12
+ children?: ReactNode;
13
+ }
14
+
15
+ declare global {
16
+ interface Window {
17
+ serwist: Serwist;
18
+ }
19
+ }
20
+
21
+ export function SerwistProvider({
22
+ swUrl,
23
+ register = true,
24
+ cacheOnNavigation = true,
25
+ reloadOnOnline = true,
26
+ options,
27
+ children,
28
+ }: SerwistProviderProps) {
29
+ const [serwist] = useState(() => {
30
+ if (typeof window === "undefined") return null;
31
+ if (!(window.serwist && window.serwist instanceof Serwist) && "serviceWorker" in navigator) {
32
+ window.serwist = new Serwist(swUrl, { ...options, type: options?.type || "module", scope: options?.scope || "/" });
33
+ }
34
+ return window.serwist ?? null;
35
+ });
36
+ useEffect(() => {
37
+ const scope = options?.scope || "/";
38
+ if (register && !isCurrentPageOutOfScope(scope)) {
39
+ void serwist?.register();
40
+ }
41
+ }, [register, options?.scope, serwist?.register]);
42
+ useEffect(() => {
43
+ const cacheUrls = async (url?: string | URL | null | undefined) => {
44
+ if (!window.navigator.onLine || !url) {
45
+ return;
46
+ }
47
+ serwist?.messageSW({
48
+ type: "CACHE_URLS",
49
+ payload: { urlsToCache: [url] },
50
+ });
51
+ };
52
+ const cacheCurrentPathname = () => cacheUrls(window.location.pathname);
53
+ const pushState = history.pushState;
54
+ const replaceState = history.replaceState;
55
+
56
+ if (cacheOnNavigation) {
57
+ history.pushState = (...args) => {
58
+ pushState.apply(history, args);
59
+ cacheUrls(args[2]);
60
+ };
61
+ history.replaceState = (...args) => {
62
+ replaceState.apply(history, args);
63
+ cacheUrls(args[2]);
64
+ };
65
+ window.addEventListener("online", cacheCurrentPathname);
66
+ }
67
+
68
+ return () => {
69
+ history.pushState = pushState;
70
+ history.replaceState = replaceState;
71
+ window.removeEventListener("online", cacheCurrentPathname);
72
+ };
73
+ }, [serwist?.messageSW, cacheOnNavigation]);
74
+ useEffect(() => {
75
+ const reload = () => location.reload();
76
+ if (reloadOnOnline) {
77
+ window.addEventListener("online", reload);
78
+ }
79
+ return () => {
80
+ window.removeEventListener("online", reload);
81
+ };
82
+ }, [reloadOnOnline]);
83
+ return <SerwistContext.Provider value={{ serwist }}>{children}</SerwistContext.Provider>;
84
+ }
85
+
86
+ export { useSerwist };
package/src/index.ts CHANGED
@@ -141,9 +141,7 @@ const withSerwistInit = (userOptions: InjectManifestOptions): ((nextConfig?: Nex
141
141
  });
142
142
 
143
143
  for (const file of cleanUpList) {
144
- fs.rm(file, { force: true }, (err) => {
145
- if (err) throw err;
146
- });
144
+ fs.rmSync(file, { force: true });
147
145
  }
148
146
 
149
147
  const shouldBuildSWEntryWorker = cacheOnNavigation;
@@ -214,7 +212,6 @@ const withSerwistInit = (userOptions: InjectManifestOptions): ((nextConfig?: Nex
214
212
  },
215
213
  ],
216
214
  manifestTransforms: [
217
- // TODO(ducanhgh): move this spread to below our transform function?
218
215
  ...manifestTransforms,
219
216
  async (manifestEntries, compilation) => {
220
217
  // This path always uses forward slashes, so it is safe to use it in the following string replace.
@@ -0,0 +1,11 @@
1
+ import type { BuildOptions } from "@serwist/cli";
2
+ import type { Optional } from "@serwist/utils";
3
+
4
+ export interface SerwistOptions extends Optional<BuildOptions, "globDirectory"> {
5
+ /**
6
+ * Whether Serwist should precache prerendered routes.
7
+ *
8
+ * @default true
9
+ */
10
+ precachePrerendered?: boolean;
11
+ }
@@ -0,0 +1,4 @@
1
+ export const generateGlobPatterns = (distDir: string) => [
2
+ `${distDir}static/**/*.{js,css,html,ico,apng,png,avif,jpg,jpeg,jfif,pjpeg,pjp,gif,svg,webp,json,webmanifest}`,
3
+ "public/**/*",
4
+ ];
@@ -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
+ };