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 =>
7
7
file . isDirectory ( ) ? walkSync ( join ( dir , file . name ) ) : join ( dir , file . name ) )
8
8
9
- function formatBytes ( bytes , decimals = 2 ) {
9
+ function formatBytes ( bytes , decimals = 2 ) {
10
10
if ( bytes === 0 ) {
11
11
return '0 B'
12
12
}
@@ -20,10 +20,51 @@ function formatBytes (bytes, decimals = 2) {
20
20
return `${ formattedSize } ${ sizes [ i ] } `
21
21
}
22
22
23
- function generatePrecacheManifest ( ) {
23
+ function escapeForSingleQuotedJsString ( str ) {
24
+ return str
25
+ . replace ( / \\ / g, '\\\\' )
26
+ . replace ( / ' / g, "\\'" )
27
+ . replace ( / \r / g, '\\r' )
28
+ . replace ( / \n / g, '\\n' )
29
+ . replace ( / \$ / g, '\\$' ) ;
30
+ }
31
+
32
+
33
+ function generateDummyPrecacheManifest ( ) {
34
+ // A dummy manifest,to be easily referenced in the public/sw.js when patched
35
+ // This will be pathced with custom assets urls after Next builds
36
+ const manifest = [ {
37
+ url : '/dummy/path/test1.js' ,
38
+ revision : 'rev-123'
39
+ } ]
40
+
41
+ const output = 'sw/precache-manifest.json'
42
+ writeFileSync ( output , JSON . stringify ( manifest , null , 2 ) )
43
+
44
+ console . log ( `Created precache manifest at ${ output } .` )
45
+ }
46
+
47
+ function patchSwAssetsURL ( assetUrlsArray ) {
48
+ const fullPath = join ( __dirname , '../public/sw.js' )
49
+ let content = readFileSync ( fullPath , 'utf-8' )
50
+ const patchedArray = JSON . stringify ( assetUrlsArray )
51
+ const escapedPatchedArrayJson = escapeForSingleQuotedJsString ( patchedArray )
52
+ const regex = / J S O N \. p a r s e \( \s * ' \[ \{ " u r l " : " \/ d u m m y \/ p a t h \/ t e s t 1 \. j s " , " r e v i s i o n " : " r e v - 1 2 3 " \} \] ' \s * \) /
53
+ if ( ! regex . test ( content ) ) {
54
+ console . warn ( '⚠️ No match found for precache manifest in sw.js' )
55
+ return
56
+ }
57
+
58
+ content = content . replace ( regex , ( ) => {
59
+ return `JSON.parse('${ escapedPatchedArrayJson } ')`
60
+ } )
61
+ writeFileSync ( fullPath , content , 'utf-8' )
62
+ console . log ( '✅ Patched service worker cached assets' )
63
+ }
64
+
65
+ async function addStaticAssetsInServiceWorker ( ) {
24
66
const manifest = [ ]
25
67
let size = 0
26
-
27
68
const addToManifest = ( filePath , url , s ) => {
28
69
const revision = getRevision ( filePath )
29
70
manifest . push ( { url, revision } )
@@ -35,7 +76,9 @@ function generatePrecacheManifest () {
35
76
const staticMatch = f => [ / \. ( g i f | j p e ? g | i c o | p n g | t t f | w o f f | w o f f 2 ) $ / ] . some ( m => m . test ( f ) )
36
77
staticFiles . filter ( staticMatch ) . forEach ( file => {
37
78
const stats = statSync ( file )
38
- addToManifest ( file , file . slice ( staticDir . length ) , stats . size )
79
+ // Normalize path separators for URLs
80
+ const url = file . slice ( staticDir . length ) . replace ( / \\ / g, '/' )
81
+ addToManifest ( file , url , stats . size )
39
82
} )
40
83
41
84
const pagesDir = join ( __dirname , '../pages' )
@@ -45,17 +88,64 @@ function generatePrecacheManifest () {
45
88
const pageMatch = f => precacheURLs . some ( url => fileToUrl ( f ) === url )
46
89
pagesFiles . filter ( pageMatch ) . forEach ( file => {
47
90
const stats = statSync ( file )
48
- // This is not ideal since dependencies of the pages may have changed
49
- // but we would still generate the same revision ...
50
- // The ideal solution would be to create a revision from the file generated by webpack
51
- // in .next/server/pages but the file may not exist yet when we run this script
52
91
addToManifest ( file , fileToUrl ( file ) , stats . size )
53
92
} )
54
93
94
+ const nextStaticDir = join ( __dirname , '../.next/static' )
95
+ // Wait until folder is emitted
96
+ console . log ( '⏳ Waiting for .next/static to be emitted...' )
97
+ let folderRetries = 0
98
+ while ( ! existsSync ( nextStaticDir ) && folderRetries < 10 ) {
99
+ // eslint-disable-next-line no-await-in-loop
100
+ await new Promise ( resolve => setTimeout ( resolve , 500 ) )
101
+ folderRetries ++
102
+ }
103
+
104
+ if ( ! existsSync ( nextStaticDir ) ) {
105
+ // Still write the manifest with whatever was collected from public/ and pages/
106
+ const output = 'sw/precache-manifest.json'
107
+ writeFileSync ( output , JSON . stringify ( manifest , null , 2 ) )
108
+ console . warn (
109
+ `⚠️ .next/static not found. Created precache manifest at ${ output } with only public/ and pages/ assets.`
110
+
111
+ )
112
+ const data = readFileSync ( 'sw/precache-manifest.json' , 'utf-8' )
113
+ const manifestArray = JSON . parse ( data )
114
+ patchSwAssetsURL ( manifestArray )
115
+ return
116
+ }
117
+ // Now watch for stabilization (files are emitted asynchronously)
118
+ let lastFileCount = 0
119
+ let stableCount = 0
120
+ const maxWaitMs = 60000
121
+ const startTime = Date . now ( )
122
+ while ( stableCount < 3 && ( Date . now ( ) - startTime ) < maxWaitMs ) {
123
+ const files = walkSync ( nextStaticDir )
124
+ if ( files . length === lastFileCount ) {
125
+ stableCount ++
126
+ } else {
127
+ stableCount = 0
128
+ lastFileCount = files . length
129
+ }
130
+ await new Promise ( resolve => setTimeout ( resolve , 500 ) )
131
+ }
132
+ // finally generate manifest
133
+ const nextStaticFiles = walkSync ( nextStaticDir )
134
+ nextStaticFiles . forEach ( file => {
135
+ const stats = statSync ( file )
136
+ // Normalize path separators for URLs
137
+ const url = `/_next/static${ file . slice ( nextStaticDir . length ) . replace ( / \\ / g, '/' ) } `
138
+ addToManifest ( file , url , stats . size )
139
+ } )
140
+ // write manifest
55
141
const output = 'sw/precache-manifest.json'
56
142
writeFileSync ( output , JSON . stringify ( manifest , null , 2 ) )
57
-
58
- console . log ( `Created precache manifest at ${ output } . Cache will include ${ manifest . length } URLs with a size of ${ formatBytes ( size ) } .` )
143
+ console . log (
144
+ `✅ Created precache manifest at ${ output } . Cache will include ${ manifest . length } URLs with a size of ${ formatBytes ( size ) } .`
145
+ )
146
+ const data = readFileSync ( 'sw/precache-manifest.json' , 'utf-8' )
147
+ const manifestArray = JSON . parse ( data )
148
+ patchSwAssetsURL ( manifestArray )
59
149
}
60
150
61
- module . exports = { generatePrecacheManifest }
151
+ module . exports = { generateDummyPrecacheManifest , addStaticAssetsInServiceWorker }
0 commit comments