Webpack with module and main transpilation - javascript

I've discovered that for some reason, webpack favors module instead of main on package.json.
We have a lot of libraries that export module in ES6 and main transpiled but we end up having stuff not being transpiled then. Taking a couple of famous React modules such as React Bootstrap or React Toolbox
I can see that I'm not going against convention here yet I'm surprised not many people run into this. React needs to run both on the browser and node if SSR is in place so I'm not sure how to proceed here.
Example library here:
https://github.com/firstandthird/domodule/blob/master/package.json#L6
Both including node_modules on babel loader and doing the switch indicated on the above solution seem to go against every other thing which, again, surprises me.
Only partial solution I've found is to not exclude node_modules on babel-loader but that seems like it might bite me back.
Here's the relevant portion of Webpack's config.
module.exports = config => ({
test: /\.m?js$/,
use: {
loader: 'babel-loader',
options: {
babelrc: false,
configFile: false,
presets: [
[
'#babel/preset-env',
{
targets: config.browserlist,
useBuiltIns: false,
modules: false,
exclude: ['transform-typeof-symbol']
}
]
],
cacheDirectory: true,
cacheCompression: config.minify,
compact: config.minify
}
}
});
Question is, what's the proper way to either configure Webpack or my libraries. I don't mind changing all the libraries as long as it's a known standard or something that we could be missing here.

Well, the standard right now is to provide pkg.main as the webpack's output fully transpiled and bundled and have pkg.module pointing in the transpiled but not bundled output.
Keep in mind that pkg.module should only have import/export from ES6 capabilities and not other stuff (like arrow functions). The unfortunate thing here is that webpack doesn't provide any way to output such a thing. It always puts it's __webpack_require__ stuff.
As you figured out, you can of course adjust your application's config to run babel on the library by including node_modules/yourlibrary if this is a private library that you only use. I don't think that a public library should enforce clients to transpile it every time, especially if there are specific rules or plugins that should apply.
The other solution that libraries use is to just take the source code and run babel only on top of it, without any webpack. This of course will work, but advanced configurations with webpack specifics (like alias,etc) will just fail.
Another solution is to use rollup as a bundler for libraries that seems to have these capabilities out of the box.
There is an open issue in webpack with more information if you are intrested.
Here is also a page from rollup that describes this concept.

Related

Webpack how to require .node file (To use the WebChimera.js package in Vue Electron)

