mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 14:15:13 +00:00
decouple builder from renderer + improvements
This commit is contained in:
parent
b61694ca21
commit
42bf9bb41d
@ -64,10 +64,10 @@ if (typeof options.rootDir !== 'string') {
|
||||
// Force development mode: add hot reloading and watching changes
|
||||
options.dev = true
|
||||
|
||||
var nuxt = module.exports = new Nuxt(options)
|
||||
var nuxt = new Nuxt(options)
|
||||
var port = argv.port || process.env.PORT || process.env.npm_package_config_nuxt_port
|
||||
var host = argv.hostname || process.env.HOST || process.env.npm_package_config_nuxt_host
|
||||
var server = nuxt.server = new nuxt.Server(nuxt).listen(port, host)
|
||||
var server = new Nuxt.Server(nuxt).listen(port, host)
|
||||
|
||||
listenOnConfigChanges(nuxt, server)
|
||||
|
||||
@ -102,3 +102,5 @@ function listenOnConfigChanges(nuxt, server) {
|
||||
chokidar.watch(nuxtConfigFile, Object.assign({}, nuxt.options.watchers.chokidar, { ignoreInitial: true }))
|
||||
.on('all', build)
|
||||
}
|
||||
|
||||
module.exports = nuxt
|
||||
|
@ -55,7 +55,9 @@ if (typeof options.rootDir !== 'string') {
|
||||
}
|
||||
options.dev = false // Force production mode (no webpack middleware called)
|
||||
|
||||
var nuxt = module.exports = new Nuxt(options)
|
||||
var nuxt = new Nuxt(options)
|
||||
var port = argv.port || process.env.PORT || process.env.npm_package_config_nuxt_port
|
||||
var host = argv.hostname || process.env.HOST || process.env.npm_package_config_nuxt_host
|
||||
module.exports = nuxt.server = new nuxt.Server(nuxt).listen(port, host)
|
||||
new Nuxt.Server(nuxt).listen(port, host)
|
||||
|
||||
module.exports = nuxt
|
||||
|
428
lib/builder.js
428
lib/builder.js
@ -4,15 +4,16 @@ import fs from 'fs-extra'
|
||||
import hash from 'hash-sum'
|
||||
import pify from 'pify'
|
||||
import webpack from 'webpack'
|
||||
import PostCompilePlugin from 'post-compile-webpack-plugin'
|
||||
import serialize from 'serialize-javascript'
|
||||
import { createBundleRenderer } from 'vue-server-renderer'
|
||||
import webpackDevMiddleware from 'webpack-dev-middleware'
|
||||
import webpackHotMiddleware from 'webpack-hot-middleware'
|
||||
import { join, resolve, basename, dirname } from 'path'
|
||||
import Tapable from 'tappable'
|
||||
import { isUrl, r, wp } from './utils'
|
||||
import { isUrl, r, wp, createRoutes } from './utils'
|
||||
import clientWebpackConfig from './webpack/client.config.js'
|
||||
import serverWebpackConfig from './webpack/server.config.js'
|
||||
import defaults from './defaults'
|
||||
import MFS from 'memory-fs'
|
||||
|
||||
const debug = require('debug')('nuxt:build')
|
||||
debug.color = 2 // Force green color
|
||||
@ -24,12 +25,39 @@ const writeFile = pify(fs.writeFile)
|
||||
const mkdirp = pify(fs.mkdirp)
|
||||
const glob = pify(require('glob'))
|
||||
|
||||
const host = process.env.HOST || process.env.npm_package_config_nuxt_host || 'localhost'
|
||||
const port = process.env.PORT || process.env.npm_package_config_nuxt_port || '3000'
|
||||
|
||||
debug('loaded')
|
||||
|
||||
export default class Builder extends Tapable {
|
||||
constructor (nuxt) {
|
||||
super()
|
||||
this.nuxt = nuxt
|
||||
this.options = nuxt.options
|
||||
|
||||
this._buildStatus = STATUS.INITIAL
|
||||
this.initialized = false
|
||||
|
||||
// Fields that set on build
|
||||
this.compiler = null
|
||||
this.webpackDevMiddleware = null
|
||||
this.webpackHotMiddleware = null
|
||||
|
||||
if (nuxt.initialized) {
|
||||
// If nuxt already initialized
|
||||
this._init = this.init().catch(this.nuxt.errorHandler)
|
||||
} else {
|
||||
// Wait for hook
|
||||
this.nuxt.plugin('init', this.init.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
async init () {
|
||||
if (this._init) {
|
||||
return this._init
|
||||
}
|
||||
|
||||
// Add extra loaders only if they are not already provided
|
||||
let extraDefaults = {}
|
||||
if (this.options.build && !Array.isArray(this.options.build.loaders)) {
|
||||
@ -38,37 +66,25 @@ export default class Builder extends Tapable {
|
||||
if (this.options.build && !Array.isArray(this.options.build.postcss)) {
|
||||
extraDefaults.postcss = defaultsPostcss
|
||||
}
|
||||
this.options.build = _.defaultsDeep(this.options.build, extraDefaults)
|
||||
_.defaultsDeep(this.options.build, extraDefaults)
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (this.options.dev && isUrl(this.options.build.publicPath)) {
|
||||
this.options.build.publicPath = defaults.build.publicPath
|
||||
}
|
||||
|
||||
// Stats
|
||||
this.webpackStats = {
|
||||
// If store defined, update store options to true unless explicitly disabled
|
||||
if (this.options.store !== false && fs.existsSync(join(this.options.srcDir, 'store'))) {
|
||||
this.options.store = true
|
||||
}
|
||||
|
||||
// Mute stats on dev
|
||||
this.webpackStats = this.options.dev ? '' : {
|
||||
chunks: false,
|
||||
children: false,
|
||||
modules: false,
|
||||
colors: true
|
||||
}
|
||||
|
||||
// Register lifecycle hooks
|
||||
if (this.nuxt.options.dev) {
|
||||
// Don't await for build on dev (faster startup)
|
||||
this.nuxt.plugin('afterInit', () => {
|
||||
this.build().catch(this.nuxt.errorHandler)
|
||||
})
|
||||
} else {
|
||||
this.nuxt.plugin('init', () => {
|
||||
// Guess it is build or production
|
||||
// If build is not called it may be nuxt.start
|
||||
if (this._buildStatus === STATUS.INITIAL) {
|
||||
return this.production()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this._buildStatus = STATUS.INITIAL
|
||||
}
|
||||
|
||||
async build () {
|
||||
@ -76,7 +92,6 @@ export default class Builder extends Tapable {
|
||||
if (this._buildStatus === STATUS.BUILD_DONE) {
|
||||
return this
|
||||
}
|
||||
|
||||
// If building
|
||||
if (this._buildStatus === STATUS.BUILDING) {
|
||||
return new Promise((resolve) => {
|
||||
@ -92,7 +107,6 @@ export default class Builder extends Tapable {
|
||||
|
||||
// Check if pages dir exists and warn if not
|
||||
this._nuxtPages = typeof this.options.build.createRoutes !== 'function'
|
||||
|
||||
if (this._nuxtPages) {
|
||||
if (!fs.existsSync(join(this.options.srcDir, 'pages'))) {
|
||||
let dir = this.options.srcDir
|
||||
@ -113,39 +127,19 @@ export default class Builder extends Tapable {
|
||||
if (!this.options.dev) {
|
||||
await mkdirp(r(this.options.buildDir, 'dist'))
|
||||
}
|
||||
|
||||
// Generate routes and interpret the template files
|
||||
await this.generateRoutesAndFiles()
|
||||
// Generate .nuxt/dist/ files
|
||||
await this.buildFiles()
|
||||
|
||||
// Start webpack build
|
||||
await this.webpackBuild()
|
||||
|
||||
// Flag to set that building is done
|
||||
this._buildStatus = STATUS.BUILD_DONE
|
||||
return this
|
||||
}
|
||||
|
||||
async production () {
|
||||
// Production, create server-renderer
|
||||
const serverConfig = this.getWebpackServerConfig()
|
||||
const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
|
||||
const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
|
||||
if (!fs.existsSync(bundlePath) || !fs.existsSync(manifestPath)) {
|
||||
throw new Error(`No build files found in ${serverConfig.output.path}, please run \`nuxt build\` before launching \`nuxt start\``)
|
||||
}
|
||||
const bundle = fs.readFileSync(bundlePath, 'utf8')
|
||||
const manifest = fs.readFileSync(manifestPath, 'utf8')
|
||||
this.createRenderer(JSON.parse(bundle), JSON.parse(manifest))
|
||||
this.addAppTemplate()
|
||||
return this
|
||||
}
|
||||
|
||||
addAppTemplate () {
|
||||
let templatePath = resolve(this.options.buildDir, 'dist', 'index.html')
|
||||
if (fs.existsSync(templatePath)) {
|
||||
this.appTemplate = _.template(fs.readFileSync(templatePath, 'utf8'), {
|
||||
interpolate: /{{([\s\S]+?)}}/g
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async generateRoutesAndFiles () {
|
||||
debug('Generating files...')
|
||||
// -- Templates --
|
||||
@ -171,7 +165,7 @@ export default class Builder extends Tapable {
|
||||
env: this.options.env,
|
||||
head: this.options.head,
|
||||
middleware: fs.existsSync(join(this.options.srcDir, 'middleware')),
|
||||
store: this.options.store || fs.existsSync(join(this.options.srcDir, 'store')),
|
||||
store: this.options.store,
|
||||
css: this.options.css,
|
||||
plugins: this.options.plugins.map((p, i) => {
|
||||
if (typeof p === 'string') p = { src: p }
|
||||
@ -180,7 +174,7 @@ export default class Builder extends Tapable {
|
||||
}),
|
||||
appPath: './App.vue',
|
||||
layouts: Object.assign({}, this.options.layouts),
|
||||
loading: (typeof this.options.loading === 'string' ? r(this.options.srcDir, this.options.loading) : this.options.loading),
|
||||
loading: typeof this.options.loading === 'string' ? r(this.options.srcDir, this.options.loading) : this.options.loading,
|
||||
transition: this.options.transition,
|
||||
components: {
|
||||
ErrorPage: this.options.ErrorPage ? r(this.options.ErrorPage) : null
|
||||
@ -212,7 +206,7 @@ export default class Builder extends Tapable {
|
||||
if (this._nuxtPages) {
|
||||
// Use nuxt.js createRoutes bases on pages/
|
||||
const files = await glob('pages/**/*.vue', { cwd: this.options.srcDir })
|
||||
templateVars.router.routes = this.createRoutes(files, this.options.srcDir)
|
||||
templateVars.router.routes = createRoutes(files, this.options.srcDir)
|
||||
} else {
|
||||
templateVars.router.routes = this.options.build.createRoutes(this.options.srcDir)
|
||||
}
|
||||
@ -221,8 +215,6 @@ export default class Builder extends Tapable {
|
||||
// let the user extend the routes
|
||||
this.options.router.extendRoutes(templateVars.router.routes, r)
|
||||
}
|
||||
// Routes for generate command
|
||||
this.routes = this.flatRoutes(templateVars.router.routes || [])
|
||||
|
||||
// -- Store --
|
||||
// Add store if needed
|
||||
@ -290,258 +282,100 @@ export default class Builder extends Tapable {
|
||||
}))
|
||||
}
|
||||
|
||||
async buildFiles () {
|
||||
webpackBuild () {
|
||||
debug('Building files...')
|
||||
let compilersOptions = []
|
||||
|
||||
// Client
|
||||
let clientConfig = clientWebpackConfig.call(this)
|
||||
clientConfig.name = '$client'
|
||||
compilersOptions.push(clientConfig)
|
||||
|
||||
// Server
|
||||
if (this.options.ssr !== false) {
|
||||
let serverConfig = serverWebpackConfig.call(this)
|
||||
serverConfig.name = '$server'
|
||||
compilersOptions.push(serverConfig)
|
||||
}
|
||||
|
||||
// Leverage webpack multi-compiler for faster builds
|
||||
this.compiler = webpack(compilersOptions)
|
||||
|
||||
// Access to compilers with name
|
||||
this.compiler.compilers.forEach(compiler => {
|
||||
if (compiler.name) {
|
||||
this.compiler[compiler.name] = compiler
|
||||
}
|
||||
})
|
||||
|
||||
// Add middleware for dev
|
||||
if (this.options.dev) {
|
||||
debug('Adding webpack middleware...')
|
||||
this.createWebpackMiddleware()
|
||||
this.webpackWatchAndUpdate()
|
||||
this.watchFiles()
|
||||
} else {
|
||||
debug('Building files...')
|
||||
await this.webpackRunClient()
|
||||
await this.webpackRunServer()
|
||||
this.addAppTemplate()
|
||||
this.webpackDev()
|
||||
}
|
||||
}
|
||||
|
||||
createRoutes (files, srcDir) {
|
||||
let routes = []
|
||||
files.forEach((file) => {
|
||||
let keys = file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/{2,}/g, '/').split('/').slice(1)
|
||||
let route = { name: '', path: '', component: r(srcDir, file) }
|
||||
let parent = routes
|
||||
keys.forEach((key, i) => {
|
||||
route.name = route.name ? route.name + '-' + key.replace('_', '') : key.replace('_', '')
|
||||
route.name += (key === '_') ? 'all' : ''
|
||||
let child = _.find(parent, { name: route.name })
|
||||
if (child) {
|
||||
if (!child.children) {
|
||||
child.children = []
|
||||
}
|
||||
parent = child.children
|
||||
route.path = ''
|
||||
} else {
|
||||
if (key === 'index' && (i + 1) === keys.length) {
|
||||
route.path += (i > 0 ? '' : '/')
|
||||
} else {
|
||||
route.path += '/' + (key === '_' ? '*' : key.replace('_', ':'))
|
||||
if (key !== '_' && key.indexOf('_') !== -1) {
|
||||
route.path += '?'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// Order Routes path
|
||||
parent.push(route)
|
||||
parent.sort((a, b) => {
|
||||
if (!a.path.length || a.path === '/') {
|
||||
return -1
|
||||
}
|
||||
if (!b.path.length || b.path === '/') {
|
||||
return 1
|
||||
}
|
||||
let res = 0
|
||||
let _a = a.path.split('/')
|
||||
let _b = b.path.split('/')
|
||||
for (let i = 0; i < _a.length; i++) {
|
||||
if (res !== 0) {
|
||||
break
|
||||
}
|
||||
let y = (_a[i].indexOf('*') > -1) ? 2 : (_a[i].indexOf(':') > -1 ? 1 : 0)
|
||||
let z = (_b[i].indexOf('*') > -1) ? 2 : (_b[i].indexOf(':') > -1 ? 1 : 0)
|
||||
res = y - z
|
||||
if (i === _b.length - 1 && res === 0) {
|
||||
res = 1
|
||||
}
|
||||
}
|
||||
return res === 0 ? -1 : res
|
||||
})
|
||||
})
|
||||
return this.cleanChildrenRoutes(routes)
|
||||
}
|
||||
|
||||
cleanChildrenRoutes (routes, isChild = false) {
|
||||
let start = -1
|
||||
let routesIndex = []
|
||||
routes.forEach((route) => {
|
||||
if (/-index$/.test(route.name) || route.name === 'index') {
|
||||
// Save indexOf 'index' key in name
|
||||
let res = route.name.split('-')
|
||||
let s = res.indexOf('index')
|
||||
start = (start === -1 || s < start) ? s : start
|
||||
routesIndex.push(res)
|
||||
}
|
||||
})
|
||||
routes.forEach((route) => {
|
||||
route.path = (isChild) ? route.path.replace('/', '') : route.path
|
||||
if (route.path.indexOf('?') > -1) {
|
||||
let names = route.name.split('-')
|
||||
let paths = route.path.split('/')
|
||||
if (!isChild) {
|
||||
paths.shift()
|
||||
} // clean first / for parents
|
||||
routesIndex.forEach((r) => {
|
||||
let i = r.indexOf('index') - start // children names
|
||||
if (i < paths.length) {
|
||||
for (let a = 0; a <= i; a++) {
|
||||
if (a === i) {
|
||||
paths[a] = paths[a].replace('?', '')
|
||||
}
|
||||
if (a < i && names[a] !== r[a]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
route.path = (isChild ? '' : '/') + paths.join('/')
|
||||
}
|
||||
route.name = route.name.replace(/-index$/, '')
|
||||
if (route.children) {
|
||||
if (route.children.find((child) => child.path === '')) {
|
||||
delete route.name
|
||||
}
|
||||
route.children = this.cleanChildrenRoutes(route.children, true)
|
||||
}
|
||||
})
|
||||
return routes
|
||||
}
|
||||
|
||||
flatRoutes (router, path = '', routes = []) {
|
||||
router.forEach((r) => {
|
||||
if (!r.path.includes(':') && !r.path.includes('*')) {
|
||||
if (r.children) {
|
||||
this.flatRoutes(r.children, path + r.path + '/', routes)
|
||||
} else {
|
||||
routes.push((r.path === '' && path[path.length - 1] === '/' ? path.slice(0, -1) : path) + r.path)
|
||||
}
|
||||
}
|
||||
})
|
||||
return routes
|
||||
}
|
||||
|
||||
getWebpackClientConfig () {
|
||||
return clientWebpackConfig.call(this)
|
||||
}
|
||||
|
||||
getWebpackServerConfig () {
|
||||
return serverWebpackConfig.call(this)
|
||||
}
|
||||
|
||||
createWebpackMiddleware () {
|
||||
const clientConfig = this.getWebpackClientConfig()
|
||||
const host = process.env.HOST || process.env.npm_package_config_nuxt_host || 'localhost'
|
||||
const port = process.env.PORT || process.env.npm_package_config_nuxt_port || '3000'
|
||||
|
||||
// setup on the fly compilation + hot-reload
|
||||
clientConfig.entry.app = _.flatten(['webpack-hot-middleware/client?reload=true', clientConfig.entry.app])
|
||||
clientConfig.plugins.push(
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new PostCompilePlugin(stats => {
|
||||
if (!stats.hasErrors() && !stats.hasWarnings()) {
|
||||
// We don't use os.host() here because browsers have special behaviour with localhost
|
||||
// For example chrome allows Geolocation api only to https or localhost origins
|
||||
let _host = host === '0.0.0.0' ? 'localhost' : host
|
||||
console.log(`> Open http://${_host}:${port}\n`) // eslint-disable-line no-console
|
||||
}
|
||||
})
|
||||
)
|
||||
const clientCompiler = webpack(clientConfig)
|
||||
this.clientCompiler = clientCompiler
|
||||
// Add the middleware to the instance context
|
||||
this.webpackDevMiddleware = pify(require('webpack-dev-middleware')(clientCompiler, {
|
||||
publicPath: clientConfig.output.publicPath,
|
||||
stats: this.webpackStats,
|
||||
quiet: true,
|
||||
noInfo: true,
|
||||
watchOptions: this.options.watchers.webpack
|
||||
}))
|
||||
this.webpackHotMiddleware = pify(require('webpack-hot-middleware')(clientCompiler, {
|
||||
log: () => {
|
||||
}
|
||||
}))
|
||||
clientCompiler.plugin('done', () => {
|
||||
const fs = this.webpackDevMiddleware.fileSystem
|
||||
const filePath = join(clientConfig.output.path, 'index.html')
|
||||
if (fs.existsSync(filePath)) {
|
||||
const template = fs.readFileSync(filePath, 'utf-8')
|
||||
this.appTemplate = _.template(template, {
|
||||
interpolate: /{{([\s\S]+?)}}/g
|
||||
})
|
||||
}
|
||||
this.watchHandler()
|
||||
})
|
||||
}
|
||||
|
||||
webpackWatchAndUpdate () {
|
||||
const MFS = require('memory-fs') // <- dependencies of webpack
|
||||
const serverFS = new MFS()
|
||||
const clientFS = this.clientCompiler.outputFileSystem
|
||||
const serverConfig = this.getWebpackServerConfig()
|
||||
const serverCompiler = webpack(serverConfig)
|
||||
const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
|
||||
const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
|
||||
serverCompiler.outputFileSystem = serverFS
|
||||
const watchHandler = (err) => {
|
||||
if (err) throw err
|
||||
const bundleExists = serverFS.existsSync(bundlePath)
|
||||
const manifestExists = clientFS.existsSync(manifestPath)
|
||||
if (bundleExists && manifestExists) {
|
||||
const bundle = serverFS.readFileSync(bundlePath, 'utf8')
|
||||
const manifest = clientFS.readFileSync(manifestPath, 'utf8')
|
||||
this.createRenderer(JSON.parse(bundle), JSON.parse(manifest))
|
||||
}
|
||||
}
|
||||
this.watchHandler = watchHandler
|
||||
this.webpackServerWatcher = serverCompiler.watch(this.options.watchers.webpack, watchHandler)
|
||||
}
|
||||
|
||||
webpackRunClient () {
|
||||
// Start build
|
||||
return new Promise((resolve, reject) => {
|
||||
const clientConfig = this.getWebpackClientConfig()
|
||||
const clientCompiler = webpack(clientConfig)
|
||||
clientCompiler.run((err, stats) => {
|
||||
if (err) return reject(err)
|
||||
console.log('[nuxt:build:client]\n', stats.toString(this.webpackStats)) // eslint-disable-line no-console
|
||||
if (stats.hasErrors()) {
|
||||
return reject(new Error('Webpack build exited with errors'))
|
||||
this.compiler.run((err, multiStats) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
for (let _stats of multiStats.stats) {
|
||||
console.log(_stats.toString(this.webpackStats)) // eslint-disable-line no-console
|
||||
if (_stats.hasErrors()) {
|
||||
return reject(new Error('Webpack build exited with errors'))
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
webpackRunServer () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const serverConfig = this.getWebpackServerConfig()
|
||||
const serverCompiler = webpack(serverConfig)
|
||||
serverCompiler.run((err, stats) => {
|
||||
if (err) return reject(err)
|
||||
console.log('[nuxt:build:server]\n', stats.toString(this.webpackStats)) // eslint-disable-line no-console
|
||||
if (stats.hasErrors()) return reject(new Error('Webpack build exited with errors'))
|
||||
const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
|
||||
const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
|
||||
readFile(bundlePath, 'utf8')
|
||||
.then(bundle => {
|
||||
readFile(manifestPath, 'utf8')
|
||||
.then(manifest => {
|
||||
this.createRenderer(JSON.parse(bundle), JSON.parse(manifest))
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
webpackDev () {
|
||||
debug('Adding webpack middleware...')
|
||||
|
||||
createRenderer (bundle, manifest) {
|
||||
// Create bundle renderer to give a fresh context for every request
|
||||
this.renderer = createBundleRenderer(bundle, Object.assign({
|
||||
clientManifest: manifest,
|
||||
runInNewContext: false,
|
||||
basedir: this.options.rootDir
|
||||
}, this.options.build.ssr))
|
||||
this.renderToString = pify(this.renderer.renderToString)
|
||||
this.renderToStream = this.renderer.renderToStream
|
||||
// Use MFS for faster builds
|
||||
let mfs = new MFS()
|
||||
this.compiler.compilers.forEach(compiler => {
|
||||
compiler.outputFileSystem = mfs
|
||||
})
|
||||
|
||||
let clientConfig = this.compiler.$client.options
|
||||
|
||||
// Setup on the fly compilation + hot-reload
|
||||
clientConfig.entry.app = _.flatten(['webpack-hot-middleware/client?reload=true', clientConfig.entry.app])
|
||||
clientConfig.plugins.push(
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
)
|
||||
|
||||
// Create webpack dev middleware
|
||||
this.webpackDevMiddleware = pify(webpackDevMiddleware(this.compiler.$client, {
|
||||
publicPath: clientConfig.output.publicPath,
|
||||
stats: this.webpackStats,
|
||||
quiet: true,
|
||||
noInfo: true,
|
||||
watchOptions: this.options.watchers.webpack
|
||||
}))
|
||||
|
||||
this.webpackHotMiddleware = pify(webpackHotMiddleware(this.compiler.$client, {
|
||||
log: false,
|
||||
heartbeat: 2500
|
||||
}))
|
||||
|
||||
// Run after compilation is done
|
||||
this.compiler.plugin('done', async stats => {
|
||||
// Reload renderer if available
|
||||
if (this.nuxt.renderer) {
|
||||
await this.nuxt.renderer.loadResources(mfs)
|
||||
}
|
||||
// Show open URL
|
||||
if (!stats.hasErrors() && !stats.hasWarnings()) {
|
||||
let _host = host === '0.0.0.0' ? 'localhost' : host
|
||||
console.log(`> Open http://${_host}:${port}\n`) // eslint-disable-line no-console
|
||||
}
|
||||
})
|
||||
|
||||
this.watchFiles()
|
||||
}
|
||||
|
||||
watchFiles () {
|
||||
|
@ -1,4 +1,43 @@
|
||||
export default {
|
||||
import _ from 'lodash'
|
||||
import { join, resolve } from 'path'
|
||||
import { existsSync } from 'fs'
|
||||
|
||||
export default function defaults (_options) {
|
||||
// Clone options to prevent unwanted side-effects
|
||||
const options = Object.assign({}, _options)
|
||||
|
||||
// Normalize options
|
||||
if (options.loading === true) {
|
||||
delete options.loading
|
||||
}
|
||||
if (options.router && typeof options.router.middleware === 'string') {
|
||||
options.router.middleware = [options.router.middleware]
|
||||
}
|
||||
if (options.router && typeof options.router.base === 'string') {
|
||||
options._routerBaseSpecified = true
|
||||
}
|
||||
if (typeof options.transition === 'string') {
|
||||
options.transition = { name: options.transition }
|
||||
}
|
||||
|
||||
// Apply defaults
|
||||
_.defaultsDeep(options, defaultOptions)
|
||||
|
||||
// Resolve dirs
|
||||
options.rootDir = (typeof options.rootDir === 'string' && options.rootDir ? options.rootDir : process.cwd())
|
||||
options.srcDir = (typeof options.srcDir === 'string' && options.srcDir ? resolve(options.rootDir, options.srcDir) : options.rootDir)
|
||||
options.buildDir = join(options.rootDir, options.buildDir)
|
||||
|
||||
// If app.html is defined, set the template path to the user template
|
||||
options.appTemplatePath = resolve(__dirname, 'views/app.template.html')
|
||||
if (existsSync(join(options.srcDir, 'app.html'))) {
|
||||
options.appTemplatePath = join(options.srcDir, 'app.html')
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
const defaultOptions = {
|
||||
dev: (process.env.NODE_ENV !== 'production'),
|
||||
buildDir: '.nuxt',
|
||||
build: {
|
||||
@ -78,6 +117,7 @@ export default {
|
||||
scrollBehavior: null
|
||||
},
|
||||
render: {
|
||||
ssr: {},
|
||||
http2: {
|
||||
push: false
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ import _ from 'lodash'
|
||||
import { resolve, join, dirname, sep } from 'path'
|
||||
import { minify } from 'html-minifier'
|
||||
import Tapable from 'tappable'
|
||||
import { isUrl, promisifyRoute, waitFor } from './utils'
|
||||
import { isUrl, promisifyRoute, waitFor, flatRoutes } from './utils'
|
||||
|
||||
const debug = require('debug')('nuxt:generate')
|
||||
const copy = pify(fs.copy)
|
||||
@ -12,6 +12,8 @@ const remove = pify(fs.remove)
|
||||
const writeFile = pify(fs.writeFile)
|
||||
const mkdirp = pify(fs.mkdirp)
|
||||
|
||||
debug('loaded')
|
||||
|
||||
export default class Generator extends Tapable {
|
||||
constructor (nuxt) {
|
||||
super()
|
||||
@ -31,7 +33,7 @@ export default class Generator extends Tapable {
|
||||
let distNuxtPath = join(distPath, (isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath))
|
||||
|
||||
// Launch build process
|
||||
await this.nuxt.builder.build()
|
||||
await this.nuxt.build()
|
||||
|
||||
// Clean destination folder
|
||||
await remove(distPath)
|
||||
@ -78,7 +80,7 @@ export default class Generator extends Tapable {
|
||||
}
|
||||
|
||||
// Generate only index.html for router.mode = 'hash'
|
||||
let routes = (this.options.router.mode === 'hash') ? ['/'] : this.nuxt.builder.routes
|
||||
let routes = (this.options.router.mode === 'hash') ? ['/'] : flatRoutes(this.options.router.routes)
|
||||
routes = decorateWithPayloads(routes)
|
||||
|
||||
while (routes.length) {
|
||||
|
103
lib/nuxt.js
103
lib/nuxt.js
@ -1,77 +1,29 @@
|
||||
import _ from 'lodash'
|
||||
import fs from 'fs-extra'
|
||||
import { resolve, join } from 'path'
|
||||
import Tapable from 'tappable'
|
||||
import * as Utils from './utils'
|
||||
import Builder from './builder'
|
||||
import Renderer from './renderer'
|
||||
import Generator from './generator'
|
||||
import ModuleContainer from './module-container'
|
||||
import Server from './server'
|
||||
import Defaults from './defaults'
|
||||
import defaults from './defaults'
|
||||
|
||||
export default class Nuxt extends Tapable {
|
||||
constructor (_options = {}) {
|
||||
super()
|
||||
|
||||
// Clone options to prevent unwanted side-effects
|
||||
const options = Object.assign({}, _options)
|
||||
this.options = defaults(_options)
|
||||
|
||||
// Normalize options
|
||||
if (options.loading === true) {
|
||||
delete options.loading
|
||||
}
|
||||
if (options.router && typeof options.router.middleware === 'string') {
|
||||
options.router.middleware = [options.router.middleware]
|
||||
}
|
||||
if (options.router && typeof options.router.base === 'string') {
|
||||
this._routerBaseSpecified = true
|
||||
}
|
||||
if (typeof options.transition === 'string') {
|
||||
options.transition = { name: options.transition }
|
||||
}
|
||||
|
||||
// Apply defaults
|
||||
this.options = _.defaultsDeep(options, Nuxt.Defaults)
|
||||
|
||||
// Resolve dirs
|
||||
this.options.rootDir = (typeof options.rootDir === 'string' && options.rootDir ? options.rootDir : process.cwd())
|
||||
this.options.srcDir = (typeof options.srcDir === 'string' && options.srcDir ? resolve(options.rootDir, options.srcDir) : this.options.rootDir)
|
||||
this.options.buildDir = join(this.options.rootDir, options.buildDir)
|
||||
|
||||
// If store defined, update store options to true
|
||||
if (fs.existsSync(join(this.options.srcDir, 'store'))) {
|
||||
this.options.store = true
|
||||
}
|
||||
|
||||
// If app.html is defined, set the template path to the user template
|
||||
this.options.appTemplatePath = resolve(__dirname, 'views/app.template.html')
|
||||
if (fs.existsSync(join(this.options.srcDir, 'app.html'))) {
|
||||
this.options.appTemplatePath = join(this.options.srcDir, 'app.html')
|
||||
}
|
||||
this.initialized = false
|
||||
this.errorHandler = this.errorHandler.bind(this)
|
||||
|
||||
// Create instance of core components
|
||||
this.moduleContainer = new Nuxt.ModuleContainer(this)
|
||||
this.builder = new Nuxt.Builder(this)
|
||||
this.renderer = new Nuxt.Renderer(this)
|
||||
this.generator = new Nuxt.Generator(this)
|
||||
|
||||
// Backward compatibility
|
||||
this.render = this.renderer.render.bind(this.renderer)
|
||||
this.renderRoute = this.renderer.renderRoute.bind(this.renderer)
|
||||
this.renderAndGetWindow = this.renderer.renderAndGetWindow.bind(this.renderer)
|
||||
this.build = this.builder.build.bind(this.builder)
|
||||
this.generate = this.generator.generate.bind(this.generator)
|
||||
this.dir = options.rootDir
|
||||
this.srcDir = options.srcDir
|
||||
this.buildDir = options.buildDir
|
||||
this.dev = options.dev
|
||||
this.Server = Nuxt.Server
|
||||
this.Utils = Nuxt.Utils
|
||||
|
||||
this.errorHandler = this.errorHandler.bind(this)
|
||||
this._init = this.init().catch(this.errorHandler)
|
||||
this.initialized = false
|
||||
}
|
||||
|
||||
async init () {
|
||||
@ -79,29 +31,56 @@ export default class Nuxt extends Tapable {
|
||||
return this._init
|
||||
}
|
||||
|
||||
// Call to build on dev
|
||||
if (this.options.dev) {
|
||||
this.builder.build().catch(this.errorHandler)
|
||||
}
|
||||
|
||||
// Wait for all components to be ready
|
||||
// Including modules
|
||||
await this.applyPluginsAsync('beforeInit')
|
||||
// Including Build
|
||||
await this.applyPluginsAsync('init')
|
||||
// Extra jobs
|
||||
this.initialized = true
|
||||
this.applyPluginsAsync('afterInit').catch(this.errorHandler)
|
||||
|
||||
this.initialized = true
|
||||
return this
|
||||
}
|
||||
|
||||
get builder () {
|
||||
if (this._builder) {
|
||||
return this._builder
|
||||
}
|
||||
const Builder = require('./builder').default
|
||||
this._builder = new Builder(this)
|
||||
return this._builder
|
||||
}
|
||||
|
||||
get generator () {
|
||||
if (this._generator) {
|
||||
return this._generator
|
||||
}
|
||||
const Generator = require('./generator').default
|
||||
this._generator = new Generator(this)
|
||||
return this._generator
|
||||
}
|
||||
|
||||
build () {
|
||||
return this.builder.build.apply(this.builder, arguments)
|
||||
}
|
||||
|
||||
generate () {
|
||||
return this.generator.generate.apply(this.generator, arguments)
|
||||
}
|
||||
|
||||
errorHandler () {
|
||||
// Global error handler
|
||||
// Silent
|
||||
if (this.options.errorHandler === false) {
|
||||
return
|
||||
}
|
||||
// Custom eventHandler
|
||||
// Custom errorHandler
|
||||
if (typeof this.options.errorHandler === 'function') {
|
||||
return this.options.errorHandler.apply(this, arguments)
|
||||
}
|
||||
// Default
|
||||
// Default handler
|
||||
// eslint-disable-next-line no-console
|
||||
console.error.apply(this, arguments)
|
||||
process.exit(1)
|
||||
@ -131,6 +110,9 @@ export default class Nuxt extends Tapable {
|
||||
if (this.customFilesWatcher) {
|
||||
this.customFilesWatcher.close()
|
||||
}
|
||||
|
||||
promises.push(this.applyPluginsAsync('close'))
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
if (typeof callback === 'function') callback()
|
||||
})
|
||||
@ -138,10 +120,7 @@ export default class Nuxt extends Tapable {
|
||||
}
|
||||
|
||||
// Add core components to Nuxt class
|
||||
Nuxt.Defaults = Defaults
|
||||
Nuxt.Utils = Utils
|
||||
Nuxt.Renderer = Renderer
|
||||
Nuxt.Builder = Builder
|
||||
Nuxt.ModuleContainer = ModuleContainer
|
||||
Nuxt.Server = Server
|
||||
Nuxt.Generator = Generator
|
||||
|
152
lib/renderer.js
152
lib/renderer.js
@ -7,8 +7,9 @@ import pify from 'pify'
|
||||
import serveStatic from 'serve-static'
|
||||
import compression from 'compression'
|
||||
import _ from 'lodash'
|
||||
import { resolve } from 'path'
|
||||
import {readFileSync} from 'fs'
|
||||
import { resolve, join } from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { createBundleRenderer } from 'vue-server-renderer'
|
||||
import { getContext, setAnsiColors, encodeHtml } from './utils'
|
||||
|
||||
const debug = require('debug')('nuxt:render')
|
||||
@ -17,61 +18,149 @@ setAnsiColors(ansiHTML)
|
||||
|
||||
let jsdom = null
|
||||
|
||||
const parseTemplate = templateStr => _.template(templateStr, {
|
||||
interpolate: /{{([\s\S]+?)}}/g
|
||||
})
|
||||
|
||||
export default class Renderer extends Tapable {
|
||||
constructor (nuxt) {
|
||||
super()
|
||||
this.nuxt = nuxt
|
||||
this.options = nuxt.options
|
||||
|
||||
this.nuxt.plugin('init', () => {
|
||||
// For serving static/ files to /
|
||||
this.serveStatic = pify(serveStatic(resolve(this.options.srcDir, 'static'), this.options.render.static))
|
||||
// Will be loaded by createRenderer
|
||||
this.bundleRenderer = null
|
||||
this.renderToStream = null
|
||||
this.renderToString = null
|
||||
|
||||
// For serving .nuxt/dist/ files (only when build.publicPath is not an URL)
|
||||
this.serveStaticNuxt = pify(serveStatic(resolve(this.options.buildDir, 'dist'), {
|
||||
maxAge: (this.options.dev ? 0 : '1y') // 1 year in production
|
||||
}))
|
||||
if (nuxt.initialized) {
|
||||
// If nuxt already initialized
|
||||
this._init = this.init().catch(this.nuxt.errorHandler)
|
||||
} else {
|
||||
// Wait for hook
|
||||
this.nuxt.plugin('init', this.init.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
// gzip middleware for production
|
||||
if (!this.options.dev && this.options.render.gzip) {
|
||||
this.gzipMiddleware = pify(compression(this.options.render.gzip))
|
||||
async init () {
|
||||
if (this._init) {
|
||||
return this._init
|
||||
}
|
||||
|
||||
// Renderer runtime resources
|
||||
this.resources = {
|
||||
clientManifest: null,
|
||||
serverBundle: null,
|
||||
appTemplate: null,
|
||||
errorTemplate: parseTemplate(fs.readFileSync(resolve(__dirname, 'views', 'error.html'), 'utf8'))
|
||||
}
|
||||
|
||||
// For serving static/ files to /
|
||||
this.serveStatic = pify(serveStatic(resolve(this.options.srcDir, 'static'), this.options.render.static))
|
||||
|
||||
// For serving .nuxt/dist/ files (only when build.publicPath is not an URL)
|
||||
this.serveStaticNuxt = pify(serveStatic(resolve(this.options.buildDir, 'dist'), {
|
||||
maxAge: (this.options.dev ? 0 : '1y') // 1 year in production
|
||||
}))
|
||||
|
||||
// gzip middleware for production
|
||||
if (!this.options.dev && this.options.render.gzip) {
|
||||
this.gzipMiddleware = pify(compression(this.options.render.gzip))
|
||||
}
|
||||
|
||||
// Try to load resources from fs
|
||||
return this.loadResources()
|
||||
}
|
||||
|
||||
async loadResources (_fs = fs, distPath) {
|
||||
distPath = distPath || resolve(this.options.buildDir, 'dist')
|
||||
|
||||
const resourceMap = {
|
||||
clientManifest: {
|
||||
path: join(distPath, 'client-manifest.json'),
|
||||
transform: JSON.parse
|
||||
},
|
||||
serverBundle: {
|
||||
path: join(distPath, 'server-bundle.json'),
|
||||
transform: JSON.parse
|
||||
},
|
||||
appTemplate: {
|
||||
path: join(distPath, 'index.html'),
|
||||
transform: parseTemplate
|
||||
}
|
||||
}
|
||||
|
||||
// Error template
|
||||
this.errorTemplate = _.template(readFileSync(resolve(__dirname, 'views', 'error.html'), 'utf8'), {
|
||||
interpolate: /{{([\s\S]+?)}}/g
|
||||
})
|
||||
Object.keys(resourceMap).forEach(resourceKey => {
|
||||
let { path, transform } = resourceMap[resourceKey]
|
||||
let data
|
||||
if (_fs.existsSync(path)) {
|
||||
data = _fs.readFileSync(path, 'utf8')
|
||||
if (typeof transform === 'function') {
|
||||
data = transform(data)
|
||||
}
|
||||
}
|
||||
if (data) {
|
||||
this.resources[resourceKey] = data
|
||||
}
|
||||
})
|
||||
|
||||
this.createRenderer()
|
||||
}
|
||||
|
||||
createRenderer () {
|
||||
// If resources are not yet provided
|
||||
if (!this.resources.serverBundle || !this.resources.clientManifest) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create bundle renderer for SSR
|
||||
this.bundleRenderer = createBundleRenderer(this.resources.serverBundle, Object.assign({
|
||||
clientManifest: this.resources.clientManifest,
|
||||
runInNewContext: false,
|
||||
basedir: this.options.rootDir
|
||||
}, this.options.render.ssr))
|
||||
|
||||
// Promisify renderToString
|
||||
this.bundleRenderer.renderToString = pify(this.bundleRenderer.renderToString)
|
||||
|
||||
debug('ready')
|
||||
}
|
||||
|
||||
async render (req, res) {
|
||||
/* istanbul ignore if */
|
||||
if (!this.nuxt.builder.renderer || !this.nuxt.builder.appTemplate) {
|
||||
if (!this.bundleRenderer || !this.resources.appTemplate) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(this.render(req, res))
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
// Get context
|
||||
const context = getContext(req, res)
|
||||
res.statusCode = 200
|
||||
|
||||
try {
|
||||
if (this.options.dev) {
|
||||
// Call webpack middleware only in development
|
||||
// Call webpack middleware only in development
|
||||
if (this.options.dev && this.nuxt.builder && this.nuxt.builder.webpackDevMiddleware) {
|
||||
await this.nuxt.builder.webpackDevMiddleware(req, res)
|
||||
await this.nuxt.builder.webpackHotMiddleware(req, res)
|
||||
}
|
||||
if (!this.options.dev && this.options.render.gzip) {
|
||||
|
||||
// Gzip middleware for production
|
||||
if (this.gzipMiddleware) {
|
||||
await this.gzipMiddleware(req, res)
|
||||
}
|
||||
|
||||
// If base in req.url, remove it for the middleware and vue-router
|
||||
if (this.options.router.base !== '/' && req.url.indexOf(this.options.router.base) === 0) {
|
||||
// Compatibility with base url for dev server
|
||||
req.url = req.url.replace(this.options.router.base, '/')
|
||||
}
|
||||
|
||||
// Serve static/ files
|
||||
await this.serveStatic(req, res)
|
||||
|
||||
// Serve .nuxt/dist/ files (only for production)
|
||||
if (!this.options.dev && req.url.indexOf(this.options.build.publicPath) === 0) {
|
||||
const url = req.url
|
||||
@ -80,17 +169,22 @@ export default class Renderer extends Tapable {
|
||||
/* istanbul ignore next */
|
||||
req.url = url
|
||||
}
|
||||
|
||||
if (this.options.dev && req.url.indexOf(this.options.build.publicPath) === 0 && req.url.includes('.hot-update.json')) {
|
||||
res.statusCode = 404
|
||||
return res.end()
|
||||
}
|
||||
|
||||
const { html, error, redirected, resourceHints } = await this.renderRoute(req.url, context)
|
||||
|
||||
if (redirected) {
|
||||
return html
|
||||
}
|
||||
|
||||
if (error) {
|
||||
res.statusCode = context.nuxt.error.statusCode || 500
|
||||
}
|
||||
|
||||
// ETag header
|
||||
if (!error && this.options.render.etag) {
|
||||
const etag = generateETag(html, this.options.render.etag)
|
||||
@ -101,6 +195,7 @@ export default class Renderer extends Tapable {
|
||||
}
|
||||
res.setHeader('ETag', etag)
|
||||
}
|
||||
|
||||
// HTTP2 push headers
|
||||
if (!error && this.options.render.http2.push) {
|
||||
// Parse resourceHints to extract HTTP.2 prefetch/push headers
|
||||
@ -118,6 +213,8 @@ export default class Renderer extends Tapable {
|
||||
// https://blog.cloudflare.com/http-2-server-push-with-multiple-assets-per-link-header
|
||||
res.setHeader('Link', pushAssets.join(','))
|
||||
}
|
||||
|
||||
// Send response
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
res.setHeader('Content-Length', Buffer.byteLength(html))
|
||||
res.end(html, 'utf8')
|
||||
@ -127,11 +224,13 @@ export default class Renderer extends Tapable {
|
||||
console.error(err) // eslint-disable-line no-console
|
||||
return err
|
||||
}
|
||||
const html = this.errorTemplate({
|
||||
// Render error template
|
||||
const html = this.resources.errorTemplate({
|
||||
/* istanbul ignore if */
|
||||
error: err,
|
||||
stack: ansiHTML(encodeHtml(err.stack))
|
||||
})
|
||||
// Send response
|
||||
res.statusCode = 500
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
res.setHeader('Content-Length', Buffer.byteLength(html))
|
||||
@ -143,29 +242,34 @@ export default class Renderer extends Tapable {
|
||||
async renderRoute (url, context = {}) {
|
||||
// Log rendered url
|
||||
debug(`Rendering url ${url}`)
|
||||
|
||||
// Add url and isSever to the context
|
||||
context.url = url
|
||||
context.isServer = true
|
||||
|
||||
// Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)
|
||||
let APP = await this.nuxt.builder.renderToString(context)
|
||||
let APP = await this.bundleRenderer.renderToString(context)
|
||||
|
||||
if (!context.nuxt.serverRendered) {
|
||||
APP = '<div id="__nuxt"></div>'
|
||||
}
|
||||
const m = context.meta.inject()
|
||||
let HEAD = m.meta.text() + m.title.text() + m.link.text() + m.style.text() + m.script.text() + m.noscript.text()
|
||||
if (this._routerBaseSpecified) {
|
||||
if (this.options._routerBaseSpecified) {
|
||||
HEAD += `<base href="${this.options.router.base}">`
|
||||
}
|
||||
const resourceHints = context.renderResourceHints()
|
||||
HEAD += resourceHints + context.renderStyles()
|
||||
APP += `<script type="text/javascript">window.__NUXT__=${serialize(context.nuxt, { isJSON: true })}</script>`
|
||||
APP += context.renderScripts()
|
||||
const html = this.nuxt.builder.appTemplate({
|
||||
|
||||
const html = this.resources.appTemplate({
|
||||
HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),
|
||||
BODY_ATTRS: m.bodyAttrs.text(),
|
||||
HEAD,
|
||||
APP
|
||||
})
|
||||
|
||||
return {
|
||||
html,
|
||||
resourceHints,
|
||||
|
115
lib/utils.js
115
lib/utils.js
@ -97,3 +97,118 @@ export function r () {
|
||||
args = args.map(normalize)
|
||||
return wp(resolve.apply(null, args))
|
||||
}
|
||||
|
||||
export function flatRoutes (router, path = '', routes = []) {
|
||||
router.forEach((r) => {
|
||||
if (!r.path.includes(':') && !r.path.includes('*')) {
|
||||
if (r.children) {
|
||||
flatRoutes(r.children, path + r.path + '/', routes)
|
||||
} else {
|
||||
routes.push((r.path === '' && path[path.length - 1] === '/' ? path.slice(0, -1) : path) + r.path)
|
||||
}
|
||||
}
|
||||
})
|
||||
return routes
|
||||
}
|
||||
|
||||
export function cleanChildrenRoutes (routes, isChild = false) {
|
||||
let start = -1
|
||||
let routesIndex = []
|
||||
routes.forEach((route) => {
|
||||
if (/-index$/.test(route.name) || route.name === 'index') {
|
||||
// Save indexOf 'index' key in name
|
||||
let res = route.name.split('-')
|
||||
let s = res.indexOf('index')
|
||||
start = (start === -1 || s < start) ? s : start
|
||||
routesIndex.push(res)
|
||||
}
|
||||
})
|
||||
routes.forEach((route) => {
|
||||
route.path = (isChild) ? route.path.replace('/', '') : route.path
|
||||
if (route.path.indexOf('?') > -1) {
|
||||
let names = route.name.split('-')
|
||||
let paths = route.path.split('/')
|
||||
if (!isChild) {
|
||||
paths.shift()
|
||||
} // clean first / for parents
|
||||
routesIndex.forEach((r) => {
|
||||
let i = r.indexOf('index') - start // children names
|
||||
if (i < paths.length) {
|
||||
for (let a = 0; a <= i; a++) {
|
||||
if (a === i) {
|
||||
paths[a] = paths[a].replace('?', '')
|
||||
}
|
||||
if (a < i && names[a] !== r[a]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
route.path = (isChild ? '' : '/') + paths.join('/')
|
||||
}
|
||||
route.name = route.name.replace(/-index$/, '')
|
||||
if (route.children) {
|
||||
if (route.children.find((child) => child.path === '')) {
|
||||
delete route.name
|
||||
}
|
||||
route.children = cleanChildrenRoutes(route.children, true)
|
||||
}
|
||||
})
|
||||
return routes
|
||||
}
|
||||
|
||||
export function createRoutes (files, srcDir) {
|
||||
let routes = []
|
||||
files.forEach((file) => {
|
||||
let keys = file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/{2,}/g, '/').split('/').slice(1)
|
||||
let route = { name: '', path: '', component: r(srcDir, file) }
|
||||
let parent = routes
|
||||
keys.forEach((key, i) => {
|
||||
route.name = route.name ? route.name + '-' + key.replace('_', '') : key.replace('_', '')
|
||||
route.name += (key === '_') ? 'all' : ''
|
||||
let child = _.find(parent, { name: route.name })
|
||||
if (child) {
|
||||
if (!child.children) {
|
||||
child.children = []
|
||||
}
|
||||
parent = child.children
|
||||
route.path = ''
|
||||
} else {
|
||||
if (key === 'index' && (i + 1) === keys.length) {
|
||||
route.path += (i > 0 ? '' : '/')
|
||||
} else {
|
||||
route.path += '/' + (key === '_' ? '*' : key.replace('_', ':'))
|
||||
if (key !== '_' && key.indexOf('_') !== -1) {
|
||||
route.path += '?'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// Order Routes path
|
||||
parent.push(route)
|
||||
parent.sort((a, b) => {
|
||||
if (!a.path.length || a.path === '/') {
|
||||
return -1
|
||||
}
|
||||
if (!b.path.length || b.path === '/') {
|
||||
return 1
|
||||
}
|
||||
let res = 0
|
||||
let _a = a.path.split('/')
|
||||
let _b = b.path.split('/')
|
||||
for (let i = 0; i < _a.length; i++) {
|
||||
if (res !== 0) {
|
||||
break
|
||||
}
|
||||
let y = (_a[i].indexOf('*') > -1) ? 2 : (_a[i].indexOf(':') > -1 ? 1 : 0)
|
||||
let z = (_b[i].indexOf('*') > -1) ? 2 : (_b[i].indexOf(':') > -1 ? 1 : 0)
|
||||
res = y - z
|
||||
if (i === _b.length - 1 && res === 0) {
|
||||
res = 1
|
||||
}
|
||||
}
|
||||
return res === 0 ? -1 : res
|
||||
})
|
||||
})
|
||||
return cleanChildrenRoutes(routes)
|
||||
}
|
||||
|
@ -84,7 +84,6 @@
|
||||
"offline-plugin": "^4.8.1",
|
||||
"opencollective": "^1.0.3",
|
||||
"pify": "^3.0.0",
|
||||
"post-compile-webpack-plugin": "^0.1.1",
|
||||
"preload-webpack-plugin": "^1.2.2",
|
||||
"progress-bar-webpack-plugin": "^1.9.3",
|
||||
"script-ext-html-webpack-plugin": "^1.8.1",
|
||||
|
@ -18,7 +18,7 @@ test.before('Init Nuxt.js', async t => {
|
||||
}
|
||||
nuxt = new Nuxt(options)
|
||||
await nuxt.build()
|
||||
server = new nuxt.Server(nuxt)
|
||||
server = new Nuxt.Server(nuxt)
|
||||
server.listen(port, 'localhost')
|
||||
})
|
||||
|
||||
|
@ -15,7 +15,7 @@ test.before('Init Nuxt.js', async t => {
|
||||
}
|
||||
nuxt = new Nuxt(options)
|
||||
await nuxt.build()
|
||||
server = new nuxt.Server(nuxt)
|
||||
server = new Nuxt.Server(nuxt)
|
||||
server.listen(port, 'localhost')
|
||||
})
|
||||
|
||||
|
@ -15,7 +15,7 @@ test.before('Init Nuxt.js', async t => {
|
||||
}
|
||||
nuxt = new Nuxt(options)
|
||||
await nuxt.build()
|
||||
server = new nuxt.Server(nuxt)
|
||||
server = new Nuxt.Server(nuxt)
|
||||
server.listen(port, 'localhost')
|
||||
})
|
||||
|
||||
|
@ -17,7 +17,7 @@ test.before('Init Nuxt.js', async t => {
|
||||
config.dev = false
|
||||
nuxt = new Nuxt(config)
|
||||
await nuxt.build()
|
||||
server = new nuxt.Server(nuxt)
|
||||
server = new Nuxt.Server(nuxt)
|
||||
server.listen(port, 'localhost')
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user