single spa react application chunk load only when needed - reactjs

I have built a single spa react app, below is the index.html for the app
there are 2 application, app1 and app2, app1 loads initially, and app2 loads only when needed
index.html for single spa config of app1 and app2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=9;IE=10;IE=Edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Project</title>
<!-- app2 css -->
<link href='http://localhost:8003/app2.css' rel='stylesheet'/>
</head>
<body>
<script>
function randstr(prefix) {
return Math.random().toString(36).replace('0.', prefix || '');
}
var suffix = randstr('?v=');
document.write('<script src="config/config.js' + suffix + '"><\/script>');
document.write('<script type="systemjs-importmap" crossorigin="anonymous">' +
'{ \n"imports": {\n' +
' "#portal/config": "http://localhost:8001/index.js' + '",\n' +
' "#portal/app1": "http://localhost:8002/app1.js' + '",\n' + // this loads first, it has login page
' "#portal/app2": "http://localhost:8003/app2.js' + '" \n }\n}' + // app 2 which loads on click
'<\/script>');
</script>
<!-- load the vendor chunk of app2-->
<script src='http://localhost:8003/vendor.app2.js'></script>
<!-- Load the common deps-->
<script>
imports and loads dependencies of react, react-dom, lodash and others
</script>
<!-- Load the application -->
<script>
SystemJS.import('#portal/config')
</script>
<div id="app" class="site-container"></div>
</body>
</html>
in the above index.html, configured the apps by systemjs-importmap and everything works great.
As you can see in the importmap, importing the app2("#portal/app2": "http://localhost:8003/app2.js'), this will load only when needed, but this bundle is bit huge and i have tried to improve the performance by extracting the css and vendor chunk, minifying the js and css, which indeed improved the performance, those scripts are added as well (http://localhost:8003/vendor.app2.js , http://localhost:8003/app2.css)
webpack.config.js for app2
const webpack = require('webpack')
const path = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const TerserPlugin = require("terser-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
entry: path.resolve(__dirname, 'src/app2.js'),
output: {
filename: 'app2.js',
library: 'app2',
libraryTarget: 'amd',
path: path.resolve(__dirname, 'build/app2'),
},
mode: 'production',
module: {
rules: [
{parser: {System: false}},
{
test: /\.js$/,
exclude: [path.resolve(__dirname, 'node_modules')],
loader: 'babel-loader',
options: {
presets: ["#babel/preset-env"]
}
},
{
test: /\.krem.css$/,
exclude: [path.resolve(__dirname, 'node_modules')],
use: [
{
loader: 'kremling-loader',
options: {
namespace: 'app2',
postcss: {
plugins: {
'autoprefixer': {},
},
},
},
},
],
},
{ test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url-loader' },
{ test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url-loader' },
// { test: /\.css$/, loader: "style-loader!css-loader"},
{ test: /\.css$/, use: [
MiniCssExtractPlugin.loader,
'css-loader',
]},
],
},
plugins: [
new CleanWebpackPlugin({
verbose: true,
cleanOnceBeforeBuildPatterns: [path.resolve(__dirname, 'build/app2')],
}),
new CopyWebpackPlugin({
patterns: [
{from: path.resolve(__dirname, 'src/ppsr.js')},
]}),
new TerserPlugin({
parallel: true,
cache: true
}),
new MiniCssExtractPlugin({
filename: '[name].css',
}),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
name: 'vendor',
chunks: 'all',
reuseExistingChunk: true,
priority: 1,
test: module =>
/[\\/]node_modules[\\/]/.test(module.context),
minChunks: 1,
minSize: 0,
},
},
},
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
],
},
devtool: 'source-map',
externals: [
/^#portal\/*/,
/^lodash$/,
/^single-spa$/,
/^rxjs\/?.*$/,
/^react$/,
/^react\/lib.*/,
/^react-dom$/,
/.*react-dom.*/,
],
}
I get the build files vendor.app2.css, app2.css , app2.js files...only js loads when needed because it is imported by importmap, and the css and vendor are in global space(means loads irrespective of app2 is loaded or not)
Question:
How can I load the vendor and CSS same as js for app2 only when needed in single spa react?

