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.
Related
I am attempting to use the memory adapter for PouchDB. I want to bundle my application (along with dependencies like Pouch and this adapter) using Rollup. In order to reduce this to a minimally reproducible issue imagine this is my application I want to bundle:
import PouchDB from 'pouchdb-browser'
import MemoryAdapterPlugin from 'pouchdb-adapter-memory'
PouchDB.plugin(MemoryAdapterPlugin)
Since I am using a node module I'm obviously going to need the rollup-node-resolve Rollup plugin. Also many of PouchDB's dependent modules are in CJS format so I'm going to also need the rollup-node-commonjs Rollup plugin. Finally PouchDB makes use of some built-in Node modules (such as events) and the memory adapter even more (buffer, etc). I'm not sure these are used at runtime (it may be just in PouchDB's code because it can work in Node or the browser) but to prevent a bundling error I'm going to also include the rollup-plugin-polyfill-node plugin but direct the resolve plugin to prefer built-ins to the browser target if they are needed and available.
With all that in place here is my rollup config:
import commonjs from '#rollup/plugin-commonjs'
import resolve from '#rollup/plugin-node-resolve'
import polyfillNode from 'rollup-plugin-polyfill-node'
export default {
input: 'index.js',
output: {
format: 'iife',
name: 'app',
file: 'bundle.js'
},
plugins: [
commonjs({
requireReturnsDefault: "auto",
}),
polyfillNode(),
resolve({
preferBuiltins: true,
browser: true
}),
]
}
This will bundle. But when I load up the bundle in a browser I get an error about the inherits polyfill not working at this line:
https://github.com/FredKSchott/rollup-plugin-polyfill-node/blob/main/polyfills/inherits.js#L7
It says superCtor is undefined. If I step back a level on the backtrace it comes from this line:
https://github.com/nodejs/readable-stream/blob/main/lib/_stream_duplex.js#L46
The Readable is undefined. When I bundle I get warnings about circular references. I think it fundementally revolves around the fact that some of the PouchDB dependencies use NPM packages as polyfills explicitly (like inherits and readable-stream) while the polyfillNode plugin is also providing some of those same polyfills and they are doing so in an incompatible way. But I don't know how to untangle it.
Finally was able to resolve this so going to provide the answer in case someone else needs to bundle PouchDB memory adapter in Rollup.
The readable streams polyfill is a mess of circular dependencies. This is fine under a CJS format but since Rollup converts CJS to ES format it becomes problematic as Rollup can't figure out the proper order to put things in. This leads to the use of inherits where the superclass is not yet defined. The readable streams polyfill has an open ticket regarding this. The general solution is to fix it upstream in Node and then cut a new copy of the polyfill based on that upstream fix. The upstream fix was made but the polyfill hasn't yet been updated. Once it is this should naturally resolve itself.
In the meantime we can use someone else's fork of the polyfill that has the circular dependencies resolved. This fork is mentioned in that issue but the punchline is to add this to the package.json:
"readable-stream": "npm:vite-compatible-readable-stream#^3.6.0",
This will force readable-stream to resolve to that fork. But that's not the whole story. There are some dependencies of the memory adapter that are locked to a different version of readable-stream. To resolve that we want to add the following to our rollup's resolve plugin:
dedupe: ['readable-stream']
This will force the readable-stream substitute that we have explicitly added to our project to be used anytime a readable stream is needed.
The final issue is that PouchDB is using an ancient version of memdown that also has circular dependency issues. The latest version does not and seems to work with the memory adapter just fine. There is an open ticket at PouchDB to update memdown and this will naturally resolve when that happens.
In the meantime to resolve that we are going follow a similar pattern as above. We will explicitly require memdown into our project although no need for a fork. Just a newer version. Then to force the memory adapter to use this version we add memdown to that dedup option as well.
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.
I don't understand why we have plugins and extends. What is the difference between them and do I need one or the other?
extends uses a config file which applies set of rules when you add that to the extends options. A plugin on the other hand provides you with a set of rules that you can individually apply depending on your need. Just having a plugin does not enforce any rule. You have to choose which rules you need.
A plugin may provide you with zero, one, or more configuration files. If the plugin provides configuration file, then you can load that in your extends section after adding the plugin in the plugins section.
So essentially, plugins given you some rules that have been coded and you can choose which ones are relevant. It may also provide config files to apply rules that the authors think are logically grouped/relevant but providing a config file is not mandatory for a plugin. extends, on the other hand, provides you the ability to apply rules in bulk based on config file specifications.
Example Plugin - eslint-plugin-react
{
"plugins": [
"react"
],
"extends": [
"eslint:recommended",
"plugin:react/recommended"
]
}
Example Config - eslint-config-google
{
"extends": [
"google"
]
}
In addition to shmit's good answer:
extends
is about extending configurations in general, not only plugins. Potential values are:
"eslint:recommended"
"eslint:all"
Shareable configuration from npm package (eslint-config-xxx or scoped name)
Plugin configuration from npm package (eslint-plugin-xxx or scoped name)
Another configuration file, like "./my/path/.eslintrc.js"
Plugin notation: plugin:<package name>/<configuration name>, e.g. for eslint-plugin-react:
"extends": ["plugin:react/recommended"]
By extending from a plugin config, we can get recommended rules without adding them manually.
plugins
A plugin is a special eslint npm package, that provides additional rule definitions (rules), environments, processors and configs for different configurations of recommended / default rule values.
The plugins property in .eslintrc.js is merely a flag to enable a given plugin after installation with npm i. We now can refer to the plugin's rules, but have to set all rules values manually.
Think of plugins as a way to activate a plugin - to use its rules, you need to add the plugin once in the chain in every case.
plugins is not needed in your own config, if it is already defined in a configuration, that you extend from by extends.
Example:
eslint-plugin-react already contains plugins: [ 'react' ], hence this entry is not needed anymore in own config and plugin rules can be used directly.
So found out that plugins add extra capabilities and extends gives you a baseline on which to add your own custom rules. Thanks to my friend Oliver for helping me answer this question!
Please refer to this git repository: https://github.com/AngularClass/angular-starter
The Angular4 Webpack Starter comes with 2 files:
tsconfig.json
and
tsconfig.webpack.json
Each file has slightly different configurations for TypeScript.
My question is regarding how these 2 files work in relation to the project.
Will the tsconfig.webpack.json only be applied to the ts-loader used by Webpack? while the tsconfig.json file will apply to everything else?
Any information on what tsconfig.webpack.json would be greatly appreciated.
The short answer is yes. The TypeScript loader registered with Webpack is explicitly configured to use the tsconfig.webpack.json file. This can be observed on line 133 of the common configuration.
The tsconfig.json file is there for IDE support.
It is worth noting that, while you state that the template uses ts-loader, it actually uses awesome-typescript-loader.
Having said that, both loaders will by default try to pick up a file named tsconfig.json and that the template is explicitly overriding this behavior on the linked line.
While there are multiple reasons why one might want to use more than one TypeScript script configuration file in a project, editors, such as Visual Studio Code, use the one named tsconfig.json to power features such as intellisense, set various options, and to determine the extent of a project.
It is more than reasonable to use the same file for both and that is actually what would happen by default.
Remarks
Please note that the AngularClass template is extremely bloated and complicated. Considering it is meant as a starting point, which you will no doubt add to, the amount of unnecessary boilerplate and cruft that you start out with by basing your application on such a template should be taken into very serious consideration. This goes double if you are new to any of the tools, transpilers, or frameworks involved.
By the way, I'm actually a contributor to that repository. They took a pull request from me that changed a utility function which someone filed an issue for as being confusing. The funny thing was that I had removed that very function from our project long before I submitted the PR improving it.
Having worked on a project which was derived from one of their templates, I wasted a lot of time ripping out Webpack config related code that was not needed but was getting in the way. We ended up with only a ~hundred lines of Webpack config total. I wasn't, and still am not a huge Webpack fan (JSPM for the win), but Webpack was not being utilized well by the template. Lots of unnecessary work was being done which actually made Webpack seem more complicated than it is. That entire helpers file is basically worthless and none of it had anything to do with Webback, or TypeScript, or even Angular.
This is also a bit troubling since the angular class website sells training material. There's nothing wrong with that in principle or in practice, but they create a lot of complexity in addition to what is inherent in an already complex tool chain.
Will the tsconfig.webpack.json only be applied to the ts-loader used
by Webpack?
Yes, that is correct. Here is where the tsconfig.webpack.json is used in webpack.common.js:
new ngcWebpack.NgcWebpackPlugin({
...
disabled: !AOT,
tsConfig: helpers.root('tsconfig.webpack.json'), <----------------
}),
and for awesome-typescript-loader here:
{
loader: 'awesome-typescript-loader',
options: {
configFileName: 'tsconfig.webpack.json', <-------------------
useCache: !isProd
}
},
while the tsconfig.json file will apply to everything else?
Yes, it's used for tslinting or if you need to produce declaration files. If you're working in IDE it can also be used for intellisense and other IDE specific functionality.
I love vim and want to keep using it to do web development although I am struggling setting up my .tern_project file with the correct libraries I need to do autocompletion. I am relatively new to JavaScript but what I have so far is making it a lot easier to learn.
There aren't many examples that I could find and I have tried to read the documentation but I do not know enough for it to be helpful. So far my .tern_project file looks like this:
{
"libs": [
"browser",
"ecma6"
],
"plugins": {
"requirejs": {
"baseURL": "./",
"paths": {}
}
}
}
I don't really know what the plugins do but I left them in for now, in libs the ecma6 really helped me with all the array methods (ie. forEach etc.). Now my question is how do I add stuff like console.table() to autocomplete?
Which library do I need to add to the .tern_project file?
Also, I am open to suggestions for better web development environments.
At this point all you've got is tern's default completion!!! Your .tern_project does not have any impact on completions that tern suggests because tern configuration file is .tern-project; Its Dash not underscore. so first rename it.
.tern-project is a json configuration file which tells tern what completions it should suggest through two property: libs and plugins.
plugins aren't much different from libs they tells tern to also suggest these completions you specify in addition to libs.
For example in your .tern-project file you've choose to use requirejs plugin. so if you use requirejs library which is a module loader and helps with writing client side modular code, then it completes variables, functions and methods from other modules.
console is a node's global. and to complete node stuff you should add node plugin. so your .tern-project file should be something like:
{
"libs": [
"browser",
"ecmascript"
],
"plugins": {
"node": {}
}
}
Note that i've used ecmascript in place of ecma6. in previous versions tern had ecma5 and ecma6 libs but in latest versions these two got combined in one lib named: ecmascript.
List of available tern libs:
browser
chai
ecmascript
jquery
react
underscore
You could always get an updated list of libs from tern js repository defs directory
List of available tern plugins :
angular
commonjs
complete_strings
doc_comment
es_modules
node
requirejs
webpack
You could always get an updated list of plugins from tern js repository plugin directory
As your javascript skills grow go add and play with libs and plugins and see what completions you get. Also note that you could have multiple .tern-project file. Tern will always search upward to root directory and uses the closest one. so you could configure completions on a project basis.