From cedea10594c99b9478334bd61e8e2a51acdbc635 Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Tue, 23 Apr 2024 20:46:31 +0300 Subject: [PATCH] add service-worker (cache routes) --- apps/web/hooks/index.ts | 1 + apps/web/hooks/worker.ts | 20 +++ apps/web/package.json | 10 +- apps/web/pages/_app.jsx | 5 +- apps/web/public/sw.js | 1 + apps/web/service-worker.ts | 29 ++++ apps/web/tsconfig.json | 2 +- package.json | 3 +- pnpm-lock.yaml | 319 ++++++++++++++++++++++++++++++++++++- turbo.json | 3 + 10 files changed, 386 insertions(+), 7 deletions(-) create mode 100644 apps/web/hooks/worker.ts create mode 100644 apps/web/public/sw.js create mode 100644 apps/web/service-worker.ts diff --git a/apps/web/hooks/index.ts b/apps/web/hooks/index.ts index 22652cc..3a5697e 100644 --- a/apps/web/hooks/index.ts +++ b/apps/web/hooks/index.ts @@ -1 +1,2 @@ export * from './loading'; +export * from './worker'; diff --git a/apps/web/hooks/worker.ts b/apps/web/hooks/worker.ts new file mode 100644 index 0000000..4d42c72 --- /dev/null +++ b/apps/web/hooks/worker.ts @@ -0,0 +1,20 @@ +/* eslint-disable no-console */ +import { withBasePath } from '@/config/urls'; +import { useEffect } from 'react'; + +export function useServiceWorker() { + useEffect(() => { + if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker + .register(withBasePath('/sw.js')) + .then((registration) => { + console.log('Service Worker registered:', registration); + }) + .catch((error) => { + console.error('Service Worker registration failed:', error); + }); + }); + } + }, []); +} diff --git a/apps/web/package.json b/apps/web/package.json index ef9f72a..1d22a36 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,7 +12,8 @@ "start": "next start", "graphql:update": "node ./scripts/graphql-update.js", "graphql:codegen": "node ./scripts/graphql-codegen.js", - "test": "jest" + "test": "jest", + "build:worker": "esbuild ./service-worker.ts --bundle --minify --outfile=./public/sw.js" }, "dependencies": { "@apollo/client": "^3.9.5", @@ -55,6 +56,7 @@ "@types/styled-components": "^5.1.26", "@vchikalkin/eslint-config-awesome": "^1.1.6", "antd": "^5.14.2", + "esbuild": "^0.20.2", "eslint": "^8.52.0", "gql-sdl": "^1.0.0", "jest": "^29.4.3", @@ -63,7 +65,11 @@ "shared": "workspace:*", "ts-jest": "^29.0.5", "ts-node": "^10.9.1", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "workbox-core": "^7.0.0", + "workbox-expiration": "^7.0.0", + "workbox-routing": "^7.0.0", + "workbox-strategies": "^7.0.0" }, "msw": { "workerDirectory": "public" diff --git a/apps/web/pages/_app.jsx b/apps/web/pages/_app.jsx index 3660ed9..f0f9953 100644 --- a/apps/web/pages/_app.jsx +++ b/apps/web/pages/_app.jsx @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import '../styles/fonts.css'; import '../styles/globals.css'; import '../styles/antd-fix.css'; @@ -7,7 +8,7 @@ import initializeApollo from '@/apollo/client'; import { Loading, Notification } from '@/Components/Common'; import Layout from '@/Components/Layout'; import { theme } from '@/config/ui'; -import { usePageLoading } from '@/hooks'; +import { usePageLoading, useServiceWorker } from '@/hooks'; import StoreProvider from '@/stores/Provider'; import getColors from '@/styles/colors'; import { GlobalStyle } from '@/styles/global-style'; @@ -32,6 +33,8 @@ function App({ Component, pageProps }) { const { loading } = usePageLoading(); + useServiceWorker(); + return ( diff --git a/apps/web/public/sw.js b/apps/web/public/sw.js new file mode 100644 index 0000000..12d2a8a --- /dev/null +++ b/apps/web/public/sw.js @@ -0,0 +1 @@ +"use strict";(()=>{try{self["workbox:core:7.0.0"]&&_()}catch{}var ce=(r,...e)=>{let t=r;return e.length>0&&(t+=` :: ${JSON.stringify(e)}`),t};var z=ce;var u=class extends Error{constructor(e,t){let o=z(e,t);super(o),this.name=e,this.details=t}};var S=new Set;function W(r){S.add(r)}var d={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:typeof registration<"u"?registration.scope:""},M=r=>[d.prefix,r,d.suffix].filter(e=>e&&e.length>0).join("-"),ue=r=>{for(let e of Object.keys(d))r(e)},w={updateDetails:r=>{ue(e=>{typeof r[e]=="string"&&(d[e]=r[e])})},getGoogleAnalyticsName:r=>r||M(d.googleAnalytics),getPrecacheName:r=>r||M(d.precache),getPrefix:()=>d.prefix,getRuntimeName:r=>r||M(d.runtime),getSuffix:()=>d.suffix};function X(r,e){let t=new URL(r);for(let o of e)t.searchParams.delete(o);return t.href}async function F(r,e,t,o){let s=X(e.url,t);if(e.url===s)return r.match(e,o);let a=Object.assign(Object.assign({},o),{ignoreSearch:!0}),n=await r.keys(e,a);for(let i of n){let c=X(i.url,t);if(s===c)return r.match(i,o)}}function y(r){r.then(()=>{})}var x=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}};async function I(){for(let r of S)await r()}var U=r=>new URL(String(r),location.href).href.replace(new RegExp(`^${location.origin}`),"");function b(r){return new Promise(e=>setTimeout(e,r))}function B(){self.addEventListener("activate",()=>self.clients.claim())}var he=(r,e)=>e.some(t=>r instanceof t),Z,ee;function de(){return Z||(Z=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])}function fe(){return ee||(ee=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])}var te=new WeakMap,j=new WeakMap,re=new WeakMap,H=new WeakMap,K=new WeakMap;function ge(r){let e=new Promise((t,o)=>{let s=()=>{r.removeEventListener("success",a),r.removeEventListener("error",n)},a=()=>{t(p(r.result)),s()},n=()=>{o(r.error),s()};r.addEventListener("success",a),r.addEventListener("error",n)});return e.then(t=>{t instanceof IDBCursor&&te.set(t,r)}).catch(()=>{}),K.set(e,r),e}function we(r){if(j.has(r))return;let e=new Promise((t,o)=>{let s=()=>{r.removeEventListener("complete",a),r.removeEventListener("error",n),r.removeEventListener("abort",n)},a=()=>{t(),s()},n=()=>{o(r.error||new DOMException("AbortError","AbortError")),s()};r.addEventListener("complete",a),r.addEventListener("error",n),r.addEventListener("abort",n)});j.set(r,e)}var G={get(r,e,t){if(r instanceof IDBTransaction){if(e==="done")return j.get(r);if(e==="objectStoreNames")return r.objectStoreNames||re.get(r);if(e==="store")return t.objectStoreNames[1]?void 0:t.objectStore(t.objectStoreNames[0])}return p(r[e])},set(r,e,t){return r[e]=t,!0},has(r,e){return r instanceof IDBTransaction&&(e==="done"||e==="store")?!0:e in r}};function oe(r){G=r(G)}function Ee(r){return r===IDBDatabase.prototype.transaction&&!("objectStoreNames"in IDBTransaction.prototype)?function(e,...t){let o=r.call(q(this),e,...t);return re.set(o,e.sort?e.sort():[e]),p(o)}:fe().includes(r)?function(...e){return r.apply(q(this),e),p(te.get(this))}:function(...e){return p(r.apply(q(this),e))}}function Ne(r){return typeof r=="function"?Ee(r):(r instanceof IDBTransaction&&we(r),he(r,de())?new Proxy(r,G):r)}function p(r){if(r instanceof IDBRequest)return ge(r);if(H.has(r))return H.get(r);let e=Ne(r);return e!==r&&(H.set(r,e),K.set(e,r)),e}var q=r=>K.get(r);function ne(r,e,{blocked:t,upgrade:o,blocking:s,terminated:a}={}){let n=indexedDB.open(r,e),i=p(n);return o&&n.addEventListener("upgradeneeded",c=>{o(p(n.result),c.oldVersion,c.newVersion,p(n.transaction),c)}),t&&n.addEventListener("blocked",c=>t(c.oldVersion,c.newVersion,c)),i.then(c=>{a&&c.addEventListener("close",()=>a()),s&&c.addEventListener("versionchange",l=>s(l.oldVersion,l.newVersion,l))}).catch(()=>{}),i}function ae(r,{blocked:e}={}){let t=indexedDB.deleteDatabase(r);return e&&t.addEventListener("blocked",o=>e(o.oldVersion,o)),p(t).then(()=>{})}var ye=["get","getKey","getAll","getAllKeys","count"],xe=["put","add","delete","clear"],Q=new Map;function se(r,e){if(!(r instanceof IDBDatabase&&!(e in r)&&typeof e=="string"))return;if(Q.get(e))return Q.get(e);let t=e.replace(/FromIndex$/,""),o=e!==t,s=xe.includes(t);if(!(t in(o?IDBIndex:IDBObjectStore).prototype)||!(s||ye.includes(t)))return;let a=async function(n,...i){let c=this.transaction(n,s?"readwrite":"readonly"),l=c.store;return o&&(l=l.index(i.shift())),(await Promise.all([l[t](...i),s&&c.done]))[0]};return Q.set(e,a),a}oe(r=>({...r,get:(e,t,o)=>se(e,t)||r.get(e,t,o),has:(e,t)=>!!se(e,t)||r.has(e,t)}));try{self["workbox:expiration:7.0.0"]&&_()}catch{}var be="workbox-expiration",R="cache-entries",ie=r=>{let e=new URL(r,location.href);return e.hash="",e.href},A=class{constructor(e){this._db=null,this._cacheName=e}_upgradeDb(e){let t=e.createObjectStore(R,{keyPath:"id"});t.createIndex("cacheName","cacheName",{unique:!1}),t.createIndex("timestamp","timestamp",{unique:!1})}_upgradeDbAndDeleteOldDbs(e){this._upgradeDb(e),this._cacheName&&ae(this._cacheName)}async setTimestamp(e,t){e=ie(e);let o={url:e,timestamp:t,cacheName:this._cacheName,id:this._getId(e)},a=(await this.getDb()).transaction(R,"readwrite",{durability:"relaxed"});await a.store.put(o),await a.done}async getTimestamp(e){let o=await(await this.getDb()).get(R,this._getId(e));return o?.timestamp}async expireEntries(e,t){let o=await this.getDb(),s=await o.transaction(R).store.index("timestamp").openCursor(null,"prev"),a=[],n=0;for(;s;){let c=s.value;c.cacheName===this._cacheName&&(e&&c.timestamp=t?a.push(s.value):n++),s=await s.continue()}let i=[];for(let c of a)await o.delete(R,c.id),i.push(c.url);return i}_getId(e){return this._cacheName+"|"+ie(e)}async getDb(){return this._db||(this._db=await ne(be,1,{upgrade:this._upgradeDbAndDeleteOldDbs.bind(this)})),this._db}};var k=class{constructor(e,t={}){this._isRunning=!1,this._rerunRequested=!1,this._maxEntries=t.maxEntries,this._maxAgeSeconds=t.maxAgeSeconds,this._matchOptions=t.matchOptions,this._cacheName=e,this._timestampModel=new A(e)}async expireEntries(){if(this._isRunning){this._rerunRequested=!0;return}this._isRunning=!0;let e=this._maxAgeSeconds?Date.now()-this._maxAgeSeconds*1e3:0,t=await this._timestampModel.expireEntries(e,this._maxEntries),o=await self.caches.open(this._cacheName);for(let s of t)await o.delete(s,this._matchOptions);this._isRunning=!1,this._rerunRequested&&(this._rerunRequested=!1,y(this.expireEntries()))}async updateTimestamp(e){await this._timestampModel.setTimestamp(e,Date.now())}async isURLExpired(e){if(this._maxAgeSeconds){let t=await this._timestampModel.getTimestamp(e),o=Date.now()-this._maxAgeSeconds*1e3;return t!==void 0?t{if(!a)return null;let n=this._isResponseDateFresh(a),i=this._getCacheExpiration(s);y(i.expireEntries());let c=i.updateTimestamp(o.url);if(t)try{t.waitUntil(c)}catch{}return n?a:null},this.cacheDidUpdate=async({cacheName:t,request:o})=>{let s=this._getCacheExpiration(t);await s.updateTimestamp(o.url),await s.expireEntries()},this._config=e,this._maxAgeSeconds=e.maxAgeSeconds,this._cacheExpirations=new Map,e.purgeOnQuotaError&&W(()=>this.deleteCacheAndMetadata())}_getCacheExpiration(e){if(e===w.getRuntimeName())throw new u("expire-custom-caches-only");let t=this._cacheExpirations.get(e);return t||(t=new k(e,this._config),this._cacheExpirations.set(e,t)),t}_isResponseDateFresh(e){if(!this._maxAgeSeconds)return!0;let t=this._getDateHeaderTimestamp(e);if(t===null)return!0;let o=Date.now();return t>=o-this._maxAgeSeconds*1e3}_getDateHeaderTimestamp(e){if(!e.headers.has("date"))return null;let t=e.headers.get("date"),s=new Date(t).getTime();return isNaN(s)?null:s}async deleteCacheAndMetadata(){for(let[e,t]of this._cacheExpirations)await self.caches.delete(e),await t.delete();this._cacheExpirations=new Map}};try{self["workbox:routing:7.0.0"]&&_()}catch{}var L="GET";var E=r=>r&&typeof r=="object"?r:{handle:r};var h=class{constructor(e,t,o=L){this.handler=E(t),this.match=e,this.method=o}setCatchHandler(e){this.catchHandler=E(e)}};var D=class extends h{constructor(e,t,o){let s=({url:a})=>{let n=e.exec(a.href);if(n&&!(a.origin!==location.origin&&n.index!==0))return n.slice(1)};super(s,t,o)}};var C=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",e=>{let{request:t}=e,o=this.handleRequest({request:t,event:e});o&&e.respondWith(o)})}addCacheListener(){self.addEventListener("message",e=>{if(e.data&&e.data.type==="CACHE_URLS"){let{payload:t}=e.data,o=Promise.all(t.urlsToCache.map(s=>{typeof s=="string"&&(s=[s]);let a=new Request(...s);return this.handleRequest({request:a,event:e})}));e.waitUntil(o),e.ports&&e.ports[0]&&o.then(()=>e.ports[0].postMessage(!0))}})}handleRequest({request:e,event:t}){let o=new URL(e.url,location.href);if(!o.protocol.startsWith("http"))return;let s=o.origin===location.origin,{params:a,route:n}=this.findMatchingRoute({event:t,request:e,sameOrigin:s,url:o}),i=n&&n.handler,c=[],l=e.method;if(!i&&this._defaultHandlerMap.has(l)&&(i=this._defaultHandlerMap.get(l)),!i)return;let g;try{g=i.handle({url:o,request:e,event:t,params:a})}catch(N){g=Promise.reject(N)}let m=n&&n.catchHandler;return g instanceof Promise&&(this._catchHandler||m)&&(g=g.catch(async N=>{if(m)try{return await m.handle({url:o,request:e,event:t,params:a})}catch(Y){Y instanceof Error&&(N=Y)}if(this._catchHandler)return this._catchHandler.handle({url:o,request:e,event:t});throw N})),g}findMatchingRoute({url:e,sameOrigin:t,request:o,event:s}){let a=this._routes.get(o.method)||[];for(let n of a){let i,c=n.match({url:e,sameOrigin:t,request:o,event:s});if(c)return i=c,(Array.isArray(i)&&i.length===0||c.constructor===Object&&Object.keys(c).length===0||typeof c=="boolean")&&(i=void 0),{route:n,params:i}}return{}}setDefaultHandler(e,t=L){this._defaultHandlerMap.set(t,E(e))}setCatchHandler(e){this._catchHandler=E(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new u("unregister-route-but-not-found-with-method",{method:e.method});let t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new u("unregister-route-route-not-registered")}};var $,V=()=>($||($=new C,$.addFetchListener(),$.addCacheListener()),$);function J(r,e,t){let o;if(typeof r=="string"){let a=new URL(r,location.href),n=({url:i})=>i.href===a.href;o=new h(n,e,t)}else if(r instanceof RegExp)o=new D(r,e,t);else if(typeof r=="function")o=new h(r,e,t);else if(r instanceof h)o=r;else throw new u("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return V().registerRoute(o),o}try{self["workbox:strategies:7.0.0"]&&_()}catch{}function P(r){return typeof r=="string"?new Request(r):r}var O=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new x,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let o of this._plugins)this._pluginStateMap.set(o,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:t}=this,o=P(e);if(o.mode==="navigate"&&t instanceof FetchEvent&&t.preloadResponse){let n=await t.preloadResponse;if(n)return n}let s=this.hasCallback("fetchDidFail")?o.clone():null;try{for(let n of this.iterateCallbacks("requestWillFetch"))o=await n({request:o.clone(),event:t})}catch(n){if(n instanceof Error)throw new u("plugin-error-request-will-fetch",{thrownErrorMessage:n.message})}let a=o.clone();try{let n;n=await fetch(o,o.mode==="navigate"?void 0:this._strategy.fetchOptions);for(let i of this.iterateCallbacks("fetchDidSucceed"))n=await i({event:t,request:a,response:n});return n}catch(n){throw s&&await this.runCallbacks("fetchDidFail",{error:n,event:t,originalRequest:s.clone(),request:a.clone()}),n}}async fetchAndCachePut(e){let t=await this.fetch(e),o=t.clone();return this.waitUntil(this.cachePut(e,o)),t}async cacheMatch(e){let t=P(e),o,{cacheName:s,matchOptions:a}=this._strategy,n=await this.getCacheKey(t,"read"),i=Object.assign(Object.assign({},a),{cacheName:s});o=await caches.match(n,i);for(let c of this.iterateCallbacks("cachedResponseWillBeUsed"))o=await c({cacheName:s,matchOptions:a,cachedResponse:o,request:n,event:this.event})||void 0;return o}async cachePut(e,t){let o=P(e);await b(0);let s=await this.getCacheKey(o,"write");if(!t)throw new u("cache-put-with-no-response",{url:U(s.url)});let a=await this._ensureResponseSafeToCache(t);if(!a)return!1;let{cacheName:n,matchOptions:i}=this._strategy,c=await self.caches.open(n),l=this.hasCallback("cacheDidUpdate"),g=l?await F(c,s.clone(),["__WB_REVISION__"],i):null;try{await c.put(s,l?a.clone():a)}catch(m){if(m instanceof Error)throw m.name==="QuotaExceededError"&&await I(),m}for(let m of this.iterateCallbacks("cacheDidUpdate"))await m({cacheName:n,oldResponse:g,newResponse:a.clone(),request:s,event:this.event});return!0}async getCacheKey(e,t){let o=`${e.url} | ${t}`;if(!this._cacheKeys[o]){let s=e;for(let a of this.iterateCallbacks("cacheKeyWillBeUsed"))s=P(await a({mode:t,request:s,event:this.event,params:this.params}));this._cacheKeys[o]=s}return this._cacheKeys[o]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let o of this.iterateCallbacks(e))await o(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]=="function"){let o=this._pluginStateMap.get(t);yield a=>{let n=Object.assign(Object.assign({},a),{state:o});return t[e](n)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){let e;for(;e=this._extendLifetimePromises.shift();)await e}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,o=!1;for(let s of this.iterateCallbacks("cacheWillUpdate"))if(t=await s({request:this.request,response:t,event:this.event})||void 0,o=!0,!t)break;return o||t&&t.status!==200&&(t=void 0),t}};var f=class{constructor(e={}){this.cacheName=w.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,o=typeof e.request=="string"?new Request(e.request):e.request,s="params"in e?e.params:void 0,a=new O(this,{event:t,request:o,params:s}),n=this._getResponse(a,o,t),i=this._awaitComplete(n,a,o,t);return[n,i]}async _getResponse(e,t,o){await e.runCallbacks("handlerWillStart",{event:o,request:t});let s;try{if(s=await this._handle(t,e),!s||s.type==="error")throw new u("no-response",{url:t.url})}catch(a){if(a instanceof Error){for(let n of e.iterateCallbacks("handlerDidError"))if(s=await n({error:a,event:o,request:t}),s)break}if(!s)throw a}for(let a of e.iterateCallbacks("handlerWillRespond"))s=await a({event:o,request:t,response:s});return s}async _awaitComplete(e,t,o,s){let a,n;try{a=await e}catch{}try{await t.runCallbacks("handlerDidRespond",{event:s,request:o,response:a}),await t.doneWaiting()}catch(i){i instanceof Error&&(n=i)}if(await t.runCallbacks("handlerDidComplete",{event:s,request:o,response:a,error:n}),t.destroy(),n)throw n}};var T=class extends f{async _handle(e,t){let o=[],s=await t.cacheMatch(e),a;if(!s)try{s=await t.fetchAndCachePut(e)}catch(n){n instanceof Error&&(a=n)}if(!s)throw new u("no-response",{url:e.url,error:a});return s}};var Re="my-cache";self.addEventListener("install",()=>{self.skipWaiting()});B();var ke=["/","/unlimited"];J(({url:r})=>ke.includes(r.pathname),new T({cacheName:Re,plugins:[new v({maxAgeSeconds:1*60,maxEntries:8})]}));})(); diff --git a/apps/web/service-worker.ts b/apps/web/service-worker.ts new file mode 100644 index 0000000..ce5827d --- /dev/null +++ b/apps/web/service-worker.ts @@ -0,0 +1,29 @@ +import { clientsClaim } from 'workbox-core'; +import { ExpirationPlugin } from 'workbox-expiration'; +import { registerRoute } from 'workbox-routing'; +import { CacheFirst } from 'workbox-strategies'; + +declare let self: ServiceWorkerGlobalScope; + +const cacheName = 'my-cache'; + +self.addEventListener('install', () => { + self.skipWaiting(); +}); + +clientsClaim(); + +const routes = ['/', '/unlimited']; + +registerRoute( + ({ url }) => routes.includes(url.pathname), + new CacheFirst({ + cacheName, + plugins: [ + new ExpirationPlugin({ + maxAgeSeconds: 1 * 60, + maxEntries: 8, + }), + ], + }) +); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index c7805c2..695c1b6 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -5,7 +5,7 @@ "exclude": ["node_modules"], "compilerOptions": { "target": "ES2020", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": ["dom", "dom.iterable", "esnext", "WebWorker"], "allowJs": true, "skipLibCheck": true, "strict": true, diff --git a/package.json b/package.json index b3346fd..ac5ab52 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "prepare": "husky install", "precommit": "pnpm format && pnpm lint:fix && pnpm test", "graphql:update": "dotenv -e .env.local turbo run graphql:update", - "graphql:codegen": "dotenv -e .env.local turbo run graphql:codegen" + "graphql:codegen": "dotenv -e .env.local turbo run graphql:codegen", + "build:worker": "dotenv -e .env.local turbo run build:worker" }, "devDependencies": { "dotenv-cli": "^7.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d61b84..1398576 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -238,6 +238,9 @@ importers: antd: specifier: ^5.14.2 version: 5.14.2(react-dom@18.2.0)(react@18.2.0) + esbuild: + specifier: ^0.20.2 + version: 0.20.2 eslint: specifier: ^8.52.0 version: 8.57.0 @@ -258,13 +261,25 @@ importers: version: link:../../packages/shared ts-jest: specifier: ^29.0.5 - version: 29.1.1(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3) + version: 29.1.1(@babel/core@7.23.9)(esbuild@0.20.2)(jest@29.7.0)(typescript@5.3.3) ts-node: specifier: ^10.9.1 version: 10.9.2(@types/node@18.19.18)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 + workbox-core: + specifier: ^7.0.0 + version: 7.0.0 + workbox-expiration: + specifier: ^7.0.0 + version: 7.0.0 + workbox-routing: + specifier: ^7.0.0 + version: 7.0.0 + workbox-strategies: + specifier: ^7.0.0 + version: 7.0.0 packages/shared: devDependencies: @@ -1367,6 +1382,213 @@ packages: jsdoc-type-pratt-parser: 4.0.0 dev: true + /@esbuild/aix-ppc64@0.20.2: + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.20.2: + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.20.2: + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.20.2: + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.20.2: + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.20.2: + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.20.2: + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.20.2: + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.20.2: + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.20.2: + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.20.2: + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.20.2: + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.20.2: + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.20.2: + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.20.2: + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.20.2: + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.20.2: + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.20.2: + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.20.2: + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.20.2: + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.20.2: + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.20.2: + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.20.2: + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6393,6 +6615,37 @@ packages: is-symbol: 1.0.4 dev: true + /esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 + dev: true + /escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -8487,6 +8740,10 @@ packages: safer-buffer: 2.1.2 dev: true + /idb@7.1.1: + resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + dev: true + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -13217,6 +13474,41 @@ packages: tslib: 2.6.2 dev: false + /ts-jest@29.1.1(@babel/core@7.23.9)(esbuild@0.20.2)(jest@29.7.0)(typescript@5.3.3): + resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.23.9 + bs-logger: 0.2.6 + esbuild: 0.20.2 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@18.19.18)(ts-node@10.9.2) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.0 + typescript: 5.3.3 + yargs-parser: 21.1.1 + dev: true + /ts-jest@29.1.1(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3): resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -13241,7 +13533,7 @@ packages: '@babel/core': 7.23.9 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@18.19.18)(ts-node@10.9.2) + jest: 29.7.0(@types/node@20.11.20)(ts-node@10.9.2) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -13981,6 +14273,29 @@ packages: dependencies: isexe: 2.0.0 + /workbox-core@7.0.0: + resolution: {integrity: sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==} + dev: true + + /workbox-expiration@7.0.0: + resolution: {integrity: sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ==} + dependencies: + idb: 7.1.1 + workbox-core: 7.0.0 + dev: true + + /workbox-routing@7.0.0: + resolution: {integrity: sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA==} + dependencies: + workbox-core: 7.0.0 + dev: true + + /workbox-strategies@7.0.0: + resolution: {integrity: sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA==} + dependencies: + workbox-core: 7.0.0 + dev: true + /wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} diff --git a/turbo.json b/turbo.json index 16132b7..0852e2c 100644 --- a/turbo.json +++ b/turbo.json @@ -36,6 +36,9 @@ }, "prebuild": { "cache": false + }, + "build:worker": { + "cache": false } } }