index.ts 6.3 KB
Newer Older
1 2
import path from 'path'
import fs from 'fs'
T
Tim Neutkens 已提交
3
import nanoid from 'next/dist/compiled/nanoid/index.js'
4
import loadConfig from 'next-server/next-config'
5
import { PHASE_PRODUCTION_BUILD } from 'next-server/constants'
6
import getBaseWebpackConfig from './webpack-config'
7 8 9 10
import { generateBuildId } from './generate-build-id'
import { writeBuildId } from './write-build-id'
import { isWriteable } from './is-writeable'
import { runCompiler, CompilerResult } from './compiler'
J
Joe Haddad 已提交
11
import { recursiveReadDir } from '../lib/recursive-readdir'
12 13 14
import { createPagesMapping, createEntrypoints } from './entries'
import formatWebpackMessages from '../client/dev-error-overlay/format-webpack-messages'
import chalk from 'chalk'
15

16 17 18 19
function collectPages(
  directory: string,
  pageExtensions: string[]
): Promise<string[]> {
J
Joe Haddad 已提交
20 21 22 23
  return recursiveReadDir(
    directory,
    new RegExp(`\\.(?:${pageExtensions.join('|')})$`)
  )
T
Tim Neutkens 已提交
24 25
}

L
Luc 已提交
26 27
function printTreeView(list: string[]) {
  list
28
    .sort((a, b) => (a > b ? 1 : -1))
L
Luc 已提交
29
    .forEach((item, i) => {
30 31 32 33 34 35 36 37
      const corner =
        i === 0
          ? list.length === 1
            ? ''
            : ''
          : i === list.length - 1
          ? ''
          : ''
L
Luc 已提交
38 39 40 41 42 43
      console.log(` \x1b[90m${corner}\x1b[39m ${item}`)
    })

  console.log()
}

44 45 46 47 48 49 50 51 52 53 54
function flatten<T>(arr: T[][]): T[] {
  return arr.reduce((acc, val) => acc.concat(val), [] as T[])
}

function getPossibleFiles(pageExtensions: string[], pages: string[]) {
  const res = pages.map(page =>
    [page].concat(pageExtensions.map(e => `${page}.${e}`))
  )
  return flatten<string>(res)
}

