Using a NPM module with global variables in webpack - javascript

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.$).

Related

Exporting outside webpack

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.

Use Webpack to prevent globals being created when importing an old library

I'm using Webpack and have to deal with importing an old AMD library that uses the define() call.
This is okay, but in the script, it sets window.VeryOldLibary = VeryOldLibary allowing global access.
I have tried using the import-loader to replace the window global without success.
How can I tell webpack to run the script in a closure and shim window for something else so nothing gets polluted in the global scope?

import auto invoking function binding this to window object

Inside a jest test, I need to import a javascript module that declare some global functions. This javascript module is autogenerated from django (ex jsi18n) and it's an auto invoking function
(function(globals) {
var django = globals.django || (globals.django = {});
...
}(this));
This is helpful for use the translation inside the react component. For example including a translation string in our .jsx file using a global defined function gettext()
<p>{ gettext('got it') }</p>
I've tried to import the module using the standard form
import './djangojs';
but jest report the error
TypeError: Cannot read property 'django' of undefined
because this it's undefined (strict mode). So I've tried to manual edit the module and adding at the end }(this || window)); and works correctly.
But the module is autogenerated every time. So how I can bind this to window for using the global object without manually editing the file?
Solution:
I have imported the django javascript modules directly using the setupFiles as #dfsq told
"setupFiles": [
"<rootDir>/path/to/jsi18n/djangojs.js",
"<rootDir>/path/to/js/reverse.js"
],
The problem is specific to Babel. Babel is commonly used in Jest testing rig, and it is likely used here.
Babel transform-es2015-modules-commonjs enables strict mode for both ES module imports and require, so this is undefined in imported module.
In order to make a piece of code executed in loose mode while its context is in strict mode, it should be evaluated in global context:
const fs = require('fs');
const script = fs.readFileSync(require.resolve('./djangojs')).toString();
new Function(script)(); // or (0, eval)(script);
instead of import './djangojs' or require('./djangojs').
It's expected that script doesn't have to be transpiled (it will be evaluated as is) and doesn't rely on the context (doesn't use require, because it's not available in global context). I.e. eval will work for regular script like ./djangojs but not for CommonJS module.
This may be useful if a module shouldn't be available globally or may vary between tests.
If it should be available for all tests then setupFiles should be used, as another answer suggests.

Using modules with webpack

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.

Webpack importing video.js returns an empty object

I am trying to use video.js via webpack.
I installed video.js via npm - npm install video.js --save-dev
In webpack I read that video.js should be loaded via script loader else it throws an error.
This is how I am loading video.js through the babel loader
module:
loaders: [
{
test: /video\.js/,
loader: 'script'
}
]
I got this solution from here https://github.com/videojs/video.js/issues/2750
This is my import statement
import videojs from 'video.js';
The issue that I now face is the import is returning an empty object, so when I try to do this:
var vidTag = ReactDOM.findDOMNode(this.refs.html5Video);
this.videojs = videojs(vidTag);
I get this error:
renderer-0.js:8031 Uncaught (in promise) TypeError: (0 , _video2.default) is not a function(…)
Any help will be much appreciated. I am new to ES6 / React / Webpack
Please take a look at the loader's README before copy&pasting some random code. The script-loader is not appropiate here, because it imports scripts into the global scope while skipping the whole module system.
So, if you wanted to use the script-loader, you would just write:
import "script-loader!video.js";
console.log(videojs); // should be an object now
Usually I would not recommend the use of the script-loader because it neglects the whole point of a module system where you import stuff explicitly into the local scope. In the example above, the import happens as a side-effect into the global scope which is effectively the same as just using a <script> tag with all its downsides like name clashes, etc.
There are often better alternatives to it, like the exports-loader, which appends a module.exports at the end of the module, thus turning an old-school global script into a CommonJS module.
In this particular case, however, you don't need a loader at all because video.js is already aware of a CommonJS module system. Just write import videojs from "video.js";.
There is another minor problem, however. If you compile this with webpack, it will print a warning to the console:
WARNING in ../~/video.js/dist/video.js
Critical dependencies:
13:480-487 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.
# ../~/video.js/dist/video.js 13:480-487
This is because webpack detects that this file has already been bundled somehow. Often it's better to include the actual src with all its tiny modules instead of one large dist because this way webpack is able to optimize the bundle in a better way. I've written down an exhaustive explanation about how to import legacy scripts with webpack.
Unfortunately, video.js does not include its src in the version deployed at npm, so you're forced to use the dist. In order to get rid of the error message and to improve webpack's build time, you can instruct webpack to skip video.js when parsing the code for require() statements by setting the module.noParse option in your webpack.config.js:
module: {
noParse: [
/node_modules[\\/]video\.js/
]
}
Usually it's safe to flag all pre-bundled modules (typically those with a dist folder) as noParse because they are already self-contained.
include SDN
<script src="//vjs.zencdn.net/5.11/video.min.js"></script>
webpack config:
config.externals = {
'video.js': 'videojs'
};

Categories