I'm exploring the idea of using Webpack with Backbone.js.
I've followed the quick start guide and has a general idea of how Webpack works, but I'm unclear on how to load dependency library like jquery / backbone / underscore.
Should they be loaded externally with <script> or is this something Webpack can handle like RequireJS's shim?
According to the webpack doc: shimming modules, ProvidePlugin and externals seem to be related to this (so is bundle! loader somewhere) but I cannot figure out when to use which.
Thanks
It's both possible: You can include libraries with a <script> (i. e. to use a library from a CDN) or include them into the generated bundle.
If you load it via <script> tag, you can use the externals option to allow to write require(...) in your modules.
Example with library from CDN:
<script src="https://code.jquery.com/jquery-git2.min.js"></script>
// the artifial module "jquery" exports the global var "jQuery"
externals: { jquery: "jQuery" }
// inside any module
var $ = require("jquery");
Example with library included in bundle:
copy `jquery-git2.min.js` to your local filesystem
// make "jquery" resolve to your local copy of the library
// i. e. through the resolve.alias option
resolve: { alias: { jquery: "/path/to/jquery-git2.min.js" } }
// inside any module
var $ = require("jquery");
The ProvidePlugin can map modules to (free) variables. So you could define: "Every time I use the (free) variable xyz inside a module you (webpack) should set xyz to require("abc")."
Example without ProvidePlugin:
// You need to require underscore before you can use it
var _ = require("underscore");
_.size(...);
Example with ProvidePlugin:
plugins: [
new webpack.ProvidePlugin({
"_": "underscore"
})
]
// If you use "_", underscore is automatically required
_.size(...)
Summary:
Library from CDN: Use <script> tag and externals option
Library from filesystem: Include the library in the bundle.
(Maybe modify resolve options to find the library)
externals: Make global vars available as module
ProvidePlugin: Make modules available as free variables inside modules
Something cool to note is that if you use the ProvidePlugin in combination with the externals property it will allow you to have jQuery passed into your webpack module closure without having to explicitly require it. This can be useful for refactoring legacy code with a lot of different files referencing $.
//webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: '[name].js'
},
externals: {
jquery: 'jQuery'
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
})
]
};
now in index.js
console.log(typeof $ === 'function');
will have a compiled output with something like below passed into the webpackBootstrap closure:
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
/* WEBPACK VAR INJECTION */(function($) {
console.log(typeof $ === 'function');
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
module.exports = jQuery;
/***/ }
/******/ ])
Therefore, you can see that $ is referencing the global/window jQuery from the CDN, but is being passed into the closure. I'm not sure if this is intended functionality or a lucky hack but it seems to work well for my use case.
I know this is an old post but thought it would be useful to mention that the webpack script loader may be useful in this case as well. From the webpack docs:
"script: Executes a JavaScript file once in global context (like in script tag), requires are not parsed."
http://webpack.github.io/docs/list-of-loaders.html
https://github.com/webpack/script-loader
I have found this particularly useful when migrating older build processes that concat JS vendor files and app files together. A word of warning is that the script loader seems only to work through overloading require() and doesn't work as far as I can tell by being specified within a webpack.config file. Although, many argue that overloading require is bad practice, it can be quite useful for concating vendor and app script in one bundle, and at the same time exposing JS Globals that don't have to be shimmed into addition webpack bundles. For example:
require('script!jquery-cookie/jquery.cookie');
require('script!history.js/scripts/bundled-uncompressed/html4+html5/jquery.history');
require('script!momentjs');
require('./scripts/main.js');
This would make $.cookie, History, and moment globally available inside and outside of this bundle, and bundle these vendor libs with the main.js script and all it's required files.
Also, useful with this technique is:
resolve: {
extensions: ["", ".js"],
modulesDirectories: ['node_modules', 'bower_components']
},
plugins: [
new webpack.ResolverPlugin(
new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
)
]
which is using Bower, will look at the main file in each required libraries package.json. In the above example, History.js doesn't have a main file specified, so the path to the file is necessary.
Related
I've just started looking into all the fancy stuff Javascript has to offer mainly Webkit. I've got a decent sized application which uses the revealing module pattern. These modules make calls to public functions of other modules to make calculations and return results.
Using this pattern was working great until I started to contemplate having dozens of script includes in my html pages. So, here we are...
For the purposes of the question, I have made a much simpler application to demonstrate what is going wrong for me.
I have two javascript modules contained within their own files:
// one.js
require("babel-loader?two!./two.js");
var one = (function(){
two.sayHello();
})()
// two.js
var two = (function(){
var sayHello = function(){
console.log('Hello World');
}
return {
sayHello: sayHello
}
})()
What I'm trying to do is use sayHello function from two.js within one.js.
So, firstly, I installed Webpack and exports-loader and created the following configuration file:
module.exports = {
entry: './index.js',
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.jsx$/,
loader: "babel-loader",
query: {
presets: ['es2015']
}
}
]
}
}
index.js is my entry file and simply includes the following single line:
require('./one.js');
Now, when trying to run this code, I'm getting the following error in the console:
Uncaught ReferenceError: two is not defined
With a little more digging, I found that my compiled bundle.js file was throwing the following Error when trying to import two.js:
throw new Error("Module parse failed: \testing_env\webpack\two.js
'import' and 'export' may only appear at the top level (2:2)\nYou may
need an appropriate loader to handle this file type.
Obviously i'm doing something wrong, I'm just not sure what. I've tried both exports-loader and babel-loader but with no joy.
Which loader should I be using to parse module dependencies?
Any help would be greatly appreciated.
Out of the box webpack has support for CommonJS (which is the same module system that Nodejs implements). This means that you need to use the require/export syntax to make use of the modules.
To export something in a module you simply do:
// a.js
function a() { console.log("I'm a module '); }
module.exports = a;
and in your other module you do:
// b.js
const a = require('./a.js');
a(); // I'm a module
require() will simply returns the exports object. Whatever you put on it you will be able to retrieve in another module.
This is why webpack is a module bundler, it comes with a built in module system. The revelaing module pattern is more practical if you load your JS files directly in the browser without a module bundler, and you want to incapsulate your data (in "modules").
The Revelaing module pattern is useful when you don't have a module system and you simply load up your JS files in the browser. Till recenlty when ES Modules were introduced in the browser, JavaScript didn't have a module system of it's own. This is why people came up with the module pattern, to have a way to incapsulate logic in the browser. But now because of tools like webpack and now we have natively the ES Modules system, we can have a more better way to incapsulate our logic.
Does this make sense?
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.$).
I'd like to better understand the differences between how promises are implemented in webpack. Normally, blissful ignorance was enough to get by as I mostly develop apps, but I am definately a little confused in how to properly develop a plugin/tool/lib.
In creating apps the two following approaches never caused any issues; I guess mostly cause it didn't matter
webpack.config.js - using babel-polyfill as an entry point
module.exports = {
entry: {
foo: [
'core-js/fn/promise', <-- here
'./js/index.js'
]
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader'
}
]
}
}
Q: In this approach, since it's a polyfill it modifies the global Promise?
webpack config - shimming using webpacks provide plugin
module.exports = {
entry: './js/index.js',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader'
}
]
},
plugins: [
new webpack.ProvidePlugin({
Promise: 'es6-promise' <-- here
})
]
};
Q: Does this mean that the Promise is a module only specific to the webpack bundling process? Does the transpiled ES5 code have a local copy or es6-promise? Does it patch the global Promise?
In regards to creating a jquery plugin/tool/lib which is using babel for transpilation...
webpack.config.js - using babel-plugin-transform-runtime
module.exports = {
entry: {
foo: [
'./js/start.js'
]
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader'
}
]
}
}
.babelrc
{
"presets": [ "es2015" ],
"plugins": ["transform-runtime"] <--here
}
start.js
require('babel-runtime/core-js/promise').default = require('es6-promise'); <--here
require('plugin');
Q: This aliases the es6-promise to the babel-runtime promise and is not global but only local to the tool?
Polyfill in webpack entry
entry: ['core-js/fn/promise', './index.js']
This has the same effect as if you imported it at the top of your entry point.
In this approach, since it's a polyfill it modifies the global Promise?
Yes, this polyfill changes the global Promise. Calling it a polyfill usually means that it patches the global built-ins, although this is not strictly adhered to. If they don't change existing APIs but only provide the functionality, they are sometimes called Ponyfills.
Webpack shimming with ProvidePlugin
new webpack.ProvidePlugin({
Promise: 'es6-promise'
})
The ProvidePlugin will import the configured module at the beginning of the module that uses it when the corresponding free variable is found. A free variable is an identifier that has not been declared in the current scope. Global variables are free variables in all local scopes.
Whenever a free Promise is encountered, webpack will add the following to the beginning of the module:
var Promise = require('es6-promise');
Does this mean that the Promise is a module only specific to the webpack bundling process?
That is correct, because ProvidePlugin is webpack specific and it's very unlikely that any other tool will respect any webpack settings.
Does the transpiled ES5 code have a local copy or es6-promise?
As with any other module, it is included once by webpack and all imports refer to that module.
Does it patch the global Promise?
It will only modify the global Promise if the imported module does it explicitly. The es6-promise you're using, does not patch the global by default as shown in Auto-polyfill.
Babel transform runtime
{
"plugins": ["transform-runtime"]
}
The babel-plugin-transform-runtime uses core-js to provide missing functionalities like Promise. As you will recall, I said that core-js modifies the global Promise. This is not true for this case, because babel uses the version that doesn't pollute the global namespace, which is in core-js/library as mentioned in the core-js README. For example:
const Promise = require('core-js/library/fn/promise');
Babel will import the core-js Promise and replace Promise with the imported variable. See also the example in babel-plugin-transform-runtime - core-js aliasing. This is essentially the same thing as webpack's ProvidePlugin except that babel does not bundle up the modules, so it's just adding the import.
This aliases the es6-promise to the babel-runtime promise and is not global but only local to the tool?
It is not global because it's just a module. Babel takes your JavaScript and outputs some other JavaScript where the configured features are transpiled to ES5. You will run or bundle the resulting JavaScript and it's practically the same as if you had written ES5 in the first place.
require('babel-runtime/core-js/promise').default = require('es6-promise');
With that you modify the export and therefore the modules will use es6-promise instead. But overwriting an export is not a good idea, especially since the imports of ES modules are immutable in the spec. Babel is currently not spec-compliant in that regard. For more details see Making transpiled ES modules more spec-compliant.
Which one should you use?
It depends on what you're doing. Apart from the difference of whether they change globals or not, you can choose whichever you prefer. For instance using babel's transform runtime allows you to use it with any tool that uses babel, not just webpack.
For a library
None.
Leave the polyfill to the application developer. But you might mention that it depends on a certain feature and when the user wants to use the library in an environment that doesn't support the feature, they have to polyfill it. It's also fairly reasonable to assume that Promises are widely supported and if an application targets older environments, they will very likely have polyfilled it already. Keep in mind that this doesn't mean that you shouldn't transpile new features / syntax. This is specifically for new functionality like Promise or String.prototype.trimLeft.
For a tool
That also depends on your definition of a tool. Let's assume a tool is a piece of software that is used by developers (e.g. webpack, eslint, etc.). In that case it is exactly the same as for any app, at the end of the day it's just another app but only targeting developers. Specifically speaking about command line tools, you should decide what minimum Node version you want to support and include anything that is needed for that, you can specify that in your package.json in the engines field.
For a plugin
Plugin is a very broad term and can be anything between a library and an app. For example a webpack plugin or loader should work as is, whereas a jQuery plugin will be part of a web app and you should treat it as a library (they should probably be called library instead of plugin). Generally you want to match the guidelines of whatever you're extending. Have a look at it and see what they are targeting. For example webpack currently supports Node verions >=4.3.0, so your plugin should too.
I downloaded RequireJS single page app sample. In the www/lib folder, I put the XRegExp source xregexp-all.js (version 2.0.0).
In www/app/main.js, I added:
var xregexp = require('xregexp-all');
print(typeof(xregexp));
The console output is:
undefined
However, requireJS doesn't emit any error messages. Am I doing something wrong?
Yes, you are doing something wrong. Only some versions of the files distributed for XRegExp perform the required define call that defines them as RequireJS modules. The one you are using does not contain that call. You can determine this for yourself by searching for the define( string in the file.
You'll need to add a shim to your configuration so that RequireJS can load XRegExp or you'll have to use a version of the file that calls define, like for instance this one which is XRegExp 3.0.0 (still alpha). If you want to use 2.x then your config would be something like:
paths: {
xregexp: "../whatever/your/path/actualy/is/xregexp-all"
}
shim: {
xregexp: {
exports: "XRegExp",
},
}
Note that this config normalizes the module name to xregexp, so you would have to require it as xregexp, not xregexp-all. In general, I prefer not to have things like -all or .min in my module names since these may change depending on the situation.
The shim tells RequireJS that the value it should export when xregexp is required is that of the global symbol XRegExp. This is generally how you handle modules that are not AMD-aware.
A quick search for "amd", or "define" in a library's source code should be the quickest way to tell if it supports AMD scheme. If not, then it's not (doh!)
However, if the library exposes globals, then you can use RequireJS shim to load it up as well as path to tell RequireJS where to retrieve it.
// Exporting a traditional library through RequireJS
require.config({
paths : {
jaykweri : 'path/to/jQuery' // moduleName : pathToModule
},
shim : {
jaykweri : {exports : '$'} // exporting the global
}
});
// Using that library in requireJS
define(['jaykweri'],function(globl){
// globl === $
});
Is it possible to import individual modules from within an optimized RequireJS/r.js bundle?
I have a javascript project broken up into two separate components - 'MyLibrary' and 'MyApplication'
MyLibrary consists of two separate modules, 'MyModule1' and 'MyModule2'.
In development mode, I can import each of these modules using RequireJS with the normal define(['MyLibrary/MyModule1'],function(){}) syntax from MyApplication.
However once running MyLibrary through r.js, this no longer appears to be possible - there doesn't appear to be a way to reference the internal modules directly anymore?
I can see from the compiled/optimized source that there are define() blocks for each module, however RequireJS within My Application doesn't appear to be able to reference these directly.
Is this possible, or will I need to bundle my entire application into a single file for this to work.
Edit: The RequireJS optimization phase is being done my the Play framework, and I have minimal control over the build config.
({appDir: "javascripts",
[info] baseUrl: ".",
[info] dir:"javascripts-min", mainConfigFile: "javascripts/build.js", modules: [{name: "main"}]})
In order to use the modules from the library, you need to instruct RequireJS how to find this modules. In main.js you need to have something like this:
require.config({
// ...
paths: {
// ...
'MyLibraryBundleName': 'dist/MyLibraryFile',
// ...
},
// ...
bundles: {
//...
'MyLibraryBundleName': ['MyLibrary/MyModule1', 'MyLibrary/MyModule2'],
//...
}
});
When MyApplication is referencing a module like this:
define(['MyLibrary/MyModule1'],function(){})
... as you mention, RequireJS will look for 'MyLibrary/MyModule1' and will find it into the 'bundles' section and after that will check the 'path' section to locate the actual file 'dist/MyLibraryFile' which will be loaded.