File size when using Webpack, Node, Express, React, React-Router - javascript

Webpack is bundling a file to the size of 1.34mb for a simple server. To me, it seems unnecessarily large considering how limited the server is. I know that using express, react, react-router, etc. will balloon the file, but to me, it seems huge!
Firstly, I was wondering if I was using webpack correctly? Secondly, where should I start to look to make it smaller?
server.js
var express = require('express')
var path = require('path')
import React from 'react'
import { match, RouterContext } from 'react-router'
import { renderToString } from 'react-dom/server'
import routes from './src/routes/routes'
var app = express()
app.use(express.static(path.join(__dirname, 'public')))
app.use(function(rq, rs, nx){
console.log("rq.url: ", rq.url)
nx()
})
app.get('*', function(req, res){
match({
routes: routes,
location: req.url
}, (err, redirect, props) => {
if(err){
res.status(500).send(err.message)
} else if(redirect) {
res.redirect(redirect.pathname + redirect.search)
} else if (props) {
console.log("PROPS: ", props)
let appHtml = renderToString(<RouterContext {...props} />)
res.status(200).send(renderPage(appHtml))
} else {
res.status(404).send('Not Found')
}
})
})
var PORT = process.env.PORT || 8089
app.listen(PORT, function(){
console.log('listening on port ' + PORT)
})
function renderPage(appHtml){
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>React With Server</title>
</head>
<body>
<div id="main">${appHtml}</div>
<script src="/javascript/bundle.js"></script>
</body>
</html>
`
}
webapack.config.js:
module.exports = {
entry: path.resolve(__dirname, 'server.js'),
output: {
filename: 'server.bundle.js',
path: __dirname
},
target: 'node',
node: {
__filename: true,
__dirname: true
},
module: {
rules: [
{
loader: 'babel-loader',
test: /\.js$/,
exclude: /node_modules/,
options: {
presets: ['latest', 'react']
}
}
]
}
}

What you're looking for is a Production build.
webpack -p
The -p flag automatically enables production mode, which optimises the bundle. Or you can configure those steps manually. With this the bundle's size is only 1/3 of the original.
From here you can start looking what actually makes your bundle the size it is, for example by using Webpack Bundle Analyzer.
As you can see db.json from mime-db is by far the biggest part of the bundle, taking up about a fourth of the total size. And react-dom in total also takes up about the same space.
You might be wondering why you have mime-db in the bundle. If you are using Yarn, you can run the why command:
yarn why mime-db
Which tells you:
This module exists because "express#accepts#mime-types" depends on it.
So you can't really get rid of that. While using Express and React you probably won't get much lower in size of the bundle.

Related

How to properly export an ES6 module function as a library for use in a node app?

Let's say that I have a node.js application, which does NOT go through my webpack bundling:
Node App
const Html = require('./build/ssr-bundle.js');
let result = Html.ssrbundle.render();
console.log(result);
Here is my ES6/JSX file, which is getting processed by webpack and I want to be able to access that render function in my node app (you guessed right, I am trying to SSR react stuff ;) )
src/Html.js -(webpack)-> build/ssr-bundle.js
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import CustomComponent from './custom-component.js';
module.exports = {
render : function () {
return ReactDOMServer.renderToString(<CustomComponent />);
} };
And here is my Webpack config
webpack.config.js
var path = require('path');
module.exports = {
entry: {
ssr: './src/Html.js',
//frontend: './src/frontend-Html.js'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: 'ssr-bundle.js',
library: 'ssrbundle'
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['env','react'],
plugins: ["transform-es2015-destructuring", "transform-object-rest-spread"]
}
},
{
test:/\.css$/,
use:['style-loader','css-loader']
}
]
},
stats: {
colors: true
},
devtool: 'source-map'
};
Whatever I do, I cannot figure out how to properly use that exported variable "ssrbundle" and subsequently the render function. If I had my node app included in the bundle, everything would be all right, but this is not what I want to do.
As apokryfos suggested, I played around with the libraryTarget Webpack setting. You can find more info on using Webpack to author a library (what I was really trying to achieve) here:
https://webpack.js.org/guides/author-libraries/
and here are code examples:
https://github.com/kalcifer/webpack-library-example/blob/master/webpack.config.babel.js.
What did the trick for me, was to set the libraryTarget to "umd" , which is different than the "var" setting which is set by default and is suitable i.e. for including the script in an HTML file

Text content did not match. Warning in React 16

I trying to build ReactJs application with server side rendering
My entry points for client and server:
client.jsx
const store = createStore(window.__INITIAL_STATE__);
hydrate(
<Provider store={store}>
<BrowserRouter>{renderRoutes(routes)}</BrowserRouter>
</Provider>,
document.querySelector('#root')
);
server.jsx
const app = express();
if (isDev) {
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('../../webpack.config.js');
const compiler = webpack(config);
app.use(express.static('/public'));
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
stats: 'errors-only',
})
);
}
app.get('*', (req, res) => {
const helmet = Helmet.renderStatic();
const htmlAttrs = helmet.htmlAttributes.toComponent();
const bodyAttrs = helmet.bodyAttributes.toComponent();
const context = {};
const data = {};
res.set('content-type', 'text/html');
res.send(
'<!DOCTYPE html>' +
renderToString(
<html {...htmlAttrs}>
<head>
{helmet.title.toComponent()}
{helmet.meta.toComponent()}
{helmet.link.toComponent()}
</head>
<body {...bodyAttrs}>
<div id="root">
<StaticRouter location={req.url} context={context}>
{renderRoutes(routes)}
</StaticRouter>
</div>
<script
dangerouslySetInnerHTML={{
__html: `window.__INITIAL_STATE__ = ${JSON.stringify(data)}`,
}}
/>
<script src="/public/vendor.js" />
<script src="/public/app.js" />
</body>
</html>
)
);
});
And component:
home.jsx
import React, { Component } from 'react';
class Home extends Component {
render() {
return <div>home</div>;
}
}
export default Home;
When I change in my component Home and refresh browser page I get this error:
Warning: Text content did not match. Server: "home" Client: "home1"
Its ok because server render old version of my code. How to reload the code on the server so that the client and server versions are equal?
For those discovering this error because your client and server purposefully render different content (like the server rendering a dark theme which gets replaced with the user preference on load), use suppressHydrationWarning to suppress the error.
For example:
<div suppressHydrationWarning>Ignore this</div>
The problem here is that your server-side application does not reflect code changes. To do that you have to configure your express app as a webpack entry.
Briefly, you need 2 webpack configurations one for server and another for client code.
The server one will look something like this
module.exports = {
entry: {
server: './server.js',
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].js'
},
target: 'node',
node: {
// Need this when working with express, otherwise the build fails
__dirname: false, // if you don't put this is, __dirname
__filename: false, // and __filename return blank or /
},
externals: [nodeExternals()], // Need this to avoid error when working with Express
module: {
rules: [
{
// Transpiles ES6-8 into ES5
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
// Loads the javacript into html template provided.
// Entry point is set below in HtmlWebPackPlugin in Plugins
test: /\.html$/,
use: [{loader: "html-loader"}]
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./index.html",
filename: "./index.html",
excludeChunks: [ 'server' ]
})
]
}
Here is a nice article explaining how to do that in details
No one said about but Cloudflare replaced double spaces with a space when you enable Auto Minify (HTML) in optimization.
And this error will happen.

How to load web components in webpack project?

I have started experimenting with webpack and webcomponents. So far I managed only to run this basic web component example without webpack. If I simply load the web component in the index.html using a <script> tag than I get it's content rendered. However, when I try to run the same example using a basic webpack setup I don't see the rendered template.
The server is launched successfully and I can see the console init message. What is the missing step? I would expect to be able to concatenate them this way.
I have the following files:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/">
<meta charset="UTF-8">
<title>Web Components App</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<vs-app></vs-app>
</body>
</html>
main.ts
module.hot && module.hot.accept();
// Components
import { App } from './app';
// Styles
require('./shared/scss/main.scss');
console.log('Initialise Main');
app.ts
/**
* App
*/
export class App extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = this.template;
}
get template() {
return `
<div>This is a div</div>
`;
}
}
window.customElements.define('vs-app', App);
webpack-common.js
const webpack = require("webpack"),
path = require("path"),
CopyWebpackPlugin = require('copy-webpack-plugin'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
// Constants
const BUILD_DIR = path.join(__dirname, "../../build"),
PUBLIC_DIR = path.join(__dirname, "../../public"),
NODE_MODULES_DIR = path.join(__dirname, "../../node_modules");
// Shared between prod and dev
module.exports = {
entry: "./public/main.ts",
output: {
// Since webpack-dev-middleware handles the files in memory, path property is used only in production.
path: BUILD_DIR,
publicPath: "/",
filename: "bundle.js"
},
resolve: {
extensions: ["*", ".ts", ".js"]
},
module: {
loaders: [{
test: /\.ts?$/,
loader: "awesome-typescript-loader",
include: PUBLIC_DIR,
exclude: /node_modules/
},
{
test: /\.css$/,
exclude: /node_modules/,
loader: "style-loader!css-loader!autoprefixer-loader"
},
{
test: /\.scss$/,
loader: 'style-loader!css-loader!sass-loader'
},
]
},
// Base plugins
plugins: [
new CopyWebpackPlugin([
{
from: `public/shared/images`,
to: 'images'
},
]),
new HtmlWebpackPlugin({
template: 'public/index.html'
}),
// Load bundle.js after libs are loaded
new ScriptExtHtmlWebpackPlugin({
defaultAttribute: 'defer'
})
],
stats: {
colors: true,
modules: true,
reasons: true,
errorDetails: true
}
};
Edit
Eventually I got the component rendered moving the following line window.customElements.define('vs-app', App); from app.ts to main.ts. In the mean time I discovered that even that is not necessary.
// This is enough to trigger rendering
import { App } from './app';
App;
However, I still have an issue. Because of webpack hot reload, the component ends up registered twice, giving an error. Still looking for a fix.
Edit 2
After some research online I managed to find the problem: I did not have the inject: false property. This triggered webpack to load twice the same stuff. Looks like I can finally move forward with developing the app. However I would love to find out alternative ways to this setup, just to get confirmation that I'm on the right road.
new HtmlWebpackPlugin({
template: 'public/index.html',
inject: false
}),

'Cannot find module' when using webpack loaders

I use custom webpack for my app with Next.js:
const path = require('path');
const Webpack = require('webpack');
module.exports = {
webpack: (config, { dev }) => {
config.plugins.push(
new Webpack.DefinePlugin([{
__BROWSER__: "typeof window !== 'undefined'",
__NETLIFY_BRANCH__: JSON.stringify(process.env.BRANCH),
__CONFIG__: JSON.stringify({
GRAPHQL_URL: process.env.GRAPHQL_URL,
SEGMENT_WRITEKEY: process.env.SEGMENT_WRITEKEY,
}),
}])
)
config.module.rules.push(
{
test: /\.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: 'graphics/[name].[ext]',
},
},
},
{
test: /\.svg$/,
loader: [
{
loader: 'babel-loader',
query: {
presets: ['es2015']
}
},
{
loader: 'svg-react-loader',
},
{
loader: 'string-replace-loader',
options: {
search: ' xmlns="http://www.w3.org/2000/svg"',
replace: '',
},
},
],
},
);
return config;
},
};
But when I try to add image to my component, my localhost launched, but I receive the error:
Cannot find module '../assets/logo.svg'
I import my image to the component as usual, and without an image, my app is running. Maybe I should add something to my webpack?
All images set in dir assets.
import logo from '../assets/logo.svg';
export default class Layout extends React.Component {
render () {
return (
<img src={logo} alt="logo" />
)
}
}
The issue (from what I understand from this: https://github.com/zeit/next.js/issues/544) is that Next.js doesn't use Webpack for server side rendering, only for client side, so you can't import an SVG like you do when it's purely a client side React project (i.e. by adding a rule for .svg files and run those files through a loader chain).
A possible solution is mentioned here, where the SVG import is not done by a Webpack loader, but by Babel (which is used on the server side code in Next.js).
It seems to work for me, like this:
npm i babel-plugin-inline-react-svg --save
add/merge the following to your Babel config (more info on how to set up a custom Babel configuration here):
"plugins": [
"inline-react-svg"
]
in your component/page, use this (the name of the variable, LogoSVG, is important and shouldn't be changed):
import LogoSVG from '../assets/logo.svg';
and use it as a React component:
return <LogoSVG alt="logo" />

Images not displaying in React App

Good afternoon,
I am working on a React/Redux app and the images that are being loaded into my store are not displaying. My galleryObject.js includes information about each image I want to display like so:
pastry1: {
name: "Pastry!",
image: "./client/images/cake1.jpg",
desc: "Tasty Pastry"
},
pastry2: {
name: "Pastry!",
image: "./client/images/cake2.jpg",
desc: "Tasty Pastry"
} ... (all the way to pastry17)
What baffles me is that the absolute path does not lead to the image being displayed, the state is being loaded correctly because I can see it in my React dev tools. I even threw in a hyperlink to an image online and it works to test it out.
My file structure is like so:
// Project
// client
//images (where the actual pictures are stored)
//data (where galleryObject.js resides)
//main.js (where everything eventually becomes bundled
In my experience this is normally a problem with how my devServer.js accesses static files in my project. The real admission here is that I copy pasta-d this devServer.js from Wes Bos' Learn Redux tutorial and this is what it looks like:
devServer.js
var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev');
var PORT = process.env.PORT || 7770
var app = express();
var compiler = webpack(config);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
app.use(require('webpack-hot-middleware')(compiler));
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(PORT, "localhost", function(err) {
if (err) {
console.log(err);
return;
}
console.log(__dirname);
console.log('Listening at http://localhost:7770');
});
Seeing a lot of webpack stuff I figured that is where I am going wrong so I checked out tcoopman's image-webpack-loader. I npm installed the module and my webpack.config.dev/prod.js both look the same as the example from tcoopman:
webpack.config.dev.js:
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool: 'source-map',
entry: [
'webpack-hot-middleware/client',
'./client/main'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/static/'
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
module: {
loaders: [
// js
{
test: /\.js$/,
loaders: ['babel'],
include: path.join(__dirname, 'client')
},
// CSS
{
test: /\.css$/,
include: path.join(__dirname, 'client'),
loader: 'style-loader!css-loader!stylus-loader'
},
// images
{
test: /\.(jpe?g|png|gif|svg)$/i,
include: path.join(__dirname, 'client'),
loaders: [
'file?hash=sha512&digest=hex&name=[hash].[ext]',
'image-webpack?bypassOnDebug&optimizationLevel=7&interlaced=false'
]
}
]
}
};
webpack.config.prod.js:
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool: 'source-map',
entry: [
'./client/main'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/static/'
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': "'production'"
}
}),
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false
}
})
],
module: {
loaders: [
// js
{
test: /\.js$/,
loaders: ['babel'],
include: path.join(__dirname, 'client')
},
// CSS
{
test: /\.scss$/,
include: path.join(__dirname, 'client'),
loaders: ["style", "css", "sass"]
},
// IMAGES
{
test: /\.(jpe?g|png|gif|svg)$/i,
include: path.join(__dirname, 'client'),
loaders: [
'file?hash=sha512&digest=hex&name=[hash].[ext]',
'image-webpack?bypassOnDebug&optimizationLevel=7&interlaced=false'
]
}
]
}
};
I'm certain that my combination of copy pasta and lack of knowledge when it comes to web pack are at fault. Bad dev. But I'd really appreciate some insight as to what else I am doing wrong not getting my images to display.
Cheers
edit showing how the images make it into the store:
project/client/store.js
import { createStore, compose } from "redux";
import { syncHistoryWithStore } from "react-router-redux";
import { browserHistory } from "react-router";
// all roots
// root reducer
import rootReducer from "./reducers/mainReducer";
// data Objects
import cakeGallery from './data/galleryObject'; // the object is passed into the default state
// default state object
const defaultState = {
cakeGallery,
open: false
};
const store = createStore(rootReducer, defaultState);
export const history = syncHistoryWithStore(browserHistory, store);
export default store;
project/client/reducers/cakeGalleryReducer.js
function cakeGallery(state={}, action){
console.log("cake gallery reducer")
console.log(state, action);
return state;
}
export default cakeGallery; // this gets combined with another reducer
// in project/client/reducers/mainReducer.js
I think here is where I run into trouble. When the page is loaded the cakeGalleryReducer.js function is firing off, so am I passing an empty object continually? This is a picture of my javascript console when the page loads initially, it still seems like I have an object that should be full.
project/client/components/App.js
// this file is basically creating a component that will
// "sprinkle on" props and dispatchers to our Main component
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import * as actionCreators from "../actions/userActions.js";
import StoreShell from "./StoreShell.js";
// cakeGallery is now known simply as images when referred to in components
function mapStateToProps(state){
return {
images: state.cakeGallery,
open: state.open
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators, dispatch);
}
const App = connect(mapStateToProps, mapDispatchToProps)(StoreShell);
// immediately call what component you want to connect to (Main)
export default App;
project/client/components/StoreShell.js
import Main from "./Main"
const StoreShell = React.createClass({
render(){
return(
<div>
<Main {...this.props} />
</div>
)
}
})
export default StoreShell;
From here the information in the intial galleryObject.js accessible as {this.props.images}.
Webpack is not that smart to import images for you.
I suggest you to import all images required in your galleryObject.js, and keep a reference of the image in each pastry object. Then when it gets to Main component, you can just use them directly.
Check it out
devServer.js
var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev');
var PORT = process.env.PORT || 7770
var app = express();
var compiler = webpack(config);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
// SOLVED IT
app.use(express.static(path.join(__dirname + "/client")));
app.use(require('webpack-hot-middleware')(compiler));
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(PORT, "localhost", function(err) {
if (err) {
console.log(err);
return;
}
console.log(__dirname);
console.log('Listening at http://localhost:7770');
});
So this line: app.use(express.static(path.join(__dirname + "/client"))); was not originally in my copy pasta'd devServer.js. What I did was make my static files (css, javascript, and most importantly images) accessible using the express.static middleware that allows you to serve up static files.
This is how I normally solve this issue but I assumed that images were already covered in the code above and below my soluton.
Thanks for everyone's input.

Categories