refactor: move runtime to src (typescript)

This commit is contained in:
Pooya Parsa 2021-01-18 11:57:38 +01:00
parent 19e6542d27
commit b07a4a5c8d
21 changed files with 491 additions and 1 deletions

View File

@ -106,7 +106,7 @@ export function getsigmaContext (nuxtOptions: NuxtOptions, input: SigmaInput): S
}
},
_internal: {
runtimeDir: resolve(__dirname, '../runtime'),
runtimeDir: resolve(__dirname, './runtime'),
hooks: new Hookable()
}
}

View File

@ -0,0 +1,19 @@
import destr from 'destr'
const runtimeConfig = process.env.RUNTIME_CONFIG
for (const type of ['private', 'public']) {
for (const key in runtimeConfig[type]) {
runtimeConfig[type][key] = destr(process.env[key] || runtimeConfig[type][key])
}
}
const $config = global.$config = {
...runtimeConfig.public,
...runtimeConfig.private
}
export default {
public: runtimeConfig.public,
private: $config
}

View File

@ -0,0 +1,71 @@
import { createRenderer } from 'vue-bundle-renderer'
import devalue from '@nuxt/devalue'
import config from './config'
import { renderToString } from '~renderer'
import server from '~build/dist/server/server'
import clientManifest from '~build/dist/server/client.manifest.json'
import htmlTemplate from '~build/views/document.template.js'
const renderer = createRenderer(server, {
clientManifest,
renderToString
})
const STATIC_ASSETS_BASE = process.env.NUXT_STATIC_BASE + '/' + process.env.NUXT_STATIC_VERSION
const PAYLOAD_JS = '/payload.js'
export async function renderMiddleware (req, res) {
let url = req.url
// payload.json request detection
let isPayloadReq = false
if (url.startsWith(STATIC_ASSETS_BASE) && url.endsWith(PAYLOAD_JS)) {
isPayloadReq = true
url = url.substr(STATIC_ASSETS_BASE.length, url.length - STATIC_ASSETS_BASE.length - PAYLOAD_JS.length)
}
const ssrContext = {
url,
runtimeConfig: {
public: config.public,
private: config.private
},
...(req.context || {})
}
const rendered = await renderer.renderToString(ssrContext)
const payload = ssrContext.nuxt /* nuxt 2 */ || ssrContext.payload /* nuxt 3 */
if (process.env.NUXT_FULL_STATIC) {
payload.staticAssetsBase = STATIC_ASSETS_BASE
}
let data
if (isPayloadReq) {
data = renderPayload(payload, url)
res.setHeader('Content-Type', 'text/javascript;charset=UTF-8')
} else {
data = renderHTML(payload, rendered, ssrContext)
res.setHeader('Content-Type', 'text/html;charset=UTF-8')
}
const error = ssrContext.nuxt && ssrContext.nuxt.error
res.statusCode = error ? error.statusCode : 200
res.end(data, 'utf-8')
}
function renderHTML (payload, rendered, ssrContext) {
const state = `<script>window.__NUXT__=${devalue(payload)}</script>`
const _html = rendered.html
return htmlTemplate({
HTML_ATTRS: '',
HEAD_ATTRS: '',
BODY_ATTRS: '',
HEAD: rendered.renderResourceHints() + rendered.renderStyles() + (ssrContext.styles || ''),
APP: _html + state + rendered.renderScripts()
})
}
function renderPayload (payload, url) {
return `__NUXT_JSONP__("${url}", ${devalue(payload)})`
}

View File

@ -0,0 +1,7 @@
import { $fetch } from 'ohmyfetch'
global.process = global.process || {};
(function () { const o = Date.now(); const t = () => Date.now() - o; global.process.hrtime = global.process.hrtime || ((o) => { const e = Math.floor(0.001 * (Date.now() - t())); const a = 0.001 * t(); let l = Math.floor(a) + e; let n = Math.floor(a % 1 * 1e9); return o && (l -= o[0], n -= o[1], n < 0 && (l--, n += 1e9)), [l, n] }) })()
global.$fetch = $fetch

View File

@ -0,0 +1,12 @@
import _renderToString from 'vue-server-renderer/basic'
export function renderToString (component, context) {
return new Promise((resolve, reject) => {
_renderToString(component, context, (err, result) => {
if (err) {
return reject(err)
}
return resolve(result)
})
})
}

View File

@ -0,0 +1,14 @@
import { createRenderer } from '~vueServerRenderer'
const _renderer = createRenderer({})
export function renderToString (component, context) {
return new Promise((resolve, reject) => {
_renderer.renderToString(component, context, (err, result) => {
if (err) {
return reject(err)
}
return resolve(result)
})
})
}

