Reduce webpack bundle size in react.js - javascript

I am setting up my webpack config for a react app. But I am not able to reduce the size of bundle.js. In development mode the size is around 4MB and in production mode the size is around 1.5MB. Here is my config:-
const path = require('path');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin')
var config = {
entry: './src/index.js',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
{
test: [/.css$/],
use:[
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpg|gif|jpeg|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'assets/images'
}
}
]
}
]
},
devtool: 'source-map',
resolve: {
extensions: ['*', '.js', '.jsx']
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: 'bundle.js'
},
devtool: 'source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: 'index.html'
})
],
devServer: {
contentBase: './dist',
hot: true,
port: 3000,
historyApiFallback: true
}
}
module.exports = (env, argv) => {
if (argv.mode === 'development') {
config.plugins = config.plugins.concat([
new BundleAnalyzerPlugin(),
])
}
if (argv.mode === 'production') {
config.plugins = config.plugins.concat([
new CleanWebpackPlugin(),
])
}
return config;
}
Please help me out in reducing the size of bundle.js. Thanks in advance :)
Refer: Bundle size in dev mode
Refer: Bundle size in prod mode
Script to run dev webpack-dev-server --config ./webpack.config.js --mode development --open --hot
Script to run prod webpack --config ./webpack.config.js --mode production --open --hot

Try adding
new DefinePlugin({
'process.env.NODE_ENV': JSON.stringify("production"),
}),
under plugins. React specifically eliminates a lot of debug code if you set that. Other libs may or may not look for it.
This is possibly rolled into the newish mode option, the docs have changed a bit since I last looked.

Bundle size 3.11MB in dev doesn't look too bad.
To further descrease the bundle size in production:
perform bundle minification
remove source maps
compress the bundle using gzip and Brotli and then let clients choose the compression

Related

Application erros while Deploying React app on heroku

I have created a React app and used webpack to bundle it. Now I am trying to deploy the app on heroku. The build is successful but when I try to open the link I get "Application Error". Is it necessary to have server.js file on root level of your react app to delpoy on heroku.
I have modified my webpack and I think it's fine but still the same error.
//here is my webpack.common.js
const webpack = require("webpack");
module.exports = {
entry: "./index.jsx",
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
loader: "babel-loader",
options: { presets: ["#babel/env"] }
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
},
resolve: { extensions: ["*", ".js", ".jsx"] },
output: {
path: path.resolve(__dirname, "dist/"),
publicPath: "/dist/",
filename: "bundle.js"
},
plugins: [new webpack.HotModuleReplacementPlugin()]
}
// webpack.dev.js
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common');
module.exports = merge(common, {
mode:'development',
devtool: 'inline-source-map',
devServer: {
contentBase: path.join(__dirname, "public/"),
port: 3000,
publicPath: "http://localhost:3000/dist/",
hot: true
}
});
I expect the app to get deployed on heroku but I am getting application error.

Using Htmlwebpackplugin with Webpack-Dev-Middleware

