I understand that the import statement delivers a read-only binding to a module and I guess it depends on the module loader, but is it intended to be possible to import, decorate and re-export using ES6 modules?
For example, this fails when using rollup.js
test.plugin.js
import * as d3 from 'd3'
d3.ui = {test: 1};
export default d3;
index.js
import * as d3 from 'd3'
import './src/test.plugin'
Rollup error...
Illegal reassignment to import 'd3'
src\test.plugin.js (6:0)
4:
5: import * as d3 from 'd3'
6: d3.ui = {test: 1};
And so does this...
test.plugin.js
export default (d3) => d3.ui = {test: 1};
index.js
import * as d3 from 'd3'
import test from './src/test.plugin'
test(d3);
The first one fails because the imports are immutable and the second because the module resolution is static.
Is it supposed to be possible to use the decorator pattern with ES6 modules?
The problem is that module objects are not extensible. What is extensible is instead an object inside the module object.
Module A
let mod = { a, b, c };
// Once exported the "mod" object cannot be extended from the outside
export mod;
index.js
// What you are saying here is
import * as mod from "moduleA.js"
// mod cannot be extended here
// mod.b can be extended though
mod.b.ui = {test: 1};
When you do the default export you can extend it as default
is effectively a nested property.
Module A
let mod = { a, b, c };
// Once exported as default
export default mod;
index.js
import mod from "moduleA.js"
// mod is effectively a prop of the module object, so it can be extended
mod.d = { ... };
In your case you can do the following:
test.plugin.js
// Import d3 as a composition of props
import * as d3 from 'd3';
// Create a new object using the Object.assign operator
// You can use the spread operator too
const d3plus = Object.assign({ui: () => 'test'}, d3);
// Now d3plus will be extendable!
export default d3plus;
index.js
import d3plus from 'test.plugin.js';
console.log(d3plus.ui);
This is my answer when I got it wrong reading the spec. To be fair also some other module bundler got it wrong before hand, as es6 modules are pretty hard.
When you have module A
and you want to decorate it with new functions/things you have two choices:
A
and then use the wrapper only later onIn the former case you can do:
test.plugin.js
import * as d3 from 'd3'
d3.ui = {test: 1};
export default d3;
index.js
// Note that using this wrapper makes sure you have the extra stuff all the time
import d3plus from './src/test.plugin';
console.log(d3plus.ui);
With the second approach you have to get the result of the decorator operation:
test.plugin.js
export default (d3) => {
d3.ui = {test: 1};
// Do not forget to return the new object
return d3;
};
index.js
import * as d3 from 'd3'
import pluginify from './src/test.plugin'
// Note that this change is local to this module only
const d3plus = pluginify(d3);
console.log(d3plus.ui);
You may use some more tricks to achieve the same result but I would recommend to make it explicit the enrichment process you are applying to the module.