Skip to content
Draft
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions app/service-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
cleanupOutdatedCaches,
createHandlerBoundToURL,
precacheAndRoute,
} from 'workbox-precaching'
import { clientsClaim } from 'workbox-core'
import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
import { NavigationRoute, registerRoute } from 'workbox-routing'
import { CacheableResponsePlugin } from 'workbox-cacheable-response'
import { ExpirationPlugin } from 'workbox-expiration'

declare let self: ServiceWorkerGlobalScope

const cacheNames = ['npmx-packages', 'npmx-packages-code-and-docs', 'npmx-vercel-proxies'] as const

async function createRuntimeCaches() {
await Promise.all(cacheNames.map(c => caches.open(c)))
}
self.addEventListener('install', event => {
event.waitUntil(createRuntimeCaches())
})

self.skipWaiting()
clientsClaim()

cleanupOutdatedCaches()
precacheAndRoute(self.__WB_MANIFEST, {
urlManipulation: ({ url }) => {
const urls: URL[] = []
// search use query params, we need to include here any page using query params
if (url.pathname.endsWith('_payload.json') || url.pathname.endsWith('/search')) {
const newUrl = new URL(url.href)
newUrl.search = ''
urls.push(newUrl)
}
return urls
},
})

// allow only fallback in dev: we don't want to cache anything
let allowlist: undefined | RegExp[]
if (import.meta.env.DEV) allowlist = [/^\/$/]

// deny api and server page calls
let denylist: undefined | RegExp[]
if (import.meta.env.PROD) {
denylist = [
// search page
/^\/search$/,
/^\/search\?/,
/^\/~/,
/^\/org\//,
// api calls
/^\/api\//,
/^\/oauth\//,
/^\/package\//,
/^\/package-code\//,
/^\/package-docs\//,
/^\/_v\//,
/^\/opensearch\.xml$/,
// exclude sw: if the user navigates to it, fallback to index.html
/^\/service-worker\.js$/,
]

registerRoute(
({ sameOrigin, url }) =>
sameOrigin &&
(url.pathname.startsWith('/package/') ||
url.pathname.startsWith('/org/') ||
url.pathname.startsWith('/~') ||
url.pathname.startsWith('/api/')),
new NetworkFirst({
cacheName: cacheNames[0],
plugins: [
new CacheableResponsePlugin({ statuses: [200] }),
new ExpirationPlugin({ maxEntries: 1000, maxAgeSeconds: 60 }),
],
}),
)
registerRoute(
({ sameOrigin, url }) =>
sameOrigin &&
(url.pathname.startsWith('/package-docs/') || url.pathname.startsWith('/package-code/')),
new StaleWhileRevalidate({
cacheName: cacheNames[1],
plugins: [
new CacheableResponsePlugin({ statuses: [200] }),
new ExpirationPlugin({ maxEntries: 1000, maxAgeSeconds: 365 * 24 * 60 * 60 }),
],
}),
)
registerRoute(
({ sameOrigin, url }) => sameOrigin && url.pathname.startsWith('/_v/'),
new NetworkFirst({
cacheName: cacheNames[1],
plugins: [
new CacheableResponsePlugin({ statuses: [200] }),
new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 60 }),
],
}),
)
}

