Skip to content

Commit 3e43e90

Browse files
authored
chore: speed up build.js (#665)
1 parent 359f69b commit 3e43e90

File tree

1 file changed

+99
-81
lines changed

1 file changed

+99
-81
lines changed

build.js

Lines changed: 99 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
/* eslint-disable no-console */
22
import { execSync } from 'node:child_process'
3-
import fs from 'node:fs'
3+
import fs from 'node:fs/promises'
44
import path from 'node:path'
55
import esbuild from 'esbuild'
66

7-
const copyPublicFiles = () => {
7+
const copyPublicFiles = async () => {
88
const srcDir = path.resolve('public')
99
const destDir = path.resolve('dist')
1010

1111
// Ensure the destination directory exists
12-
if (!fs.existsSync(destDir)) {
13-
fs.mkdirSync(destDir, { recursive: true })
14-
}
12+
await fs.mkdir(destDir, { recursive: true })
1513

1614
// 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+
)
2625
}
2726

2827
function gitRevision () {
@@ -55,33 +54,32 @@ function gitRevision () {
5554
/**
5655
* Inject the dist/index.js and dist/index.css into the dist/index.html file
5756
*
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
5959
*/
60-
const injectAssets = (metafile) => {
60+
const injectAssets = async (metafile, revision) => {
6161
const htmlFilePath = path.resolve('dist/index.html')
62-
63-
// Extract the output file names from the metafile
6462
const outputs = metafile.outputs
63+
64+
// Get the built JS and CSS filenames
6565
const scriptFile = Object.keys(outputs).find(file => file.endsWith('.js') && file.includes('ipfs-sw-index'))
6666
const cssFile = Object.keys(outputs).find(file => file.endsWith('.css') && file.includes('ipfs-sw-index'))
6767

68+
if (!scriptFile || !cssFile) {
69+
console.error('Could not find the required assets in the metafile.')
70+
return
71+
}
72+
6873
const scriptTag = `<script type="module" src="${path.basename(scriptFile)}"></script>`
6974
const linkTag = `<link rel="stylesheet" href="${path.basename(cssFile)}">`
7075

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(/<%= GIT_VERSION %>/g, revision)
7681

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(/<%= GIT_VERSION %>/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)
8583
console.log(`Injected ${path.basename(scriptFile)} and ${path.basename(cssFile)} into index.html.`)
8684
}
8785

@@ -96,93 +94,113 @@ const injectAssets = (metafile) => {
9694
*
9795
* @see https://github.com/ipfs/service-worker-gateway/issues/628
9896
*
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
10099
*/
101-
const injectFirstHitJs = (metafile) => {
100+
const injectFirstHitJs = async (metafile, revision) => {
102101
const htmlFilePath = path.resolve('dist/ipfs-sw-first-hit.html')
103102

104103
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+
105110
const scriptTag = `<script src="/${path.basename(scriptFile)}"></script>`
106-
let htmlContent = fs.readFileSync(htmlFilePath, 'utf8')
107-
htmlContent = htmlContent.replace(/<%= GIT_VERSION %>/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(/<%= GIT_VERSION %>/g, revision)
114+
.replace('</body>', `${scriptTag}</body>`)
115+
116+
await fs.writeFile(htmlFilePath, htmlContent)
110117
}
111118

112119
/**
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.
114137
*
115138
* @type {esbuild.Plugin}
116139
*/
117140
const renameSwPlugin = {
118141
name: 'rename-sw-plugin',
119142
setup (build) {
120-
build.onEnd(() => {
143+
build.onEnd(async () => {
121144
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(/sourceMappingURL=.*\.js\.map/, '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(/sourceMappingURL=.*\.js\.map/, 'sourceMappingURL=ipfs-sw-sw.js.map')
158+
await fs.writeFile(newPath, newContents)
159+
}
137160
}
138-
}
139-
})
161+
})
162+
)
140163
})
141164
}
142165
}
143166

144167
/**
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.
148169
*
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-
/**
164170
* @type {esbuild.Plugin}
165171
*/
166172
const modifyBuiltFiles = {
167173
name: 'modify-built-files',
168174
setup (build) {
169175
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()
174190
})
175191
}
176192
}
177193

178194
/**
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.
181199
*/
182200
const excludeFilesPlugin = (extensions) => ({
183201
name: 'exclude-files',
184202
setup (build) {
185-
build.onResolve({ filter: /.*/ }, async (args) => {
203+
build.onResolve({ filter: /.*/ }, (args) => {
186204
if (extensions.some(ext => args.path.endsWith(ext))) {
187205
return { path: args.path, namespace: 'exclude', external: true }
188206
}

0 commit comments

Comments
 (0)