Skip to content

Commit

Permalink
feature: Use listr to display progress and errors for transformations (
Browse files Browse the repository at this point in the history
…#92)

* Promisify transform functions and use listr to show success/errors
* Improve startup time by requiring code in appropriate branches
* Added throwing webpack config fixture
  • Loading branch information
okonet authored Mar 24, 2017
1 parent dcde2b6 commit 5920019
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 76 deletions.
9 changes: 2 additions & 7 deletions bin/webpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ var yargs = require('yargs')

require('./config-yargs')(yargs);


var DISPLAY_GROUP = 'Stats options:';
var BASIC_GROUP = 'Basic options:';

Expand Down Expand Up @@ -156,19 +155,15 @@ if(argv.verbose) {
argv['display-cached-assets'] = true;
}

var processOptions = require('./process-options');
var initInquirer = require('../lib/initialize');

if(argv.init) {
initInquirer(argv._);
require('../lib/initialize')(argv._);
} else if(argv.migrate) {
const filePaths = argv._;
if (!filePaths.length) {
throw new Error('Please specify a path to your webpack config');
}
const inputConfigPath = path.resolve(process.cwd(), filePaths[0]);

require('../lib/migrate.js')(inputConfigPath, inputConfigPath);
} else {
processOptions(yargs,argv);
require('./process-options')(yargs,argv);
}
105 changes: 76 additions & 29 deletions lib/migrate.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,83 @@
const fs = require('fs');
const diff = require('diff');
const chalk = require('chalk');
const transform = require('./transformations').transform;
const diff = require('diff');
const inquirer = require('inquirer');
const PLazy = require('p-lazy');
const Listr = require('listr');

module.exports = (currentConfigPath, outputConfigPath) => {
let currentConfig = fs.readFileSync(currentConfigPath, 'utf8');
const outputConfig = transform(currentConfig);
const diffOutput = diff.diffLines(currentConfig, outputConfig);
diffOutput.map(diffLine => {
if (diffLine.added) {
process.stdout.write(chalk.green(`+ ${diffLine.value}`));
} else if (diffLine.removed) {
process.stdout.write(chalk.red(`- ${diffLine.value}`));
}
});
inquirer
.prompt([
{
type: 'confirm',
name: 'confirmMigration',
message: 'Are you sure these changes are fine?',
default: 'Y'
}
])
.then(answers => {
if (answers['confirmMigration']) {
// TODO validate the config
fs.writeFileSync(outputConfigPath, outputConfig, 'utf8');
process.stdout.write(chalk.green(`Congratulations! Your new webpack v2 config file is at ${outputConfigPath}`));
} else {
process.stdout.write(chalk.yellow('Migration aborted'));
module.exports = function transformFile(currentConfigPath, outputConfigPath, options) {
const recastOptions = Object.assign({
quote: 'single'
}, options);
const tasks = new Listr([
{
title: 'Reading webpack config',
task: (ctx) => new PLazy((resolve, reject) => {
fs.readFile(currentConfigPath, 'utf8', (err, content) => {
if (err) {
reject(err);
}
try {
console.time('Reading webpack config');
const jscodeshift = require('jscodeshift');
ctx.source = content;
ctx.ast = jscodeshift(content);
resolve();
console.timeEnd('Reading webpack config');
} catch (err) {
reject('Error generating AST', err);
}
});
})
},
{
title: 'Migrating config from v1 to v2',
task: (ctx) => {
const transformations = require('./transformations').transformations;
return new Listr(Object.keys(transformations).map(key => {
const transform = transformations[key];
return {
title: key,
task: () => transform(ctx.ast, ctx.source)
};
}));
}
}
]);

tasks.run()
.then((ctx) => {
const result = ctx.ast.toSource(recastOptions);
const diffOutput = diff.diffLines(ctx.source, result);
diffOutput.forEach(diffLine => {
if (diffLine.added) {
process.stdout.write(chalk.green(`+ ${diffLine.value}`));
} else if (diffLine.removed) {
process.stdout.write(chalk.red(`- ${diffLine.value}`));
}
});
inquirer
.prompt([
{
type: 'confirm',
name: 'confirmMigration',
message: 'Are you sure these changes are fine?',
default: 'Y'
}
])
.then(answers => {
if (answers['confirmMigration']) {
// TODO validate the config
fs.writeFileSync(outputConfigPath, result, 'utf8');
console.log(chalk.green(`✔︎ New webpack v2 config file is at ${outputConfigPath}`));
} else {
console.log(chalk.red('✖ Migration aborted'));
}
});
})
.catch(err => {
console.log(chalk.red('✖ ︎Migration aborted due to some errors'));
console.error(err);
process.exitCode = 1;
});
};
81 changes: 81 additions & 0 deletions lib/transformations/__testfixtures__/failing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
var webpack = require('webpack');
var nodeEnvironment = process.env.NODE_ENV
var _ = require('lodash');

var config = {
entry: {
'lib': './app/index.js',
'email': './app/email.js'
},
plugins: [
new webpack.DefinePlugin({
'INCLUDE_ALL_MODULES': function includeAllModulesGlobalFn(modulesArray, application) {
modulesArray.forEach(function executeModuleIncludesFn(moduleFn) {
moduleFn(application);
});
},
ENVIRONMENT: JSON.stringify(nodeEnvironment)
})
],
output: {
path: __dirname + '/app',
filename: 'bundle.js'
},
resolve: {
root: __dirname + '/app'
},
module: {
// preLoaders: [
// { test: /\.js?$/, loader: 'eslint', exclude: /node_modules/ }
// ],
loaders: [
{ test: /\.js$/, exclude: /(node_modules)/, loader: 'babel' },
{ test: /\.html/, exclude: [/(node_modules)/, /src\/index\.html/], loader: 'html-loader' },
{ test: /\.s?css$/, loader: 'style!css!sass' },
{ test: /\.(png|jpg)$/, loader: 'url-loader?mimetype=image/png' }
]
},
// extra configuration options.
// eslint: {
// configFile: '.eslintrc.js'
// }
}

switch (nodeEnvironment) {
case 'production':
config.plugins.push(new webpack.optimize.UglifyJsPlugin());
case 'preproduction':
config.output.path = __dirname + '/dist';
config.plugins.push(new webpack.optimize.DedupePlugin());
config.plugins.push(new webpack.optimize.OccurenceOrderPlugin());

config.output.filename = '[name].js';

config.entry = {
'lib': ['./app/index.js', 'angular', 'lodash'],
'email': ['./app/email.js', 'angular']
};

config.devtool = 'source-map';
config.output.libraryTarget = 'commonjs2';

break;

case 'test':
config.entry = './index.js';
break;

case 'development':
config.entry = {
'lib': ['./app/index.js', 'webpack/hot/dev-server'],
'email': ['./app/email.js', 'webpack/hot/dev-server']
};
config.output.filename = '[name].js';
config.devtool = 'source-map';
break;

default:
console.warn('Unknown or Undefined Node Environment. Please refer to package.json for available build commands.');
}

module.exports = config;
19 changes: 4 additions & 15 deletions lib/transformations/defineTest.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

/* global expect, describe, it */

'use strict';

const fs = require('fs');
Expand All @@ -33,7 +22,7 @@ const path = require('path');
* - Test data should be located in a directory called __testfixtures__
* alongside the transform and __tests__ directory.
*/
function runTest(dirName, transformName, testFilePrefix) {
function runSingleTansform(dirName, transformName, testFilePrefix) {
if (!testFilePrefix) {
testFilePrefix = transformName;
}
Expand All @@ -53,8 +42,7 @@ function runTest(dirName, transformName, testFilePrefix) {
jscodeshift = jscodeshift.withParser(module.parser);
}
const ast = jscodeshift(source);
const output = transform(jscodeshift, ast, source).toSource({ quote: 'single' });
expect(output).toMatchSnapshot();
return transform(jscodeshift, ast, source).toSource({ quote: 'single' });
}

/**
Expand All @@ -67,7 +55,8 @@ function defineTest(dirName, transformName, testFilePrefix) {
: 'transforms correctly';
describe(transformName, () => {
it(testName, () => {
runTest(dirName, transformName, testFilePrefix);
const output = runSingleTansform(dirName, transformName, testFilePrefix);
expect(output).toMatchSnapshot();
});
});
}
Expand Down
51 changes: 38 additions & 13 deletions lib/transformations/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const jscodeshift = require('jscodeshift');
const pEachSeries = require('p-each-series');
const PLazy = require('p-lazy');

const loadersTransform = require('./loaders/loaders');
const resolveTransform = require('./resolve/resolve');
Expand All @@ -9,7 +11,7 @@ const bannerPluginTransform = require('./bannerPlugin/bannerPlugin');
const extractTextPluginTransform = require('./extractTextPlugin/extractTextPlugin');
const removeDeprecatedPluginsTransform = require('./removeDeprecatedPlugins/removeDeprecatedPlugins');

const transformations = {
const transformsObject = {
loadersTransform,
resolveTransform,
removeJsonLoaderTransform,
Expand All @@ -20,28 +22,51 @@ const transformations = {
removeDeprecatedPluginsTransform
};

const transformations = Object.keys(transformsObject).reduce((res, key) => {
res[key] = (ast, source) => transformSingleAST(ast, source, transformsObject[key]);
return res;
}, {});

function transformSingleAST(ast, source, transformFunction) {
return new PLazy((resolve, reject) => {
setTimeout(() => {
try {
resolve(transformFunction(jscodeshift, ast, source));
} catch (err) {
reject(err);
}
}, 0);
});
}

/*
* @function transform
*
* Tranforms a given source code by applying selected transformations to the AST
*
* @param { String } source - Source file contents
* @param { Array<Function> } transformations - List of trnasformation functions in defined the
* order to apply. By default all defined transfomations.
* @param { Object } options - Reacst formatting options
* @returns { String } Transformed source code
* */
* @function transform
*
* Tranforms a given source code by applying selected transformations to the AST
*
* @param { String } source - Source file contents
* @param { Array<Function> } transformations - List of trnasformation functions in defined the
* order to apply. By default all defined transfomations.
* @param { Object } options - Reacst formatting options
* @returns { String } Transformed source code
* */
function transform(source, transforms, options) {
const ast = jscodeshift(source);
const recastOptions = Object.assign({
quote: 'single'
}, options);
transforms = transforms || Object.keys(transformations).map(k => transformations[k]);
transforms.forEach(f => f(jscodeshift, ast, source));
return ast.toSource(recastOptions);
return pEachSeries(transforms, f => f(ast, source))
.then(() => {
return ast.toSource(recastOptions);
})
.catch(err => {
console.error(err);
});
}

module.exports = {
transform,
transformSingleAST,
transformations
};
32 changes: 20 additions & 12 deletions lib/transformations/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,34 @@ module.exports = {
`;

describe('transform', () => {
it('should not transform if no transformations defined', () => {
const output = transform(input, []);
expect(output).toEqual(input);
it('should not transform if no transformations defined', (done) => {
transform(input, []).then(output => {
expect(output).toEqual(input);
done();
});
});

it('should transform using all transformations', () => {
const output = transform(input);
expect(output).toMatchSnapshot();
it('should transform using all transformations', (done) => {
transform(input).then(output => {
expect(output).toMatchSnapshot();
done();
});
});

it('should transform only using specified transformations', () => {
const output = transform(input, [transformations.loadersTransform]);
expect(output).toMatchSnapshot();
it('should transform only using specified transformations', (done) => {
transform(input, [transformations.loadersTransform]).then(output => {
expect(output).toMatchSnapshot();
done();
});
});

it('should respect recast options', () => {
const output = transform(input, undefined, {
it('should respect recast options', (done) => {
transform(input, undefined, {
quote: 'double',
trailingComma: true
}).then(output => {
expect(output).toMatchSnapshot();
done();
});
expect(output).toMatchSnapshot();
});
});
Loading

0 comments on commit 5920019

Please sign in to comment.