Skip to content

Commit

Permalink
fix: render HTML when all compilers are ready in lazy mode (#424)
Browse files Browse the repository at this point in the history
* Render HTML when all compilers are ready

* Rename argument

* Document api.compilers
  • Loading branch information
saltysugar authored and egoist committed Sep 9, 2019
1 parent cf85e81 commit 12ce2a5
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 22 deletions.
54 changes: 54 additions & 0 deletions packages/saber/lib/Compiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const { EventEmitter } = require('events')

module.exports = class Compiler extends EventEmitter {
constructor(type, api) {
super()
this.type = type
this.api = api
this.status = 'waiting'
}

injectToWebpack(config) {
const ID = `compiler-${this.type}`
const context = this
config.plugin(ID).use(
class {
apply(compiler) {
compiler.hooks.watchRun.tap(ID, () => {
context.status = 'building'
context.emit('status-changed', {
status: context.status,
allCompilers: {
ready: false,
hasError: false
}
})
})
compiler.hooks.done.tap(ID, stats => {
if (stats.hasErrors()) {
context.status = 'error'
} else {
context.status = 'success'
}

const allCompilers = { ready: true, hasError: false }
Object.values(context.api.compilers).forEach(({ status }) => {
if (status !== 'success' && status !== 'error') {
allCompilers.ready = false
}

if (status === 'error') {
allCompilers.hasError = true
}
})

context.emit('status-changed', {
status: context.status,
allCompilers
})
})
}
}
)
}
}
5 changes: 5 additions & 0 deletions packages/saber/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const Transformers = require('./Transformers')
const configLoader = require('./utils/configLoader')
const resolvePackage = require('./utils/resolvePackage')
const builtinPlugins = require('./plugins')
const Compiler = require('./Compiler')

class Saber {
constructor(opts = {}, config = {}) {
Expand Down Expand Up @@ -65,6 +66,10 @@ class Saber {

this.transformers = new Transformers()
this.runtimePolyfills = new Set()
this.compilers = {
server: new Compiler('server', this),
client: new Compiler('client', this)
}

for (const hook of Object.keys(this.hooks)) {
const ignoreNames = ['theme-node-api', 'user-node-api']
Expand Down
4 changes: 4 additions & 0 deletions packages/saber/lib/webpack/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,9 @@ module.exports = (api, { type }) => {
}
])

if (api.compilers[type]) {
api.compilers[type].injectToWebpack(config)
}

return config
}
4 changes: 2 additions & 2 deletions packages/saber/vue-renderer/app/dev-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ export const init = ({ router }) => {

client.subscribe(obj => {
if (obj.action === 'router:push' && obj.id === __SABER_DEV_CLIENT_ID__) {
if (obj.error) {
if (obj.hasError) {
console.error(`You need to refresh the page when the error is fixed!`)
}
if (module.hot.status() === 'idle') {
if (obj.alreadyBuilt) {
router.push(obj.route)
} else {
const handler = status => {
Expand Down
55 changes: 35 additions & 20 deletions packages/saber/vue-renderer/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const path = require('path')
const { EventEmitter } = require('events')
const { fs, slash } = require('saber-utils')
const { log } = require('saber-log')
const { SyncWaterfallHook } = require('tapable')
Expand Down Expand Up @@ -195,7 +194,7 @@ class VueRenderer {
component: function() {
${
this.api.lazy && !this.visitedRoutes.has(page.permalink)
? 'return Promise.resolve({render: function(){}})'
? `return Promise.resolve({render: function(h){return h('div', {}, ['Please refresh..'])}})`
: `
return import(${chunkNameComment}${JSON.stringify(
componentPath
Expand Down Expand Up @@ -385,14 +384,6 @@ class VueRenderer {
log: false
})

const event = new EventEmitter()
clientCompiler.hooks.watchRun.tap('saber-serve', () => {
event.emit('rebuild')
})
clientCompiler.hooks.done.tap('saber-serve', stats => {
event.emit('done', stats.hasErrors())
})

const serverConfig = this.api.getWebpackConfig({ type: 'server' })
const serverCompiler = webpack(serverConfig)
const mfs = new webpack.MemoryOutputFileSystem()
Expand All @@ -401,17 +392,33 @@ class VueRenderer {
let serverBundle
let clientManifest

serverCompiler.hooks.done.tap('init-renderer', stats => {
if (!stats.hasErrors()) {
const onceAllCompilersAreReady = handler => {
const listener = ({ allCompilers }) => {
if (allCompilers.ready) {
Object.values(this.api.compilers).forEach(compiler => {
compiler.off('status-changed', listener)
})
handler(allCompilers)
}
}

Object.values(this.api.compilers).forEach(compiler => {
compiler.on('status-changed', listener)
})
}

this.api.compilers.server.on('status-changed', ({ status }) => {
if (status === 'success') {
serverBundle = readJSON(
this.api.resolveCache('bundle-manifest/server.json'),
mfs.readFileSync.bind(mfs)
)
this.initRenderer({ serverBundle, clientManifest })
}
})
clientCompiler.hooks.done.tap('init-renderer', stats => {
if (!stats.hasErrors()) {

this.api.compilers.client.on('status-changed', ({ status }) => {
if (status === 'success') {
clientManifest = readJSON(
this.api.resolveCache('bundle-manifest/client.json'),
clientCompiler.outputFileSystem.readFileSync.bind(
Expand All @@ -421,30 +428,38 @@ class VueRenderer {
this.initRenderer({ serverBundle, clientManifest })
}
})
serverCompiler.watch({}, () => {})

serverCompiler.watch({}, error => {
if (error) {
console.error(error)
}
})

server.get('/_saber/visit-page', async (req, res) => {
let [, pathname, hash] = /^([^#]+)(#.+)?$/.exec(req.query.route) || []
pathname = removeTrailingSlash(pathname)
const fullPath = pathname + (hash || '')

log.info(`Navigating to ${fullPath}`)
res.end()

if (this.builtRoutes.has(pathname)) {
log.info(`Navigating to ${fullPath}`)
hotMiddleware.publish({
action: 'router:push',
route: fullPath,
id: req.query.id
id: req.query.id,
alreadyBuilt: true
})
} else {
event.once('done', error => {
log.info(`Compiling ${fullPath}`)
onceAllCompilersAreReady(({ hasError }) => {
log.info(`Navigating to ${fullPath}`)
this.builtRoutes.add(pathname)
hotMiddleware.publish({
action: 'router:push',
route: fullPath,
id: req.query.id,
error
hasError
})
})
this.visitedRoutes.add(pathname)
Expand Down Expand Up @@ -509,7 +524,7 @@ class VueRenderer {
if (this.builtRoutes.has(pathname)) {
render()
} else {
event.once('done', () => {
onceAllCompilersAreReady(() => {
this.builtRoutes.add(pathname)
render()
})
Expand Down
28 changes: 28 additions & 0 deletions website/pages/docs/saber-instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,31 @@ Called after exporting a page to static HTML file.

Called after generating static HTML files (in production mode).

## compilers

Saber by default runs two compilers, `compilers.client` for client bundle and `compilers.server` for server bundle. Saber exposed some useful events and helpers for you to interactive with underlying webpack compilers.

### Events

#### `status-changed`

When webpack finished compiling, this event will be emitted.

```js
api.compilers.client.on('status-changed', ({ status, allCompilers }) => {
// status: 'waiting' | 'building' | 'success' | 'error'

// allCompilers.hasError: boolean

// Whether the status of every compiler is `success` or `error`
// allCompilers.ready: boolean
})
```

### Properties

#### `status`

- Type: `'waiting' | 'building' | 'success' | 'error'`

The compiler status.

0 comments on commit 12ce2a5

Please sign in to comment.