Related
I have a project in which I bundle a components library using Rollup (generating a bundle.esm.js file). These components are then used in another project, that generates web pages which use these components - each page is using different components.
The problem is, that the entire components library is always bundled with the different page bundles, regardless of which components I'm using, unnecessarily increasing the bundle size.
This is my Rollup setup:
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import pkg from './package.json';
const extensions = [
'.js', '.jsx', '.ts', '.tsx',
];
export default [
{
input: './src/base/index.ts',
plugins: [
peerDepsExternal(),
resolve({ extensions }),
babel({
exclude: 'node_modules/**',
extensions,
}),
commonjs(),
],
output: [
{ file: pkg.main, format: 'cjs', sourcemap: true },
{ file: pkg.module, format: 'es', sourcemap: true },
],
watch: {
clearScreen: false,
},
},
];
I have "modules" set to false in webpack, as well.
There are things you will need to do to achieve treeshakable code from both sides - the built package and the project using it.
From your code snippet, I see that you have not add flag preserveModules: true in the rollup config file to prevent the build output from bundling. Webpack can not treeshake a bundled file FYI.
export default {
...
preserveModules: true,
...
}
On the side of the project that using it, you have to specify sideEffects in the package.json - read the doc to know how to config them. Beside that, the optimization in webpack has to has sideEffects: true, also read the doc here.
Hope this helps!
As you don't know which components of your Component Library (CL) will be needed by the adopters repositories you need to export everything but in a way
the adopters can execute a tree-shaking on your CL when they do their own build (and just include what they really need).
In a few words, you have to make your CL, tree-shakable. In order to achieve this, on your CL repo you have to:
Use bundlers that support tree-shaking (rollup, webpack, etc..)
Create the build for modules of type es/esm, NOT commonJS/cjs, etc..
Ensure no transpilers/compilers (babel,tsconfig, etc..) usually used as plugins, transform your ES module syntax to another module syntax.
By the default, the behavior of the popular Babel preset #babel/preset-env may break this rule, see the documentation for more details.
// babelrc.json example that worked for me
[
"#babel/preset-env",
{
"targets": ">0.2%, not dead, not op_mini all"
}
],
In the codebase, you always have to use import/export (no require) syntax, and import specifically the things you need only.
import arrayUtils from "array-utils"; //WRONG
import { unique, implode, explode } from "array-utils"; //OK
Configure your sideEffects on the package.json.
"sideEffects": ["**/*.css"], //example 1
"sideEffects": false, //example 2
DO NOT create a single-bundle file but keep the files separated after your build process (official docs don't say this but was the only solution that worked for me)
// rollup.config.js example
const config = [
{
input: 'src/index.ts',
output: [
{
format: 'esm', // set ES modules
dir: 'lib', // indicate not create a single-file
preserveModules: true, // indicate not create a single-file
preserveModulesRoot: 'src', // optional but useful to create a more plain folder structure
sourcemap: true, //optional
},
],
... }]
Additionally, you may need to change your module entry point in order the adopters can directly access to the proper index.js file where you are exporting everthing:
// package.json example
{
...
"module": "lib/index.js", //set the entrypoint file
}
Note: Remember that tree-shaking is executed by an adopter repository that has a build process that supports tree-shaking (eg: a CRA repo) and usually tree-shaking is just executed on prod mode (npm run build), no on dev mode. So be sure to properly test if this is working or not.
I'm using node.js and webpack to create a bundle. From what I've read, node.js should contain fs module for managing files. However when I call require("fs") I get an Cannot find module "fs" error. What should I do?
I came across this problem myself when bundling with webpack and found the answer on this thread.
The way to solve it for me was to use the following config:
module.exports = {
entry: "./app",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [
{
test: /\.js$/,
exclude: 'node_modules',
loader: 'babel',
query: {presets: ['es2015']},
}
]
},
target: 'node'
};
By setting target to node webpack will make the necessary changes to bundle your node application
Edit: This answer targeted webpack 1.x which has now been superseded.
If you are running your webpack bundle in nodejs environment then target: 'node' is required in webpack.config.js file otherwise webpack takes default value as web for target check here.
You can resolve the issue in two ways
Add below configuration to your webpack.config.js
node: {
fs: "empty"
}
OR
Add below configuration to your package.json
"browser": {
"fs": false
}
Edit:
promising fix is
"browser": {
"fs": false
}
I had the same issue when bundling a NWjs application using webworkers (which in turn had node enabled).
The solution I found was to include each native module I used in externals with the prefix commonjs to the name of the module. For example:
...
target: "webworker", // or 'node' or 'node-webkit'
externals:{
fs: "commonjs fs",
path: "commonjs path"
}
...
I've done the same for targets "webworker" and "node-webkit" in different projects to solve the same issue.
webpack nwjs webworker nodejs node
Add below configuration to your webpack.config.js
resolve: {
fallback: {
fs: false
}
}
I needed to build a class that would use fetch if executed in a browser, or fs if executed in node. For other reasons, it was impractical to produce separate bundles, so I produced a single browser-targeted bundle.
The solution I used was to use eval('require("fs")') if the script was running in node.
const fs = eval('require("fs")')
Browser-safe (fs is null in the browser):
const fs = typeof window === 'object'
? null
: eval('require("fs")')
After trying everything I found on the internet (target, externals, node configs), the only solution that actually worked for me was replacing:
const filesystem = require("fs")
or
import fs from "fs"
by the special webpack version
const fs = __non_webpack_require__("fs")
This generates a require function that is not parsed by webpack.
In addition to the answer of PDG
I'm used to this short copy/paste candy.
Using path and fs :
var nodeModules = {};
fs.readdirSync(path.resolve(__dirname, 'node_modules'))
.filter(x => ['.bin'].indexOf(x) === -1)
.forEach(mod => { nodeModules[mod] = `commonjs ${mod}`; });
// Before your webpack configuration
module.exports = {
...
}
Then inside your configuration file, include the nodeModules variable in the externals
...
externals: nodeModules,
...
It would be more elegant to use pre-defined solution as:
Adding target: 'node' to webpack config file.
More info on: official documentation
For the solution we are building we had to force an older version of webpack:
npm install --save --force webpack#webpack-3
I have a large project that I've been refactoring to use webpack and ES6 modules. Many of my dependencies are using commonJS or AMD modules, so I'm requiring them the old way.
For example,
const Pikaday = require("pikaday");
Unfortunately, when I now run
webpack --progress
it looks like webpack is trying to parse every single file in that path. I get several warnings that look like, for example:
Module parse failed: /path/to/node_modules/pikaday/index.html
Eventually, I get the following error:
ERROR in ./~/pikaday/tests/methods.js
Module not found: Error: Cannot resolve module 'expect.js' in
/Users/myuser/web/myproject/node_modules/pikaday/tests
I'm getting an error from a tests directory in this dependency, which I obviously don't care about parsing for my bundle. Why is webpack trying to parse every file in that path, and how do I correct this behavior?
For clarity, I'll post the important parts of my webpack.config.js file here.
var webpack = require("webpack");
var path = require("path");
module.exports = {
entry : {
main : './public/src/Main/main.js',
},
output : {
path : './public/build',
filename : '[name].bundle.js'
},
module : {
loaders : [
{
test : /\.json$/,
loader : 'json'
},
{
test : /\.js$/,
loader : "babel?presets[]=es2015"
},
{
test : /\.css$/,
loaders : [
'style',
'css'
]
},
{
test: /\.scss$/,
loaders: ["style", "css", "sass"]
},
{
test : /\.(svg|jpg|png|jpeg)$/,
loader : 'url'
},
{
test : /vendor\/.+\.(jsx|js)$/,
loader : 'imports?jQuery=jquery,$=jquery,this=>window'
}
]
},
"plugins" : [
new webpack.ProvidePlugin({
$ : "jquery",
jQuery : "jquery",
"window.jQuery" : "jquery"
}),
new webpack.ProvidePlugin({
$clamp : "clamp-js"
}),
new webpack.ProvidePlugin({
})
]
};
When processing modules, Webpack analyses all of the dependencies reachable from your own code, traversing through everything. It will attempt to process both AMD or CommonJS style code.
In this case, the module you are loading does not appear to use a format of loading dependencies what Webpack can analyse properly. When the code from here:
var id = 'moment';
try { moment = req(id); } catch (e) {}
runs, Webpack is not smart enough to see that req(id) is actually req('moment'). Because it has no way of knowing what id is, it will consider req(id) to possibly be loading any file inside the Pikaday module, which as you're seeing, includes the tests folder, and an index.html file, both of which are causing issues for you.
In this case you are lucky because while the AMD section of the module is not parsable, this module also includes a CommonJS prefix here, which is parsable by Webpack. Because of that, you should be able to follow instructions from Is there a way to disable AMDPlugin? which will make Webpack stop processing AMD modules, and thus use the CommonJS section of the code.
Keep in mind, if you actually need to depend on any AMD modules, this will break those, but most things are CommonJS these days anyway.
Either way, it would definitely be good to file a bug on the Pikaday module, since this should be a trivial fix in the module itself.
I'm trying to do something that I believe should be possible, but I really can't understand how to do it just from the webpack documentation.
I am writing a JavaScript library with several modules that may or not depend on each other. On top of that, jQuery is used by all modules and some of them may need jQuery plugins. This library will then be used on several different websites which may require some or all modules.
Defining the dependencies between my modules was very easy, but defining their third-party dependencies seems to be harder then I expected.
What I would like to achieve: for each app I want to have two bundle files one with the necessary third-party dependencies and other with the necessary modules from my library.
Example:
Let's imagine that my library has the following modules:
a (requires: jquery, jquery.plugin1)
b (requires: jquery, a)
c (requires: jquery, jquery.ui, a, b)
d (requires: jquery, jquery.plugin2, a)
And I have an app (see it as a unique entry file) that requires modules a, b and c. Webpack for this case should generate the following files:
vendor bundle: with jquery, jquery.plugin1 and jquery.ui;
website bundle: with modules a, b and c;
In the end, I would prefer to have jQuery as a global so I don't need to require it on every single file (I could require it only on the main file, for example). And jQuery plugins would just extend the $ global in case they are required (it is not a problem if they are available to other modules that don't need them).
Assuming this is possible, what would be an example of a webpack configuration file for this case? I tried several combinations of loaders, externals, and plugins on my configuration file, but I don't really get what they are doing and which ones should I use. Thank you!
in my webpack.config.js (Version 1,2,3) file, I have
function isExternal(module) {
var context = module.context;
if (typeof context !== 'string') {
return false;
}
return context.indexOf('node_modules') !== -1;
}
in my plugins array
plugins: [
new CommonsChunkPlugin({
name: 'vendors',
minChunks: function(module) {
return isExternal(module);
}
}),
// Other plugins
]
Now I have a file that only adds 3rd party libs to one file as required.
If you want get more granular where you separate your vendors and entry point files:
plugins: [
new CommonsChunkPlugin({
name: 'common',
minChunks: function(module, count) {
return !isExternal(module) && count >= 2; // adjustable
}
}),
new CommonsChunkPlugin({
name: 'vendors',
chunks: ['common'],
// or if you have an key value object for your entries
// chunks: Object.keys(entry).concat('common')
minChunks: function(module) {
return isExternal(module);
}
})
]
Note that the order of the plugins matters a lot.
Also, this is going to change in version 4. When that's official, I update this answer.
Update: indexOf search change for windows users
I am not sure if I fully understand your problem but since I had similar issue recently I will try to help you out.
Vendor bundle.
You should use CommonsChunkPlugin for that. in the configuration you specify the name of the chunk (e.g. vendor), and file name that will be generated (vendor.js).
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity),
Now important part, you have to now specify what does it mean vendor library and you do that in an entry section. One one more item to entry list with the same name as the name of the newly declared chunk (i.e. 'vendor' in this case). The value of that entry should be the list of all the modules that you want to move to vendor bundle.
in your case it should look something like:
entry: {
app: 'entry.js',
vendor: ['jquery', 'jquery.plugin1']
}
JQuery as global
Had the same problem and solved it with ProvidePlugin. here you are not defining global object but kind of shurtcuts to modules. i.e. you can configure it like that:
new webpack.ProvidePlugin({
$: "jquery"
})
And now you can just use $ anywhere in your code - webpack will automatically convert that to
require('jquery')
I hope it helped. you can also look at my webpack configuration file that is here
I love webpack, but I agree that the documentation is not the nicest one in the world... but hey.. people were saying same thing about Angular documentation in the begining :)
Edit:
To have entrypoint-specific vendor chunks just use CommonsChunkPlugins multiple times:
new webpack.optimize.CommonsChunkPlugin("vendor-page1", "vendor-page1.js", Infinity),
new webpack.optimize.CommonsChunkPlugin("vendor-page2", "vendor-page2.js", Infinity),
and then declare different extenral libraries for different files:
entry: {
page1: ['entry.js'],
page2: ['entry2.js'],
"vendor-page1": [
'lodash'
],
"vendor-page2": [
'jquery'
]
},
If some libraries are overlapping (and for most of them) between entry points then you can extract them to common file using same plugin just with different configuration. See this example.
In case you're interested in bundling automatically your scripts separately from vendors ones:
var webpack = require('webpack'),
pkg = require('./package.json'), //loads npm config file
html = require('html-webpack-plugin');
module.exports = {
context : __dirname + '/app',
entry : {
app : __dirname + '/app/index.js',
vendor : Object.keys(pkg.dependencies) //get npm vendors deps from config
},
output : {
path : __dirname + '/dist',
filename : 'app.min-[hash:6].js'
},
plugins: [
//Finally add this line to bundle the vendor code separately
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.min-[hash:6].js'),
new html({template : __dirname + '/app/index.html'})
]
};
You can read more about this feature in official documentation.
Also not sure if I fully understand your case, but here is config snippet to create separate vendor chunks for each of your bundles:
entry: {
bundle1: './build/bundles/bundle1.js',
bundle2: './build/bundles/bundle2.js',
'vendor-bundle1': [
'react',
'react-router'
],
'vendor-bundle2': [
'react',
'react-router',
'flummox',
'immutable'
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle1',
chunks: ['bundle1'],
filename: 'vendor-bundle1.js',
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle2',
chunks: ['bundle2'],
filename: 'vendor-bundle2-whatever.js',
minChunks: Infinity
}),
]
And link to CommonsChunkPlugin docs: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin
I want to minimize my Javascript code for production. However I don't want to minimize the vendors' code because they already have a minimize version.
My current webpack.config.js splits the output code in two chunks.
module.exports = {
entry: {
vendor: ['jquery','angular'],
app: ['./Client/app.start.js']
},
output: {
filename: 'bundle.js',
path: __dirname
},
resolve : {
alias : {
'angular' : 'angular/angular.min.js',
'jquery' : 'jquery/dist/jquery.min.js'
}
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.bundle.js"),
new webpack.optimize.UglifyJsPlugin({minimize: true})
]
}
When I run >> webpack, both chunks ("bundle.js" and "vendor.bundle.js") are minimized. How can I configure Webpack to only minimize "bundle.js"?
Thanks
If, for some reason, you really want to minify only one bundle you could use the test option of the UglifyJsPlugin. Use the bundle name and don't test for individual modules with a regex because when the code is consumed by UglifyJsPlugin it's already bundled.
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false },
test: /(vendor.bundle.js\.js)+/i
})
See the docs: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
test, include, exclude (RegExp|Array): configure filtering of processed files (default: test: /.js($|\?)/i)
Usually, you would have different configs (one with the uglify and another without), for production and development, you would minimize only in production, if that's what you want.
You probably already know this, but is good to be thorough. What you may not know, is that webpack does the job better and it is recommended to use untouched code and let webpack do its thing. I don't believe that the uglifyJsPlugin is able to target chunks, maybe there is another plugin that could do it, but i am unaware.
As a side note, you don't have to worry about double minification, it adds a small effect, considering that it is a production environment and that it doesn't change by the minute.
This can be achieved via a hack as you can see in below code in webpack.config.js file:
`
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const PRODUCTION = process.env.NODE_ENV === 'production';
const plugins = [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', filename: './assets/js/vendor.min.js',
minChunks: Infinity
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
}),
(...etc other plugins)
];
if (PRODUCTION) {
plugins.push(new UglifyJsPlugin({
include: /\.min\.js$/
}));
}
`
Check if you are creating production build.
Then, you can name the chunks you want to create as minified with".min.js" extension.
Rest unglifyjsplugin -> include filter will ensure that only these chuks will be minified.
Here, in this case, it'll only minify the vendor.min.js file.