Skip to content

Commit 1a50c57

Browse files
clydinhansl
authored andcommitted
refactor(@angular-devkit/build-optimizer): use direct transform API when possible
1 parent 0e3bbf6 commit 1a50c57

File tree

5 files changed

+125
-70
lines changed

5 files changed

+125
-70
lines changed

packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,7 @@ export function buildOptimizer(options: BuildOptimizerOptions): TransformJavascr
9090
// Determine which transforms to apply.
9191
const getTransforms = [];
9292

93-
if (testWrapEnums(content)) {
94-
getTransforms.push(getWrapEnumsTransformer);
95-
}
96-
97-
if (testImportTslib(content)) {
98-
getTransforms.push(getImportTslibTransformer);
99-
}
100-
101-
if (testPrefixClasses(content)) {
102-
getTransforms.push(getPrefixClassesTransformer);
103-
}
104-
93+
let typeCheck = false;
10594
if (options.isSideEffectFree || originalFilePath && isKnownSideEffectFree(originalFilePath)) {
10695
getTransforms.push(
10796
// getPrefixFunctionsTransformer is rather dangerous, apply only to known pure es5 modules.
@@ -112,11 +101,29 @@ export function buildOptimizer(options: BuildOptimizerOptions): TransformJavascr
112101
getScrubFileTransformer,
113102
getFoldFileTransformer,
114103
);
104+
typeCheck = true;
115105
} else if (testScrubFile(content)) {
106+
// Always test as these require the type checker
116107
getTransforms.push(
117108
getScrubFileTransformer,
118109
getFoldFileTransformer,
119110
);
111+
typeCheck = true;
112+
}
113+
114+
// tests are not needed for fast path
115+
const ignoreTest = !options.emitSourceMap && !typeCheck;
116+
117+
if (ignoreTest || testPrefixClasses(content)) {
118+
getTransforms.unshift(getPrefixClassesTransformer);
119+
}
120+
121+
if (ignoreTest || testImportTslib(content)) {
122+
getTransforms.unshift(getImportTslibTransformer);
123+
}
124+
125+
if (ignoreTest || testWrapEnums(content)) {
126+
getTransforms.unshift(getWrapEnumsTransformer);
120127
}
121128

122129
const transformJavascriptOpts: TransformJavascriptOptions = {
@@ -126,6 +133,7 @@ export function buildOptimizer(options: BuildOptimizerOptions): TransformJavascr
126133
emitSourceMap: options.emitSourceMap,
127134
strict: options.strict,
128135
getTransforms,
136+
typeCheck,
129137
};
130138

131139
return transformJavascript(transformJavascriptOpts);

packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,6 @@ describe('build-optimizer', () => {
8888
});
8989
});
9090

91-
it('doesn\'t process files without decorators/ctorParameters/outside Angular', () => {
92-
const input = tags.oneLine`
93-
var Clazz = (function () { function Clazz() { } return Clazz; }());
94-
${staticProperty}
95-
`;
96-
97-
const boOutput = buildOptimizer({ content: input });
98-
expect(boOutput.content).toBeFalsy();
99-
expect(boOutput.emitSkipped).toEqual(true);
100-
});
101-
10291
it('supports es2015 modules', () => {
10392
// prefix-functions would add PURE_IMPORTS_START and PURE to the super call.
10493
// This test ensures it isn't applied to es2015 modules.

packages/angular_devkit/build_optimizer/src/helpers/transform-javascript.ts

Lines changed: 103 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export interface TransformJavascriptOptions {
1515
outputFilePath?: string;
1616
emitSourceMap?: boolean;
1717
strict?: boolean;
18-
getTransforms: Array<(program: ts.Program) => ts.TransformerFactory<ts.SourceFile>>;
18+
typeCheck?: boolean;
19+
getTransforms: Array<(program?: ts.Program) => ts.TransformerFactory<ts.SourceFile>>;
1920
}
2021

2122
export interface TransformJavascriptOutput {
@@ -24,6 +25,42 @@ export interface TransformJavascriptOutput {
2425
emitSkipped: boolean;
2526
}
2627

28+
interface DiagnosticSourceFile extends ts.SourceFile {
29+
readonly parseDiagnostics?: ReadonlyArray<ts.Diagnostic>;
30+
}
31+
32+
function validateDiagnostics(diagnostics: ReadonlyArray<ts.Diagnostic>, strict?: boolean): boolean {
33+
// Print error diagnostics.
34+
const checkDiagnostics = (diagnostics: ReadonlyArray<ts.Diagnostic>) => {
35+
if (diagnostics && diagnostics.length > 0) {
36+
let errors = '';
37+
errors = errors + '\n' + ts.formatDiagnostics(diagnostics, {
38+
getCurrentDirectory: () => ts.sys.getCurrentDirectory(),
39+
getNewLine: () => ts.sys.newLine,
40+
getCanonicalFileName: (f: string) => f,
41+
});
42+
43+
return errors;
44+
}
45+
};
46+
47+
const hasError = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error);
48+
if (hasError) {
49+
// Throw only if we're in strict mode, otherwise return original content.
50+
if (strict) {
51+
throw new Error(`
52+
TS failed with the following error messages:
53+
54+
${checkDiagnostics(diagnostics)}
55+
`);
56+
} else {
57+
return false;
58+
}
59+
}
60+
61+
return true;
62+
}
63+
2764
export function transformJavascript(
2865
options: TransformJavascriptOptions,
2966
): TransformJavascriptOutput {
@@ -46,23 +83,68 @@ export function transformJavascript(
4683
};
4784
}
4885

49-
// Print error diagnostics.
50-
const checkDiagnostics = (diagnostics: ReadonlyArray<ts.Diagnostic>) => {
51-
if (diagnostics && diagnostics.length > 0) {
52-
let errors = '';
53-
errors = errors + '\n' + ts.formatDiagnostics(diagnostics, {
54-
getCurrentDirectory: () => ts.sys.getCurrentDirectory(),
55-
getNewLine: () => ts.sys.newLine,
56-
getCanonicalFileName: (f: string) => f,
57-
});
86+
const allowFastPath = options.typeCheck === false && !emitSourceMap;
87+
const outputs = new Map<string, string>();
88+
const tempFilename = 'bo-default-file.js';
89+
const tempSourceFile = ts.createSourceFile(
90+
tempFilename,
91+
content,
92+
ts.ScriptTarget.Latest,
93+
allowFastPath,
94+
);
95+
const parseDiagnostics = (tempSourceFile as DiagnosticSourceFile).parseDiagnostics;
5896

59-
return errors;
60-
}
97+
const tsOptions: ts.CompilerOptions = {
98+
// We target latest so that there is no downleveling.
99+
target: ts.ScriptTarget.Latest,
100+
isolatedModules: true,
101+
suppressOutputPathCheck: true,
102+
allowNonTsExtensions: true,
103+
noLib: true,
104+
noResolve: true,
105+
sourceMap: emitSourceMap,
106+
inlineSources: emitSourceMap,
107+
inlineSourceMap: false,
61108
};
62109

63-
const outputs = new Map<string, string>();
64-
const tempFilename = 'bo-default-file.js';
65-
const tempSourceFile = ts.createSourceFile(tempFilename, content, ts.ScriptTarget.Latest);
110+
if (allowFastPath && parseDiagnostics) {
111+
if (!validateDiagnostics(parseDiagnostics, strict)) {
112+
return {
113+
content: null,
114+
sourceMap: null,
115+
emitSkipped: true,
116+
};
117+
}
118+
119+
const transforms = getTransforms.map((getTf) => getTf(undefined));
120+
121+
const result = ts.transform(tempSourceFile, transforms, tsOptions);
122+
if (result.transformed.length === 0 || result.transformed[0] === tempSourceFile) {
123+
return {
124+
content: null,
125+
sourceMap: null,
126+
emitSkipped: true,
127+
};
128+
}
129+
130+
const printer = ts.createPrinter(
131+
undefined,
132+
{
133+
onEmitNode: result.emitNodeWithNotification,
134+
substituteNode: result.substituteNode,
135+
},
136+
);
137+
138+
const output = printer.printFile(result.transformed[0]);
139+
140+
result.dispose();
141+
142+
return {
143+
content: output,
144+
sourceMap: null,
145+
emitSkipped: false,
146+
};
147+
}
66148

67149
const host: ts.CompilerHost = {
68150
getSourceFile: (fileName) => {
@@ -83,39 +165,15 @@ export function transformJavascript(
83165
writeFile: (fileName, text) => outputs.set(fileName, text),
84166
};
85167

86-
const tsOptions: ts.CompilerOptions = {
87-
// We target latest so that there is no downleveling.
88-
target: ts.ScriptTarget.Latest,
89-
isolatedModules: true,
90-
suppressOutputPathCheck: true,
91-
allowNonTsExtensions: true,
92-
noLib: true,
93-
noResolve: true,
94-
sourceMap: emitSourceMap,
95-
inlineSources: emitSourceMap,
96-
inlineSourceMap: false,
97-
};
98-
99168
const program = ts.createProgram([tempFilename], tsOptions, host);
100169

101170
const diagnostics = program.getSyntacticDiagnostics(tempSourceFile);
102-
const hasError = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error);
103-
104-
if (hasError) {
105-
// Throw only if we're in strict mode, otherwise return original content.
106-
if (strict) {
107-
throw new Error(`
108-
TS failed with the following error messages:
109-
110-
${checkDiagnostics(diagnostics)}
111-
`);
112-
} else {
113-
return {
114-
content: null,
115-
sourceMap: null,
116-
emitSkipped: true,
117-
};
118-
}
171+
if (!validateDiagnostics(diagnostics, strict)) {
172+
return {
173+
content: null,
174+
sourceMap: null,
175+
emitSkipped: true,
176+
};
119177
}
120178

121179
// We need the checker inside transforms.

packages/angular_devkit/build_optimizer/src/transforms/class-fold_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { getFoldFileTransformer } from './class-fold';
1111

1212

1313
const transform = (content: string) => transformJavascript(
14-
{ content, getTransforms: [getFoldFileTransformer] }).content;
14+
{ content, getTransforms: [getFoldFileTransformer], typeCheck: true }).content;
1515

1616
describe('class-fold', () => {
1717
it('folds static properties into class', () => {

packages/angular_devkit/build_optimizer/src/transforms/scrub-file_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { getScrubFileTransformer, testScrubFile } from './scrub-file';
1111

1212

1313
const transform = (content: string) => transformJavascript(
14-
{ content, getTransforms: [getScrubFileTransformer] }).content;
14+
{ content, getTransforms: [getScrubFileTransformer], typeCheck: true }).content;
1515

1616
describe('scrub-file', () => {
1717
const clazz = 'var Clazz = (function () { function Clazz() { } return Clazz; }());';

0 commit comments

Comments
 (0)