View File

@ -0,0 +1 @@
export { renderToString } from '@vue/server-renderer'

View File

@ -0,0 +1,19 @@
import '~polyfill'
import { localCall } from '../server'
export default async function handle (context, req) {
const url = '/' + (req.params.url || '')
const { body, status, statusText, headers } = await localCall({
url,
headers: req.headers,
method: req.method,
body: req.body
})
context.res = {
status,
headers,
body: body ? body.toString() : statusText
}
}

View File

@ -0,0 +1,23 @@
import '~polyfill'
import { localCall } from '../server/call'
async function cli () {
const url = process.argv[2] || '/'
const debug = (label, ...args) => console.debug(`> ${label}:`, ...args)
const r = await localCall({ url })
debug('URL', url)
debug('StatusCode', r.status)
debug('StatusMessage', r.statusText)
for (const header of r.headers.entries()) {
debug(header[0], header[1])
}
console.log('\n', r.body.toString())
}
if (require.main === module) {
cli().catch((err) => {
console.error(err)
process.exit(1)
})
}

View File

@ -0,0 +1,46 @@
import '~polyfill'
import { getAssetFromKV } from '@cloudflare/kv-asset-handler'
import { localCall } from '../server'
const PUBLIC_PATH = process.env.PUBLIC_PATH // Default: /_nuxt/
addEventListener('fetch', (event) => {
event.respondWith(handleEvent(event))
})
async function handleEvent (event) {
try {
return await getAssetFromKV(event, { cacheControl: assetsCacheControl })
} catch (_err) {
// Ignore
}
const url = new URL(event.request.url)
const r = await localCall({
event,
url: url.pathname,
host: url.hostname,
protocol: url.protocol,
headers: event.request.headers,
method: event.request.method,
redirect: event.request.redirect,
body: event.request.body
})
return new Response(r.body, {
headers: r.headers,
status: r.status,
statusText: r.statusText
})
}
function assetsCacheControl (request) {
if (request.url.includes(PUBLIC_PATH) /* TODO: Check with routerBase */) {
return {
browserTTL: 31536000,
edgeTTL: 31536000
}
}
return {}
}

View File

@ -0,0 +1,20 @@
import '~polyfill'
import { localCall } from '../server'
export async function handler (event, context) {
const r = await localCall({
event,
url: event.path,
context,
headers: event.headers,
method: event.httpMethod,
query: event.queryStringParameters,
body: event.body // TODO: handle event.isBase64Encoded
})
return {
statusCode: r.status,
headers: r.headers,
body: r.body.toString()
}
}

View File

@ -0,0 +1,14 @@
import '~polyfill'
import { Server } from 'http'
import { parentPort } from 'worker_threads'
import type { AddressInfo } from 'net'
import { handle } from '../server'
const server = new Server(handle)
const netServer = server.listen(0, () => {
parentPort.postMessage({
event: 'listen',
port: (netServer.address() as AddressInfo).port
})
})

View File

@ -0,0 +1,2 @@
import '~polyfill'
export * from '../server'

View File

@ -0,0 +1,18 @@
import '~polyfill'
import { Server } from 'http'
import { handle } from '../server'
const server = new Server(handle)
const port = process.env.NUXT_PORT || process.env.PORT || 3000
const host = process.env.NUXT_HOST || process.env.HOST || 'localhost'
server.listen(port, host, (err) => {
if (err) {
console.error(err)
process.exit(1)
}
console.log(`Listening on http://${host}:${port}`)
})
export default {}

View File

@ -0,0 +1,39 @@
import '~polyfill'
import { localCall } from '../server'
addEventListener('fetch', (event: any) => {
const url = new URL(event.request.url)
if (url.pathname.includes('.') /* is file */) {
return
}
event.respondWith(handleEvent(url, event))
})
async function handleEvent (url, event) {
const r = await localCall({
event,
url: url.pathname,
host: url.hostname,
protocol: url.protocol,
headers: event.request.headers,
method: event.request.method,
redirect: event.request.redirect,
body: event.request.body
})
return new Response(r.body, {
headers: r.headers,
status: r.status,
statusText: r.statusText
})
}
self.addEventListener('install', () => {
self.skipWaiting()
})
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
})

View File

@ -0,0 +1,4 @@
import '~polyfill'
import { handle } from '../server'
export default handle

View File

