Skip to content

Commit

Permalink
Merge 8f68852 into 66287ec
Browse files Browse the repository at this point in the history
  • Loading branch information
geowarin committed Aug 9, 2016
2 parents 66287ec + 8f68852 commit b625581
Show file tree
Hide file tree
Showing 14 changed files with 452 additions and 27 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Tarec takes all the best practices in the React community and makes them availab
* Babel 6 stage 0
* Tree-shaking with webpack 2
* Hot reloading with react-hmr
* Great DX experience with build notifications and clean error messages
* Pre-configured loaders for all resources (images, fonts, json, ...)
* Separate bundles for vendors and your code (css and js)
* Cache-busting
Expand Down Expand Up @@ -90,6 +91,20 @@ happypack:

Happypack is only used in development mode.

### Notifications

By default, tarec will display an os notification on build errors.
This can be very handy because you don't always have the console visible when coding.
But if you are annoyed by this feature, you can disable it:

```yml
build:
# show notifications on build failure. Default, true
showNotification: true
```

![Build error notifications](http://i.imgur.com/UN7hhJF.gif)

### Babel aliases

Create a `tarec.yml` file and configure [aliases](https://github.com/tleunen/babel-plugin-module-alias) like this:
Expand Down
13 changes: 9 additions & 4 deletions lib/commands/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,25 @@ module.exports = function start (context, args) {
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: context.webpackConfig.output.publicPath,
stats: {
colors: true
quiet: true,
watchOptions: {
ignored: /node_modules/
}
}));

app.use(require('webpack-hot-middleware')(compiler));
app.use(require('webpack-hot-middleware')(compiler, {
quiet: true,
reload: true,
log: () => {}
}));

app.listen(context.serverPort, '0.0.0.0', (err) => {
if (err) {
debug.log(err);
return;
}

debug.log(`Listening at ${chalk.blue(url)}`);
debug.log(`${chalk.green('Compiling...')}`);
if (args.open) {
open(url);
}
Expand Down
3 changes: 3 additions & 0 deletions lib/config/loadUserConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ module.exports = function loadUserConfig(configFile) {
enabled: false,
cache: true,
cpus: os.cpus().length
},
build: {
showNotifications: true
}
};

Expand Down
28 changes: 17 additions & 11 deletions lib/utils/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,23 @@ class Debugger {
this.enabled = true;
}

capture () {
this.enabled = true;
this.capturing = true;
}

endCapture () {
this.enabled = false;
this.capturing = false;
this.capturedMessages = [];
}

log (...args) {
if (this.enabled) {
this.captureConsole(args, console.log);
}
}

info (...args) {
if (this.enabled) {
this.captureConsole(args, console.info, 'green');
Expand All @@ -36,9 +47,10 @@ class Debugger {
}
}

capture () {
this.enabled = true;
this.capturing = true;
clearConsole () {
if (!this.capturing && this.enabled) {
process.stdout.write('\x1bc');
}
}

captureConsole (args, method, color) {
Expand All @@ -55,17 +67,11 @@ class Debugger {
}
return array.map(e => {
if (typeof e === 'string') {
return chalk[color](e);
return chalk[color](e);
}
return e;
})
}

endCapture () {
this.enabled = false;
this.capturing = false;
this.capturedMessages = [];
}
}

module.exports = new Debugger();
77 changes: 77 additions & 0 deletions lib/webpack/plugins/formatMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const RequestShortener = require("webpack/lib/RequestShortener");
const requestShortener = new RequestShortener(process.cwd());

function cleanStackTrace (message) {
return message
.replace(/^\s*at\s.*:\d+:\d+[\s\)]*\n/gm, ''); // at ... ...:x:y
}

function isBabelSyntaxError (e) {
return e.name === 'ModuleBuildError' && e.message.indexOf('SyntaxError') >= 0;
}

function isModuleNotFoundError (e) {
return e.name === 'ModuleNotFoundError'
&& e.message.indexOf('Module not found') === 0
&& e.dependencies && e.dependencies.length;
}

function formatMessage (webpackError) {

const error = extractError(webpackError);
if (isBabelSyntaxError(webpackError)) {
error.message = cleanStackTrace(error.message + '\n');
error.type = 'babel-syntax-error';
error.severity = 1000;
} else if (isModuleNotFoundError(webpackError)) {
error.message = `Module not found ${webpackError.dependencies[0].request}`;
error.module = webpackError.dependencies[0].request;
error.type = 'module-not-found';
error.severity = 900;
} else {
error.severity = 0;
}

return error;
}

function extractError (e) {
return {
message: e.message,
file: getFile(e),
origin: getOrigin(e),
name: e.name
};
}


function getFile (e) {
if (e.file) {
return e.file;
} else if (e.module && e.module.readableIdentifier && typeof e.module.readableIdentifier === "function") {
return e.module.readableIdentifier(requestShortener);
}
}

function getOrigin (e) {
let origin = '';
if (e.dependencies && e.origin) {
origin += '\n @ ' + e.origin.readableIdentifier(requestShortener);
e.dependencies.forEach(function (dep) {
if (!dep.loc) return;
if (typeof dep.loc === "string") return;
if (!dep.loc.start) return;
if (!dep.loc.end) return;
origin += ' ' + dep.loc.start.line + ':' + dep.loc.start.column + '-' +
(dep.loc.start.line !== dep.loc.end.line ? dep.loc.end.line + ':' : '') + dep.loc.end.column;
});
var current = e.origin;
while (current.issuer) {
current = current.issuer;
origin += '\n @ ' + current.readableIdentifier(requestShortener);
}
}
return origin;
}

module.exports = formatMessage;
118 changes: 118 additions & 0 deletions lib/webpack/plugins/notifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
const path = require('path');
const chalk = require('chalk');
const os = require('os');
const formatMessage = require('./formatMessage');
const debug = require('../../utils/debug');

function safeRequire (moduleName) {
try {
return require(moduleName);
} catch (ignored) {}
}

const LOGO = path.join(__dirname, 'tarec_logo_ico.png');

class NotifierPlugin {

constructor ({notificationTitle, compilationSuccessMessage, showNotifications}) {
this.notificationTitle = notificationTitle;
this.compilationSuccessMessage = compilationSuccessMessage;
this.notifier = showNotifications && safeRequire('node-notifier');
}

notify (serverity, error) {
this.notifier.notify({
title: this.notificationTitle,
message: serverity + ': ' + error.name,
subtitle: error.file || '',
icon: LOGO
});
}

apply (compiler) {

compiler.plugin('done', stats => {
debug.clearConsole();

const hasErrors = stats.hasErrors();
const hasWarnings = stats.hasWarnings();
if (!hasErrors && !hasWarnings) {
const time = stats.endTime - stats.startTime;
debug.log(chalk.green('Compiled successfully in ' + time + 'ms'));
if (this.compilationSuccessMessage) {
debug.log(this.compilationSuccessMessage);
}
return;
}

if (hasErrors) {
let formattedErrors = stats.compilation.errors.map(formatMessage);
const nbErrors = formattedErrors.length;
displayCompilationMessage(`Failed to compile with ${nbErrors} errors`, 'red');

if (this.notifier) {
this.notify('Error', formattedErrors[0]);
}

formattedErrors = getMaxSeverityErrors(formattedErrors, 'severity');
if (formattedErrors[0].type === 'module-not-found') {
console.log('These dependencies were not found in node_modules:');
console.log();
formattedErrors.forEach((error, index) => console.log('*', error.module));
console.log();
console.log('Did you forget to run npm install --save for them?')
} else {
formattedErrors.forEach((error, index) => displayError(index, 'Error', error));
}

return;
}

if (hasWarnings) {
const formattedWarnings = stats.compilation.warnings.map(formatMessage);
const nbWarning = formattedWarnings.length;
displayCompilationMessage(`Compiled with ${nbWarning} warnings`, 'yellow');

formattedWarnings.forEach((warning, index) => displayError(index, 'Warning', warning));
}
});

compiler.plugin('invalid', () => {
debug.clearConsole();
debug.log(chalk.cyan('Compiling...'));
});
}
}

function getMaxSeverityErrors (errors) {
const maxSeverity = getMaxInt(errors, 'severity');
return errors.filter(e => e.severity === maxSeverity);
}

function getMaxInt(collection, propertyName) {
return collection.reduce((res, curr) => {
return curr[propertyName] > res ? curr[propertyName] : res;
}, 0)
}

module.exports = NotifierPlugin;

function displayError (index, severity, error) {
if (error.file) {
debug.log(chalk.red((index + 1) + ') ' + severity) + ' in ' + error.file);
} else {
debug.log(chalk.red((index + 1) + ') ' + severity));
}
debug.log();
debug.log(error.message);
if (error.origin) {
debug.log(error.origin);
}
debug.log();
}

function displayCompilationMessage (message, color) {
debug.log();
debug.log(chalk[color](message));
debug.log();
}
Binary file added lib/webpack/plugins/tarec_logo_ico.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 11 additions & 5 deletions lib/webpack/webpack.dev.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const allLoaders = Object.assign(requireDir('../loaders'), requireDir('../loaders/dev'));
const debug = require('../utils/debug');
const chalk = require('chalk');
const NotifierPlugin = require('./plugins/notifier');

const env = 'development';

Expand All @@ -22,7 +23,7 @@ const defaultDefine = {

// https://gist.github.com/sokra/27b24881210b56bbaff7
// http://www.2ality.com/2015/12/webpack-tree-shaking.html
module.exports = function devConfig(context, args) {
module.exports = function devConfig (context, args) {
const loaders = getLoaders(allLoaders, context);
const definitions = Object.assign({}, defaultDefine, context.userConfig.define);

Expand All @@ -46,7 +47,12 @@ module.exports = function devConfig(context, args) {
title: context.pkg.name,
template: context.indexPath
}),
new AddAssetHtmlPlugin(makeDlls(context))
new AddAssetHtmlPlugin(makeDlls(context)),
new NotifierPlugin({
notificationTitle: context.pkg.name,
compilationSuccessMessage: `You application is accessible at ${chalk.cyan(`http://localhost:${context.serverPort}`)}`,
showNotifications: context.userConfig.build.showNotifications
})
]
.concat(...makeHappy(args, context, loaders))
.concat(...makeDllPlugins(context, args)),
Expand All @@ -64,7 +70,7 @@ module.exports = function devConfig(context, args) {
}
};

function makeHappy(args, context, loaders) {
function makeHappy (args, context, loaders) {
if (!context.userConfig.happypack.enabled && !args.happy) {
return [];
}
Expand All @@ -79,7 +85,7 @@ function makeHappy(args, context, loaders) {
}))
}

function makeDllPlugins(context) {
function makeDllPlugins (context) {
return context.dlls.map(dll => {
return new webpack.DllReferencePlugin({
context: context.projectDir,
Expand All @@ -88,7 +94,7 @@ function makeDllPlugins(context) {
})
}

function makeDlls(context) {
function makeDlls (context) {
if (context.dlls.length > 0) {
debug.info(`Loading ${context.dlls.length} dlls from '.tarec/dll'`);
debug.log(`If you update your dependencies, do not forget to generate new dlls with ${chalk.blue('tarec dll')}`);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"lodash.find": "4.5.1",
"lodash.groupby": "4.5.1",
"lodash.merge": "4.5.1",
"node-notifier": "4.6.0",
"open": "0.0.5",
"postcss-loader": "0.9.1",
"react-transform-catch-errors": "1.0.2",
Expand Down
Loading

0 comments on commit b625581

Please sign in to comment.