Short Summary
Is combining htmlwebpackplugin functionality with webpack-dev-middleware impossible because of dev-middleware's reliance on files in memory? Screenshots of script outputs at bottom of this post. Because I've chosen to implement cache-hashed filenames in my production config, I can't seem to use dev-middleware anymore.
My Setup
I have 2 main configurations for my webpack instance. One for development (with hot reload) and one for production. I utilize webpack-merge to create a common.config that I'll eventually extract commonalities between the two configurations (for now it's fairly blank). In terms of app setup I have an API run separately in Python.
The problem
On my production config I'm using splitchunks for vendor/bundle splitting as well as some minimizations. It works perfectly fine. However, when I'm trying to run my development environment, although it's creating the the appropriate bundles for development [i.e. without the hashing] according to the terminal, the index.html file is unable to be found (likely because webpack-dev-middleware looks for things in memory). As a result, I can't see my development environment and I can't see any of the hot reload changes? Previously I would generate all my bundle files with npm run build:production and then use NPM start. I imagine dev-middleware would overlay it's in-memory version of the bundle.js changes over my file on disk, but now that I'm using hashed filenames on prod I can't really do that anymore?
Package.json scripts
"scripts": {
"clean": "rimraf dist/*.{js,css,eot,woff,woff2,svg,ttf,jpg,map,json}",
"build":
"webpack -p --progress --verbose --colors --display-error-details --config webpack/common.config.js",
"build:production": "npm run clean && npm run build",
"flow": "flow",
"lint": "eslint src",
"start": "nodemon bin/server.js",
The relevant parts of server.js
(function initWebpack() {
const webpack = require('webpack');
const webpackConfig = require('./webpack/common.config');
const compiler = webpack(webpackConfig);
app.use(
require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: webpackConfig.output.publicPath,
}),
);
app.use(
require('webpack-hot-middleware')(compiler, {
log: console.log,
path: '/__webpack_hmr',
heartbeat: 10 * 1000,
}),
);
app.use(express.static(path.join(__dirname, '/')));
})();
app.get(/.*/, (req, res) => {
res.sendFile(path.join(__dirname, '/dist/index.html'));
});
common.config.js
const path = require('path');
const merge = require('webpack-merge');
// const HtmlWebpackPlugin = require('html-webpack-plugin');
const development = require('./dev.config');
const production = require('./prod.config');
const TARGET = process.env.npm_lifecycle_event;
const PATHS = {
app: path.join(__dirname, '../src'),
build: path.join(__dirname, '../dist'),
nodeModulesDir: path.join(__dirname, 'node_modules'),
indexFile: path.join(__dirname, './src/index'),
};
process.env.BABEL_ENV = TARGET;
const common = {
entry: [PATHS.app],
output: {
path: PATHS.build,
},
};
if (TARGET === 'start' || !TARGET) {
module.exports = merge(development, common);
}
if (TARGET === 'build' || !TARGET) {
module.exports = merge(production, common);
}
dev.config.js
const webpack = require('webpack');
const path = require('path');
const fs = require('fs');
require('babel-polyfill').default;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const PATHS = {
app: path.join(__dirname, '../src'),
};
module.exports = {
devtool: 'cheap-module-eval-source-map',
entry: ['webpack-hot-middleware/client', './src/index'],
mode: 'development',
output: {
publicPath: '/dist/',
},
resolve: {
extensions: ['.jsx', '.js', '.json', '.scss', '.less'],
modules: ['node_modules', PATHS.app],
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
'postcss-loader',
],
},
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
},
},
'postcss-loader',
'sass-loader',
],
},
{
test: /\.less$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
},
},
'postcss-loader',
{
loader: 'less-loader',
options: {
//addlater
},
},
],
},
{
test: /bootstrap-sass\/assets\/javascripts\//,
use: [
{
loader: 'imports-loader',
options: {
jQuery: 'jquery',
},
},
],
},
{
test: require.resolve('jquery'),
use: [
{
loader: 'expose-loader',
options: '$',
},
{
loader: 'expose-loader',
options: 'jQuery',
},
],
},
{
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 50000,
mimetype: 'application/font-woff',
},
},
],
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
},
__DEVELOPMENT__: true,
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.ProvidePlugin({
jQuery: 'jquery',
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index_template.html',
}),
],
};
Here's an example of what my index.html file looks like after using npm run build:production. As you can see, the in-memory version of index.html probably can't work with this anymore with the hashed filenames?
<link href="/dist/vendor.a85f.css" rel="stylesheet"><link href="/dist/main.4d1e.css" rel="stylesheet"></head>
<body>
<div id="root"></div>
<script type="text/javascript" src="/dist/manifest.81a7.js"></script><script type="text/javascript" src="/dist/vendor.99aa.js"></script><script type="text/javascript" src="/dist/main.6eb4.js"></script></body>
Other notes:
On latest version of webpack 4.
My production version works fine
Any help much appreciated.
UPDATE:
I've swapped out rimraf dist for clean webpack plugin and moved it to my common.config. That way on each build it's doing the clean before generating index.html. However, I've noticed that when I use npm start, while the output in terminal is showing that files are emitted....I can't find them anywhere? After investigated webpack-dev-middleware, it seems they store things in memory. This is probably the core problem. How can I tie htmlwebpack plugin together with something like dev-middleware if it's in memory or perhaps I need to maintain a separate index.html file? I'm guessing the reason why this flow worked previously was because I had static names for bundle.js for both prod and dev so the in-memory version had no problem. now that the names are hashed from the prod version...it doesn't know what to do?

Vue.js / webpack: how do I clear out old bundle main-*.js files when hot-reload transpiles them?

