@serwist/vite 8.2.0 → 8.4.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/dist/context.d.ts CHANGED
@@ -1,10 +1,32 @@
1
1
  import type { ResolvedConfig } from "vite";
2
2
  import type { PluginOptions, ResolvedPluginOptions } from "./types.js";
3
+ export type SerwistViteFrameworks = "nuxt" | "sveltekit";
3
4
  export interface SerwistViteContext {
5
+ /**
6
+ * Resolved Vite config.
7
+ *
8
+ * Note: This value is set by our main plugin, located at plugins/main.ts.
9
+ */
4
10
  viteConfig: ResolvedConfig;
11
+ /**
12
+ * Provided options.
13
+ */
5
14
  userOptions: PluginOptions;
15
+ /**
16
+ * Resolved options.
17
+ *
18
+ * Note: this is different from `userOptions` in that it has been parsed, whereas
19
+ * `userOptions` is the raw configuration that the user provides us.
20
+ */
6
21
  options: ResolvedPluginOptions;
7
22
  useImportRegister: boolean;
23
+ /**
24
+ * Is the plugin running on dev?
25
+ *
26
+ * Note: This value is set by our dev plugin, located at plugins/dev.ts.
27
+ */
8
28
  devEnvironment: boolean;
29
+ /** To tailor our APIs to these frameworks. */
30
+ framework: SerwistViteFrameworks | undefined;
9
31
  }
10
- export declare const createContext: (userOptions: PluginOptions) => SerwistViteContext;
32
+ export declare const createContext: (userOptions: PluginOptions, framework: SerwistViteFrameworks | undefined) => SerwistViteContext;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  import type { Plugin } from "vite";
2
+ import { createApi } from "./api.js";
3
+ import { createContext } from "./context.js";
4
+ import { buildPlugin } from "./plugins/build.js";
5
+ import { devPlugin } from "./plugins/dev.js";
6
+ import { mainPlugin } from "./plugins/main.js";
2
7
  import type { PluginOptions } from "./types.js";
8
+ import { resolveEntry, toFs } from "./utils.js";
3
9
  /**
4
10
  * Integrates Serwist into your Vite app.
5
11
  * @param userOptions
6
12
  * @returns
7
13
  */
8
14
  export declare const serwist: (userOptions: PluginOptions) => Plugin[];
15
+ export { buildPlugin as build, createApi, createContext, devPlugin as dev, mainPlugin as main, resolveEntry, toFs };
16
+ export type { SerwistViteContext } from "./context.js";
9
17
  export * from "./types.js";
package/dist/index.js CHANGED
@@ -1,11 +1,22 @@
1
- import { m as mainPlugin, d as devPlugin, c as createContext, a as createApi } from './main-dzWmj8BB.js';
1
+ import { m as mainPlugin, d as devPlugin, c as createContext, a as createApi } from './main.js';
2
+ export { r as resolveEntry, t as toFs } from './main.js';
3
+ import 'node:assert';
4
+ import 'node:fs/promises';
2
5
  import 'node:path';
6
+ import 'vite';
3
7
  import 'node:process';
8
+ import '@serwist/build';
4
9
  import 'node:crypto';
5
10
  import 'node:fs';
6
11
  import 'fast-glob';
7
12
 
