webpack & using node modules in an isomorphic package - javascript

I am building an isomorphic package where I am using a flag in various files to detect if we are in the browser or in node. If in node, I require an internal package ie if (isNode) { require("/.nodeStuff) } that has as one of its dependencies the fs module. However, webpack does not like this for obvious reasons. Is there any type of module-based webpack config file that I can configure to ignore the node-based requires entirely so that this does not happen?

First option
As stated in the docs, in order to solve this isomorphic problem you could simply run two builds, one for each environment (node and web). The guide can be found here. Keep in mind you should probably mock any built ins in the clientConfig by adding this block
node: { fs: 'empty',//any other node lib used }. That way webpack will not complain and since your client code will be under the !IS_NODE condition the empty fs will never be used.
Although this is a solid solution you end up with 2 bundles and you need of course a way to distribute them to the correct platform each time.
Second way
This solution is based on the not very well known __non_webpack_require__ function. This is a webpack specific function that will instruct the parser to avoid bundling this module that is being requested and assume that a global require function is available. This is exactly what happens while running in node instead of a browser.
//webpack.config.js
{
mode: "development",
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
node: false
}
// nodeStuff.js
const fs = __non_webpack_require__('fs'); //this will be transformed to require('fs')
fs.writeFileSync('some','thing)
That way since nodeStuff.js will only be required under the IS_NODE condition, the native require will be available.
I would suggest to use __non_webpack_require__ on native libraries only, that you are sure that will be available!

Related

Webpack tree shaking not working between packages

Good evening!
I have been trying for a few days to get tree shaking between different packages to work.
Before going further, I have created a minimum repro that I will explain throughout this post: https://github.com/Apidcloud/tree-shaking-webpack
I have also opened an issue on webpack repo: https://github.com/webpack/webpack/issues/8951
For simplicity sake, the example just uses webpack. Babel is not used.
The above example has two packages, both with their respective bundle:
core - exports 2 functions, cube and unusedFn
consumer - imports cube from core and exports its own function, consumerFn
Core package
Note that square function is not exported in the index.js file. It's a way to know that tree shaking is indeed working within core at least, as it's not included in the final bundle (which is correct).
Consumer package
As you can see, only cube is being imported from core. It then exports its own function (consumerFn) consuming cube.
Problem
The problem is that the consumer bundle is including everything from the core bundle. That is, it's including unusedFn when it shouldn't, resulting in a bigger bundle.
Ultimately, the goal is to do the same in a monorepo with multiple packages. There's no point on having them if each package is bundling the everything from the others. The goal is to bundle only what's necessary for each package.
Using optimizationBailout I can see that ModuleConcatenation plugin is issuing some warning messages. I also used --verbose flag:
Here's my webpack.config.js:
const path = require('path');
module.exports = {
mode: 'production',
entry: {
core: './src/index.js',
consumer: './consumer/index.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
// for simplicity sake I removed the UMD specifics here.
// the problem is the same, with or without it.
},
optimization: {
usedExports: true,
sideEffects: true
},
stats: {
// Examine all modules
maxModules: Infinity,
// Display bailout reasons
optimizationBailout: true
}
};
I also have "sideEffects": false in the package.json.
I went through webpack's guide too, but I'm not sure what is missing.
Related issues:
webpack-3-babel-and-tree-shaking-not-working
webpack-including-unused-exports-in-final-bundle-not-tree-shaking

Node.js require webpacked modules

I'm trying to get rid of the thousand files you get once you npm install various modules having their own dependencies.
Thus I was thinking of compiling only the libraries using webpack into one javascript file (and other required resources), then loading it to the Node.js project this way :
Entry point, that will be compiled to bundle by webpack.
module.exports = {
lodash : require('lodash'),
colors : require('colors'),
test : require('test'),
abc : require('abc')
} ;
Main
var { lodash, colors, test, abc } = require('./lib/bundle') ;
The problem I got is that some modules require system (or uncompilable) modules, such as fs, and webpack tries to bundle them to.
You just have to specify in the webpack.config.js file :
node: {
fs : "empty",
electron : "empty"
}
However, once packed into bundle, it seems that every require('fs') is replaced by Object.freeze({}) because of this setting, and then the modules fail using fs.
Would anyone have a solution for using packed modules in a Node.js project ?
P.S.: I tried using yarn with yarn autoclean --force to remove all unnecessary files, but it only removed 5% to 10% of the total.
The problem using the current node config object and set fs: 'empty' is that it will provide an empty object for those modules. More info about Webpack Node here.
You can set the Webpack target property to 'node'
Compile for usage in a Node.js-like environment (uses Node.js require to load chunks and not touch any built in modules like fs or path)
module.exports = {
target: 'node'
};
Read more about Webpack Targets
Also, to import a built-in module, use __non_webpack_require__
Generates a require function that is not parsed by webpack

Using Babel's `sourceRoot` Doesn't Affect Imports

Currently I can do:
require('./frontend/src/components/SomeComponent');
But if I set the following in my webpack.config.js:
resolve: {
root: path.resolve('frontend', 'src')
}
I can instead do:
require('components/SomeComponent');
The problem is, when I don't use Webpack (eg. in a test environment) all of my imports break. According to the Babel docs, the sourceRoot property sets the "root from which all sources are relative." This made me think I could add the following to my .babelrc to fix my imports:
"sourceRoot": "frontend/src"
... but no such luck. When I do require('components/SomeComponent'); in babel-node it fails. When I just use Babel to transpile the file, the require line is the same whether or not I set a sourceRoot.
So, my question is, is there any way (with or without sourceRoot) to simulate webpack's resolve.root in Babel?
P.S. I know there are several Babel plug-ins which address this problem, but all of the ones I've seen require you to add a ~ to the require path (which of course breaks imports in Webpack).
Many project have webpack + babel, and in many projects you sometimes bypass webpack (as in your case - for tests).
In such cases, all the resolve aliases should live in babel.
There are plugins out there to allow one reading the configuration of the other (and similar plugins for eslint etc.).

How can I use babel-polyfill for multiple separated entries / outputs?

I have this in my webpack.config.js to create two different outputs from two sources:
module.exports = {
entry: {
'dist/index.js': ['babel-polyfill', './src/Component.jsx'],
'example/bundle.js': ['babel-polyfill', './src/Page.jsx'],
},
output: {
path: './',
filename: '[name]',
},
...
Compiling it with webpack works just fine, but if I load index.js in a browser I get this error:
Uncaught Error: only one instance of babel-polyfill is allowed
I need babel-polyfill for both outputs. What can I do?
When developing a library (as opposed to an application), the Babel team does not recommend including babel-polyfill in your library. We recommend either:
Assume the ES6 globals are present, thus you'd instruct your library users to load babel-polyfill in their own codebase.
Use babel-runtime by enabling babel-plugin-transform-runtime, which attempts to analyze your code to figure out what ES6 library functionality you are using, then rewrites the code to load the polyfilled logic from babel-runtime instead of from the global scope. One downside of this approach is that it has no way to polyfill new .prototype methods like Array.prototype.find since it can't know if foo.find is an array.
So my recommendation would be to remove babel-polyfill from your dist/index.js bundle entirely.
Use idempotent-babel-polyfill
import 'idempotent-babel-polyfill';
https://github.com/codejamninja/idempotent-babel-polyfill

Is it possible to write Chrome Apps using node.js modules?

I want to write a Chrome App, but I also want to inter-op with some .Net code using Edge.js. Now I've tried it out in a Nodejs app, but am unable to figure out how to do it in a Chrome App.
I've watched the YouTube video of Paul Kinlan (the Chrome Apps office hours - NodeJS in chrome packaged apps), but can't get the code to run. I've also tried browserify with no success.
Is there a working sample which uses any of the node modules in a Chrome App (because the available resources look to be older).
Thanks in advance,
Manoj.
I've run code written for node.js inside a chrome packaged apps, and have used modules published to npm, using either browserify or webpack.
The only real tricky bit for me traditionally has been exporting functionality for use by my web app, since you don't have access to require(). I usually just create a special module that exports all global symbols I want to access and use that as my entry point.
E.g., using webpack, I'd create a file called globals.js:
module.exports = exports = {
a: require('a'),
b: require('b'),
...
}
Then create a webpack.config.js:
module.exports = {
context: __dirname + "/js",
entry: {
globals: [
"globals.js",
],
},
output: {
// Make sure to use [name] or [id] in output.filename
// when using multiple entry points
path: __dirname + "/js/generated",
filename: "[name].bundle.js",
chunkFilename: "[id].bundle.js",
library: "[name]",
libraryTarget: "umd",
}
};
Then I can pack that and include the generated bundle in my application, and use the global variable globals now.
I am not sure Edge.js works, but I would not consider it likely that it will be possible to webpack/browserify that into a web/chrome app, because their is no support for native bindings, and inter-process communication is a lot different. I'm just not sure how it could work.
(But you can probably implement your own interop with .net applications using a different kind of IPC, perhaps)

Categories