Related

React application built by Webpack returns blank page

I have a React app that I used Webpack to deploy it. The webpack.config.js file is as below:
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const InterpolateHtmlPlugin = require('interpolate-html-plugin');
let mode = "development"
let target = "web";
if(process.env.NODE_ENV === "production") {
mode = "production";
target = "browserslist";
}
module.exports = {
entry: './src/index.js',
mode: mode,
target: target,
output: {
path: path.resolve(__dirname, "dist"),
assetModuleFilename: "images/[hash][ext][query]"
},
stats: {
children: true,
errorDetails: true
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin(),
new HtmlWebpackPlugin({
template: "./public/index.html"
}),
new InterpolateHtmlPlugin({PUBLIC_URL: '/' })
],
resolve: {
extensions: [".js", ".jsx"]
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg|ico)$/i,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 30 * 1024,
}
}
},
{
test: /\.(s[ac]|c)ss$/i,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: ""
}
},
"css-loader",
"postcss-loader",
"sass-loader"]
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
}
}
]
},
devtool: "source-map",
devServer: {
port: 3000,
allowedHosts: "all",
static: "./dist",
hot: true
},
};
and the index.html file enjoys %PUBLIC_URL%:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<base href="%PUBLIC_URL%/" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>PSWebsite</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
The npm run build command runs successfully but the npm start returns blank page

Webpack doesn't work in prod but in dev env is fine

I am serving with NodeJS react as static but index.html cant render bundle.js file (in network tab it says that bundle.js is Not found) ;
Express looks like this:
app.get(/^\/ui\/(?!images|vendor|assets|packages)/, function (req, res, next) {
res.sendFile(path.join(__dirname, 'public/ui/index.html'))
});
app.use(express.static(path.join(__dirname, 'public')));
app.listen(port, function () {
console.log('App listening on port ' + port);
});
The folder structure looks like this:
the index html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Font Awesome CDN-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css"
integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g=="
crossorigin="anonymous" referrerpolicy="no-referrer"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Skenario Labs - Banking</title>
</head>
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
</body>
Webpack common :
module.exports = {
entry: path.resolve(__dirname, '..', '/src/index.tsx'),
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
},
],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader"
]
},
{
test: /\.(woff(2)?|eot|ttf|otf|)$/,
type: 'asset/inline',
},
{
test: /\.(png|jpe?g|gif|jp2|webp|ico|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]',
},
},
],
},
output: {
path: path.resolve(__dirname, '..', './build'),
filename: 'bundle.js',
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '..', './public/ui/index.html'),
favicon: "./public/ui/assets/images/favicon.png",
filename: 'index.html',
inject: 'body'
}),
],
stats: 'errors-only',
}
Ive tried to use it both ways :
<script src="/bundle.js"></script>
<script src="bundle.js"></script>
But then getting status 304 and the bundle.js inside shows white screen instead of JS

React production build shows blank page

