diff --git a/lib/build.js b/lib/build.js index 01ef111755..cfff63893a 100644 --- a/lib/build.js +++ b/lib/build.js @@ -9,8 +9,8 @@ import webpack from 'webpack' import PostCompilePlugin from 'post-compile-webpack-plugin' import serialize from 'serialize-javascript' import { createBundleRenderer } from 'vue-server-renderer' -import { join, resolve, sep } from 'path' -import { isUrl } from './utils' +import { join, resolve, basename, dirname } from 'path' +import { isUrl, r } from './utils' import clientWebpackConfig from './webpack/client.config.js' import serverWebpackConfig from './webpack/server.config.js' const debug = require('debug')('nuxt:build') @@ -20,27 +20,9 @@ const utimes = pify(fs.utimes) const writeFile = pify(fs.writeFile) const mkdirp = pify(fs.mkdirp) const glob = pify(require('glob')) -const reqSep = /\//g -const sysSep = _.escapeRegExp(sep) -const normalize = string => string.replace(reqSep, sysSep) -const wp = function (p) { - /* istanbul ignore if */ - if (/^win/.test(process.platform)) { - p = p.replace(/\\/g, '\\\\') - } - return p -} -const r = function () { - let args = Array.from(arguments) - if (_.last(args).includes('~')) { - return wp(_.last(args)) - } - args = args.map(normalize) - return wp(resolve.apply(null, args)) -} + let webpackStats = 'none' -// force green color -debug.color = 2 +debug.color = 2 // force green color const defaults = { analyze: false, @@ -163,17 +145,8 @@ function addAppTemplate () { } async function generateRoutesAndFiles () { - debug('Generating routes...') - // Layouts - let layouts = {} - const layoutsFiles = await glob('layouts/*.vue', { cwd: this.srcDir }) - layoutsFiles.forEach((file) => { - let name = file.split('/').slice(-1)[0].replace('.vue', '') - if (name === 'error') return - layouts[name] = r(this.srcDir, file) - }) - const files = await glob('pages/**/*.vue', { cwd: this.srcDir }) - // Interpret and move template files to .nuxt/ + debug('Generating files...') + // -- Templates -- let templatesFiles = [ 'App.vue', 'client.js', @@ -188,9 +161,8 @@ async function generateRoutesAndFiles () { 'components/nuxt-link.js', 'components/nuxt.vue' ] - this.options.store = fs.existsSync(join(this.srcDir, 'store')) - let templateVars = { - nuxt: this.options, + const templateVars = { + options: this.options, uniqBy: _.uniqBy, isDev: this.dev, router: { @@ -203,7 +175,7 @@ async function generateRoutesAndFiles () { env: this.options.env, head: this.options.head, middleware: fs.existsSync(join(this.srcDir, 'middleware')), - store: this.options.store, + store: this.options.store || fs.existsSync(join(this.srcDir, 'store')), css: this.options.css, plugins: this.options.plugins.map((p) => { if (typeof p === 'string') { @@ -212,80 +184,107 @@ async function generateRoutesAndFiles () { return { src: r(this.srcDir, p.src), ssr: (p.ssr !== false), injectAs: (p.injectAs || false) } }), appPath: './App.vue', - layouts: layouts, + layouts: Object.assign({}, this.options.layouts), loading: (typeof this.options.loading === 'string' ? r(this.srcDir, this.options.loading) : this.options.loading), transition: this.options.transition, components: { - ErrorPage: null + ErrorPage: this.options.ErrorPage ? r(this.options.ErrorPage) : null } } + + // -- Layouts -- + if (fs.existsSync(resolve(this.srcDir, 'layouts'))) { + const layoutsFiles = await glob('layouts/*.vue', {cwd: this.srcDir}) + layoutsFiles.forEach((file) => { + let name = file.split('/').slice(-1)[0].replace('.vue', '') + if (name === 'error') return + templateVars.layouts[name] = r(this.srcDir, file) + }) + if (layoutsFiles.includes('layouts/error.vue')) { + templateVars.components.ErrorPage = r(this.srcDir, 'layouts/error.vue') + } + } + // If no default layout, create its folder and add the default folder + if (!templateVars.layouts.default) { + await mkdirp(r(this.dir, '.nuxt/layouts')) + templatesFiles.push('layouts/default.vue') + templateVars.layouts.default = r(__dirname, 'app', 'layouts', 'default.vue') + } + + // -- Routes -- + debug('Generating routes...') // Format routes for the lib/app/router.js template - templateVars.router.routes = createRoutes(files, this.srcDir) + if (fs.existsSync(resolve(this.srcDir, 'pages'))) { + const files = await glob('pages/**/*.vue', {cwd: this.srcDir}) + templateVars.router.routes = createRoutes(files, this.srcDir) + } if (typeof this.options.router.extendRoutes === 'function') { // let the user extend the routes this.options.router.extendRoutes.call(this, templateVars.router.routes, r) } - // Routes for Generate command + // Routes for generate command this.routes = flatRoutes(templateVars.router.routes) - debug('Generating files...') - if (layoutsFiles.includes('layouts/error.vue')) { - templateVars.components.ErrorPage = r(this.srcDir, 'layouts/error.vue') - } - // If no default layout, create its folder and add the default folder - if (!layouts.default) { - await mkdirp(r(this.dir, '.nuxt/layouts')) - templatesFiles.push('layouts/default.vue') - layouts.default = r(__dirname, 'app', 'layouts', 'default.vue') - } + + // -- Store -- // Add store if needed if (this.options.store) { templatesFiles.push('store.js') } + // Resolve template files + const customTemplateFiles = this.options.build.templates.map(t => t.dst || basename(t.src || t)) templatesFiles = templatesFiles.map(file => { - // Allow override anything using a file with same name in srcDir/app + // Allow override templates using a file with same name in ${srcDir}/app const customPath = r(this.srcDir, 'app', file) const customFileExists = fs.existsSync(customPath) + // Skip if custom file was already provided in build.templates[] + if (customTemplateFiles.indexOf(file) !== -1 && !customFileExists) { + return + } return { src: customFileExists ? customPath : r(__dirname, 'app', file), dst: file, custom: customFileExists } - }) - // Add external template files (used in modules) - if (Array.isArray(this.options.build.templates)) { - templatesFiles = templatesFiles.concat(this.options.build.templates.map(t => { - return Object.assign({custom: true}, t) - })) - } - let moveTemplates = templatesFiles.map(({src, dst, options, custom}) => { + }).filter(i => !!i) + + // -- Custom templates -- + // Add custom template files + templatesFiles = templatesFiles.concat(this.options.build.templates.map(t => { + return Object.assign({ + src: r(this.dir, t.src || t), + dst: t.dst || basename(t.src || t), + custom: true + }, t) + })) + + // Interpret and move template files to .nuxt/ + return templatesFiles.map(async ({src, dst, options, custom}) => { // Add template to watchers this.options.build.watch.push(src) // Render template to dst - return readFile(src, 'utf8') - .then((fileContent) => { - const template = _.template(fileContent, { - imports: { - serialize, - hash - } - }) - const content = template(Object.assign({}, templateVars, { - options: options || {}, - custom, - src, - dst - })) - const path = r(this.dir, '.nuxt', dst) - return writeFile(path, content, 'utf8') - .then(() => { - // Fix webpack loop (https://github.com/webpack/watchpack/issues/25#issuecomment-287789288) - const dateFS = Date.now() / 1000 - 30 - return utimes(path, dateFS, dateFS) - }) + const fileContent = await readFile(src, 'utf8') + const template = _.template(fileContent, { + imports: { + serialize, + hash + } }) + const content = template(Object.assign({}, templateVars, { + options: options || {}, + custom, + src, + dst + })) + const path = r(this.dir, '.nuxt', dst) + // Ensure parent dir exits + await mkdirp(dirname(path)) + // Write file + await writeFile(path, content, 'utf8') + // Fix webpack loop (https://github.com/webpack/watchpack/issues/25#issuecomment-287789288) + const dateFS = Date.now() / 1000 - 30 + return utimes(path, dateFS, dateFS) }) - await moveTemplates } function createRoutes (files, srcDir) { diff --git a/lib/nuxt.js b/lib/nuxt.js index a7f04242ae..dbc5a7c4d2 100644 --- a/lib/nuxt.js +++ b/lib/nuxt.js @@ -27,7 +27,9 @@ class Nuxt { plugins: [], css: [], modules: [], + layouts: {}, serverMiddlewares: [], + ErrorPage: null, cache: false, loading: { color: 'black', @@ -148,7 +150,7 @@ class Nuxt { if (this.customFilesWatcher) { this.customFilesWatcher.close() } - Promise.all(promises).then(() => { + return Promise.all(promises).then(() => { if (typeof callback === 'function') callback() }) }