Bug with building SSR for reactjs-website - node.js

I have some react-app and trying to enable server-side rendering.
I make the server/index.js and describe the server logic (using express)
import path from 'path';
import fs from 'fs';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import express from 'express';
import App from 'src/App';
const PORT = process.env.PORT || 3006;
const app = express();
app.get('/', (req, res) => {
const app = ReactDOMServer.renderToString(<App />);
const indexFile = path.resolve('./build/index.html'); <-- it's result of `npm run build`
fs.readFile(indexFile, 'utf8', (err, data) => {
if (err) {
console.error('Something went wrong:', err);
return res.status(500).send('Oops, better luck next time!');
}
return res.send(
data.replace('<div id="root"></div>', `<div id="root">${app}</div>`)
);
});
});
app.use(express.static('./build'));
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
Then I use webpack for convert jsx to js
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: './server/index.js',
target: 'node',
externals: [nodeExternals()],
devtool: false,
output: {
path: path.resolve('build'),
filename: 'server.js',
asyncChunks: true,
clean: true
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'babel-loader',
options: { presets: ['solid'] },
},
{
loader: 'ts-loader',
options: {
compilerOptions: { noEmit: false },
}
}],
exclude: /node_modules/,
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader, // Creates `style` nodes from JS strings
"css-loader", // Translates CSS into CommonJS
"sass-loader", // Compiles Sass to CSS
],
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.js$/,
use: 'babel-loader'
}
]
},
plugins: [
new MiniCssExtractPlugin()
],
resolve: {
roots: [
'node_modules'
],
alias: {
src: path.resolve(__dirname, 'src')
},
extensions: ['.tsx', '.ts', '.js'],
},
};
Then I try to start server node build/server.js but got some error
/* harmony import */ var solid_js_web__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! solid-js/web */ "./node_modules/solid-js/web/dist/server.js");
...
const _tmpl$ = /*#__PURE__*/(0,solid_js_web__WEBPACK_IMPORTED_MODULE_0__.template)(`<li></li>`, 2),
TypeError: (0 , solid_js_web__WEBPACK_IMPORTED_MODULE_0__.template) is not a function
I can't understand how it happened and what's wrong? How can I make it work?

It was my mistake in webpack config
{
loader: 'babel-loader',
options: {
presets: ['#babel/env', '#babel/preset-react'], <-- correct
// presets: ['solid'], <-- incorrect
},
},

Related

How to add to external parameters are express in the webpack configuration

I'm trying to build my project using webpack. this is my webpack configuration file.
import * as path from 'path';
import * as webpack from 'webpack';
import { fileURLToPath } from 'url';
import nodeExternals from 'webpack-node-externals';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const input = path.resolve(__dirname, '../src/server');
const output = path.resolve(__dirname, '../server');
const config: webpack.Configuration = {
entry: {
server: path.resolve(input, './index.ts'),
},
experiments: {
outputModule: true,
},
externals: [
nodeExternals(),
],
module: {
rules: [
{
exclude: /node_modules/,
loader: 'ts-loader',
options: {
transpileOnly: true,
},
test: /\.ts(x?)$/,
},
],
},
name: 'server',
output: {
filename: '[name].js',
path: output,
publicPath: '/node/',
chunkFormat: 'module',
},
resolve: {
extensions: ['*', '.js', '.jsx', '.json', '.ts', '.tsx'],
},
target: 'node',
};
export default config;
when I run bundle, I get an error.
module.exports = require("express"); ^
ReferenceError: require is not defined in ES module scope, you can use
import instead This file is being treated as an ES module because it
has a '.js' file extension and
'/test/package.json' contains "type":
"module". To treat it as a CommonJS script, rename it to use the
'.cjs' file extension.
in the bundle I see
/***/ ((module) => {
module.exports = require("express");
/***/ }),
I expect to get something like this
/***/ ((module) => {
module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("http");
/***/ }),
I tried adding this code to config, but it didn't help
externals: [
nodeExternals(),
'express',
]
server.ts
import express from 'express';
const app = express();
app.get('/', (request, response) => {
response.send('Hi there');
});
app.listen(3000, () => {
console.log('Listen on the port 3000...');
});

Why express.js can not find static files?

