Skip to content

Commit

Permalink
GranularChunks conformance check (vercel#11710)
Browse files Browse the repository at this point in the history
Adding a conformance plugin the make sure users don't undo the benefits of the granularChunks config.

The plugin makes sure that minSize, maxInitialRequests values aren't overridden. Also ensures the cacheGroups - vendors, framework, libs, common, shared are maintained.

The warning and error messages do not break the build with this change. They only display a message.

cc - @prateekbh, @atcastle
  • Loading branch information
janicklas-ralph authored and rokinsky committed Jul 11, 2020
1 parent 03b4e28 commit 84bb360
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 1 deletion.
10 changes: 10 additions & 0 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import WebpackConformancePlugin, {
DuplicatePolyfillsConformanceCheck,
MinificationConformanceCheck,
ReactSyncScriptsConformanceCheck,
GranularChunksConformanceCheck,
} from './webpack/plugins/webpack-conformance-plugin'
import { WellKnownErrorsPlugin } from './webpack/plugins/wellknown-errors-plugin'
import { codeFrameColumns } from '@babel/code-frame'
Expand Down Expand Up @@ -490,6 +491,9 @@ export default async function getBaseWebpackConfig(
?.BlockedAPIToBePolyfilled || []
),
},
GranularChunksConformanceCheck: {
enabled: true,
},
},
config.conformance
)
Expand Down Expand Up @@ -1019,6 +1023,12 @@ export default async function getBaseWebpackConfig(
conformanceConfig.DuplicatePolyfillsConformanceCheck
.BlockedAPIToBePolyfilled,
}),
!isServer &&
config.experimental.granularChunks &&
conformanceConfig.GranularChunksConformanceCheck.enabled &&
new GranularChunksConformanceCheck(
splitChunksConfigs.prodGranular
),
].filter(Boolean),
}),
new WellKnownErrorsPlugin(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import chalk from 'chalk'
import {
IWebpackConformanceTest,
IConformanceTestResult,
IConformanceTestStatus,
} from '../TestInterface'
import {
CONFORMANCE_ERROR_PREFIX,
CONFORMANCE_WARNING_PREFIX,
} from '../constants'
import { deepEqual } from '../utils/utils'

export interface GranularChunksConformanceCheck
extends IWebpackConformanceTest {
granularChunksConfig: any
}

function getWarningMessage(modifiedProp: string) {
return (
`${CONFORMANCE_WARNING_PREFIX}: The splitChunks config as part of the granularChunks flag has ` +
`been carefully crafted to optimize build size and build times. Modifying - ${chalk.bold(
modifiedProp
)} could result in slower builds and increased code duplication`
)
}

function getErrorMessage(message: string) {
return (
`${CONFORMANCE_ERROR_PREFIX}: The splitChunks config as part of the granularChunks flag has ` +
`been carefully crafted to optimize build size and build times. Please avoid changes to ${chalk.bold(
message
)}`
)
}

export class GranularChunksConformanceCheck {
constructor(granularChunksConfig: any) {
this.granularChunksConfig = granularChunksConfig
}

public buildStared(options: any): IConformanceTestResult {
const userSplitChunks = options.optimization.splitChunks
const warnings = []
const errors = []

if (
userSplitChunks.maxInitialRequests !==
this.granularChunksConfig.maxInitialRequests
) {
warnings.push('splitChunks.maxInitialRequests')
}

if (userSplitChunks.minSize !== this.granularChunksConfig.minSize) {
warnings.push('splitChunks.minSize')
}

const userCacheGroup = userSplitChunks.cacheGroups
const originalCacheGroup = this.granularChunksConfig.cacheGroups

if (userCacheGroup.vendors !== false) {
errors.push('splitChunks.cacheGroups.vendors')
}

if (!deepEqual(userCacheGroup.framework, originalCacheGroup.framework)) {
errors.push('splitChunks.cacheGroups.framework')
}

if (!deepEqual(userCacheGroup.lib, originalCacheGroup.lib)) {
errors.push('splitChunks.cacheGroups.lib')
}

if (!deepEqual(userCacheGroup.commons, originalCacheGroup.commons)) {
errors.push('splitChunks.cacheGroups.commons')
}

if (!deepEqual(userCacheGroup.shared, originalCacheGroup.shared)) {
errors.push('splitChunks.cacheGroups.shared')
}

if (!warnings.length && !errors.length) {
return {
result: IConformanceTestStatus.SUCCESS,
}
}

const failedResult: IConformanceTestResult = {
result: IConformanceTestStatus.FAILED,
}

if (warnings.length) {
failedResult.warnings = warnings.map((warning) => ({
message: getWarningMessage(warning),
}))
}

if (errors.length) {
failedResult.warnings = errors.map((error) => ({
message: getErrorMessage(error),
}))
}

return failedResult
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { visit } from 'next/dist/compiled/recast'
export { MinificationConformanceCheck } from './checks/minification-conformance-check'
export { ReactSyncScriptsConformanceCheck } from './checks/react-sync-scripts-conformance-check'
export { DuplicatePolyfillsConformanceCheck } from './checks/duplicate-polyfills-conformance-check'
export { GranularChunksConformanceCheck } from './checks/granular-chunks-conformance'

export interface IWebpackConformancePluginOptions {
tests: IWebpackConformanceTest[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const assert = require('assert').strict

export function deepEqual(a: any, b: any) {
try {
assert.deepStrictEqual(a, b)
return true
} catch (_) {
return false
}
}
12 changes: 12 additions & 0 deletions test/integration/conformance/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,16 @@ module.exports = {
experimental: {
conformance: true,
},
webpack(cfg, { dev, isServer }) {
if (!dev && !isServer) {
cfg.optimization.splitChunks.cacheGroups.vendors = {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
enforce: true,
}
}

return cfg
},
}
13 changes: 12 additions & 1 deletion test/integration/conformance/test/index.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-env jest */

import { join } from 'path'
import chalk from 'chalk'
import { nextBuild } from 'next-test-utils'
import { join } from 'path'

const appDir = join(__dirname, '../')
jest.setTimeout(1000 * 60 * 2)
Expand All @@ -28,4 +29,14 @@ describe('Conformance system', () => {
'[BUILD CONFORMANCE WARNING]: Found polyfill.io loading polyfill for fetch.'
)
})

it('Should warn about changes to granularChunks config', async () => {
const { stderr } = build
expect(stderr).toContain(
'[BUILD CONFORMANCE ERROR]: The splitChunks config as part of the granularChunks flag has ' +
`been carefully crafted to optimize build size and build times. Please avoid changes to ${chalk.bold(
'splitChunks.cacheGroups.vendors'
)}`
)
})
})

0 comments on commit 84bb360

Please sign in to comment.