|
1 | 1 | const { createHash } = require('crypto')
|
2 |
| -const { readdirSync, readFileSync, statSync, writeFileSync } = require('fs') |
3 |
| -const { join } = require('path') |
| 2 | +const { readdirSync, readFileSync, statSync, writeFileSync, existsSync } = require('fs') |
| 3 | +const { join, resolve } = require('path') |
4 | 4 |
|
5 | 5 | const getRevision = filePath => createHash('md5').update(readFileSync(filePath)).digest('hex')
|
6 | 6 | const walkSync = dir => readdirSync(dir, { withFileTypes: true }).flatMap(file =>
|
@@ -58,4 +58,105 @@ function generatePrecacheManifest () {
|
58 | 58 | console.log(`Created precache manifest at ${output}. Cache will include ${manifest.length} URLs with a size of ${formatBytes(size)}.`)
|
59 | 59 | }
|
60 | 60 |
|
61 |
| -module.exports = { generatePrecacheManifest } |
| 61 | +function patchSwAssetsURL(assetUrlsArray) { |
| 62 | + const fullPath = resolve('public/sw.js'); |
| 63 | + let content = readFileSync(fullPath, 'utf-8'); |
| 64 | + const patchedArray = JSON.stringify(assetUrlsArray); |
| 65 | + const regex = /Q=JSON\.parse\(\s*["'](.*?)["']\s*\);/s; |
| 66 | + if (!regex.test(content)) { |
| 67 | + console.warn('⚠️ No match found for precache manifest in sw.js'); |
| 68 | + return; |
| 69 | + } |
| 70 | + |
| 71 | + content = content.replace( |
| 72 | + regex, |
| 73 | + `Q=JSON.parse("${patchedArray}");` |
| 74 | + ); |
| 75 | + writeFileSync(fullPath, content, 'utf-8'); |
| 76 | + console.log('✅ Patched service worker cached assets'); |
| 77 | +} |
| 78 | + |
| 79 | + |
| 80 | +async function addStaticAssetsInServiceWorker () { |
| 81 | + const manifest = [] |
| 82 | + let size = 0 |
| 83 | + const addToManifest = (filePath, url, s) => { |
| 84 | + const revision = getRevision(filePath) |
| 85 | + manifest.push({ url, revision }) |
| 86 | + size += s |
| 87 | + } |
| 88 | + |
| 89 | + const staticDir = join(__dirname, '../public') |
| 90 | + const staticFiles = walkSync(staticDir) |
| 91 | + const staticMatch = f => [/\.(gif|jpe?g|ico|png|ttf|woff|woff2)$/].some(m => m.test(f)) |
| 92 | + staticFiles.filter(staticMatch).forEach(file => { |
| 93 | + const stats = statSync(file) |
| 94 | + // Normalize path separators for URLs |
| 95 | + const url = file.slice(staticDir.length).replace(/\\/g, '/') |
| 96 | + addToManifest(file, url, stats.size) |
| 97 | + }) |
| 98 | + |
| 99 | + const pagesDir = join(__dirname, '../pages') |
| 100 | + const precacheURLs = ['/offline'] |
| 101 | + const pagesFiles = walkSync(pagesDir) |
| 102 | + const fileToUrl = f => f.slice(pagesDir.length).replace(/\.js$/, '') |
| 103 | + const pageMatch = f => precacheURLs.some(url => fileToUrl(f) === url) |
| 104 | + pagesFiles.filter(pageMatch).forEach(file => { |
| 105 | + const stats = statSync(file) |
| 106 | + addToManifest(file, fileToUrl(file), stats.size) |
| 107 | + }) |
| 108 | + |
| 109 | + const nextStaticDir = join(__dirname, '../.next/static') |
| 110 | + // Wait until folder is emitted |
| 111 | + console.log('⏳ Waiting for .next/static to be emitted...') |
| 112 | + let folderRetries = 0 |
| 113 | + while (!existsSync(nextStaticDir) && folderRetries < 10) { |
| 114 | + // eslint-disable-next-line no-await-in-loop |
| 115 | + await new Promise(resolve => setTimeout(resolve, 500)) |
| 116 | + folderRetries++ |
| 117 | + } |
| 118 | + |
| 119 | + if (!existsSync(nextStaticDir)) { |
| 120 | + // Still write the manifest with whatever was collected from public/ and pages/ |
| 121 | + const output = 'sw/precache-manifest.json' |
| 122 | + writeFileSync(output, JSON.stringify(manifest, null, 2)) |
| 123 | + console.warn( |
| 124 | + `⚠️ .next/static not found. Created precache manifest at ${output} with only public/ and pages/ assets.` |
| 125 | + ) |
| 126 | + return |
| 127 | + } |
| 128 | + // Now watch for stabilization (files are emitted asynchronously) |
| 129 | + let lastFileCount = 0 |
| 130 | + let stableCount = 0 |
| 131 | + const maxWaitMs = 60000 |
| 132 | + const startTime = Date.now() |
| 133 | + while (stableCount < 3 && (Date.now() - startTime) < maxWaitMs) { |
| 134 | + const files = walkSync(nextStaticDir) |
| 135 | + if (files.length === lastFileCount) { |
| 136 | + stableCount++ |
| 137 | + } else { |
| 138 | + stableCount = 0 |
| 139 | + lastFileCount = files.length |
| 140 | + } |
| 141 | + await new Promise(resolve => setTimeout(resolve, 500)) |
| 142 | + } |
| 143 | + // finally generate manifest |
| 144 | + const nextStaticFiles = walkSync(nextStaticDir) |
| 145 | + nextStaticFiles.forEach(file => { |
| 146 | + const stats = statSync(file) |
| 147 | + // Normalize path separators for URLs |
| 148 | + const url = `/_next/static${file.slice(nextStaticDir.length).replace(/\\/g, '/')}` |
| 149 | + addToManifest(file, url, stats.size) |
| 150 | + }) |
| 151 | + // write manifest |
| 152 | + const output = 'sw/precache-manifest.json' |
| 153 | + writeFileSync(output, JSON.stringify(manifest, null, 2)) |
| 154 | + console.log( |
| 155 | + `✅ Created precache manifest at ${output}. Cache will include ${manifest.length} URLs with a size of ${formatBytes(size)}.` |
| 156 | + ) |
| 157 | + const data = readFileSync('sw/precache-manifest.json', 'utf-8') |
| 158 | + const manifestArray = JSON.parse(data) |
| 159 | + patchSwAssetsURL(manifestArray) |
| 160 | +} |
| 161 | + |
| 162 | +module.exports = { generatePrecacheManifest, addStaticAssetsInServiceWorker } |
0 commit comments