In my app js
app.use(express.static(join(__dirname, '/dist')))
Webpack config:
module.exports = {
entry: "./src/main.js",
output: {
path: join(__dirname, "/dist/js/"),
filename: "bundle.js",
publicPath: "/"
},
mode: "development"
// ...
};
In my footer.hbs
<script src="/js/bundle.js"></script>
In my package.json Scripts
"startw": "webpack-dev-server --open",
"server": "node app.js",
When I make a change in any js file, webpack is compiling but it does not overwrite /js/bundle.js. How can I make webpack recompile my changed bundle js to overwrite dist/js/bundle.js ?
I have tried
https://webpack.js.org/guides/development/#using-webpack-dev-server
and
webpack-dev-middleware
For production, I use
"dev": "./node_modules/.bin/webpack -p --watch",
This compiled as expected to dist/js/bundle.js but this is tedious to run it each time I make a simple change to a js file.
I want to inject the auto compiled output js to use it in my app. I dont mind refreshing the page myself and do not care if I dont get to use HMR.
Related
I've create a react app through the create-react-app package and I want to get a Add To Homescreen button on my app. I run my development server from yarn start on localhost:8080. I've added the Bypass user engagement checks in Chrome through > chrome://flags/#bypass-app-banner-engagement-checks
I navigate to my app in my browser (on the pc) and go into responsive manipulation, but I see a error in the log:
Site cannot be installed: Page has no manifest URL
The create-react-app plugin puts the Manifest in the ./public folder but I'm using webpack-dev-server to start the app "start": "webpack-dev-server --mode development --open --hot --env.presets serviceworker",
In my webpack.config.js I have:
module.exports = {
entry: './src/index.tsx',
devServer: {
contentBase: '/public'
},
My ./public folder contains the manifest.json file. But when I start the app I get the error:
Site cannot be installed: Page has no manifest URL
I think my webpack is not pointing to the correct folder, or the webpack config is wrong some other way.
The path in the contentBase should start with a .. So:
devServer: {
contentBase: './public'
},
And in my src/index.html:
<link href="manifest.json" rel="manifest" />
Now the manifest is loaded.
Note: I am not using NextJS for my SSR application. I also did not use create-react-app to create my application.
I have a React Server Side Rendered (SSR) application which was hand built (as apposed to using a tool like create-react-app). I use WebPack to bundle up the server side code and the client side code. I followed the excellent Udemy course https://www.udemy.com/course/server-side-rendering-with-react-and-redux/ to understand how to create a React SSR application
My Application
Application structure
webpack.base.config.js
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/i,
use: 'file-loader',
},
{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
cwd: __dirname,
},
},
],
},
resolve: {
extensions: ['.js', '.jsx'],
},
};
webpack.client.config.js
const path = require('path');
const { merge } = require('webpack-merge');
const CopyPlugin = require('copy-webpack-plugin');
const baseConfig = require('./webpack.base.config.js');
const config = {
entry: './src/client/client.jsx',
output: {
filename: 'client-bundle.js',
path: path.resolve(__dirname, 'public'),
},
plugins: [
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, 'src', 'styles'),
to: path.resolve(__dirname, 'public'),
},
],
}),
],
};
module.exports = merge(baseConfig, config);
webpack.server.config.js
const path = require('path');
const { merge } = require('webpack-merge');
const webpackNodeExternals = require('webpack-node-externals');
const baseConfig = require('./webpack.base.config.js');
const config = {
target: 'node',
entry: './src/server/server.js',
output: {
filename: 'server-bundle.js',
path: path.resolve(__dirname, 'build'),
},
externals: [webpackNodeExternals()],
};
module.exports = merge(baseConfig, config);
Routes
{
...Home,
path: '/',
exact: true,
},
{
...Page1,
path: '/page1',
exact: true,
},
Client Side Routing
<BrowserRouter>
...
</BrowserRouter>
Server Side Routing
<StaticRouter context={context} location={req.path}>
...
</StaticRouter>
Server Side generated HTML template
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<div id="root">${content}</div>
<script src="client-bundle.js"></script>
</body>
</html>
package.json scripts
"scripts": {
"start": "node build/server-bundle.js",
"build": "npm-run-all --parallel prod:build*",
"prod:build-server-bundle": "webpack --config webpack.server.config.js",
"prod:build-client-bundle": "webpack --config webpack.client.config.js",
"dev": "npm-run-all --parallel dev:*",
"dev:run-server": "nodemon --watch build --exec node build/server-bundle.js",
"dev:build-server-bundle": "webpack --config webpack.server.config.js --watch",
"dev:build-client-bundle": "webpack --config webpack.client.config.js --watch",
"lint": "eslint ./src --ext .js,.jsx"
},
Running my application
I run the application locally using
npm run dev
My application URLs are therefore
http://localhost:/
http://localhost:/page1
My Requirements
I would like my application to have a customizable URL path, for example "/a/b" so that my URLs would be
http://localhost:/a/b
http://localhost:/a/b/page1
or if my path is "xyz" my URLs would be
http://localhost:/xyz
http://localhost:/xyz/page1
How to i enable a custom base path in my React SSR Application.
What i tried
I hardcoded a path in my application in the HTML, and routers, i..e
<html>
<head>
<link rel="stylesheet" type="text/css" href="a/b/styles.css">
</head>
<body>
<div id="root">${content}</div>
<script src="a/b/client-bundle.js"></script>
</body>
</html>
<BrowserRouter basename="a/b/">
...
</BrowserRouter>
<StaticRouter context={context} location={req.path} basename="a/b/">
...
</StaticRouter>
But this does not work, going to either of the following
http://localhost
http://localhost/a/b
renders my home page with no stylesheet applied and no client side bundle. This is because neither of the following can be found and return a 404
http://localhost/a/b/styles.css
http://localhost/a/b/client-bundle.js
Furthermore, if i use a link to invoke the router, the URL for the styles and client-bundle has the path twice, i.e.
client side navigation to
http://localhost:8080/a/b/contact
means styles and client-bundle request urls are
http://localhost/a/b/a/b/styles.css
http://localhost/a/b/a/b/client-bundle.js
You can simply add an env variable basePath and then use that to set your routes.
Routes
{
...Home,
path: `${process.env.basePath}/`,
exact: true,
},
{
...Page1,
path: `${process.env.basePath}/page1`,
exact: true,
},
Now, if your basePath is '/a/b', your index component will be available on yourdomain/a/b/ and page1 will be available on yourdomain/a/b/page1
Hassaan Tauqir's post above which I have marked as the answer helped me refine the solution. Thank you to Hassaan.
package.json
Change the scripts for the PRODUCTION environment to have the BASE_URL specified.
"prod:build-server-bundle": "cross-env BASE_URL=/a/b webpack --config webpack.server.config.js",
"prod:build-client-bundle": "cross-env BASE_URL=/a/b webpack --config webpack.client.config.js",
Note: You have to use "cross-env" otherwise this will not work on every operating system, therefore I had to install "cross-env" first
npm install cross-env
I left the DEVELOPMENT scripts unchanged as I do not need a path when testing locally
"dev:build-server-bundle": "webpack --config webpack.server.config.js --watch",
"dev:build-client-bundle": "webpack --config webpack.client.config.js --watch",
webpack.base.config.js
Reading in BASE_URL
The "BASE_URL" is accessible in "webpack.base.config.js". I added code so that I can handle the "BASE_URL" being specified with a trailing slash or not.
// Set up the BASE_URL parameter, ensuring it does not have a trailing slash
let BASE_URL = '';
if (process.env.BASE_URL) {
BASE_URL = process.env.BASE_URL.toString().trim();
if (BASE_URL.substr(-1) === '/') {
BASE_URL = BASE_URL.substr(0, BASE_URL.length - 1);
}
}
publicPath
In the "module.exports" add the "output" section and add the "publicPath" setting. "publicPath" allows you to specify the base path for all the assets within your app, for example I have images which I reference in my application using the following code.
import myImage from '../images/myImage.png';
....
<img src={myImage } alt="myImage " />
....
"publicPath" must end in a trailing slash, therefore if we have a BASE_URL I append a / otherwise I leave it empty.
output: {
publicPath: (BASE_URL) ? `${BASE_URL}/` : '',
},
For more information on "publicPath" see https://webpack.js.org/guides/public-path/
webpack.DefinePlugin
In the "module.exports" add the "webpack.DefinePlugin" setting the environment variable to be passed through to the reset of the application
plugins: [
new webpack.DefinePlugin({
'process.env.BASE_URL': JSON.stringify(BASE_URL),
}),
],
For more information on "DefaultPlugin" see https://webpack.js.org/plugins/define-plugin/
Server Side Routing
Add the "basename" to the server side router, whose value is the variable specified in the "DefinePlugin" in the webpack config file
<StaticRouter context={context} location={req.path} basename={process.env.BASE_URL}>
...
</StaticRouter>
Client Side Routing
Add the "basename" to the client side router, whose value is the variable specified in the "DefinePlugin" in the webpack config file
<BrowserRouter basename={process.env.BASE_URL}>
...
</BrowserRouter>
I want to see how much of a speed boost I get from using the non dev version of everything so I built my site using my "production" webconfig. but dev tools still is telling me it is in "development" mode
This page is using the development build of React.
const merge = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const common = require("./webpack.common.js");
module.exports = merge(common, {
// Provides process.env.NODE_ENV with value production.
// Enables FlagDependencyUsagePlugin, FlagIncludedChunksPlugin,
// ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin,
// SideEffectsFlagPlugin and UglifyJsPlugin.
mode: "production",
// see https://webpack.js.org/configuration/optimization/
optimization: {
// minimize default is true
minimizer: [
// Optimize/minimize CSS assets.
// Solves extract-text-webpack-plugin CSS duplication problem
// By default it uses cssnano but a custom CSS processor can be specified
new OptimizeCSSAssetsPlugin({})
]
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
// only use MiniCssExtractPlugin in production and without style-loader
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
}
]
},
plugins: [
// Mini CSS Extract plugin extracts CSS into separate files.
// It creates a CSS file per JS file which contains CSS.
// It supports On-Demand-Loading of CSS and SourceMaps.
// It requires webpack 4 to work.
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
new BundleAnalyzerPlugin()
]
});
in my package.json
"scripts": {
"dev": "cross-env NODE_ENV=dev webpack-serve --config webpack.dev.js --open",
"prod": "cross-env NODE_ENV=prod webpack -p --config webpack.prod.js",
"qa": "cross-env NODE_ENV=QA webpack --config webpack.prod.js"
},
My confidence is growing that your problem stems from setting NODE_ENV to prod in your package.json. I think you should instead set it to production, or allow webpack to set it internally with the mode option in your webpack config.
The react-scripts package explicitly sets this value to production for building and searching for NODE_ENV in the facebook/react project in github project shows tons of checks for production as the value instead of prod.
Here's the index.js file for react-is, many other projects in the react source follow the same pattern:
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-is.production.min.js');
} else {
module.exports = require('./cjs/react-is.development.js');
}
I would try changing your build script to:
"prod": "cross-env NODE_ENV=production webpack -p --config webpack.prod.js",
or even let webpack -p set it automatically:
"prod": "cross-env webpack -p --config webpack.prod.js",
I'm new to React and I'm still trying to understand how things are put together. In webpack, I understand that we have to run the webpack command initially and it will generate the index.js file where we output it in the config. For my questions:
What role does this file play in runtime?
Everytime i do an npm start, does it automatically update my index.js file?
Here is my webpack.config:
var config = {
entry: './main.js',
output: {
path: __dirname,
filename: 'index.js',
},
devServer: {
inline: true,
port: 8080
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
}
]
}
}
module.exports = config;
With or without initially running webpack command my code still runs, reason for me being confused as to what it really does, because even without having the index.js in my directory I'm still able to run my code
Why we are using webpack:
When we run webpack command it will create a single index.js file in given the location.Our browser understands only vanilla html, css and javascript.But with react you are probably going to use jsx or es6. So we need to transform these to what browser understands.
According to your webpack.config , webpack is going to convert all jsx file into .js file (using bable loader)and bundle it to a single file as index.js.
Role plays by index.js:
You will be having an index.html file in your app directory.webpack automatically load index.js file to body of index.html file.This if final index.js file browser is going to use.
If you are using following configuration in package.json
{
"scripts": {
"dev": "webpack -d --watch",
"build" : "webpack -p"
},
}
then webpack keeps watching any changes in .jsx file and update index.js
As you are saying you code is running without webpack.It means you are using simple .js file.But to use es6 or .jsx you need webpack.
Hope it helps!. For more you can read https://tylermcginnis.com/react-js-tutorial-1-5-utilizing-webpack-and-babel-to-build-a-react-js-app/
React router allows react apps to handle /arbitrary/route. In order this to work, I need my server to send the React app on any matched route.
But webpack dev server doesn't handle arbitrary end points.
There is a solution here using additional express server.
How to allow for webpack-dev-server to allow entry points from react-router
But I don't want to fire up another express server to allow route matching. I just want to tell webpack dev server to match any url and send me my react app. please.
I found the easiest solution to include a small config:
devServer: {
port: 3000,
historyApiFallback: {
index: 'index.html'
}
}
I found this by visiting: PUSHSTATE WITH WEBPACK-DEV-SERVER.
historyApiFallback option on official documentation for webpack-dev-server explains clearly how you can achieve either by using
historyApiFallback: true
which simply falls back to index.html when the route is not found
or
// output.publicPath: '/foo-app/'
historyApiFallback: {
index: '/foo-app/'
}
Adding public path to config helps webpack to understand real root (/) even when you are on subroutes, eg. /article/uuid
So modify your webpack config and add following:
output: {
publicPath: "/"
}
devServer: {
historyApiFallback: true
}
Without publicPath resources might not be loaded properly, only index.html.
Tested on Webpack 4.6
Larger part of config (just to have better picture):
entry: "./main.js",
output: {
publicPath: "/",
path: path.join(__dirname, "public"),
filename: "bundle-[hash].js"
},
devServer: {
host: "domain.local",
https: true,
port: 123,
hot: true,
contentBase: "./public",
inline: true,
disableHostCheck: true,
historyApiFallback: true
}
Works for me like this
devServer: {
contentBase: "./src",
hot: true,
port: 3000,
historyApiFallback: true
},
Working on riot app
My situation was a little different, since I am using the angular CLI with webpack and the 'eject' option after running the ng eject command. I modified the ejected npm script for 'npm start' in the package.json to pass in the --history-api-fallback flag
"start": "webpack-dev-server --port=4200 --history-api-fallback"
"scripts": {
"ng": "ng",
"start": "webpack-dev-server --port=4200 --history-api-fallback",
"build": "webpack",
"test": "karma start ./karma.conf.js",
"lint": "ng lint",
"e2e": "protractor ./protractor.conf.js",
"prepree2e": "npm start",
"pree2e": "webdriver-manager update --standalone false --gecko false --quiet",
"startold": "webpack-dev-server --inline --progress --port 8080",
"testold": "karma start",
"buildold": "rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail"},
I agree with the majority of existing answers.
One key thing I wanted to mention is if you hit issues when manually reloading pages on deeper paths where it keeps the all but the last section of the path and tacks on the name of your js bundle file you probably need an extra setting (specifically the publicPath setting).
For example, if I have a path /foo/bar and my bundler file is called bundle.js. When I try to manually refresh the page I get a 404 saying /foo/bundle.js cannot be found. Interestingly if you try reloading from the path /foo you see no issues (this is because the fallback handles it).
Try using the below in conjunction with your existing webpack config to fix the issue. output.publicPath is the key piece!
output: {
filename: 'bundle.js',
publicPath: '/',
path: path.resolve(__dirname, 'public')
},
...
devServer: {
historyApiFallback: true
}
If you choose to use webpack-dev-server, you should not use it to serve your entire React app. You should use it to serve your bundle.js file as well as the static dependencies. In this case, you would have to start 2 servers, one for the Node.js entry points, that are actually going to process routes and serve the HTML, and another one for the bundle and static resources.
If you really want a single server, you have to stop using the webpack-dev-server and start using the webpack-dev-middleware within your app-server. It will process bundles "on the fly" (I think it supports caching and hot module replacements) and make sure your calls to bundle.js are always up to date.
For me I had dots "." in my path e.g. /orgs.csv so I had to put this in my webpack confg.
devServer: {
historyApiFallback: {
disableDotRule: true,
},
},
You can enable historyApiFallback to serve the index.html instead of an 404 error when no other resource has been found at this location.
let devServer = new WebpackDevServer(compiler, {
historyApiFallback: true,
});
If you want to serve different files for different URIs, you can add basic rewriting rules to this option. The index.html will still be served for other paths.
let devServer = new WebpackDevServer(compiler, {
historyApiFallback: {
rewrites: [
{ from: /^\/page1/, to: '/page1.html' },
{ from: /^\/page2/, to: '/page2.html' },
{ from: /^\/page3/, to: '/page3.html' },
]
},
});
I know this question is for webpack-dev-server, but for anyone who uses webpack-serve 2.0. with webpack 4.16.5; webpack-serve allows add-ons.You'll need to create serve.config.js:
const serve = require('webpack-serve');
const argv = {};
const config = require('./webpack.config.js');
const history = require('connect-history-api-fallback');
const convert = require('koa-connect');
serve(argv, { config }).then((result) => {
server.on('listening', ({ server, options }) => {
options.add: (app, middleware, options) => {
// HistoryApiFallback
const historyOptions = {
// ... configure options
};
app.use(convert(history(historyOptions)));
}
});
});
Reference
You will need to change the dev script from webpack-serve to node serve.config.js.