Skip to content

Commit 1f58588

Browse files
committed
chore: try and make it testable
1 parent 75181f7 commit 1f58588

File tree

3 files changed

+212
-217
lines changed

3 files changed

+212
-217
lines changed
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { promises } from 'fs'
2+
import { join } from 'path'
3+
4+
import { build } from '@netlify/esbuild'
5+
import { FSWatcher, watch } from 'chokidar'
6+
7+
const toFileList = (watched: Record<string, Array<string>>) =>
8+
Object.entries(watched).flatMap(([dir, files]) => files.map((file) => join(dir, file)))
9+
10+
/**
11+
* Compile the middleware file using esbuild
12+
*/
13+
14+
const buildMiddlewareFile = async (entryPoints: Array<string>, base: string) => {
15+
try {
16+
await build({
17+
entryPoints,
18+
outfile: join(base, '.netlify', 'middleware.js'),
19+
bundle: true,
20+
format: 'esm',
21+
target: 'esnext',
22+
absWorkingDir: base,
23+
})
24+
} catch (error) {
25+
console.error(error.toString())
26+
}
27+
}
28+
29+
/**
30+
* We only compile middleware if there's exactly one file. If there's more than one, we log a warning and don't compile.
31+
*/
32+
const shouldFilesBeCompiled = (watchedFiles: Array<string>, isFirstRun: boolean) => {
33+
if (watchedFiles.length === 0) {
34+
if (!isFirstRun) {
35+
// Only log on subsequent builds, because having it on first build makes it seem like a warning, when it's a normal state
36+
console.log('No middleware found')
37+
}
38+
return false
39+
}
40+
if (watchedFiles.length > 1) {
41+
console.log('Multiple middleware files found:')
42+
console.log(watchedFiles.join('\n'))
43+
console.log('This is not supported.')
44+
return false
45+
}
46+
return true
47+
}
48+
49+
const updateWatchedFiles = async (watcher: FSWatcher, base: string, isFirstRun = false) => {
50+
try {
51+
// Start by deleting the old file. If we error out, we don't want to leave the old file around
52+
await promises.unlink(join(base, '.netlify', 'middleware.js'))
53+
} catch {
54+
// Ignore, because it's fine if it didn't exist
55+
}
56+
// The list of watched files is an object with the directory as the key and an array of files as the value.
57+
// We need to flatten this into a list of files
58+
const watchedFiles = toFileList(watcher.getWatched())
59+
if (!shouldFilesBeCompiled(watchedFiles, isFirstRun)) {
60+
watcher.emit('build')
61+
return
62+
}
63+
console.log(`${isFirstRun ? 'Building' : 'Rebuilding'} middleware ${watchedFiles[0]}...`)
64+
await buildMiddlewareFile(watchedFiles, base)
65+
console.log('...done')
66+
watcher.emit('build')
67+
}
68+
69+
export const startWatching = (base: string) => {
70+
const watcher = watch(['middleware.js', 'middleware.ts', 'src/middleware.js', 'src/middleware.ts'], {
71+
// Try and ensure renames just emit one event
72+
atomic: true,
73+
// Don't emit for every watched file, just once after the scan is done
74+
ignoreInitial: true,
75+
cwd: base,
76+
})
77+
78+
watcher
79+
.on('change', (path) => {
80+
console.log(`File ${path} has been changed`)
81+
updateWatchedFiles(watcher, base)
82+
})
83+
.on('add', (path) => {
84+
console.log(`File ${path} has been added`)
85+
updateWatchedFiles(watcher, base)
86+
})
87+
.on('unlink', (path) => {
88+
console.log(`File ${path} has been removed`)
89+
updateWatchedFiles(watcher, base)
90+
})
91+
92+
return {
93+
watcher,
94+
isReady: new Promise<void>((resolve) => {
95+
watcher.on('ready', async () => {
96+
console.log('Initial scan complete. Ready for changes')
97+
// This only happens on the first scan
98+
await updateWatchedFiles(watcher, base, true)
99+
console.log('Ready')
100+
resolve()
101+
})
102+
}),
103+
nextBuild: () =>
104+
new Promise<void>((resolve) => {
105+
watcher.once('build', resolve)
106+
}),
107+
}
108+
}
+8-107
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,13 @@
1-
import { promises } from 'fs'
2-
import { join } from 'path'
1+
import { resolve } from 'path'
32

