Confusion over various webpack shimming approaches - javascript

I'm a little confused on the various ways webpack allows to expose a variable that isn't available on npm or to be put in the bundle. I was able to expose the google visualizations chart script's global google var by using
resolve: {
extensions: ['.js', '.json'],
alias: {
'google': path.resolve(__dirname, 'vendor', 'google.js')
}
}
combined with
plugins: [
new webpack.ProvidePlugin({
'google': 'google'
})
]
however looking at the webpack docs there a couple other ways to shim, which look like they might do something similar. There is imports-loader and exports-loader, and script-loader. I know that I've linked to the docs, but I still find their descriptions of when these four should be used a bit unclear.
Also looking at this example, is this require not assigned to a variable? Where is it meant to go? And where is the documentation on what is going on with this syntax?
require("imports?$=jquery!./file.js")
Can someone provide me some examples of when each of these should be used?

scripts-loader
I never used this myself, but the idea is simple I guess. I think it can be used if for some reason you want to inject a script or a function or something in one of the modules/files you have no control over them.
imports-loader & exports-loader
In one of the apps I worked on we had to use tinymce which in its older versions was dependent on this being always window because it was built to work as a global script. Not as a CommonJS or ES module.
So in order to fix that, we had to use the import-loader so it can inject window to the script. Here how it looked like in webpack.config.js
{ test: require.resolve('tinymce/tinymce'), use: ['imports?this=>window', 'exports?tinymce'] }
Which says inject window in place of this & also we are using exports-loader here so we can export the global tinymce as a default export named tinymce so we can use it as a normal module in our app.
Thankfully all of this is already fixed in the latest releases.
ProvidePlugin
In my experience, this is useful when a library is depending on another library being in a global scope or something. Like jQuery plugins for example, they do use one of these $, window.$, jQuery & window.jQuery
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.$': 'jquery',
'window.jQuery': 'jquery',
}),
So what this plugin will do is to make sure when webpack sees one of these variations it will provide the jQuery object to it instead.
The difference between this & imports-loader for example that you might not know which variation is used by which script. So you let webpack handle this while the imports-loader is kind of more specific.
I hope this helped you a bit to understand the differences between all of them, also this is the new documentation page which I think better than the one you were checking https://webpack.js.org/guides/shimming/

imports and exports loaders are very simple to understand. If you use one of them, or both, your module is wrapped into another function with exports and imports.
For example, I'm using paho-mqtt module meant to be used like global <script src=""> on the page:
import Paho from 'imports-loader?this=>window!exports-loader?Paho!paho-mqtt';
//and this is transformed by webpack to something like:
(function(window){
//wow you can use `window here`, `this` in the global context === window.
// original module code here
// that exposes global var `Paho`
module.exports = Paho;
})(this);

Related

How to make webpack not use the window object when bundling?

I'm making a React component library to abstract out some components I use in multiple projects. Some projects are made with CRA, some with Gatsby, some might be something else, etc. I used the Neutrino.js framework/toolchain as it was linked on the React docs site, but the issue I ran into is that by default the output files of the build all use the window object, which causes gatsby build to break as window doesn't exist in Node/SSR. Is there a way to make Neutrino/webpack output a bundle that doesn't use window? While searching for a solution and comparing to other libraries it seems that ESM is the best but I'm not sure how to get webpack to use it, I think it's currently not supported. Is there another tool I should be using for this?
Add globalObject configuration to your webpack configuration:
output: {
globalObject: "this",
},
The default is window
For example:
To make UMD build available on both browsers and Node.js, set output.globalObject option to 'this'.
module.exports = {
// ...
output: {
library: 'myLib',
libraryTarget: 'umd',
filename: 'myLib.js',
globalObject: 'this'
}
};
-From docs

What "plugins" property in .eslintrc does?

