Skip to content

Commit

Permalink
Migrate from Browserify to rollup (#5904)
Browse files Browse the repository at this point in the history
Browserify isn't optimal bundling Chart.js because it adds too many internal wrappers, doesn't handle external/global dependencies and doesn't provide a way to generate ESM builds. Therefore, it seems the right choice to switch to rollup, so move all the build process in `rollup.config.js` and make Gulp to execute `rollup -c`.

We also had to switch to Terser instead of UglifyJS because this last one contains a breaking bug. Note that tests now use the exact same rollup config as our builds (the minified one) to ensure that the bundling and minification steps don't break anything. Finally, replace the `gulp watch` task by `gulp build --watch` to be consistent with the other `unittest` and `docs` watching syntax.
  • Loading branch information
simonbrunel authored Dec 14, 2018
1 parent f2a9e66 commit 2a97ec2
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 124 deletions.
3 changes: 2 additions & 1 deletion docs/developers/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ This will install the local development dependencies for Chart.js, along with a
The following commands are now available from the repository root:

```bash
> gulp build // build Chart.js in ./dist
> gulp build // build dist files in ./dist
> gulp build --watch // build and watch for changes
> gulp unittest // run tests from ./test/specs
> gulp unittest --watch // run tests and watch for source changes
> gulp unittest --coverage // run tests and generate coverage reports in ./coverage
Expand Down
120 changes: 32 additions & 88 deletions gulpfile.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,34 @@
var gulp = require('gulp');
var concat = require('gulp-concat');
var eslint = require('gulp-eslint');
var file = require('gulp-file');
var insert = require('gulp-insert');
var replace = require('gulp-replace');
var size = require('gulp-size');
var streamify = require('gulp-streamify');
var uglify = require('gulp-uglify');
var terser = require('gulp-terser');
var util = require('gulp-util');
var zip = require('gulp-zip');
var exec = require('child-process-promise').exec;
var exec = require('child_process').exec;
var karma = require('karma');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var merge = require('merge-stream');
var collapse = require('bundle-collapser/plugin');
var yargs = require('yargs');
var path = require('path');
var fs = require('fs');
var htmllint = require('gulp-htmllint');
var pkg = require('./package.json');

var argv = yargs
.option('force-output', {default: false})
.option('silent-errors', {default: false})
.option('verbose', {default: false})
.argv;

var srcDir = './src/';
var outDir = './dist/';

var header = "/*!\n" +
" * Chart.js\n" +
" * http://chartjs.org/\n" +
" * Version: {{ version }}\n" +
" *\n" +
" * Copyright " + (new Date().getFullYear()) + " Chart.js Contributors\n" +
" * Released under the MIT license\n" +
" * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md\n" +
" */\n";

if (argv.verbose) {
util.log("Gulp running with options: " + JSON.stringify(argv, null, 2));
}

gulp.task('bower', bowerTask);
gulp.task('build', buildTask);
gulp.task('package', packageTask);
gulp.task('watch', watchTask);
gulp.task('lint-html', lintHtmlTask);
gulp.task('lint-js', lintJsTask);
gulp.task('lint', gulp.parallel('lint-html', 'lint-js'));
Expand All @@ -57,7 +38,25 @@ gulp.task('test', gulp.parallel('lint', 'unittest'));
gulp.task('library-size', librarySizeTask);
gulp.task('module-sizes', moduleSizesTask);
gulp.task('size', gulp.parallel('library-size', 'module-sizes'));
gulp.task('default', gulp.parallel('build', 'watch'));
gulp.task('default', gulp.parallel('build'));

function run(bin, args, done) {
return new Promise(function(resolve, reject) {
var exe = '"' + process.execPath + '"';
var src = require.resolve(bin);
var ps = exec([exe, src].concat(args || []).join(' '));

ps.stdout.pipe(process.stdout);
ps.stderr.pipe(process.stderr);
ps.on('close', function(error) {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}

/**
* Generates the bower.json manifest file which will be pushed along release tags.
Expand All @@ -70,7 +69,7 @@ function bowerTask() {
homepage: pkg.homepage,
license: pkg.license,
version: pkg.version,
main: outDir + "Chart.js",
main: outDir + 'Chart.js',
ignore: [
'.github',
'.codeclimate.yml',
Expand All @@ -86,52 +85,7 @@ function bowerTask() {
}

function buildTask() {

var errorHandler = function (err) {
if(argv.forceOutput) {
var browserError = 'console.error("Gulp: ' + err.toString() + '")';
['Chart', 'Chart.min', 'Chart.bundle', 'Chart.bundle.min'].forEach(function(fileName) {
fs.writeFileSync(outDir+fileName+'.js', browserError);
});
}
if(argv.silentErrors) {
util.log(util.colors.red('[Error]'), err.toString());
this.emit('end');
} else {
throw err;
}
}

var bundled = browserify('./src/chart.js', { standalone: 'Chart' })
.plugin(collapse)
.bundle()
.on('error', errorHandler)
.pipe(source('Chart.bundle.js'))
.pipe(insert.prepend(header))
.pipe(streamify(replace('{{ version }}', pkg.version)))
.pipe(gulp.dest(outDir))
.pipe(streamify(uglify()))
.pipe(insert.prepend(header))
.pipe(streamify(replace('{{ version }}', pkg.version)))
.pipe(streamify(concat('Chart.bundle.min.js')))
.pipe(gulp.dest(outDir));

var nonBundled = browserify('./src/chart.js', { standalone: 'Chart' })
.ignore('moment')
.plugin(collapse)
.bundle()
.on('error', errorHandler)
.pipe(source('Chart.js'))
.pipe(insert.prepend(header))
.pipe(streamify(replace('{{ version }}', pkg.version)))
.pipe(gulp.dest(outDir))
.pipe(streamify(uglify()))
.pipe(insert.prepend(header))
.pipe(streamify(replace('{{ version }}', pkg.version)))
.pipe(streamify(concat('Chart.min.js')))
.pipe(gulp.dest(outDir));

return merge(bundled, nonBundled);
return run('rollup/bin/rollup', ['-c', argv.watch ? '--watch' : '']);
}

function packageTask() {
Expand Down Expand Up @@ -180,17 +134,12 @@ function lintHtmlTask() {
}));
}

function docsTask(done) {
var script = require.resolve('gitbook-cli/bin/gitbook.js');
var cmd = '"' + process.execPath + '"';

exec([cmd, script, 'install', './'].join(' ')).then(() => {
return exec([cmd, script, argv.watch ? 'serve' : 'build', './', './dist/docs'].join(' '));
}).then(() => {
done();
}).catch((err) => {
done(new Error(err.stdout || err));
})
function docsTask() {
var bin = 'gitbook-cli/bin/gitbook.js';
var cmd = argv.watch ? 'serve' : 'build';

return run(bin, ['install', './'])
.then(() => run(bin, [cmd, './', './dist/docs']));
}

function unittestTask(done) {
Expand All @@ -199,9 +148,8 @@ function unittestTask(done) {
singleRun: !argv.watch,
args: {
coverage: !!argv.coverage,
inputs: argv.inputs
? argv.inputs.split(';')
: ['./test/specs/**/*.js']
inputs: (argv.inputs || 'test/specs/**/*.js').split(';'),
watch: argv.watch
}
},
// https://github.com/karma-runner/gulp-karma/issues/18
Expand All @@ -220,13 +168,9 @@ function librarySizeTask() {

function moduleSizesTask() {
return gulp.src(srcDir + '**/*.js')
.pipe(uglify())
.pipe(terser())
.pipe(size({
showFiles: true,
gzip: true
}));
}

function watchTask() {
return gulp.watch('./src/**', gulp.parallel('build'));
}
76 changes: 56 additions & 20 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
/* eslint camelcase: 0 */
/* eslint-env es6 */

const commonjs = require('rollup-plugin-commonjs');
const istanbul = require('rollup-plugin-istanbul');
const resolve = require('rollup-plugin-node-resolve');
const builds = require('./rollup.config');

module.exports = function(karma) {
var args = karma.args || {};
var config = {
frameworks: ['browserify', 'jasmine'],
const args = karma.args || {};

// Use the same rollup config as our dist files: when debugging (--watch),
// we will prefer the unminified build which is easier to browse and works
// better with source mapping. In other cases, pick the minified build to
// make sure that the minification process (terser) doesn't break anything.
const regex = args.watch ? /Chart\.js$/ : /Chart\.min\.js$/;
const build = builds.filter(v => v.output.file.match(regex))[0];

if (args.watch) {
build.output.sourcemap = 'inline';
}

karma.set({
frameworks: ['jasmine'],
reporters: ['progress', 'kjhtml'],
browsers: ['chrome', 'firefox'],
logLevel: karma.LOG_WARN,

// Explicitly disable hardware acceleration to make image
// diff more stable when ran on Travis and dev machine.
Expand All @@ -31,42 +49,60 @@ module.exports = function(karma) {
{pattern: 'test/fixtures/**/*.png', included: false},
'node_modules/moment/min/moment.min.js',
'test/index.js',
'src/**/*.js'
'src/chart.js'
].concat(args.inputs),

preprocessors: {
'test/index.js': ['browserify'],
'src/**/*.js': ['browserify']
'test/index.js': ['rollup'],
'src/chart.js': ['sources']
},

browserify: {
debug: true
rollupPreprocessor: {
plugins: [
resolve(),
commonjs()
],
output: {
name: 'test',
format: 'umd'
}
},

customPreprocessors: {
sources: {
base: 'rollup',
options: build
}
},

// These settings deal with browser disconnects. We had seen test flakiness from Firefox
// [Firefox 56.0.0 (Linux 0.0.0)]: Disconnected (1 times), because no message in 10000 ms.
// https://github.com/jasmine/jasmine/issues/1327#issuecomment-332939551
browserDisconnectTolerance: 3
};
});

// https://swizec.com/blog/how-to-run-javascript-tests-in-chrome-on-travis/swizec/6647
if (process.env.TRAVIS) {
config.customLaunchers.chrome.flags.push('--no-sandbox');
karma.customLaunchers.chrome.flags.push('--no-sandbox');
}

if (args.coverage) {
config.reporters.push('coverage');
config.browserify.transform = ['browserify-istanbul'];

// https://github.com/karma-runner/karma-coverage/blob/master/docs/configuration.md
config.coverageReporter = {
karma.reporters.push('coverage');
karma.coverageReporter = {
dir: 'coverage/',
reporters: [
{type: 'html', subdir: 'report-html'},
{type: 'lcovonly', subdir: '.', file: 'lcov.info'}
{type: 'html', subdir: 'html'},
{type: 'lcovonly', subdir: '.'}
]
};
[
karma.rollupPreprocessor,
karma.customPreprocessors.sources.options
].forEach(v => {
(v.plugins || (v.plugins = [])).unshift(
istanbul({
include: 'src/**/*.js'
}));
});
}

karma.set(config);
};
16 changes: 7 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,39 +23,37 @@
"url": "https://github.com/chartjs/Chart.js/issues"
},
"devDependencies": {
"browserify": "^16.2.3",
"browserify-istanbul": "^3.0.1",
"bundle-collapser": "^1.3.0",
"child-process-promise": "^2.2.1",
"coveralls": "^3.0.0",
"eslint": "^5.9.0",
"eslint-config-chartjs": "^0.1.0",
"eslint-plugin-html": "^5.0.0",
"gitbook-cli": "^2.3.2",
"gulp": "^4.0.0",
"gulp-concat": "^2.6.0",
"gulp-eslint": "^5.0.0",
"gulp-file": "^0.4.0",
"gulp-htmllint": "^0.0.16",
"gulp-insert": "^0.5.0",
"gulp-replace": "^1.0.0",
"gulp-size": "^3.0.0",
"gulp-streamify": "^1.0.2",
"gulp-uglify": "^3.0.0",
"gulp-terser": "^1.1.6",
"gulp-util": "^3.0.0",
"gulp-zip": "^4.2.0",
"jasmine": "^3.3.0",
"jasmine-core": "^3.3.0",
"karma": "^3.1.1",
"karma-browserify": "^5.1.1",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^1.0.1",
"karma-jasmine": "^2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"karma-rollup-preprocessor": "^6.1.1",
"merge-stream": "^1.0.1",
"pixelmatch": "^4.0.2",
"vinyl-source-stream": "^2.0.0",
"rollup": "^0.67.4",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-istanbul": "^2.0.1",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-terser": "^3.0.0",
"watchify": "^3.9.0",
"yargs": "^12.0.5"
},
Expand Down
Loading

0 comments on commit 2a97ec2

Please sign in to comment.