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.
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
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'));
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.
homepage: pkg.homepage,
license: pkg.license,
version: pkg.version,
- main: outDir + "Chart.js",
+ main: outDir + 'Chart.js',
ignore: [
'.github',
'.codeclimate.yml',
}
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() {
}));
}
-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) {
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
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'));
-}
-/* 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.
{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);
};
"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"
},
--- /dev/null
+/* eslint-env es6 */
+
+const commonjs = require('rollup-plugin-commonjs');
+const resolve = require('rollup-plugin-node-resolve');
+const terser = require('rollup-plugin-terser').terser;
+const pkg = require('./package.json');
+
+const input = 'src/chart.js';
+const banner = `/*!
+ * Chart.js v${pkg.version}
+ * ${pkg.homepage}
+ * (c) ${new Date().getFullYear()} Chart.js Contributors
+ * Released under the MIT License
+ */`;
+
+module.exports = [
+ // UMD builds (excluding moment)
+ // dist/Chart.min.js
+ // dist/Chart.js
+ {
+ input: input,
+ plugins: [
+ resolve(),
+ commonjs()
+ ],
+ output: {
+ name: 'Chart',
+ file: 'dist/Chart.js',
+ banner: banner,
+ format: 'umd',
+ indent: false,
+ globals: {
+ moment: 'moment'
+ }
+ },
+ external: [
+ 'moment'
+ ]
+ },
+ {
+ input: input,
+ plugins: [
+ resolve(),
+ commonjs(),
+ terser({
+ output: {
+ preamble: banner
+ }
+ })
+ ],
+ output: {
+ name: 'Chart',
+ file: 'dist/Chart.min.js',
+ format: 'umd',
+ indent: false,
+ globals: {
+ moment: 'moment'
+ }
+ },
+ external: [
+ 'moment'
+ ]
+ },
+
+ // UMD builds (including moment)
+ // dist/Chart.bundle.min.js
+ // dist/Chart.bundle.js
+ {
+ input: input,
+ plugins: [
+ resolve(),
+ commonjs()
+ ],
+ output: {
+ name: 'Chart',
+ file: 'dist/Chart.bundle.js',
+ banner: banner,
+ format: 'umd',
+ indent: false
+ }
+ },
+ {
+ input: input,
+ plugins: [
+ resolve(),
+ commonjs(),
+ terser({
+ output: {
+ preamble: banner
+ }
+ })
+ ],
+ output: {
+ name: 'Chart',
+ file: 'dist/Chart.bundle.min.js',
+ format: 'umd',
+ indent: false
+ }
+ }
+];
var Element = require('./core.element');
-var exports = module.exports = Element.extend({
+var exports = Element.extend({
chart: null, // the animation associated chart instance
currentStep: 0, // the current animation step
numSteps: 60, // default number of steps
onAnimationComplete: null, // user specified callback to fire when the animation finishes
});
+module.exports = exports;
+
// DEPRECATIONS
/**
}
// Base class for all dataset controllers (line, bar, etc)
-var DatasetController = module.exports = function(chart, datasetIndex) {
+var DatasetController = function(chart, datasetIndex) {
this.initialize(chart, datasetIndex);
};
});
DatasetController.extend = helpers.inherits;
+
+module.exports = DatasetController;
return pushOrConcat([], splitNewlines(callback));
}
-var exports = module.exports = Element.extend({
+var exports = Element.extend({
initialize: function() {
this._model = getBaseModel(this._options);
this._lastActive = [];
*/
exports.positioners = positioners;
+module.exports = exports;
/**
* @namespace Chart.helpers.canvas
*/
-var exports = module.exports = {
+var exports = {
/**
* Clears the entire canvas associated to the given `chart`.
* @param {Chart} chart - The chart for which to clear the canvas.
}
};
+module.exports = exports;
+
// DEPRECATIONS
/**
'use strict';
var moment = require('moment');
-moment = typeof moment === 'function' ? moment : window.moment;
-
var defaults = require('../core/core.defaults');
var helpers = require('../helpers/index');
var Scale = require('../core/core.scale');