Webpack 5 cannot import UMD module - javascript

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';
[...]
})));

Related

Load bootstrap JS with esbuild

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".

Build both distributive and NPM package from the same source

I often make small open source tools and I don't want to limit my users. My packages are usually just one function, so this is what I want my users to get:
A JS file that they can add via the src in the script tag. That script should add my function so they can call it in a script below. Useful for users who don't want to use a package manager at all:
<script src="https://amazingCDN.com/isEven.js"></script>
<script>
isEven()
</script>
A JS file that can be published as a package, so users who use NPM can just type npm install isEven and then import my package.
Both JS files should be built from the same source. Let's say my source only contains a named function that should be added to the window and should be importable if I use Webpack. Let's say I will publish a package myself and I only want my building pipeline to generate two JS files from my source. What about CDN let's say I use jsDelivr and it can retrieve my JS file from Github and minify it so I don't care about minifying my file myself.
I tried writing my code as a module and using Browserify with the standalone flag. It actually works with CommonJS modules, but to make it work with ES modules I have to use esmify, and it just returns an object with the default key, so I can't call it like foo(), I have to call it like foo.default(). This is not what I want.
I also tried writing it as a standalone file and just doing
echo 'export default ' | paste -d'\0' - src.js > module.js
It kinda works but I wonder if there are more sophisticated and reliable solutions.
How do I achieve this?
It sounds like you might want to package your project as a UMD
https://github.com/umdjs/umd
Rollup can target UMD as output and is a bit more minimal than either webpack or browserify (especially for tiny single function libs)
isEven.mjs
function isEven(x) {
return (x % 2) === 0 && x !== 0;
}
export {
isEven
};
$ rollup isEven.mjs --format=umd --name=isEven
Will result in
(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.isEven = {}));
}(this, (function (exports) { 'use strict';
function isEven(x) {
return (x % 2) === 0 && x !== 0;
}
exports.isEven = isEven;
Object.defineProperty(exports, '__esModule', { value: true });
})));
Which I think is exactly what you want.
I solved this and created a template repo for this exact task.
You can create your repo from this template, implement your library as a NPM package with ES modules, and then on push the dist folder will be created, and there will be the distributive js file named after your library. Your users can just add it to a script tag and your library will appear at the global namespace.

Typescript does not use JS functions (Typeof)

I am trying to execute a JS code in the Angular typescript, it is a code that is not mine, the case is that the code outside Angular works perfectly, once inside the compiler I get many errors, among them I cannot solve this:
(function (global, factory) {
console.log("Adminlscn.js en uso");
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.adminlte = {}));
}(this, (function (exports) { 'use strict';....
I don't know why it doesn't catch or define or global, typescript is supposed to be an extension of JS, all js code should work
The terminal give me that mistake:
But the #types has been installed and i have this at tsconfig.json:
TypeScript doesn't know what these things are. You need to declare them for the compiler. Add this above the function, so that the compiler doesn't complain anymore. You can add typings instead of any if you want:
declare define: any;
declare global: any;

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.

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