@ -0,0 +1,67 @@
// import ansiHTML from 'ansi-html'
const cwd = process.cwd()
// TODO: Handle process.env.DEBUG
export function handleError (error, req, res) {
const stack = (error.stack || '')
.split('\n')
.splice(1)
.filter(line => line.includes('at '))
.map((line) => {
const text = line
.replace(cwd + '/', './')
.replace('webpack:/', '')
.replace('.vue', '.js') // TODO: Support sourcemap
.trim()
return {
text,
internal: (line.includes('node_modules') && !line.includes('.cache')) ||
line.includes('internal') ||
line.includes('new Promise')
}
})
console.error(error.message + '\n' + stack.map(l => ' ' + l.text).join(' \n'))
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Nuxt Error</title>
<style>
html, body {
background: white;
color: red;
font-family: monospace;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100%;
}
.stack {
padding-left: 2em;
}
.stack.internal {
color: grey;
}
</style>
</head>
<body>
<div>
<div>${req.method} ${req.url}</div><br>
<h1>${error.toString()}</h1>
<pre>${stack.map(i =>
`<span class="stack${i.internal ? ' internal' : ''}">${i.text}</span>`
).join('\n')
}</pre>
</div>
</body>
</html>
`
res.statusCode = error.statusCode || 500
res.statusMessage = error.statusMessage || 'Invernal Error'
res.end(html)
}

View File

@ -0,0 +1,23 @@
import '../app/config'
import { createApp, useBase } from 'h3'
import { createFetch } from 'ohmyfetch'
import destr from 'destr'
import { createCall, createFetch as createLocalFetch } from '@nuxt/un/runtime/fetch'
import { timingMiddleware } from './timing'
import { handleError } from './error'
import serverMiddleware from '~serverMiddleware'
const app = createApp({
debug: destr(process.env.DEBUG),
onError: handleError
})
app.use(timingMiddleware)
app.use(serverMiddleware)
app.use(() => import('../app/render').then(e => e.renderMiddleware), { lazy: true })
export const stack = app.stack
export const handle = useBase(process.env.ROUTER_BASE, app)
export const localCall = createCall(handle)
export const localFetch = createLocalFetch(localCall, global.fetch)
export const $fetch = global.$fetch = createFetch({ fetch: localFetch })

View File

@ -0,0 +1,63 @@
import { sendError } from 'h3'
import { getAsset, readAsset } from '~static'
const METHODS = ['HEAD', 'GET']
const PUBLIC_PATH = process.env.PUBLIC_PATH // Default: /_nuxt/
const TWO_DAYS = 2 * 60 * 60 * 24
// eslint-disable-next-line
export default async function serveStatic(req, res) {
if (!METHODS.includes(req.method)) {
return
}
let id = req.url.split('?')[0]
if (id.startsWith('/')) {
id = id.substr(1)
}
if (id.endsWith('/')) {
id = id.substr(0, id.length - 1)
}
const asset = getAsset(id) || getAsset(id = id + '/index.html')
if (!asset) {
if (id.startsWith(PUBLIC_PATH)) {
sendError(res, 'Asset not found: ' + id, false, 404)
}
return
}
const ifNotMatch = req.headers['if-none-match'] === asset.etag
if (ifNotMatch) {
res.statusCode = 304
return res.end('Not Modified (etag)')
}
const ifModifiedSinceH = req.headers['if-modified-since']
if (ifModifiedSinceH && asset.mtime) {
if (new Date(ifModifiedSinceH) >= new Date(asset.mtime)) {
res.statusCode = 304
return res.end('Not Modified (mtime)')
}
}
if (asset.type) {
res.setHeader('Content-Type', asset.type)
}
if (asset.etag) {
res.setHeader('ETag', asset.etag)
}
if (asset.mtime) {
res.setHeader('Last-Modified', asset.mtime)
}
if (id.startsWith(PUBLIC_PATH)) {
res.setHeader('Cache-Control', `max-age=${TWO_DAYS}, immutable`)
}
const contents = await readAsset(id)
return res.end(contents)
}

View File

@ -0,0 +1,22 @@
export const globalTiming = global.__timing__ || {
start: () => 0,
end: () => 0,
metrics: []
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
export function timingMiddleware (_req, res, next) {
const start = globalTiming.start()
const _end = res.end
res.end = (data, encoding, callback) => {
const metrics = [['Generate', globalTiming.end(start)], ...globalTiming.metrics]
const serverTiming = metrics.map(m => `-;dur=${m[1]};desc="${encodeURIComponent(m[0])}"`).join(', ')
if (!res.headersSent) {
res.setHeader('Server-Timing', serverTiming)
}
_end.call(res, data, encoding, callback)
}
next()
}

6
packages/nitro/src/runtime/types.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare module NodeJS {
interface Global {
__timing__: any
$config: any
}
}