1
1
/* eslint-disable no-console */
2
2
import { execSync } from 'node:child_process'
3
- import fs from 'node:fs'
3
+ import fs from 'node:fs/promises '
4
4
import path from 'node:path'
5
5
import esbuild from 'esbuild'
6
6
7
- const copyPublicFiles = ( ) => {
7
+ const copyPublicFiles = async ( ) => {
8
8
const srcDir = path . resolve ( 'public' )
9
9
const destDir = path . resolve ( 'dist' )
10
10
11
11
// Ensure the destination directory exists
12
- if ( ! fs . existsSync ( destDir ) ) {
13
- fs . mkdirSync ( destDir , { recursive : true } )
14
- }
12
+ await fs . mkdir ( destDir , { recursive : true } )
15
13
16
14
// Read all files in the source directory
17
- const files = fs . readdirSync ( srcDir )
18
-
19
- // Copy each file to the destination directory
20
- files . forEach ( file => {
21
- const srcFile = path . join ( srcDir , file )
22
- const destFile = path . join ( destDir , file )
23
- fs . copyFileSync ( srcFile , destFile )
24
- console . log ( `${ file } copied to dist folder.` )
25
- } )
15
+ const files = await fs . readdir ( srcDir )
16
+
17
+ await Promise . all (
18
+ files . map ( async ( file ) => {
19
+ const srcFile = path . join ( srcDir , file )
20
+ const destFile = path . join ( destDir , file )
21
+ await fs . copyFile ( srcFile , destFile )
22
+ console . log ( `${ file } copied to dist folder.` )
23
+ } )
24
+ )
26
25
}
27
26
28
27
function gitRevision ( ) {
@@ -55,33 +54,32 @@ function gitRevision () {
55
54
/**
56
55
* Inject the dist/index.js and dist/index.css into the dist/index.html file
57
56
*
58
- * @param {esbuild.Metafile } metafile
57
+ * @param {esbuild.Metafile } metafile - esbuild's metafile to extract output file names
58
+ * @param {string } revision - Pre-computed Git revision string
59
59
*/
60
- const injectAssets = ( metafile ) => {
60
+ const injectAssets = async ( metafile , revision ) => {
61
61
const htmlFilePath = path . resolve ( 'dist/index.html' )
62
-
63
- // Extract the output file names from the metafile
64
62
const outputs = metafile . outputs
63
+
64
+ // Get the built JS and CSS filenames
65
65
const scriptFile = Object . keys ( outputs ) . find ( file => file . endsWith ( '.js' ) && file . includes ( 'ipfs-sw-index' ) )
66
66
const cssFile = Object . keys ( outputs ) . find ( file => file . endsWith ( '.css' ) && file . includes ( 'ipfs-sw-index' ) )
67
67
68
+ if ( ! scriptFile || ! cssFile ) {
69
+ console . error ( 'Could not find the required assets in the metafile.' )
70
+ return
71
+ }
72
+
68
73
const scriptTag = `<script type="module" src="${ path . basename ( scriptFile ) } "></script>`
69
74
const linkTag = `<link rel="stylesheet" href="${ path . basename ( cssFile ) } ">`
70
75
71
- // Read the index.html file
72
- let htmlContent = fs . readFileSync ( htmlFilePath , 'utf8' )
73
-
74
- // Inject the link tag for CSS before the closing </head> tag
75
- htmlContent = htmlContent . replace ( '</head>' , ` ${ linkTag } </head>` )
76
+ let htmlContent = await fs . readFile ( htmlFilePath , 'utf8' )
77
+ htmlContent = htmlContent
78
+ . replace ( '</head>' , ` ${ linkTag } </head>` )
79
+ . replace ( '</body>' , ` ${ scriptTag } </body>` )
80
+ . replace ( / < % = G I T _ V E R S I O N % > / g , revision )
76
81
77
- // Inject the script tag for JS before the closing </body> tag
78
- htmlContent = htmlContent . replace ( '</body>' , `${ scriptTag } </body>` )
79
-
80
- // Inject the git revision into the index
81
- htmlContent = htmlContent . replace ( / < % = G I T _ V E R S I O N % > / g, gitRevision ( ) )
82
-
83
- // Write the modified HTML back to the index.html file
84
- fs . writeFileSync ( htmlFilePath , htmlContent )
82
+ await fs . writeFile ( htmlFilePath , htmlContent )
85
83
console . log ( `Injected ${ path . basename ( scriptFile ) } and ${ path . basename ( cssFile ) } into index.html.` )
86
84
}
87
85
@@ -96,93 +94,113 @@ const injectAssets = (metafile) => {
96
94
*
97
95
* @see https://github.com/ipfs/service-worker-gateway/issues/628
98
96
*
99
- * @param {esbuild.Metafile } metafile
97
+ * @param {esbuild.Metafile } metafile - esbuild's metafile to extract output file names
98
+ * @param {string } revision - Pre-computed Git revision string
100
99
*/
101
- const injectFirstHitJs = ( metafile ) => {
100
+ const injectFirstHitJs = async ( metafile , revision ) => {
102
101
const htmlFilePath = path . resolve ( 'dist/ipfs-sw-first-hit.html' )
103
102
104
103
const scriptFile = Object . keys ( metafile . outputs ) . find ( file => file . endsWith ( '.js' ) && file . includes ( 'ipfs-sw-first-hit' ) )
104
+
105
+ if ( ! scriptFile ) {
106
+ console . error ( 'Could not find the ipfs-sw-first-hit JS file in the metafile.' )
107
+ return
108
+ }
109
+
105
110
const scriptTag = `<script src="/${ path . basename ( scriptFile ) } "></script>`
106
- let htmlContent = fs . readFileSync ( htmlFilePath , 'utf8' )
107
- htmlContent = htmlContent . replace ( / < % = G I T _ V E R S I O N % > / g, gitRevision ( ) )
108
- htmlContent = htmlContent . replace ( '</body>' , `${ scriptTag } </body>` )
109
- fs . writeFileSync ( htmlFilePath , htmlContent )
111
+ let htmlContent = await fs . readFile ( htmlFilePath , 'utf8' )
112
+ htmlContent = htmlContent
113
+ . replace ( / < % = G I T _ V E R S I O N % > / g, revision )
114
+ . replace ( '</body>' , `${ scriptTag } </body>` )
115
+
116
+ await fs . writeFile ( htmlFilePath , htmlContent )
110
117
}
111
118
112
119
/**
113
- * We need the service worker to have a consistent name
120
+ * Asynchronously modify the _redirects file by appending entries for all files
121
+ * in the dist folder except for index.html, _redirects, and _kubo_redirects.
122
+ */
123
+ const modifyRedirects = async ( ) => {
124
+ const redirectsFilePath = path . resolve ( 'dist/_redirects' )
125
+ const redirectsContent = await fs . readFile ( redirectsFilePath , 'utf8' )
126
+ const distFiles = await fs . readdir ( path . resolve ( 'dist' ) )
127
+ const files = distFiles . filter ( file => ! [ '_redirects' , 'index.html' , '_kubo_redirects' ] . includes ( file ) )
128
+ const lines = redirectsContent . split ( '\n' )
129
+ files . forEach ( file => {
130
+ lines . push ( `/*/${ file } /${ file } ` )
131
+ } )
132
+ await fs . writeFile ( redirectsFilePath , lines . join ( '\n' ) )
133
+ }
134
+
135
+ /**
136
+ * Plugin to ensure the service worker has a consistent name.
114
137
*
115
138
* @type {esbuild.Plugin }
116
139
*/
117
140
const renameSwPlugin = {
118
141
name : 'rename-sw-plugin' ,
119
142
setup ( build ) {
120
- build . onEnd ( ( ) => {
143
+ build . onEnd ( async ( ) => {
121
144
const outdir = path . resolve ( 'dist' )
122
- const files = fs . readdirSync ( outdir )
123
-
124
- files . forEach ( file => {
125
- if ( file . startsWith ( 'ipfs-sw-sw-' ) ) {
126
- // everything after the dot
127
- const extension = file . slice ( file . indexOf ( '.' ) )
128
- const oldPath = path . join ( outdir , file )
129
- const newPath = path . join ( outdir , `ipfs-sw-sw${ extension } ` )
130
- fs . renameSync ( oldPath , newPath )
131
- console . log ( `Renamed ${ file } to ipfs-sw-sw${ extension } ` )
132
- if ( extension === '.js' ) {
133
- // Replace sourceMappingURL with new path
134
- const contents = fs . readFileSync ( newPath , 'utf8 ' )
135
- const newContents = contents . replace ( / s o u r c e M a p p i n g U R L = . * \. j s \. m a p / , 'sourceMappingURL=ipfs-sw-sw.js.map' )
136
- fs . writeFileSync ( newPath , newContents )
145
+ const files = await fs . readdir ( outdir )
146
+
147
+ await Promise . all (
148
+ files . map ( async file => {
149
+ if ( file . startsWith ( 'ipfs-sw-sw-' ) ) {
150
+ const extension = file . slice ( file . indexOf ( '.' ) )
151
+ const oldPath = path . join ( outdir , file )
152
+ const newPath = path . join ( outdir , `ipfs-sw-sw${ extension } ` )
153
+ await fs . rename ( oldPath , newPath )
154
+ console . log ( `Renamed ${ file } to ipfs-sw-sw${ extension } ` )
155
+ if ( extension === '.js' ) {
156
+ const contents = await fs . readFile ( newPath , 'utf8' )
157
+ const newContents = contents . replace ( / s o u r c e M a p p i n g U R L = . * \. j s \. m a p / , 'sourceMappingURL=ipfs-sw-sw.js.map ' )
158
+ await fs . writeFile ( newPath , newContents )
159
+ }
137
160
}
138
- }
139
- } )
161
+ } )
162
+ )
140
163
} )
141
164
}
142
165
}
143
166
144
167
/**
145
- * For every file in the dist folder except for _redirects, and index.html, we need to make sure that
146
- * redirects from /{splat}/ipfs-sw-{asset}.css and /{splat}/ipfs-sw-{asset}.js are redirected to root /ipfs-sw-{asset}.css and /ipfs-sw-{asset}.js
147
- * respectively.
168
+ * Plugin to modify built files by running post-build tasks.
148
169
*
149
- * This is only needed when hosting the `./dist` folder on cloudflare pages. When hosting with an IPFS gateway, the _redirects file should be replaced with the _kubo_redirects file
150
- */
151
- const modifyRedirects = ( ) => {
152
- const redirectsFilePath = path . resolve ( 'dist/_redirects' )
153
- const redirectsContent = fs . readFileSync ( redirectsFilePath , 'utf8' )
154
- const files = fs . readdirSync ( path . resolve ( 'dist' ) ) . filter ( file => ! [ '_redirects' , 'index.html' , '_kubo_redirects' ] . includes ( file ) )
155
- const lines = redirectsContent . split ( '\n' )
156
- files . forEach ( file => {
157
- lines . push ( `/*/${ file } /${ file } ` )
158
- } )
159
-
160
- fs . writeFileSync ( redirectsFilePath , lines . join ( '\n' ) )
161
- }
162
-
163
- /**
164
170
* @type {esbuild.Plugin }
165
171
*/
166
172
const modifyBuiltFiles = {
167
173
name : 'modify-built-files' ,
168
174
setup ( build ) {
169
175
build . onEnd ( async ( result ) => {
170
- copyPublicFiles ( )
171
- injectAssets ( result . metafile )
172
- injectFirstHitJs ( result . metafile )
173
- modifyRedirects ( )
176
+ // Cache the Git revision once
177
+ const revision = gitRevision ( )
178
+
179
+ // Run copyPublicFiles first to make sure public assets are in place
180
+ await copyPublicFiles ( )
181
+
182
+ // Run injection tasks concurrently since they modify separate files
183
+ await Promise . all ( [
184
+ injectAssets ( result . metafile , revision ) ,
185
+ injectFirstHitJs ( result . metafile , revision )
186
+ ] )
187
+
188
+ // Modify the redirects file last
189
+ await modifyRedirects ( )
174
190
} )
175
191
}
176
192
}
177
193
178
194
/**
179
- * @param {string[] } extensions - The extension of the imported files to exclude. Must match the fill ending path in the import(js) or url(css) statement.
180
- * @returns {esbuild.Plugin }
195
+ * Creates a plugin that excludes files with the given extensions.
196
+ *
197
+ * @param {string[] } extensions - The extension strings to exclude (e.g. ['.eot?#iefix'])
198
+ * @returns {esbuild.Plugin } - An esbuild plugin.
181
199
*/
182
200
const excludeFilesPlugin = ( extensions ) => ( {
183
201
name : 'exclude-files' ,
184
202
setup ( build ) {
185
- build . onResolve ( { filter : / .* / } , async ( args ) => {
203
+ build . onResolve ( { filter : / .* / } , ( args ) => {
186
204
if ( extensions . some ( ext => args . path . endsWith ( ext ) ) ) {
187
205
return { path : args . path , namespace : 'exclude' , external : true }
188
206
}
0 commit comments