Skip to content

Commit

Permalink
feat(vue-renderer): use async fs (#5186)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 authored and clarkdo committed Mar 8, 2019
1 parent 2cbddeb commit d07aefa
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 54 deletions.
3 changes: 3 additions & 0 deletions packages/server/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ export default class Server {
}

async listen(port, host, socket) {
// Don't start listening before nuxt is ready
await this.nuxt.ready()

// Create a new listener
const listener = new Listener({
port: isNaN(parseInt(port)) ? this.options.server.port : port,
Expand Down
1 change: 1 addition & 0 deletions packages/server/test/server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ describe('server: server', () => {
serverMiddleware: []
},
hook: jest.fn(),
ready: jest.fn(),
callHook: jest.fn(),
resolver: {
requireModule: jest.fn()
Expand Down
93 changes: 45 additions & 48 deletions packages/vue-renderer/src/renderer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'path'
import crypto from 'crypto'
import fs from 'fs'
import fs from 'fs-extra'
import consola from 'consola'
import devalue from '@nuxt/devalue'
import invert from 'lodash/invert'
Expand Down Expand Up @@ -113,7 +113,7 @@ export default class VueRenderer {
async ready() {
// -- Development mode --
if (this.context.options.dev) {
this.context.nuxt.hook('build:resources', mfs => this.loadResources(mfs, true))
this.context.nuxt.hook('build:resources', mfs => this.loadResources(mfs))
return
}

Expand Down Expand Up @@ -141,34 +141,28 @@ export default class VueRenderer {
}
}

loadResources(_fs, isMFS = false) {
async loadResources(_fs) {
const distPath = path.resolve(this.context.options.buildDir, 'dist', 'server')
const updated = []
const { resourceMap } = this

const readResource = (fileName, encoding) => {
const readResource = async (fileName, encoding) => {
try {
const fullPath = path.resolve(distPath, fileName)
if (!_fs.existsSync(fullPath)) {
if (!await _fs.exists(fullPath)) {
return
}
const contents = _fs.readFileSync(fullPath, encoding)
if (isMFS) {
// Cleanup MFS as soon as possible to save memory
_fs.unlinkSync(fullPath)
delete this._assetsMapping
}
const contents = await _fs.readFile(fullPath, encoding)
return contents
} catch (err) {
consola.error('Unable to load resource:', fileName, err)
}
}

for (const resourceName in resourceMap) {
const { fileName, transform, encoding } = resourceMap[resourceName]
for (const resourceName in this.resourceMap) {
const { fileName, transform, encoding } = this.resourceMap[resourceName]

// Load resource
let resource = readResource(fileName, encoding)
let resource = await readResource(fileName, encoding)

// Skip unavailable resources
if (!resource) {
Expand All @@ -178,43 +172,47 @@ export default class VueRenderer {

// Apply transforms
if (typeof transform === 'function') {
resource = transform(resource, {
readResource,
oldValue: this.context.resources[resourceName]
})
resource = await transform(resource, { readResource })
}

// Update resource
this.context.resources[resourceName] = resource
updated.push(resourceName)
}

// Load templates
await this.loadTemplates()

// Detect if any resource updated
if (updated.length > 0) {
// Invalidate assetsMapping cache
delete this._assetsMapping

// Create new renderer
this.createRenderer()
}

// Call resourcesLoaded hook
consola.debug('Resources loaded:', updated.join(','))
return this.context.nuxt.callHook('render:resourcesLoaded', this.context.resources)
}

async loadTemplates() {
// Reload error template
const errorTemplatePath = path.resolve(this.context.options.buildDir, 'views/error.html')
if (fs.existsSync(errorTemplatePath)) {
this.context.resources.errorTemplate = this.parseTemplate(
fs.readFileSync(errorTemplatePath, 'utf8')
)
if (await fs.exists(errorTemplatePath)) {
const errorTemplate = await fs.readFile(errorTemplatePath, 'utf8')
this.context.resources.errorTemplate = this.parseTemplate(errorTemplate)
}

// Reload loading template
const loadingHTMLPath = path.resolve(this.context.options.buildDir, 'loading.html')
if (fs.existsSync(loadingHTMLPath)) {
this.context.resources.loadingHTML = fs.readFileSync(loadingHTMLPath, 'utf8')
this.context.resources.loadingHTML = this.context.resources.loadingHTML
.replace(/\r|\n|[\t\s]{3,}/g, '')
if (await fs.exists(loadingHTMLPath)) {
this.context.resources.loadingHTML = await fs.readFile(loadingHTMLPath, 'utf8')
this.context.resources.loadingHTML = this.context.resources.loadingHTML.replace(/\r|\n|[\t\s]{3,}/g, '')
} else {
this.context.resources.loadingHTML = ''
}

// Call createRenderer if any resource changed
if (updated.length > 0) {
this.createRenderer()
}

// Call resourcesLoaded hook
consola.debug('Resources loaded:', updated.join(','))
return this.context.nuxt.callHook('render:resourcesLoaded', this.context.resources)
}

// TODO: Remove in Nuxt 3
Expand Down Expand Up @@ -470,22 +468,21 @@ export default class VueRenderer {
serverManifest: {
fileName: 'server.manifest.json',
// BundleRenderer needs resolved contents
transform: (src, { readResource, oldValue = { files: {}, maps: {} } }) => {
transform: async (src, { readResource }) => {
const serverManifest = JSON.parse(src)

const resolveAssets = (obj, oldObj) => {
Object.keys(obj).forEach((name) => {
obj[name] = readResource(obj[name])
// Try to reuse deleted MFS files if no new version exists
if (!obj[name]) {
obj[name] = oldObj[name]
}
})
return obj
const readResources = async (obj) => {
const _obj = {}
await Promise.all(Object.keys(obj).map(async (key) => {
_obj[key] = await readResource(obj[key])
}))
return _obj
}

const files = resolveAssets(serverManifest.files, oldValue.files)
const maps = resolveAssets(serverManifest.maps, oldValue.maps)
const [files, maps] = await Promise.all([
readResources(serverManifest.files),
readResources(serverManifest.maps)
])

// Try to parse sourcemaps
for (const map in maps) {
Expand Down
8 changes: 2 additions & 6 deletions packages/webpack/src/builder.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import path from 'path'
import pify from 'pify'
import webpack from 'webpack'
import MFS from 'memory-fs'
import Glob from 'glob'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'
Expand All @@ -12,6 +11,7 @@ import {
sequence,
wrapArray
} from '@nuxt/utils'
import AsyncMFS from './utils/async-mfs'

import { ClientConfig, ModernConfig, ServerConfig } from './config'
import PerfLoader from './utils/perf-loader'
Expand All @@ -29,11 +29,7 @@ export class WebpackBundler {

// Initialize shared MFS for dev
if (this.buildContext.options.dev) {
this.mfs = new MFS()

// TODO: Enable when async FS required
// this.mfs.exists = function (...args) { return Promise.resolve(this.existsSync(...args)) }
// this.mfs.readFile = function (...args) { return Promise.resolve(this.readFileSync(...args)) }
this.mfs = new AsyncMFS()
}
}

Expand Down
24 changes: 24 additions & 0 deletions packages/webpack/src/utils/async-mfs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import MFS from 'memory-fs'
export default class AsyncMFS extends MFS {}

const syncRegex = /Sync$/

const propsToPromisify = Object.getOwnPropertyNames(MFS.prototype).filter(n => syncRegex.test(n))

for (const prop of propsToPromisify) {
const asyncProp = prop.replace(syncRegex, '')
const origAsync = AsyncMFS.prototype[asyncProp]

AsyncMFS.prototype[asyncProp] = function (...args) {
// Callback support for webpack
if (origAsync && args.length && typeof args[args.length - 1] === 'function') {
return origAsync.call(this, ...args)
}

try {
return Promise.resolve(MFS.prototype[prop].call(this, ...args))
} catch (error) {
return Promise.reject(error)
}
}
}
1 change: 1 addition & 0 deletions test/unit/express.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('express', () => {
beforeAll(async () => {
const config = await loadFixture('basic')
nuxt = new Nuxt(config)
await nuxt.ready()

port = await getPort()

Expand Down

0 comments on commit d07aefa

Please sign in to comment.