I'm trying to put my ReactJS website into production. However, I believe that my setup with my server and webpack is not well made so that it fails to load the built CSS and js on Heroku. Can anyone check on my setup, please?
server.js:
const path = require('path');
const express = require('express');
const compression = require('compression');
const minify = require('express-minify');
const webpack = require('webpack');
const webpackMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const config = require('./webpack.config.js');
const app = express();
/** *************************** Environment Setup ************************** **/
const isDeveloping = process.env.NODE_ENV !== 'production';
const port = isDeveloping ? 2333 : process.env.PORT;
/** ****************************** Output Setup **************************** **/
if (isDeveloping) {
const compiler = webpack(config);
const middleware = webpackMiddleware(compiler, {
publicPath: config.output.publicPath,
contentBase: 'src',
stats: {
colors: true,
hash: false,
timings: true,
chunks: false,
chunkModules: false,
modules: false
}
});
app.use(middleware);
// Compress everything to speedup
app.use(compression({threshold: 0}));
// Minify and cache everything
app.use(minify());
app.use(webpackHotMiddleware(compiler));
app.get('*', function response(req, res) {
res.sendFile(path.join(__dirname, '/dist'));
res.end();
});
} else {
// Compress everything to speedup
app.use(compression({threshold: 0}));
// Minify and cache everything
app.use(minify());
app.use(express.static(__dirname + '/dist'));
app.get('*', function response(req, res) {
res.sendFile(path.join(__dirname, '/dist'));
});
}
/** **************************** Server Running **************************** **/
app.listen(port, '0.0.0.0', function onStart(err) {
if (err) {
console.log(err);
}
console.info('==> 🌎 Listening on port %s.', port);
});
webpack.production.config.js:
'use strict';
var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var StatsPlugin = require('stats-webpack-plugin');
module.exports = {
entry: [
path.join(__dirname, 'app/App.jsx')
],
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]-[hash].min.js',
publicPath: '/dist/'
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new HtmlWebpackPlugin({
template: 'app/index.tpl.html',
inject: 'body',
filename: 'index.html'
}),
new ExtractTextPlugin('[name]-[hash].min.css'),
new webpack.optimize.UglifyJsPlugin({
minimize: true,
compressor: {
warnings: false,
screw_ie8: true
}
}),
new StatsPlugin('webpack.stats.json', {
source: false,
modules: false
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
],
resolve: {
root: path.resolve('./'),
alias: {
jsx: 'app/jsx',
components: 'app/jsx/components',
utils: 'app/jsx/utils'
},
extensions: ['', '.js', '.jsx']
},
module: {
loaders: [{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
"presets": ["es2015", "stage-0", "react"]
}
}, {
test: /\.json?$/,
loader: 'json'
}, {
test: /\.css$/,
loader: ExtractTextPlugin.extract('style', 'css?modules&localIdentName=[name]---[local]---[hash:base64:5]!postcss')
}, {
test: /img.*\.(jpg|jpeg|gif|png|svg)$/i,
loader: 'url-loader?name=/app/img/[name].[ext]'
}, {
test: /\.ico$/,
loader: 'file-loader?name=app/img/[name].[ext]'
}]
},
postcss: [
require('autoprefixer')
]
};
package.json script:
...
"scripts": {
"test": "",
"start": "node server",
"build": "rimraf dist && cross-env NODE_ENV=production webpack --config ./webpack.production.config.js --progress --profile --colors",
"eslint": "eslint .",
"jscs": "jscs .",
"prod": "NODE_ENV=production node server",
"postinstall": "npm run build"
},
...
Thanks for the help.
Related
Pretty new to backend dev.
I'm having trouble getting routes from a React app to work on my shared hosting space whenever refreshing /some-page. Locally I got it working with express but I'm not sure how to get it working on the shared space.
Express config:
const path = require("path");
const express = require("express");
const app = express();
const publicPath = path.join(__dirname, "..", "public");
app.use(express.static(publicPath));
app.get("*", (req, res) => {
res.sendFile(path.join(publicPath, "index.html"));
});
app.listen(process.env.PORT || 3000, () => {
console.log("Server is up!");
});
Webpack config:
const path = require("path");
module.exports = (env) => {
const isProduction = env === "production";
console.log(`env: ${env}`);
return {
mode: "none",
entry: "./src/index.js",
output: {
path: path.join(__dirname, "public", "dist"),
filename: "bundle.js",
publicPath: "/",
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{
test: /\.s?css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
{ loader: "sass-loader" },
],
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: "file-loader",
options: {
outputPath: "img/",
},
},
],
},
],
},
devtool: "source-map",
devServer: {
contentBase: path.join(__dirname, "public"),
historyApiFallback: true,
},
};
};
Package.json scripts:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build-dev": "webpack",
"build-prod": "webpack -p --env production",
"dev-server": "webpack-dev-server",
"start": "node server/server.js"
},
How do I get the express config working on the shared hosting server to have everything running like it does locally? I only have what's inside the dist folder uploaded with ftp on the shared space now.
Shared hosting account is at Siteground.
Ok, got this working by redirecting all traffic to the index.html for all routes != "/", but not necessarily using the express backend code, bc of the shared hosting limitations obviously.
I have a problem with webpack-hot-middleware and I'm not sure what I'm doing wrong.
In summary: Everytime I run the command node./ dev webpack runs and start to monitoring changes. This part is working great.
When I change my src / assets / js / index.js file, it refreshes the page after aply the changes. But with my src / assets / styles / index.scss file, it is only if that change is the first change I made after webpack start monitoring.
If I run node./ dev and change theindex.scss, the browser refresh after the changes are made in the output. On the second time, the browser does not refresh. Same happens if I change my index.js and tries to changeindex.scss.
In my Chrome console tab, it shows the following messages (when the page does not upload):
[HMR] bundle rebuilding client.js:242
[HMR] bundle rebuilt in 2407ms process-update.js:39
[HMR] Checking for updates on the server... process-update.js:110
[HMR] Nothing hot updated. process-update.js:119
[HMR] App is up to date.
Here is a Sample of my working code:
dev.js
const webpack = require('webpack');
const webpackMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const express = require('express');
const app = express();
const config = require('./webpack.dev.conf');
const DEFAULT_PORT = 3000;
const options = {
publicPath: config.output.publicPath
};
config.entry.main.push('webpack-hot-middleware/client?reload=true');
const compiler = webpack(config);
console.log('Starting the dev web server...');
app.use(webpackMiddleware(compiler, options));
app.use(webpackHotMiddleware(compiler));
app.listen(DEFAULT_PORT, (err) => {
if (err) {
console.log(err);
}
console.log('WebpackHotMiddleware is listening at http://localhost:3000/...');
});
webpack.dev.conf.js
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const base = require('./webpack.base.conf');
const appHtmlTitle = 'Hello World';
process.env.NODE_ENV = 'development';
const dirSrc = path.join(__dirname, 'src');
process.noDeprecation = true;
module.exports = {
mode: 'development',
devtool: 'source-map',
output: {
path: path.join(__dirname, 'dev'),
publicPath: '/',
filename: 'assets/js/[name].js?[hash]'
},
optimization: {
splitChunks: {
chunks: 'all' // include all types of chunks
},
},
entry: {
main: [
path.join(dirSrc, 'assets', 'js', 'index'),
path.join(dirSrc, 'assets', 'styles', 'index.scss')
]
},
module: {
rules: [{
test: /\.html$/,
loader: 'html-loader',
options: { minimize: true }
},
{
enforce: 'pre',
test: /\.js$/,
exclude: [/node_modules/],
loader: 'eslint-loader'
},
{
test: /\.js?$/,
exclude: [/node_modules/],
loader: 'babel-loader'
},
// CSS / SASS
{
test: /\.(s)?css/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: { publicPath: '/' }
},
{ loader: 'css-loader' },
{ loader: 'sass-loader' }
]
},
// IMAGES
{
test: /\.(jpe?g|png|gif)$/i,
use: [
{
loader: 'file-loader',
options: {
publicPath: '/',
name: 'assets/images/[name].[ext]'
}
}
]
},
// FONTS
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'file-loader',
options: {
publicPath: '/',
name: 'assets/fonts/[name].[ext]'
}
}]
},
plugins: [
new CleanWebpackPlugin(['dev'], { verbose: false }),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new HtmlWebpackPlugin({
filename: path.join(__dirname, 'dev', 'index.html'),
template: 'src/index.ejs',
title: appHtmlTitle,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
}),
new MiniCssExtractPlugin({
publicPath: '/',
filename: 'assets/css/[name].css?[hash]'
}),
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(false)
})
]
};
This is my current webpack configuration. Its been a while since I've had to do this, and the last time I did it webpack 2 was just coming out. At that point there was a plugin that would allow me to define my output. Now that plugin is no longer valid.
What I need to do is use the development version of ReactJS but my builds keep building with the production version. So error handling is next to impossible since react removes a bulk of the errors in a production build.
const fs = require('fs');
const os = require('os');
const path = require('path');
const webpack = require('webpack');
const files = fs.readdirSync('./src/scripts/').filter(function (file) {
return path.extname(file) === '.js';
});
const entries = files.reduce(function (obj, file, index) {
const key = path.basename(file, '.js');
obj[key] = [
'./src/scripts/' + key
];
return obj;
}, {});
entries.hotreload = 'react-hot-loader/patch';
console.log(argv.mode);
module.exports = {
mode: 'development',
entry: entries,
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
output: {
path: __dirname + '/dist/scripts',
publicPath: '/',
filename: '[name].js'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
devServer: {
contentBase: './dist/scripts',
hot: true
}
};
This is also how I start webpack webpack-dev-server --config ./webpack.config.js --mode development which doesn't seem to do me any good.
Well, I create a script to run the webpack server. This is how I start the dev server npm start.
Here is my scripts:
"scripts": {
"dev": "webpack --mode development",
"start": "webpack-dev-server",
"build": "cross-env NODE_ENV=production webpack-dev-server --client-log-level none --color --compress"
},
And here my webpack.config.js:
const modoDev = process.env.NODE_ENV != "production";
const webpack = require('webpack');
const ExtractTextPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const path = require('path');
module.exports = {
mode: modoDev ? 'development' : 'production',
entry: './src/index.jsx',
output: {
path: __dirname + '/public',
filename: './app.js',
publicPath: '/'
},
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true
}),
new OptimizeCssAssetsPlugin({})
]
},
devServer: {
host: '0.0.0.0',
port: 8080,
contentBase: './public',
historyApiFallback: {
index: "/"
},
},
resolve: {
extensions: ['*', '.js', '.jsx'],
alias: {
modules: path.resolve(__dirname + '/node_modules/')
}
},
plugins: [new ExtractTextPlugin({
filename: 'app.css'
}), new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})],
module: {
rules: [{
test: /.js[x]?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.(sa|sc|c)ss$/,
use: [ExtractTextPlugin.loader, 'css-loader', 'sass-loader']
}, {
test: /\.woff|.woff2|.ttf|.eot|.svg|.png|.jpg*.*$/,
use: ['file-loader']
},
],
}
};
I have a valid webpack.config.js that I use constantly for development, with the following content:
var path = require('path')
const merge = require('webpack-merge');
const NpmInstallPlugin = require('npm-install-webpack2-plugin');
var webpack = require('webpack')
var ManifestPlugin = require('webpack-manifest-plugin')
var InlineChunkManifestHtmlWebpackPlugin = require('inline-chunk-manifest-html-webpack-plugin')
var WebpackChunkHash = require("webpack-chunk-hash")
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var CleanWebpackPlugin = require('clean-webpack-plugin')
const proxyTarget = 'http://localhost:2246/'
const visitPort = '8056'
const isMinimize =false
const outputFolderName = ''
const TARGET = process.env.npm_lifecycle_event;
isDebug = process.env.NODE_ENV === 'production' ?
true :
false;
const common = {
entry: './src/app.js',
devServer: {
hot: true,
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
},
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
resolve: {
alias: {
vue: 'vue/dist/vue.js'
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
}
// other vue-loader options go here
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
{
test: /\.(woff|woff2|ttf|eot)(\?\S*)?$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve : {
alias: {
'vue$': 'vue/dist/vue'
}
}
}
if (process.env.NODE_ENV === 'production') {
module.exports.plugins = [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
} else {
module.exports.devtool = '#source-map'
}
if(TARGET === 'build') {
module.exports = merge(common,
{
devtool: 'source-map',
});
}
if(TARGET === "dev-server") {
module.exports = merge(common, {
devtool: 'cheap-module-eval-source-map',
devServer: {
historyApiFallback: true,
hot: true,
inline: true,
stats: true,
noInfo: true,
quiet: true,
stats: 'errors-only',
// host: process.env.HOST,
port: 3001
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new NpmInstallPlugin({
save: true // --save
})
]
});
}
The following npm run commands are in package.json and all of the commands run successfully
"scripts": {
"dev-server": "cross-env NODE_ENV=development webpack-dev-server",
"dev": "cross-env NODE_ENV=development electron ./",
"build": "cross-env NODE_ENV=production webpack --progress --profile --colors",
"start": "cross-env NODE_ENV=production electron ./",
"package": "npm run build; npm run package-osx;
}
Problem
I am trying to use the webpack-bundle-size-analyzer to check the bundled packages. The following command
$ webpack --json | webpack-bundle-size-analyzer
produced this error
/usr/local/lib/node_modules/webpack/bin/convert-argv.js:507
throw new Error("'output.filename' is required, either in config file or as --output-filename");
^
Error: 'output.filename' is required, either in config file or as --output-filename
at processOptions (/usr/local/lib/node_modules/webpack/bin/convert-argv.js:507:11)
at processConfiguredOptions (/usr/local/lib/node_modules/webpack/bin/convert-argv.js:150:4)
at module.exports (/usr/local/lib/node_modules/webpack/bin/convert-argv.js:112:10)
at yargs.parse (/usr/local/lib/node_modules/webpack/bin/webpack.js:171:41)
at Object.Yargs.self.parse (/usr/local/lib/node_modules/webpack/node_modules/yargs/yargs.js:533:18)
at Object.<anonymous> (/usr/local/lib/node_modules/webpack/bin/webpack.js:152:7)
at Module._compile (module.js:573:30)
at Object.Module._extensions..js (module.js:584:10)
at Module.load (module.js:507:32)
at tryModuleLoad (module.js:470:12)
at Function.Module._load (module.js:462:3)
at Function.Module.runMain (module.js:609:10)
at startup (bootstrap_node.js:158:16)
at bootstrap_node.js:598:3
Error: The input is not valid JSON.
Check that:
- You passed the '--json' argument to 'webpack'
- There is no extra non-JSON content in the output, such as log messages.
The parsing error was:
SyntaxError: Unexpected end of JSON input
webpack: 3.6.0
webpack-bundle-size-analyzer: 2.7.0
Additional Notes
I have validated the webpack.config.js file, at the root of project directory
No spelling mistakes in the webpack.config.js file as far as I can see
I just need a simple JSON output from webpack to feed to webpack-bundle-size-analyzer
I'm having a hard time finding resources that explain how to connect webpack to a express server app. I'm wanting to use webpack for babel to use es6 when writing react and use its hot-module and cheap-module-source-map. But, webpack runs it's own express server and that currently conflicts with my express app. I want my express app to dictate the port and routes but still get the benefits of using webpack.
Any ideas?
The express app looks something like this:
var express = require('express'),
Sequelize = require('sequelize'),
/*
set up sequelize ...
app.route ...
*/
app.listen(port), function () {
console.log('Express server listening on port ' + port
});
You don't need the webpack-dev-server to use Webpack for Babel to use ES2015 when writing React and use its hot-module and cheap-module-source-map.
Webpack configuration for React app in development env:
module.exports = {
entry: {
app: [
'react-hot-loader/patch',
'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',
'app/index.js,
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
})
.babelrc looks like this:
{
"presets": ["react", "es2015", "stage-0"],
"env": {
"development": {
"plugins": ["react-hot-loader/babel"]
}
}
}
app/index.js:
import { AppContainer} from 'react-hot-loader'
...
<AppContainer>
<App />
</AppContainer>
...
if (module.hot) {
module.hot.accept('./routes', () => {
// Hot reloading
})
}
server/index.js:
import webpack from 'webpack'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'
import webpackConfig from './webpack.dev.config'
const compiler = webpack(webpackConfig)
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: webpackConfig.output.publicPath,
}))
app.use(webpackHotMiddleware(compiler, {
path: '/__webpack_hmr',
heartbeat: 10000,
}))
I am not sure if it's allowed to refer to my own repo here, but please check my Github repo here to see how I have integrated React, Express, Webpack, HMR and Babel.
What I ended up doing was I used 2 different configurations, 1 for packing the server stuff together using webpack, and 1 for packing all the browser stuff together and also run webpack dev server for hot reloading.
Server webpack config aka webpack.node.config.js now looks like this:
var webpack = require('webpack');
var path = require('path');
var fs = require('fs');
var nodeModules = {};
// note the path.resolve(__dirname, ...) part
// without it, eslint-import-resolver-webpack fails
// since eslint might be invoked with different cwd
fs.readdirSync(path.resolve(__dirname, 'node_modules'))
.filter(x => ['.bin'].indexOf(x) === -1)
.forEach(mod => { nodeModules[mod] = `commonjs ${mod}`; });
// es5 style alternative
// fs.readdirSync(path.resolve(__dirname, 'node_modules'))
// .filter(function(x) {
// return ['.bin'].indexOf(x) === -1;
// })
// .forEach(function(mod) {
// nodeModules[mod] = 'commonjs ' + mod;
// });
module.exports =
{
// The configuration for the server-side rendering
name: 'server',
target: 'node',
entry: './app/server/serverEntryPrototype.js',
output: {
path: './bin/',
publicPath: 'bin/',
filename: 'serverEntryPoint.js'
},
externals: nodeModules,
module: {
loaders: [
{ test: /\.js$/,
loaders: [
// 'imports?document=this',
// 'react-hot',
'babel-loader'
//,'jsx-loader'
]
},
{ test: /\.json$/, loader: 'json-loader' },
]
},
plugins: [
// new webpack.NormalModuleReplacementPlugin("^(react-bootstrap-modal)$", "^(react)$")
// new webpack.IgnorePlugin(new RegExp("^(react-bootstrap-modal)$"))
// new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
};
Browser webpack config aka webpack.browser.config.js now looks like this:
var webpack = require('webpack');
var path = require('path');
var buildPath = path.resolve(__dirname, 'assets');
var fs = require('fs');
var commonLoaders = [
{ test: /\.js$/,
loaders: [
'react-hot',
'babel-loader'
//,'jsx-loader'
]
}
];
module.exports =
{
// Makes sure errors in console map to the correct file
// and line number
name: 'browser',
devtool: 'eval',
entry: [
//'./bin/www.js',
'./app/index.js',
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8081' // WebpackDevServer host and port
],
output: {
path: buildPath,
filename: '[name].js',
// Everything related to Webpack should go through a build path,
// localhost:3000/build. That makes proxying easier to handle
publicPath: 'http://localhost:8081/assets/'
},
extensions: [
'',
'.jsx', '.js',
'.json',
'.html',
'.css', '.styl', '.scss', '.sass'
],
module: {
loaders: [
// Compile es6 to js.
{
test: /app\/.*\.jsx?$/,
loaders: [
'react-hot',
'babel-loader'
]
},
///app\/.*\.json$/
{ test: /\.json$/, loader: 'json-loader' },
// Styles
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.s(a|c)ss$/, loader: 'style!css?localIdentName=[path][name]---[local]---[hash:base64:5]!postcss!sass' },
// Fonts
{ test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url-loader?limit=10000&minetype=application/font-woff' },
{ test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'file-loader' }
//{ test: /\.png$/, loader: 'url-loader?limit=100000' },
//{ test: /\.jpg$/, loader: 'file-loader' }
],
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
]
},
postcss: [
require('autoprefixer-core')
],
devtool: 'source-map'
}
;