Skip to content

Commit

Permalink
feat: 支持 early-hints 优化技术
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucifier129 committed Jun 12, 2024
1 parent 299b64b commit afc957f
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 11 deletions.
72 changes: 72 additions & 0 deletions controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,75 @@ export default class Controller {
}
}


disableEarlyHints = false

earlyHintsLinks = []

addEarlyHintsLinks(earlyHints) {
this.earlyHintsLinks = this.earlyHintsLinks.concat(earlyHints)
}

flushHeaders(headers) {
if (this.disableEarlyHints || !this.context.res || this.context.res.headersSent) {
return
}

const presetEarlyHints = [
{
uri: `${this.context.publicPath}/${this.context.assets.vendor}`,
rel: 'preload',
as: 'script',
},
{
uri: `${this.context.publicPath}/${this.context.assets.index}`,
rel: 'preload',
as: 'script',
},
]

const preloadEarlyHints = this.SSR !== false ? [] : Object.keys(this.preload).map((name) => {
return {
uri: this.getClientAssetFullPath(this.preload[name]),
rel: 'preload',
as: 'style',
}
})

const earlyHintsLinks = this.earlyHintsLinks.map(item => {
const { uri, ...rest } = item
const url = _.isAbsoluteUrl(uri) ? uri : this.getClientAssetFullPath(uri)

return {
uri: url,
...rest,
}
})

const link = [...presetEarlyHints, ...preloadEarlyHints, ...earlyHintsLinks].map((item) => {
const { uri, ...rest } = item
const result = [`<${uri}>`]

for (const key in rest) {
result.push(`${key}=${rest[key]}`)
}

return result.join('; ')
})

this.context.res?.writeHead(200, 'OK', {
'Content-Type': 'text/html, charset=utf-8',
...headers,
// 禁止 nginx 反向代理层缓存
'X-Accel-Buffering': 'no',
'Link': link
});

this.context.res?.flushHeaders()
// 写入空格,保证响应头被发送
this.context.res?.write(' ')
}

/**
* 封装 fetch, https://github.github.io/fetch
* options.json === false 不自动转换为 json
Expand Down Expand Up @@ -434,6 +503,7 @@ export default class Controller {
SSR = await this.SSR(location, context)
}
if (SSR === false) {
this.flushHeaders()
let View = Loading || EmptyView
return <View />
}
Expand Down Expand Up @@ -566,6 +636,8 @@ export default class Controller {
await Promise.all(promiseList)
}

this.flushHeaders()

this.bindStoreWithView()
return this.render()
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-imvc",
"version": "2.10.17",
"version": "2.10.18",
"description": "An Isomorphic MVC Framework",
"main": "./index",
"bin": {
Expand Down
18 changes: 16 additions & 2 deletions page/createPageRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export default async function createPageRouter(options) {
let layoutView = config.layout
? process.env.NODE_ENV !== 'production'
? getRightPath(path.resolve(config.root, config.routes, config.layout))
: config.layout
: path.resolve(config.root, config.routes, config.layout)
: path.join(__dirname, 'view')

// 纯浏览器端渲染模式,用前置中间件拦截所有请求
Expand Down Expand Up @@ -242,7 +242,21 @@ export default async function createPageRouter(options) {
}

// 支持通过 res.locals.layoutView 动态确定 layoutView
res.render(res.locals.layoutView || layoutView, data)
const finalLayoutPath = res.locals.layoutView
? getRightPath(path.resolve(config.root, config.routes, res.locals.layoutView))
: layoutView

const LayoutView = getModule(require(finalLayoutPath))

const html = ReactDOMServer.renderToStaticMarkup(
<LayoutView {...res.locals} {...data} />
)

if (!res.headersSent) {
res.setHeader('Content-Type', 'text/html')
}

res.end(`<!DOCTYPE html>${html}`)
} catch (error) {
if (process.env.NODE_ENV === 'development') {
console.error(error)
Expand Down
6 changes: 5 additions & 1 deletion project/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export default [
},
{
path: '/preload',
controller: () => import('./preload/controller')
controller: () => import('./preload/Controller')
},
{
path: '/prefetch-header',
controller: () => import('./prefetch-header/Controller')
}
]
44 changes: 44 additions & 0 deletions project/src/prefetch-header/Controller.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Controller from '../../../controller'
import React, { } from 'react'
import { Style } from '../../../component'

const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

export default class extends Controller {
// SSR = false // enable server side rendering
preload = {
css: '/prefetch-header/preload.css',
}
View = View
disableEarlyHints: boolean = false
async componentWillCreate() {
this.addEarlyHintsLinks([
{
uri: '/img/react.png',
rel: 'preload',
as: 'image',
},
{
uri: '/prefetch-header/preload.css',
rel: 'preload',
as: 'style',
},
])

this.flushHeaders()

await delay(500)
}
constructor(location: any, context: any) {
super(location, context)
}
}

function View() {
return (
<div id="style">
<Style name="css" />
<div>static view content</div>
</div>
)
}
3 changes: 3 additions & 0 deletions project/src/prefetch-header/preload.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.style {
height: 50px
}
14 changes: 7 additions & 7 deletions start/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ module.exports = async function start(options) {
res.status(err.status || 500)
res.send(err.stack)
})
} else {
// production error handler
// no stacktraces leaked to user
app.use(function (err, req, res, next) {
res.status(err.status || 500)
res.json(err.message)
})
}

// production error handler
// no stacktraces leaked to user
app.use(function (err, req, res, next) {
res.status(err.status || 500)
res.json(err.message)
})

/**
* Event listener for HTTP server "listening" event.
*/
Expand Down

0 comments on commit afc957f

Please sign in to comment.