J
Joe Haddad 已提交
55 56 57
export default async function build(
  dir: string,
  conf = null,
58
  { pages: _pages = [] as string[] } = {}
J
Joe Haddad 已提交
59
): Promise<void> {
60 61 62 63
  if (!(await isWriteable(dir))) {
    throw new Error(
      '> Build directory is not writeable. https://err.sh/zeit/next.js/build-dir-not-writeable'
    )
64 65
  }

66 67 68 69
  const debug =
    process.env.__NEXT_BUILDER_EXPERIMENTAL_DEBUG === 'true' ||
    process.env.__NEXT_BUILDER_EXPERIMENTAL_DEBUG === '1'

J
Joe Haddad 已提交
70 71 72 73 74
  console.log(
    debug
      ? 'Creating a development build ...'
      : 'Creating an optimized production build ...'
  )
75 76
  console.log()

77
  const config = loadConfig(PHASE_PRODUCTION_BUILD, dir, conf)
78 79 80
  const buildId = debug
    ? 'unoptimized-build'
    : await generateBuildId(config.generateBuildId, nanoid)
81 82 83 84 85 86 87 88 89
  const distDir = path.join(dir, config.distDir)
  const pagesDir = path.join(dir, 'pages')

  const pages =
    Array.isArray(_pages) && _pages.length
      ? _pages
      : process.env.__NEXT_BUILDER_EXPERIMENTAL_PAGE
      ? [process.env.__NEXT_BUILDER_EXPERIMENTAL_PAGE]
      : []
T
Tim Neutkens 已提交
90

91 92
  const __selectivePageBuilding = pages ? Boolean(pages.length) : false

93 94 95 96 97
  if (__selectivePageBuilding && config.target !== 'serverless') {
    throw new Error(
      'Cannot use selective page building without the serverless target.'
    )
  }
98

99 100
  let pagePaths
  if (__selectivePageBuilding && pages[0] !== '**') {
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    const explodedPages = flatten<string>(pages.map(p => p.split(','))).map(
      p => {
        let resolvedPage: string | undefined
        if (path.isAbsolute(p)) {
          resolvedPage = getPossibleFiles(config.pageExtensions, [
            path.join(pagesDir, p),
            p,
          ]).find(f => fs.existsSync(f))
        } else {
          resolvedPage = getPossibleFiles(config.pageExtensions, [
            path.join(pagesDir, p),
            path.join(dir, p),
          ]).find(f => fs.existsSync(f))
        }
        return { original: p, resolved: resolvedPage || null }
      }
    )

    const missingPage = explodedPages.find(({ resolved }) => !resolved)
    if (missingPage) {
      throw new Error(`Unable to identify page: ${missingPage.original}`)
    }
    pagePaths = explodedPages.map(
      page => '/' + path.relative(pagesDir, page.resolved!)
J
Joe Haddad 已提交
125 126 127 128 129 130 131 132 133
    )
  } else {
    pagePaths = await collectPages(pagesDir, config.pageExtensions)
  }
  const mappedPages = createPagesMapping(pagePaths, config.pageExtensions)
  const entrypoints = createEntrypoints(
    mappedPages,
    config.target,
    buildId,
134
    __selectivePageBuilding,
J
Joe Haddad 已提交
135 136
    config
  )
137 138
  const configs = await Promise.all([
    getBaseWebpackConfig(dir, {
139
      debug,
140 141 142 143 144
      buildId,
      isServer: false,
      config,
      target: config.target,
      entrypoints: entrypoints.client,
145
      __selectivePageBuilding,
146
    }),
147
    getBaseWebpackConfig(dir, {
148
      debug,
149 150 151 152 153
      buildId,
      isServer: true,
      config,
      target: config.target,
      entrypoints: entrypoints.server,
154
      __selectivePageBuilding,
155
    }),
156
  ])
157

158
  let result: CompilerResult = { warnings: [], errors: [] }
T
Tim Neutkens 已提交
159
  if (config.target === 'serverless') {
160 161 162 163
    if (config.publicRuntimeConfig)
      throw new Error(
        'Cannot use publicRuntimeConfig with target=serverless https://err.sh/zeit/next.js/serverless-publicRuntimeConfig'
      )
164

165
    const clientResult = await runCompiler(configs[0])
166
    // Fail build if clientResult contains errors
167 168 169 170 171
    if (clientResult.errors.length > 0) {
      result = {
        warnings: [...clientResult.warnings],
        errors: [...clientResult.errors],
      }
172
    } else {
173
      const serverResult = await runCompiler(configs[1])
174 175 176 177
      result = {
        warnings: [...clientResult.warnings, ...serverResult.warnings],
        errors: [...clientResult.errors, ...serverResult.errors],
      }
178
    }
179 180 181 182
  } else {
    result = await runCompiler(configs)
  }

183
  result = formatWebpackMessages(result)
184 185

  if (result.errors.length > 0) {
186 187 188 189 190
    // Only keep the first error. Others are often indicative
    // of the same problem, but confuse the reader with noise.
    if (result.errors.length > 1) {
      result.errors.length = 1
    }
191
    const error = result.errors.join('\n\n')
192 193

    console.error(chalk.red('Failed to compile.\n'))
194
    console.error(error)
195
    console.error()
196 197

    if (error.indexOf('private-next-pages') > -1) {
J
Joe Haddad 已提交
198 199 200
      throw new Error(
        '> webpack config.resolve.alias was incorrectly overriden. https://err.sh/zeit/next.js/invalid-resolve-alias'
      )
201
    }
202
    throw new Error('> Build failed because of webpack errors')
203 204 205 206 207 208
  } else if (result.warnings.length > 0) {
    console.warn(chalk.yellow('Compiled with warnings.\n'))
    console.warn(result.warnings.join('\n\n'))
    console.warn()
  } else {
    console.log(chalk.green('Compiled successfully.\n'))
209
  }
L
Luc 已提交
210

J
Joe Haddad 已提交
211
  printTreeView(Object.keys(mappedPages))
L
Luc 已提交
212

213
  await writeBuildId(distDir, buildId, __selectivePageBuilding)
214
}