// to allow work offline
registerRoute(new NavigationRoute(createHandlerBoundToURL('/'), { allowlist, denylist }))
2 changes: 2 additions & 0 deletions knip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const config: KnipConfig = {
'app/middleware/**/*.ts!',
'app/plugins/**/*.ts!',
'app/utils/**/*.ts!',
'app/service-worker.ts!',
'server/**/*.ts!',
'modules/**/*.ts!',
'config/**/*.ts!',
Expand All @@ -34,6 +35,7 @@ const config: KnipConfig = {
'puppeteer',
/** Needs to be explicitly installed, even though it is not imported, to avoid type errors. */
'unplugin-vue-router',
'workbox-build',
'vite-plugin-pwa',
'@vueuse/shared',

Expand Down
48 changes: 44 additions & 4 deletions nuxt.config.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a problem with the icon, it is square, but on other shapes of icons (for example, round ones), the edges are white, this needs to be fixed.

It would also be great to add a monochrome version of the icon to have this icon themed as well.

Screenshot_20260208_222757_Lawnchair.png

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to change the svg icons again, will be fixed once the service worker fixed (search page)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need some specific designs - let me know

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we only need to add some padding at pwa assets generator configuration...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we cannot check PWA icons at devtools, we'll need to use external tools: https://issues.chromium.org/issues/492211941

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/npmx-dev/npmx.dev/blob/77b9103a6b1ec172706a6438c92e7328dabfafce/public/pwa-64x64.png

How to add them and where? The icon itself looks fine. I can make it circle or just "./" with a transparent background, but I don't understand what the problem is...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The screenshot is the PWA icons when installed at android device, android "desktop" icon, we can use https://maskable.app/ to check the paddings.

The icon comes from the preset (will be generated when running the pwa icons script here https://github.com/npmx-dev/npmx.dev/blob/main/package.json#L31)

For example, the paddings can be added via custom preset (here the logic https://github.com/elk-zone/elk/blob/main/scripts/generate-pwa-icons.ts#L55-L80 , I added the manual preset al elk.zone then extracted to pwa assets generarator package) => we can add some padding to the svg or we can add the padding when generating the png files.

Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ export default defineNuxtConfig({
'/_v/view': { proxy: 'https://npmx.dev/_vercel/insights/view' },
'/_v/event': { proxy: 'https://npmx.dev/_vercel/insights/event' },
'/_v/session': { proxy: 'https://npmx.dev/_vercel/insights/session' },
// PWA manifest
'/manifest.webmanifest': {
headers: {
'Content-Type': 'application/manifest+json',
'Cache-Control': 'public, max-age=0, must-revalidate',
Comment thread
shuuji3 marked this conversation as resolved.
},
},
},

experimental: {
Expand Down Expand Up @@ -192,18 +199,51 @@ export default defineNuxtConfig({
},
},

$test: {
pwa: {
disable: true,
},
},

pwa: {
// Disable service worker
disable: true,
pwaAssets: {
config: false,
registerType: 'autoUpdate',
strategies: 'injectManifest',
srcDir: '.',
filename: 'service-worker.ts',
client: {
periodicSyncForUpdates: 3_600, // Check for updates every hour
},
injectManifest: {
minify: process.env.VITE_DEV_PWA !== 'true',
sourcemap: process.env.VITE_DEV_PWA !== 'true',
enableWorkboxModulesLogs: process.env.VITE_DEV_PWA === 'true' ? true : undefined,
globPatterns: ['**/*.{js,json,css,html,txt,svg,png,ico,webp,woff,woff2,ttf,eot,otf,wasm}'],
globIgnores: ['manifest**.webmanifest'],
},
devOptions: {
enabled: process.env.VITE_DEV_PWA === 'true',
type: 'module',
},
manifest: {
id: '/',
scope: '/',
start_url: '/',
name: 'npmx',
short_name: 'npmx',
description: 'A fast, modern browser for the npm registry',
theme_color: '#0a0a0a',
background_color: '#0a0a0a',
orientation: 'portrait',
display: 'standalone',
display_override: ['window-controls-overlay'],
// categories: ['social', 'social networking', 'news'],
handle_links: 'preferred',
launch_handler: {
client_mode: ['navigate-existing', 'auto'],
},
edge_side_panel: {
preferred_width: 480,
},
icons: [
{
src: 'pwa-64x64.png',
Expand Down
13 changes: 11 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"build": "nuxt build",
"build:lunaria": "node ./lunaria/lunaria.ts",
"build:test": "NODE_ENV=test pnpm build",
"build:local:pwa": "VITE_DEV_PWA=true nuxt build",
"dev": "nuxt dev",
"dev:pwa": "VITE_DEV_PWA=true nuxt dev",
"dev:docs": "pnpm run --filter npmx-docs dev --port=3001",
"i18n:check": "node scripts/compare-translations.ts",
"i18n:check:fix": "node scripts/compare-translations.ts --fix",
Expand Down Expand Up @@ -71,7 +73,7 @@
"@unocss/preset-wind4": "66.6.0",
"@upstash/redis": "1.36.1",
"@vite-pwa/assets-generator": "1.0.2",
"@vite-pwa/nuxt": "1.1.0",
"@vite-pwa/nuxt": "1.1.1",
"@voidzero-dev/vite-plus-core": "0.0.0-833c515fa25cef20905a7f9affb156dfa6f151ab",
"@vueuse/core": "14.2.0",
"@vueuse/integrations": "14.2.0",
Expand Down Expand Up @@ -105,7 +107,14 @@
"vite-plugin-pwa": "1.2.0",
"vite-plus": "0.0.0-833c515fa25cef20905a7f9affb156dfa6f151ab",
"vue": "3.5.27",
"vue-data-ui": "3.14.9"
"vue-data-ui": "3.14.9",
"workbox-build": "7.4.0",
"workbox-cacheable-response": "7.4.0",
"workbox-core": "7.4.0",
"workbox-expiration": "7.4.0",
"workbox-precaching": "7.4.0",
"workbox-routing": "7.4.0",
"workbox-strategies": "7.4.0"
},
"devDependencies": {
"@e18e/eslint-plugin": "0.1.4",
Expand Down
31 changes: 26 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/middleware/canonical-redirects.global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const pages = [
'/privacy',
'/search',
'/settings',
'/manifest.webmanifest',
]

const cacheControl = 's-maxage=3600, stale-while-revalidate=36000'
Expand Down
Loading