Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental: Serverless Trace target #8246

Merged
merged 14 commits into from
Aug 5, 2019
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"execa": "2.0.3",
"express": "4.17.0",
"faunadb": "2.6.1",
"firebase": "6.3.4",
"fs-extra": "7.0.1",
"get-port": "5.0.0",
"isomorphic-unfetch": "3.0.0",
Expand Down
6 changes: 4 additions & 2 deletions packages/next-server/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import findUp from 'find-up'
import { CONFIG_FILE } from '../lib/constants'
import { execOnce } from '../lib/utils'

const targets = ['server', 'serverless']
const targets = ['server', 'serverless', 'experimental-serverless-trace']

const defaultConfig: { [key: string]: any } = {
env: [],
Expand Down Expand Up @@ -120,10 +120,12 @@ export default function loadConfig(
}

if (
userConfig.target === 'serverless' &&
userConfig.target &&
userConfig.target !== 'server' &&
userConfig.publicRuntimeConfig &&
Object.keys(userConfig.publicRuntimeConfig).length !== 0
) {
// TODO: change error message tone to "Only compatible with [fat] server mode"
throw new Error(
'Cannot use publicRuntimeConfig with target=serverless https://err.sh/zeit/next.js/serverless-publicRuntimeConfig'
)
Expand Down
25 changes: 15 additions & 10 deletions packages/next-server/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import compression from 'compression'

import {
BUILD_ID_FILE,
BUILD_MANIFEST,
CLIENT_PUBLIC_FILES_PATH,
CLIENT_STATIC_FILES_PATH,
CLIENT_STATIC_FILES_RUNTIME,
Expand Down Expand Up @@ -98,7 +97,9 @@ export default class Server {
this.publicDir = join(this.dir, CLIENT_PUBLIC_FILES_PATH)
this.pagesManifest = join(
this.distDir,
this.nextConfig.target || 'server',
this.nextConfig.target === 'server'
? SERVER_DIRECTORY
: SERVERLESS_DIRECTORY,
PAGES_MANIFEST
)

Expand Down Expand Up @@ -131,7 +132,7 @@ export default class Server {
this.renderOpts.runtimeConfig = publicRuntimeConfig
}

if (compress && this.nextConfig.target !== 'serverless') {
if (compress && this.nextConfig.target === 'server') {
this.compression = compression() as Middleware
}

Expand Down Expand Up @@ -319,7 +320,7 @@ export default class Server {
return this.render404(req, res)
}

if (!this.renderOpts.dev && this.nextConfig.target === 'serverless') {
if (!this.renderOpts.dev && this._isLikeServerless) {
const mod = require(resolverFunction)
if (typeof mod.default === 'function') {
return mod.default(req, res)
Expand All @@ -342,7 +343,7 @@ export default class Server {
return getPagePath(
pathname,
this.distDir,
this.nextConfig.target === 'serverless',
this._isLikeServerless,
this.renderOpts.dev
)
}
Expand All @@ -352,9 +353,7 @@ export default class Server {
const publicFiles = recursiveReadDirSync(this.publicDir)
const serverBuildPath = join(
this.distDir,
this.nextConfig.target === 'serverless'
? SERVERLESS_DIRECTORY
: SERVER_DIRECTORY
this._isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY
)
const pagesManifest = require(join(serverBuildPath, PAGES_MANIFEST))

Expand Down Expand Up @@ -458,8 +457,7 @@ export default class Server {
pathname: string,
query: ParsedUrlQuery = {}
) {
const serverless =
!this.renderOpts.dev && this.nextConfig.target === 'serverless'
const serverless = !this.renderOpts.dev && this._isLikeServerless
// try serving a static AMP version first
if (query.amp) {
try {
Expand Down Expand Up @@ -708,4 +706,11 @@ export default class Server {
throw err
}
}

private get _isLikeServerless(): boolean {
const isServerless = this.nextConfig.target === 'serverless'
const isServerlessTrace =
this.nextConfig.target === 'experimental-serverless-trace'
return isServerless || isServerlessTrace
Timer marked this conversation as resolved.
Show resolved Hide resolved
}
}
9 changes: 6 additions & 3 deletions packages/next/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type Entrypoints = {

export function createEntrypoints(
pages: PagesMapping,
target: 'server' | 'serverless',
target: 'server' | 'serverless' | 'experimental-serverless-trace',
buildId: string,
dynamicBuildId: boolean,
config: any
Expand All @@ -72,7 +72,10 @@ export function createEntrypoints(

const bundlePath = join('static', buildId, 'pages', bundleFile)

if (isApiRoute && target === 'serverless') {
if (
isApiRoute &&
(target === 'serverless' || target === 'experimental-serverless-trace')
) {
const serverlessLoaderOptions: ServerlessLoaderQuery = {
page,
absolutePagePath,
Expand All @@ -84,7 +87,7 @@ export function createEntrypoints(
} else if (isApiRoute || target === 'server') {
server[bundlePath] = [absolutePagePath]
} else if (
target === 'serverless' &&
(target === 'serverless' || target === 'experimental-serverless-trace') &&
Timer marked this conversation as resolved.
Show resolved Hide resolved
page !== '/_app' &&
page !== '/_document'
) {
Expand Down
27 changes: 14 additions & 13 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,15 @@ export default async function build(dir: string, conf = null): Promise<void> {
isFlyingShuttle || process.env.__NEXT_BUILDER_EXPERIMENTAL_PAGE
)

if (selectivePageBuilding && target !== 'serverless') {
const isServerless = target === 'serverless'
const isServerlessTrace = target === 'experimental-serverless-trace'
const isLikeServerless = isServerless || isServerlessTrace

if (selectivePageBuilding && !isServerless) {
throw new Error(
`Cannot use ${
isFlyingShuttle ? 'flying shuttle' : '`now dev`'
} without the serverless target.`
} without the \`serverless\` target.`
)
}

Expand Down Expand Up @@ -199,7 +203,8 @@ export default async function build(dir: string, conf = null): Promise<void> {
])

let result: CompilerResult = { warnings: [], errors: [] }
if (target === 'serverless') {
// TODO: why do we need this??
Timer marked this conversation as resolved.
Show resolved Hide resolved
if (isLikeServerless) {
const clientResult = await runCompiler(configs[0])
// Fail build if clientResult contains errors
if (clientResult.errors.length > 0) {
Expand Down Expand Up @@ -273,7 +278,7 @@ export default async function build(dir: string, conf = null): Promise<void> {
const pageKeys = Object.keys(mappedPages)
const manifestPath = path.join(
distDir,
target === 'serverless' ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY,
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY,
PAGES_MANIFEST
)

Expand Down Expand Up @@ -303,12 +308,12 @@ export default async function build(dir: string, conf = null): Promise<void> {
const actualPage = page === '/' ? '/index' : page
const size = await getPageSizeInKb(actualPage, distPath, buildId)
const bundleRelative = path.join(
target === 'serverless' ? 'pages' : `static/${buildId}/pages`,
isLikeServerless ? 'pages' : `static/${buildId}/pages`,
actualPage + '.js'
)
const serverBundle = path.join(
distPath,
target === 'serverless' ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY,
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY,
bundleRelative
)

Expand All @@ -324,7 +329,7 @@ export default async function build(dir: string, conf = null): Promise<void> {

if (nonReservedPage && customAppGetInitialProps === undefined) {
customAppGetInitialProps = hasCustomAppGetInitialProps(
target === 'serverless'
isLikeServerless
? serverBundle
: path.join(
distPath,
Expand Down Expand Up @@ -437,7 +442,7 @@ export default async function build(dir: string, conf = null): Promise<void> {
for (const file of toMove) {
const orig = path.join(exportOptions.outdir, file)
const dest = path.join(serverDir, file)
const relativeDest = (target === 'serverless'
const relativeDest = (isLikeServerless
? path.join('pages', file)
: path.join('static', buildId, 'pages', file)
).replace(/\\/g, '/')
Expand Down Expand Up @@ -465,9 +470,5 @@ export default async function build(dir: string, conf = null): Promise<void> {
await flyingShuttle.save(allStaticPages, pageInfos)
}

printTreeView(
Object.keys(allMappedPages),
allPageInfos,
target === 'serverless'
)
printTreeView(Object.keys(allMappedPages), allPageInfos, isLikeServerless)
}
27 changes: 17 additions & 10 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
import fs from 'fs'
import {
CLIENT_STATIC_FILES_RUNTIME_MAIN,
CLIENT_STATIC_FILES_RUNTIME_WEBPACK,
REACT_LOADABLE_MANIFEST,
SERVER_DIRECTORY,
SERVERLESS_DIRECTORY,
} from 'next-server/constants'
import resolve from 'next/dist/compiled/resolve/index.js'
import path from 'path'
Expand Down Expand Up @@ -79,7 +79,11 @@ export default async function getBaseWebpackConfig(
.split(process.platform === 'win32' ? ';' : ':')
.filter(p => !!p)

const outputDir = target === 'serverless' ? 'serverless' : SERVER_DIRECTORY
const isServerless = target === 'serverless'
const isServerlessTrace = target === 'experimental-serverless-trace'
const isLikeServerless = isServerless || isServerlessTrace

const outputDir = isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY
const outputPath = path.join(distDir, isServer ? outputDir : '')
const totalPages = Object.keys(entrypoints).length
const clientEntries = !isServer
Expand Down Expand Up @@ -238,7 +242,7 @@ export default async function getBaseWebpackConfig(
target: isServer ? 'node' : 'web',
externals: !isServer
? undefined
: target !== 'serverless'
: !isServerless
? [
(context, request, callback) => {
const notExternalModules = [
Expand Down Expand Up @@ -293,8 +297,8 @@ export default async function getBaseWebpackConfig(
},
]
: [
// When the serverless target is used all node_modules will be compiled into the output bundles
// So that the serverless bundles have 0 runtime dependencies
// When the 'serverless' target is used all node_modules will be compiled into the output bundles
// So that the 'serverless' bundles have 0 runtime dependencies
'amp-toolbox-optimizer', // except this one
(context, request, callback) => {
if (
Expand Down Expand Up @@ -561,11 +565,14 @@ export default async function getBaseWebpackConfig(
)
},
}),
target === 'serverless' &&
(isServer || selectivePageBuilding) &&
new ServerlessPlugin(buildId, { isServer }),
isServer && new PagesManifestPlugin(target === 'serverless'),
target !== 'serverless' &&
isLikeServerless &&
new ServerlessPlugin(buildId, {
isServer,
isFlyingShuttle: selectivePageBuilding,
isTrace: isServerlessTrace,
}),
isServer && new PagesManifestPlugin(isLikeServerless),
target === 'server' &&
isServer &&
new NextJsSSRModuleCachePlugin({ outputPath }),
isServer && new NextJsSsrImportPlugin(),
Expand Down
52 changes: 34 additions & 18 deletions packages/next/build/webpack/plugins/serverless-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,47 @@ function interceptFileWrites(
export class ServerlessPlugin {
private buildId: string
private isServer: boolean
private isTrace: boolean
private isFlyingShuttle: boolean

constructor(buildId: string, { isServer = false } = {}) {
constructor(
buildId: string,
{
isServer,
isTrace,
isFlyingShuttle,
}: { isServer: boolean; isTrace: boolean; isFlyingShuttle: boolean }
) {
this.buildId = buildId
this.isServer = isServer
this.isTrace = isTrace
this.isFlyingShuttle = isFlyingShuttle
}

apply(compiler: Compiler) {
if (this.isServer) {
interceptFileWrites(compiler, content =>
replaceInBuffer(content, NEXT_REPLACE_BUILD_ID, this.buildId)
)
if (!this.isServer) {
if (this.isFlyingShuttle) {
compiler.hooks.emit.tap('ServerlessPlugin', compilation => {
const assetNames = Object.keys(compilation.assets).filter(f =>
f.includes(this.buildId)
)
for (const name of assetNames) {
compilation.assets[
name
.replace(new RegExp(`${this.buildId}[\\/\\\\]`), 'client/')
.replace(/[.]js$/, `.${this.buildId}.js`)
] = compilation.assets[name]
}
})
}
return
}

interceptFileWrites(compiler, content =>
replaceInBuffer(content, NEXT_REPLACE_BUILD_ID, this.buildId)
)

if (!this.isTrace) {
compiler.hooks.compilation.tap('ServerlessPlugin', compilation => {
compilation.hooks.optimizeChunksBasic.tap(
'ServerlessPlugin',
Expand All @@ -86,19 +115,6 @@ export class ServerlessPlugin {
}
)
})
} else {
compiler.hooks.emit.tap('ServerlessPlugin', compilation => {
const assetNames = Object.keys(compilation.assets).filter(f =>
f.includes(this.buildId)
)
for (const name of assetNames) {
compilation.assets[
name
.replace(new RegExp(`${this.buildId}[\\/\\\\]`), 'client/')
.replace(/[.]js$/, `.${this.buildId}.js`)
] = compilation.assets[name]
}
})
}
}
}
4 changes: 3 additions & 1 deletion packages/next/export/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ export default async function (dir, options, configuration) {
serverRuntimeConfig,
concurrency,
subFolders,
serverless: nextConfig.target === 'serverless'
serverless:
nextConfig.target === 'serverless' ||
nextConfig.target === 'experimental-serverless-trace'
})
worker.on('message', ({ type, payload }) => {
if (type === 'progress' && progress) {
Expand Down
6 changes: 6 additions & 0 deletions test/integration/external-assets/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
target: 'experimental-serverless-trace',
onDemandEntries: {
maxInactiveAge: 1000 * 60 * 60
}
}
19 changes: 19 additions & 0 deletions test/integration/external-assets/pages/about/history.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import firebase from 'firebase/app'
import 'firebase/firestore'

if (!firebase.apps.length) {
firebase.initializeApp({ projectId: 'noop' })
}

const store = firebase.firestore()

const Comp = ({ results }) => {
return <div>Hello Firebase: {results}</div>
}

Comp.getInitialProps = async () => {
const query = await store.collection('users').get()
return { results: query.size }
}

export default Comp
Loading