Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding conformance webpack plugin (#9716)
* adding Conformance Plugin behind a flag * fixing compiler ts error * fixing spelling errors 🤦🏻♂️ * addressing comments * bug fix * making it const enum * reverting const enum Co-authored-by: Joe Haddad <timer150@gmail.com>
- Loading branch information
Showing
7 changed files
with
247 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
packages/next/build/webpack/plugins/webpack-conformance-plugin/TestInterface.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { NodePath } from 'ast-types/lib/node-path' | ||
|
||
export interface IConformanceAnomaly { | ||
message: string | ||
stack_trace?: string | ||
} | ||
|
||
export enum IConformanceTestStatus { | ||
SUCCESS, | ||
FAILED, | ||
} | ||
export interface IConformanceTestResult { | ||
result: IConformanceTestStatus | ||
warnings?: Array<IConformanceAnomaly> | ||
errors?: Array<IConformanceAnomaly> | ||
} | ||
|
||
export interface IParsedModuleDetails { | ||
request: string | ||
} | ||
|
||
export type NodeInspector = ( | ||
node: NodePath, | ||
details: IParsedModuleDetails | ||
) => IConformanceTestResult | ||
|
||
export interface IGetAstNodeResult { | ||
visitor: string | ||
inspectNode: NodeInspector | ||
} | ||
|
||
export interface IWebpackConformanceTest { | ||
buildStared?: (options: any) => IConformanceTestResult | ||
getAstNode?: () => IGetAstNodeResult[] | ||
buildCompleted?: (assets: any) => IConformanceTestResult | ||
} |
27 changes: 27 additions & 0 deletions
27
...build/webpack/plugins/webpack-conformance-plugin/checks/minification-conformance-check.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { | ||
IWebpackConformanceTest, | ||
IConformanceTestResult, | ||
IConformanceTestStatus, | ||
} from '../TestInterface' | ||
import { CONFORMANCE_ERROR_PREFIX } from '../constants' | ||
|
||
export class MinificationConformanceCheck implements IWebpackConformanceTest { | ||
public buildStared(options: any): IConformanceTestResult { | ||
// TODO(prateekbh@): Implement warning for using Terser maybe? | ||
|
||
if (options.optimization.minimize === false) { | ||
return { | ||
result: IConformanceTestStatus.FAILED, | ||
errors: [ | ||
{ | ||
message: `${CONFORMANCE_ERROR_PREFIX}: Minification is disabled for this build.\nDisabling minification can result in serious performance degradation.`, | ||
}, | ||
], | ||
} | ||
} else { | ||
return { | ||
result: IConformanceTestStatus.SUCCESS, | ||
} | ||
} | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
packages/next/build/webpack/plugins/webpack-conformance-plugin/constants.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import chalk from 'chalk' | ||
|
||
const { red, yellow } = chalk | ||
|
||
export const CONFORMANCE_ERROR_PREFIX: string = red('[BUILD CONFORMANCE ERROR]') | ||
export const CONFORMANCE_WARNING_PREFIX: string = yellow( | ||
'[BUILD CONFORMANCE WARNING]' | ||
) |
149 changes: 149 additions & 0 deletions
149
packages/next/build/webpack/plugins/webpack-conformance-plugin/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { Compiler, compilation } from 'webpack' | ||
import { | ||
IConformanceTestResult, | ||
IWebpackConformanceTest, | ||
IConformanceAnomaly, | ||
IGetAstNodeResult, | ||
NodeInspector, | ||
IConformanceTestStatus, | ||
} from './TestInterface' | ||
import { NodePath } from 'ast-types/lib/node-path' | ||
import { visit } from 'recast' | ||
|
||
export { MinificationConformanceCheck } from './checks/minification-conformance-check' | ||
// export { ReactSyncScriptsConformanceTest } from './tests/react-sync-scripts-conformance'; | ||
|
||
export interface IWebpackConformancePluginOptions { | ||
tests: IWebpackConformanceTest[] | ||
} | ||
|
||
interface VisitorMap { | ||
[key: string]: (path: NodePath) => void | ||
} | ||
|
||
export default class WebpackConformancePlugin { | ||
private tests: IWebpackConformanceTest[] | ||
private errors: Array<IConformanceAnomaly> | ||
private warnings: Array<IConformanceAnomaly> | ||
private compiler?: Compiler | ||
|
||
constructor(options: IWebpackConformancePluginOptions) { | ||
this.tests = [] | ||
if (options.tests) { | ||
this.tests.push(...options.tests) | ||
} | ||
this.errors = [] | ||
this.warnings = [] | ||
} | ||
|
||
private gatherResults(results: Array<IConformanceTestResult>): void { | ||
results.forEach(result => { | ||
if (result.result === IConformanceTestStatus.FAILED) { | ||
result.errors && this.errors.push(...result.errors) | ||
result.warnings && this.warnings.push(...result.warnings) | ||
} | ||
}) | ||
} | ||
|
||
private buildStartedHandler = ( | ||
compilation: compilation.Compilation, | ||
callback: () => void | ||
) => { | ||
const buildStartedResults: IConformanceTestResult[] = this.tests.map( | ||
test => { | ||
if (test.buildStared && this.compiler) { | ||
return test.buildStared(this.compiler.options) | ||
} | ||
return { | ||
result: IConformanceTestStatus.SUCCESS, | ||
} as IConformanceTestResult | ||
} | ||
) | ||
|
||
this.gatherResults(buildStartedResults) | ||
callback() | ||
} | ||
|
||
private buildCompletedHandler = ( | ||
compilation: compilation.Compilation, | ||
cb: () => void | ||
): void => { | ||
const buildCompletedResults: IConformanceTestResult[] = this.tests.map( | ||
test => { | ||
if (test.buildCompleted) { | ||
return test.buildCompleted(compilation.assets) | ||
} | ||
return { | ||
result: IConformanceTestStatus.SUCCESS, | ||
} as IConformanceTestResult | ||
} | ||
) | ||
|
||
this.gatherResults(buildCompletedResults) | ||
compilation.errors.push(...this.errors) | ||
compilation.warnings.push(...this.warnings) | ||
cb() | ||
} | ||
|
||
private parserHandler = (factory: compilation.NormalModuleFactory): void => { | ||
const JS_TYPES = ['auto', 'esm', 'dynamic'] | ||
const collectedVisitors: Map<string, [NodeInspector?]> = new Map() | ||
// Collect all intereseted visitors from all tests. | ||
this.tests.forEach(test => { | ||
if (test.getAstNode) { | ||
const getAstNodeCallbacks: IGetAstNodeResult[] = test.getAstNode() | ||
getAstNodeCallbacks.forEach(result => { | ||
if (!collectedVisitors.has(result.visitor)) { | ||
collectedVisitors.set(result.visitor, []) | ||
} | ||
// @ts-ignore | ||
collectedVisitors.get(result.visitor).push(result.inspectNode) | ||
}) | ||
} | ||
}) | ||
|
||
// Do an extra walk per module and add interested visitors to the walk. | ||
for (const type of JS_TYPES) { | ||
factory.hooks.parser | ||
.for('javascript/' + type) | ||
.tap(this.constructor.name, parser => { | ||
parser.hooks.program.tap(this.constructor.name, (ast: any) => { | ||
const visitors: VisitorMap = {} | ||
const that = this | ||
for (const visitorKey of collectedVisitors.keys()) { | ||
visitors[visitorKey] = function(path: NodePath) { | ||
const callbacks = collectedVisitors.get(visitorKey) || [] | ||
callbacks.forEach(cb => { | ||
if (!cb) { | ||
return | ||
} | ||
const { request } = parser.state.module | ||
const outcome = cb(path, { request }) | ||
that.gatherResults([outcome]) | ||
}) | ||
this.traverse(path) | ||
return false | ||
} | ||
} | ||
visit(ast, visitors) | ||
}) | ||
}) | ||
} | ||
} | ||
|
||
public apply(compiler: Compiler) { | ||
this.compiler = compiler | ||
compiler.hooks.make.tapAsync( | ||
this.constructor.name, | ||
this.buildStartedHandler | ||
) | ||
compiler.hooks.emit.tapAsync( | ||
this.constructor.name, | ||
this.buildCompletedHandler | ||
) | ||
compiler.hooks.normalModuleFactory.tap( | ||
this.constructor.name, | ||
this.parserHandler | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters