Skip to content
This repository was archived by the owner on May 1, 2020. It is now read-only.

Commit d8d9a4e

Browse files
authored
feat(minification): code-split bundles will be minified (#814)
* feat(code-splitting): uglify now minifies all bundles * style(): semicolons * chore(babili): get multiple bundles working with babili * style(imports): remove un-used import * test(uglify): add error path tests) * test(babili): add tests for babili * fix(babili): improve error handling * test(babili): modify error test * chore(babili): make error handling fail correctly * test(babili): more error path tests * fix(uglify): file name is now correct * fix(transpile): file name is now correct * feat(code-splitting): uglify now minifies all bundles * style(): semicolons * chore(babili): get multiple bundles working with babili * style(imports): remove un-used import * test(uglify): add error path tests) * test(babili): add tests for babili * fix(babili): improve error handling * test(babili): modify error test * chore(babili): make error handling fail correctly * test(babili): more error path tests * fix(uglify): file name is now correct * fix(transpile): file name is now correct * style(console): remove un-needed console logs
1 parent 3c36e0c commit d8d9a4e

File tree

5 files changed

+113
-53
lines changed

5 files changed

+113
-53
lines changed

src/babili.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as babili from './babili';
2+
import * as configUtil from './util/config';
3+
4+
describe('babili function', () => {
5+
beforeEach(() => {
6+
spyOn(configUtil, 'getUserConfigFile').and.returnValue('fileContents');
7+
});
8+
9+
it('should call main babili function', () => {
10+
const context = {
11+
rootDir: '/Users/justinwillis/Projects/ionic-conference-app'
12+
};
13+
const configFile = 'configFileContents';
14+
15+
return babili.babili(context, configFile).then(() => {
16+
expect(configUtil.getUserConfigFile).toHaveBeenCalledWith(context, babili.taskInfo, configFile);
17+
});
18+
});
19+
20+
it('should throw if context does not have a rootDir', () => {
21+
const context = {};
22+
const configFile = 'configFileContents';
23+
24+
expect(babili.babili(context, configFile)).toThrow();
25+
});
26+
27+
it('should fail because it does not have a valid build context', () => {
28+
const context: null = null;
29+
const configFile = 'configFileContents';
30+
31+
expect(babili.babili(context, configFile)).toThrow();
32+
});
33+
34+
it('should fail because it does not have a valid config file', () => {
35+
const context = {};
36+
const configFile: null = null;
37+
38+
expect(babili.babili(context, configFile)).toThrow();
39+
});
40+
41+
});

src/babili.ts

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,67 +4,54 @@ import { spawn } from 'cross-spawn';
44
import { fillConfigDefaults, getUserConfigFile } from './util/config';
55
import { BuildContext, TaskInfo } from './util/interfaces';
66
import { Logger } from './logger/logger';
7-
import { writeFileAsync } from './util/helpers';
87

98
export function babili(context: BuildContext, configFile?: string) {
109

1110
configFile = getUserConfigFile(context, taskInfo, configFile);
11+
1212
const logger = new Logger('babili - experimental');
1313

1414
return babiliWorker(context, configFile).then(() => {
1515
logger.finish();
1616
})
1717
.catch(err => {
18-
throw logger.fail(err);
18+
return logger.fail(err);
1919
});
2020
}
2121

2222

2323
export function babiliWorker(context: BuildContext, configFile: string) {
2424
const babiliConfig: BabiliConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);
2525
// TODO - figure out source maps??
26-
return runBabili(context, babiliConfig).then((minifiedCode: string) => {
27-
// write the file back to disk
28-
const fileToWrite = join(context.buildDir, babiliConfig.destFileName);
29-
return writeFileAsync(fileToWrite, minifiedCode);
30-
});
26+
return runBabili(context, babiliConfig);
3127
}
3228

3329
function runBabili(context: BuildContext, config: BabiliConfig) {
34-
const babiliPath = join(context.rootDir, 'node_modules', '.bin', 'babili');
35-
const bundlePath = join(context.buildDir, config.sourceFile);
36-
return runBabiliImpl(babiliPath, bundlePath);
30+
return runBabiliImpl(context);
3731
}
3832

39-
function runBabiliImpl(pathToBabili: string, pathToBundle: string) {
33+
function runBabiliImpl(context: BuildContext) {
4034
// TODO - is there a better way to run this?
41-
let chunks: string[] = [];
4235
return new Promise((resolve, reject) => {
43-
const command = spawn(pathToBabili, [pathToBundle]);
44-
command.stdout.on('data', (buffer: Buffer) => {
45-
const stringRepresentation = buffer.toString();
46-
Logger.debug(`[Babili] ${stringRepresentation}`);
47-
chunks.push(stringRepresentation);
48-
});
49-
50-
command.stderr.on('data', (buffer: Buffer) => {
51-
Logger.warn(`[Babili] ${buffer.toString()}`);
52-
});
53-
36+
if (!context.rootDir) {
37+
return reject(new Error('Babili failed because the context passed did not have a rootDir'));
38+
}
39+
const babiliPath = join(context.rootDir, 'node_modules', '.bin', 'babili');
40+
const command = spawn(babiliPath, [context.buildDir, '--out-dir', context.buildDir]);
5441
command.on('close', (code: number) => {
55-
if ( code !== 0) {
42+
if (code !== 0) {
5643
return reject(new Error('Babili failed with a non-zero status code'));
5744
}
58-
return resolve(chunks.join(''));
45+
return resolve();
5946
});
6047
});
6148
}
6249

6350
export const taskInfo: TaskInfo = {
6451
fullArg: '--babili',
6552
shortArg: null,
66-
envVar: 'IONIC_EXP_BABILI',
67-
packageConfig: 'ionic_exp_babili',
53+
envVar: 'IONIC_USE_EXPERIMENTAL_BABILI',
54+
packageConfig: 'ionic_use_experimental_babili',
6855
defaultConfigFile: 'babili.config'
6956
};
7057

src/transpile.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { EventEmitter } from 'events';
88
import { fork, ChildProcess } from 'child_process';
99
import { inlineTemplate } from './template';
1010
import { Logger } from './logger/logger';
11-
import { readFileSync, writeFileSync } from 'fs';
11+
import { readFileSync, writeFileSync, readdirSync } from 'fs';
1212
import { runTypeScriptDiagnostics } from './logger/logger-typescript';
1313
import { printDiagnostics, clearDiagnostics, DiagnosticsType } from './logger/logger-diagnostics';
1414
import * as path from 'path';
@@ -310,18 +310,23 @@ export function transpileBundle(context: BuildContext, target: ts.ScriptTarget =
310310
function transpileBundleImpl(context: BuildContext, target: ts.ScriptTarget) {
311311
const logger = new Logger('transpile bundle');
312312
try {
313-
const bundlePath = path.join(context.buildDir, process.env[Constants.ENV_OUTPUT_JS_FILE_NAME]);
314-
const bundleContent = readFileSync(bundlePath).toString();
315-
const tsConfig = getTsConfig(context);
316-
const transpileOptions: ts.TranspileOptions = {
317-
compilerOptions: tsConfig.options,
318-
fileName: bundlePath,
319-
reportDiagnostics: true
320-
};
321-
// override the target value
322-
transpileOptions.compilerOptions.target = target;
323-
const transpiledOutput = ts.transpileModule(bundleContent, transpileOptions);
324-
writeFileSync(bundlePath, transpiledOutput.outputText);
313+
const files = readdirSync(context.buildDir);
314+
files.forEach((file) => {
315+
if (file.indexOf('deptree') === -1 && file.indexOf('map') === -1 && file.indexOf('sw-toolbox') === -1 && file.indexOf('polyfills') === -1) {
316+
const bundlePath = path.join(context.buildDir, file);
317+
const bundleContent = readFileSync(bundlePath).toString();
318+
const tsConfig = getTsConfig(context);
319+
const transpileOptions: ts.TranspileOptions = {
320+
compilerOptions: tsConfig.options,
321+
fileName: bundlePath,
322+
reportDiagnostics: true
323+
};
324+
// override the target value
325+
transpileOptions.compilerOptions.target = target;
326+
const transpiledOutput = ts.transpileModule(bundleContent, transpileOptions);
327+
writeFileSync(bundlePath, transpiledOutput.outputText);
328+
}
329+
});
325330
logger.finish();
326331
} catch (ex) {
327332
throw logger.fail(ex);

src/uglifyjs.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,28 @@ describe('uglifyjs function', () => {
1717
expect(workerClient.runWorker).toHaveBeenCalledWith('uglifyjs', 'uglifyjsWorker', context, 'fileContents');
1818
});
1919
});
20+
21+
it('should fail because it does not have a valid build context', () => {
22+
const context: null = null;
23+
const configFile = 'configFileContents';
24+
25+
expect(uglifyjs.uglifyjs(context, configFile)).toThrow();
26+
});
27+
28+
it('should fail because it does not have a valid config file', () => {
29+
const context = {};
30+
const configFile: null = null;
31+
32+
expect(uglifyjs.uglifyjs(context, configFile)).toThrow();
33+
});
34+
35+
it('should not fail if a config is not passed', () => {
36+
const context = {};
37+
let configFile: any;
38+
39+
return uglifyjs.uglifyjs(context).then(() => {
40+
expect(configUtil.getUserConfigFile).toHaveBeenCalledWith(context, uglifyjs.taskInfo, configFile);
41+
expect(workerClient.runWorker).toHaveBeenCalledWith('uglifyjs', 'uglifyjsWorker', context, 'fileContents');
42+
});
43+
});
2044
});

src/uglifyjs.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { join } from 'path';
22
import * as uglify from 'uglify-js';
33

44
import { Logger } from './logger/logger';
5+
import { readdirSync, writeFileSync } from 'fs';
56
import { fillConfigDefaults, generateContext, getUserConfigFile } from './util/config';
67
import { BuildError } from './util/errors';
7-
import { writeFileAsync } from './util/helpers';
88
import { BuildContext, TaskInfo } from './util/interfaces';
99
import { runWorker } from './worker-client';
1010

@@ -30,21 +30,24 @@ export function uglifyjsWorker(context: BuildContext, configFile: string): Promi
3030
try {
3131
// provide a full path for the config options
3232
context = generateContext(context);
33-
const uglifyJsConfig: UglifyJsConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);
34-
uglifyJsConfig.sourceFile = join(context.buildDir, uglifyJsConfig.sourceFile);
35-
uglifyJsConfig.inSourceMap = join(context.buildDir, uglifyJsConfig.inSourceMap);
36-
uglifyJsConfig.destFileName = join(context.buildDir, uglifyJsConfig.destFileName);
33+
const files = readdirSync(context.buildDir);
3734

38-
const minifiedOutputPath = join(context.buildDir, uglifyJsConfig.outSourceMap);
39-
const minifyOutput: uglify.MinifyOutput = runUglifyInternal(uglifyJsConfig);
35+
files.forEach((file) => {
36+
if (file.indexOf('deptree') === -1 && file.indexOf('map') === -1 && file.indexOf('sw-toolbox') === -1 && file.indexOf('polyfills') === -1) {
37+
const uglifyJsConfig: UglifyJsConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);
38+
uglifyJsConfig.sourceFile = join(context.buildDir, file);
39+
uglifyJsConfig.inSourceMap = join(context.buildDir, uglifyJsConfig.inSourceMap);
40+
uglifyJsConfig.destFileName = join(context.buildDir, file);
4041

41-
const writeFilePromises = [
42-
writeFileAsync(uglifyJsConfig.destFileName, minifyOutput.code),
43-
writeFileAsync(minifiedOutputPath, minifyOutput.map)
44-
];
42+
const minifiedOutputPath = join(context.buildDir, uglifyJsConfig.outSourceMap);
43+
const minifyOutput: uglify.MinifyOutput = runUglifyInternal(uglifyJsConfig);
4544

46-
return Promise.all(writeFilePromises).then(() => {
47-
resolve();
45+
46+
writeFileSync(uglifyJsConfig.destFileName, minifyOutput.code);
47+
writeFileSync(minifiedOutputPath, minifyOutput.map);
48+
49+
resolve();
50+
}
4851
});
4952

5053
} catch (e) {

0 commit comments

Comments
 (0)