I am using webpack to bundle my react project. My project depends on material-ui for below component:
material-ui/Dialog
material-ui/styles/getMuiTheme
material-ui/styles/MuiThemeProvider
material-ui/FlatButton
material-ui/TextField
webpack-bundle-size-analyzer reports material-ui takes 1.07MB size. Below is my webpack config file:
const webpack = require('webpack');
const path = require('path');
const NpmInstallPlugin = require('npm-install-webpack-plugin');
const WebpackShellPlugin = require('webpack-shell-plugin');
var CompressionPlugin = require("compression-webpack-plugin");
const PATHS = {
react: path.join(__dirname, 'node_modules', 'react', 'dist', 'react.min.js'),
app: path.join(__dirname, 'src'),
build: path.join(__dirname, './dist')
};
module.exports = {
entry: {
app: './app/index.jsx',
android: './app/utils/platform_android.js',
ios: './app/utils/platform_ios.js',
web: './app/utils/platform_web.js',
vendor: [
'axios',
'react',
'react-dom',
'react-redux',
'react-router',
'react-router-redux',
'redux',
'redux-thunk',
'react-alert',
'sha1',
'moment',
'nuka-carousel',
'react-cookie',
'material-ui',
'react-spinkit',
'react-tap-event-plugin',
'react-tappable',
'history',
],
},
output: {
path: PATHS.build,
filename: '[name].bundle.js',
},
watch: false,
devtool: 'source-map',
relativeUrls: true,
resolve: {
extensions: ['', '.js', '.jsx', '.css', '.less'],
modulesDirectories: ['node_modules'],
alias: {
normalize_css: __dirname + '/node_modules/normalize.css/normalize.css',
}
},
module: {
preLoaders: [
{
test: /\.js$/,
loader: "source-map-loader"
},
// {
// test: /\.js$/,
// exclude: /node_modules/,
// loader: 'jshint-loader'
// }
],
loaders: [
{
test: /\.html$/,
loader: 'file?name=[name].[ext]',
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader?presets=es2015',
},
{
test: /\.less$/,
loader: "style!css!less",
},
{test: /\.css$/, loader: 'style-loader!css-loader'},
{test: /\.png$/, loader: "url-loader?limit=100000"},
// {test: /\.(jpe?g|png|gif|svg)$/i, loader: "file-loader?name=/public/icons/[path]/[name].[ext]"},
{
test: /\.js$/,
exclude: /node_modules/,
loaders: ['babel-loader?presets=es2015']
},
{
test: /\.svg$/,
loader: 'svg-sprite',
include: /public\/icons/
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
output: {
comments: false,
},
minimize: true
}),
new NpmInstallPlugin({
save: true // --save
}),
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
}
}),
new WebpackShellPlugin({
onBuildStart: ['echo "Webpack Start"'],
onBuildEnd: [
'cp ./dist/*.js ../assets/dist/;rm -fr dist/web;' +
'mkdir -p dist/web/dist;cp ./dist/*.js ./dist/web/dist/;cp ./index.html ./dist/web/;cp -r public dist/web/',
]
}),
new CompressionPlugin({
asset: "[path].gz[query]",
algorithm: "gzip",
test: /\.js$|\.html$/,
threshold: 10240,
minRatio: 0.8
}),
new webpack.optimize.CommonsChunkPlugin(/* chunkName= */["vendor"], /* filename= */"[name].bundle.js", Infinity),
],
devServer: {
colors: true,
contentBase: __dirname,
historyApiFallback: true,
hot: true,
inline: true,
port: 9093,
progress: true,
stats: {
cached: false
}
}
}
I already tried to use CompressionPlugin, UglifyJsPlugin to optimize my bundle files but it still takes more than 1MB. How can I reduce its size? I don't want to use gzip since my app is running on webview on mobile device and some of them doesn't support gzip encoding.
Finally I figured out what the problem. In my webpack config file, I separate all vendor js into a different js bundle file. And I listed 'material-ui' there. When package my app, the whole 'material-ui' library will be packaged into vendor.js. I have to remove the material-ui from vendor list, in this way only the components required by my source code will be packaged.
There are a couple of things you could do to reduce the size:
Make sure you are only requiring in the components you need from material-ui and not the whole library
Try using Minifyify
See more suggestions in this GitHub Issue.
I would go for Option 2 listed in their documentation here: Minimizing Bundle Size. They suggest this provides a much better experience for both developers and users, I think with much faster loading times for both.
Setup your Babel configuration with babel-plugin-import or babel-plugin-transform-imports. These configurations make sure only the files that you use from Material-UI are included in your builds.
Convert all you Material-UI import statements as such:
-import Button from '#material-ui/core/Button';
-import TextField from '#material-ui/core/TextField';
+import { Button, TextField } from '#material-ui/core';
Related
I have a React app that I wrote with Webpack (without using CRA), and I am trying to implement server-side rendering to it. I wrote two webpack config files (one for server and one for client), in my original webpack config I had loaders for most of the types im going to use (ts, js, css, image formats and ttf), so my new config files had the same loaders but each one had a different output path (server under dist and client under dist/public).
However, with this I was having the same font in both output path, so I removed from one of them and then from both config files, the font loader, and still I was getting the fonts on both directories. Below are my webpack configurations.
To note that I downloaded a font and placed it under my src folder and using it in my index.css which is imported in App.tsx (which is used in both client.tsx and server.tsx).
webpack.config.server.ts:
import path from 'path';
import webpack from 'webpack';
import nodeExternals from 'webpack-node-externals';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
const config: webpack.Configuration = {
mode: 'development',
entry: path.resolve(__dirname, '..', 'src', 'server.tsx'),
output: {
path: path.join(__dirname, '..', 'dist'),
filename: 'server.js',
clean: true
},
target: 'node',
externals: [ nodeExternals() ],
resolve: {
extensions: [ '.ts', '.tsx', '.js', '.jsx', '.json' ]
},
module: {
rules: [
{
test: /\.jsx?$/i,
exclude: /node_modules/i,
use: 'babel-loader'
},
{
test: /\.tsx?$/i,
exclude: /node_modules/i,
use: 'ts-loader'
},
{
test: /\.css$/,
use: [ MiniCssExtractPlugin.loader, 'css-loader' ]
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource'
}/*,
{
test: /\.ttf$/i,
type: 'asset/resource'
}*/
]
},
plugins: [
new MiniCssExtractPlugin()
]
};
export default config;
webpack.config.client.ts:
import path from 'path';
import webpack from 'webpack';
const config: webpack.Configuration = {
mode: 'development',
entry: path.resolve(__dirname, '..', 'src', 'client.tsx'),
output: {
path: path.join(__dirname, '..', 'dist', 'public'),
filename: 'bundle.js',
clean: true
},
target: 'web',
resolve: {
extensions: [ '.ts', '.tsx', '.js', '.jsx', '.json' ]
},
module: {
rules: [
{
test: /\.jsx?$/i,
exclude: /node_modules/i,
use: 'babel-loader'
},
{
test: /\.tsx?$/i,
exclude: /node_modules/i,
use: 'ts-loader'
},
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource'
}/*,
{
test: /\.ttf$/i,
type: 'asset/resource'
}*/
]
}
};
export default config;
This is the build directory (with font commented and uncommented).
I find it a bit redundant for both configs to generate the same font, is there something missing in my configuration I should add? I am still trying to figure out how to properly implement server-side rendering so my configuration files may not be the best configurations.
I have a small React app that I'm minifying with Webpack, and I noticed that ~80% of my minified index.js file is just Bootstrap css that I'm not using, plus Webpack warns me that my entry file is too big (~900kb vs ~250kb recommended). I found a few answers here from a long time ago that said to remove parts of bootstrap.min.css file that I don't need, and I'm wondering if that's still the recommended solution for a React app using react-bootstrap?
This is my production webpack config:
const path = require('path')
const HtmlWebPackPlugin = require('html-webpack-plugin')
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
module.exports = {
entry: '/src/index.tsx',
output: {
path: path.resolve(__dirname, 'prod'),
},
mode: 'production',
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react'],
},
},
},
{
test: /\.css$/,
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'node_modules/react-toastify/dist'),
path.resolve(
__dirname,
'node_modules/bootstrap/dist/css/bootstrap.min.css'
),
path.resolve(
__dirname,
'node_modules/react-grid-layout/css/styles.css'
),
path.resolve(
__dirname,
'node_modules/react-resizable/css/styles.css'
),
],
use: ['style-loader', 'css-loader'],
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: 'ts-loader',
},
],
},
plugins: [
new HtmlWebPackPlugin({
template: './src/index.html',
}),
new FaviconsWebpackPlugin('./src/logo.png'),
],
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
}
And at the top of my App.tsx file I have
import 'bootstrap/dist/css/bootstrap.min.css'
Ok so I was able to (I think) accomplish what I was asking. I first tried to follow the instructions here:
https://medium.com/dwarves-foundation/remove-unused-css-styles-from-bootstrap-using-purgecss-88395a2c5772
So running purgecss --css [path to bootstrap css] ---content src/index.html --output public did create very minified CSS files, but either I did something wrong or purgecss removed too much, because the bootstrap styles didn't seem to be applied to anything.
Instead, I installed MiniCssExtractPlugin, and pointed webpack to the full bootstrap.min.css file. Running webpack with the below (updated) webpack.prod.js config file reduced the entry file size from ~900kb to ~360kb, which is still above the recommended limit but is about 1/3 the size of the entry before these changes.
Edit: and it also seems good that now the css styles are in their own actual main.css file, rather than for some reason being inside main.js, which is how it was before adding MiniCssExtractPlugin.
const path = require('path')
const HtmlWebPackPlugin = require('html-webpack-plugin')
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: '/src/index.tsx',
output: {
path: path.resolve(__dirname, 'prod'),
},
mode: 'production',
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react'],
},
},
},
{
test: /\.css$/,
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'public')
],
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: 'ts-loader',
},
],
},
plugins: [
new HtmlWebPackPlugin({
template: './src/index.html',
}),
new MiniCssExtractPlugin({filename: "[name].css", chunkFilename: "[id].css"}),
new FaviconsWebpackPlugin('./src/logo.png'),
],
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
}
I try to upgrade my app from webpack 2 to webpack 4.16.5.
Because I want not again end up in a hard to understand some hundreds line config, I start with a minimal config. This is my current:
const path = require("path");
const HtmlWebPackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const context = path.resolve(__dirname, "app");
module.exports = {
entry: {
home: "./index.js"
},
context,
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.html$/,
use: [
{
loader: "html-loader",
options: { minimize: true }
}
]
},
{
test: /\.(css|sass|scss)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader"
},
{
loader: "postcss-loader"
},
{
loader: "sass-loader"
}
]
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./index.html",
filename: "./index.html"
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
],
resolve: {
extensions: [".js", ".jsx", ".css", "json"],
modules: [path.resolve(__dirname, "node_modules"), context]
}
};
But I run in problems importing the CSS files from react-toolbox i.e.:
import Dialog from 'react-toolbox/lib/dialog';
in a js file and also
#import "react-toolbox/lib/button/theme.css";
causes errors like this:
ERROR in ../node_modules/react-toolbox/lib/switch/theme.css (../node_modules/css-loader!../node_modules/postcss-loader/src!../node_modules/sass-loader/lib/loader.js!../node_modules/react-toolbox/lib/switch/theme.css)
Module build failed (from ../node_modules/css-loader/index.js):
Error: composition is only allowed when the selector is single: local class name not in ".disabled", ".disabled" is weird
Does anyone have a working application with wbpack4 and react-toolbox? Also, any hints on what may cause these errors are welcome!
I'm learning react.js with the js stack from scratch tutorial and try to using react-toolbox components.
Finnaly, i have a working demo with webpack 4 and the react-toolbox, it's based on the react-toolbox-example.
This is my settings:
add css-modules related packages
$ npm install postcss postcss-cssnext postcss-import postcss-loader css-loader style-loader
add a postcss.config.js
module.exports = {
plugins: {
'postcss-import': {
root: __dirname,
},
'postcss-cssnext': {}
},
};
add a webpack rule
...
{ test: /\.css$/, use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
sourceMap: true,
importLoaders: 1,
localIdentName: '[name]--[local]--[hash:base64:8]'
}
},
'postcss-loader'
]},
...
add cmrh.conf.js - Using the css-modules-require-hook for SSR(Optional)
module.exports = {
generateScopedName: '[name]--[local]--[hash:base64:8]'
}
You can see all the settings in here, hope it will work for you.
I have a React-based web application and I'm trying to build an electron app out of it. I have gotten quite far and the app seems to load but somewhere in between I get an error saying require is not defined.
These are the versions of the tools I'm using:
webpack 3.6
react 15.6.1
electron 1.7.6
Here's a screenshot of the line where the error occurs:
Note that require is defined in Console - I read somewhere that this could be a race condition, but even if that's the case, what do I do about it?
Here's my webpack.config.js (note that I'm using the electron-renderer target):
var path = require('path');
var webpack = require('webpack');
var StatsPlugin = require('stats-webpack-plugin');
var devServerPort = 3808;
var presets = ['es2015', 'react', 'stage-0'];
var options = {
entry: {
'application': [
'react-hot-loader/patch',
'app/application.jsx'
]
},
output: {path: __dirname, filename: 'js/bundle.js' },
resolve: {
modules: [
path.join(__dirname, 'node_modules/'),
path.join(__dirname, 'app/')
],
extensions: ['.js', '.jsx']
},
node: {
__dirname: false,
__filename: false
},
plugins: [
// must match electron.webpack.manifest_filename
new StatsPlugin('manifest.json', {
// We only need assetsByChunkName
chunkModules: false,
source: false,
chunks: false,
modules: false,
assets: true
}),
new webpack.ProvidePlugin({
"React": "react",
}),
new webpack.ProvidePlugin({
"ReactDOM": "react-dom",
}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
'process.env.BASE_URL': JSON.stringify('localhost:3000'),
'global': {}, // bizarre lodash(?) webpack workaround
'global.GENTLY': false // superagent client fix
})
],
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loaders: "babel-loader", query: { presets: ['react', 'es2015', 'stage-0'] }},
{ test: /\.jsx$/, exclude: /node_modules/, loaders: "babel-loader", query: { presets: presets }},
{ test: /\.css$/, loader: "style-loader!css-loader" },
{ test: /\.png$/, loader: "url-loader?limit=100000" },
{ test: /\.jpg$/, loader: "file-loader" },
{ test: /\.(png|)$/, loader: 'url-loader?limit=100000' },
{
test: /\.(woff|woff2|ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: "file-loader"
},
{ test: /\.scss$/, loaders: ["style-loader", "css-loader?sourceMap", "sass-loader?sourceMap"] },
{
test: /\.json$/,
loaders: ['json-loader']
}
]
},
};
options.target = 'electron-renderer';
module.exports = options;
I even tried using webpack-target-electron-renderer but it caused more problems.
I've had a similar issue in the past, if it is in fact the same problem here's how to solve it.
The require you have shown is within the wrapping IIFE, which means that this is not window but the function, meaning that when you try to find require it's not in scope. In order to fix this you need use imports-loader.
In your case, under module and then loaders, add:
{
test: require.resolve("/*require/import path which requires the file where require("url") is*/"),
use: "imports-loader?this=>window"
}
Hope this solves your problem.
You need to use something like browserify or babelify.
See a more in-depth explanation here.
I'm trying to understand the innings and outings of webpack and it's quirks.
So i've set up a project working with webpack 1(i know it's outdated, but for now it will work for my needs), that coupled with react and redux.
While developing and testing locally it works like a charm all is good. when i try to set it to production it all goes A-wire. when i try to access the app i get a famous
Uncaught SyntaxError: Unexpected token <
While inspecting the firefox dev console and chrome dev console i see that on the sources the the html file is there, but the bundle is not.
The bundle is generated on the corresponding folder, either while in development mode, or in production mode.
I've tried the adding adding a dot for relative path, removing the dot on the relative path, moving the location of the js bundle file around, setting it to async and still no solution.
Bellow are the webpack config for development and production.
Development webpack file structure
const webpack = require('webpack');
const path = require('path');
//const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: ['whatwg-fetch','./src/index.js'],
module: {
devtool: 'source-map',
loaders: [
{
test: /\.js?$/,
loader: 'babel',
exclude: /node_modules/
},
{
test:/\.jsx$/,
loader: 'babel',
exclude: /node_modules/,
},
{
test: /\.scss$/,
loader: 'style!css!sass'
},{
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.png$/,
loader: "url-loader?limit=100000"
},
{
test: /\.jpg$/,
loader: "file-loader"
},
{
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url?limit=10000&mimetype=application/font-woff'
},
{
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url?limit=10000&mimetype=application/octet-stream'
},
{
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
loader: 'file'
},
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url?limit=10000&mimetype=image/svg+xml'
}
]
},
resolve: {
extensions: ['', '.js','.jsx']
},
output: {
path: path.join(__dirname, '/dist'),
publicPath: '/',
filename: 'bundle.js'
},
devServer: {
contentBase: './dist',
hot: true
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
/* new CleanWebpackPlugin(['dist'], {
verbose: true,
dry: false,
exclude: ['index.html','server.bundle.js','dbFactory.js','httpService.js']
}) */
]
};
Webpack production file structure
const config = require('./webpack.config.js');
const webpack = require('webpack');
config.plugins.push(
new webpack.DefinePlugin({
"process.env": {
"NODE_ENV": JSON.stringify("production")
}
})
);
config.plugins.push(
new webpack.optimize.UglifyJsPlugin({
comments:false,
minimize:true,
mangle:true,
compress: {
warnings: false,
sequences: true,
dead_code: true,
conditionals: true,
booleans: true,
unused: true,
if_return: true,
join_vars: true,
drop_console: true,
screw_ie8: true,
}
})
);
config.plugins.push(
new webpack.optimize.DedupePlugin()
);
/*
config.plugins.push(
new webpack.optimize.CommonsChunkPlugin({
name:'vendor',
entries:['history',
'react',
'react-dom',
'react-router',
'react-redux',
'redux'],
chunks:['vendor'],
minChunks:Infinity
})
);*/
module.exports = config;
The package.json command to generate the bundle is the following one
"postinstall": "webpack -p --config webpack.prod.config.js --progress --colors"
now with some more time to spare and some console.log abuse it looks like that the actual bundle was being created on the correct folder but it was not being obtained correctly by express.
One lesson learned is that always check your paths!