Webpack: Using a jQuery Plugin in jQuery set up by ProvidePlugin - javascript

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.

Related

Why is rollup not generating a global variable despite output name being specified?

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';

How to set up rollup.js with a d3 plugin?

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 });
})));

Configure webpack to ignore define statement

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 {}
});

Using a script that exposes a global var in a Webpack build

In my (first) Webpack build I'm having trouble comprehending how I should be loading a script that simply exposes a global var.
The script I'm trying to load is basically something like this:
//File: MyLibrary.js
var MyLibrary = (function(window) {
function MyLibrary() {
// Do librarious stuff
}
return MyLibrary;
})(typeof window !== 'undefined' ? window : null);
I figured I should use the exports-loader since according to the docs it should be just the thing for this case:
The file sets a variable in the global context with var XModule = ....
var XModule = require("exports?XModule!./file.js")
So I put this in my config:
module: {
loaders: [
{
test: /MyLibrary\.js$/,
loader: "exports?MyLibrary!./MyLibrary.js"
}
]
}
But this results in an error:
ERROR in Loader MyLibrary.js didn't return a function
which confuses me, since it's not supposed to return a function, that's the whole point why I'm using this particular loader...
So how should I load the script?
you don't specify the path to the library in loader property, simply:
module: {
loaders: [
{
test: /MyLibrary\.js$/,
loader: "exports?MyLibrary"
}
]
}

require.js modules not loading properly

I have my bootstrap file which defines the require.js paths, and loads the app and config modules.
// Filename: bootstrap
// Require.js allows us to configure shortcut alias
// There usage will become more apparent futher along in the tutorial.
require.config({
paths: {
bfwd: 'com/bfwd',
plugins: 'jquery/plugins',
ui: 'jquery/ui',
jquery: 'jquery/jquery.min',
'jquery-ui': 'jquery/jquery-ui.min',
backbone: 'core/backbone.min',
underscore: 'core/underscore.min'
}
});
console.log('loading bootstrap');
require([
// Load our app module and pass it to our definition function
'app',
'config'
], function(App){
// The "app" dependency is passed in as "App"
// Again, the other dependencies passed in are not "AMD" therefore don't pass a parameter to this function
console.log('initializing app');
App.initialize();
});
app.js is loaded like it should, and it's dependencies are loaded. it's define callback is called, with all the correct dependencies passed as arguments. No error is thrown. HOWEVER, in the bootstrap's callback, App is undefined! no arguments are passed. What can be causing this? Here's my app file ( modified for space)
// Filename: app.js
define(
'app',
[
'jquery',
'underscore',
'backbone',
'jquery-ui',
'bfwd/core',
'plugins/jquery.VistaProgressBar-0.6'
],
function($, _, Backbone){
var initialize = function()
{
//initialize code here
}
return
{
initialize: initialize
};
}
);
As far as I am aware you should probably just drop the 'app' string in your app.js define method.
// Filename: app.js
define([
'jquery',
'underscore',
'backbone',
'jquery-ui',
'bfwd/core',
'plugins/jquery.VistaProgressBar-0.6'
], function($, _, Backbone){
...
);
Ok I had the same problem, the key is the jquery path alias you define. It turns out that RequireJS has some special handling for jquery. If you use the jquery module name it will do a little bit of magic there.
Depending on what you have in jquery.min.js it may cause some problems, also the jquery plugin you have there may be a problem. Here are the relevant lines of code from the RequireJS source:
if (fullName) {
//If module already defined for context, or already loaded,
//then leave. Also leave if jQuery is registering but it does
//not match the desired version number in the config.
if (fullName in defined || loaded[id] === true ||
(fullName === "jquery" && config.jQuery &&
config.jQuery !== callback().fn.jquery)) {
return;
}
//Set specified/loaded here for modules that are also loaded
//as part of a layer, where onScriptLoad is not fired
//for those cases. Do this after the inline define and
//dependency tracing is done.
specified[id] = true;
loaded[id] = true;
//If module is jQuery set up delaying its dom ready listeners.
if (fullName === "jquery" && callback) {
jQueryCheck(callback());
}
}
For me I have it setup such that I have a file called /libs/jquery/jquery.js which returns the jquery object (just a wrapper for RequireJS). What I ended up doing was simply changing the path alias from jquery to $jquery. This helps avoid the undesired magic behavior.
In the original tutorial I read they use jQuery which also works.
This is a simple example that might help get you started:
I've created a very simple module:
https://gist.github.com/c556b6c759b1a41dd99d
define([], function () {
function my_alert (msg) {
alert(msg);
}
return {
"alert": my_alert
};
});
And used it in this fiddle, with only jQuery as an extra dependency:
http://jsfiddle.net/NjTgm/
<script src="http://requirejs.org/docs/release/1.0.7/minified/require.js"></script>
<script type="text/javascript">
require.config({
paths: {
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min",
"app": "https://gist.github.com/raw/c556b6c759b1a41dd99d/20d0084c9e767835446b46072536103bd5aa8c6b/gistfile1.js"
},
waitSeconds: 40
});
</script>
<div id="message">hello</div>
<script type="text/javascript">
require( ["jquery", "app"],
function ($, app) {
alert($.fn.jquery + "\n" + $("#message").text());
app.alert("hello from app");
}
);
</script>
This is how I do it with requirejs and backbone:
first, define main or bootstrap file with config:
// bootstrap.js
require.config({
paths: {
text: 'lib/text',
jQuery: 'lib/jquery-1.7.2.min',
jqueryui: 'lib/jquery-ui-1.8.22.custom.min',
Underscore: 'lib/underscore-1.3.3',
Backbone: 'lib/backbone-0.9.2'
},
shim: {
'Underscore': {
exports: '_'
},
'jQuery': {
exports: 'jQuery'
},
'jqueryui': {
exports: 'jqueryui'
},
'Zepto': {
exports: '$'
},
'Backbone': {
deps: ['Underscore', 'Zepto'],
exports: 'Backbone'
}
});
define(function (require) {
'use strict';
var RootView = require('src/RootView');
new RootView();
});
Then, I use this syntax to load my scripts. I find it easier than the array notation to just define my depencies via var declarations.
// rootview.js
define(function (require) {
'use strict';
var $ = require('Zepto'),
Backbone = require('Backbone'),
LoginView = require('./LoginView'),
ApplicationView = require('./ApplicationView'),
jQuery = require('jQuery').noConflict();
return Backbone.View.extend({
// append the view to the already created container
el: $('.application-container'),
initialize: function () {
/* .... */
},
render: function () {
/* .... */
}
});
});
Hope it helps!
This is a bit late, but I just had this problem. My solution can be found here:
https://stackoverflow.com/questions/27644844/can-a-return-statement-be-broken-across-multiple-lines-in-javascript
I posted that question for a different reason, to ask why my fix worked in the first place. Elclanrs provided the perfect answer. To make a long story short, the undefined is probably appearing due to javascript's automatic semicolon insertion: Automatic semicolon insertion & return statements
If you try changing the position of the curly bracket from underneath to directly after the return statement, I think your problem will disappear.
// Filename: app.js
define(
.
.
.
function($, _, Backbone){
var initialize = function()
{
//initialize code here
}
return {
initialize: initialize
};
}
);

Categories