rsnext/packages/next/build/webpack/plugins/serverless-plugin.ts
Guy Bedford bd8f3c625f
ncc Webpack build (#7301)
* add back ncc

* fixup ncc build
2019-05-16 11:55:54 -05:00

101 lines
3 KiB
TypeScript

import { Compiler } from 'next/dist/compiled/webpack'
import { connectChunkAndModule } from 'next/dist/compiled/webpack/lib/GraphHelpers'
/**
* Makes sure there are no dynamic chunks when the target is serverless
* The dynamic chunks are integrated back into their parent chunk
* This is to make sure there is a single render bundle instead of that bundle importing dynamic chunks
*/
const NEXT_REPLACE_BUILD_ID = '__NEXT_REPLACE__BUILD_ID__'
function replaceInBuffer(buffer: Buffer, from: string, to: string) {
const target = Buffer.from(from, 'utf8')
const replacement = Buffer.from(to, 'utf8')
function bufferTee(source: Buffer): Buffer {
const index = source.indexOf(target)
if (index === -1) {
// Escape recursion loop
return source
}
const b1 = source.slice(0, index)
const b2 = source.slice(index + target.length)
const nextBuffer = bufferTee(b2)
return Buffer.concat(
[b1, replacement, nextBuffer],
index + replacement.length + nextBuffer.length
)
}
return bufferTee(buffer)
}
function interceptFileWrites(
compiler: Compiler,
contentFn: (input: Buffer) => Buffer
) {
compiler.outputFileSystem = new Proxy(compiler.outputFileSystem, {
get(target, propKey) {
const orig = (target as any)[propKey]
if (propKey !== 'writeFile') {
return orig
}
return function(targetPath: string, content: Buffer, ...args: any[]) {
return orig.call(target, targetPath, contentFn(content), ...args)
}
},
})
}
export class ServerlessPlugin {
private buildId: string
private isServer: boolean
constructor(buildId: string, { isServer = false } = {}) {
this.buildId = buildId
this.isServer = isServer
}
apply(compiler: Compiler) {
if (this.isServer) {
interceptFileWrites(compiler, content =>
replaceInBuffer(content, NEXT_REPLACE_BUILD_ID, this.buildId)
)
compiler.hooks.compilation.tap('ServerlessPlugin', compilation => {
compilation.hooks.optimizeChunksBasic.tap('ServerlessPlugin', chunks => {
chunks.forEach(chunk => {
// If chunk is not an entry point skip them
if (chunk.hasEntryModule()) {
const dynamicChunks = chunk.getAllAsyncChunks()
if (dynamicChunks.size !== 0) {
for (const dynamicChunk of dynamicChunks) {
for (const module of dynamicChunk.modulesIterable) {
connectChunkAndModule(chunk, module)
}
}
}
}
})
})
})
} 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]
}
})
}
}
}