This commit is contained in:
darenhsu
2022-07-17 13:16:16 +08:00
parent 84759556ff
commit befd344ab0
28070 changed files with 4008428 additions and 1 deletions
+146
View File
@@ -0,0 +1,146 @@
const { resolve } = require('path')
const { readFileSync } = require('fs')
const connect = require('connect')
const serveStatic = require('serve-static')
const getPort = require('get-port-please')
const { json, end, header } = require('node-res')
const { parseStack } = require('./utils/error')
const SSE = require('./sse')
class LoadingUI {
constructor (options) {
this.options = options
this._lastBroadcast = 0
this.states = []
this.allDone = true
this.hasErrors = false
this.serveIndex = this.serveIndex.bind(this)
this._init()
}
_init () {
// Create a connect middleware stack
this.app = connect()
// Create an SSE handler instance
this.sse = new SSE()
// Fix CORS
this.app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
next()
})
// Subscribe to SSR channel
this.app.use('/sse', (req, res) => this.sse.subscribe(req, res))
// Serve state with JSON
this.app.use('/json', (req, res) => json(req, res, this.state))
// Load indexTemplate
const distPath = resolve(__dirname, '../app-dist')
this.indexTemplate = readFileSync(resolve(distPath, 'index.html'), 'utf-8')
// Serve assets
this.app.use('/assets', serveStatic(resolve(distPath, 'assets')))
}
async initAlt ({ url }) {
if (this._server || this.options.baseURLAlt) {
return
}
// Redirect users directly open this port
this.app.use('/', (req, res) => {
res.setHeader('Location', url)
res.statusCode = 307
res.end(url)
})
// Start listening on alternative port
const port = await getPort({ random: true, name: 'nuxt_loading' })
return new Promise((resolve, reject) => {
this._server = this.app.listen(port, (err) => {
if (err) { return reject(err) }
this.options.baseURLAlt = `http://localhost:${port}`
resolve()
})
})
}
close () {
if (this._server) {
return new Promise((resolve, reject) => {
this._server.close((err) => {
if (err) {
return reject(err)
}
resolve()
})
})
}
}
get state () {
return {
error: this.error,
states: this.states,
allDone: this.allDone,
hasErrors: this.hasErrors
}
}
setStates (states) {
this.clearError()
this.states = states
this.allDone = this.states.every(state => state.progress === 0 || state.progress === 100)
this.hasErrors = this.states.some(state => state.hasErrors === true)
this.broadcastState()
}
setError (error) {
this.clearStates(true)
this.error = {
description: error.toString(),
stack: parseStack(error.stack).join('\n')
}
this.broadcastState()
}
clearError () {
this.error = undefined
}
clearStates (hasErrors) {
this.states = []
this.allDone = false
this.hasErrors = !!hasErrors
}
broadcastState () {
const now = new Date()
if ((now - this._lastBroadcast > 500) || this.allDone || this.hasErrors) {
this.sse.broadcast('state', this.state)
this._lastBroadcast = now
}
}
serveIndex (req, res) {
const html = this.indexTemplate
.replace('__STATE__', JSON.stringify(this.state))
.replace('__OPTIONS__', JSON.stringify(this.options))
.replace(/__BASE_URL__/g, this.options.baseURL)
header(res, 'Content-Type', 'text/html')
end(res, html)
}
}
module.exports = LoadingUI
+48
View File
@@ -0,0 +1,48 @@
module.exports = function NuxtLoadingScreen () {
if (!this.options.dev) {
return
}
const defu = require('defu')
const LoadingUI = require('./loading')
const { nuxt } = this
const baseURL = nuxt.options.router.base + '_loading'
const options = this.options.build.loadingScreen = defu(this.options.build.loadingScreen, {
baseURL,
baseURLAlt: baseURL,
altPort: false,
image: undefined,
colors: {}
})
const loading = new LoadingUI(options)
nuxt.options.serverMiddleware.push({
path: '/_loading',
handler: (req, res) => { loading.app(req, res) }
})
if (options.altPort) {
nuxt.hook('listen', async (_, { url }) => {
await loading.initAlt({ url })
})
}
nuxt.hook('close', async () => {
await loading.close()
})
nuxt.hook('bundler:progress', (states) => {
loading.setStates(states)
})
nuxt.hook('cli:buildError', (error) => {
loading.setError(error)
})
nuxt.hook('server:nuxt:renderLoading', (req, res) => {
loading.serveIndex(req, res)
})
}
+40
View File
@@ -0,0 +1,40 @@
const { status, header } = require('node-res')
class SSE {
constructor () {
this.subscriptions = new Set()
this.counter = 0
}
// Subscribe to a channel and set initial headers
subscribe (req, res) {
req.socket.setTimeout(0)
status(res, 200)
header(res, 'Content-Type', 'text/event-stream')
header(res, 'Cache-Control', 'no-cache')
header(res, 'Connection', 'keep-alive')
this.subscriptions.add(res)
res.on('close', () => this.subscriptions.delete(res))
this.broadcast('ready', {})
}
// Publish event and data to all connected clients
broadcast (event, data) {
this.counter++
// Do console.log(this.subscriptions.size) to see, if there are any memory leaks
for (const res of this.subscriptions) {
this.clientBroadcast(res, event, data)
}
}
// Publish event and data to a given response object
clientBroadcast (res, event, data) {
res.write(`id: ${this.counter}\n`)
res.write('event: message\n')
res.write(`data: ${JSON.stringify({ event, ...data })}\n\n`)
}
}
module.exports = SSE
+17
View File
@@ -0,0 +1,17 @@
import { sep } from 'path'
// copied from https://github.com/nuxt/consola/blob/master/src/utils/error.js
export function parseStack (stack) {
const cwd = process.cwd() + sep
const lines = stack
.split('\n')
.splice(1)
.map(l => l
.trim()
.replace('file://', '')
.replace(cwd, '')
)
return lines
}
View File