Skip to content

Instantly share code, notes, and snippets.

@whaaaley
Last active March 28, 2019 03:31
Show Gist options
  • Save whaaaley/25bfc3051635a126001d099e210d541a to your computer and use it in GitHub Desktop.
Save whaaaley/25bfc3051635a126001d099e210d541a to your computer and use it in GitHub Desktop.

Writing A Browser Reload Node Server 2019

Technology

The End Goal

node server --css "<css build command>" --js "<js build command>"

Creating A Node Server

const http = require('http')

const server = http.createServer((request, response) => {
  // ...
})

server.listen(3000, () => {
  console.log('Running at http://localhost:3000')
})

Handling Requests

const handler = require('serve-handler')

const server = http.createServer((request, response) => {
  return handler(request, response, {
    public: 'public',
    rewrites: [{ source: '**', destination: '/index.html' }]
  })
})

Handling Server Sent Events

const crypto = require('crypto')

const clientMap = new Map()

const sendEvent = (data, response) => {
  response.write('data:' + data + '\n\n')
}

const server = http.createServer((request, response) => {
  response.setHeader('Content-Type', 'text/event-stream')

  const clientID = crypto.randomBytes(6).toString('hex')
  const heartbeat = setInterval(() => sendEvent('', response), 90000)

  clientMap.set(clientID, response)
  sendEvent('connect', response)

  request.on('aborted', () => {
    clearInterval(heartbeat)
    clientMap.delete(clientID)
  })
})

Parsing CLI Arguments

const argv = process.argv
const commands = {}

for (let i = 2; i < argv.length; i += 2) {
  commands[argv[i]] = argv[i + 1]
}

Running CLI Commands

const child_process = require('child_process') // eslint-disable-line

const execute = flag => {
  const process = child_process.exec(commands[flag], { stdio: 'inherit' })

  process.on('close', () => {
    for (let [key] of clientMap) {
      sendEvent('reload', clientMap.get(key))
    }
  })
}

Watching For File Changes

const fs = require('fs')

const watchHandler = (e, filename) => {
  if (filename.endsWith('.scss')) {
    return execute('css')
  }

  if (filename.endsWith('.js')) {
    return execute('js')
  }
}

fs.watch('src', { recursive: true }, watchHandler)
fs.watch('src/shared', { recursive: true }, watchHandler)

Connecting and Reloading The Browser

<script>
  const reloadSource = new EventSource('/reload') // eslint-disable-line

  reloadSource.onmessage = data => {
    if (data === 'connect') {
      return console.log('Automatic Reload: connected')
    }

    if (data === 'reload') {
      window.location.reload()
    }

    console.log('Automatic Reload: heartbeat')
  }
</script>

Putting It All Together

const child_process = require('child_process') // eslint-disable-line
const crypto = require('crypto')
const fs = require('fs')
const handler = require('serve-handler')
const http = require('http')

const argv = process.argv
const commands = {}

for (let i = 2; i < argv.length; i += 2) {
  commands[argv[i]] = argv[i + 1]
}

const clientMap = new Map()

const sendEvent = (data, response) => {
  response.write('data:' + data + '\n\n')
}

const server = http.createServer((request, response) => {
  if (request.url !== '/reload') {
    return handler(request, response, {
      public: 'public',
      rewrites: [{ source: '**', destination: '/index.html' }]
    })
  }

  response.setHeader('Content-Type', 'text/event-stream')

  const clientID = crypto.randomBytes(6).toString('hex')
  const heartbeat = setInterval(() => sendEvent('', response), 90000)

  clientMap.set(clientID, response)
  sendEvent('connect', response)

  request.on('aborted', () => {
    clearInterval(heartbeat)
    clientMap.delete(clientID)
  })
})

const execute = flag => {
  const process = child_process.exec(commands[flag], { stdio: 'inherit' })

  process.on('close', () => {
    for (let [key] of clientMap) {
      sendEvent('reload', clientMap.get(key))
    }
  })
}

const watchHandler = (e, filename) => {
  if (filename.endsWith('.scss')) {
    return execute('css')
  }

  if (filename.endsWith('.js')) {
    return execute('js')
  }
}

fs.watch('src', { recursive: true }, watchHandler)
fs.watch('src/shared', { recursive: true }, watchHandler)

server.listen(3000, () => {
  console.log('\nRunning at http://localhost:3000')
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment