Load bootstrap JS with esbuild - javascript

I'm trying to import the bootstrap JS in my esbuild build:
// index.js
import 'jquery-import'
import 'bootstrap'
// jquery-import.js
import jquery from 'jquery'
window.jQuery = window.$ = jquery;
The build target is esm. The problem is that the following bootstrap code is run:
factory(exports, require('jquery'), require('popper.js'));
This does not use my global window.jQuery but rather requires its own, which means that my global window.$.fn doesn't include the Bootstrap extensions. Also, I think that this makes jQuery load twice.
Is there a way of fixing this without adapting the bootstrap source code?
A little more background on this:
The bootstrap source code begins with the following statements (lightly adapted for better readability):
(function (global, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
// Option 1: The route taken by esbuild
factory(exports, require('jquery'), require('popper.js'));
} else if (typeof define === 'function' && define.amd) {
// Option 2
define(['exports', 'jquery', 'popper.js'], factory);
} else {
// Option 2
(global = global || self, factory(global.bootstrap = {}, global.jQuery, global.Popper));
}
}(this, function (exports, $, Popper) { 'use strict';
I have tried commenting out individual options, and neither 1, 2 or 3 work by just taking the already globally defined window.jQuery.
Many thanks for your help!

The reason it's not working, is for some reason your esbuild is trying to import a UMD file (typically used for old Node and browser hybrid code).
Having looked at bootstrap's package.json, it seems that "module": "dist/js/bootstrap.js", which exports bootstrap as modules, which is probably what esbuild should be referencing.
So to fix this without fixing the esbuild config, I'd conjecture you could try:
import 'bootstrap/dist/js/bootstrap'
...or to fix it with esbuild config, add this:
const buildOpts = {
mainFields: ['module', 'main'],
}
This setting tells the bundler to prioritises module, then main entrypoints.
Normally this is module before main, but it may be the other way round because you set platform to "node".

Related

Webpack 5 cannot import UMD module

I recently upgraded to Webpack 5 and I'm having some trouble importing UMD modules.
In particular, I'm trying to import Leaflet. Leaflet seems to export a UMD module created by rollup.js, which looks like this:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.L = {})));
}(this, (function (exports) { 'use strict';
[...]
})));
As you can see, it first tries to use CommonJS (if exports and module is defined), then it tries to use AMD (if define is defined), and otherwise it tries to put itself to the global scope by referencing this from the module context.
Webpack (without any loaders) compiles it to this:
(function (global, factory) {
typeof exports === 'object' && "object" !== 'undefined' ? factory(exports) :
typeof define === 'function' && __webpack_require__.amdO ? define(['exports'], factory) :
(factory((global.L = {})));
}(undefined, (function (exports) { 'use strict';
[...]
})));
Concretely, what it did was:
Replace typeof module with "object"
Replace define.amd with __webpack_require__.amd0
Replace this with undefined
During the build, webpack gives me a warning: export 'default' (imported as 'L') was not found in 'leaflet' (possible exports: ). When I open the compiled bundle in the browser, I get the error Uncaught TypeError: Cannot set property 'L' of undefined.
What happens is that CommonJS fails (because exports is not defined), AMD fails (because define is not defined) and finally setting the global also fails because this is replaced with undefined.
According to the Webpack docs, Webpack should universally support importing CommonJS and AMD modules, but it seems in this case neither of the two work.
What can I do to fix this?
I narrowed the problem down and discovered that the problem was caused by the fact that I was using an imports-loader in a certain way.
Before the update, I used an imports-loader to add any needed transitive dependencies that my dependencies were not importing, in particular CSS files. The configuration looked something like this:
{ module: { rules: [ {
test: /\/node_modules\/leaflet\/.*\.js$/,
loader: "imports-loader?asdf=leaflet/dist/leaflet.css"
} ] } }
After the upgrade, webpack didn't accept the string syntax for loaders anymore, and imports-loader also changed its syntax, so I changed it to:
{ module: { rules: [ {
test: /\/node_modules\/leaflet\/.*\.js$/,
loader: "imports-loader",
options: {
imports: "default leaflet/dist/leaflet.css asdf"
}
} ] } }
This seemed to do the job and properly included the necessary CSS file. However, when I discovered that this part of the config is the cause of the problem, I dug deeper into the documentation of imports-loader. It turns out the configuration I was using was now generating the following line of code:
import asdf from "leaflet/dist/leaflet.css";
(and I could have used "side-effects leaflet/dist/leaflet.css" to get the same result without having to use asdf as a bogus import name). While this properly imported the CSS file, it probably caused webpack to think that this file is a proper ES module, disabling support for CommonJS/AMD/UMD modules. I changed the configuration of imports-loader to the following:
{ module: { rules: [ {
test: /\/node_modules\/leaflet\/.*\.js$/,
loader: "imports-loader",
options: {
imports: "pure leaflet/dist/leaflet.css",
type: "commonjs"
}
} ] } }
According to the docs, this creates the following code line:
require("leaflet/dist/leaflet.css");
After making this change, it seems to work without problems.
Interestingly, it seems that webpack actually detects the whole UMD block rather than just providing the exports and define variables, so it compiles the Leaflet code into this now:
(function (global, factory) {
true ? factory(exports) :
0;
}(this, (function (exports) { 'use strict';
[...]
})));

How to use CodeMirror runmode standalone version in a webpack setup

I'm using CodeMirror as a simple syntax highlighter (≠ code editor) with the runmode addon. In order to save some bandwidth to users, i want to switch to runmode-standlone.js version, which is also included in the package but doesn't bring the whole CodeMirror power and weight attached.
The problem, as referenced by the library author is that CodeMirror library is listed as dependency of each mode (the lang packages):
you have to somehow convince your bundler to load the runmode-standalone shim instead of the regular core library for that dependency. — Author
Anyone managed to do this?
What i've already tried
Note, i've setup an alias so when i reference codemirror, it imports the standalone version (just FYI, because i've also tried without it)
// webpack.config.js
resolve: {
alias: {
codemirror$: path.resolve(__dirname, "node_modules/codemirror/addon/runmode/runmode-standalone.js")
}
}
All combinations of script-loader, imports-loader to try to execute mode modules as normal scripts
import "codemirror";
// same as import "codemirror/addon/runmode/runmode-standalone.js";
// nope
import "script-loader!codemirror/mode/javascript/javascript";
// nope
import "imports-loader?define=>false!codemirror/mode/javascript/javascript";
// nope
import "imports-loader?define=>false,module=>false,require=>false!codemirror/mode/javascript/javascript";
A similar strategy to what's suggested for ignoring moment.js locales (repo, SO thread, using webpackIgnorePlugin
// codemirror/mode/javascript/javascript.js import looks like
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
...
So i've tried to make it ignore
new webpack.IgnorePlugin({
resourceRegExp: /^\.\.\/lib\/codemirror$/,
contextRegExp: /^codemirror\/mode\/(.*)/
});
Setup details
Using codemirror#5.47.0 and webpack#4.29.6
Configure webpack to use externals.
The externals configuration option provides a way of excluding dependencies from the output bundles. Instead, the created bundle relies on that dependency to be present in the consumer's (any end-user application) environment.
externals: {
'../lib/codemirror': 'CodeMirror', // for mode/meta.js
'../../lib/codemirror': 'CodeMirror' // for mode/[mode]/[mode].js
}

WebPack replace vendor require call with global variable