I'm trying to include a VLC video playing in my Electron app, which is possible through WebChimera.js. This package is distributed a bit weirdly (to me at least), to use it you need to require wcjs-prebuilt, specify some settings in package.json and configure Webpack to allow importing .node files as explained in this Wiki page for WebChimera.js.
However I believe this Wiki page is outdated, as loaders isn't a valid key anymore in a Webpack config. I'm not very experienced using Webpack so most of this is new to me. Also note that this Wiki explanation used a fork of node-loader, although this fork seems to be merged to the actual node-loader now (?).
I now use this Webpack config:
target: 'node',
node: {
__dirname: false,
},
module: {
rules: [
{
test: /\.node$/,
loader: 'node-loader',
},
],
},
externals: [
'wcjs-prebuilt',
],
Because that's how the Webpack page for node-loader seems to do it. However this doesn't work for me, as now I get the error: Uncaught ReferenceError: exports is not defined in the chunk-vendors.js:1 file. Which probably means it's trying to use require syntax somewhere it shouldn't, but I have no idea how to proceed here. This error still occurs in an otherwise empty vue-electron project (template here), when I comment out all WebChimera related code. WebChimera code I use for testing in this project (Right now I'm just trying to get it to work):
const wcjs = require("wcjs-prebuilt");
console.log(wcjs)
When I remove the webpack config I showed above, the error about exports is not defined goes away, which is why I believe it's something in my webpack config rather than my code causing that error.
Long story short, I want to know how configure webpack to allow me to import or a require a .node file.
I'm able to get vue electron building with wcjs-prebuilt using a vue.config.js like this. You will also need to set the VLC_PLUGIN_PATH correctly or video won't play.
module.exports = {
configureWebpack: {
externals: {
'wcjs-prebuilt': 'commonjs wcjs-prebuilt'
},
},
chainWebpack: (config) => {
config.module
.rule('node')
.test(/.node$/i)
.use('node-loader')
.loader('node-loader')
.end()
},
pluginOptions: {
electronBuilder: {
externals: ['wcjs-prebuilt']
}
}
}
Since posting the question I've switched to mpv.js for video playback so this isn't an issue for me anymore. However after posting this question I experimented a lot, after I finally got it working in Webpack somehow (see first link below), it worked for me but with distorted video. The node file added some properties to an array which Webpack somehow stripped away, causing some missing values in the video renderer. I fixed that by forking WebChimera and editing the C++ code so that the values weren't added as properties but as separate values.
I ended up forking WebChimera.js, wcjs-prebuilt, wcjs-renderer, and libvlc_wrapper to get VLC to finally work with Webpack+Electron, all that probably wasn't necessary but oh well..
Links for whoever might be interested:
https://github.com/RuurdBijlsma/vlc-video-demo (working demo project featuring VLC in an Electron+Webpack+Vue project.)
https://github.com/RuurdBijlsma/libvlc_wrapper
https://github.com/RuurdBijlsma/wcjs-renderer
https://github.com/RuurdBijlsma/WebChimera.js
https://github.com/RuurdBijlsma/wcjs-prebuilt

Is there a way to setup webpack config to load specific core-js entries

DISCLAIMER: I'm not terribly familiar with webpack or babel outside of simple setup, so if the question isn't clear then I apologize and will do my best to offer further clarity.
So, the situation currently is that a coworker updated a bunch of packages recently, babel among them, and babel is no longer transpiling the code properly for .forEach and spread operators in ie11 (specifically when iterating over a node list). The resulting behavior is a bit frustrating; simply put, nothing happens when the page is loaded in those browsers, no console errors, just nothing.
While troubleshooting this, I've been able to get it fixed by adding core-js as a dependency in package.json and adding the following imports to the main.js file:
import 'core-js/stable/array/for-each';
import 'core-js/stable/array/from';
import 'core-js/stable/dom-collections';
import 'core-js/stable/object/get-own-property-symbols';
The question is, is there a way to get this same result purely through the webpack config? Again, I'm not all that familiar with how to play around in webpack outside of some basic common setup tasks, so I hope I'm phrasing this in a way that makes sense. If not, I'll do my best to correct based on feedback.
You can add this by webpack.
Take a look at the documentation https://github.com/zloirock/core-js#babelpreset-env
You need babel .babelrc
{
"presets": [
[
"#babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
Then you do not need to add dependency in main.js all dependencies are added automatically by core-js
I prepared the code https://github.com/tomik23/webpack-babel-corejs

Why adding babel-loader in webpack conf file?

I am new to webpack and have seen some examples as following:
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
// ....
Why is this necessary since webpack auto transpiles es6 to es5?
Edit:
Ok, it DOES NOT transpile automatically unless instructed to do so.
Why is this necessary since webpack auto transpiles es6 to es5?
Webpack does not auto-transpile ES6 to ES5. It is simply a build tool. It does nothing but execute the plugins & loaders you tell it to.
But the es6 code I wrote get transpiled to es5 even without this "babel" rule
I don't see it transpiling ES6 to ES5.
The first example I looked for in your code was the conversion of let to var in the bundled code since this is probably the most commonly used ES6 feature.
With babel-loader, let gets converted to var (and some other fancy maneuvering). Without, it remains let.
To explore this, I commented out UglifyJS so the bundle was readable and ctrl+fed the file. You should be able to see this same behavior.
If you're expecting import to be converted to require, this won't happen as webpack just reads the file and loads it into the bundle. So, no require & no import appear in the bundle. This isn't transpilation, though. It's just a function of how webpack's bundling process works (searching for & injecting dependencies into the bundle).
Bonus points:
I would recommend adding your dist directory to .gitignore. Typically, you don't want your bundled code version controlled. You should rely on your build tools to handle this (you can add webpack to your package.json's postinstall if you want to simplify the installation for consumers of your project).
In hindsight, I realize you probably only added the dist directory because I asked to see the bundled code. Sorry! :p But I'll leave this here in case it helps someone else in the future.

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.

Is it possible to run a build in browserify/babel against a certain browser target?

Looking at tables such as these:
http://kangax.github.io/compat-table/es6/
It looks like Chrome is really close to supporting a lot of ES6, meaning (in my mind) that I should be able to drop the following during development:
var babelifyOptions = {
presets: ['es2015', 'stage-0', 'react'],
extensions: ['.js', '.jsx']
};
when using browserify:
browserify(browserifyOptions)
.transform(babelify.configure(babelifyOptions))
.bundle()
.pipe(source('app.js'))
.pipe(buffer())
.pipe(gulp.dest('./dist/js'));
And with that hopefully speeding up build times. When building to production, obviously transpilation is still required.
However, when I drop out es2015 presets, browserify chokes on the build, not understanding things like .... This makes sense, but is it possible to run browserify while targeting Chrome only? (ie allowing token/operators/features that Chrome currently understands).
I do not think browserify is the issue but your version of NodeJS is. I think NodeJS 6 was the first one to support a lot of the ES2015 features by default but notably it does not support the new ES module system. There are a few Babel presets that patch around the things that are missing. Here is just a list of the ones I found in a quick search:
babel-preset-es2015-node6
babel-preset-node6
babel-preset-es2015-node
babel-preset-es6-node6
Note: I've not used any of them so I cannot make a recommendation for you.

Categories

Resources