module.exports = {
root: true,
parser: '#typescript-eslint/parser',
plugins: ['#typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:#typescript-eslint/recommended',
],
};
Whenever I add or remove this line: plugins: ['#typescript-eslint']
eslint seems to behave the same. What exactly plugins property does and when using it is required?
This question is pretty straight forward if you think about what a plugin is.
The docs don't really do a good job of just out-right saying what an ESLint plugin is, though if you read through the docs (https://eslint.org/docs/user-guide/configuring), then it's pretty trivial to figure out:
ESLint supports the use of third-party plugins
When using rules, environments or configs defined by plugins. Before using the plugin, you have to install it using npm.
So a plugin is a 3rd party module that can define rules, environments, or configs.
So to answer your question:
What exactly plugins property does [sic]
the plugins property tells ESLint what plugins you want to use
and when using it is required? [sic]
when you use something from a plugin, you must first tell ESLint about it with the plugins property.
Plugins seem to work anyway when this field is omitted
If you use the extends option with the syntax plugin:<plugin>/<config>, then ESLint will load a specific file from the plugin ahead of time.
Why? Because this allows a plugin to provide a config and reduce the amount of config you need. A plugin's config can provide the plugins option for you, meaning you don't need to do it yourself.
I was also curious about what is parser and plugin
From documentation
"For example, once this parser successfully produces an AST for the TypeScript source code, it might well contain some information which simply does not exist in a standard JavaScript context, such as the data for a TypeScript-specific construct, like an interface.
The core rules built into ESLint, such as indent have no knowledge of such constructs, so it is impossible to expect them to work out of the box with them.
Instead, you also need to make use of one more plugins which will add or extend rules with TypeScript-specific features."
This helped me to understand it better.

Webpack - How to reuse global library name when it already exists?

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

Using the whitelist option with Babel's external-helpers