Having trouble with WebPack. I have a vendor library (saying ChildVendor), which implements requireJS and commonJS compatibility, so, I need to require it in my WebPack project like var lib = require('./childVendor');. This ChildVendor library has a dependency (saying SuperVendor), and both of them are requirejs- and commonjs-adapted, so, the heading of childVendor.js looks like:
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(["superVendor"], factory);
} else if (typeof exports === 'object') {
module.exports = factory(require('superVendor'));
} else {
root.Shepherd = factory(root.SuperVendor);
}
}(this, function(SuperVendor) { /*...*/ }));
The main problem is that I need to include that SuperVendor library globally on html-file manually (so, it is just initialized as window.SuperVendor), because it should be used by other third-party libraries.
To solve this, I have tried webpack.ProvidePlugin, like
plugins: [
new webpack.ProvidePlugin({
'superVendor': 'SuperVendor'
})
],
but an error is still the same (Module not found: Error: Can't resolve 'superVendor' in '...').
ProvidePlugin is not the solution for what you want to do. The configuration you've set with it tells Webpack:
Whenever superVendor is encountered as a free variable in a module, load the module SuperVendor and set the variable superVendor to what exported by SuperVendor.
In other words if you have a module that contains this:
superVendor.someMethod();
Webpack interprets it as if it were:
var superVendor = require("SuperVendor");
superVendor.someMethod();
What you should use it the externals configuration option:
externals: {
'superVendor': 'SuperVendor'
}
This tells Webpack that if the module superVendor is required it should be sought from the external environment with the name SuperVendor.

Why is webpack looking for jQuery when the module doesn't require it?

I'd like to preface this question by saying it is hard for me to understand what exactly is going on in this issue and I apologise if it's not up to Stackoverflow's Q&A standards.
I'm trying to import an NPM module that's then compiled by Webpack on our project. This is the module in question, and this is the file that's giving us problems.
At the end of that file, there is the export code:
if (typeof(module) !== 'undefined')
{
module.exports = window.Swiper;
}
else if (typeof define === 'function' && define.amd) {
define([], function () {
'use strict';
return window.Swiper;
});
}
And that's it. Throughout the file there are some references to jQuery but nothing that suggests webpack should include it as a module. Do a search for "jQuery" and see for yourself.
So why is it then when we import the plugin like so:
import Swiper from 'swiper'
We get the following error?
No where else in the module's package.json or similar does it describe jQuery as a dependency. I have no idea what's happening. If it's any use, here is the code that seems to throw the error in the compiled bundle:
else if (typeof define === 'function' && define.amd) {
define([], function () {
'use strict';
return window.Swiper;
});
}
//# sourceMappingURL=maps/swiper.js.map
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(!(function webpackMissingModule() { var e = new Error("Cannot find module \"jquery\""); e.code = 'MODULE_NOT_FOUND'; throw e; }()))))
I think Swiper itself requires jQuery. Because you are in an AMD/webpack environment, Swiper is trying to "import/require" it and webpack cannot find it.

How to declare dependencies inside an amd compatability check without creating 404/mismatched anonymous module during builds?

I'm struggling to add a plugin using the following AMD compatability setup to my application:
The snippet from foo.js in question:
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(exports);
}
window.foo = {};
module(window.foo, {hex_sha256: hex_sha256});
}(['exports', 'sha256'], function (exports, sha256) {
// foo
}));
I'm setting foo as a dependency in another module called bar like so:
define(["jquery", "foo", "sha256", "c", "d"], function() {
// stuff
});
And inside the r.js optimizer I define bar to be:
{
name: "bar"
, include: ["foo", "sha256", "c", "d"]
, exclude: ["jquery"]
},
This generates the bar.js file allright including the abovementioned files.
However when I load my built application, there are still two requests being triggered to foo.js and sha256.js, which both 404 (optimizer cleans up built files) and who are inside my bar.js build layer.
Question:
I'm a little lost with the module amd check and am suspecting it being responsible for the unwanted call. Can anyone shed some light on what I should modify in the AMD check to make foo callable form inside a build layer?
Thanks!
EDIT:
I tried shimming like so:
shim: {
'foo': { deps: ['sha256'] }
},
which takes care of the 404, but returns an:
Mismatched anonymous define() module: function (exports, sha256) {....
error, so I'm still stuck assuming the hardcoded dependency to sha256 in my AMD check to be the culprit. Maybe this helps.
EDIT:
I'm pretty sure my problems stem from the dependency declaration inside the AMD compatability check.
The two solutions below require modifications to the source of foo. Since you can control it, they are both viable:
(Quick and dirty) Hardcode the module name in the define call:
// foo.js
if (typeof define === 'function' && define.amd) {
return define("foo", dependencies, module);
}
(Cleaner) See the code of Knockout.js at the beginning. I am using it in a project and it seems to compile good with r.js. I tried a simple project setup like yours, and it works; so you have to replace the AMD compatibility code as follows:
(function(factory) {
// Support three module loading scenarios
if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
// [1] CommonJS/Node.js
var target = module['exports'] || exports; // module.exports is for Node.js
var hex_sha256 = require("sha256");
factory(target, hex_sha256);
} else if (typeof define === 'function' && define['amd']) {
// [2] AMD anonymous module
define(['exports','sha256'], factory);
} else {
// [3] No module loader (plain <script> tag) - put directly in global namespace
factory(window['foo'] = {}, hex_sha256);
}
}(function(exports, sha256){
// same foo
}));
I do not know what magic happens inside r.js and this works...

Categories

Resources