feat: client mode modern and support dev/generate (#4264)

This commit is contained in:
Clark Du 2018-11-07 23:37:06 +00:00 committed by GitHub
parent 83f8b1183c
commit 2a36dbad22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 223 additions and 88 deletions

View File

@ -24,17 +24,6 @@ export default {
default: true, default: true,
description: 'Don\'t generate static version for SPA mode (useful for nuxt start)' description: 'Don\'t generate static version for SPA mode (useful for nuxt start)'
}, },
modern: {
alias: 'm',
type: 'boolean',
description: 'Build app for modern browsers',
prepare(cmd, options, argv) {
options.build = options.build || {}
if (argv.modern) {
options.build.modern = !!argv.modern
}
}
},
quiet: { quiet: {
alias: 'q', alias: 'q',
type: 'boolean', type: 'boolean',

View File

@ -11,6 +11,15 @@ export default {
type: 'boolean', type: 'boolean',
default: true, default: true,
description: 'Only generate pages for dynamic routes. Nuxt has to be built once before using this option' description: 'Only generate pages for dynamic routes. Nuxt has to be built once before using this option'
},
modern: {
...common.modern,
description: 'Generate app in modern build (modern mode can be only client)',
prepare(cmd, options, argv) {
if (argv.modern) {
options.modern = 'client'
}
}
} }
}, },
async run(cmd) { async run(cmd) {

View File

@ -9,18 +9,7 @@ export default {
usage: 'start <dir>', usage: 'start <dir>',
options: { options: {
...common, ...common,
...server, ...server
modern: {
alias: 'm',
type: 'boolean',
description: 'Build app for modern browsers',
prepare(cmd, options, argv) {
options.build = options.build || {}
if (argv.modern) {
options.build.modern = !!argv.modern
}
}
}
}, },
async run(cmd) { async run(cmd) {
const argv = cmd.getArgv() const argv = cmd.getArgv()

View File

@ -15,6 +15,16 @@ export default {
default: 'nuxt.config.js', default: 'nuxt.config.js',
description: 'Path to Nuxt.js config file (default: nuxt.config.js)' description: 'Path to Nuxt.js config file (default: nuxt.config.js)'
}, },
modern: {
alias: 'm',
type: 'string',
description: 'Build/Start app for modern browsers, e.g. server, client and false',
prepare(cmd, options, argv) {
if (argv.modern) {
options.modern = argv.modern
}
}
},
version: { version: {
type: 'boolean', type: 'boolean',
description: 'Display the Nuxt version' description: 'Display the Nuxt version'

View File

@ -10,6 +10,7 @@ exports[`cli/command builds help text 1`] = `
--spa, -s Launch in SPA mode --spa, -s Launch in SPA mode
--universal, -u Launch in Universal mode (default) --universal, -u Launch in Universal mode (default)
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js) --config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
--modern, -m Build/Start app for modern browsers, e.g. server, client and false
--version Display the Nuxt version --version Display the Nuxt version
--help, -h Display this message --help, -h Display this message
--port, -p Port number on which to start the application --port, -p Port number on which to start the application
@ -30,6 +31,7 @@ exports[`cli/command loads command from name 1`] = `
--spa, -s Launch in SPA mode --spa, -s Launch in SPA mode
--universal, -u Launch in Universal mode (default) --universal, -u Launch in Universal mode (default)
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js) --config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
--modern, -m Build/Start app for modern browsers, e.g. server, client and false
--version Display the Nuxt version --version Display the Nuxt version
--help, -h Display this message --help, -h Display this message
--port, -p Port number on which to start the application --port, -p Port number on which to start the application

View File

@ -18,7 +18,7 @@ describe('cli/command', () => {
const cmd = new Command({ options: allOptions }) const cmd = new Command({ options: allOptions })
const minimistOptions = cmd._getMinimistOptions() const minimistOptions = cmd._getMinimistOptions()
expect(minimistOptions.string.length).toBe(4) expect(minimistOptions.string.length).toBe(5)
expect(minimistOptions.boolean.length).toBe(4) expect(minimistOptions.boolean.length).toBe(4)
expect(minimistOptions.alias.c).toBe('config-file') expect(minimistOptions.alias.c).toBe('config-file')
expect(minimistOptions.default.c).toBe(common['config-file'].default) expect(minimistOptions.default.c).toBe(common['config-file'].default)

View File

@ -1,2 +1,3 @@
export { default as Hookable } from './hookable' export { default as Hookable } from './hookable'
export { default as ModernBrowsers } from '../data/modern-browsers.json'
export * from './utils' export * from './utils'

View File

@ -12,6 +12,7 @@ export default () => ({
// Mode // Mode
mode: 'universal', mode: 'universal',
modern: false,
// Globals // Globals
globalName: `nuxt`, globalName: `nuxt`,

View File

@ -7,7 +7,6 @@ export default () => ({
extractCSS: false, extractCSS: false,
cssSourceMap: undefined, cssSourceMap: undefined,
ssr: undefined, ssr: undefined,
modern: undefined,
parallel: false, parallel: false,
cache: false, cache: false,
publicPath: '/_nuxt/', publicPath: '/_nuxt/',

View File

@ -216,6 +216,10 @@ export function getNuxtConfig(_options) {
const modePreset = options.modes[options.mode || 'universal'] const modePreset = options.modes[options.mode || 'universal']
defaultsDeep(options, modePreset) defaultsDeep(options, modePreset)
if (options.modern === true) {
options.modern = 'server'
}
// If no server-side rendering, add appear true transition // If no server-side rendering, add appear true transition
/* istanbul ignore if */ /* istanbul ignore if */
if (options.render.ssr === false && options.transition) { if (options.render.ssr === false && options.transition) {

View File

@ -11,6 +11,7 @@
"@nuxt/common": "^2.2.0", "@nuxt/common": "^2.2.0",
"@nuxt/config": "^2.2.0", "@nuxt/config": "^2.2.0",
"@nuxtjs/youch": "^4.2.3", "@nuxtjs/youch": "^4.2.3",
"browserslist-useragent": "^2.0.1",
"chalk": "^2.4.1", "chalk": "^2.4.1",
"compression": "^1.7.3", "compression": "^1.7.3",
"connect": "^3.6.6", "connect": "^3.6.6",

View File

@ -0,0 +1,22 @@
import { ModernBrowsers } from '@nuxt/common'
import { matchesUA } from 'browserslist-useragent'
const modernBrowsers = Object.keys(ModernBrowsers)
.map(browser => `${browser} >= ${ModernBrowsers[browser]}`)
const isModernBrowser = (ua) => {
return Boolean(ua) && matchesUA(ua, {
allowHigherVersions: true,
browsers: modernBrowsers
})
}
export default function (req, res, next) {
const { socket = {}, headers } = req
if (socket.isModernBrowser === undefined) {
const ua = headers && headers['user-agent']
socket.isModernBrowser = isModernBrowser(ua)
}
req.isModernBrowser = socket.isModernBrowser
next()
}

View File

@ -13,6 +13,7 @@ import ServerContext from './context'
import renderAndGetWindow from './jsdom' import renderAndGetWindow from './jsdom'
import nuxtMiddleware from './middleware/nuxt' import nuxtMiddleware from './middleware/nuxt'
import errorMiddleware from './middleware/error' import errorMiddleware from './middleware/error'
import modernMiddleware from './middleware/modern'
export default class Server { export default class Server {
constructor(nuxt) { constructor(nuxt) {
@ -29,8 +30,8 @@ export default class Server {
this.resources = {} this.resources = {}
// Will be available on dev // Will be available on dev
this.webpackDevMiddleware = null this.devMiddleware = null
this.webpackHotMiddleware = null this.hotMiddleware = null
// Create new connect instance // Create new connect instance
this.app = connect() this.app = connect()
@ -70,14 +71,19 @@ export default class Server {
} }
} }
if (this.options.modern === 'server') {
this.useMiddleware(modernMiddleware)
}
// Add webpack middleware support only for development // Add webpack middleware support only for development
if (this.options.dev) { if (this.options.dev) {
this.useMiddleware(async (req, res, next) => { this.useMiddleware(async (req, res, next) => {
if (this.webpackDevMiddleware) { const name = req.isModernBrowser ? 'modern' : 'client'
await this.webpackDevMiddleware(req, res) if (this.devMiddleware[name]) {
await this.devMiddleware[name](req, res)
} }
if (this.webpackHotMiddleware) { if (this.hotMiddleware[name]) {
await this.webpackHotMiddleware(req, res) await this.hotMiddleware[name](req, res)
} }
next() next()
}) })

View File

@ -10,7 +10,6 @@
"dependencies": { "dependencies": {
"@nuxt/common": "^2.2.0", "@nuxt/common": "^2.2.0",
"@nuxtjs/devalue": "^1.1.0", "@nuxtjs/devalue": "^1.1.0",
"browserslist-useragent": "^2.0.1",
"consola": "^2.2.2", "consola": "^2.2.2",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"lru-cache": "^4.1.3", "lru-cache": "^4.1.3",

View File

@ -3,12 +3,11 @@ import crypto from 'crypto'
import fs from 'fs-extra' import fs from 'fs-extra'
import consola from 'consola' import consola from 'consola'
import devalue from '@nuxtjs/devalue' import devalue from '@nuxtjs/devalue'
import invert from 'lodash/invert'
import template from 'lodash/template' import template from 'lodash/template'
import { waitFor } from '@nuxt/common' import { waitFor } from '@nuxt/common'
import { matchesUA } from 'browserslist-useragent'
import { createBundleRenderer } from 'vue-server-renderer' import { createBundleRenderer } from 'vue-server-renderer'
import ModernBrowsers from '../data/modern-browsers.json'
import SPAMetaRenderer from './spa-meta' import SPAMetaRenderer from './spa-meta'
export default class VueRenderer { export default class VueRenderer {
@ -22,9 +21,6 @@ export default class VueRenderer {
spa: null spa: null
} }
this.modernBrowsers = Object.keys(ModernBrowsers)
.map(browser => `${browser} >= ${ModernBrowsers[browser]}`)
// Renderer runtime resources // Renderer runtime resources
Object.assign(this.context.resources, { Object.assign(this.context.resources, {
clientManifest: null, clientManifest: null,
@ -36,6 +32,52 @@ export default class VueRenderer {
}) })
} }
get assetsMapping() {
if (this._assetsMapping) return this._assetsMapping
const legacyAssets = this.context.resources.clientManifest.assetsMapping
const modernAssets = invert(this.context.resources.modernManifest.assetsMapping)
const mapping = {}
for (const legacyJsFile in legacyAssets) {
const chunkNamesHash = legacyAssets[legacyJsFile]
mapping[legacyJsFile] = modernAssets[chunkNamesHash]
}
delete this.context.resources.clientManifest.assetsMapping
delete this.context.resources.modernManifest.assetsMapping
this._assetsMapping = mapping
return mapping
}
renderScripts(context) {
if (this.context.options.modern === 'client') {
const publicPath = this.context.options.build.publicPath
const scriptPattern = /<script[^>]*?src="([^"]*)"[^>]*>[^<]*<\/script>/g
return context.renderScripts().replace(scriptPattern, (scriptTag, jsFile) => {
const legacyJsFile = jsFile.replace(publicPath, '')
const modernJsFile = this.assetsMapping[legacyJsFile]
const moduleTag = scriptTag.replace('<script', '<script type="module"').replace(legacyJsFile, modernJsFile)
const noModuleTag = scriptTag.replace('<script', '<script nomodule')
return noModuleTag + moduleTag
})
}
return context.renderScripts()
}
renderResourceHints(context) {
if (this.context.options.modern === 'client') {
const modulePreloadTags = []
for (const legacyJsFile of context.getPreloadFiles()) {
if (legacyJsFile.asType === 'script') {
const publicPath = this.context.options.build.publicPath
const modernJsFile = this.assetsMapping[legacyJsFile.file]
modulePreloadTags.push(`<link rel="modulepreload" href="${publicPath}${modernJsFile}" as="script">`)
}
}
return modulePreloadTags.join('')
}
return context.renderResourceHints()
}
async ready() { async ready() {
// Production: Load SSR resources from fs // Production: Load SSR resources from fs
if (!this.context.options.dev) { if (!this.context.options.dev) {
@ -151,7 +193,7 @@ export default class VueRenderer {
rendererOptions rendererOptions
) )
if (this.context.options.build.modern) { if (this.context.options.modern === 'server') {
this.renderer.modern = createBundleRenderer( this.renderer.modern = createBundleRenderer(
this.context.resources.serverBundle, this.context.resources.serverBundle,
{ {
@ -186,7 +228,8 @@ export default class VueRenderer {
context.url = url context.url = url
// Basic response if SSR is disabled or spa data provided // Basic response if SSR is disabled or spa data provided
const spa = context.spa || (context.res && context.res.spa) const { req, res } = context
const spa = context.spa || (res && res.spa)
const ENV = this.context.options.env const ENV = this.context.options.env
if (this.noSSR || spa) { if (this.noSSR || spa) {
@ -224,18 +267,9 @@ export default class VueRenderer {
return { html, getPreloadFiles } return { html, getPreloadFiles }
} }
const { req: { socket = {}, headers } = {} } = context
if (socket.isModernBrowser === undefined) {
const ua = headers && headers['user-agent']
socket.isModernBrowser = this.renderer.modern && ua && matchesUA(ua, {
allowHigherVersions: true,
browsers: this.modernBrowsers
})
}
let APP let APP
// Call renderToString from the bundleRenderer and generate the HTML (will update the context as well) // Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)
if (socket.isModernBrowser) { if (req && req.isModernBrowser) {
APP = await this.renderer.modern.renderToString(context) APP = await this.renderer.modern.renderToString(context)
} else { } else {
APP = await this.renderer.ssr.renderToString(context) APP = await this.renderer.ssr.renderToString(context)
@ -257,7 +291,7 @@ export default class VueRenderer {
} }
if (this.context.options.render.resourceHints) { if (this.context.options.render.resourceHints) {
HEAD += context.renderResourceHints() HEAD += this.renderResourceHints(context)
} }
await this.context.nuxt.callHook('render:routeContext', context.nuxt) await this.context.nuxt.callHook('render:routeContext', context.nuxt)
@ -273,7 +307,7 @@ export default class VueRenderer {
} }
APP += `<script>${serializedSession}</script>` APP += `<script>${serializedSession}</script>`
APP += context.renderScripts() APP += this.renderScripts(context)
APP += m.script.text({ body: true }) APP += m.script.text({ body: true })
APP += m.noscript.text({ body: true }) APP += m.noscript.text({ body: true })

View File

@ -24,8 +24,8 @@ export class WebpackBuilder {
// Fields that set on build // Fields that set on build
this.compilers = [] this.compilers = []
this.compilersWatching = [] this.compilersWatching = []
this.webpackDevMiddleware = null this.devMiddleware = {}
this.webpackHotMiddleware = null this.hotMiddleware = {}
this.perfLoader = null this.perfLoader = null
// Initialize shared FS and Cache // Initialize shared FS and Cache
@ -47,7 +47,7 @@ export class WebpackBuilder {
// Modern // Modern
let modernConfig let modernConfig
if (options.build.modern) { if (options.modern) {
modernConfig = new ModernConfig(this).config() modernConfig = new ModernConfig(this).config()
compilersOptions.push(modernConfig) compilersOptions.push(modernConfig)
} }
@ -141,7 +141,7 @@ export class WebpackBuilder {
if (options.dev) { if (options.dev) {
// --- Dev Build --- // --- Dev Build ---
// Client Build, watch is started by dev-middleware // Client Build, watch is started by dev-middleware
if (compiler.options.name === 'client') { if (['client', 'modern'].includes(name)) {
return this.webpackDev(compiler) return this.webpackDev(compiler)
} }
// Server, build and watch for changes // Server, build and watch for changes
@ -178,10 +178,11 @@ export class WebpackBuilder {
webpackDev(compiler) { webpackDev(compiler) {
consola.debug('Adding webpack middleware...') consola.debug('Adding webpack middleware...')
const name = [compiler.options.name]
const { nuxt: { server }, options } = this.context const { nuxt: { server }, options } = this.context
// Create webpack dev middleware // Create webpack dev middleware
this.webpackDevMiddleware = pify( this.devMiddleware[name] = pify(
webpackDevMiddleware( webpackDevMiddleware(
compiler, compiler,
Object.assign( Object.assign(
@ -196,9 +197,9 @@ export class WebpackBuilder {
) )
) )
this.webpackDevMiddleware.close = pify(this.webpackDevMiddleware.close) this.devMiddleware[name].close = pify(this.devMiddleware[name].close)
this.webpackHotMiddleware = pify( this.hotMiddleware[name] = pify(
webpackHotMiddleware( webpackHotMiddleware(
compiler, compiler,
Object.assign( Object.assign(
@ -206,15 +207,18 @@ export class WebpackBuilder {
log: false, log: false,
heartbeat: 10000 heartbeat: 10000
}, },
options.build.hotMiddleware options.build.hotMiddleware,
{
path: `/__webpack_hmr/${name}`
}
) )
) )
) )
// Inject to renderer instance // Inject to renderer instance
if (server) { if (server) {
server.webpackDevMiddleware = this.webpackDevMiddleware server.devMiddleware = this.devMiddleware
server.webpackHotMiddleware = this.webpackHotMiddleware server.hotMiddleware = this.hotMiddleware
} }
} }
@ -223,8 +227,8 @@ export class WebpackBuilder {
watching.close() watching.close()
} }
// Stop webpack middleware // Stop webpack middleware
if (this.webpackDevMiddleware) { for (const devMiddleware of Object.values(this.devMiddleware)) {
await this.webpackDevMiddleware.close() await devMiddleware.close()
} }
} }

View File

@ -103,7 +103,7 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
}, this.options.build.analyze))) }, this.options.build.analyze)))
} }
if (this.options.build.modern) { if (this.options.modern) {
plugins.push(new ModernModePlugin({ plugins.push(new ModernModePlugin({
targetDir: path.resolve(this.options.buildDir, 'dist', 'client'), targetDir: path.resolve(this.options.buildDir, 'dist', 'client'),
isModernBuild: this.isModern isModernBuild: this.isModern
@ -167,9 +167,9 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
if (this.options.dev) { if (this.options.dev) {
config.entry.app.unshift( config.entry.app.unshift(
// https://github.com/glenjamin/webpack-hot-middleware#config // https://github.com/glenjamin/webpack-hot-middleware#config
`webpack-hot-middleware/client?name=client&reload=true&timeout=30000&path=${ `webpack-hot-middleware/client?name=${this.name}&reload=true&timeout=30000&path=${
this.options.router.base this.options.router.base
}/__webpack_hmr`.replace(/\/\//g, '/') }/__webpack_hmr/${this.name}`.replace(/\/\//g, '/')
) )
} }

View File

@ -26,12 +26,20 @@ export default class VueSSRClientPlugin {
.filter(file => isJS(file) || isCSS(file)) .filter(file => isJS(file) || isCSS(file))
.filter(file => !initialFiles.includes(file)) .filter(file => !initialFiles.includes(file))
const assetsMapping = {}
stats.assets
.filter(({ name }) => isJS(name))
.forEach(({ name, chunkNames }) => {
assetsMapping[name] = hash(chunkNames.join('|'))
})
const manifest = { const manifest = {
publicPath: stats.publicPath, publicPath: stats.publicPath,
all: allFiles, all: allFiles,
initial: initialFiles, initial: initialFiles,
async: asyncFiles, async: asyncFiles,
modules: { /* [identifier: string]: Array<index: number> */ } modules: { /* [identifier: string]: Array<index: number> */ },
assetsMapping
} }
const assetModules = stats.modules.filter(m => m.assets.length) const assetModules = stats.modules.filter(m => m.assets.length)

View File

@ -2,12 +2,14 @@
** This plugin is inspired by @vue/cli-service ModernModePlugin ** This plugin is inspired by @vue/cli-service ModernModePlugin
** https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/webpack/ModernModePlugin.js ** https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/webpack/ModernModePlugin.js
*/ */
import path from 'path' import EventEmitter from 'events'
import fs from 'fs-extra'
// https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc // https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc
const safariFix = `!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();` const safariFix = `!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();`
const assetsMap = {}
const watcher = new EventEmitter()
class ModernModePlugin { class ModernModePlugin {
constructor({ targetDir, isModernBuild }) { constructor({ targetDir, isModernBuild }) {
this.targetDir = targetDir this.targetDir = targetDir
@ -22,20 +24,33 @@ class ModernModePlugin {
} }
} }
set assets({ name, content }) {
assetsMap[name] = content
watcher.emit(name)
}
getAssets(name) {
return assetsMap[name] ||
new Promise((resolve) => {
watcher.once(name, () => {
return assetsMap[name] && resolve(assetsMap[name])
})
return assetsMap[name] && resolve(assetsMap[name])
})
}
applyLegacy(compiler) { applyLegacy(compiler) {
const ID = `nuxt-legacy-bundle` const ID = `nuxt-legacy-bundle`
compiler.hooks.compilation.tap(ID, (compilation) => { compiler.hooks.compilation.tap(ID, (compilation) => {
// For html-webpack-plugin 4.0 // For html-webpack-plugin 4.0
// HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync(ID, async (data, cb) => { // HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync(ID, async (data, cb) => {
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, async (data, cb) => { compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, (data, cb) => {
// get stats, write to disk // get stats, write to disk
await fs.ensureDir(this.targetDir) this.assets = {
const htmlName = path.basename(data.plugin.options.filename) name: data.plugin.options.filename,
// Watch out for output files in sub directories content: data.body
const htmlPath = path.dirname(data.plugin.options.filename) }
const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
await fs.mkdirp(path.dirname(tempFilename))
await fs.writeFile(tempFilename, JSON.stringify(data.body))
cb() cb()
}) })
}) })
@ -72,15 +87,16 @@ class ModernModePlugin {
}) })
// inject links for legacy assets as <script nomodule> // inject links for legacy assets as <script nomodule>
const htmlName = path.basename(data.plugin.options.filename) const fileName = data.plugin.options.filename
// Watch out for output files in sub directories const legacyAssets = (await this.getAssets(fileName))
const htmlPath = path.dirname(data.plugin.options.filename)
const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
const legacyAssets = JSON.parse(await fs.readFile(tempFilename, 'utf-8'))
.filter(a => a.tagName === 'script' && a.attributes) .filter(a => a.tagName === 'script' && a.attributes)
legacyAssets.forEach(a => (a.attributes.nomodule = ''))
data.body.push(...legacyAssets) for (const a of legacyAssets) {
await fs.remove(tempFilename) a.attributes.nomodule = ''
data.body.push(a)
}
delete assetsMap[fileName]
cb() cb()
}) })

View File

@ -1,6 +1,6 @@
export default { export default {
modern: true,
build: { build: {
modern: true,
filenames: { filenames: {
app: ({ isModern }) => { app: ({ isModern }) => {
return `${isModern ? 'modern-' : ''}[name].js` return `${isModern ? 'modern-' : ''}[name].js`

View File

@ -0,0 +1,36 @@
import { loadFixture, getPort, Nuxt, rp } from '../utils'
let nuxt, port
const url = route => 'http://localhost:' + port + route
describe('modern client mode', () => {
beforeAll(async () => {
const options = await loadFixture('modern', { modern: 'client' })
nuxt = new Nuxt(options)
port = await getPort()
await nuxt.server.listen(port, 'localhost')
})
test('should contain nomodule legacy resources', async () => {
const response = await rp(url('/'))
expect(response).toContain('script nomodule src="/_nuxt/app.js')
expect(response).toContain('script nomodule src="/_nuxt/commons.app.js')
})
test('should contain module modern resources', async () => {
const response = await rp(url('/'))
expect(response).toContain('<script type="module" src="/_nuxt/modern-app.js"')
expect(response).toContain('<script type="module" src="/_nuxt/modern-commons.app.js"')
})
test('should contain module preload resources', async () => {
const response = await rp(url('/'))
expect(response).toContain('<link rel="modulepreload" href="/_nuxt/modern-app.js" as="script">')
expect(response).toContain('<link rel="modulepreload" href="/_nuxt/modern-commons.app.js" as="script">')
})
// Close server and ask nuxt to stop listening to file changes
afterAll(async () => {
await nuxt.close()
})
})

View File

@ -3,9 +3,9 @@ import { loadFixture, getPort, Nuxt, rp } from '../utils'
let nuxt, port let nuxt, port
const url = route => 'http://localhost:' + port + route const url = route => 'http://localhost:' + port + route
describe('modern build', () => { describe('modern server mode', () => {
beforeAll(async () => { beforeAll(async () => {
const options = await loadFixture('modern') const options = await loadFixture('modern', { modern: 'server' })
nuxt = new Nuxt(options) nuxt = new Nuxt(options)
port = await getPort() port = await getPort()
await nuxt.server.listen(port, 'localhost') await nuxt.server.listen(port, 'localhost')
@ -36,4 +36,9 @@ describe('modern build', () => {
const response = await rp(url('/_nuxt/app.js')) const response = await rp(url('/_nuxt/app.js'))
expect(response).not.toContain('arrow:()=>"build test"') expect(response).not.toContain('arrow:()=>"build test"')
}) })
// Close server and ask nuxt to stop listening to file changes
afterAll(async () => {
await nuxt.close()
})
}) })