2020-07-01 17:34:00 +02:00
|
|
|
// Based on https://github.com/webpack-contrib/webpack-hot-middleware/blob/9708d781ae0e46179cf8ea1a94719de4679aaf53/middleware.js
|
|
|
|
// Included License below
|
|
|
|
|
|
|
|
// Copyright JS Foundation and other contributors
|
|
|
|
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
// a copy of this software and associated documentation files (the
|
|
|
|
// 'Software'), to deal in the Software without restriction, including
|
|
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
// the following conditions:
|
|
|
|
|
|
|
|
// The above copyright notice and this permission notice shall be
|
|
|
|
// included in all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
|
|
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
|
|
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
|
|
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
|
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
2021-10-24 23:04:26 +02:00
|
|
|
import type { webpack5 as webpack } from 'next/dist/compiled/webpack/webpack'
|
2021-10-15 09:09:54 +02:00
|
|
|
import type ws from 'ws'
|
2020-07-01 17:34:00 +02:00
|
|
|
|
|
|
|
export class WebpackHotMiddleware {
|
|
|
|
eventStream: EventStream
|
|
|
|
latestStats: webpack.Stats | null
|
2021-04-22 13:08:47 +02:00
|
|
|
clientLatestStats: webpack.Stats | null
|
2020-07-01 17:34:00 +02:00
|
|
|
closed: boolean
|
2021-04-22 13:08:47 +02:00
|
|
|
serverError: boolean
|
2020-07-01 17:34:00 +02:00
|
|
|
|
2021-04-22 13:08:47 +02:00
|
|
|
constructor(compilers: webpack.Compiler[]) {
|
2020-07-01 17:34:00 +02:00
|
|
|
this.eventStream = new EventStream()
|
|
|
|
this.latestStats = null
|
2021-04-22 13:08:47 +02:00
|
|
|
this.clientLatestStats = null
|
|
|
|
this.serverError = false
|
2020-07-01 17:34:00 +02:00
|
|
|
this.closed = false
|
|
|
|
|
2021-04-22 13:08:47 +02:00
|
|
|
compilers[0].hooks.invalid.tap(
|
|
|
|
'webpack-hot-middleware',
|
|
|
|
this.onClientInvalid
|
|
|
|
)
|
|
|
|
compilers[0].hooks.done.tap('webpack-hot-middleware', this.onClientDone)
|
|
|
|
|
|
|
|
compilers[1].hooks.invalid.tap(
|
|
|
|
'webpack-hot-middleware',
|
|
|
|
this.onServerInvalid
|
|
|
|
)
|
|
|
|
compilers[1].hooks.done.tap('webpack-hot-middleware', this.onServerDone)
|
2020-07-01 17:34:00 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 13:08:47 +02:00
|
|
|
onServerInvalid = () => {
|
|
|
|
if (!this.serverError) return
|
|
|
|
|
|
|
|
this.serverError = false
|
|
|
|
|
|
|
|
if (this.clientLatestStats) {
|
|
|
|
this.latestStats = this.clientLatestStats
|
|
|
|
this.publishStats('built', this.latestStats)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
onClientInvalid = () => {
|
|
|
|
if (this.closed || this.serverError) return
|
2020-07-01 17:34:00 +02:00
|
|
|
this.latestStats = null
|
|
|
|
this.eventStream.publish({ action: 'building' })
|
|
|
|
}
|
2021-04-22 13:08:47 +02:00
|
|
|
onServerDone = (statsResult: webpack.Stats) => {
|
2020-07-01 17:34:00 +02:00
|
|
|
if (this.closed) return
|
|
|
|
// Keep hold of latest stats so they can be propagated to new clients
|
2021-04-22 13:08:47 +02:00
|
|
|
// this.latestStats = statsResult
|
|
|
|
// this.publishStats('built', this.latestStats)
|
|
|
|
this.serverError = statsResult.hasErrors()
|
|
|
|
|
|
|
|
if (this.serverError) {
|
|
|
|
this.latestStats = statsResult
|
|
|
|
this.publishStats('built', this.latestStats)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
onClientDone = (statsResult: webpack.Stats) => {
|
|
|
|
this.clientLatestStats = statsResult
|
|
|
|
|
|
|
|
if (this.closed || this.serverError) return
|
|
|
|
// Keep hold of latest stats so they can be propagated to new clients
|
2020-07-01 17:34:00 +02:00
|
|
|
this.latestStats = statsResult
|
|
|
|
this.publishStats('built', this.latestStats)
|
|
|
|
}
|
2021-10-15 09:09:54 +02:00
|
|
|
|
|
|
|
onHMR = (client: ws) => {
|
|
|
|
if (this.closed) return
|
|
|
|
this.eventStream.handler(client)
|
2020-07-01 17:34:00 +02:00
|
|
|
if (this.latestStats) {
|
|
|
|
// Explicitly not passing in `log` fn as we don't want to log again on
|
|
|
|
// the server
|
|
|
|
this.publishStats('sync', this.latestStats)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
publishStats = (action: string, statsResult: webpack.Stats) => {
|
|
|
|
const stats = statsResult.toJson({
|
|
|
|
all: false,
|
|
|
|
hash: true,
|
|
|
|
warnings: true,
|
|
|
|
errors: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
this.eventStream.publish({
|
|
|
|
action: action,
|
|
|
|
hash: stats.hash,
|
|
|
|
warnings: stats.warnings || [],
|
|
|
|
errors: stats.errors || [],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
publish = (payload: any) => {
|
|
|
|
if (this.closed) return
|
|
|
|
this.eventStream.publish(payload)
|
|
|
|
}
|
|
|
|
close = () => {
|
|
|
|
if (this.closed) return
|
|
|
|
// Can't remove compiler plugins, so we just set a flag and noop if closed
|
|
|
|
// https://github.com/webpack/tapable/issues/32#issuecomment-350644466
|
|
|
|
this.closed = true
|
|
|
|
this.eventStream.close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class EventStream {
|
2021-10-15 09:09:54 +02:00
|
|
|
clients: Set<ws>
|
2020-07-01 17:34:00 +02:00
|
|
|
constructor() {
|
|
|
|
this.clients = new Set()
|
|
|
|
}
|
|
|
|
|
2021-10-15 09:09:54 +02:00
|
|
|
everyClient(fn: (client: ws) => void) {
|
2020-07-01 17:34:00 +02:00
|
|
|
for (const client of this.clients) {
|
|
|
|
fn(client)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
close() {
|
|
|
|
this.everyClient((client) => {
|
2021-10-15 09:09:54 +02:00
|
|
|
client.close()
|
2020-07-01 17:34:00 +02:00
|
|
|
})
|
|
|
|
this.clients.clear()
|
|
|
|
}
|
|
|
|
|
2021-10-15 09:09:54 +02:00
|
|
|
handler(client: ws) {
|
|
|
|
this.clients.add(client)
|
|
|
|
client.addEventListener('close', () => {
|
|
|
|
this.clients.delete(client)
|
2020-07-01 17:34:00 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
publish(payload: any) {
|
|
|
|
this.everyClient((client) => {
|
2021-10-15 09:09:54 +02:00
|
|
|
client.send(JSON.stringify(payload))
|
2020-07-01 17:34:00 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|