|
1 |
| -import type { Buffer } from 'buffer' |
2 |
| -import { resolve } from 'path' |
3 |
| -import { Transform } from 'stream' |
| 1 | +import { promises } from 'fs' |
| 2 | +import { join } from 'path' |
4 | 3 |
|
5 | 4 | import { OnPreBuild } from '@netlify/build'
|
6 |
| -import execa from 'execa' |
7 |
| -import { unlink } from 'fs-extra' |
8 |
| -import mergeStream from 'merge-stream' |
| 5 | +import { build } from '@netlify/esbuild' |
| 6 | +import { watch } from 'chokidar' |
9 | 7 |
|
10 | 8 | import { writeDevEdgeFunction } from './edge'
|
11 | 9 | import { patchNextFiles } from './files'
|
12 | 10 |
|
| 11 | +const fileList = (watched: Record<string, Array<string>>) => |
| 12 | + Object.entries(watched).flatMap(([dir, files]) => files.map((file) => `${dir}/${file}`)) |
| 13 | + |
| 14 | +let watcher |
| 15 | + |
13 | 16 | // The types haven't been updated yet
|
14 | 17 | export const onPreDev: OnPreBuild = async ({ constants, netlifyConfig }) => {
|
15 | 18 | const base = netlifyConfig.build.base ?? process.cwd()
|
16 | 19 |
|
17 | 20 | // Need to patch the files, because build might not have been run
|
18 | 21 | await patchNextFiles(base)
|
19 | 22 |
|
20 |
| - // Clean up old functions |
21 |
| - await unlink(resolve('.netlify', 'middleware.js')).catch(() => { |
22 |
| - // Ignore if it doesn't exist |
23 |
| - }) |
24 | 23 | await writeDevEdgeFunction(constants)
|
25 | 24 |
|
26 |
| - // Eventually we might want to do this via esbuild's API, but for now the CLI works fine |
27 |
| - const common = [`--bundle`, `--outdir=${resolve('.netlify')}`, `--format=esm`, `--target=esnext`, '--watch'] |
28 |
| - const opts = { |
29 |
| - all: true, |
30 |
| - env: { ...process.env, FORCE_COLOR: '1' }, |
31 |
| - } |
32 |
| - // TypeScript |
33 |
| - const tsout = execa(`esbuild`, [...common, resolve(base, 'middleware.ts')], opts).all |
34 |
| - |
35 |
| - // JavaScript |
36 |
| - const jsout = execa(`esbuild`, [...common, resolve(base, 'middleware.js')], opts).all |
| 25 | + watcher = watch(['middleware.js', 'middleware.ts', 'src/middleware.js', 'src/middleware.ts'], { |
| 26 | + persistent: true, |
| 27 | + atomic: true, |
| 28 | + ignoreInitial: true, |
| 29 | + cwd: base, |
| 30 | + }) |
37 | 31 |
|
38 |
| - const filter = new Transform({ |
39 |
| - transform(chunk: Buffer, encoding, callback) { |
40 |
| - const str = chunk.toString(encoding) |
| 32 | + const update = async (initial = false) => { |
| 33 | + try { |
| 34 | + await promises.unlink(join(base, '.netlify', 'middleware.js')) |
| 35 | + } catch {} |
41 | 36 |
|
42 |
| - // Skip if message includes this, because we run even when the files are missing |
43 |
| - if (!str.includes('[ERROR] Could not resolve')) { |
44 |
| - this.push(chunk) |
| 37 | + const watchedFiles = fileList(watcher.getWatched()) |
| 38 | + if (watchedFiles.length === 0) { |
| 39 | + if (!initial) { |
| 40 | + console.log('No middleware found') |
45 | 41 | }
|
46 |
| - callback() |
47 |
| - }, |
48 |
| - }) |
| 42 | + return |
| 43 | + } |
| 44 | + if (watchedFiles.length > 1) { |
| 45 | + console.log('Multiple middleware files found:') |
| 46 | + console.log(watchedFiles.join('\n')) |
| 47 | + console.log('This is not supported.') |
| 48 | + |
| 49 | + return |
| 50 | + } |
| 51 | + console.log(`${initial ? 'Building' : 'Rebuilding'} middleware ${watchedFiles[0]}...`) |
| 52 | + try { |
| 53 | + await build({ |
| 54 | + entryPoints: watchedFiles, |
| 55 | + outfile: '.next/middleware.js', |
| 56 | + bundle: true, |
| 57 | + format: 'esm', |
| 58 | + target: 'esnext', |
| 59 | + }) |
| 60 | + } catch (error) { |
| 61 | + console.error(error) |
| 62 | + return |
| 63 | + } |
49 | 64 |
|
50 |
| - mergeStream(tsout, jsout).pipe(filter).pipe(process.stdout) |
| 65 | + console.log('...done') |
| 66 | + } |
51 | 67 |
|
52 |
| - // Don't return the promise because we don't want to wait for the child process to finish |
| 68 | + watcher |
| 69 | + .on('change', (path) => { |
| 70 | + console.log(`File ${path} has been changed`) |
| 71 | + update() |
| 72 | + }) |
| 73 | + .on('add', (path) => { |
| 74 | + console.log(`File ${path} has been added`) |
| 75 | + update() |
| 76 | + }) |
| 77 | + .on('unlink', (path) => { |
| 78 | + console.log(`File ${path} has been removed`) |
| 79 | + update() |
| 80 | + }) |
| 81 | + .on('ready', () => { |
| 82 | + console.log('Initial scan complete. Ready for changes') |
| 83 | + update(true) |
| 84 | + }) |
| 85 | + |
| 86 | + const promise = new Promise<void>((resolve) => { |
| 87 | + process.on('SIGINT', () => { |
| 88 | + watcher.close() |
| 89 | + resolve() |
| 90 | + }) |
| 91 | + }) |
53 | 92 | }
|
0 commit comments