mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 23:52:06 +00:00
feat(vue-renderer): use async fs (#5186)
This commit is contained in:
parent
2cbddeb142
commit
d07aefa5db
@ -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,
|
||||
|
@ -57,6 +57,7 @@ describe('server: server', () => {
|
||||
serverMiddleware: []
|
||||
},
|
||||
hook: jest.fn(),
|
||||
ready: jest.fn(),
|
||||
callHook: jest.fn(),
|
||||
resolver: {
|
||||
requireModule: jest.fn()
|
||||
|
@ -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'
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
@ -178,10 +172,7 @@ 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
|
||||
@ -189,26 +180,15 @@ export default class VueRenderer {
|
||||
updated.push(resourceName)
|
||||
}
|
||||
|
||||
// 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')
|
||||
)
|
||||
}
|
||||
// Load templates
|
||||
await this.loadTemplates()
|
||||
|
||||
// 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, '')
|
||||
} else {
|
||||
this.context.resources.loadingHTML = ''
|
||||
}
|
||||
|
||||
// Call createRenderer if any resource changed
|
||||
// Detect if any resource updated
|
||||
if (updated.length > 0) {
|
||||
// Invalidate assetsMapping cache
|
||||
delete this._assetsMapping
|
||||
|
||||
// Create new renderer
|
||||
this.createRenderer()
|
||||
}
|
||||
|
||||
@ -217,6 +197,24 @@ export default class VueRenderer {
|
||||
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 (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 (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 = ''
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove in Nuxt 3
|
||||
get noSSR() { /* Backward compatibility */
|
||||
return this.context.options.render.ssr === false
|
||||
@ -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) {
|
||||
|
@ -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'
|
||||
@ -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'
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
24
packages/webpack/src/utils/async-mfs.js
Normal file
24
packages/webpack/src/utils/async-mfs.js
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ describe('express', () => {
|
||||
beforeAll(async () => {
|
||||
const config = await loadFixture('basic')
|
||||
nuxt = new Nuxt(config)
|
||||
await nuxt.ready()
|
||||
|
||||
port = await getPort()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user