Add node_modules bundling under the --lambdas flag for next build (#5690)

* Add node_modules bundling under the —lambdas flag for next build

* Run minifier when lambdas mode is enabled

* Add lambdas option to next.config.js

* Add test for lambdas option
This commit is contained in:
Tim Neutkens 2018-11-17 20:15:33 +01:00 committed by Guillermo Rauch
parent b63dda7cf7
commit 7d78c3b641
6 changed files with 109 additions and 12 deletions

View file

@ -7,9 +7,10 @@ import { printAndExit } from '../server/lib/utils'
const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help'
h: 'help',
l: 'lambdas'
},
boolean: ['h']
boolean: ['h', 'l']
})
if (argv.help) {
@ -28,6 +29,7 @@ if (argv.help) {
}
const dir = resolve(argv._[0] || '.')
const lambdas = argv.lambdas
// Check if pages dir exists and warn if not
if (!existsSync(dir)) {
@ -42,7 +44,7 @@ if (!existsSync(join(dir, 'pages'))) {
printAndExit('> Couldn\'t find a `pages` directory. Please create one under the project root')
}
build(dir)
build(dir, null, lambdas)
.catch((err) => {
printAndExit(err)
})

View file

@ -32,8 +32,9 @@ async function ensureProjectDirectoryIsWriteAble (dir) {
}
}
export default async function build (dir, conf = null) {
export default async function build (dir, conf = null, lambdas = false) {
const config = loadConfig(PHASE_PRODUCTION_BUILD, dir, conf)
const lambdasOption = config.lambdas ? config.lambdas : lambdas
const distDir = join(dir, config.distDir)
const buildId = await generateBuildId(config.generateBuildId, nanoid)
@ -41,8 +42,8 @@ export default async function build (dir, conf = null) {
try {
const configs = await Promise.all([
getBaseWebpackConfig(dir, { buildId, isServer: false, config }),
getBaseWebpackConfig(dir, { buildId, isServer: true, config })
getBaseWebpackConfig(dir, { buildId, isServer: false, config, lambdas: lambdasOption }),
getBaseWebpackConfig(dir, { buildId, isServer: true, config, lambdas: lambdasOption })
])
await runCompiler(configs)

View file

@ -25,13 +25,41 @@ import AssetsSizePlugin from './webpack/plugins/assets-size-plugin'
// The externals config makes sure that
// on the server side when modules are
// in node_modules they don't get compiled by webpack
function externalsConfig (dir, isServer) {
function externalsConfig (dir, isServer, lambdas) {
const externals = []
if (!isServer) {
return externals
}
// When lambdas mode is enabled all node_modules will be compiled into the server bundles
// So that all dependencies can be devDependencies and are not required to be installed
if (lambdas) {
return [
(context, request, callback) => {
resolve(request, { basedir: context, preserveSymlinks: true }, (err, res) => {
if (err) {
return callback()
}
if (res.match(/next-server[/\\]dist[/\\]lib[/\\]head/)) {
return callback(null, `commonjs next-server/dist/lib/head.js`)
}
if (res.match(/next-server[/\\]dist[/\\]lib[/\\]asset/)) {
return callback(null, `commonjs next-server/dist/lib/asset.js`)
}
if (res.match(/next-server[/\\]dist[/\\]lib[/\\]runtime-config/)) {
return callback(null, `commonjs next-server/dist/lib/runtime-config.js`)
}
// Default pages have to be transpiled
if (res.match(/next-server[/\\]dist[/\\]lib[/\\]loadable/)) {
return callback(null, `commonjs next-server/dist/lib/loadable.js`)
}
callback()
})
}
]
}
const notExternalModules = ['next/app', 'next/document', 'next/error', 'http-status', 'string-hash']
externals.push((context, request, callback) => {
@ -74,7 +102,26 @@ function externalsConfig (dir, isServer) {
return externals
}
function optimizationConfig ({dir, dev, isServer, totalPages}) {
function optimizationConfig ({dir, dev, isServer, totalPages, lambdas}) {
if (isServer && lambdas) {
return {
splitChunks: false,
minimizer: [
new TerserPlugin({
parallel: true,
sourceMap: false,
cache: true,
cacheKeys: (keys) => {
// path changes per build because we add buildId
// because the input is already hashed the path is not needed
delete keys.path
return keys
}
})
]
}
}
if (isServer) {
return {
splitChunks: false,
@ -135,10 +182,11 @@ type BaseConfigContext = {|
dev: boolean,
isServer: boolean,
buildId: string,
config: NextConfig
config: NextConfig,
lambdas: boolean
|}
export default async function getBaseWebpackConfig (dir: string, {dev = false, isServer = false, buildId, config}: BaseConfigContext) {
export default async function getBaseWebpackConfig (dir: string, {dev = false, isServer = false, buildId, config, lambdas = false}: BaseConfigContext) {
const defaultLoaders = {
babel: {
loader: 'next-babel-loader',
@ -194,8 +242,8 @@ export default async function getBaseWebpackConfig (dir: string, {dev = false, i
name: isServer ? 'server' : 'client',
cache: true,
target: isServer ? 'node' : 'web',
externals: externalsConfig(dir, isServer),
optimization: optimizationConfig({dir, dev, isServer, totalPages}),
externals: externalsConfig(dir, isServer, lambdas),
optimization: optimizationConfig({dir, dev, isServer, totalPages, lambdas}),
recordsPath: path.join(outputPath, 'records.json'),
context: dir,
// Kept as function to be backwards compatible

View file

@ -0,0 +1,7 @@
module.exports = {
onDemandEntries: {
// Make sure entries are not getting disposed.
maxInactiveAge: 1000 * 60 * 60
},
lambdas: true
}

View file

@ -0,0 +1 @@
export default () => 'Hello World'

View file

@ -0,0 +1,38 @@
/* eslint-env jest */
/* global jasmine, test */
import { join } from 'path'
import {
nextServer,
nextBuild,
startApp,
stopApp,
renderViaHTTP
} from 'next-test-utils'
const appDir = join(__dirname, '../')
let appPort
let server
let app
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5
const context = {}
describe('Lambdas', () => {
beforeAll(async () => {
await nextBuild(appDir)
app = nextServer({
dir: join(__dirname, '../'),
dev: false,
quiet: true
})
server = await startApp(app)
context.appPort = appPort = server.address().port
})
afterAll(() => stopApp(server))
it('should render the page', async () => {
const html = await renderViaHTTP(appPort, '/')
expect(html).toMatch(/Hello World/)
})
})