8
- const buildPlugin = (ctx, api)=>{
13
+ /**
14
+ * Internal build plugin used by `@serwist/vite`.
15
+ * @internal
16
+ * @param ctx
17
+ * @param api
18
+ * @returns
19
+ */ const buildPlugin = (ctx, api)=>{
9
20
  return {
10
21
  name: "@serwist/vite:build",
11
22
  enforce: "post",
@@ -30,13 +41,13 @@ const buildPlugin = (ctx, api)=>{
30
41
  * @param userOptions
31
42
  * @returns
32
43
  */ const serwist = (userOptions)=>{
33
- const ctx = createContext(userOptions);
44
+ const ctx = createContext(userOptions, undefined);
34
45
  const api = createApi(ctx);
35
46
  return [
36
47
  mainPlugin(ctx, api),
37
48
  buildPlugin(ctx, api),
38
- devPlugin(ctx)
49
+ devPlugin(ctx, api)
39
50
  ];
40
51
  };
41
52
 
42
- export { serwist };
53
+ export { buildPlugin as build, createApi, createContext, devPlugin as dev, mainPlugin as main, serwist };
@@ -1,4 +1,4 @@
1
1
  import type { Plugin } from "vite";
2
2
  import type { SerwistViteContext } from "../../context.js";
3
3
  import type { SerwistViteApi } from "../../types.js";
4
- export declare const serwistSveltePlugin: (ctx: SerwistViteContext, api: SerwistViteApi) => Plugin<any>;
4
+ export declare const buildPlugin: (ctx: SerwistViteContext, api: SerwistViteApi) => Plugin<any>;
@@ -1,15 +1,47 @@
1
- import { l as loadSerwistBuild, m as mainPlugin, d as devPlugin, c as createContext, a as createApi } from '../../main-dzWmj8BB.js';
1
+ import { r as resolveEntry, m as mainPlugin, d as devPlugin, c as createContext, a as createApi } from '../../main.js';
2
+ import crypto from 'node:crypto';
3
+ import os from 'node:os';
2
4
  import path from 'node:path';
5
+ import { errors } from '@serwist/build';
6
+ import 'node:assert';
7
+ import 'node:fs/promises';
8
+ import 'vite';
3
9
  import 'node:process';
4
- import 'node:crypto';
5
10
  import 'node:fs';
6
11
  import 'fast-glob';
7
12
 
13
+ const buildPlugin = (ctx, api)=>{
14
+ return {
15
+ name: "@serwist/vite/integration-svelte:build",
16
+ apply: "build",
17
+ enforce: "pre",
18
+ closeBundle: {
19
+ sequential: true,
20
+ enforce: "pre",
21
+ async handler () {
22
+ if (api && !api.disabled && ctx.viteConfig.build.ssr) {
23
+ await api.generateSW();
24
+ }
25
+ }
26
+ }
27
+ };
28
+ };
29
+
8
30
  const configurateSvelteKitOptions = (viteConfig, kit, options)=>{
9
31
  const clientOutDir = path.resolve(viteConfig.root, viteConfig.build.outDir, "../client");
10
32
  // Kit fixes the service worker's name to 'service-worker.js'
11
- options.swSrc = path.resolve(clientOutDir, "service-worker.js");
12
- options.swDest = path.resolve(clientOutDir, "service-worker.js");
33
+ if (viteConfig.isProduction) {
34
+ options.swSrc = path.resolve(clientOutDir, "service-worker.js");
35
+ options.swDest = path.resolve(clientOutDir, "service-worker.js");
36
+ } else {
37
+ const swSrc = resolveEntry(path.join(viteConfig.root, kit.files?.serviceWorker ?? "src/service-worker"));
38
+ if (swSrc) {
39
+ options.swSrc = swSrc;
40
+ options.swDest = path.join(os.tmpdir(), `serwist-vite-integration-svelte-${crypto.randomUUID()}.js`);
41
+ } else {
42
+ throw new Error(errors["invalid-sw-src"]);
43
+ }
44
+ }
13
45
  options.swUrl = "/service-worker.js";
14
46
  // SvelteKit's outDir is `.svelte-kit/output/client`.
15
47
  // We need to include the parent folder in globDirectory since SvelteKit will generate SSG in `.svelte-kit/output/prerendered` folder.
@@ -36,6 +68,9 @@ const configurateSvelteKitOptions = (viteConfig, kit, options)=>{
36
68
  if (!("dontCacheBustURLsMatching" in options)) {
37
69
  options.dontCacheBustURLsMatching = new RegExp(`${buildAssetsDir}immutable/`);
38
70
  }
71
+ if (!options.injectionPoint) {
72
+ options.injectionPoint = "self.__SW_MANIFEST";
73
+ }
39
74
  };
40
75
  function createManifestTransform(base, webManifestName, options) {
41
76
  return async (entries)=>{
@@ -107,30 +142,7 @@ function buildGlobIgnores(globIgnores) {
107
142
  ];
108
143
  }
109
144
 
110
- const serwistSveltePlugin = (ctx, api)=>{
111
- return {
112
- name: "@serwist/vite/integration-svelte:build",
113
- apply: "build",
114
- enforce: "pre",
115
- closeBundle: {
116
- sequential: true,
117
- enforce: "pre",
118
- async handler () {
119
- if (api && !api.disabled && ctx.viteConfig.build.ssr) {
120
- const [injectManifest, logSerwistResult] = await Promise.all([
121
- loadSerwistBuild().then((m)=>m.injectManifest),
122
- import('../../log-Dyh-Moyt.js').then((m)=>m.logSerwistResult)
123
- ]);
124
- // Inject the manifest
125
- const buildResult = await injectManifest(ctx.options.injectManifest);
126
- // Log Serwist result
127
- logSerwistResult(buildResult, ctx.viteConfig);
128
- }
129
- }
130
- }
131
- };
132
- };
133
-
145
+ // TODO: handle SvelteKit build errors.
134
146
  /**
135
147
  * Integrates Serwist into your SvelteKit app.
136
148
  * @param userOptions
@@ -139,12 +151,12 @@ const serwistSveltePlugin = (ctx, api)=>{
139
151
  if (!userOptions.integration) userOptions.integration = {};
140
152
  userOptions.integration.closeBundleOrder = "pre";
141
153
  userOptions.integration.configureOptions = (viteConfig, options)=>configurateSvelteKitOptions(viteConfig, userOptions.kit ?? {}, options);
142
- const ctx = createContext(userOptions);
154
+ const ctx = createContext(userOptions, "sveltekit");
143
155
  const api = createApi(ctx);
144
156
  return [
145
157
  mainPlugin(ctx, api),
146
- devPlugin(ctx),
147
- serwistSveltePlugin(ctx, api)
158
+ devPlugin(ctx, api),
159
+ buildPlugin(ctx, api)
148
160
  ];
149
161
  };
150
162
 
@@ -1,7 +1,7 @@
1
1
  import type { KitConfig } from "@sveltejs/kit";
2
2
  import type { PluginOptions as BasePluginOptions } from "../../types.js";
3
3
  import type { Optional } from "../../utils-types.js";
4
- export interface KitOptions extends Pick<KitConfig, "appDir"> {
4
+ export interface KitOptions extends Pick<KitConfig, "appDir" | "files"> {
5
5
  /**
6
6
  * @see https://kit.svelte.dev/docs/adapter-static#options-fallback
7
7
  */
package/dist/main.js ADDED
@@ -0,0 +1,543 @@
1
+ import assert from 'node:assert';
2
+ import fs from 'node:fs/promises';
3
+ import path, { resolve } from 'node:path';
4
+ import { normalizePath } from 'vite';
5
+ import process$1 from 'node:process';
6
+ import { validateInjectManifestOptions } from '@serwist/build';
7
+ import crypto from 'node:crypto';
8
+ import fs$1 from 'node:fs';
9
+ import fg from 'fast-glob';
10
+
11
+ let enabled = true;
12
+ // Support both browser and node environments
13
+ const globalVar = typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {};
14
+ /**
15
+ * Detect how much colors the current terminal supports
16
+ */ let supportLevel = 0 /* none */ ;
17
+ if (globalVar.process && globalVar.process.env && globalVar.process.stdout) {
18
+ const { FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM, COLORTERM } = globalVar.process.env;
19
+ if (NODE_DISABLE_COLORS || NO_COLOR || FORCE_COLOR === '0') {
20
+ enabled = false;
21
+ } else if (FORCE_COLOR === '1' || FORCE_COLOR === '2' || FORCE_COLOR === '3') {
22
+ enabled = true;
23
+ } else if (TERM === 'dumb') {
24
+ enabled = false;
25
+ } else if ('CI' in globalVar.process.env && [
26
+ 'TRAVIS',
27
+ 'CIRCLECI',
28
+ 'APPVEYOR',
29
+ 'GITLAB_CI',
30
+ 'GITHUB_ACTIONS',
31
+ 'BUILDKITE',
32
+ 'DRONE'
33
+ ].some((vendor)=>vendor in globalVar.process.env)) {
34
+ enabled = true;
35
+ } else {
36
+ enabled = process.stdout.isTTY;
37
+ }
38
+ if (enabled) {
39
+ // Windows supports 24bit True Colors since Windows 10 revision #14931,
40
+ // see https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/
41
+ if (process.platform === 'win32') {
42
+ supportLevel = 3 /* trueColor */ ;
43
+ } else {
44
+ if (COLORTERM && (COLORTERM === 'truecolor' || COLORTERM === '24bit')) {
45
+ supportLevel = 3 /* trueColor */ ;
46
+ } else if (TERM && (TERM.endsWith('-256color') || TERM.endsWith('256'))) {
47
+ supportLevel = 2 /* ansi256 */ ;
48
+ } else {
49
+ supportLevel = 1 /* ansi */ ;
50
+ }
51
+ }
52
+ }
53
+ }
54
+ let options = {
55
+ enabled,
56
+ supportLevel
57
+ };
58
+ function kolorist(start, end, level = 1 /* ansi */ ) {
59
+ const open = `\x1b[${start}m`;
60
+ const close = `\x1b[${end}m`;
61
+ const regex = new RegExp(`\\x1b\\[${end}m`, 'g');
62
+ return (str)=>{
63
+ return options.enabled && options.supportLevel >= level ? open + ('' + str).replace(regex, open) + close : '' + str;
64
+ };
65
+ }
66
+ const dim = kolorist(2, 22);
67
+ const green = kolorist(32, 39);
68
+ const yellow = kolorist(33, 39);
69
+ const cyan = kolorist(36, 39);
70
+
71
+ var version = "8.4.0";
72
+
73
+ const logSerwistResult = (buildResult, viteOptions)=>{
74
+ const { logLevel = "info" } = viteOptions;
75
+ if (logLevel === "silent") return;
76
+ const { count, size, warnings } = buildResult;
77
+ if (logLevel === "info") {
78
+ console.info([
79
+ "",
80
+ `${cyan(`@serwist/vite v${version}`)} ${green("files generated.")}`,
81
+ `${green("✓")} ${count} precache entries ${dim(`(${(size / 1024).toFixed(2)} KiB)`)}`,
82
+ // log build warning
83
+ warnings && warnings.length > 0 ? yellow([
84
+ "⚠ warnings",
85
+ ...warnings.map((w)=>` ${w}`),
86
+ ""
87
+ ].join("\n")) : ""
88
+ ].join("\n"));
89
+ }
90
+ };
91
+
92
+ const loadSerwistBuild = async ()=>{
93
+ // "@serwist/build" is large and makes config loading slow.
94
+ // Since it is not always used, we only load this when it is needed.
95
+ try {
96
+ return await import('@serwist/build');
97
+ } catch (_) {
98
+ // We don't have a default export, don't worry.
99
+ return require("@serwist/build");
100
+ }
101
+ };
102
+ const injectManifest = async (config)=>{
103
+ const { validateViteInjectManifestOptions, getFileManifestEntries, stringify } = await loadSerwistBuild();
104
+ const options = validateViteInjectManifestOptions(config);
105
+ const { count, size, manifestEntries, warnings } = await getFileManifestEntries(options);
106
+ const manifestString = manifestEntries === undefined ? "undefined" : stringify(manifestEntries);
107
+ return {
108
+ warnings,
109
+ size,
110
+ count,
111
+ manifestEntries,
112
+ manifestString
113
+ };
114
+ };
115
+ const generateServiceWorker = async (ctx)=>{
116
+ const { format, plugins, rollupOptions } = ctx.options.injectManifestRollupOptions;
117
+ const parsedSwDest = path.parse(ctx.options.injectManifest.swDest);
118
+ let injectManifestResult = undefined;
119
+ if (ctx.options.injectManifest.injectionPoint) {
120
+ await ctx.options.integration?.beforeBuildServiceWorker?.(ctx.options);
121
+ injectManifestResult = await injectManifest(ctx.options.injectManifest);
122
+ }
123
+ const isProduction = ctx.options.mode === "production";
124
+ const isDev = ctx.options.mode === "development";
125
+ if (isProduction && ctx.framework === "sveltekit" || isDev && !ctx.options.devOptions.bundle) {
126
+ if (!injectManifestResult) {
127
+ throw new Error(`injectManifest failed to generate results. This is likely a bug.`);
128
+ }
129
+ const { errors, escapeRegExp, getSourceMapURL, rebasePath, replaceAndUpdateSourceMap, translateURLToSourcemapPaths } = await loadSerwistBuild();
130
+ // Make sure we leave swSrc and swDest out of the precache manifest.
131
+ for (const file of [
132
+ ctx.options.injectManifest.swSrc,
133
+ ctx.options.injectManifest.swDest
134
+ ]){
135
+ ctx.options.injectManifest.globIgnores.push(rebasePath({
136
+ file,
137
+ baseDirectory: ctx.options.injectManifest.globDirectory
138
+ }));
139
+ }
140
+ const injectionPoint = ctx.options.injectManifest.injectionPoint;
141
+ const globalRegexp = new RegExp(escapeRegExp(injectionPoint), "g");
142
+ let swFileContents;
143
+ try {
144
+ swFileContents = await fs.readFile(ctx.options.injectManifest.swSrc, "utf8");
145
+ } catch (error) {
146
+ throw new Error(`${errors["invalid-sw-src"]} ${error instanceof Error && error.message ? error.message : ""}`);
147
+ }
148
+ const injectionResults = swFileContents.match(globalRegexp);
149
+ // See https://github.com/GoogleChrome/workbox/issues/2230
150
+ if (!injectionResults) {
151
+ throw new Error(`${errors["injection-point-not-found"]} ${injectionPoint}`);
152
+ }
153
+ assert(injectionResults.length === 1, `${errors["multiple-injection-points"]} ${injectionPoint}`);
154
+ const filesToWrite = {};
155
+ const url = getSourceMapURL(swFileContents);
156
+ // See https://github.com/GoogleChrome/workbox/issues/2957
157
+ const { destPath, srcPath, warning } = translateURLToSourcemapPaths(url, ctx.options.injectManifest.swSrc, ctx.options.injectManifest.swDest);
158
+ if (warning) {
159
+ injectManifestResult.warnings.push(warning);
160
+ }
161
+ // If our swSrc file contains a sourcemap, we would invalidate that
162
+ // mapping if we just replaced injectionPoint with the stringified manifest.
163
+ // Instead, we need to update the swDest contents as well as the sourcemap
164
+ // (assuming it's a real file, not a data: URL) at the same time.
165
+ // See https://github.com/GoogleChrome/workbox/issues/2235
166
+ // and https://github.com/GoogleChrome/workbox/issues/2648
167
+ if (srcPath && destPath) {
168
+ const { map, source } = await replaceAndUpdateSourceMap({
169
+ originalMap: JSON.parse(await fs.readFile(srcPath, "utf8")),
170
+ jsFilename: path.basename(ctx.options.injectManifest.swDest),
171
+ originalSource: swFileContents,
172
+ replaceString: injectManifestResult.manifestString,
173
+ searchString: injectionPoint
174
+ });
175
+ filesToWrite[ctx.options.injectManifest.swDest] = source;
176
+ filesToWrite[destPath] = map;
177
+ } else {
178
+ // If there's no sourcemap associated with swSrc, a simple string
179
+ // replacement will suffice.
180
+ filesToWrite[ctx.options.injectManifest.swDest] = swFileContents.replace(globalRegexp, injectManifestResult.manifestString);
181
+ }
182
+ for (const [file, contents] of Object.entries(filesToWrite)){
183
+ try {
184
+ await fs.mkdir(path.dirname(file), {
185
+ recursive: true
186
+ });
187
+ } catch (error) {
188
+ throw new Error(errors["unable-to-make-sw-directory"] + ` '${error instanceof Error && error.message ? error.message : ""}'`);
189
+ }
190
+ await fs.writeFile(file, contents);
191
+ }
192
+ } else {
193
+ const define = {
194
+ // Nuxt does some really weird stuff. During the build, they MANUALLY
195
+ // set browser APIs, such as window, document, location,..., to `undefined`??
196
+ // Probably some Vue or server stuff. Their `define` doesn't seem to have anything
197
+ // particularly useful for the service worker anyway, so we don't extend it.
198
+ ...ctx.framework === "nuxt" ? undefined : ctx.viteConfig.define,
199
+ "process.env.NODE_ENV": `"${ctx.options.mode}"`
200
+ };
201
+ if (ctx.options.injectManifest.injectionPoint && injectManifestResult) {
202
+ define[ctx.options.injectManifest.injectionPoint] = injectManifestResult.manifestString;
203
+ }
204
+ const { build } = await import('vite');
205
+ await build({
206
+ logLevel: ctx.viteConfig.isProduction ? "info" : "warn",
207
+ root: ctx.viteConfig.root,
208
+ base: ctx.viteConfig.base,
209
+ resolve: ctx.viteConfig.resolve,
210
+ // Don't copy anything from public folder
211
+ publicDir: false,
212
+ build: {
213
+ sourcemap: ctx.viteConfig.build.sourcemap,
214
+ lib: {
215
+ entry: ctx.options.injectManifest.swSrc,
216
+ name: "app",
217
+ formats: [
218
+ format
219
+ ]
220
+ },
221
+ rollupOptions: {
222
+ ...rollupOptions,
223
+ plugins,
224
+ output: {
225
+ entryFileNames: parsedSwDest.base
226
+ }
227
+ },
228
+ outDir: parsedSwDest.dir,
229
+ emptyOutDir: false,
230
+ minify: isProduction || ctx.options.devOptions.minify
231
+ },
232
+ configFile: false,
233
+ define
234
+ });
235
+ }
236
+ return injectManifestResult;
237
+ };
238
+
239
+ const createApi = (ctx)=>{
240
+ return {
241
+ get disabled () {
242
+ return ctx?.options?.disable;
243
+ },
244
+ async generateSW () {
245
+ if (ctx.options.disable) {
246
+ return undefined;
247
+ }
248
+ const buildResult = await generateServiceWorker(ctx);
249
+ if (buildResult) {
250
+ if (ctx.viteConfig.isProduction) {
251
+ // Log Serwist result
252
+ logSerwistResult(buildResult, ctx.viteConfig);
253
+ } else if (buildResult.warnings && buildResult.warnings.length > 0) {
254
+ console.warn(yellow([
255
+ "[@serwist/vite] Warnings",
256
+ ...buildResult.warnings.map((w)=>` - ${w}`),
257
+ ""
258
+ ].join("\n")));
259
+ }
260
+ }
261
+ },
262
+ extendManifestEntries (fn) {
263
+ const { options } = ctx;
264
+ if (options.disable) return;
265
+ const result = fn(options.injectManifest.additionalPrecacheEntries || []);
266
+ if (result != null) {
267
+ options.injectManifest.additionalPrecacheEntries = result;
268
+ }
269
+ }
270
+ };
271
+ };
272
+
273
+ const createContext = (userOptions, framework)=>{
274
+ return {
275
+ userOptions,
276
+ options: undefined,
277
+ viteConfig: undefined,
278
+ useImportRegister: false,
279
+ devEnvironment: false,
280
+ framework
281
+ };
282
+ };
283
+
284
+ const slash = (str)=>{
285
+ return str.replace(/\\/g, "/");
286
+ };
287
+ const resolveBasePath = (base)=>{
288
+ if (isAbsolute(base)) return base;
289
+ return !base.startsWith("/") && !base.startsWith("./") ? `/${base}` : base;
290
+ };
291
+ const isAbsolute = (url)=>{
292
+ return url.match(/^(?:[a-z]+:)?\/\//i);
293
+ };
294
+ // Source: https://github.com/sveltejs/kit/blob/6419d3eaa7bf1b0a756b28f06a73f71fe042de0a/packages/kit/src/utils/filesystem.js
295
+ // License: MIT
296
+ /**
297
+ * Internal function used by `@serwist/vite`.
298
+ * Resolves a file path without extension. Also handles `/index` if the path
299
+ * actually points to a directory.
300
+ * @internal
301
+ * @param ctx
302
+ * @param api
303
+ * @returns
304
+ */ const resolveEntry = (entry)=>{
305
+ if (fs$1.existsSync(entry)) {
306
+ const stats = fs$1.statSync(entry);
307
+ if (stats.isDirectory()) {
308
+ return resolveEntry(path.join(entry, "index"));
309
+ }
310
+ return entry;
311
+ } else {
312
+ const dir = path.dirname(entry);
313
+ if (fs$1.existsSync(dir)) {
314
+ const base = path.basename(entry);
315
+ const files = fs$1.readdirSync(dir);
316
+ const found = files.find((file)=>file.replace(/\.[^.]+$/, "") === base);
317
+ if (found) return path.join(dir, found);
318
+ }
319
+ }
320
+ return null;
321
+ };
322
+ // Source: https://github.com/sveltejs/kit/blob/6419d3eaa7bf1b0a756b28f06a73f71fe042de0a/packages/kit/src/utils/filesystem.js
323
+ // License: MIT
324
+ /**
325
+ * Internal function used by `@serwist/vite`.
326
+ * Converts a filesystem path to a Vite `@fs` URL.
327
+ * @internal
328
+ * @param ctx
329
+ * @param api
330
+ * @returns
331
+ */ function toFs(str) {
332
+ str = str.replace(/\\/g, "/");
333
+ // Windows/Linux separation - Windows starts with a drive letter, we need a / in front there
334
+ return `/@fs${str.startsWith("/") ? "" : "/"}${str}`;
335
+ }
336
+
337
+ // This plugin handles the service worker in two ways:
338
+ // - If `devOptions.bundle` is enabled, hook a middleware that bundles the service worker
339
+ // through `api.generateSW()` and returns the result into Vite's dev server.
340
+ // - Otherwise, run `injectManifest` and return the service worker through `async load(id)`. Although
341
+ // `precacheEntries` is always `undefined`, we still do this to check the user's `injectManifest` options
342
+ // in dev mode.
343
+ /**
344
+ * Internal dev plugin used by `@serwist/vite`.
345
+ * @internal
346
+ * @param ctx
347
+ * @param api
348
+ * @returns
349
+ */ const devPlugin = (ctx, api)=>{
350
+ return {
351
+ name: "@serwist/vite:dev",
352
+ apply: "serve",
353
+ configureServer (server) {
354
+ ctx.devEnvironment = true;
355
+ server.middlewares.use(async (req, res, next)=>{
356
+ if (!ctx.options.disable && req.url === ctx.options.swUrl) {
357
+ if (ctx.options.devOptions.bundle) {
358
+ await api.generateSW();
359
+ const content = await fs.readFile(ctx.options.injectManifest.swDest, "utf-8");
360
+ await fs.rm(ctx.options.injectManifest.swDest);
361
+ res.setHeader("Content-Type", "application/javascript");
362
+ res.write(content);
363
+ res.end();
364
+ } else {
365
+ res.setHeader("Content-Type", "application/javascript");
366
+ res.write(`import "${toFs(path.resolve(ctx.options.injectManifest.swSrc))}";`);
367
+ res.end();
368
+ }
369
+ } else {
370
+ next();
371
+ }
372
+ });
373
+ },
374
+ async load (id) {
375
+ if (!ctx.options.disable && !ctx.options.devOptions.bundle) {
376
+ const swSrcId = normalizePath(ctx.options.injectManifest.swSrc);
377
+ if (id === swSrcId) {
378
+ await api.generateSW();
379
+ const content = await fs.readFile(ctx.options.injectManifest.swDest, "utf-8");
380
+ await fs.rm(ctx.options.injectManifest.swDest);
381
+ return content;
382
+ }
383
+ }
384
+ return undefined;
385
+ }
386
+ };
387
+ };
388
+
389
+ const INTERNAL_SERWIST_VIRTUAL = "virtual:internal-serwist";
390
+ const RESOLVED_INTERNAL_SERWIST_VIRTUAL = `\0${INTERNAL_SERWIST_VIRTUAL}`;
391
+
392
+ const buildManifestEntry = (publicDir, url)=>{
393
+ return new Promise((resolve$1, reject)=>{
394
+ const cHash = crypto.createHash("MD5");
395
+ const stream = fs$1.createReadStream(resolve(publicDir, url));
396
+ stream.on("error", (err)=>{
397
+ reject(err);
398
+ });
399
+ stream.on("data", (chunk)=>{
400
+ cHash.update(chunk);
401
+ });
402
+ stream.on("end", ()=>{
403
+ return resolve$1({
404
+ url,
405
+ revision: `${cHash.digest("hex")}`
406
+ });
407
+ });
408
+ });
409
+ };
410
+ const lookupAdditionalPrecacheEntries = (serwistOptions)=>{
411
+ return serwistOptions.additionalPrecacheEntries || [];
412
+ };
413
+ // we need to make icons relative, we can have for example icon entries with: /pwa.png
414
+ // fast-glob will not resolve absolute paths
415
+ const normalizeIconPath = (path)=>{
416
+ return path.startsWith("/") ? path.substring(1) : path;
417
+ };
418
+ const configureStaticAssets = async (resolvedPluginOptions, viteConfig)=>{
419
+ const { injectManifest, includeAssets } = resolvedPluginOptions;
420
+ const { publicDir } = viteConfig;
421
+ const globs = [];
422
+ const manifestEntries = lookupAdditionalPrecacheEntries(injectManifest);
423
+ if (includeAssets) {
424
+ // we need to make icons relative, we can have for example icon entries with: /pwa.png
425
+ // fast-glob will not resolve absolute paths
426
+ if (Array.isArray(includeAssets)) globs.push(...includeAssets.map(normalizeIconPath));
427
+ else globs.push(normalizeIconPath(includeAssets));
428
+ }
429
+ if (globs.length > 0) {
430
+ let assets = await fg(globs, {
431
+ cwd: publicDir,
432
+ onlyFiles: true,
433
+ unique: true
434
+ });
435
+ // we also need to remove from the list existing included by the user
436
+ if (manifestEntries.length > 0) {
437
+ const included = manifestEntries.map((me)=>{
438
+ if (typeof me === "string") return me;
439
+ else return me.url;
440
+ });
441
+ assets = assets.filter((a)=>!included.includes(a));
442
+ }
443
+ const assetsEntries = await Promise.all(assets.map((a)=>{
444
+ return buildManifestEntry(publicDir, a);
445
+ }));
446
+ manifestEntries.push(...assetsEntries);
447
+ }
448
+ if (manifestEntries.length > 0) {
449
+ injectManifest.additionalPrecacheEntries = manifestEntries;
450
+ }
451
+ };
452
+
453
+ const resolveOptions = async (options, viteConfig)=>{
454
+ const { type = "classic", mode = process$1.env.NODE_ENV || "production", injectRegister = "auto", registerType = "prompt", minify = true, base = viteConfig.base, scope: _scope, swUrl = "/sw.js", includeAssets = undefined, useCredentials = false, disable = false, integration = {}, buildBase, devOptions, plugins = [], rollupOptions = {}, rollupFormat = "es", ...injectManifest } = options;
455
+ const basePath = resolveBasePath(base);
456
+ // check typescript service worker for injectManifest strategy
457
+ const scope = _scope || basePath;
458
+ let assetsDir = slash(viteConfig.build.assetsDir ?? "assets");
459
+ if (assetsDir[assetsDir.length - 1] !== "/") assetsDir += "/";
460
+ const resolvedDevOptions = {
461
+ bundle: true,
462
+ minify: false,
463
+ ...devOptions
464
+ };
465
+ // remove './' prefix from assetsDir
466
+ const dontCacheBustURLsMatching = new RegExp(`^${assetsDir.replace(/^\.*?\//, "")}`);
467
+ validateInjectManifestOptions(injectManifest);
468
+ const { swSrc, swDest, ...userInjectManifest } = injectManifest || {};
469
+ const resolvedPluginOptions = {
470
+ base: basePath,
471
+ type,
472
+ mode,
473
+ injectRegister,
474
+ registerType,
475
+ useCredentials,
476
+ swUrl,
477
+ injectManifest: {
478
+ dontCacheBustURLsMatching,
479
+ ...userInjectManifest,
480
+ swSrc: path.resolve(viteConfig.root, swSrc),
481
+ swDest: path.resolve(viteConfig.root, viteConfig.build.outDir, swDest),
482
+ disablePrecacheManifest: !viteConfig.isProduction
483
+ },
484
+ scope,
485
+ minify,
486
+ includeAssets,
487
+ disable,
488
+ integration,
489
+ buildBase: buildBase ?? basePath,
490
+ injectManifestRollupOptions: {
491
+ plugins,
492
+ rollupOptions,
493
+ format: rollupFormat
494
+ },
495
+ devOptions: resolvedDevOptions
496
+ };
497
+ // calculate hash only when required
498
+ const calculateHash = !resolvedPluginOptions.disable && resolvedPluginOptions.includeAssets && viteConfig.command === "build";
499
+ if (calculateHash) await configureStaticAssets(resolvedPluginOptions, viteConfig);
500
+ return resolvedPluginOptions;
501
+ };
502
+
503
+ /**
504
+ * Internal plugin used by `@serwist/vite`.
505
+ * @internal
506
+ * @param ctx
507
+ * @param api
508
+ * @returns
509
+ */ const mainPlugin = (ctx, api)=>{
510
+ return {
511
+ name: "@serwist/vite",
512
+ enforce: "pre",
513
+ config () {
514
+ return {
515
+ ssr: {
516
+ noExternal: []
517
+ }
518
+ };
519
+ },
520
+ async configResolved (config) {
521
+ ctx.viteConfig = config;
522
+ ctx.userOptions?.integration?.configureOptions?.(config, ctx.userOptions);
523
+ ctx.options = await resolveOptions(ctx.userOptions, config);
524
+ },
525
+ resolveId (id) {
526
+ if (id === INTERNAL_SERWIST_VIRTUAL) {
527
+ return RESOLVED_INTERNAL_SERWIST_VIRTUAL;
528
+ }
529
+ return undefined;
530
+ },
531
+ load (id) {
532
+ if (id === RESOLVED_INTERNAL_SERWIST_VIRTUAL) {
533
+ return `export const swUrl = "${path.posix.join(ctx.options.buildBase, ctx.options.swUrl)}";
534
+ export const swScope = "${ctx.options.scope}";
535
+ export const swType = "${ctx.devEnvironment ? "module" : ctx.options.type}";`;
536
+ }
537
+ return undefined;
538
+ },
539
+ api
540
+ };
541
+ };
542
+
543
+ export { createApi as a, createContext as c, devPlugin as d, mainPlugin as m, resolveEntry as r, toFs as t };
package/dist/modules.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import type * as SerwistBuild from "@serwist/build";
2
- import type { ResolvedConfig } from "vite";
3
- import type { ResolvedPluginOptions } from "./types.js";
2
+ import type { SerwistViteContext } from "./context.js";
4
3
  export declare const loadSerwistBuild: () => Promise<typeof SerwistBuild>;
5
- export declare const generateInjectManifest: (options: ResolvedPluginOptions, viteOptions: ResolvedConfig) => Promise<void>;
4
+ interface BuildResult extends SerwistBuild.GetManifestResult {
5
+ manifestString: string;
6
+ }
7
+ export declare const injectManifest: (config: SerwistBuild.ViteInjectManifestOptions) => Promise<BuildResult>;
8
+ export declare const generateServiceWorker: (ctx: SerwistViteContext) => Promise<BuildResult | undefined>;
9
+ export {};
@@ -1,4 +1,11 @@
1
1
  import type { Plugin } from "vite";
2
2
  import type { SerwistViteContext } from "../context.js";
3
3
  import type { SerwistViteApi } from "../types.js";
4
+ /**
5
+ * Internal build plugin used by `@serwist/vite`.
6
+ * @internal
7
+ * @param ctx
8
+ * @param api
9
+ * @returns
10
+ */
4
11
  export declare const buildPlugin: (ctx: SerwistViteContext, api: SerwistViteApi) => Plugin<any>;
@@ -1,3 +1,11 @@
1
- import type { Plugin } from "vite";
1
+ import { type Plugin } from "vite";
2
2
  import type { SerwistViteContext } from "../context.js";
3
- export declare const devPlugin: (ctx: SerwistViteContext) => Plugin;
3
+ import type { SerwistViteApi } from "../types.js";
4
+ /**
5
+ * Internal dev plugin used by `@serwist/vite`.
6
+ * @internal
7
+ * @param ctx
8
+ * @param api
9
+ * @returns
10
+ */
11
+ export declare const devPlugin: (ctx: SerwistViteContext, api: SerwistViteApi) => Plugin;
@@ -1,4 +1,11 @@
1
1
  import type { Plugin } from "vite";
2
2
  import type { SerwistViteContext } from "../context.js";
3
3
  import type { SerwistViteApi } from "../types.js";
4
+ /**
5
+ * Internal plugin used by `@serwist/vite`.
6
+ * @internal
7
+ * @param ctx
8
+ * @param api
9
+ * @returns
10
+ */
4
11
  export declare const mainPlugin: (ctx: SerwistViteContext, api: SerwistViteApi) => Plugin<any>;
package/dist/types.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { InjectManifestOptions, ManifestEntry } from "@serwist/build";
1
+ import type { ManifestEntry, ViteInjectManifestOptions } from "@serwist/build";
2
2
  import type { RollupOptions } from "rollup";
3
- import type { Plugin, ResolvedConfig } from "vite";
3
+ import type { BuildOptions, Plugin, ResolvedConfig } from "vite";
4
4
  export type InjectManifestVitePlugins = string[] | ((vitePluginIds: string[]) => string[]);
5
- export interface CustomInjectManifestOptions extends Omit<InjectManifestOptions, "disablePrecacheManifest"> {
5
+ export interface CustomInjectManifestOptions extends Omit<ViteInjectManifestOptions, "disablePrecacheManifest"> {
6
6
  /**
7
7
  * The URL to the service worker.
8
8
  * @default "/sw.js"
@@ -34,6 +34,18 @@ export interface SerwistViteHooks {
34
34
  closeBundleOrder?: "pre" | "post" | null;
35
35
  configureOptions?: (viteOptions: ResolvedConfig, options: PluginOptions) => void | Promise<void>;
36
36
  }
37
+ export interface DevOptions {
38
+ /**
39
+ * Whether the service worker should be bundled in development mode.
40
+ *
41
+ * [Many browsers still do not support ES Modules in service workers.](https://caniuse.com/mdn-api_serviceworker_ecmascript_modules) However, in development
42
+ * mode, certain frameworks, such as SvelteKit, do not bundle the service worker. As a result, trying to register that service worker on browsers lacking
43
+ * support, such as Firefox or Safari, will fail, but doing so on browsers not lacking support will not fail. This option is provided to prevent that from
44
+ * happening. What the plugin does is intercepting any request to the service worker (requests for `swUrl`) and returning a bundled one.
45
+ */
46
+ bundle?: boolean;
47
+ minify?: BuildOptions["minify"];
48
+ }
37
49
  /**
38
50
  * Plugin options.
39
51
  */
@@ -135,15 +147,18 @@ export interface BasePluginOptions {
135
147
  */
136
148
  buildBase?: string;
137
149
  }
138
- export type PluginOptions = Partial<BasePluginOptions> & CustomInjectManifestOptions;
150
+ export interface PluginOptions extends Partial<BasePluginOptions>, CustomInjectManifestOptions {
151
+ devOptions?: DevOptions;
152
+ }
139
153
  export interface InjectManifestRollupOptions {
140
154
  format: "es" | "iife";
141
155
  plugins: Plugin[];
142
156
  rollupOptions: RollupOptions;
143
157
  }
144
158
  export interface ResolvedPluginOptions extends Required<BasePluginOptions>, Required<Pick<CustomInjectManifestOptions, "swUrl">> {
145
- injectManifest: InjectManifestOptions;
159
+ injectManifest: ViteInjectManifestOptions;
146
160
  injectManifestRollupOptions: InjectManifestRollupOptions;
161
+ devOptions: Required<DevOptions>;
147
162
  }
148
163
  export interface ShareTargetFiles {
149
164
  name: string;
package/dist/utils.d.ts CHANGED
@@ -2,3 +2,22 @@ export declare const slash: (str: string) => string;
2
2
  export declare const resolveBasePath: (base: string) => string;
3
3
  export declare const isAbsolute: (url: string) => RegExpMatchArray | null;
4
4
  export declare const normalizePath: (path: string) => string;
5
+ /**
6
+ * Internal function used by `@serwist/vite`.
7
+ * Resolves a file path without extension. Also handles `/index` if the path
8
+ * actually points to a directory.
9
+ * @internal
10
+ * @param ctx
11
+ * @param api
12
+ * @returns
13
+ */
14
+ export declare const resolveEntry: (entry: string) => string | null;
15
+ /**
16
+ * Internal function used by `@serwist/vite`.
17
+ * Converts a filesystem path to a Vite `@fs` URL.
18
+ * @internal
19
+ * @param ctx
20
+ * @param api
21
+ * @returns
22
+ */
23
+ export declare function toFs(str: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serwist/vite",
3
- "version": "8.2.0",
3
+ "version": "8.4.0",
4
4
  "type": "module",
5
5
  "description": "A module that integrates Serwist into your Vite application.",
6
6
  "files": [
@@ -29,7 +29,7 @@
29
29
  "license": "MIT",
30
30
  "repository": "serwist/serwist",
31
31
  "bugs": "https://github.com/serwist/serwist/issues",
32
- "homepage": "https://serwist.vercel.app",
32
+ "homepage": "https://serwist.pages.dev",
33
33
  "sideEffects": false,
34
34
  "main": "./dist/index.js",
35
35
  "types": "./dist/index.d.ts",
@@ -77,8 +77,8 @@
77
77
  "debug": "4.3.4",
78
78
  "fast-glob": "3.3.2",
79
79
  "pretty-bytes": "6.1.1",
80
- "@serwist/build": "8.2.0",
81
- "@serwist/window": "8.2.0"
80
+ "@serwist/build": "8.4.0",
81
+ "@serwist/window": "8.4.0"
82
82
  },
83
83
  "devDependencies": {
84
84
  "@playwright/test": "1.40.1",
@@ -100,7 +100,7 @@
100
100
  "typescript": "5.4.0-dev.20231226",
101
101
  "vite": "5.0.10",
102
102
  "vue": "3.3.13",
103
- "@serwist/constants": "8.2.0"
103
+ "@serwist/constants": "8.4.0"
104
104
  },
105
105
  "peerDependencies": {
106
106
  "@sveltejs/kit": "^1.0.0 || ^2.0.0",
@@ -133,6 +133,7 @@
133
133
  },
134
134
  "scripts": {
135
135
  "build": "rimraf dist && cross-env NODE_ENV=production rollup --config rollup.config.js",
136
+ "dev": "rollup --config rollup.config.js --watch",
136
137
  "lint": "eslint src --ext ts,tsx,js,jsx,cjs,mjs",
137
138
  "typecheck": "tsc"
138
139
  }
@@ -1,3 +0,0 @@
1
- import type { BuildResult } from "@serwist/build";
2
- import type { ResolvedConfig } from "vite";
3
- export declare function logSerwistResult(buildResult: Pick<BuildResult, "count" | "size" | "warnings">, viteOptions: ResolvedConfig): void;
@@ -1,26 +0,0 @@
1
- import { b as cyan, v as version, g as green, e as dim, y as yellow } from './main-dzWmj8BB.js';
2
- import 'node:path';
3
- import 'node:process';
4
- import 'node:crypto';
5
- import 'node:fs';
6
- import 'fast-glob';
7
-
8
- function logSerwistResult(buildResult, viteOptions) {
9
- const { logLevel = "info" } = viteOptions;
10
- if (logLevel === "silent") return;
11
- const { count, size, warnings } = buildResult;
12
- if (logLevel === "info") {
13
- console.info([
14
- "",
15
- `${cyan(`@serwist/vite/integration-svelte v${version}`)} ${green("files generated.")}`,
16
- `${green("✓")} ${count} precache entries ${dim(`(${(size / 1024).toFixed(2)} KiB)`)}`,
17
- warnings && warnings.length > 0 ? yellow([
18
- "⚠ warnings",
19
- ...warnings.map((w)=>` ${w}`),
20
- ""
21
- ].join("\n")) : ""
22
- ].join("\n"));
23
- }
24
- }
25
-
26
- export { logSerwistResult };
@@ -1,348 +0,0 @@
1
- import path, { resolve } from 'node:path';
2
- import process$1 from 'node:process';
3
- import crypto from 'node:crypto';
4
- import fs from 'node:fs';
5
- import fg from 'fast-glob';
6
-
7
- let enabled = true;
8
- // Support both browser and node environments
9
- const globalVar = typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {};
10
- /**
11
- * Detect how much colors the current terminal supports
12
- */ let supportLevel = 0 /* none */ ;
13
- if (globalVar.process && globalVar.process.env && globalVar.process.stdout) {
14
- const { FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM, COLORTERM } = globalVar.process.env;
15
- if (NODE_DISABLE_COLORS || NO_COLOR || FORCE_COLOR === '0') {
16
- enabled = false;
17
- } else if (FORCE_COLOR === '1' || FORCE_COLOR === '2' || FORCE_COLOR === '3') {
18
- enabled = true;
19
- } else if (TERM === 'dumb') {
20
- enabled = false;
21
- } else if ('CI' in globalVar.process.env && [
22
- 'TRAVIS',
23
- 'CIRCLECI',
24
- 'APPVEYOR',
25
- 'GITLAB_CI',
26
- 'GITHUB_ACTIONS',
27
- 'BUILDKITE',
28
- 'DRONE'
29
- ].some((vendor)=>vendor in globalVar.process.env)) {
30
- enabled = true;
31
- } else {
32
- enabled = process.stdout.isTTY;
33
- }
34
- if (enabled) {
35
- // Windows supports 24bit True Colors since Windows 10 revision #14931,
36
- // see https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/
37
- if (process.platform === 'win32') {
38
- supportLevel = 3 /* trueColor */ ;
39
- } else {
40
- if (COLORTERM && (COLORTERM === 'truecolor' || COLORTERM === '24bit')) {
41
- supportLevel = 3 /* trueColor */ ;
42
- } else if (TERM && (TERM.endsWith('-256color') || TERM.endsWith('256'))) {
43
- supportLevel = 2 /* ansi256 */ ;
44
- } else {
45
- supportLevel = 1 /* ansi */ ;
46
- }
47
- }
48
- }
49
- }
50
- let options = {
51
- enabled,
52
- supportLevel
53
- };
54
- function kolorist(start, end, level = 1 /* ansi */ ) {
55
- const open = `\x1b[${start}m`;
56
- const close = `\x1b[${end}m`;
57
- const regex = new RegExp(`\\x1b\\[${end}m`, 'g');
58
- return (str)=>{
59
- return options.enabled && options.supportLevel >= level ? open + ('' + str).replace(regex, open) + close : '' + str;
60
- };
61
- }
62
- const dim = kolorist(2, 22);
63
- const green = kolorist(32, 39);
64
- const yellow = kolorist(33, 39);
65
- const cyan = kolorist(36, 39);
66
-
67
- var version = "8.2.0";
68
-
69
- const logSerwistResult = (buildResult, viteOptions)=>{
70
- const { logLevel = "info" } = viteOptions;
71
- if (logLevel === "silent") return;
72
- const { count, size, warnings } = buildResult;
73
- if (logLevel === "info") {
74
- console.info([
75
- "",
76
- `${cyan(`@serwist/vite v${version}`)} ${green("files generated.")}`,
77
- `${green("✓")} ${count} precache entries ${dim(`(${(size / 1024).toFixed(2)} KiB)`)}`,
78
- // log build warning
79
- warnings && warnings.length > 0 ? yellow([
80
- "⚠ warnings",
81
- ...warnings.map((w)=>` ${w}`),
82
- ""
83
- ].join("\n")) : ""
84
- ].join("\n"));
85
- }
86
- };
87
-
88
- const loadSerwistBuild = async ()=>{
89
- // "@serwist/build" is large and makes config loading slow.
90
- // Since it is not always used, we only load this when it is needed.
91
- try {
92
- return await import('@serwist/build');
93
- } catch (_) {
94
- return require("@serwist/build");
95
- }
96
- };
97
- const generateInjectManifest = async (options, viteOptions)=>{
98
- // We will have something like this from swSrc:
99
- /*
100
- // sw.js
101
- import { precacheAndRoute } from 'workbox-precaching'
102
- // self.__WB_MANIFEST is default injection point
103
- precacheAndRoute(self.__WB_MANIFEST)
104
- */ const { build } = await import('vite');
105
- const define = {
106
- ...viteOptions.define ?? {}
107
- };
108
- define["process.env.NODE_ENV"] = JSON.stringify(options.mode);
109
- const { format, plugins, rollupOptions } = options.injectManifestRollupOptions;
110
- const parsedSwDest = path.parse(options.injectManifest.swDest);
111
- await build({
112
- root: viteOptions.root,
113
- base: viteOptions.base,
114
- resolve: viteOptions.resolve,
115
- // Don't copy anything from public folder
116
- publicDir: false,
117
- build: {
118
- sourcemap: viteOptions.build.sourcemap,
119
- lib: {
120
- entry: options.injectManifest.swSrc,
121
- name: "app",
122
- formats: [
123
- format
124
- ]
125
- },
126
- rollupOptions: {
127
- ...rollupOptions,
128
- plugins,
129
- output: {
130
- entryFileNames: parsedSwDest.base
131
- }
132
- },
133
- outDir: parsedSwDest.dir,
134
- emptyOutDir: false
135
- },
136
- configFile: false,
137
- define
138
- });
139
- // If the user doesn't have an injectionPoint, skip injectManifest.
140
- if (!options.injectManifest.injectionPoint) return;
141
- await options.integration?.beforeBuildServiceWorker?.(options);
142
- const resolvedInjectManifestOptions = {
143
- ...options.injectManifest,
144
- // This will not fail since there is an injectionPoint
145
- swSrc: options.injectManifest.swDest
146
- };
147
- const { injectManifest } = await loadSerwistBuild();
148
- // Inject the manifest
149
- const buildResult = await injectManifest(resolvedInjectManifestOptions);
150
- // Log workbox result
151
- logSerwistResult(buildResult, viteOptions);
152
- };
153
-
154
- const createApi = (ctx)=>{
155
- return {
156
- get disabled () {
157
- return ctx?.options?.disable;
158
- },
159
- async generateSW () {
160
- if (ctx.options.disable) {
161
- return undefined;
162
- }
163
- return await generateInjectManifest(ctx.options, ctx.viteConfig);
164
- },
165
- extendManifestEntries (fn) {
166
- const { options } = ctx;
167
- if (options.disable) return;
168
- const result = fn(options.injectManifest.additionalPrecacheEntries || []);
169
- if (result != null) {
170
- options.injectManifest.additionalPrecacheEntries = result;
171
- }
172
- }
173
- };
174
- };
175
-
176
- const createContext = (userOptions)=>{
177
- return {
178
- userOptions,
179
- options: undefined,
180
- viteConfig: undefined,
181
- useImportRegister: false,
182
- devEnvironment: false
183
- };
184
- };
185
-
186
- const devPlugin = (ctx)=>{
187
- return {
188
- name: "@serwist/vite:dev",
189
- apply: "serve",
190
- configureServer () {
191
- ctx.devEnvironment = true;
192
- }
193
- };
194
- };
195
-
196
- const INTERNAL_SERWIST_VIRTUAL = "virtual:internal-serwist";
197
- const RESOLVED_INTERNAL_SERWIST_VIRTUAL = `\0${INTERNAL_SERWIST_VIRTUAL}`;
198
-
199
- const buildManifestEntry = (publicDir, url)=>{
200
- return new Promise((resolve$1, reject)=>{
201
- const cHash = crypto.createHash("MD5");
202
- const stream = fs.createReadStream(resolve(publicDir, url));
203
- stream.on("error", (err)=>{
204
- reject(err);
205
- });
206
- stream.on("data", (chunk)=>{
207
- cHash.update(chunk);
208
- });
209
- stream.on("end", ()=>{
210
- return resolve$1({
211
- url,
212
- revision: `${cHash.digest("hex")}`
213
- });
214
- });
215
- });
216
- };
217
- const lookupAdditionalPrecacheEntries = (serwistOptions)=>{
218
- return serwistOptions.additionalPrecacheEntries || [];
219
- };
220
- // we need to make icons relative, we can have for example icon entries with: /pwa.png
221
- // fast-glob will not resolve absolute paths
222
- const normalizeIconPath = (path)=>{
223
- return path.startsWith("/") ? path.substring(1) : path;
224
- };
225
- const configureStaticAssets = async (resolvedPluginOptions, viteConfig)=>{
226
- const { injectManifest, includeAssets } = resolvedPluginOptions;
227
- const { publicDir } = viteConfig;
228
- const globs = [];
229
- const manifestEntries = lookupAdditionalPrecacheEntries(injectManifest);
230
- if (includeAssets) {
231
- // we need to make icons relative, we can have for example icon entries with: /pwa.png
232
- // fast-glob will not resolve absolute paths
233
- if (Array.isArray(includeAssets)) globs.push(...includeAssets.map(normalizeIconPath));
234
- else globs.push(normalizeIconPath(includeAssets));
235
- }
236
- if (globs.length > 0) {
237
- let assets = await fg(globs, {
238
- cwd: publicDir,
239
- onlyFiles: true,
240
- unique: true
241
- });
242
- // we also need to remove from the list existing included by the user
243
- if (manifestEntries.length > 0) {
244
- const included = manifestEntries.map((me)=>{
245
- if (typeof me === "string") return me;
246
- else return me.url;
247
- });
248
- assets = assets.filter((a)=>!included.includes(a));
249
- }
250
- const assetsEntries = await Promise.all(assets.map((a)=>{
251
- return buildManifestEntry(publicDir, a);
252
- }));
253
- manifestEntries.push(...assetsEntries);
254
- }
255
- if (manifestEntries.length > 0) {
256
- injectManifest.additionalPrecacheEntries = manifestEntries;
257
- }
258
- };
259
-
260
- const slash = (str)=>{
261
- return str.replace(/\\/g, "/");
262
- };
263
- const resolveBasePath = (base)=>{
264
- if (isAbsolute(base)) return base;
265
- return !base.startsWith("/") && !base.startsWith("./") ? `/${base}` : base;
266
- };
267
- const isAbsolute = (url)=>{
268
- return url.match(/^(?:[a-z]+:)?\/\//i);
269
- };
270
-
271
- const resolveOptions = async (options, viteConfig)=>{
272
- const { type = "classic", mode = process$1.env.NODE_ENV || "production", injectRegister = "auto", registerType = "prompt", minify = true, base = viteConfig.base, includeAssets = undefined, useCredentials = false, disable = false, integration = {}, buildBase, ...injectManifest } = options;
273
- const basePath = resolveBasePath(base);
274
- // check typescript service worker for injectManifest strategy
275
- const scope = options.scope || basePath;
276
- let assetsDir = slash(viteConfig.build.assetsDir ?? "assets");
277
- if (assetsDir[assetsDir.length - 1] !== "/") assetsDir += "/";
278
- // remove './' prefix from assetsDir
279
- const dontCacheBustURLsMatching = new RegExp(`^${assetsDir.replace(/^\.*?\//, "")}`);
280
- const { plugins = [], rollupOptions = {}, rollupFormat = "es", swUrl = "/sw.js", swSrc, swDest, ...userInjectManifest } = injectManifest || {};
281
- const resolvedPluginOptions = {
282
- base: basePath,
283
- type,
284
- mode,
285
- injectRegister,
286
- registerType,
287
- useCredentials,
288
- swUrl,
289
- injectManifest: {
290
- dontCacheBustURLsMatching,
291
- ...userInjectManifest,
292
- swSrc: path.resolve(viteConfig.root, swSrc),
293
- swDest: path.resolve(viteConfig.root, viteConfig.build.outDir, swDest),
294
- disablePrecacheManifest: !viteConfig.isProduction
295
- },
296
- scope,
297
- minify,
298
- includeAssets,
299
- disable,
300
- integration,
301
- buildBase: buildBase ?? basePath,
302
- injectManifestRollupOptions: {
303
- plugins,
304
- rollupOptions,
305
- format: rollupFormat
306
- }
307
- };
308
- // calculate hash only when required
309
- const calculateHash = !resolvedPluginOptions.disable && resolvedPluginOptions.includeAssets && viteConfig.command === "build";
310
- if (calculateHash) await configureStaticAssets(resolvedPluginOptions, viteConfig);
311
- return resolvedPluginOptions;
312
- };
313
-
314
- const mainPlugin = (ctx, api)=>{
315
- return {
316
- name: "@serwist/vite",
317
- enforce: "pre",
318
- config () {
319
- return {
320
- ssr: {
321
- noExternal: []
322
- }
323
- };
324
- },
325
- async configResolved (config) {
326
- ctx.viteConfig = config;
327
- ctx.userOptions?.integration?.configureOptions?.(config, ctx.userOptions);
328
- ctx.options = await resolveOptions(ctx.userOptions, config);
329
- },
330
- resolveId (id) {
331
- if (id === INTERNAL_SERWIST_VIRTUAL) {
332
- return RESOLVED_INTERNAL_SERWIST_VIRTUAL;
333
- }
334
- return undefined;
335
- },
336
- load (id) {
337
- if (id === RESOLVED_INTERNAL_SERWIST_VIRTUAL) {
338
- return `export const swUrl = "${path.posix.join(ctx.options.buildBase, ctx.options.swUrl)}";
339
- export const swScope = "${ctx.options.scope}";
340
- export const swType = "${ctx.devEnvironment ? "module" : ctx.options.type}";`;
341
- }
342
- return undefined;
343
- },
344
- api
345
- };
346
- };
347
-
348
- export { createApi as a, cyan as b, createContext as c, devPlugin as d, dim as e, green as g, loadSerwistBuild as l, mainPlugin as m, version as v, yellow as y };