4-
import { build } from '@netlify/esbuild'
5-
import { FSWatcher, watch } from 'chokidar'
3+
import { startWatching } from './compiler'
64

7-
const toFileList = (watched: Record<string, Array<string>>) =>
8-
Object.entries(watched).flatMap(([dir, files]) => files.map((file) => join(dir, file)))
9-
10-
/**
11-
* Compile the middleware file using esbuild
12-
*/
13-
14-
const buildMiddlewareFile = async (entryPoints: Array<string>, base: string) => {
15-
try {
16-
await build({
17-
entryPoints,
18-
outfile: join(base, '.netlify', 'middleware.js'),
19-
bundle: true,
20-
format: 'esm',
21-
target: 'esnext',
22-
})
23-
} catch (error) {
24-
console.error(error)
25-
}
26-
}
27-
28-
/**
29-
* We only compile middleware if there's exactly one file. If there's more than one, we log a warning and don't compile.
30-
*/
31-
const shouldFilesBeCompiled = (watchedFiles: Array<string>, isFirstRun: boolean) => {
32-
if (watchedFiles.length === 0) {
33-
if (!isFirstRun) {
34-
// Only log on subsequent builds, because having it on first build makes it seem like a warning, when it's a normal state
35-
console.log('No middleware found')
36-
}
37-
return false
38-
}
39-
if (watchedFiles.length > 1) {
40-
console.log('Multiple middleware files found:')
41-
console.log(watchedFiles.join('\n'))
42-
console.log('This is not supported.')
43-
return false
44-
}
45-
return true
46-
}
47-
48-
const updateWatchedFiles = async (watcher: FSWatcher, base: string, isFirstRun = false) => {
49-
try {
50-
// Start by deleting the old file. If we error out, we don't want to leave the old file around
51-
await promises.unlink(join(base, '.netlify', 'middleware.js'))
52-
} catch {
53-
// Ignore, because it's fine if it didn't exist
54-
}
55-
// The list of watched files is an object with the directory as the key and an array of files as the value.
56-
// We need to flatten this into a list of files
57-
const watchedFiles = toFileList(watcher.getWatched())
58-
59-
if (!shouldFilesBeCompiled(watchedFiles, isFirstRun)) {
60-
return
61-
}
62-
63-
console.log(`${isFirstRun ? 'Building' : 'Rebuilding'} middleware ${watchedFiles[0]}...`)
64-
await buildMiddlewareFile(watchedFiles, base)
65-
console.log('...done')
66-
}
67-
68-
const start = async (base: string, once = false) => {
69-
const watcher = watch(['middleware.js', 'middleware.ts', 'src/middleware.js', 'src/middleware.ts'], {
70-
// Try and ensure renames just emit one event
71-
atomic: true,
72-
// Don't emit for every watched file, just once after the scan is done
73-
ignoreInitial: true,
74-
cwd: base,
75-
})
76-
77-
try {
78-
watcher
79-
.on('change', (path) => {
80-
console.log(`File ${path} has been changed`)
81-
updateWatchedFiles(watcher, base)
82-
})
83-
.on('add', (path) => {
84-
console.log(`File ${path} has been added`)
85-
updateWatchedFiles(watcher, base)
86-
})
87-
.on('unlink', (path) => {
88-
console.log(`File ${path} has been removed`)
89-
updateWatchedFiles(watcher, base)
90-
})
91-
.on('ready', async () => {
92-
console.log('Initial scan complete. Ready for changes')
93-
// This only happens on the first scan
94-
await updateWatchedFiles(watcher, base, true)
95-
if (once) {
96-
watcher.close()
97-
}
98-
})
99-
100-
await new Promise((resolve) => {
101-
watcher.on('close', resolve)
102-
})
103-
} finally {
104-
await watcher.close()
5+
const run = async () => {
6+
const { isReady, watcher } = startWatching(resolve(process.argv[2]))
7+
await isReady
8+
if (process.argv[3] === '--once') {
9+
watcher.close()
10510
}
10611
}
10712

108-
start(process.argv[2], process.argv[3] === '--once').catch((error) => {
109-
console.error(error)
110-
// eslint-disable-next-line n/no-process-exit
111-
process.exit(1)
112-
})
13+
run()

0 commit comments

Comments
 (0)