feat(config, vue-app, vue-renderer): support dynamic base and publicPath (#8520)

Co-authored-by: pooya parsa <pyapar@gmail.com>
This commit is contained in:
Daniel Roe 2020-12-22 17:15:59 +00:00 committed by GitHub
parent dec8f99fc3
commit 9681a8937d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 115 additions and 18 deletions

View File

@ -4,8 +4,8 @@ import { defaultsDeep, pick, uniq } from 'lodash'
import defu from 'defu'
import consola from 'consola'
import destr from 'destr'
import { TARGETS, MODES, guardDir, isNonEmptyString, isPureObject, isUrl, getMainModule, urlJoin, getPKG } from '@nuxt/utils'
import { normalizeURL, withTrailingSlash } from '@nuxt/ufo'
import { TARGETS, MODES, guardDir, isNonEmptyString, isPureObject, isUrl, getMainModule, getPKG } from '@nuxt/utils'
import { joinURL, normalizeURL, withTrailingSlash } from '@nuxt/ufo'
import { defaultNuxtConfigFile, getDefaultNuxtConfig } from './config'
export function getNuxtConfig (_options) {
@ -450,17 +450,27 @@ export function getNuxtConfig (_options) {
.map(([path, handler]) => ({ path, handler }))
}
// App config (internal for nuxt2 at this stage)
const useCDN = isUrl(options.build.publicPath) && !options.dev
options.app = defu(options.app, {
basePath: options.router.base,
assetsPath: useCDN ? '/' : joinURL(options.router.base, options.build.publicPath),
cdnURL: useCDN ? options.build.publicPath : null
})
// Expose app config to $config.app
options.publicRuntimeConfig = options.publicRuntimeConfig || {}
options.publicRuntimeConfig.app = options.app
// Generate staticAssets
const { staticAssets } = options.generate
if (!staticAssets.version) {
staticAssets.version = String(Math.round(Date.now() / 1000))
}
if (!staticAssets.base) {
const publicPath = isUrl(options.build.publicPath) ? '' : options.build.publicPath // "/_nuxt" or custom CDN URL
staticAssets.base = urlJoin(publicPath, staticAssets.dir)
staticAssets.base = joinURL(options.app.assetsPath, staticAssets.dir)
}
if (!staticAssets.versionBase) {
staticAssets.versionBase = urlJoin(staticAssets.base, staticAssets.version)
staticAssets.versionBase = joinURL(staticAssets.base, staticAssets.version)
}
// createRequire

View File

@ -19,6 +19,11 @@ Object {
"~": "/var/nuxt/test",
"~~": "/var/nuxt/test",
},
"app": Object {
"assetsPath": "/_nuxt/",
"basePath": "/",
"cdnURL": null,
},
"appTemplatePath": "/var/nuxt/test/.nuxt/views/app.template.html",
"build": Object {
"_publicPath": "/_nuxt/",
@ -312,7 +317,13 @@ Object {
},
"plugins": Array [],
"privateRuntimeConfig": Object {},
"publicRuntimeConfig": Object {},
"publicRuntimeConfig": Object {
"app": Object {
"assetsPath": "/_nuxt/",
"basePath": "/",
"cdnURL": null,
},
},
"render": Object {
"bundleRenderer": Object {
"runInNewContext": false,

View File

@ -29,11 +29,9 @@ export default class Generator {
)
// Payloads for full static
if (this.isFullStatic) {
const { build: { publicPath: _publicPath }, router: { base } } = this.options
const publicPath = isUrl(_publicPath) ? _publicPath : base
const { staticAssets, manifest } = this.options.generate
this.staticAssetsDir = path.resolve(this.distNuxtPath, staticAssets.dir, staticAssets.version)
this.staticAssetsBase = urlJoin(publicPath, this.options.generate.staticAssets.versionBase)
this.staticAssetsBase = urlJoin(this.options.app.cdnURL || '/', this.options.generate.staticAssets.versionBase)
if (manifest) {
this.manifest = defu(manifest, {
routes: []

View File

@ -15,7 +15,8 @@ import {
compile,
getQueryDiff,
globalHandleError,
isSamePath
isSamePath,
urlJoin
} from './utils.js'
import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
<% if (features.fetch) { %>import fetchMixin from './mixins/fetch.client'<% } %>
@ -45,6 +46,11 @@ let router
// Try to rehydrate SSR data from window
const NUXT = window.<%= globals.context %> || {}
const $config = NUXT.config || {}
if ($config.app) {
__webpack_public_path__ = urlJoin($config.app.cdnURL || '/', $config.app.assetsPath)
}
Object.assign(Vue.config, <%= serialize(vue.config) %>)<%= isTest ? '// eslint-disable-line' : '' %>
<% if (nuxtOptions.render.ssrLog) { %>

View File

@ -88,7 +88,7 @@ function registerModule (path, rawModule, options = {}) {
<% } %>
async function createApp(ssrContext, config = {}) {
const router = await createRouter(ssrContext)
const router = await createRouter(ssrContext, config)
<% if (store) { %>
const store = createStore(ssrContext)

View File

@ -109,8 +109,9 @@ function decodeObj(obj) {
}
}
export function createRouter () {
const router = new Router(routerOptions)
export function createRouter (ssrContext, config) {
const base = (config.app && config.app.basePath) || routerOptions.base
const router = new Router({ ...routerOptions, base })
// TODO: remove in Nuxt 3
const originalPush = router.push

View File

@ -46,7 +46,8 @@ const createNext = ssrContext => (opts) => {
}
opts.query = stringify(opts.query)
opts.path = opts.path + (opts.query ? '?' + opts.query : '')
const routerBase = '<%= router.base %>'
const $config = ssrContext.runtimeConfig || {}
const routerBase = ($config.app && $config.app.basePath) || '<%= router.base %>'
if (!opts.path.startsWith('http') && (routerBase !== '/' && !opts.path.startsWith(routerBase))) {
opts.path = urlJoin(routerBase, opts.path)
}

View File

@ -180,7 +180,7 @@ export async function setContext (app, context) {
<%= (store ? 'store: app.store,' : '') %>
payload: context.payload,
error: context.error,
base: '<%= router.base %>',
base: app.router.options.base,
env: <%= JSON.stringify(env) %><%= isTest ? '// eslint-disable-line' : '' %>
}
// Only set once

View File

@ -2,7 +2,7 @@ import path from 'path'
import fs from 'fs-extra'
import consola from 'consola'
import { template } from 'lodash'
import { TARGETS, isModernRequest, waitFor } from '@nuxt/utils'
import { TARGETS, isModernRequest, urlJoin, waitFor } from '@nuxt/utils'
import { normalizeURL } from '@nuxt/ufo'
import SPARenderer from './renderers/spa'
@ -310,14 +310,15 @@ export default class VueRenderer {
}
get resourceMap () {
const publicPath = urlJoin(this.options.app.cdnURL || '/', this.options.app.assetsPath)
return {
clientManifest: {
fileName: 'client.manifest.json',
transform: src => JSON.parse(src)
transform: src => Object.assign(JSON.parse(src), { publicPath })
},
modernManifest: {
fileName: 'modern.manifest.json',
transform: src => JSON.parse(src)
transform: src => Object.assign(JSON.parse(src), { publicPath })
},
serverManifest: {
fileName: 'server.manifest.json',

View File

@ -0,0 +1,69 @@
import path from 'path'
import { readFileSync } from 'fs'
import { ResourceLoader } from 'jsdom'
import { loadFixture, getPort, Nuxt, Builder } from '../utils'
let fetchCount = 0
class ProxyLoader extends ResourceLoader {
fetch (url, options) {
if (url.startsWith('https://cdn.nuxtjs.org')) {
fetchCount++
const param = url.slice('https://cdn.nuxtjs.org'.length + 1)
const file = path.join(nuxt.options.buildDir, 'dist/client', param)
const fileContents = readFileSync(file, 'utf-8')
return Promise.resolve(Buffer.from(fileContents))
}
return super.fetch(url, options)
}
}
const resourceLoader = new ProxyLoader()
let port
let nuxt
const url = route => 'http://localhost:' + port + route
describe('basic ssr', () => {
beforeAll(async () => {
const options = await loadFixture('basic')
const builderNuxt = new Nuxt(options)
await builderNuxt.ready()
const builder = new Builder(builderNuxt)
await builder.build()
const runOptions = await loadFixture('basic', {
router: {
base: '/path'
},
build: {
publicPath: 'https://cdn.nuxtjs.org'
}
})
nuxt = new Nuxt(runOptions)
await nuxt.ready()
port = await getPort()
await nuxt.server.listen(port, '0.0.0.0')
})
test('dynamic config is injected', async () => {
const window = await nuxt.server.renderAndGetWindow(url('/path/'), { resources: resourceLoader })
expect(window.document.body.innerHTML).toContain('<h1>Index page</h1>')
expect(window.__NUXT__.config.app.basePath).toBe('/path/')
expect(window.__NUXT__.config.app.cdnURL).toBe('https://cdn.nuxtjs.org/')
expect(window.__NUXT__.config.app.assetsPath).toBe('/')
expect(fetchCount).toBeGreaterThan(0)
})
// Close server and ask nuxt to stop listening to file changes
afterAll(async () => {
await nuxt.close()
})
})