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

Uglify fixes #832

Merged
merged 4 commits into from
Mar 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions config/uglifyjs.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,6 @@

module.exports = {

/**
* sourceFile: The javascript file to minify
*/
sourceFile: process.env.IONIC_OUTPUT_JS_FILE_NAME,

/**
* destFileName: file name for the minified js in the build dir
*/
destFileName: process.env.IONIC_OUTPUT_JS_FILE_NAME,

/**
* inSourceMap: file name for the input source map
*/
inSourceMap: process.env.IONIC_OUTPUT_JS_FILE_NAME + '.map',

/**
* outSourceMap: file name for the output source map
*/
outSourceMap: process.env.IONIC_OUTPUT_JS_FILE_NAME + '.map',

/**
* mangle: uglify 2's mangle option
*/
Expand Down
67 changes: 42 additions & 25 deletions src/babili.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,64 @@ import * as crossSpawn from 'cross-spawn';
import { EventEmitter } from 'events';

describe('babili function', () => {
let emitter: EventEmitter;

beforeEach(() => {
emitter = new EventEmitter();

spyOn(configUtil, 'getUserConfigFile').and.returnValue('fileContents');
spyOn(crossSpawn, 'spawn').and.returnValue(emitter);


});

it('should call main babili function', () => {


it('should reject promise when non-zero status code', () => {
const spawnMock: any = {
on: () => {}
};
spyOn(crossSpawn, 'spawn').and.returnValue(spawnMock);
const onSpy = spyOn(spawnMock, 'on');

const context = {
rootDir: '/Users/noone/Projects/ionic-conference-app'
nodeModulesDir: '/Users/noone/Projects/ionic-conference-app/node_modules'
};
const configFile = 'configFileContents';
const knownError = 'should never get here';

const promise = babili.runBabili(context);
const spawnCallback = onSpy.calls.first().args[1];
spawnCallback(1);

let pr = babili.babili(context, configFile);
emitter.emit('close', 0);
pr.then(() => {
expect(configUtil.getUserConfigFile).toHaveBeenCalledWith(context, babili.taskInfo, configFile);
return promise.then(() => {
throw new Error(knownError);
}).catch((err: Error) => {
expect(err.message).not.toEqual(knownError);
});
});

it('should throw if context does not have a rootDir', () => {
const context = {};
const configFile = 'configFileContents';
it('should resolve promise when zero status code', () => {
const spawnMock: any = {
on: () => {}
};
spyOn(crossSpawn, 'spawn').and.returnValue(spawnMock);
const onSpy = spyOn(spawnMock, 'on');

expect(babili.babili(context, configFile)).toThrow();
});
const context = {
nodeModulesDir: '/Users/noone/Projects/ionic-conference-app/node_modules'
};

it('should fail because it does not have a valid build context', () => {
const context: null = null;
const configFile = 'configFileContents';
const promise = babili.runBabili(context);
const spawnCallback = onSpy.calls.first().args[1];
spawnCallback(0);

expect(babili.babili(context, configFile)).toThrow();
return promise;
});

it('should fail because it does not have a valid config file', () => {
it('should throw if context does not have a rootDir', () => {
const context = {};
const configFile: null = null;

expect(babili.babili(context, configFile)).toThrow();
const knownError = 'should never get here';
const promise = babili.runBabili(context);
return promise.then(() => {
throw new Error(knownError);
}).catch((err: Error) => {
expect(err.message).not.toEqual(knownError);
});
});

});
12 changes: 4 additions & 8 deletions src/babili.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,16 @@ export function babili(context: BuildContext, configFile?: string) {
export function babiliWorker(context: BuildContext, configFile: string) {
const babiliConfig: BabiliConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);
// TODO - figure out source maps??
return runBabili(context, babiliConfig);
return runBabili(context);
}

function runBabili(context: BuildContext, config: BabiliConfig) {
return runBabiliImpl(context);
}

function runBabiliImpl(context: BuildContext) {
export function runBabili(context: BuildContext) {
// TODO - is there a better way to run this?
return new Promise((resolve, reject) => {
if (!context.rootDir) {
if (!context.nodeModulesDir) {
return reject(new Error('Babili failed because the context passed did not have a rootDir'));
}
const babiliPath = join(context.rootDir, 'node_modules', '.bin', 'babili');
const babiliPath = join(context.nodeModulesDir, '.bin', 'babili');
const command = spawn(babiliPath, [context.buildDir, '--out-dir', context.buildDir]);
command.on('close', (code: number) => {
if (code !== 0) {
Expand Down
33 changes: 33 additions & 0 deletions src/optimization.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { join } from 'path';

import * as optimization from './optimization';
import * as decorators from './optimization/decorators';
import * as treeshake from './optimization/treeshake';
Expand Down Expand Up @@ -28,4 +30,35 @@ describe('optimization task', () => {
expect(treeshake.calculateUnusedComponents).not.toHaveBeenCalled();
});
});

describe('purgeGeneratedFiles', () => {
it('should remove files in buildDir with suffix from the cache', () => {
const buildDir = '/some/fake/dir/myApp/www/build';
const context = {
fileCache: new FileCache(),
buildDir: buildDir
};
const suffix = 'deptree.js';
const filePathOne = join(buildDir, `0.${suffix}`);
const filePathTwo = join(buildDir, `1.${suffix}`);
const filePathThree = join(buildDir, `main.js`);
const filePathFour = join(buildDir, `main.css`);
const filePathFive = join('some', 'fake', 'dir', 'myApp', 'src', `app.ts`);
const filePathSix = join('some', 'fake', 'dir', 'myApp', 'src', `app.js`);
const filePathSeven = join('some', 'fake', 'dir', 'myApp', 'src', 'pages', `1.${suffix}`);
context.fileCache.set(filePathOne, { path: filePathOne, content: filePathOne});
context.fileCache.set(filePathTwo, { path: filePathTwo, content: filePathTwo});
context.fileCache.set(filePathThree, { path: filePathThree, content: filePathThree});
context.fileCache.set(filePathFour, { path: filePathFour, content: filePathFour});
context.fileCache.set(filePathFive, { path: filePathFive, content: filePathFive});
context.fileCache.set(filePathSix, { path: filePathSix, content: filePathSix});
context.fileCache.set(filePathSeven, { path: filePathSeven, content: filePathSeven});

optimization.purgeGeneratedFiles(context, suffix);

expect(context.fileCache.getAll().length).toEqual(5);
expect(context.fileCache.get(filePathOne)).toBeFalsy();
expect(context.fileCache.get(filePathTwo)).toBeFalsy();
});
});
});
10 changes: 6 additions & 4 deletions src/optimization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Logger } from './logger/logger';
import { fillConfigDefaults, getUserConfigFile, replacePathVars } from './util/config';
import * as Constants from './util/constants';
import { BuildError } from './util/errors';
import { getBooleanPropertyValue, webpackStatsToDependencyMap, printDependencyMap, unlinkAsync } from './util/helpers';
import { getBooleanPropertyValue, webpackStatsToDependencyMap, printDependencyMap } from './util/helpers';
import { BuildContext, TaskInfo } from './util/interfaces';
import { runWebpackFullBuild, WebpackConfig } from './webpack';
import { purgeDecorators } from './optimization/decorators';
Expand Down Expand Up @@ -32,7 +32,8 @@ function optimizationWorker(context: BuildContext, configFile: string): Promise<
printDependencyMap(dependencyMap);
Logger.debug('Original Dependency Map End');
}
return deleteOptimizationJsFile(join(webpackConfig.output.path, webpackConfig.output.filename));

purgeGeneratedFiles(context, webpackConfig.output.filename);
}).then(() => {
return doOptimizations(context, dependencyMap);
});
Expand All @@ -41,8 +42,9 @@ function optimizationWorker(context: BuildContext, configFile: string): Promise<
}
}

export function deleteOptimizationJsFile(fileToDelete: string) {
return unlinkAsync(fileToDelete);
export function purgeGeneratedFiles(context: BuildContext, fileNameSuffix: string) {
const buildFiles = context.fileCache.getAll().filter(file => file.path.indexOf(context.buildDir) >= 0 && file.path.indexOf(fileNameSuffix) >= 0);
buildFiles.forEach(buildFile => context.fileCache.remove(buildFile.path));
}

export function doOptimizations(context: BuildContext, dependencyMap: Map<string, Set<string>>) {
Expand Down
93 changes: 60 additions & 33 deletions src/uglifyjs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,71 @@
import * as uglifyjs from './uglifyjs';
import * as configUtil from './util/config';
import * as workerClient from './worker-client';

describe('uglifyjs function', () => {
beforeEach(() => {
spyOn(configUtil, 'getUserConfigFile').and.returnValue('fileContents');
spyOn(workerClient, 'runWorker').and.returnValue(Promise.resolve());
});
import * as fs from 'fs';
import { join } from 'path';

it('should call workerClient function', () => {
const context = {};
const configFile = 'configFileContents';
import * as uglifyLib from 'uglify-js';

return uglifyjs.uglifyjs(context, configFile).then(() => {
expect(configUtil.getUserConfigFile).toHaveBeenCalledWith(context, uglifyjs.taskInfo, configFile);
expect(workerClient.runWorker).toHaveBeenCalledWith('uglifyjs', 'uglifyjsWorker', context, 'fileContents');
});
});
import * as helpers from './util/helpers';
import * as uglifyTask from './uglifyjs';

it('should fail because it does not have a valid build context', () => {
const context: null = null;
const configFile = 'configFileContents';

expect(uglifyjs.uglifyjs(context, configFile)).toThrow();
});
describe('uglifyjs', () => {
describe('uglifyjsWorkerImpl', () => {
it('should call uglify for the appropriate files', () => {
const buildDir = join('some', 'fake', 'dir', 'myApp', 'www', 'build');
const context = {
buildDir: buildDir
};
const fileNames = ['polyfills.js', 'sw-toolbox.js', '0.main.js', '0.main.js.map', '1.main.js', '1.main.js.map', 'main.js', 'main.js.map'];
const mockMinfiedResponse = {
code: 'code',
map: 'map'
};
const mockUglifyConfig = {
mangle: true,
compress: true
};

it('should fail because it does not have a valid config file', () => {
const context = {};
const configFile: null = null;
spyOn(fs, 'readdirSync').and.returnValue(fileNames);
const uglifySpy = spyOn(uglifyLib, 'minify').and.returnValue(mockMinfiedResponse);
const writeFileSpy = spyOn(helpers, helpers.writeFileAsync.name).and.returnValue(Promise.resolve());

expect(uglifyjs.uglifyjs(context, configFile)).toThrow();
});
const promise = uglifyTask.uglifyjsWorkerImpl(context, mockUglifyConfig);

return promise.then(() => {
expect(uglifyLib.minify).toHaveBeenCalledTimes(3);
expect(uglifySpy.calls.all()[0].args[0]).toEqual(join(buildDir, '0.main.js'));
expect(uglifySpy.calls.all()[0].args[1].compress).toEqual(true);
expect(uglifySpy.calls.all()[0].args[1].mangle).toEqual(true);
expect(uglifySpy.calls.all()[0].args[1].inSourceMap).toEqual(join(buildDir, '0.main.js.map'));
expect(uglifySpy.calls.all()[0].args[1].outSourceMap).toEqual(join(buildDir, '0.main.js.map'));

expect(uglifySpy.calls.all()[1].args[0]).toEqual(join(buildDir, '1.main.js'));
expect(uglifySpy.calls.all()[1].args[1].compress).toEqual(true);
expect(uglifySpy.calls.all()[1].args[1].mangle).toEqual(true);
expect(uglifySpy.calls.all()[1].args[1].inSourceMap).toEqual(join(buildDir, '1.main.js.map'));
expect(uglifySpy.calls.all()[1].args[1].outSourceMap).toEqual(join(buildDir, '1.main.js.map'));

expect(uglifySpy.calls.all()[2].args[0]).toEqual(join(buildDir, 'main.js'));
expect(uglifySpy.calls.all()[2].args[1].compress).toEqual(true);
expect(uglifySpy.calls.all()[2].args[1].mangle).toEqual(true);
expect(uglifySpy.calls.all()[2].args[1].inSourceMap).toEqual(join(buildDir, 'main.js.map'));
expect(uglifySpy.calls.all()[2].args[1].outSourceMap).toEqual(join(buildDir, 'main.js.map'));

expect(writeFileSpy).toHaveBeenCalledTimes(6);
expect(writeFileSpy.calls.all()[0].args[0]).toEqual(join(buildDir, '0.main.js'));
expect(writeFileSpy.calls.all()[0].args[1]).toEqual(mockMinfiedResponse.code);
expect(writeFileSpy.calls.all()[1].args[0]).toEqual(join(buildDir, '0.main.js.map'));
expect(writeFileSpy.calls.all()[1].args[1]).toEqual(mockMinfiedResponse.map);

it('should not fail if a config is not passed', () => {
const context = {};
let configFile: any;
expect(writeFileSpy.calls.all()[2].args[0]).toEqual(join(buildDir, '1.main.js'));
expect(writeFileSpy.calls.all()[2].args[1]).toEqual(mockMinfiedResponse.code);
expect(writeFileSpy.calls.all()[3].args[0]).toEqual(join(buildDir, '1.main.js.map'));
expect(writeFileSpy.calls.all()[3].args[1]).toEqual(mockMinfiedResponse.map);

return uglifyjs.uglifyjs(context).then(() => {
expect(configUtil.getUserConfigFile).toHaveBeenCalledWith(context, uglifyjs.taskInfo, configFile);
expect(workerClient.runWorker).toHaveBeenCalledWith('uglifyjs', 'uglifyjsWorker', context, 'fileContents');
expect(writeFileSpy.calls.all()[4].args[0]).toEqual(join(buildDir, 'main.js'));
expect(writeFileSpy.calls.all()[4].args[1]).toEqual(mockMinfiedResponse.code);
expect(writeFileSpy.calls.all()[5].args[0]).toEqual(join(buildDir, 'main.js.map'));
expect(writeFileSpy.calls.all()[5].args[1]).toEqual(mockMinfiedResponse.map);
});
});
});
});
Loading