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

Commit

Permalink
fix(uglify): verify source maps are generated correctly for all bundl…
Browse files Browse the repository at this point in the history
…es, tests

* refactor(optimization): don't delete file from disk since we now write to memory

don't delete file from disk since we now write to memory

* test(babili): fix babili tests

fix babili tests

* needs tests but working

* tests
  • Loading branch information
danbucholtz authored Mar 24, 2017
1 parent 869455e commit fc44ca6
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 128 deletions.
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

0 comments on commit fc44ca6

Please sign in to comment.