From 77c08e0bfb3f663b8294bb37c004e3b44a0b10a6 Mon Sep 17 00:00:00 2001 From: Abdur-Rahman Fashola Date: Sun, 11 Aug 2024 07:54:55 +0100 Subject: [PATCH 1/9] chore(playground): added an SPA sandbox for testing purposes --- playground/public/entry.worker.js | 360 ++++++++---------------------- playground/spa/entry.client.tsx | 20 ++ playground/spa/entry.worker.ts | 104 +++++++++ playground/spa/root.tsx | 37 +++ playground/spa/routes/_index.tsx | 136 +++++++++++ playground/spa/routes/logout.tsx | 21 ++ playground/spa/routes/test.tsx | 11 + playground/vite.config.ts | 6 +- 8 files changed, 423 insertions(+), 272 deletions(-) create mode 100644 playground/spa/entry.client.tsx create mode 100644 playground/spa/entry.worker.ts create mode 100644 playground/spa/root.tsx create mode 100644 playground/spa/routes/_index.tsx create mode 100644 playground/spa/routes/logout.tsx create mode 100644 playground/spa/routes/test.tsx diff --git a/playground/public/entry.worker.js b/playground/public/entry.worker.js index bd0006b6..ac7f02f1 100644 --- a/playground/public/entry.worker.js +++ b/playground/public/entry.worker.js @@ -4969,7 +4969,6 @@ class PushManager { console.error("Notification error:", event); } } -self.logger = logger; console.log("Hello from service worker!"); console.log("development", "https://api.example.com", "value"); const documentCache = new EnhancedCache("document-cache", { @@ -5000,6 +4999,10 @@ const isAssetRequest = (request) => { return self.__workerManifest.assets.includes(url.pathname) && hasNoParams; }; const defaultFetchHandler = async ({ request, context }) => { + const req = context.event.request; + console.log(req); + req.referrer; + req.headers.get("referer"); if (isAssetRequest(request)) { return assetCache.handleRequest(request); } @@ -5032,63 +5035,107 @@ const entryWorker = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP defaultFetchHandler, getLoadContext }, Symbol.toStringTag, { value: "Module" })); -var __getOwnPropNames$2 = Object.getOwnPropertyNames; -var __commonJS$2 = (cb, mod) => function __require() { - return mod || (0, cb[__getOwnPropNames$2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +var __getOwnPropNames$1 = Object.getOwnPropertyNames; +var __commonJS$1 = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames$1(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; -var require_worker_runtime$2 = __commonJS$2({ +var require_worker_runtime$1 = __commonJS$1({ "@remix-pwa/worker-runtime"(exports, module) { module.exports = {}; } }); -var worker_runtime_default$2 = require_worker_runtime$2(); +var worker_runtime_default$1 = require_worker_runtime$1(); const route0 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, - default: worker_runtime_default$2 + default: worker_runtime_default$1 }, Symbol.toStringTag, { value: "Module" })); -const workerAction$2 = async ({ context }) => { - const { fetchFromServer } = context; - console.log("Worker action called"); - try { - const response = await fetchFromServer(); - console.log(Object.fromEntries(response.headers.entries())); - } catch (error) { - console.error(error); - } - return new Response(JSON.stringify({ - message: "Modified action response, Remix Actions are quite out of the picture here" - }), { - headers: { - "Content-Type": "application/json; charset=utf-8" - } - }); +const workerLoader$1 = () => { + console.log("Worker loader called in logout"); + return null; }; const route1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, - workerAction: workerAction$2 + workerLoader: workerLoader$1 }, Symbol.toStringTag, { value: "Module" })); -async function workerLoader$3({ context }) { - const { fetchFromServer } = context; - const message = await Promise.race([ - fetchFromServer().then((response) => response.json()).then(({ message: message2 }) => message2).catch(() => new Promise((resolve) => setTimeout(() => resolve(null), 5e3))), - // utilizing a slower one even when cached - new Promise((resolve) => setTimeout(resolve, 500, "Hello World!\n\nā€¢ This message is sent to you from the client šŸ˜œ (Edited, again ---)!")) - ]); - return new Response( - JSON.stringify({ - message - }), - { - headers: { - "Content-Type": "application/json" - } - } - ); -} +const workerLoader = () => { + console.log("Worker loader called"); + return null; +}; const route2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, - workerLoader: workerLoader$3 + workerLoader }, Symbol.toStringTag, { value: "Module" })); +var __getOwnPropNames = Object.getOwnPropertyNames; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var require_worker_runtime = __commonJS({ + "@remix-pwa/worker-runtime"(exports, module) { + module.exports = {}; + } +}); +var worker_runtime_default = require_worker_runtime(); +const route3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + default: worker_runtime_default +}, Symbol.toStringTag, { value: "Module" })); +const assets = [ + "/entry.worker.css", + "/entry.worker.js", + "/favicon.ico", + "/manifest.json" +]; +const routes = { + "root": { + id: "root", + parentId: void 0, + path: "", + index: void 0, + caseSensitive: void 0, + hasLoader: false, + hasAction: false, + hasWorkerLoader: false, + hasWorkerAction: false, + module: route0 + }, + "routes/logout": { + id: "routes/logout", + parentId: "root", + path: "logout", + index: void 0, + caseSensitive: void 0, + hasLoader: false, + hasAction: false, + hasWorkerLoader: true, + hasWorkerAction: false, + module: route1 + }, + "routes/_index": { + id: "routes/_index", + parentId: "root", + path: void 0, + index: true, + caseSensitive: void 0, + hasLoader: false, + hasAction: false, + hasWorkerLoader: true, + hasWorkerAction: false, + module: route2 + }, + "routes/test": { + id: "routes/test", + parentId: "root", + path: "test", + index: void 0, + caseSensitive: void 0, + hasLoader: false, + hasAction: false, + hasWorkerLoader: false, + hasWorkerAction: false, + module: route3 + } +}; +const entry = { module: entryWorker }; /** * @remix-run/router v1.18.0 * @@ -9114,233 +9161,6 @@ const router$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp resolveTo, stripBasename }, Symbol.toStringTag, { value: "Module" })); -const workerAction$1 = async ({ request, context }) => { - const formData = await request.formData(); - const { database, fetchFromServer } = context; - try { - fetchFromServer(); - await database.selections.add(Object.fromEntries(formData.entries())); - return redirect$1("/selection"); - } catch (error) { - throw json$1({ message: "Something went wrong", error }, 500); - } -}; -const workerLoader$2 = async ({ context }) => { - try { - const { fetchFromServer, database } = context; - const [serverResult, clientResult] = await Promise.allSettled([ - // NOTE: If the user decides to use the server loader, must use the `context.event.request` object instead of `request`. - // This is because we strip the `_data` and `index` from the request object just to follow what Remix does. - fetchFromServer().then((response) => response.json()).then(({ flights: flights2 }) => flights2), - database.flights.toArray() - ]); - const flights = serverResult.value || clientResult.value; - if (serverResult.value) { - await database.flights.bulkPut( - flights.map((f) => ({ - ...f, - flightNumber: `${f.flightNumber.split("-")[0].trim()} - client` - })) - ); - } - return defer$1({ flights }); - } catch (error) { - console.error(error); - throw json$1({ message: "Something went wrong", error }, 500); - } -}; -const route3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - workerAction: workerAction$1, - workerLoader: workerLoader$2 -}, Symbol.toStringTag, { value: "Module" })); -async function workerLoader$1({ context }) { - const { database } = context; - const selections = await database.selections.toArray(); - return json$1({ selections }); -} -const route4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - workerLoader: workerLoader$1 -}, Symbol.toStringTag, { value: "Module" })); -const workerAction = async ({ context }) => { - const { fetchFromServer, event } = context; - try { - await fetchFromServer(); - } catch (error) { - console.error(error); - } - return new Response(JSON.stringify({ - message: "Offline or Online. I shall always respond!" - }), { - headers: { - "Content-Type": "application/json; charset=utf-8" - } - }); -}; -const route5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - workerAction -}, Symbol.toStringTag, { value: "Module" })); -var __getOwnPropNames$1 = Object.getOwnPropertyNames; -var __commonJS$1 = (cb, mod) => function __require() { - return mod || (0, cb[__getOwnPropNames$1(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; -}; -var require_worker_runtime$1 = __commonJS$1({ - "@remix-pwa/worker-runtime"(exports, module) { - module.exports = {}; - } -}); -var worker_runtime_default$1 = require_worker_runtime$1(); -const route6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - default: worker_runtime_default$1 -}, Symbol.toStringTag, { value: "Module" })); -var __getOwnPropNames = Object.getOwnPropertyNames; -var __commonJS = (cb, mod) => function __require() { - return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; -}; -var require_worker_runtime = __commonJS({ - "@remix-pwa/worker-runtime"(exports, module) { - module.exports = {}; - } -}); -var worker_runtime_default = require_worker_runtime(); -const route7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - default: worker_runtime_default -}, Symbol.toStringTag, { value: "Module" })); -async function workerLoader({ context }) { - const { fetchFromServer } = context; - const data = await fetchFromServer().then((response) => response.json()); - console.log(data); - return new Response(JSON.stringify(data), { - headers: { - "Content-Type": "application/json" - } - }); -} -const route8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - workerLoader -}, Symbol.toStringTag, { value: "Module" })); -const assets = [ - "/entry.worker.css", - "/entry.worker.js", - "/favicon.ico", - "/manifest.json" -]; -const routes = { - "root": { - id: "root", - parentId: void 0, - path: "", - index: void 0, - caseSensitive: void 0, - hasLoader: false, - hasAction: false, - hasWorkerLoader: false, - hasWorkerAction: false, - module: route0 - }, - "routes/basic-action": { - id: "routes/basic-action", - parentId: "root", - path: "basic-action", - index: void 0, - caseSensitive: void 0, - hasLoader: true, - hasAction: true, - hasWorkerLoader: false, - hasWorkerAction: true, - module: route1 - }, - "routes/basic-loader": { - id: "routes/basic-loader", - parentId: "root", - path: "basic-loader", - index: void 0, - caseSensitive: void 0, - hasLoader: true, - hasAction: false, - hasWorkerLoader: true, - hasWorkerAction: false, - module: route2 - }, - "routes/_app.flights": { - id: "routes/_app.flights", - parentId: "routes/_app", - path: "flights", - index: void 0, - caseSensitive: void 0, - hasLoader: true, - hasAction: true, - hasWorkerLoader: true, - hasWorkerAction: true, - module: route3 - }, - "routes/selection": { - id: "routes/selection", - parentId: "root", - path: "selection", - index: void 0, - caseSensitive: void 0, - hasLoader: true, - hasAction: false, - hasWorkerLoader: true, - hasWorkerAction: false, - module: route4 - }, - "routes/sync-away": { - id: "routes/sync-away", - parentId: "root", - path: "sync-away", - index: void 0, - caseSensitive: void 0, - hasLoader: false, - hasAction: true, - hasWorkerLoader: false, - hasWorkerAction: true, - module: route5 - }, - "routes/_index": { - id: "routes/_index", - parentId: "root", - path: void 0, - index: true, - caseSensitive: void 0, - hasLoader: true, - hasAction: false, - hasWorkerLoader: false, - hasWorkerAction: false, - module: route6 - }, - "routes/push": { - id: "routes/push", - parentId: "root", - path: "push", - index: void 0, - caseSensitive: void 0, - hasLoader: false, - hasAction: false, - hasWorkerLoader: false, - hasWorkerAction: false, - module: route7 - }, - "routes/_app": { - id: "routes/_app", - parentId: "root", - path: void 0, - index: void 0, - caseSensitive: void 0, - hasLoader: true, - hasAction: false, - hasWorkerLoader: true, - hasWorkerAction: false, - module: route8 - } -}; -const entry = { module: entryWorker }; var mode$2 = {}; /** * @remix-run/server-runtime v2.10.3 diff --git a/playground/spa/entry.client.tsx b/playground/spa/entry.client.tsx new file mode 100644 index 00000000..22cb4908 --- /dev/null +++ b/playground/spa/entry.client.tsx @@ -0,0 +1,20 @@ +/** + * By default, Remix will handle hydrating your app on the client for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` āœØ + * For more information, see https://remix.run/file-conventions/entry.client + */ + +import { RemixBrowser } from "@remix-run/react"; +// import { spaFetch } from "@remix-pwa/sw"; +import { startTransition } from "react"; +import { hydrateRoot } from "react-dom/client"; + +// window.fetch = spaFetch; + +startTransition(() => { + hydrateRoot( + document, + + ); +}); + diff --git a/playground/spa/entry.worker.ts b/playground/spa/entry.worker.ts new file mode 100644 index 00000000..6e121230 --- /dev/null +++ b/playground/spa/entry.worker.ts @@ -0,0 +1,104 @@ +/// + +import { EnhancedCache, isDocumentRequest, isLoaderRequest, logger, NavigationHandler } from '@remix-pwa/sw'; +import { PushManager } from '@remix-pwa/push/client'; + +declare let self: ServiceWorkerGlobalScope; + +console.log('Hello from service worker!'); +// @ts-ignore +console.log(process.env.NODE_ENV, process.env.API_URL, miscellaneous); + +const documentCache = new EnhancedCache('document-cache', { + version: 'v1', + strategy: 'NetworkFirst', + strategyOptions: {} +}) + +const assetCache = new EnhancedCache('asset-cache', { + version: 'v1', + strategy: 'CacheFirst', + strategyOptions: {} +}) + +const dataCache = new EnhancedCache('data-cache', { + version: 'v1', + strategy: 'NetworkFirst', + strategyOptions: {} +}) + +/** + * The load context works same as in Remix. The return values of this function will be injected in the worker action/loader. + * @param {FetchEvent} [event] The fetch event request. + * @returns {object} the context object. + */ +export const getLoadContext = () => { + // const stores = createStorageRepository(); + + return { + database: [], + stores: [], + caches: [documentCache, dataCache], + }; +}; + +const isAssetRequest = (request: Request)=> { + const url = new URL(request.url); + + const hasNoParams = url.search === ''; + + return self.__workerManifest.assets.includes(url.pathname) && hasNoParams; +} + +export const defaultFetchHandler = async ({ request, context }: any) => { + const req = context.event.request; + + console.log(req) + + // The referrer (the URL of the page that made the request) + const referrer = req.referrer; // This is usually the full URL of the document + + // You can also check the 'Referer' header (note the different spelling) + const refererHeader = req.headers.get('referer'); + + // console.log('Referrer (page making the request):', referrer, req.url, refererHeader, Object.fromEntries(req.headers)); + + if (isAssetRequest(request)) { + return assetCache.handleRequest(request); + } + + if (isDocumentRequest(request)) { + return documentCache.handleRequest(request); + } + + const url = new URL(context.event.request.url); + + // If it is loader request, and there's no worker route API for it, + // we have to run it ourselves + if (isLoaderRequest(request) && self.__workerManifest.routes[url.searchParams.get('_data') ?? ''].hasLoader) { + return dataCache.handleRequest(request); + } + + // logger.log('default handler'); + return context.fetchFromServer(); +} + +self.addEventListener('install', (event: ExtendableEvent) => { + logger.log('installing service worker'); + logger.warn('This is a playground service worker šŸ“¦. It is not intended for production use.'); + event.waitUntil(self.skipWaiting()); +}); + +self.addEventListener('activate', event => { + event.waitUntil(self.clients.claim()); +}); + +const msgHandler = new NavigationHandler({ + cache: documentCache, +}) + +self.addEventListener('message', async event => { + await msgHandler.handleMessage(event); +}) + +new PushManager() diff --git a/playground/spa/root.tsx b/playground/spa/root.tsx new file mode 100644 index 00000000..99a97970 --- /dev/null +++ b/playground/spa/root.tsx @@ -0,0 +1,37 @@ +import { + ManifestLink, + useSWEffect, +} from "@remix-pwa/sw"; +import { + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from "@remix-run/react"; +import { useEffect } from "react"; + +export default function App() { + useSWEffect() + + useEffect(() => { + if (typeof window !== 'undefined') console.log(window.__remixContext) + }, []) + + return ( + + + + + + + + + + + + + + + ); +} diff --git a/playground/spa/routes/_index.tsx b/playground/spa/routes/_index.tsx new file mode 100644 index 00000000..37b5a864 --- /dev/null +++ b/playground/spa/routes/_index.tsx @@ -0,0 +1,136 @@ +import { usePWAManager } from "@remix-pwa/client"; +import { spaFetch } from "@remix-pwa/sw"; +import { ClientLoaderFunctionArgs, Form, Link, useFetcher } from "@remix-run/react"; + +export const meta = () => { + return [ + { title: "šŸ“¦ (SPA) Remix PWA Sandbox" }, + { name: "description", content: "Progressive web apps proof of concept" }, + ]; +}; + +const CustomLink = ({ href, children }: any) => { + return ( + {children} {'>>'} + ) +} + +export const workerLoader = () => { + console.log('Worker loader called'); + return null; +} + +export const clientAction = async ({ request, context }: ClientLoaderFunctionArgs) => { + console.log('Client action called'); + console.log(request, context) + + await spaFetch('https://example.com', { method: 'POST', mode: 'no-cors' }); + + return null; +} + +export default function Index() { + const fetcher = useFetcher(); + const { promptInstall } = usePWAManager(); + + return ( +
+
+

Remix PWA - Worker Actions & Loaders

+ +
+
+ +
+
+
+

Basic Worker Loaders

+
+ Remix PWA evolution now includes loaders and actions that run solely on the worker thread. Utilizing esbuild + in its new compiler, Remix PWA now seeks to provide a more robust and performant experience for developers. +
+ Basic loaders route +
+
+

Basic Worker Actions

+
+ Worker actions are much more wicked than their loader counterparts. That's also right, Remix PWA now intercepts +  everything! Including POST, PUT, etc. requests. This means that you can now + intercept all requests and do whatever you want with them. Click the link below to see it in action! +  (got it, eh?) +
+ Basic actions route +
+
+

Pathless routes

+
+ Worker loaders & actions aren't just restricted to your normal routes, they also work in pathless routes! + And yes, they have the same behaviour as Remix normal loaders and actions in that they are nested and exhibit + a waterfall effect. Make sure to 'Log in' else unknown horrors awaits! +
+ Pathless Route +
+
+

Syncing your app šŸ’æ

+
+ Normally when you have no network and something fails, if it isn't something cached like a page or data, you get + an error and that's it. What if I submitted something? Or requested something? The user had to manually retry again. + Good thing we used the word "had", click the link to find out what's changed. +
+ Break the internet +
+
+

Basic Caching

+
+ Remix PWA now allows you to cache directly in your routes. Talk about finetuning your app's performance! + In this demo, we would be caching the response from the server and returning it if it's available. If not, + we would be returning the client's response instead. This is a very powerful feature that allows you to + cache your responses directly in your routes. See you on the other side of the link šŸš€! +
+ Getting started with caching +
+
+

Caching Strategies

+
+ Remix PWA has embraced the strategy approach since remix-pwa@v2 and we haven't let go in the next major + version bump. Bringing back all four strategies (Cache Only, Cache First, Network Only, Network First) as well as a + new one - Stale While Revalidate, this version +
+ Explore caching strategies +
+
+

Caching: A deeper dive šŸ¤æ

+
+ We have so far been using strategy wrappers to interact with the cache, a cache that seems super-charged thanks to its + new capabilities. How about we dive a bit deeper under the hood and tinker with stuffs by ourselves? Remix PWA is + against too much abstraction and we abstract only when deemed necessary, meaning you can say hello + to @remix-pwa/cache! The package that makes all of this possible. +
+ Begin the dive šŸŠā€ā™€ļø +
+
+

Utilities: A lot of them too!

+
+ This update doesn't just bring new caching packages, it also includes the long-awaited +  @remix-pwa/client package that enhances the feel of your app much further. Follow the link + to head into a no-rule zone showcasing some (we can't showcase all!) of the prominent features this update brings. +
+ Begin the dive šŸŠā€ā™€ļø +
+
+
+ ); +} diff --git a/playground/spa/routes/logout.tsx b/playground/spa/routes/logout.tsx new file mode 100644 index 00000000..fb8c0370 --- /dev/null +++ b/playground/spa/routes/logout.tsx @@ -0,0 +1,21 @@ +import { ClientActionFunctionArgs } from "@remix-run/react"; + +export const workerLoader = () => { + console.log('Worker loader called in logout'); + return null; +} + +export const clientAction = async ({ request }: ClientActionFunctionArgs) => { + console.log('clientAction in logout'); + console.log(request) + + await fetch('https://google.com', { method: 'POST', mode: 'no-cors' }); + + return null; +} + +// export default function C( ){ +// return ( +//
Logout?
+// ) +// } \ No newline at end of file diff --git a/playground/spa/routes/test.tsx b/playground/spa/routes/test.tsx new file mode 100644 index 00000000..a577fc1f --- /dev/null +++ b/playground/spa/routes/test.tsx @@ -0,0 +1,11 @@ +export const clientLoader = async () => { + console.log('Client loader called in test'); + await fetch('https://hashnode.com', { method: 'POST', mode: 'no-cors' }); + return null; +} + +export default function Test() { + return ( +
Test
+ ) +} diff --git a/playground/vite.config.ts b/playground/vite.config.ts index b31f7dfa..2d1c7021 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -6,12 +6,14 @@ import { remixPWA } from '@remix-pwa/dev'; installGlobals(); +const spaMode = true; + export default defineConfig({ plugins: [ remix({ ignoredRouteFiles: ["**/.*"], - appDirectory: './src/app', - // ssr: false, + appDirectory: spaMode ? './spa' : './src/app', + ssr: !spaMode, }), tsconfigPaths(), remixPWA({ From 94881595b417dcd25d48d7fe04bdcce6af49ffff Mon Sep 17 00:00:00 2001 From: Abdur-Rahman Fashola Date: Mon, 12 Aug 2024 12:14:09 +0100 Subject: [PATCH 2/9] chore: added an img playground --- playground/public/entry.worker.js | 25 ++++++++++++++++++------- playground/spa/entry.worker.ts | 6 +++++- playground/spa/routes/_index.tsx | 4 ++-- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/playground/public/entry.worker.js b/playground/public/entry.worker.js index ac7f02f1..a49102c8 100644 --- a/playground/public/entry.worker.js +++ b/playground/public/entry.worker.js @@ -5000,9 +5000,11 @@ const isAssetRequest = (request) => { }; const defaultFetchHandler = async ({ request, context }) => { const req = context.event.request; - console.log(req); req.referrer; req.headers.get("referer"); + if (req.destination === "image") { + console.log("Image request", req); + } if (isAssetRequest(request)) { return assetCache.handleRequest(request); } @@ -9414,13 +9416,21 @@ function createArgumentsFrom({ event, loadContext, path }) { function isMethod(request, methods) { return methods.includes(request.method.toLowerCase()); } -function isActionRequest(request) { +function isLoaderMethod(request) { + return isMethod(request, ["get"]); +} +function isActionMethod(request) { + return isMethod(request, ["post", "delete", "put", "patch", "head"]); +} +function isActionRequest(request, spaMode = false) { const url = new URL(request.url); - return isMethod(request, ["post", "delete", "put", "patch"]) && url.searchParams.get("_data"); + const qualifies = spaMode ? true : url.searchParams.get("_data"); + return isActionMethod(request) && qualifies; } -function isLoaderRequest(request) { +function isLoaderRequest(request, spaMode = false) { const url = new URL(request.url); - return isMethod(request, ["get"]) && url.searchParams.get("_data"); + const qualifies = spaMode ? true : url.searchParams.get("_data"); + return isLoaderMethod(request) && qualifies; } function errorResponseToJson(errorResponse) { return json_1(errorResponse.error || { message: "Unexpected Server Error" }, { @@ -9436,6 +9446,7 @@ function isRemixResponse(response) { } async function handleRequest({ defaultHandler: defaultHandler2, errorHandler, event, loadContext, routes: routes2 }) { var _a; + const isSPAMode = true === "true"; const url = new URL(event.request.url); const routeId = url.searchParams.get("_data"); const route = routeId ? routes2[routeId] : void 0; @@ -9445,7 +9456,7 @@ async function handleRequest({ defaultHandler: defaultHandler2, errorHandler, ev context: loadContext }; try { - if (isLoaderRequest(event.request) && (route == null ? void 0 : route.module.workerLoader)) { + if (isLoaderRequest(event.request, isSPAMode) && (route == null ? void 0 : route.module.workerLoader)) { return await handleLoader({ event, loader: route.module.workerLoader, @@ -9454,7 +9465,7 @@ async function handleRequest({ defaultHandler: defaultHandler2, errorHandler, ev loadContext }).then(responseHandler); } - if (isActionRequest(event.request) && ((_a = route == null ? void 0 : route.module) == null ? void 0 : _a.workerAction)) { + if (isActionRequest(event.request, isSPAMode) && ((_a = route == null ? void 0 : route.module) == null ? void 0 : _a.workerAction)) { return await handleAction({ event, action: route.module.workerAction, diff --git a/playground/spa/entry.worker.ts b/playground/spa/entry.worker.ts index 6e121230..d4575df7 100644 --- a/playground/spa/entry.worker.ts +++ b/playground/spa/entry.worker.ts @@ -53,7 +53,7 @@ const isAssetRequest = (request: Request)=> { export const defaultFetchHandler = async ({ request, context }: any) => { const req = context.event.request; - console.log(req) + // console.log(req) // The referrer (the URL of the page that made the request) const referrer = req.referrer; // This is usually the full URL of the document @@ -61,6 +61,10 @@ export const defaultFetchHandler = async ({ request, context }: any) => { // You can also check the 'Referer' header (note the different spelling) const refererHeader = req.headers.get('referer'); + if ((req as Request).destination === 'image') { + console.log('Image request', req); + } + // console.log('Referrer (page making the request):', referrer, req.url, refererHeader, Object.fromEntries(req.headers)); if (isAssetRequest(request)) { diff --git a/playground/spa/routes/_index.tsx b/playground/spa/routes/_index.tsx index 37b5a864..57e1e03b 100644 --- a/playground/spa/routes/_index.tsx +++ b/playground/spa/routes/_index.tsx @@ -1,5 +1,4 @@ import { usePWAManager } from "@remix-pwa/client"; -import { spaFetch } from "@remix-pwa/sw"; import { ClientLoaderFunctionArgs, Form, Link, useFetcher } from "@remix-run/react"; export const meta = () => { @@ -24,7 +23,7 @@ export const clientAction = async ({ request, context }: ClientLoaderFunctionArg console.log('Client action called'); console.log(request, context) - await spaFetch('https://example.com', { method: 'POST', mode: 'no-cors' }); + await fetch('https://example.com', { method: 'POST', mode: 'no-cors' }); return null; } @@ -55,6 +54,7 @@ export default function Index() {
+ Get out

Basic Worker Loaders

From 9cf2978be159d374cf130b3d3830473d74323b67 Mon Sep 17 00:00:00 2001 From: Martin Wahlberg Date: Mon, 19 Aug 2024 15:58:11 +0200 Subject: [PATCH 3/9] fix: stop useNetworkConnectivity from causing hydration errors. --- .../client/hooks/useNetworkConnectivity.ts | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/packages/client/hooks/useNetworkConnectivity.ts b/packages/client/hooks/useNetworkConnectivity.ts index d4662d45..a2a3d596 100644 --- a/packages/client/hooks/useNetworkConnectivity.ts +++ b/packages/client/hooks/useNetworkConnectivity.ts @@ -1,7 +1,18 @@ /* eslint-disable n/no-callback-literal */ -import { useEffect, useState } from 'react'; +import { useEffect, useSyncExternalStore } from 'react'; -import { isWindowAvailable } from '../lib/user-agent.js'; +const subscribeToNetworkConnectivity = (callback: () => void) => { + window.addEventListener('online', callback); + window.addEventListener('offline', callback); + return () => { + window.removeEventListener('online', callback); + window.removeEventListener('offline', callback); + }; +}; + +const getNetworkConnectivitySnapshot = () => window.navigator.onLine; + +const getNetworkConnectivityServerSnapshot = () => false; export const useNetworkConnectivity = ( options: { @@ -9,29 +20,19 @@ export const useNetworkConnectivity = ( onOffline?: (isOnline: boolean) => void; } = {} ) => { - const [isOnline, setIsOnline] = useState(isWindowAvailable() ? navigator.onLine : false); - - const handleOnline = () => { - setIsOnline(true); - options.onOnline && options.onOnline(true); - }; - - const handleOffline = () => { - setIsOnline(false); - options.onOffline && options.onOffline(false); - }; + const isOnline = useSyncExternalStore( + subscribeToNetworkConnectivity, + getNetworkConnectivitySnapshot, + getNetworkConnectivityServerSnapshot + ); useEffect(() => { - if (isWindowAvailable()) { - window.addEventListener('online', handleOnline); - window.addEventListener('offline', handleOffline); - - return () => { - window.removeEventListener('online', handleOnline); - window.removeEventListener('offline', handleOffline); - }; + if (isOnline) { + options.onOnline && options.onOnline(true); + } else if (options.onOnline) { + options.onOffline && options.onOffline(false); } - }, []); + }, [isOnline, options]); return isOnline; }; From a377627c16cf691203b3973e64bf1a5608114ace Mon Sep 17 00:00:00 2001 From: Eran Hirsch Date: Wed, 21 Aug 2024 15:23:03 +0300 Subject: [PATCH 4/9] add --- packages/dev/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dev/index.ts b/packages/dev/index.ts index 3005a5f7..36c6f1cf 100644 --- a/packages/dev/index.ts +++ b/packages/dev/index.ts @@ -105,6 +105,7 @@ export interface WebAppManifest { type?: string; platform?: string; label?: string; + form_factor?: 'narrow' | 'wide'; }>; shortcuts?: Array<{ name?: string; From 43c4987a009a77c87dc0702688b8644c50c23087 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 23 Aug 2024 22:09:28 +0000 Subject: [PATCH 5/9] chore(release): @remix-pwa/client@3.0.7-dev.1 [skip ci] ## @remix-pwa/client 3.0.7-dev.1 (2024-08-23) ### Bug Fixes * stop useNetworkConnectivity from causing hydration errors. 9cf2978 --- packages/client/CHANGELOG.md | 7 +++++++ packages/client/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index df50d268..20f97e01 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,3 +1,10 @@ +## @remix-pwa/client 3.0.7-dev.1 (2024-08-23) + + +### Bug Fixes + +* stop useNetworkConnectivity from causing hydration errors. 9cf2978 + ## @remix-pwa/client 3.0.6 (2024-07-21) diff --git a/packages/client/package.json b/packages/client/package.json index 515ef0c6..1d65e98e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@remix-pwa/client", - "version": "3.0.6", + "version": "3.0.7-dev.1", "description": "A set of utilities for client-side development to enhance the native feel of your Remix App", "repository": { "type": "git", From 7528a69f433f9cabf305ec0579d18ac56fbff1ac Mon Sep 17 00:00:00 2001 From: Andrii Yusyp Date: Wed, 4 Sep 2024 23:34:16 +0300 Subject: [PATCH 6/9] Add crossOrigin to ManifestLink to be able to fix CORS issues --- packages/sw/src/components/ManifestLink.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/sw/src/components/ManifestLink.tsx b/packages/sw/src/components/ManifestLink.tsx index 109b4e56..55a7b152 100644 --- a/packages/sw/src/components/ManifestLink.tsx +++ b/packages/sw/src/components/ManifestLink.tsx @@ -1,5 +1,12 @@ +import type { LinkHTMLAttributes } from 'react'; import React from 'react'; -export const ManifestLink = ({ manifestUrl = '/manifest.webmanifest' }: { manifestUrl?: string }) => { - return ; +export const ManifestLink = ({ + crossOrigin, + manifestUrl = '/manifest.webmanifest', +}: { + manifestUrl?: string; + crossOrigin?: LinkHTMLAttributes['crossOrigin']; +}) => { + return ; }; From 942f76a26cf894d7aa7efd2f0781bb3c4a080b23 Mon Sep 17 00:00:00 2001 From: ShafSpecs Date: Tue, 24 Sep 2024 17:24:30 +0100 Subject: [PATCH 7/9] fix(sw): changed manifest link `rel` from `manifest` to `webmanifest` --- packages/sw/src/components/ManifestLink.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sw/src/components/ManifestLink.tsx b/packages/sw/src/components/ManifestLink.tsx index 109b4e56..3fbe8b48 100644 --- a/packages/sw/src/components/ManifestLink.tsx +++ b/packages/sw/src/components/ManifestLink.tsx @@ -1,5 +1,5 @@ import React from 'react'; export const ManifestLink = ({ manifestUrl = '/manifest.webmanifest' }: { manifestUrl?: string }) => { - return ; + return ; }; From 70a204453041d5f3b446a3add6e7d213c8bf96dc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 24 Sep 2024 16:30:54 +0000 Subject: [PATCH 8/9] chore(release): @remix-pwa/sw@3.0.10-dev.1 [skip ci] ## @remix-pwa/sw 3.0.10-dev.1 (2024-09-24) ### Bug Fixes * **sw:** changed manifest link `rel` from `manifest` to `webmanifest` 942f76a --- packages/sw/CHANGELOG.md | 7 +++++++ packages/sw/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/sw/CHANGELOG.md b/packages/sw/CHANGELOG.md index f89a34e7..cef4e51e 100644 --- a/packages/sw/CHANGELOG.md +++ b/packages/sw/CHANGELOG.md @@ -1,3 +1,10 @@ +## @remix-pwa/sw 3.0.10-dev.1 (2024-09-24) + + +### Bug Fixes + +* **sw:** changed manifest link `rel` from `manifest` to `webmanifest` 942f76a + ## @remix-pwa/sw 3.0.9 (2024-08-11) diff --git a/packages/sw/package.json b/packages/sw/package.json index e67571bb..44741744 100644 --- a/packages/sw/package.json +++ b/packages/sw/package.json @@ -1,6 +1,6 @@ { "name": "@remix-pwa/sw", - "version": "3.0.9", + "version": "3.0.10-dev.1", "description": "Service Worker APIs and utilities for Remix PWA", "repository": { "type": "git", From cfefe4f7da81a35f502132c6b87e059fa7be0ae4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 24 Sep 2024 16:31:10 +0000 Subject: [PATCH 9/9] chore(release): @remix-pwa/sync@3.0.5-dev.1 [skip ci] ## @remix-pwa/sync 3.0.5-dev.1 (2024-09-24) ### Dependencies * **@remix-pwa/sw:** upgraded to 3.0.10-dev.1 --- packages/sync/CHANGELOG.md | 10 ++++++++++ packages/sync/package.json | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/sync/CHANGELOG.md b/packages/sync/CHANGELOG.md index a0d872d1..67624920 100644 --- a/packages/sync/CHANGELOG.md +++ b/packages/sync/CHANGELOG.md @@ -1,3 +1,13 @@ +## @remix-pwa/sync 3.0.5-dev.1 (2024-09-24) + + + + + +### Dependencies + +* **@remix-pwa/sw:** upgraded to 3.0.10-dev.1 + ## @remix-pwa/sync 3.0.4 (2024-08-11) diff --git a/packages/sync/package.json b/packages/sync/package.json index 394276de..fd7f1ebc 100644 --- a/packages/sync/package.json +++ b/packages/sync/package.json @@ -1,6 +1,6 @@ { "name": "@remix-pwa/sync", - "version": "3.0.4", + "version": "3.0.5-dev.1", "description": "A Background Sync addon for Remix PWA", "repository": { "type": "git", @@ -34,7 +34,7 @@ "rimraf": "^6.0.1" }, "dependencies": { - "@remix-pwa/sw": "^3.0.9", + "@remix-pwa/sw": "^3.0.10-dev.1", "idb": "^8.0.0" } }