Multiple Webpack in React for SSR - reactjs

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.

Related

Can I delete parts of bootstrap.min.css that I'm not using?

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'],
},
}

How to configure css modules in my React app

Im not using the create react app project. Here is my webpack.base.js (someone else set this up so im just working off what i have)
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: "./src/index.tsx",
target: "web",
mode: "development",
output: {
filename: "[name].[chunkhash].bundle.js",
path: path.resolve(__dirname, "/opt/valkyrie/html"),
clean: true
},
watchOptions: {
ignored: '**/node_modules',
},
resolve: {
modules: ["src", "node_modules"],
extensions: [
".tsx",
".ts",
".js",
".jsx",
".svg",
".css",
".json",
".mdx",
".png",
".scss",
".sass"
],
alias: {
react: path.resolve('./node_modules/react')
}
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
exclude: /node_modules/
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '/public/icons/[name].[ext]'
}
},
],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: "Spectrum App",
template: __dirname + "/public/index.html",
inject: "body",
filename: "index.html",
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css",
}),
]
};
Can someone tell me how I turn on css modules? Googling around i see a number of posts about turning it on for the create react app but they mention existing settings that i dont have in mine.
Im using React 17.0.2
Add this to webpack module
module: {
loaders: [
{
test: /\.css/,
loaders: ['style', 'css'],
include: `${__dirname}/src`,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
and please rename the css files as component_name.module.css and import the css file in jsx/js as import styles from './component_name.module.css';

How to set a webpack 2 alias for a folder?

I have a react project that looks like the following:
app
|_ components
|_ containers
|_ actions
|_ reducers
|_ themes
|_ theme1
|_ index.jsx
|_ theme2
|_ index.jsx
package.json
webpack.config.js
My question is:
Is there any way to set an alias that allows me to call any file inside the themes folder?
I'm using webpack 2 and I found somethings like this:
resolve: {
extensions: ['*', '.js', '.jsx'],
alias: {
Themes$: path.resolve(__dirname, 'src/themes/')
}
},
I would also like to import these files in the following way:
import * as Themes from 'Themes';
When I run my project, I get the following error:
4:1 error 'Themes' should be listed in the project's dependencies.
Run 'npm i -S Themes' to add it import/no-extraneous-dependencies
4:8 error 'Template' is defined but never used
no-unused-vars 4:23 error Multiple spaces found before ''Themes''
no-multi-spaces 4:23 error Absolute imports should come before
relative imports import/first
4:23 error Unable to resolve path to module 'Themes'
import/no-unresolved 4:23 error Missing file extension for
"Themes"
I found some examples in this documentation, but I am not able to figure out the right way to implement it. Can anyone show me how I can set my webpack config in the correct way?
Below is my webpack.config.js:
var path = require('path');
var CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
entry: ['babel-polyfill', './src/js/index.jsx'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
enforce: "pre",
test: /\.jsx$/,
exclude: /node_modules/,
loader: "eslint-loader",
},
{
test: /\.jsx?$/,
exclude: [/node_modules/],
use: [{
loader: 'babel-loader',
options: { presets: ['es2015', 'react', 'stage-0'] }
}]
},
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader'}
]
},
{
test: /\.less$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader'},
{ loader: 'less-loader' },
]
},
{
test: /\.(jpg|jpeg|png|gif|svg)$/i,
use: {
loader: 'url?limit=10000!img?progressive=true'
}
},
{ 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' }
]
},
resolveLoader: { moduleExtensions: ["-loader"] },
devtool: 'source-map',
resolve: {
extensions: ['*', '.js', '.jsx'],
alias: {
Themes$: path.resolve(__dirname, 'src/themes/')
}
},
plugins: [
new CopyWebpackPlugin([
{ from: './src/html' },
{ from: './src/img/favicon.ico', to: './img'}
])
],
devServer: {
inline: true,
hot: true,
contentBase: path.join(__dirname, 'dist'),
port: 5000
}
}
Try to change configuration to variant with alias look like here:
resolve: {
extensions: ['*', '.js', '.jsx'],
alias: {
Themes: path.resolve(__dirname, 'src/themes/')
}
},
Then add to themes directory index.js (path: app/themes/index.js):
import * as theme1 from './theme1';
import * as theme2 from './theme2';
export default {
theme1,
theme2
}
File: app/themes/theme1/index.jsx should export object of all staff inside theme1 directory.
import Someting from './Someting';
import Anything from './Anything';
export default {
Someting,
Anything
}
Now you can try:
import * as MyThemes from 'Themes';
console.log(MyThemes.theme1.Someting);
console.log(MyThemes.theme1.Anything);
or
import MyFirstTheme from 'Themes/theme1';
console.log(MyFirstTheme.Someting);
console.log(MyFirstTheme.Anything);
all the same for theme2 directory.

Webpack defaulting to /public directory

I have a React app I'm porting to Webpack, and I can successfully launch it using webpack-dev-server.
Unfortunately I have to navigate to the public directory to load the app:
localhost:10000/public/
...which interferes with React router. Is it possible to have it mount to / instead? I.e:
localhost:10000/
The publicPath directive in output doesn't seem to influence this.
// webpack.config.js
module.exports = {
devServer: {
inline: true,
port: 10000 // Defaults to 8080
},
entry: {
app: ['./src/app.jsx']
},
devtool: 'source-map',
resolve: {
extensions: ['', '.js', '.jsx']
},
output: {
path: path.resolve(__dirname, "public/scripts"),
publicPath: '/scripts/',
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: [
"es2015",
"stage-0",
"react"
],
plugins: [
"transform-flow-strip-types"
]
}
}
]
}
};
Try setting the contentBase to public folder
https://webpack.github.io/docs/configuration.html#devserver

why material-ui takes too much space?

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';

Resources