From 29e54d8a9060b0fc6eab4cb64a13302af1eb079d Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Fri, 11 Jan 2019 22:01:23 +0100 Subject: [PATCH] Make moment optional from our UMD builds Create a rollup plugin altering the UMD header to wrap optional dependencies between try/catch, which allows to load moment only when the dependency is installed. Since AMD loaders are asynchronous, `'moment'` needs to be explicitly loaded before 'chart.js' so when 'chart.js' requires moment, it's already loaded and returns synchronously (at least with requirejs). --- rollup.config.js | 9 ++++++- rollup.plugins.js | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 rollup.plugins.js diff --git a/rollup.config.js b/rollup.config.js index 9f76d957317..83ae38d67c5 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -3,6 +3,7 @@ const commonjs = require('rollup-plugin-commonjs'); const resolve = require('rollup-plugin-node-resolve'); const terser = require('rollup-plugin-terser').terser; +const optional = require('./rollup.plugins').optional; const pkg = require('./package.json'); const input = 'src/chart.js'; @@ -21,7 +22,10 @@ module.exports = [ input: input, plugins: [ resolve(), - commonjs() + commonjs(), + optional({ + include: ['moment'] + }) ], output: { name: 'Chart', @@ -42,6 +46,9 @@ module.exports = [ plugins: [ resolve(), commonjs(), + optional({ + include: ['moment'] + }), terser({ output: { preamble: banner diff --git a/rollup.plugins.js b/rollup.plugins.js new file mode 100644 index 00000000000..39c75700e5b --- /dev/null +++ b/rollup.plugins.js @@ -0,0 +1,61 @@ +/* eslint-env es6 */ + +const UMD_WRAPPER_RE = /(\(function \(global, factory\) \{)((?:\s.*?)*)(\}\(this,)/; +const CJS_FACTORY_RE = /(module.exports = )(factory\(.*?\))( :)/; +const AMD_FACTORY_RE = /(define\()(.*?, factory)(\) :)/; + +function optional(config = {}) { + return { + name: 'optional', + renderChunk(code, chunk, options) { + if (options.format !== 'umd') { + this.error('only UMD format is currently supported'); + } + + const wrapper = UMD_WRAPPER_RE.exec(code); + const include = config.include; + if (!wrapper) { + this.error('failed to parse the UMD wrapper'); + } + + let content = wrapper[2]; + let factory = (CJS_FACTORY_RE.exec(content) || [])[2]; + let updated = false; + + for (let lib of chunk.imports) { + if (!include || include.indexOf(lib) !== -1) { + const regex = new RegExp(`require\\('${lib}'\\)`); + if (!regex.test(factory)) { + this.error(`failed to parse the CJS require for ${lib}`); + } + + // We need to write inline try / catch with explicit require + // in order to enable statical extraction of dependencies: + // try { return require('moment'); } catch(e) {} + const loader = `function() { try { return require('${lib}'); } catch(e) { } }()`; + factory = factory.replace(regex, loader); + updated = true; + } + } + + if (!updated) { + return; + } + + // Replace the CJS factory by our updated one. + content = content.replace(CJS_FACTORY_RE, `$1${factory}$3`); + + // Replace the AMD factory by our updated one: we need to use the + // following AMD form in order to be able to try/catch require: + // define(['require'], function(require) { ... require(...); ... }) + // https://github.com/amdjs/amdjs-api/wiki/AMD#using-require-and-exports + content = content.replace(AMD_FACTORY_RE, `$1['require'], function(require) { return ${factory}; }$3`); + + return code.replace(UMD_WRAPPER_RE, `$1${content}$3`); + } + }; +} + +module.exports = { + optional +};