I'm trying to use Rollup with Babel's external-helpers. It works, but it's dropping a bunch of babel helpers which I don't even need, for example asyncGenerator.
The docs show a whitelist option but I can't get it to work
rollup.rollup({
entry: 'src/buttonDropdown.es6',
plugins: [
babel({
presets: ['react', ['es2015', { modules: false }], 'stage-2'],
plugins: [['external-helpers', { whitelist: ['asyncGenerator'] }]]
})
]
})
The above has no effect: all Babel helpers are still dropped into my resulting bundle.
What is the correct way of using this feature, and is there a full list of which helpers' names the whitelist array takes?
Or is there some other Rollup plugin I should be using with Rollup to automatically "tree shake" the babel external helpers.
Problem
The babel-plugin-external-helpers plugin is not responsible for injecting those dependencies in the final bundle.
The only thing it controls is that how the generated code will access those functions. For example:
classCallCheck(this, Foo);
// or
babelHelpers.classCallCheck(this, Foo);
It is needed so all rollup-plugin-babel needs to do is to inject babelHelpers in every module.
The documentation is misleading, the whitelist options is not on the external-helpers plugin. It's on the completely separate module and command line tool called babel-external-helpers, which is actually responsible for generating babelHelpers.
It's rollup-plugin-babel what is injecting babelHelpers. And does it using a trick to modularize the final code. It calls babel-external-helpers to generate the helpers, and ignores the whitelist parameter. See my issue requesting to expose an option.
This approach is correct, because rollup will tree-shake the unused helper functions. However some of the helpers (like asyncGenerator) are written in a way that is hard to detect if the initialization has any side effects, thus preventing removal during tree-shaking.
Workaround
I forked rollup-plugin-babel and created a PR which exposes the whitelist option of building babelHelpers in the plugin's options. It can be used this way:
require("rollup").rollup({
entry: "./src/main.js",
plugins: [
require("rollup-plugin-babel")({
"presets": [["es2015", { "modules": false }]],
"plugins": ["external-helpers"],
"externalHelpersWhitelist": ['classCallCheck', 'inherits', 'possibleConstructorReturn']
})
]
}).then(bundle => {
var result = bundle.generate({
format: 'iife'
});
require("fs").writeFileSync("./dist/bundle.js", result.code);
}).then(null, err => console.error(err));
Note that I didn't publish distribution version on npm, you will have to clone the git repo and build it using rollup -c.
Solution
In my opinion the right solution would be to somehow detect or tell rollup that those exports are pure, so can be removed by tree shaking. I will start a discussion about it on github after doing some research.
As I have found in this particular issue in the GitHub page.
The Babel member Hzoo suggests that
Right now the intention of the preset is to allow people to use it without customization - if you want to modify it then you'll have to
just define plugins yourself or make your own preset.
But still if you want to exclude a specific plugin from the default preset then here are some steps.
As suggested by Krucher you can create a fork to the undesirable plugin in the following way
First one is by forking technique
"babel": {
"presets": [
"es2015"
],
"disablePlugins": [
"babel-plugin-transform-es2015-modules-commonjs"
]
}
But if two or more people want to include the es2015-with-commonjs then it would be a problem.For that you have to define your own preset or extend the preset of that module.
The second method would involve the tree-shaking as shown in this article done by Dr. Axel Rauschmayer.
According to the article webpack2 is used with the Babel6.
This helps in removal of the unwanted imports that might have been used anywhere in the project in two ways
First, all ES6 module files are combined into a single bundle file. In that file, exports that were not imported anywhere are not exported, anymore.
Second, the bundle is minified, while eliminating dead code. Therefore, entities that are neither exported nor used inside their modules do not appear in the minified bundle. Without the first step, dead code elimination would never remove exports (registering an export keeps it alive).
Other details can be found in the article.
Simple implemetation is referred as here.
The third method involves creating your own preset for the particular module.
Creating aplugin and greating your own preset can be implemented according to the documentation here
Also as an extra tip you should also use babel-plugin-transforn-runtime
If any of your modules have an external dependancy,the bundle as a whole will have the same external dependancy whether or not you actually used it which may have some side-effects.
There are also a lot of issues with tree shaking of rollup.js as seen in this article
Also as shown in the presets documentation
Enabled by default
These plugins have no effect anymore, as a newer babylon version enabled them by default
- async-functions (since babylon 6.9.1)
- exponentiation-operator (since babylon 6.9.1)
- trailing-function-commas (since babylon 6.9.1)**
Also the concept of whitelisting and blacklisting the plugins has benn brilliantly explained by loganfsmyth here in this thread.
you can pass a whitelist option to specify specific transformations to run, or a blacklist to specific transformations to disable.
You cannot blacklist specific plugins, but you may list only the plugins you want, excluding the ones you do not wish to run.
Update :
According to this article here is an important update -
"The --external-helpers option is now a plugin. To avoid repeated inclusion of Babel’s helper functions, you’ll now need to install and apply the babel-plugin-transform-runtime package, and then require the babel-runtime package within your code (yes, even if you’re using the polyfill)."
Hope this may solve your problem
Hope it may help you.

How to prevent browserify-shim from requiring all shims?

I am using browserify and browserify-shim in a project, run through gulp using gulp-browserify.
gulp.src(['./resources/js/main.js'])
.pipe(browserify({
shim: {
angular: {
path: './node_modules/angular/angular.js',
exports: 'angular'
},
'angular-animate': {
path: './node_modules/angular-animate/angular-animate.js',
exports: 'ngAnimate',
depends: {
angular: 'angular',
jQuery: 'jQuery'
}
},
[...]
}
}))
.pipe(concat('app.js'))
.pipe(gulp.dest('./web/js'));
This setup works fine and, for most parts, as intended. However, Browserify will always include all shimmed libraries in the build, even if none of them is called by require().
The documentation seems to be non-existant on this topic. Is there a way to prevent this? It seems very counter-intuitive to me - the build should only contain what I actually require.
(Update: I installed angular and other libs using napa/npm)
When you shim with Browserify, it's making those libraries (and specifically the objects you tell it to "export") global. The convention is to still use require() for those libraries, however, doing so is just best practice so that if the library were to convert to module.exports down the road, you won't have to replace the global references in your code. Plus, it's nicer to list all of the files you require at the top in good node form. :)
So to answer your question, by shimming those libraries, you've told browserify to include them as global variables so they can be used anywhere, so they'll be included in the build automatically, regardless of whether you require() them.
If you want to include some and not others based on something like gulp.env, you could try building the options object separately and passing it into the browserify function.

Categories

Resources