I have this problem where my react production build shows a blank page when served with simple node server. I have tried to tackle this problem different ways but cant find a solution to this. Any suggestions how to fix this problem would be much appreciated.
I am using BrowserRouter as the routing option on client side.
Here is my webpack.common.js code for the production build:
const path = require("path");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const outputPath = path.join(__dirname, "dist");
const port = process.env.PORT || 8000;
module.exports = {
context: __dirname,
entry: {
main: ["#babel/polyfill", "./src/App.js"]
},
output: {
path: path.join(__dirname, "dist"),
filename: "bundle.js"
},
resolve: {
modules: ["node_modules", "./src"],
extensions: [".js", ".jsx"]
},
module: {
rules: [
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: "css-loader!sass-loader"
})
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: "css-loader"
})
},
{
test: /\.(js|jsx)$/,
loader: "babel-loader",
exclude: /node_modules/
},
{
test: /\.(jpg|jpeg|png|gif|mp3|svg)$/,
loaders: ["file-loader"]
},
{
test: /\.html$/,
use: [
{
loader: "html-loader"
}
]
}
]
},
plugins: [
new ExtractTextPlugin("bundle.css"),
new HtmlWebpackPlugin({
baseUrl: process.env.NODE_ENV == 'development' ? '/' : '/',
filename: "index.html",
template: path.join(__dirname, "./public/index.html")
})
],
devServer: {
port,
historyApiFallback: true,
publicPath: "/"
}
};
Here is the entry for the compiler to run the production build:
const webpack = require('webpack');
const merge = require('webpack-merge');
const webpackCommonConfig = require('./webpack.config.common');
module.exports = merge(webpackCommonConfig, {
plugins: [
new webpack.EnvironmentPlugin({ NODE_ENV: 'production' }),
],
devtool: "source-map",
devServer: {
compress: true,
},
});
Here is the code for node server serving the application:
const express = require('express')
const path = require('path')
const port = process.env.PORT || 8000
const fs = require('fs');
const app = express()
app.get(['/bundle.css', '/bundle.css.map'], (req, res) => {
res.writeHead(200, {'Content-Type': 'text/css'});
fs.createReadStream(path.resolve(__dirname, `../dist/${req.url}`)).pipe(res);
})
app.get(['/bundle.js', '/bundle.js.map'], (req, res) => {
res.writeHead(200, {'Content-Type': 'text/javascript'});
fs.createReadStream(path.resolve(__dirname, `../dist/${req.url}`)).pipe(res);
})
app.get('*', function (request, response){
response.sendFile(path.resolve(__dirname, '../dist', 'index.html'))
})
app.listen(port)
console.log("server started on port " + port)
Here is the index.html that is compiled to dist folder:
<!DOCTYPE html>
<html lang="en" style="height:100%">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.gif">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<title>Zoi</title>
<link href="bundle.css" rel="stylesheet"></head>
<body style="height:100%">
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<script type="text/javascript" src="bundle.js"></script></body>
</html>

after splitting Chunks in webpack unable to run app

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

Deploying Angular 2 tutorial webpack build

I am currently trying to learn ng2.
I have followed the tutorial here : https://angular.io/docs/ts/latest/guide/webpack.html
And have found creating a dist of my build, upon attempting to run it, both locally and on Github Pages, I am receiving a 404 error on my bundle files.
I have followed the tutorial line for line and it does not appear to be working.
Can anyone help?
My webpack build is as follows :
webpack.common.js
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');
module.exports = {
entry: {
'polyfills': './src/polyfills.ts',
'vendor': './src/vendor.ts',
'app': './src/main.ts'
},
resolve: {
extensions: ['', '.js', '.ts']
},
module: {
loaders: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file?name=assets/[name].[hash].[ext]'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'polyfills']
}),
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
};
webpack.prod.js
var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
module.exports = webpackMerge(commonConfig, {
devtool: 'source-map',
output: {
path: helpers.root('dist'),
publicPath: '/',
filename: '[name].[hash].js',
chunkFilename: '[id].[hash].chunk.js'
},
htmlLoader: {
minimize: false // workaround for ng2
},
plugins: [
new webpack.NoErrorsPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618
mangle: {
keep_fnames: true
}
}),
new ExtractTextPlugin('[name].[hash].css'),
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(ENV)
}
})
]
});
Index.html
<!DOCTYPE html>
<html>
<head>
<base href=/>
<title>Angular With Webpack</title>
<meta charset=UTF-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<link href="/app.f053dbe7ce9dd32c3e43.css" rel="stylesheet">
</head>
<body>
<my-app>Loading...</my-app>
<script type="text/javascript" src="/polyfills.f053dbe7ce9dd32c3e43.js"></script>
<script type="text/javascript" src="/vendor.f053dbe7ce9dd32c3e43.js"></script>
<script type="text/javascript" src="/app.f053dbe7ce9dd32c3e43.js"></script>
</body>
</html>
I had the same problem! Do you have karma.conf.js in config dir? Your project structure should be like :

Resources