I recently started using Webpack for frontend and came across the issue with modules.
For example, I have two modules, one uses another (to be specific its angular-bootstrap-slider and bootstrap-slider). angular-bootstrap-slider was failing to initialize due the fact that Slider function was undefined.
Now I understand that I can either export Slider globally (which I did with jquery and angular libs) or import Slider in angular-bootstrap-slider (I picked that).
I don't like both options, because global exports is one of the things I wanted to avoid using webpack and importing something in library means changing it's code.
So am I missing something or maybe there is some best practice to deal with dependencies?
What you are looking for is shimming modules.
This allows you to declare that Slider is in fact to be imported from the module bootstrap-slider:
...
plugins: [
new webpack.ProvidePlugin({
'Slider': 'bootstrap-slider'
})
]
I think you can use imports-loader: https://github.com/webpack/imports-loader
imports loader for webpack
Can be used to inject variables into the scope of a module. This is
especially useful if third-party modules are relying on global
variables like $ or this being the window object.
Related
This is just something I thought today and I didn't see a lot of information so I'm going to share this weird cases and how I personally solved them (if there's a better way please comment, but meanwhile this might help others ^^)
In a regular module, you would do something like this to export your function/library/object/data:
// regular NodeJS way:
module.exports = data;
// ES6 way
// (will get transpiled to the regular way using the module variable by webpack)
export data;
default export data;
When compiling the library usually babel or tsc are used, but if for any reason you want not only to compile (transpile) your library but also pack it using webpack, you will encounter this case.
As you know, in a webpack bundle the module variable is local to the bundle (every module/file gets wrapped with a function where module is a parameter = local variable), so nothing really gets exported outside the bundle, is just nicely managed by webpack.
That means that you can't also access the contents using the regular require/import methods.
In some case you might find necessary to export outside webpack. (i.e. you are trying to build a library using webpack and you want it to be accessible by other people). This basically means you need to access the original module variable, but webpack doesn't expose it like it happened with __non_webpack_require__.
See also: Importing runtime modules from outside webpack bundle
The solution is to create our own __non_webpack_module__ (as webpack does with __non_webpack_require__.
How I did it is using webpack.BannerPlugin to inject some code outside the bundle. This code is prepended to the build after the minification is done, so it's preserved safely.
In your webpack.config.js:
plugins: [
new BannerPlugin({
raw: true,
banner: `const __non_webpack_module__ = module;`,
}),
]
And again, if you are using TypeScript, in global.d.ts:
declare const __non_webpack_module__: NodeModule;
And now, you can do something like this in your code:
__non_webpack_module__.exports = /* your class/function/data/whatever */
This will allow to import it as usual from other files
Tip: You might want to look at BannerPlugin to check other options, like include or exclude so this variable is only generated on the desired files, etc.
I'm trying to use the exif-js in a webpack+babeljs project. This library (exif-js) creates a global variable "EXIF", but I can't access it in Chrome devtools neither in my js script.
I tried to use webpack provide-plugin to make "EXIF" visible in all pages, but it is not working.
plugins: [
new webpack.ProvidePlugin({
EXIF: 'exif-js/exif.js'
})
]
What is the best way to use this library in a webpack project?
Thanks!
It looks like in CommonJS, it exports the EXIF var instead of attaching it to the global scope.
Which means with webpack you can just import it like any other module:
var exif = require('exif-js');
To demonstrate, see this webpackbin
If you actually do need it in global scope, you can manually attach it to the window object after importing it:
var exif = require('exif-js');
window.EXIF = exif;
To answer the actual title of the question regarding using scripts that set global variables, you can usually either use ProvidePlugin as you demonstrated, or you can 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 environment. This feature is typically most useful to library developers, however there are a variety of applications for it.
For example (in your webpack.config.js):
externals: {
jquery: 'jQuery'
}
However, this works differently than ProvidePlugin. Whereas ProvidePlugin resolves undeclared variables that are assumed to be global, externals resolves specific module names to global variables that are assumed to exist (eg: require('jquery') would resolve to window.$).
My code:
import $ from 'jquery'
import jQuery from 'jquery'
import owlCarousel from '../../node_modules/owlcarousel/owl-carousel/owl.carousel'
class App {
…
_initSlider() {
$("#partners-carousel").owlCarousel();
}
}
I have 'jQuery is not defined' in browser console. What's wrong?
I can use jQuery as $ in methods of this class, but not with name 'jQuery'.
According to this comment and apply it to your case, when you're doing:
import $ from 'jquery'
import jQuery from 'jquery'
you aren't actually using a named export.
The problem is that when you do import $ ..., import jQuery ... and then import 'owlCarousel' (which depends on jQuery), these are evaluated before, even if you declare window.jQuery = jquery right after importing jquery. That's one of the ways ES6 module semantics differs from CommonJS' require.
One way to get around this is to instead do this:
Create file jquery-global.js
// jquery-global.js
import jquery from 'jquery';
window.jQuery = jquery;
window.$ = jquery;
then import it in you main file:
// main.js
import './jquery-global.js';
import 'owlCarousel' from '../../node_modules/owlcarousel/owl-carousel/owl.carousel'
class App {
...
_initSlider() {
$("#partners-carousel").owlCarousel();
}
}
That way you make sure that the jQuery global is defined before owlCarousel is loaded.
#Serge You should have mentioned in your question that you are using browserify & babelify to bundle/transpile your code (I knew it from comments), this will help people find the correct answer to your question.
As of 2021, ECMA2015+/ES6+ don't allow the use of import-maps/bare-module-path natively in the browser. So basically you can't do the following directly in the browser, because the browser doesn't behave like nodejs, it doesn't understand how/where to fetch for the source of your scripts, you can't just say:
import $ from 'jquery'
import jQuery from 'jquery'
However, you can do this by the help of bundlers like WebPack which opens that door for import-maps/bare-module-path to be used in the browser. Besides, huge work is currently being done to support the implementation of import-maps directly in the browser without the need of bundlers, but it's not implemented yet. I know that this question is old enough for the OP to follow, but in general, you can use WebPack to bundle your code and import your dependencies the way you mentioned.
P.S. Regarding the answer proposed by #egel in Oct 2016 (which is an old answer with limited solutions at that time) some people asked for more clarifications. Please note the following statement by Nicolás Bevacqua regarding the scope of ES6+ modules:
Declarations in ES6 modules are scoped to that module. That means that any variables declared inside a module aren’t available to other modules unless they’re explicitly exported as part of the module’s API (and then imported in the module that wants to access them).
ES6+ module system is awesome and makes things much more organized, but can we fully implement ES6+ modules in the browser without the need of bundlers/transpilers? This is a tough question. Things may get harder when some of your JavaScript dependencies are just old/classic scripts that do not support the ES6+ modules system, and do not use the export keyword to export functions/values for you. Here, developers tend to do some workarounds to solve the problem in hand. The window object is used to attach functions/variables in order to use them across all modules. Here the window object is used as a carrier to transfer functions/data across different modules within your code base, and this is not a recommended approach though.
Below is quoted from javascript.info:
If we really need to make a window-level global variable, we can explicitly assign it to window and access as window.user. But that’s an exception requiring a good reason.
So lets say I have some small bit of library code that I develop and test in isolation. I use RequireJS during development and have a root level file that depends on 1 other file. So it's define looks something like...
// lib/main.js
define(['lib/dep1'] function(dep1) {
...
})
I run r.js on the code which results in dist/myLibrary.js, which looks something like this:
define('lib/dep1',[], function(){...})
define('lib/main',["lib/dep1"], function(dep1){...})
If I pull myLibrary.js straight into another project it won't work. Nothing is defining itself as the module for that file. But if I append an actual module definition, it works.
define('lib/dep1',[], function(){...})
define('lib/main',["lib/dep1"], function(dep1){...})
define(['lib/main'], function(lib) {
return lib;
})
And the ['lib/main'] seems to be scoped to the module, because if I have an actual lib/main in my app, it doesn't get used.
Questions:
Regarding the scoping, is that the normal behavior? The fact that lib/main is recognized as a module id from the same file rather than going to look for it someplace else. If I import 10 such libraries that all have a lib/main, they won't collide?
Is there a better way? I am at least initially unconcerned about supporting the non-AMD use case as this is all internal lib development and we all use RequireJS. So within a fully AMDed environment, is there another, better way to do this? Assuming there's no pitfall to this approach, it seems fairly simple and boilerplate to support.
I'm using an external library that consists of a "core" plus multiple "extensions". Every extension depends on the core. Think jQuery or Rx.
What I need to do is to bundle the core together with some of the extensions and provide that as a single module. On the surface, it seems that something like this should work:
// lib.js
define(
"lib",
["./Lib/lib", "./Lib/ext1", "./Lib/ext2"],
function(lib) { return lib; }
);
The problem, however, is that extensions expect the "core" to be available by the module ID of "lib". In other words, "ext1" is defined like this:
// Lib/ext1.js
define( ["lib"], function(lib) { lib.ext.someFunc = ... } );
One can spot the problem here: because the name "lib" refers to my "bundled" module instead of just the "core", it is not yet available at the time ext1 loads, so the whole chain becomes circular and falls apart.
Of course, I could map the core to "lib" and then give my bundled module a different name:
// main.js
require.config( { paths: { lib: "Lib/lib" } } );
// lib.js
define(
"bundled-lib",
["./Lib/lib", "./Lib/ext1", "./Lib/ext2"],
function(lib) { return lib; }
);
But that approach is highly undesirable for a few reasons:
It's just plain inconvenient to use a different name. There is no good common sense name that I could use instead, "lib" is pretty much the only option, and anything else will look ugly.
But more importantly, this may lead to hard-to-catch bugs. Down the road, when I have forgotten all about this little hack, I may just follow my common sense and import "lib" instead of "bundled-lib", and then my extensions will not get loaded. Or sometimes they will. If some other module which correctly imports "bundled-lib" just happens to load before the new "lib"-importing module, then it will work. Otherwise, it won't. Meaning that my application will either crash or not depending on whether certain features have or have not been used.
So the bottom line is, I would like to bundle the core with extensions, call the bundle "lib", but somehow have the extensions to only import the core, while not modifying the extensions themselves.
Any ideas anyone?
This sounds very similar to jQuery and jQuery plugins. Seems like you have an understanding of the implications, so you just need to make a decision on which method is preferred.
I would not go with a module that returns 'lib', which already has those extensions. If you feel feel that you only need single dependency where you reference extended lib, just go with your 'bundled-lib' approach.
To stick with best practices and not confuse yourself in a future I believe it would be best not to bundle, but for those modules where you rely on extensions, include dependency to your core lib and those extensions:
define(['lib', 'ext1'], function(lib){
// module definition...
});
This way it is VERY clear what are dependencies for this module. I'm sure you thought about it and I just hope it help you with making a decision.
So just a few hours after asking the question, I have found the answer on my own.
The answer is - the map config of RequireJS.
Basically, as it turns out, I can define a mapping of module names as seen by each module individually.
In particular, my problem as it is described in the question, would be solved by the following configuration:
require.config( {
map: {
'ext1': { 'lib': 'Lib/lib' }
'ext2': { 'lib': 'Lib/lib' }
}
} );
This will make both ext1 and ext2 see module 'Lib/lib' as 'lib', while not affecting all other modules.