I followed steps from given https://www.npmjs.com/package/react-hot-loader
Install
npm install react-hot-loader
Add react-hot-loader/babel to your .babelrc:
//.babelrc
{
"plugins": ["react-hot-loader/babel"]
}
Mark your root component as hot-exported:
//Button.js
class Button extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1,
};
}
handleClick = () =>
this.setState({
counter: this.state.counter + 1,
});
render() {
return (
<div>
<h2> Cool Counter {this.state.counter} !!!!</h2>
<button onClick={this.handleClick}>{this.state.counter}</button>
</div>
);
}
}
export default hot(Button);
//App.js
import React from 'react';
import { hot } from 'react-hot-loader/root';
import Button from './controls/Button';
const App = () => (
<div style={{ color: 'purple' }}>
<Button />
</div>
);
export default hot(App);
Make sure react-hot-loader is required before react and react-dom:
//Main.js
import 'react-hot-loader';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import './main.scss';
ReactDOM.render(<App />, document.getElementById('container'));
If you need hooks support, use #hot-loader/react-dom
npm install #hot-loader/react-dom
Use webpack aliases
// webpack.config.js
module.exports = {
// ...
resolve: {
alias: {
'react-dom': '#hot-loader/react-dom',
},
},
};
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React-Hot-Loader-Test</title>
</head>
<body>
<div id="container"></div>
<script src="/public/main.js"></script>
<link href="/public/main.css" rel="stylesheet" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
</body>
//server.js
var webpack = require('webpack');
var WebPackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');
new WebPackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true,
historyApiFallback: true,
}).listen(8080, 'localhost', function (err, result) {
if (err) {
return console.log(err);
}
console.log('Listening on localhost:8080');
});
//webpack.config.js
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: {
main: ['react-hot-loader/patch', './src/main.js'],
},
resolve: {
alias: {
'react-dom': '#hot-loader/react-dom',
},
modules: [path.resolve('./src'), path.resolve('./node_modules')],
},
output: {
path: path.resolve(__dirname, 'public'),
filename: devMode ? '[name].js' : '[name].[chunkhash].js',
publicPath: '/public/',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['react-hot-loader/webpack', 'babel-loader'],
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
'sass-loader',
],
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: devMode ? '[name].css' : 's[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
}),
],
};
//.babelrc
{
"presets": ["#babel/preset-env", "#babel/preset-react"],
"plugins": ["#babel/plugin-proposal-class-properties", "react-hot-loader/babel"]
}
Finally running 2 command
1. webpack -d
2. node server.js
result:
λ node server up and running
i 「wds」: Project is running at http://localhost:8080/
i 「wds」: webpack output is served from /public/
i 「wds」: Content not from webpack is served from G:\Projects\React-Second
i 「wds」: 404s will fallback to /index.html
react hot loading only working for html,css,jsx change
Question:
Hooks not storing previous state upon cliking button and then changing any css,html or jsx, UseState or Set state not preserving the state, it always starts with 1.
Please help what did i miss, i'am struggling from hours.
Regards
Shaz
Found the solution
//webpack.config.js
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: {
main: ['webpack-dev-server/client?http://localhost:8080', 'webpack/hot/only-dev-server', './src/main.js'],
// main: ['react-hot-loader/patch', './src/main.js'],
},
resolve: {
alias: {
'react-dom': '#hot-loader/react-dom',
},
modules: [path.resolve('./src'), path.resolve('./node_modules')],
},
output: {
path: path.resolve(__dirname, 'public'),
filename: devMode ? '[name].js' : '[name].[chunkhash].js',
publicPath: '/public/',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
// use: {
// loader: 'babel-loader',
// // options: {
// // presets: ['#babel/preset-env', '#babel/preset-react'],
// // },
// },
use: ['react-hot-loader/webpack', 'babel-loader'],
//use: ['babel-loader'],
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
'sass-loader',
],
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: devMode ? '[name].css' : 's[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
}),
],
// devServer: {
// contentBase: path.join(__dirname),
// compress: false,
// port: 8080,
// historyApiFallback: true,
// watchContentBase: false,
// publicPath: '/public/',
// },
};
Related
I try to lazy load routes using react-router-dom but it doesn't work. Webpack should automatically split chunks on import() but it doesn't, I always end up with one main.hash.js file instead of multiple chunks.
Is there something I'm missing ?
App Component:
import * as React from 'react';
import { Route, BrowserRouter, Link } from 'react-router-dom';
const Todos = React.lazy(() => import('routes/Todos'))
class App extends React.Component<{}, {}> {
render() {
return (
<>
<BrowserRouter>
<React.Suspense fallback={<div>loading...</div>}>
<Route exact path="/" render={() => <Link to="/todos">Todos</Link>} />
<Route exact path="/todos" component={Todos} />
</React.Suspense>
</BrowserRouter>
</>
)
}
}
export default App;
Here is the webpack config in case it may be related to some plugins or missing config on this side:
webpack common config:
const webpack = require('webpack');
const HtmlWebPackPlugin = require('html-webpack-plugin');
// clean folder (dist in this case)
const CleanWebpackPlugin = require('clean-webpack-plugin');
// copy files
const CopyPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
entry: path.resolve(__dirname, 'src', 'index.tsx'),
resolve: {
extensions: ['.js', '.ts', '.tsx', '.scss'],
alias: {
'src': path.resolve(__dirname, 'src/'),
'components': path.resolve(__dirname, 'src/components/'),
'routes': path.resolve(__dirname, 'src/routes/'),
}
},
plugins: [
new CleanWebpackPlugin(['dist'], {}),
new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "index.html"
}),
new CopyPlugin([
{ from: 'assets', to: 'assets' },
]),
]
};
webpack prod config:
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const path = require('path');
// split css per js file
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// optimize js
const TerserPlugin = require('terser-webpack-plugin');
// optimize css
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
// service-worker
const Workbox = require('workbox-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
module: {
rules: [
{
test: /\.ts|\.tsx$/,
loader: "ts-loader",
include: path.resolve(__dirname, 'src')
},
{
test: /\.scss$/,
loader: MiniCssExtractPlugin.loader
},
{
test: /\.scss$/,
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[hash:base64:5]'
}
},
{
test: /\.scss$/,
loader: 'postcss-loader',
},
{
test: /\.scss$/,
loader: 'sass-loader',
}
],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[hash].css',
chunkFilename: '[id].[hash].css',
}),
new OptimizeCssAssetsPlugin({}),
new Workbox.GenerateSW({
clientsClaim: true,
skipWaiting: true,
exclude: [/\.(?:png|jpg|jpeg|svg)$/],
runtimeCaching: [
{
urlPattern: /https?:\/\/.+/,
handler: 'StaleWhileRevalidate',
options: {
cacheableResponse: {
statuses: [0, 200]
}
}
}, {
urlPattern: /\.(?:png|jpg|jpeg|svg)$/,
handler: 'CacheFirst',
}],
})
],
optimization: {
minimizer: [new TerserPlugin()],
},
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
}
});
From: https://github.com/webpack/webpack/issues/5703#issuecomment-357512412
compilerOptions.module has to be set to esnext in order for webpack to split dynamic imports.
in my react app i'm using webpack 4,before implementing split chunk everything is working fine but after splitChunks it generating vendor.js file and it showing on html file also but page is blank.
my webpack file
const webpack = require('webpack');
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackMd5Hash = require('webpack-md5-hash');
const CompressionPlugin = require('compression-webpack-plugin');
const VENDOR_LIBS =[
'antd','axios','moment','rc-time-picker','react',
'react-dom','react-ga','react-google-maps','react-loadable',
'react-redux','react-router','react-router-dom','recompose','redux','redux-thunk'
];
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
entry:{
vendor: VENDOR_LIBS,
main: './src/app.js',
},
output: {
path: path.join(__dirname, 'public'),
filename: '[name].chunkhash.bundle.js',
chunkFilename: '[name].chunkhash.bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.s?css$/,
use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
},{
test: /\.(gif|svg|jpg|png|ttf|eot|woff(2)?)(\?[a-z0-9=&.]+)?$/,
loader: "file-loader",
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'style.[contenthash].css',
}),
new HtmlWebpackPlugin({
inject: false,
hash: true,
template: './src/index.html',
filename: 'index.html'
}),
new WebpackMd5Hash(),
new CompressionPlugin({
algorithm: 'gzip',
test : /\.js$|\.css$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/,
threshold: 10240,
minRatio: 0.8
}),
],
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
enforce: true
},
}
},
runtimeChunk: true,
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({})
],
},
};
and in my /src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<link rel="stylesheet" href="<%=htmlWebpackPlugin.files.chunks.main.css %>">
<title>Book Ambulance - Stanplus</title>
</head>
<body>
<div id="app">
</div>
<script src="<%= htmlWebpackPlugin.files.chunks.main.entry %>"></script>
<script src="<%= htmlWebpackPlugin.files.chunks.vendor.entry %>"></script>
</body>
</html>
you can that those files are loading in browser but page is blank
i have to questions
a) why my app not showing nothing even not any error(may be something in output,splitChunks or in html page).
b) after doing UglifyJsPlugin,CompressionPlugin and dynamically adding routes and import libs/files but also my vendor.js file size is 580 KB,except all these techniques how can i reduce the size of my vendor.js file
I'm writing a React, Server Side Rendering, Router4, Helmet, CSS Modules boilerplate, everything is awesome but one thing hurts my soul:
At first see my webpack.production.config.js:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const StatsPlugin = require('stats-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const distDir = path.join(__dirname, 'dist');
const srcDir = path.join(__dirname);
module.exports = [
{
name: 'client',
target: 'web',
entry: `${srcDir}/client.jsx`,
output: {
path: distDir,
filename: 'client.js',
publicPath: distDir,
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules\/)/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[hash:base64:10]',
sourceMap: false,
}
},
{
loader: 'sass-loader'
}
]
}
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new CleanWebpackPlugin(distDir),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true,
drop_console: true,
drop_debugger: true
}
}),
new webpack.optimize.OccurrenceOrderPlugin(),
]
},
{
name: 'server',
target: 'node',
entry: `${srcDir}/server.jsx`,
output: {
path: distDir,
filename: 'server.js',
libraryTarget: 'commonjs2',
publicPath: distDir,
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules\/)/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: "isomorphic-style-loader",
use: [
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[hash:base64:10]',
sourceMap: false
}
},
{
loader: 'sass-loader'
}
]
})
}
],
},
plugins: [
new ExtractTextPlugin({
filename: 'styles.css',
allChunks: true
}),
new OptimizeCssAssetsPlugin({
cssProcessorOptions: { discardComments: { removeAll: true } }
}),
new StatsPlugin('stats.json', {
chunkModules: true,
modules: true,
chunks: true,
exclude: [/node_modules[\\\/]react/],
}),
]
}
];
And this is my template.js file that server use this for building basic HTML:
export default ({ markup, helmet }) => {
return `<!DOCTYPE html>
<html ${helmet.htmlAttributes.toString()}>
<head>
${helmet.title.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
</head>
<body ${helmet.bodyAttributes.toString()}>
<div id="root">${markup}</div>
<script src="/dist/client.js" async></script>
</body>
</html>`;
};
Just like I write, I set sourceMap to false, so all styles loaded as <style> tag inside <head> tag.
If I set the soureMap to true, the <link> tag appears inside <head> tag, Even it is not server side, and has a weird blob url for loading CSS:
In fact I wanna server side link tag inside head tag with direct pointing to styles.css, how I can do it ?
My whole project is in THIS LINK
It has not very lot codes, little and simple, it is just a simple boilerplate. take a look
For production builds,inside client config, you don't use the style loader. You need to use the extract-text-webpack-plugin instead. You did it correctly in your server build config. But this config shouldn't be in your server build, as in the server's source code, you never use (s)css files.
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[hash:base64:10]',
}
},
{
loader: 'sass-loader'
}
]
})
}
...
plugins: [
new ExtractTextPlugin({
filename: 'styles.css'
}),
]
Add this to your client build config.
link={[{rel: "stylesheet", href: "/dist/styles.css"}]}
And add a link props to your App.jsx to load a <link> tag inside your <head> tag.
So your App.jsx render methode became:
render() {
return (
<div>
<Helmet
htmlAttributes={{lang: "en", amp: undefined}} // amp takes no value
titleTemplate="%s | React App"
titleAttributes={{itemprop: "name", lang: "en"}}
meta={[
{name: "description", content: "Server side rendering example"},
{name: "viewport", content: "width=device-width, initial-scale=1"},
]}
link={[{rel: "stylesheet", href: "/dist/styles.css"}]}/*ADD THIS*/
/>
<Switch>
<Route exact path='/' component={Homepage}/>
<Route path="/about" component={About}/>
<Route path="/contact" component={Contact}/>
</Switch>
</div>
);
}
Your client build config builds/bundles everything need for frontend. JS, CSS, Images, ... and puts this to dist folder.
Your server only serves this dist folder from root /. That's the only thing your server does (besides providing an api for example)
When I compile and run my Webpack application, I keep getting thrown this error. Anyone have an idea what my be causing it. I have tried adding the <div id="root"></div> before the script tag but that didnt solve it. I am using webpack version 3.3.0.
Thanks. This is my webpack.config file.
var webpack = require('webpack');
var path = require('path');
var react = require('react');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var BUILD_DIR = path.resolve(__dirname, 'public');
var APP_DIR = path.resolve(__dirname, 'src/client/app');
var config = {
entry: APP_DIR + '/index.js',
output: {
path: BUILD_DIR,
filename: 'bundle.js'
},
module: {
rules: [{
test: /\.js?$/,
include: path.resolve(__dirname, 'src'),
exclude: [/node_modules/],
use: [{
loader: "babel-loader",
options: {
presets: ["stage-0","es2015","react"],
plugins: ["transform-class-properties", "react-html-attrs"]
}
}]
},
{
test: /\.css?$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.html?$/,
use: [
"htmllint-loader",
{
loader: "html-loader",
options: {}
}
]
}],
},
devServer: {
contentBase: path.join(__dirname, "public"),
compress: true,
port: 9000
},
plugins: [new HtmlWebpackPlugin({
title: 'My Project',
filename: 'main.html'
})]
}
module.exports = config;
Add index.js file in src/client/app/ and make sure you have import ReactDom from 'react-dom;
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component{
render(){
return(
<div>
<h1>Hello App</h1>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementByID('root'));
At this case I am trying to set up sort of high-order component and further implement 'dynamic' image loads for it. Can you please explain what has been done wrong in order to reference to the inside the component.
React Component
class Slide extends Component {
constructor(props) {
super(props)
}
render () {
let imageLeft = {
backgroundImage: 'url(./assets/introleft.png)'
}
return (
<div className={styles.someStyles}>
<div className={styles.someStyles} style={imageLeft} > </div>
</div>
)
}
}
... state
export default connect(mapStateToProps)(Slide);
Project structure
Webpack config
const path = require('path'),
webpack = require('webpack'),
HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./index.js'
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
publicPath: '/'
},
context: path.resolve(__dirname, 'logic'),
devtool: 'inline-source-map',
devServer: {
hot: true,
contentBase: path.resolve(__dirname, 'build'),
publicPath: '/'
},
module: {
rules: [
{
test: /\.js$/,
use: [
'babel-loader',
],
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader?modules',
'postcss-loader',
],
},{
test: /\.png$/,
use: { loader: 'url-loader', options: { limit: 15000 } },
},
{
test: /\.svg$/,
use: {
loader: 'svg-url-loader',
options: {}
}
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new HtmlWebpackPlugin({
template: './index.template.html'
})<script> tag
],
};
P.S: Project has no node.js server, just wepback-dev. So react-router uses hash history /#, wounder if it somehow affects the webpack publicPath property.
When you're using backgroundImage: 'url(./assets/introleft.png)', this will be inserted as is (it's just a string), so your url-loader won't be applied to it. Instead you should import the image:
import introleft from './assets/introleft.png';
Webpack will have applied the url-loader and you get back either the data URL or the URL to the extracted image, if the size was too big. All you need to do now is put that URL into your backgroundImage:
backgroundImage: `url(${introleft})`