Skip to content

fix: use separate watcher script for middleware in dev #1831

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0eb9eb1
fix: watch middleware in src folder
ascorbic Dec 2, 2022
7b1bc45
fix: move watcher into subprocess
ascorbic Dec 5, 2022
d34718d
chore: add comments
ascorbic Dec 5, 2022
b94f132
chore: use join
ascorbic Dec 5, 2022
848faee
Merge branch 'main' into mk/dev-watch
ascorbic Dec 5, 2022
bc0c49d
chore: changes from review
ascorbic Dec 6, 2022
3058c24
feat: add support for running watcher once
ascorbic Dec 6, 2022
6795c91
chore: add onPreDev tests
ascorbic Dec 6, 2022
6cab2cc
chore: add watcher tests
ascorbic Dec 6, 2022
6562611
chore: add more tests
ascorbic Dec 6, 2022
5013442
chore: snap
ascorbic Dec 6, 2022
82f5f71
chore: remove duplicates
ascorbic Dec 6, 2022
c61caf0
chore: run jest serially
ascorbic Dec 6, 2022
514cb1d
chore: skip the watcher test
ascorbic Dec 6, 2022
4dac4a2
chore: re-enable some tests
ascorbic Dec 6, 2022
5d6a551
chore: enable more
ascorbic Dec 6, 2022
bd8bf3d
chore: wait to kill processes
ascorbic Dec 6, 2022
75181f7
chore: re-enable tests
ascorbic Dec 6, 2022
1f58588
chore: try and make it testable
ascorbic Dec 7, 2022
839771b
chore: just one worker
ascorbic Dec 7, 2022
e20dfe3
chore: disable predev test
ascorbic Dec 7, 2022
0f9e28b
chore: so much logging
ascorbic Dec 7, 2022
7bbf3ed
Merge branch 'main' into mk/dev-watch
ericapisani Dec 8, 2022
ff45167
Merge remote-tracking branch 'origin/main' into mk/dev-watch
nickytonline Mar 17, 2023
8b8ed08
chore: added some comments and renamed some functions
nickytonline Mar 17, 2023
559b48d
Update test/index.spec.js
nickytonline Mar 17, 2023
29dad6f
chore: removed some white space
nickytonline Mar 20, 2023
f88a68d
chore: fixed tests
nickytonline Mar 20, 2023
3d26c71
test: skipping some tests for onPreDev middleware as they only run su…
nickytonline Mar 20, 2023
713a740
chore: removed console.logs that were for debugging
nickytonline Mar 20, 2023
ab838e9
chore: added another test to skip for now
nickytonline Mar 20, 2023
c64bb95
Merge branch 'main' into mk/dev-watch
nickytonline Mar 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ on:
schedule:
- cron: '0 0 * * *'

concurrency:
group: ${{ github.head_ref }}
cancel-in-progress: true

jobs:
build:
name: Unit tests
Expand Down
62 changes: 26 additions & 36 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@
"\\.[jt]sx?$": "babel-jest"
},
"verbose": true,
"testTimeout": 60000
"testTimeout": 60000,
"maxWorkers": 1
},
"jest-junit": {
"outputDirectory": "reports",
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@netlify/ipx": "^1.3.2",
"@vercel/node-bridge": "^2.1.0",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
"destr": "^1.1.1",
"execa": "^5.1.1",
"follow-redirects": "^1.15.2",
Expand Down
108 changes: 108 additions & 0 deletions packages/runtime/src/helpers/compiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { promises } from 'fs'
import { join } from 'path'

import { build } from '@netlify/esbuild'
import { FSWatcher, watch } from 'chokidar'

const toFileList = (watched: Record<string, Array<string>>) =>
Object.entries(watched).flatMap(([dir, files]) => files.map((file) => join(dir, file)))

/**
* Compile the middleware file using esbuild
*/

const buildMiddlewareFile = async (entryPoints: Array<string>, base: string) => {
try {
await build({
entryPoints,
outfile: join(base, '.netlify', 'middleware.js'),
bundle: true,
format: 'esm',
target: 'esnext',
absWorkingDir: base,
})
} catch (error) {
console.error(error.toString())
}
}

/**
* We only compile middleware if there's exactly one file. If there's more than one, we log a warning and don't compile.
*/
const shouldFilesBeCompiled = (watchedFiles: Array<string>, isFirstRun: boolean) => {
if (watchedFiles.length === 0) {
if (!isFirstRun) {
// Only log on subsequent builds, because having it on first build makes it seem like a warning, when it's a normal state
console.log('No middleware found')
}
return false
}
if (watchedFiles.length > 1) {
console.log('Multiple middleware files found:')
console.log(watchedFiles.join('\n'))
console.log('This is not supported.')
return false
}
return true
}

const updateWatchedFiles = async (watcher: FSWatcher, base: string, isFirstRun = false) => {
try {
// Start by deleting the old file. If we error out, we don't want to leave the old file around
await promises.unlink(join(base, '.netlify', 'middleware.js'))
} catch {
// Ignore, because it's fine if it didn't exist
}
// The list of watched files is an object with the directory as the key and an array of files as the value.
// We need to flatten this into a list of files
const watchedFiles = toFileList(watcher.getWatched())
if (!shouldFilesBeCompiled(watchedFiles, isFirstRun)) {
watcher.emit('build')
return
}
console.log(`${isFirstRun ? 'Building' : 'Rebuilding'} middleware ${watchedFiles[0]}...`)
await buildMiddlewareFile(watchedFiles, base)
console.log('...done')
watcher.emit('build')
}

export const startWatching = (base: string) => {
const watcher = watch(['middleware.js', 'middleware.ts', 'src/middleware.js', 'src/middleware.ts'], {
// Try and ensure renames just emit one event
atomic: true,
// Don't emit for every watched file, just once after the scan is done
ignoreInitial: true,
cwd: base,
})

watcher
.on('change', (path) => {
console.log(`File ${path} has been changed`)
updateWatchedFiles(watcher, base)
})
.on('add', (path) => {
console.log(`File ${path} has been added`)
updateWatchedFiles(watcher, base)
})
.on('unlink', (path) => {
console.log(`File ${path} has been removed`)
updateWatchedFiles(watcher, base)
})

return {
watcher,
isReady: new Promise<void>((resolve) => {
watcher.on('ready', async () => {
console.log('Initial scan complete. Ready for changes')
// This only happens on the first scan
await updateWatchedFiles(watcher, base, true)
console.log('Ready')
resolve()
})
}),
nextBuild: () =>
new Promise<void>((resolve) => {
watcher.once('build', resolve)
}),
}
}
Loading