I currently write a d3 plugin. However, I want to call this plugin as property of the global d3 as in the original example:
d3.foo()
But when I do this, my configurations for rollup lead to a clash of the d3 references.
Here is one minimal example (with just one file) to illustrate:
I downloaded the original example of the d3 plugin and slightly changed the source file foo.js:
//.src/foo.js
import * as d3 from "d3";
export default function() {
return d3.select("body").append("div").text(42);
};
So here is my plugin. It uses d3 functions (d3.select()) therefor d3 is imported at the top.
My index.js looks like this:
export {default as foo} from "./src/foo";
So I export the function foo().
My rollup.config.js looks like this:
//rollup.config.js
import babel from "rollup-plugin-babel";
var globals = {
"d3": "d3",
};
export default {
entry: "index.js",
dest: "build/d3-foo.js",
format: "umd",
moduleName: "d3",
external: Object.keys(globals),
globals: globals,
plugins: [
babel({
exclude: "node_modules/**"})
]
};
I have set moduleName to "d3" since I want to call my plugin as d3.foo(). I also set the globals and external to "d3" since I don't want the d3 modules to be bundled by rollup.
To call my d3 plugin I use the following html code:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.js"></script>
<script src="./build/d3-foo.js"></script>
</head>
<body>
<script>
d3.foo();
</script>
</body>
</html>
But this does not work since the d3 namespace is refering to the d3 library which does not contain a function called d3.foo().
The generated bundle looks like this:
// build/d3-foo.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3'], factory) :
(factory((global.d3 = {}),global.d3));
}(this, (function (exports,d3) { 'use strict';
function foo () {
return d3.select("body").append("div").text(42);
}
exports.foo = foo;
Object.defineProperty(exports, '__esModule', { value: true });
})));
If instead I call the moduleName in rollup.config.js any other name (e.g. d4) I can call the plugin with d4.foo() and it works.
How do need to adjust the rollup config file to be able name my plugin d3.foo as suggested by mike bostock in his blog?
Any help would be greatly appreciated!
I found a solution how the desired final bundle should look like, thanks to Mike Bostock.
The rollup.config.js can be specified as follows:
// rollup.config.js
import babel from "rollup-plugin-babel";
import * as meta from "./package.json";
export default {
input: "index.js",
external: ["d3"],
output: {
file: `build/${meta.name}.js`,
name: "d3",
format: "umd",
indent: false,
extend: true,
// banner: `// ${meta.homepage} v${meta.version} Copyright ${(new Date).getFullYear()} ${meta.author}`,
globals: {d3: "d3"},
plugins: [
babel({
exclude: "node_modules/**"})
]
},
};
...resulting in the following bundle:
// build/d3-foo.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3));
}(this, (function (exports,d3) { 'use strict';
function foo() {
return d3.select("body").append("div").text(42);
}
exports.foo = foo;
Object.defineProperty(exports, '__esModule', { value: true });
})));
Related
I am trying to use singlejs which is part of the AWS Chime SDK for JavaScript. The singlejs sample generates amazon-chime-sdk.min.js and you are meant to be able to access the SDK via the global variable ChimeSDK. However in the latest version the generated file does not include this global variable. Here is the rollup.config.js that I'm using:
import commonjs from '#rollup/plugin-commonjs';
import resolve, { nodeResolve } from '#rollup/plugin-node-resolve';
import json from '#rollup/plugin-json';
import terser from '#rollup/plugin-terser';
export default {
input: 'src/index.js',
output: [
{
file: 'build/amazon-chime-sdk.min.js',
format: 'umd',
name: 'ChimeSDK',
sourcemap: true,
},
],
plugins: [
[nodeResolve({
browser: true,
mainFields: ['module','browser'],
})],
json(),
commonjs()
//,
//[terser()]
],
onwarn: (warning, next) => {
if (warning.code === 'CIRCULAR_DEPENDENCY') {
// TODO: Fix https://github.com/aws/amazon-chime-sdk-js/issues/107
return;
} else if (warning.code === 'EVAL') {
return;
} else if (warning.code === 'THIS_IS_UNDEFINED') {
// https://stackoverflow.com/questions/43556940/rollup-js-and-this-keyword-is-equivalent-to-undefined
return;
}
next(warning);
},
};
The source index.js is simply:
export * from 'amazon-chime-sdk-js';
When rollup --config rollup.config.js runs it builds successfully. However the generated file begins like this:
(function (factory) {
typeof define === 'function' && define.amd ? define(factory) :
factory();
})((function () { 'use strict';
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
//etc
Towards the end of the file it has:
var hasRequiredBuild;
function requireBuild () {
if (hasRequiredBuild) return build$7;
hasRequiredBuild = 1;
(function (exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultEventController = exports.DefaultDevicePixelRatioMonitor = exports.DefaultDeviceController = exports.DefaultContentShareController = exports.DefaultBrowserBehavior = exports.DefaultAudioVideoFacade =
//etc
There is no sign of the ChimeSDK global variable and my code (which worked with an earlier build of amazon-chime-sdk.min.js, based on version 2.x of the SDK and generated in the same way but with an earlier and simpler configuration) no longer runs, complaining that ChimeSDK is not defined.
It looks like something which could be fixed if I had a better understanding of Rollup and umd (also tried iife format but similar result, no global variable).
Have tried the exact configuration in the repository with same result; I've bumped the versions and disabled terser temporarily in the config above.
Update the src/index.js file with the following code and then rebuild the code with npm run bundle. Rollup recommends a default export if we have only single export.
export * as default from 'amazon-chime-sdk-js';
I'm probably just incorrect about my assumptions of how this should work, so I would appreciate it if somebody can either tell me what to change in my Rollup config or how I should use the resulting bundle.
Let me set the scene. I'm writing a library, bundling it with Rollup, and then want to write some HTML page where I import a named export from that libray. It keeps telling me that it can't find the named export.
For your convenience, I have boiled the issue down to this minimal example:
I have The following files in my project folder:
min-example.js:
export function minExample(){
return "can you find me?";
}
package.json:
{
"name": "min-example",
"version": "1.0.0",
"description": "A minimal example of an issue with rollup",
"scripts": {
"build": "rollup -c"
},
"author": "nvcleemp",
"license": "ISC",
"devDependencies": {
"rollup": "^2.58.0"
}
}
rollup.config.js
export default [
{
input: "min-example.js",
output: {
file: `min-example.bundle.js`,
name: "min-example",
format: "umd"
}
}
];
If I run npm run build, then this creates the file min-example.bundle.js:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["min-example"] = {}));
})(this, (function (exports) { 'use strict';
function minExample(){
return "can you find me?";
}
exports.minExample = minExample;
Object.defineProperty(exports, '__esModule', { value: true });
}));
I would have expected that I could use this file in the following manner:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Minimum example</title>
</head>
<body>
<script type="module">
import { minExample } from './min-example.bundle.js';
console.log(minExample());
</script>
</body>
</html>
As I said above however, this complains that it can't find the named export minExample.
Note that it does work without using Rollup:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Minimum example</title>
</head>
<body>
<script type="module">
import { minExample } from './min-example.js';
console.log(minExample());
</script>
</body>
</html>
OK, a bit more trial-and-error let to a solution. Apparently the output format should be es instead of umd, so the file rollup.config.js had to be changed to
export default [
{
input: "min-example.js",
output: {
file: `min-example.bundle.js`,
name: "min-example",
format: "es"
}
}
];
So I was indeed under the false impression that umd just combined es and cjs.
I have a gulp task to run webpack to bundle my files. I'm trying to use jQuery with typeahead.js, but the call to require('jquery') inside the typeahead.js file seems to be loading a new jQuery object instead of using the one at the "global" scope as created by ProvidePlugin. How do I add a jQuery plugin to the jQuery instance created by ProvidePlugin?
Here is my gulp task:
gulp.task("bundlejs", function(cb) {
pump([
gulp.src("js/index.js"),
webpack({
output: { filename: BUNDLE_NAME },
plugins: [
new webpack.webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
})
],
devtool: 'source-map'
}),
gulp.dest(OUTPUT_JS)
], cb);
});
And inside an imported module:
require("imports-loader?define=>false!typeahead.js");
console.info($.fn.typeahead); // <== prints 'undefined'
Just for some additional information, the top of the typeahead bundle looks like this (full file here):
(function(root, factory) {
if (typeof define === "function" && define.amd) {
define("bloodhound", [ "jquery" ], function(a0) {
return root["Bloodhound"] = factory(a0);
});
} else if (typeof exports === "object") {
module.exports = factory(require("jquery")); // <== this line gets called
} else {
root["Bloodhound"] = factory(jQuery);
}
})( ... );
What am I doing wrong?
Update
Per the comment stream, here is what I'm getting with my unique ID code.
Here is my /path/to/unique-id.js file:
var id = 0;
module.exports = function(o) {
if (typeof o.__uniqueid == "undefined") {
Object.defineProperty(o, "__uniqueid", {
value: ++id,
enumerable: false,
writable: false
});
}
return o.__uniqueid;
};
Using var u = require('/path/to/unique-id.js')...
In my Application.js file (the one where I use require("imports-loader?define=>false!typeahead.js"):
console.info(u($)); // <== prints 1
console.info(u(jQuery)); // <== prints 1
console.info(u(window.jQuery)); // <== prints 1
console.info(u(require('jquery'))); // <== prints 1
I modified typeahead.bundle.js to include some calls as well:
(function(root, factory) {
if (typeof define === "function" && define.amd) {
define("bloodhound", [ "jquery" ], function(a0) {
return root["Bloodhound"] = factory(a0);
});
} else if (typeof exports === "object") {
console.info(u(window.$)); // <== this prints 1, like I want
console.info(u(require('jquery')); // <== this prints 2
console.info(u(require('jquery')); // <== this prints 2
module.exports = factory(require("jquery")); // <== original module code
} else {
root["Bloodhound"] = factory(jQuery);
}
})( ... );
Update 2 (lame solution)
In the spirit of progressive problem solving and transparency, I got it to work in this kludgey way:
require("imports-loader?exports=>false,define=>false,jQuery=>window.$!typeahead.js");
I don't like this way at all, so any other answers would be appreciated.
Update 3 (another solution using another library)
I ended up switching to selectize and had to use an even lamer solution, but I thought it might help someone else going through the same problems as me:
In the module:
var selectizeRoot = {
jQuery: require('jquery'),
Sifter: require('sifter'),
MicroPlugin: require('microplugin')
};
require('imports-loader?define=>false,exports=>false,this=>selectizeRoot!selectize');
This forced the jQuery used in my modules (and apparently the ProvidePlugin) into the function that adds the selectize plugin to jQuery.
Is there any way to import a requirejs config in to my grunt config file? Right now I have to keep two identical versions, one in app/main.js and one in my Gruntfile.js:
module.exports = function(grunt) {
// can I import app/main.js requireConfig here?
var requireConfig = {
paths: {
jquery: 'lib/jquery'
// etc...
}
};
});
My main.js looks something like this:
requirejs.config({
paths: {
jquery: 'lib/jquery'
// etc...
}
});
define(['app'], function(app){
app.start();
});
You can use standard module pattern which supports different type of module system like following.
Your requirejs config file like this
amd-config.js
(function(factory) {
if (typeof define === 'function' && define.amd) {
// Register as an AMD module if available...
define('amd-config', [], factory());
} else if (typeof exports === 'object') {
// Next for Node.js, CommonJS, browserify...
module.exports = factory();
} else {
// setting browser global when none of the above are available
window.amdConfig = factory();
}
}
(function() {
var amdConfig = {
baseUrl: 'scripts',
paths: {
//Paths here
}
};
return amdConfig;
}));
In gruntfile you can just require like any other module.
var requireConfig = require('amd-config');
Include it normally like you do in index.html with script tag before app.js
and then in app.js use it like following.
requirejs.config(window.amdConfig);
define(['app'], function(app){
app.start();
});
PS: There are cleaner way of including it in app.js.
More cleaner than second, create global variable require and include the script before requirejs script. requirejs checks if there is global variable with name require containing object. If its there, it is used as a config object. So you dont have to call requirejs.config yourself.
You can require the file like you require other files. In that case it will be treated as a require module and you will receive the object in require callback. call your requirejs.config like following.
```
require(['amd-config'], function(amdConfig){
requirejs.config(amdConfig);
require(['app'], function(app){
app.start();
});
});
```
A simpler approach you could use, if you are using grunt to build the project. You can simply use:
options:{
mainConfigFile: "path/to/Config.js"
}
granted you need to use:
https://github.com/gruntjs/grunt-contrib-requirejs
You can try something like this:
function getRequireConfig(requireFilePath) {
var config;
var configFileContent,
_require;
_require = require;
require = {
data: {},
config : function (configParam) {
this.data = configParam;
},
get : function () {
return this.data;
}
};
configFileContent = readFileSync(requireFilePath);
eval(configFileContent);
config = require.get();
require = _require;
return config;
}
What it is doing is:
Override require definition to a custom implementation
Load require config file
Eval it so that the config function of custom implementation will be
called Get the config object from data
How can I confgure webpack to ignore amd 'define' statements in the file, like I can do it with 'require' with externals option?
as stated here: https://github.com/webpack/webpack/issues/3017#issuecomment-285954512
you could do:
module: {
rules: [
{ parser: { amd: false } }
]
}
Officially it is recommended to set define to false with imports-loader.
loaders: [
{ test: /\.js/, loader: 'imports?define=>false'}
]
But it is useful only if define is called in UMD style - something like this:
if (typeof define === 'function' && define.amd) {
define([], factory)
}
If you can change code that calls define and there is no UMD's if this is what worked for me:
var define = window['infor']; // keep webpack out of way
// use define from global scope (requirejs or other used loader) as needed
define('mymodule', ['dep1'], function (dep1) {
return {}
});