Working on a set of scripts that will run in a browser context where certain modules (e.g. underscore) will be available as global modules. However, I'm depending on modules in node_modules that require / import underscore directly. Is it possible to configure WebPack to depend on the global underscore instance when compiling these files instead of duplicating that library in my compiled scripts?
What you're looking for are Externals:
externals configuration in webpack provides a way of not including a dependency in the bundle. Instead the created bundle relies on that dependency to be present in the consumers environment. This typically applies to library developers though application developers can make good use of this feature too.
This even works for modules in node_modules, as webpack walks the entire dependency tree to figure out what to include in the resulting bundle.
There's even an example that's specifically for your use case, it looks like:
externals : {
lodash : {
commonjs: "lodash",
amd: "lodash",
root: "_" // indicates global variable
}
}
This syntax is used to describe all the possible ways that an external library can be available. lodash here is available as lodash under AMD and CommonJS module systems but available as _ in a global variable form.
If you want to rely on a library already being available in the environment when your bundle is loaded, you need to make use of externals.
module.exports = {
externals: {
underscore: "_"
}
}
The key of the object (underscore) is what you use to import it, and the value (_) is the global variable it will look for.
require("underscore"); // Will return the _ variable from the global environment!
Related
Good evening!
I have this monorepo with multiple packages, where each of them is bundled independently using Webpack.
Before it was a monorepo, I would have a single bundle file, and it would be possible to have it available through a global variable within the browser through output.library property. Now I have the following since I have multiple entries:
output: {
library: "SC",
// export itself to UMD format
libraryTarget: "umd",
umdNamedDefine: true,
filename: "[name]/dist/organization-[name].js",
// fix for https://github.com/webpack/webpack/issues/6525
globalObject: `(typeof self !== 'undefined' ? self : this)`
}
The problem is that if I use this same config for every package, and I import more than one to the browser using script tags, only the latest script will actually be available because it's essentially recreating the global variable each time.
Is there a way to reuse it? Or maybe a better convention I could use here.
In node, for instance, I import each of them using the bundle name, but in the browser, I feel like it should all be under the same global variable.
Thank you for any suggestions!
As mentioned in the issue I created over webpack's repository, the solution is to use the following:
library: ["MyLibrary", "[name]"]
That will make all packages available under the same global variable MyLibrary but separated by their respective entry (i.e., MyLibrary.entryOne and MyLibrary.entryTwo).
My TypeScript project is compiled for the browser with AMD modules. I include lodash.min.js myself, along with a bunch of other UMD libraries that all declare global variables, like moment and _. I need to tell my TypeScript about the global _ variable for Lodash. How can I do this with imports? If I install #types/lodash and use import * as _ from "lodash";, it tries to dynamically fetch an AMD Lodash module at runtime, which breaks everything.
If I have one file where I declare the _ global, i.e. export declare const _: LodashStatic;, I cannot even import my declaration in another file, or it tries to load stuff at runtime. What is even the point of the declare keyword if TypeScript treats it like a functioning import. I thought declarations just tell the compiler to shut up and that there will be separately loaded stuff at runtime. Lucky for me, I can use declare const _: LodashStatic; in every single one of my hundreds of files and everything works, but it would be nice if I could just declare it once and then import in other files...
The usual way to tell Typescript about the existence of global types is through the types array in your tsconfig.json. If you have #types/lodash installed, then having the following in your tsconfig.json should allow you to reference _ anywhere:
{
"compilerOptions": {
"types": ["lodash"]
}
}
I understand that Svelte can produce AMD output and find some details on how to do this in the docs. I can also find some info on how to configure Rollup to output AMD modules. But what about input? What do I need to do when I have AMD modules as dependencies?
For example, suppose I have two different third party libraries that are both distributed as AMD libraries and I want to use those libraries in my Svelte project. How would I need to modify eg. this nested components demo to allow these AMD modules to be used as dependencies in my Svelte components?
Also, am I able to configure whether I bundle these libraries together with my Svelte components? If so, where would I need to do that?
Note
I also raised this issue on Github.
AMD modules are a pain to convert to ES modules, so you may find it difficult to bundle them with Rollup. (There's rollup-plugin-amd but it comes with caveats.)
But you can easily treat them as external dependencies that get loaded separately — just import them as normal then configure Rollup:
// rollup.config.js
export default {
// ...
format: 'amd',
external: ['an-external-amd-module'],
paths: {
'an-external-amd-module': 'https://my-cdn.com/an-external-amd-module.js'
}
};
You can see a demo here (repo here) — note that we're loading an exernal AMD module called the-answer, even though it's a regular import, because of the Rollup config.
If I use imports-loader, what does it mean exports=>false part in configuration? It should inject variable var exports = false, but I don't know when and why I need this variable.
module : {
loaders : [
{
test : /eonasdan-bootstrap-datetimepicker/,
loader : 'imports?define=>false,exports=>false,moment=moment'
}]
}
Imports is for shimming third party code that is expecting global to be defined such as jQuery $ or AMD's define. The reason you might want to do this is because module bundlers often bundle to formats that both AMD and CommonJS understand aka universal module definition UMD format. When importing a UMD module it will first check to see if define (AMD) exists and then exports (CommonJS). Webpack has an easier time parsing CommonJS (nodes native format) so setting define to false explicitly tells webpack to not parse it as an AMD module.
UPDATE
It seems like they are likely disabling all module exports and defining moment as the moment js library. I would guess that the code in that library is extending the bootstrap datepicker control with features from moment.
I have a project that uses nodeJS module format (commonJS) and should also (in parts) run in the browser.
I do have non-isomorphic code paths where I conditionally include modules:
var impl;
try {
// in node, use node-impl
impl = require('../node-impl');
} catch (err) {
// running in browser, use browser-impl
impl = require('../browser-impl');
}
Now, I want to use webpack to create a bundle that runs in the browser. I therefore need to defined the external (nodeJS specific) modules as external in the webpack.config.js so that they don't get included in the bundle:
external: {
'../node-impl': true
}
I verified that the '../node-impl' code is actually not included in bundle, but the emitted code looks like this:
/***/ },
/* 33 */
/***/ function(module, exports) {
module.exports = ../node-impl;
/***/ },
This is syntactically wrong JS and the browser will throw a syntax error there.
How is this scenario properly handled with webpack.js? Be aware that I do not wish to use webpack for running with nodeJS, only the browser bundles should be created with webpack.
// Your actual situation:
var impl;
try {
impl = require('../node-impl');
} catch(e) {
impl = require('../browser-impl');
}
You need to refactor this snippet to:
var impl = require('../node-impl');
After this rework, your code is able to work only in a node js environment, that's is good because we are going to mock this request when bundling for browsers...
// webpack.browser.config.js
module.exports = {
resolve: {
alias: {
'../node-impl': '../browser-impl'
}
}
};
Webpack - Resolve.Alias
Or using a package.json#browser, or https://webpack.github.io/docs/configuration.html#resolve-packagealias
I don't think this is the intended purpose of the externals config. Per the docs,
Specify dependencies that shouldn’t be resolved by webpack, but should become dependencies of the resulting bundle. The kind of the dependency depends on output.libraryTarget.
So you're telling webpack that your build requires that module, but not to bundle it in. It's leaving it out, but attempting to require it.
There are probably a couple of ways to do what you want. Worth mentioning that you can easily have webpack produce multiple builds, with different/shared config, from a single config file, which opens up a lot of possibilities. But the way I'd suggest is to use the DefinePlugin to define a boolean 'constant' that represents the execution context (e.g. IN_BROWSER = true). Check that constant in your conditional around the require(). Webpack's parser isn't that smart but it can evaluate boolean variables, so it will resolve the conditional correctly and only require the appropriate module. (Using a non-boolean, like CONTEXT = 'browser' is too confusing for webpack, and it will parse each require statement.) You can then use the Uglify plugin to remove the 'dead code' in the conditional so it doesn't bloat your production build.
With the help of #Hitmands I could come up with a solution that is still not perfect, but fits my needs. I introduce a fictious nonexistingmodule and declare it as external; I then declare the node-impl specific modules to be available as nonexistingmodule:
externals: {
'nonexistingmodule': true,
'../node-impl': 'nonexistingmodule'
}
This way I can keep the try/catch pattern to load specific implementations and it will still run on node. In the browser the loading of nonexistingmodule fails, and the browser-impl module is loaded - just as I intended.
I am a big fan of "don't refactor your code to match a tool", so I am going with this solution.