Skip to content

Commit 04fd604

Browse files
authored
Merge pull request #269 from remix-pwa/v5-rc
Remix PWA v5 Release Candidate
2 parents 3004f72 + 752b57a commit 04fd604

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+5527
-6712
lines changed

package-lock.json

+3,807-2,008
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"packages/dev",
99
"packages/eslint-config",
1010
"packages/lint-staged-config",
11+
"packages/manifest",
1112
"packages/push",
1213
"packages/sw",
1314
"packages/sync",

packages/client/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ export {
4545
export { useBadgeApi } from './hooks/useBadgeApi.js';
4646
export { useBatteryManager } from './hooks/useBatteryManager.js';
4747
export { useNetworkConnectivity } from './hooks/useNetworkConnectivity.js';
48-
export { usePWAManager } from './hooks/usePWAManager.js';
4948
export { usePermission } from './hooks/usePermission.js';
5049
export type { PermissionName, PermissionState, PermissionStatus } from './hooks/usePermission.js';
50+
export { usePWAManager } from './hooks/usePWAManager.js';

packages/dev/index.ts

-146
Original file line numberDiff line numberDiff line change
@@ -1,149 +1,3 @@
11
// Vite ✨
22
export { remixPWA } from './src/index.js';
33
export type { PWAOptions as PWAViteOptions } from './src/types.js';
4-
5-
export interface WebAppManifest {
6-
/**
7-
* The name member is a string that represents the name of the web application as it is usually
8-
* displayed to the user (e.g., amongst a list of other applications, or as a label for an icon).
9-
*/
10-
name?: string;
11-
/**
12-
* The short_name member is a string that represents the name of the web application displayed to the
13-
* user if there is not enough space to display name.
14-
*/
15-
short_name?: string;
16-
/**
17-
* The description member is a string that provides a description of the purpose of the web application.
18-
*/
19-
description?: string;
20-
/**
21-
* The icons member specifies an array of image objects that can serve as application icons for different
22-
* contexts. For example, they can be used to represent the web application amongst a list of other applications,
23-
* or to integrate the web application with an operating system's task switcher and/or system preferences.
24-
*/
25-
icons?: Array<{
26-
src: string;
27-
sizes?: string;
28-
type?: string;
29-
purpose?: 'any' | 'maskable' | 'monochrome';
30-
}>;
31-
/**
32-
* The start_url member is a string that represents the start URL of the web application — the preferential URL that
33-
* should be loaded when the user launches the web application (e.g., when the user taps on the web application's icon
34-
* from a device's application menu or homescreen).
35-
*/
36-
start_url?: string;
37-
/**
38-
* The display member is a string that determines the developers’ preferred display mode for the website.
39-
* The display mode changes how much of browser UI is shown to the user and can range from a browser (when the full
40-
* browser window is shown) to standalone (when the app is run without any browser UI).
41-
*
42-
* The default for display is `browser`, which results in the normal browser UI being shown.
43-
*
44-
* The full options are:
45-
* - `fullscreen`: All of the available display area is used and no user agent chrome is shown.
46-
* - `standalone`: The application will look and feel like a standalone application. This can include the application having a different window, its own icon in the application launcher, etc.
47-
* - `minimal-ui`: The application will look and feel like a standalone application, but will have a minimal set of UI elements for controlling navigation.
48-
* - `browser`: The application opens in a conventional browser tab or new window, depending on the browser and platform.
49-
*/
50-
display?: 'fullscreen' | 'standalone' | 'minimal-ui' | 'browser';
51-
display_override?: Array<'window-controls-overlay' | 'bordered' | 'standard'>;
52-
/**
53-
* The orientation member is a string that represents the default orientation of the web application.
54-
* The value must be a string set to one of the following values:
55-
* - `any`
56-
* - `natural`
57-
* - `landscape`
58-
* - `landscape-primary`
59-
* - `landscape-secondary`
60-
* - `portrait`
61-
* - `portrait-primary`
62-
* - `portrait-secondary`
63-
*/
64-
orientation?:
65-
| 'any'
66-
| 'natural'
67-
| 'landscape'
68-
| 'landscape-primary'
69-
| 'landscape-secondary'
70-
| 'portrait'
71-
| 'portrait-primary'
72-
| 'portrait-secondary';
73-
/**
74-
* The dir member is a string that represents the directionality of the web application.
75-
*
76-
* The value must be a string set to one of the following values:
77-
* - `ltr`: Left to right
78-
* - `rtl`: Right to left
79-
* - `auto`: Let the user agent decide based on the value of the `lang` attribute on the root element
80-
*/
81-
dir?: 'ltr' | 'rtl' | 'auto';
82-
/**
83-
* The lang member is a string that represents the primary language for the [localizable members](https://www.w3.org/TR/appmanifest/#dfn-localizable-members)
84-
* of the manifest (as knowing the language can also help with directionality).
85-
*/
86-
lang?: string;
87-
prefer_related_applications?: boolean;
88-
related_applications?: Array<{
89-
platform: string;
90-
url?: string;
91-
id?: string;
92-
min_version?: string;
93-
fingerprints?: Array<{
94-
type: string;
95-
value: string;
96-
}>;
97-
}>;
98-
/**
99-
* The scope member is a string that represents the navigation scope of this web application's application context.
100-
*/
101-
scope?: string;
102-
screenshots?: Array<{
103-
src: string;
104-
sizes?: string;
105-
type?: string;
106-
platform?: string;
107-
label?: string;
108-
form_factor?: 'narrow' | 'wide';
109-
}>;
110-
shortcuts?: Array<{
111-
name?: string;
112-
short_name?: string;
113-
description?: string;
114-
url?: string;
115-
icons?: Array<{
116-
src: string;
117-
sizes?: string;
118-
type?: string;
119-
purpose?: 'any' | 'maskable' | 'monochrome';
120-
}>;
121-
}>;
122-
share_target?: {
123-
action?: string;
124-
method?: 'GET' | 'POST';
125-
enctype?: string;
126-
params?: {
127-
[key: string]: {
128-
name?: string;
129-
title?: string;
130-
description?: string;
131-
};
132-
};
133-
};
134-
protocol_handlers?: Array<{
135-
protocol: string;
136-
url: string;
137-
}>;
138-
note?: string;
139-
/**
140-
* The background_color member defines a placeholder background color for the application page to display before its stylesheet is loaded.
141-
*/
142-
background_color?: string;
143-
/**
144-
* The theme_color member is a string that defines the default theme color for the application.
145-
*/
146-
theme_color?: string;
147-
categories?: Array<string>;
148-
iarc_rating_ids?: Array<string>;
149-
}

packages/dev/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"@babel/traverse": "^7.24.8",
4444
"@rollup/plugin-commonjs": "^26.0.1",
4545
"@rollup/plugin-node-resolve": "^15.2.3",
46-
"chokidar": "^3.6.0",
46+
"chokidar": "^4.0.1",
4747
"fast-glob": "^3.3.2",
4848
"fs-extra": "^11.2.0",
4949
"lodash": "^4.17.21",
@@ -54,7 +54,7 @@
5454
"devDependencies": {
5555
"@remix-pwa/eslint-config": "^0.0.0",
5656
"@remix-pwa/lint-staged-config": "^0.0.0",
57-
"@remix-run/dev": "^2.10.3",
57+
"@remix-run/dev": "^2.12.1",
5858
"@types/babel__core": "^7.20.5",
5959
"@types/babel__generator": "^7.6.8",
6060
"@types/babel__traverse": "^7.20.6",

packages/dev/src/__test__/utils.test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ describe('Plugin resolver test suite', () => {
7171
'process.env.NODE_ENV': 'production',
7272
'process.env.__REMIX_PWA_SPA_MODE': 'false',
7373
},
74+
injectSWRegister: true,
7475
workerSourceMap: false,
7576
publicPath: '/build/',
7677
entryWorkerFile: 'entry.worker.ts',
@@ -112,6 +113,7 @@ describe('Plugin resolver test suite', () => {
112113
appDirectory: '/Users/ryan/Projects/remix-pwa/app',
113114
ignoredSWRouteFiles: [],
114115
entryWorkerFile: 'entry.worker.ts',
116+
injectSWRegister: true,
115117
publicPath: '/build/',
116118
workerEntryPoint: '@remix-pwa/worker-runtime',
117119
workerSourceMap: false,
@@ -144,6 +146,7 @@ describe('Plugin resolver test suite', () => {
144146
'process.env.__REMIX_PWA_SPA_MODE': 'false',
145147
},
146148
entryWorkerFile: 'entry.worker.ts',
149+
injectSWRegister: true,
147150
workerEntryPoint: '@remix-pwa/worker-runtime',
148151
scope: '/',
149152
rootDirectory: '/Users/ryan/Projects/remix-pwa',
@@ -182,6 +185,7 @@ describe('Plugin resolver test suite', () => {
182185
'process.env.API_URL': 'https://api.example.com',
183186
},
184187
entryWorkerFile: 'entry.worker.ts',
188+
injectSWRegister: true,
185189
workerEntryPoint: '@remix-pwa/worker-runtime',
186190
scope: '/',
187191
rootDirectory: '/Users/ryan/Projects/remix-pwa',

packages/dev/src/plugins/__test__/virtual-sw-plugin.test.ts

+69
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,33 @@ describe('Remix PWA Vite VirtualSW Plugin', () => {
238238
caseSensitive: true,`
239239
);
240240
});
241+
242+
test('should skip worker route APIs if single fetch is enabled', async () => {
243+
const { createRouteManifest } = await import('../virtual-sw');
244+
const routes = {
245+
'routes/home': {
246+
id: 'routes/home',
247+
parentId: 'root',
248+
path: '/home',
249+
index: false,
250+
caseSensitive: true,
251+
file: 'home.tsx',
252+
},
253+
'routes/about': {
254+
id: 'routes/about',
255+
parentId: 'root',
256+
path: '/about',
257+
index: false,
258+
caseSensitive: true,
259+
file: 'about.tsx',
260+
},
261+
} as RouteManifest;
262+
263+
const result = await createRouteManifest(routes, '/', [], true);
264+
265+
expect(result).not.contain('workerLoader');
266+
expect(result).not.contain('workerAction');
267+
});
241268
});
242269
});
243270

@@ -270,10 +297,18 @@ describe('Remix PWA Vite VirtualSW Plugin', () => {
270297
},
271298
} as RouteManifest,
272299
},
300+
viteConfig: {
301+
logger: {
302+
warnOnce: (str: string) => str,
303+
},
304+
},
273305
isRemixDevServer: true,
274306
__remixPluginContext: {
275307
remixConfig: {
276308
buildDirectory: '/build/',
309+
future: {
310+
unstable_singleFetch: false,
311+
},
277312
},
278313
},
279314
} as unknown as PWAPluginContext;
@@ -344,6 +379,21 @@ const a = 1;`);
344379
expect(await plugin[1].load('\0virtual:entry-sw')).toContain('export const routes = {');
345380
expect(await plugin[1].load('\0virtual:entry-sw')).toContain('export const entry = { module: entryWorker }');
346381
});
382+
383+
test('should not include worker route module imports when single fetch is enabled', async () => {
384+
mockContext.__remixPluginContext.remixConfig = {
385+
...mockContext.__remixPluginContext.remixConfig,
386+
// @ts-ignore We don't care about the rest
387+
future: {
388+
unstable_singleFetch: true,
389+
},
390+
};
391+
392+
const _plugin = (await import('../virtual-sw')).VirtualSWPlugins;
393+
plugin = _plugin(mockContext as PWAPluginContext);
394+
395+
expect(await plugin[1].load('\0virtual:entry-sw')).not.contain('import * as route');
396+
});
347397
});
348398

349399
describe('Virtual Routes Plugin', () => {
@@ -371,6 +421,25 @@ const a = 1;`);
371421

372422
expect(result).toBe('module.exports = {}');
373423
});
424+
425+
test('should return an empty module always if the single fetch is enabled', async () => {
426+
mockContext.__remixPluginContext.remixConfig = {
427+
...mockContext.__remixPluginContext.remixConfig,
428+
// @ts-ignore We don't care about the rest
429+
future: {
430+
unstable_singleFetch: true,
431+
},
432+
};
433+
434+
const _plugin = (await import('../virtual-sw')).VirtualSWPlugins;
435+
plugin = _plugin(mockContext as PWAPluginContext);
436+
437+
const homeResult = await plugin[2].load('virtual:worker:routes/home.tsx');
438+
const aboutResult = await plugin[2].load('virtual:worker:routes/about.tsx');
439+
440+
expect(homeResult).toBe(undefined);
441+
expect(aboutResult).toBe(undefined);
442+
});
374443
});
375444

376445
describe('Virtual Assets Plugin', () => {

packages/dev/src/plugins/bundler.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,22 @@ const transformedObject = (obj: Record<string, string>) =>
1313
Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, JSON.stringify(value)]));
1414

1515
export async function buildWorker(_ctx: PWAPluginContext) {
16-
const DEFAULT_VARS = {
16+
const DEFAULT_VARS: Record<string, string | any> = {
1717
'process.env.NODE_ENV': _ctx.isDev ? 'development' : 'production',
1818
'process.env.__REMIX_PWA_SPA_MODE': _ctx.__remixPluginContext.remixConfig.ssr ? 'false' : 'true',
19+
'process.env.__REMIX_SINGLE_FETCH': _ctx.__remixPluginContext.remixConfig.future.unstable_singleFetch
20+
? 'true'
21+
: 'false',
1922
};
2023

24+
DEFAULT_VARS['process.env'] = JSON.stringify(
25+
Object.fromEntries(
26+
Object.entries(DEFAULT_VARS)
27+
.filter(([key]) => key.startsWith('process.env.'))
28+
.map(([key, value]) => [key.replace('process.env.', ''), value])
29+
)
30+
);
31+
2132
try {
2233
await build({
2334
logLevel: 'error',
@@ -122,7 +133,7 @@ export function BundlerPlugin(ctx: PWAPluginContext): Plugin {
122133
return testString.startsWith('.');
123134
},
124135
followSymlinks: false,
125-
disableGlobbing: false,
136+
// disableGlobbing: false, // if an error arises 👈
126137
});
127138

128139
const shouldAppReload = (path: string) => {

packages/dev/src/plugins/loader.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,18 @@ export function LoaderPlugin(ctx: PWAPluginContext): Plugin {
77
name: 'vite-plugin-remix-pwa:loader',
88
enforce: 'pre',
99
transform(code, id) {
10-
if (Array.isArray(id.match(/root\.(tsx|jsx)$/)) && ctx.options.registerSW === 'script') {
10+
if (
11+
Array.isArray(id.match(/root\.(tsx|jsx)$/)) &&
12+
(ctx.options.registerSW === 'script' || ctx.options.injectSWRegister)
13+
) {
14+
if (code.includes('<PWAScripts')) {
15+
ctx.viteConfig.logger.warnOnce(
16+
'💥 Usage of `PWAScripts` disables Service Worker injection! Either remove it or disable `injectSWRegister`'
17+
);
18+
19+
return code;
20+
}
21+
1122
return code.replace(
1223
'</head>',
1324
[

0 commit comments

Comments
 (0)