I created simple express.js server
Server config:
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);
const SERVER_PORT = 3000;
if (process.env.NODE_ENV === "development") {
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
);
app.use(webpackHotMiddleware(compiler));
}
app.use(express.static("build"));
app.get("/", (req, res) => {
res.sendFile(__dirname + '/build/index.html', { root: __dirname })
});
app.listen(SERVER_PORT, (error) => {
if (error) {
console.error(error)
} else {
console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", SERVER_PORT, SERVER_PORT)
}
});
I build project with webpack. here is webpack config:
const BUILD_DIR = path.resolve(__dirname, './build');
const ASSETS_PATH = path.resolve(__dirname, './src/js');
const STATIC_PATH = path.resolve(__dirname, './public')
var webpack_config = {
mode: ( process.env.NODE_ENV === "production" ) ? "production" : "development",
context: __dirname,
entry: {
main: [
"react",
"react-dom",
"react-router"
],
react_app: [
path.join(ASSETS_PATH, "/index.tsx"),
"webpack-hot-middleware/client?path=/__webpack_hmr&reload=true"
]
},
output: {
path: BUILD_DIR,
filename: 'js/[name].min.js',
publicPath: '/build',
hotUpdateChunkFilename: '.hot/hot-update.js',
hotUpdateMainFilename: '.hot/hot-update.json',
},
resolve: {
extensions: [' ', '.web.js', '.ts', '.tsx', '.js', '.jsx', 'css'],
},
devtool: ("production" === process.env.NODE_ENV) ? "source-map" : "eval-source-map",
module: {
rules: [
{
test: /\.(jsx|js)$/,
use: ['babel-loader?compact=true&comments=true&minified=true', 'eslint-loader'],
exclude: /node_modules/
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: '/node_modules/'
},
{
test: /\.(ttf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: '/css/fonts/'
}
}
},
{
test: /\.(eot|svg|png|jpg|gif|ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: '/css/media/'
}
}
},
{
test: /\.jpe?g$|\.ico$|\.gif$|\.png$/,
exclude: /node_modules/,
use: {
loader: 'file-loader',
options: {
limit: 1024 * 10,
name: '[name].[ext]',
outputPath: 'images/'
}
}
},
{
test: /\.json$/,
loader: "json-loader"
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
}
}
, 'css-loader'],
},
]
},
plugins: [
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
}
}),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
title: ' Appointment-APP | Stefanini EMEA ',
template: './public/index.html',
filename: path.join(BUILD_DIR, '/index.html'),
chunks: ['main', 'react_app']
}),
new CopyWebpackPlugin({
patterns: [
{
from: path.join(STATIC_PATH, '/images'),
to: path.join(BUILD_DIR, '/images'),
toType: 'dir'
},
]
}),
new MiniCssExtractPlugin({
filename: 'css/[name].css',
chunkFilename: '[id].css',
}),
],
};
Here I have 2 mods- production and delete. In delete mode it works nice, but when I launch server and try to enter http://localhost:3000/ I get errors like:
Refused to apply style from 'http://localhost:3000/build/css/react_app.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
localhost/:9 GET http://localhost:3000/build/js/main.min.js net::ERR_ABORTED 404 (Not Found)
So, I specified static folder path - app.use(express.static("build"));, but no result. It seems, that express cannot find files in "buid" folder
You have to code like this
app.use('/', express.static(path.join(__dirname, 'build')))
then check.
may be this will be useful for somebody. to solve this issue I used express documentation and Jay Suchak variant. Code
app.use('/build', express.static(path.join(__dirname, 'build')));
worked for me

webpack-hot-middleware only updates Css/Sass Once

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)
})
]
};

WebPack and Express server not working together

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

Webpack external not cacheable

I'm using webpack to bundle node.js web server based on Express.js framework.
Webpack build works fine, but at the end it gives me two red messages:
[1] external "express" 42 bytes {0} [not cacheable]
[2] external "path" 42 bytes {0} [not cacheable]
What does that mean and should I fix it? If yes then how to fix it?
My webpack config is here:
var server = {
devtool: 'source-map',
entry: './src/server.ts',
target: 'node',
// Config for our build files
output: {
path: root('dist/server'),
filename: '[name].bundle.js',
sourceMapFilename: '[name].map',
chunkFilename: '[id].chunk.js'
},
externals: nodeModules,
module: {
preLoaders: [
// { test: /\.ts$/, loader: 'tslint-loader', exclude: [ root('node_modules') ] },
// TODO(gdi2290): `exclude: [ root('node_modules/rxjs') ]` fixed with rxjs 5 beta.2 release
{ test: /\.js$/, loader: "source-map-loader", exclude: [ root('node_modules/rxjs') ] }
],
loaders: [
// Support for .ts files.
{ test: /\.ts$/, loader: 'ts-loader', exclude: [ /\.(spec|e2e|async)\.ts$/ ] }
]
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(true),
// replace
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(metadata.ENV),
'NODE_ENV': JSON.stringify(metadata.ENV)
}
})
],
};
My server.ts module:
console.log('Starting web server...');
import * as path from 'path';
import * as express from 'express';
let app = express();
let root = path.join(path.resolve(__dirname, '..'));
var port = process.env.PORT || 8080; // set our port
var router = express.Router();
router.get('/', function(req, res) {
res.json({ message: 'hooray! welcome to our api!' });
});
app.use('/api', router);
app.listen(port);
console.log('Server started on port ' + port);
It's because of the externals definition. See the relevant test case.

Resources