I'm using Vue.js to make an SPA application with Django and I transpile, uglify, and bundle the code using webpack (specifically webpack-simple from vue-cli setup).
I use the following to "watch" and hot-reload the code:
$ ./node_modules/.bin/webpack --config webpack.config.js --watch
The problem is every time I change the code and it gets built it generates a new bundle .js file and updates webpack-stats.json to point to that one, but doesn't delete the old ones. How do I have it delete the old (useless) files?
webpack.config.js:
var path = require("path")
var webpack = require('webpack')
var BundleTracker = require('webpack-bundle-tracker')
function resolve (dir) {
return path.join(__dirname, dir)
}
module.exports = {
context: __dirname,
// entry point of our app.
// assets/js/index.js should require other js modules and dependencies it needs
entry: './src/main',
output: {
path: path.resolve('./static/bundles/'),
filename: "[name]-[hash].js",
},
plugins: [
new BundleTracker({filename: './webpack-stats.json'}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
],
module: {
loaders: [
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'}, // to transform JSX into JS
{test: /\.vue$/, loader: 'vue-loader'}
],
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'#': resolve('src')
}
},
}
webpack-stats.json:
{
"status":"done",
"chunks":{
"main":[
{
"name":"main-faa72a69b29c1decd182.js",
"path":"/Users/me/Code/projectname/static/bundles/main-faa72a69b29c1decd182.js"
}
]
}
}
Also what's a good way to add this to git/source control? Otherwise it changes everytime and I have to add it like so:
$ git add static/bundles/main-XXXXX.js -f
which gets annoying.
Any pointers? Thanks!
You need clean-webpack-plugin github link
First install it:
npm i clean-webpack-plugin --save-dev
Then in webpack.config.js add these lines(I have added comments the lines I added):
var path = require("path")
var webpack = require('webpack')
var BundleTracker = require('webpack-bundle-tracker')
const CleanWebpackPlugin = require('clean-webpack-plugin'); // require clean-webpack-plugin
function resolve (dir) {
return path.join(__dirname, dir)
}
// the path(s) that should be cleaned
let pathsToClean = [
path.resolve('./static/bundles/'), // same as output path
]
// the clean options to use
let cleanOptions = {
root: __dirname,
exclude: [], // add files you wanna exclude here
verbose: true,
dry: false
}
module.exports = {
context: __dirname,
// entry point of our app.
// assets/js/index.js should require other js modules and dependencies it needs
entry: './src/main',
output: {
path: path.resolve('./static/bundles/'),
filename: "[name]-[hash].js",
},
plugins: [
new CleanWebpackPlugin(pathsToClean, cleanOptions), // add clean-webpack to plugins
new BundleTracker({filename: './webpack-stats.json'}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
],
module: {
loaders: [
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'}, // to transform JSX into JS
{test: /\.vue$/, loader: 'vue-loader'}
],
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'#': resolve('src')
}
},
}
And that's it, now every time you will run npm run build, the plugin will delete the static/bundles/ folder then build, so all your previous files will get removed, only new files will be there. It won't remove old files while watching with npm run watch
The current latest version does not need any options passed in for most cases. Consult the documentation for more specifics https://www.npmjs.com/package/clean-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpackConfig = {
plugins: [
/**
* All files inside webpack's output.path directory will be removed once, but the
* directory itself will not be. If using webpack 4+'s default configuration,
* everything under <PROJECT_DIR>/dist/ will be removed.
* Use cleanOnceBeforeBuildPatterns to override this behavior.
*
* During rebuilds, all webpack assets that are not used anymore
* will be removed automatically.
*
* See `Options and Defaults` for information
*/
new CleanWebpackPlugin(),
],
};
module.exports = webpackConfig;
You should adjust webpack so a new bundle is only being created when actually building for production.
From the webpack-simple vue-cli template, you'll see that uglifying and minifying only take place when it is set to a production env, not a dev env:
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}

Webpack dev server not watching files after changing name of root directory

I recently changed the name of my root directory (where package.json and webpack.config.js sits) and now webpack-dev-server is not updating anytime I change my files.
Here's my webpack config:
var debug = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
var path = require('path');
module.exports = {
context: path.join(__dirname, "src"),
devtool: debug ? "inline-sourcemap" : null,
entry: "./js/init.js",
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
query: {
presets: ['react', 'es2015', 'stage-0'],
plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy'],
}
}
]
},
output: {
path: __dirname + "/src/",
filename: "app.js"
},
plugins: debug ? [] : [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
],
devServer: {
port: 3000,
hot: true,
historyApiFallback: {
index: 'index.html'
}
}
};
And my directory looks like this (Client React is the folder that had its name changed):
Let me clarify that this worked fine before, so I really have no idea why this isn't working now.
Edit: Scripts in package.json
"scripts": {
"dev": "./node_modules/.bin/webpack-dev-server --content-base src --inline --hot",
"build": "webpack"
},
You have to remove the brackets. Probably because they are not properly escaped by the watch module that webpack uses (watchpack) or the part that does the final watching in the System itself. I recommend you don't use any special characters inside directory- or filenames because of such bugs.

Webpack --hot in windows not rebuilding bundle

So I am using webpack-dev-server and its live reload ability. I am on a windows machine. When I change a js file it seems to be reloading the browser but it doesn't rebuild the bundle. Here is my webpack config file
var webpack = require("webpack");
var path = require('path');
module.exports = {
entry: ['./app/thirdparty', "./app/app.js"],
output: {
filename: "./build/bundle.js",
publicPath: "/assets/"
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}]
},
resolve: {
extensions: ['', '.js', '.es6']
},
include: path.join(__dirname, 'app')
}
I've tried to run it with
webpack-dev-server
and
webpack-dev-server --hot
But the bundle is not rebuilt
So I solved my own question.
I had a file looking like :
output: {
filename: "./build/bundle.js",
publicPath: "/assets/"
}
I changed it to
output : {
path: path.resolve('build/js'),
publicPath: "/public/js/",
filename : "bundle.js",
}
Which means it will create a bundle.js that will end up in /build/js/bundle.js
BUT it needs to be referred to in index.html as public/js/bundle.js because of how the publicPath is specified. Also running
webpack-dev-server --inline
Made everything work. Its obvious once you understand web pack I guess...

Categories