Merge pull request #260 from timlrx/feat/hmr

feat: add custom next-remote-watch
This commit is contained in:
Timothy 2021-10-17 16:20:39 +08:00 committed by GitHub
commit a724f7cc98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 355 additions and 1 deletions

View File

@ -0,0 +1,23 @@
import { useEffect } from 'react'
import Router from 'next/router'
/**
* Client-side complement to next-remote-watch
* Re-triggers getStaticProps when watched mdx files change
*
*/
export const ClientReload = () => {
// Exclude socket.io from prod bundle
useEffect(() => {
import('socket.io-client').then((module) => {
const socket = module.io()
socket.on('reload', (data) => {
Router.replace(Router.asPath, undefined, {
scroll: false,
})
})
})
}, [])
return null
}

205
package-lock.json generated
View File

@ -1477,6 +1477,12 @@
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz",
"integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g=="
},
"@socket.io/component-emitter": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz",
"integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==",
"dev": true
},
"@svgr/babel-plugin-add-jsx-attribute": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz",
@ -1646,6 +1652,24 @@
"@types/responselike": "*"
}
},
"@types/component-emitter": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz",
"integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==",
"dev": true
},
"@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
"dev": true
},
"@types/cors": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
"dev": true
},
"@types/debug": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
@ -2195,6 +2219,12 @@
"@babel/helper-define-polyfill-provider": "^0.2.2"
}
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
"dev": true
},
"bail": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.1.tgz",
@ -2205,11 +2235,23 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base64-arraybuffer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz",
"integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==",
"dev": true
},
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"dev": true
},
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@ -2876,6 +2918,16 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dev": true,
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"cosmiconfig": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz",
@ -3371,6 +3423,72 @@
"once": "^1.4.0"
}
},
"engine.io": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.0.0.tgz",
"integrity": "sha512-Ui7yl3JajEIaACg8MOUwWvuuwU7jepZqX3BKs1ho7NQRuP4LhN4XIykXhp8bEy+x/DhA0LBZZXYSCkZDqrwMMg==",
"dev": true,
"requires": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.0.0",
"ws": "~8.2.3"
},
"dependencies": {
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
"dev": true
},
"ws": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
"dev": true
}
}
},
"engine.io-client": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.0.2.tgz",
"integrity": "sha512-cAep9lhZV6Q8jMXx3TNSU5cydMzMed8/O7Tz5uzyqZvpNPtQ3WQXrLYGADxlsuaFmOLN7wZLmT7ImiFhUOku8g==",
"dev": true,
"requires": {
"@socket.io/component-emitter": "~3.0.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.0.0",
"has-cors": "1.1.0",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"ws": "~8.2.3",
"xmlhttprequest-ssl": "~2.0.0",
"yeast": "0.1.2"
},
"dependencies": {
"ws": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
"dev": true
}
}
},
"engine.io-parser": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.1.tgz",
"integrity": "sha512-j4p3WwJrG2k92VISM0op7wiq60vO92MlF3CRGxhKHy9ywG1/Dkc72g0dXeDQ+//hrcDn8gqQzoEkdO9FN0d9AA==",
"dev": true,
"requires": {
"base64-arraybuffer": "~1.0.1"
}
},
"enquirer": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
@ -4691,6 +4809,12 @@
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
"integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA=="
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=",
"dev": true
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@ -7550,6 +7674,18 @@
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
},
"parseqs": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==",
"dev": true
},
"parseuri": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==",
"dev": true
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -8839,6 +8975,63 @@
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg=="
},
"socket.io": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.3.1.tgz",
"integrity": "sha512-HC5w5Olv2XZ0XJ4gOLGzzHEuOCfj3G0SmoW3jLHYYh34EVsIr3EkW9h6kgfW+K3TFEcmYy8JcPWe//KUkBp5jA==",
"dev": true,
"requires": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.2",
"engine.io": "~6.0.0",
"socket.io-adapter": "~2.3.2",
"socket.io-parser": "~4.0.4"
}
},
"socket.io-adapter": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.2.tgz",
"integrity": "sha512-PBZpxUPYjmoogY0aoaTmo1643JelsaS1CiAwNjRVdrI0X9Seuc19Y2Wife8k88avW6haG8cznvwbubAZwH4Mtg==",
"dev": true
},
"socket.io-client": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.3.2.tgz",
"integrity": "sha512-2B9LqSunN60yV8F7S84CCEEcgbYNfrn7ejIInZtLZ7ppWtiX8rGZAjvdCvbnC8bqo/9RlCNOUsORLyskxSFP1g==",
"dev": true,
"requires": {
"@socket.io/component-emitter": "~3.0.0",
"backo2": "~1.0.2",
"debug": "~4.3.2",
"engine.io-client": "~6.0.1",
"parseuri": "0.0.6",
"socket.io-parser": "~4.1.1"
},
"dependencies": {
"socket.io-parser": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.1.tgz",
"integrity": "sha512-USQVLSkDWE5nbcY760ExdKaJxCE65kcsG/8k5FDGZVVxpD1pA7hABYXYkCUvxUuYYh/+uQw0N/fvBzfT8o07KA==",
"dev": true,
"requires": {
"@socket.io/component-emitter": "~3.0.0",
"debug": "~4.3.1"
}
}
}
},
"socket.io-parser": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz",
"integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==",
"dev": true,
"requires": {
"@types/component-emitter": "^1.2.10",
"component-emitter": "~1.3.0",
"debug": "~4.3.1"
}
},
"source-map": {
"version": "0.8.0-beta.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz",
@ -10203,6 +10396,12 @@
}
}
},
"xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"dev": true
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@ -10218,6 +10417,12 @@
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
},
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=",
"dev": true
},
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -3,7 +3,7 @@
"version": "1.1.2",
"private": true,
"scripts": {
"start": "next-remote-watch ./data",
"start": "node ./scripts/next-remote-watch.js ./data",
"dev": "next dev",
"build": "next build && node ./scripts/generate-sitemap",
"serve": "next start",
@ -56,6 +56,8 @@
"inquirer": "^8.1.1",
"lint-staged": "^11.0.0",
"next-remote-watch": "^1.0.0",
"socket.io": "^4.1.3",
"socket.io-client": "^4.1.3",
"prettier": "2.2.1"
},
"lint-staged": {

View File

@ -6,6 +6,9 @@ import Head from 'next/head'
import Analytics from '@/components/analytics'
import LayoutWrapper from '@/components/LayoutWrapper'
import { ClientReload } from '@/components/ClientReload'
const isDevelopment = process.env.NODE_ENV === 'development'
export default function App({ Component, pageProps }) {
return (
@ -13,6 +16,7 @@ export default function App({ Component, pageProps }) {
<Head>
<meta content="width=device-width, initial-scale=1" name="viewport" />
</Head>
{isDevelopment && <ClientReload />}
<Analytics />
<LayoutWrapper>
<Component {...pageProps} />

View File

@ -0,0 +1,120 @@
#!/usr/bin/env node
// Adapated from https://github.com/hashicorp/next-remote-watch
// A copy of next-remote-watch with an additional ws reload emitter.
// The app listens to the event and triggers a client-side router refresh
// see components/ClientReload.js
const chalk = require('chalk')
const chokidar = require('chokidar')
const program = require('commander')
const http = require('http')
const SocketIO = require('socket.io')
const express = require('express')
const spawn = require('child_process').spawn
const next = require('next')
const path = require('path')
const { parse } = require('url')
const pkg = require('../package.json')
const defaultWatchEvent = 'change'
program.storeOptionsAsProperties().version(pkg.version)
program
.option('-r, --root [dir]', 'root directory of your nextjs app')
.option('-s, --script [path]', 'path to the script you want to trigger on a watcher event', false)
.option('-c, --command [cmd]', 'command to execute on a watcher event', false)
.option(
'-e, --event [name]',
`name of event to watch, defaults to ${defaultWatchEvent}`,
defaultWatchEvent
)
.option('-p, --polling [name]', `use polling for the watcher, defaults to false`, false)
.parse(process.argv)
const shell = process.env.SHELL
const app = next({ dev: true, dir: program.root || process.cwd() })
const port = parseInt(process.env.PORT, 10) || 3000
const handle = app.getRequestHandler()
app.prepare().then(() => {
// if directories are provided, watch them for changes and trigger reload
if (program.args.length > 0) {
chokidar
.watch(program.args, { usePolling: Boolean(program.polling) })
.on(program.event, async (filePathContext, eventContext = defaultWatchEvent) => {
// Emit changes via socketio
io.sockets.emit('reload', filePathContext)
app.server.hotReloader.send('building')
if (program.command) {
// Use spawn here so that we can pipe stdio from the command without buffering
spawn(
shell,
[
'-c',
program.command
.replace(/\{event\}/gi, filePathContext)
.replace(/\{path\}/gi, eventContext),
],
{
stdio: 'inherit',
}
)
}
if (program.script) {
try {
// find the path of your --script script
const scriptPath = path.join(process.cwd(), program.script.toString())
// require your --script script
const executeFile = require(scriptPath)
// run the exported function from your --script script
executeFile(filePathContext, eventContext)
} catch (e) {
console.error('Remote script failed')
console.error(e)
return e
}
}
app.server.hotReloader.send('reloadPage')
})
}
// create an express server
const expressApp = express()
const server = http.createServer(expressApp)
// watch files with socketIO
const io = SocketIO(server)
// special handling for mdx reload route
const reloadRoute = express.Router()
reloadRoute.use(express.json())
reloadRoute.all('/', (req, res) => {
// log message if present
const msg = req.body.message
const color = req.body.color
msg && console.log(color ? chalk[color](msg) : msg)
// reload the nextjs app
app.server.hotReloader.send('building')
app.server.hotReloader.send('reloadPage')
res.end('Reload initiated')
})
expressApp.use('/__next_reload', reloadRoute)
// handle all other routes with next.js
expressApp.all('*', (req, res) => handle(req, res, parse(req.url, true)))
// fire it up
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})