I was trying to use gzip to reduce the size of the bundle of my current angular project. The bundle report claims that after using gzip it would reduce my product-build's size to about 30% or less. But I don't know how to make that work.
Currently, my project is built in Angular and is sent to a port through Node.js Express.js server. Guess we would need config both Angular and NodeJS to make it work?
What I have tried so far:
It looks like the gzip can be applied in Angular 5- until Google disabled the ng eject. Also, I found a lot of Nginx tutorials with gzip but none of Node.js by now. So guess I don't quite understand either of them.
I tried to add the following code in my node server, but it doesn't seem to affect the performance of the project with or without the compression.
const express = require('express');
const http = require('http');
const https = require('https');
const compression = require('compression');
const app = express();
const port = 4201;
app.use(compression());
app.use(express.static(__dirname + '/dist'));
Edit:
After testing with http://www.gidnetwork.com/tools/gzip-test.php, my site has been compressed into gzip. But the performance in Google Page Insight doesn't change. Perhaps only the index.html is compressed?
I use node version 16.13.2 and it has a built-in compression package.
So, all you need is to use the below server.ts file as your node server
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
const compression = require('compression');
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/your-project-name/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
server.use(compression());
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
function run(): void {
const port = process.env['PORT'] || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
Try zlib, nodejs has a stable support in core now.
An example (taken from the docs):
// server example
// Running a gzip operation on every request is quite expensive.
// It would be much more efficient to cache the compressed buffer.
var zlib = require('zlib');
var http = require('http');
var fs = require('fs');
http.createServer(function(request, response) {
var raw = fs.createReadStream('index.html');
var acceptEncoding = request.headers['accept-encoding'];
if (!acceptEncoding) {
acceptEncoding = '';
}
// Note: this is not a conformant accept-encoding parser.
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
if (acceptEncoding.match(/\bdeflate\b/)) {
response.writeHead(200, { 'content-encoding': 'deflate' });
raw.pipe(zlib.createDeflate()).pipe(response);
} else if (acceptEncoding.match(/\bgzip\b/)) {
response.writeHead(200, { 'content-encoding': 'gzip' });
raw.pipe(zlib.createGzip()).pipe(response);
} else {
response.writeHead(200, {});
raw.pipe(response);
}
}).listen(1337);
for angular you have to install gzip-all
npm i gzip-all
and on your package.json file
package.json
"scripts": {
"build": "ng build --prod --no-delete-output-path --aot --base-href /app/ && gzip-all \Project file.....\" ",
},
Related
I have an SSR server for React.JS app made with CRA, ConnectedRouter, Redux and Saga. I am trying to host this server under AWS Lambda using the below code:
const serverlessExpress = require('#vendia/serverless-express');
const app = require('./index');
const binaryMimeTypes = [
'application/javascript',
...
'text/xml'
];
exports.handler = serverlessExpress({
app,
binaryMimeTypes
}).handler;
This wrapper pulls the setup code below:
const md5File = require('md5-file');
const fs = require('fs');
const path = require('path');
// CSS styles will be imported on load and that complicates matters... ignore those bad boys!
const ignoreStyles = require('ignore-styles');
const register = ignoreStyles.default;
// We also want to ignore all image requests
// When running locally these will load from a standard import
// When running on the server, we want to load via their hashed version in the build folder
const extensions = ['.gif', '.jpeg', '.jpg', '.png', '.svg'];
// Override the default style ignorer, also modifying all image requests
register(ignoreStyles.DEFAULT_EXTENSIONS, (mod, filename) => {
if (!extensions.find(f => filename.endsWith(f))) {
// If we find a style
return ignoreStyles.noOp();
}
// for images that less than 10k, CRA will turn it into Base64 string, but here we have to do it again
const stats = fs.statSync(filename);
const fileSizeInBytes = stats.size / 1024;
if (fileSizeInBytes <= 10) {
mod.exports = `data:image/${mod.filename
.split('.')
.pop()};base64,${fs.readFileSync(mod.filename, {
encoding: 'base64'
})}`;
return ignoreStyles.noOp();
}
// If we find an image
const hash = md5File.sync(filename).slice(0, 8);
const bn = path.basename(filename).replace(/(\.\w{3})$/, `.${hash}$1`);
mod.exports = `/static/media/${bn}`;
});
// Set up babel to do its thing... env for the latest toys, react-app for CRA
// Notice three plugins: the first two allow us to use import rather than require, the third is for code splitting
// Polyfill is required for Babel 7, polyfill includes a custom regenerator runtime and core-js
require('#babel/polyfill');
require('#babel/register')({
ignore: [/\/(build|node_modules)\//],
presets: ['#babel/preset-env', '#babel/preset-react'],
plugins: [
'#babel/plugin-syntax-dynamic-import',
'#babel/plugin-proposal-class-properties',
'dynamic-import-node',
'react-loadable/babel'
]
});
// Now that the nonsense is over... load up the server entry point
const app = require('./server');
module.exports = app;
Then I have the regular express server in my server.js
// Express requirements
import bodyParser from 'body-parser';
import compression from 'compression';
import express from 'express';
import morgan from 'morgan';
import path from 'path';
import forceDomain from 'forcedomain';
import Loadable from 'react-loadable';
import cookieParser from 'cookie-parser';
// Our loader - this basically acts as the entry point for each page load
import loader from './loader';
// Create our express app using the port optionally specified
const main = () => {
const app = express();
const PORT = process.env.PORT || 3000;
// Compress, parse, log, and raid the cookie jar
app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(morgan('dev'));
app.use(cookieParser());
// Set up homepage, static assets, and capture everything else
app.use(express.Router().get('/', loader));
const favicon = require('serve-favicon');
app.use(favicon(path.resolve(__dirname, '../build/icons/favicon.ico')));
app.use(express.static(path.resolve(__dirname, '../build')));
app.use(loader);
// We tell React Loadable to load all required assets and start listening - ROCK AND ROLL!
Loadable.preloadAll().then(() => {
app.listen(PORT, console.log(`App listening on port ${PORT}!`));
});
// Handle the bugs somehow
app.on('error', error => {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof PORT === 'string' ? 'Pipe ' + PORT : 'Port ' + PORT;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
});
return app;
};
module.exports = main();
Then, I have the loader do:
// Express requirements
import path from 'path';
import fs from 'fs';
// React requirements
import React from 'react';
import { renderToString } from 'react-dom/server';
import Helmet from 'react-helmet';
import { Provider } from 'react-redux';
import { StaticRouter } from 'react-router-dom';
import { Frontload, frontloadServerRender } from 'react-frontload';
import Loadable from 'react-loadable';
// Our store, entrypoint, and manifest
import createStore from '../src/configureStore';
import App from '../src/containers/app';
import manifest from '../build/asset-manifest.json';
// Some optional Redux functions related to user authentication
//import { setCurrentUser, logoutUser } from '../src/modules/auth';
// LOADER
export default (req, res) => {
/*
A simple helper function to prepare the HTML markup. This loads:
- Page title
- SEO meta tags
- Preloaded state (for Redux) depending on the current route
- Code-split script tags depending on the current route
*/
const injectHTML = (data, { html, title, meta, body, scripts, state }) => {
data = data.replace('<html>', `<html ${html}>`);
data = data.replace(/<title>.*?<\/title>/g, title);
data = data.replace('</head>', `${meta}</head>`);
data = data.replace(
'<div id="root"></div>',
`<div id="root">${body}</div><script>window.__PRELOADED_STATE__ = ${state}</script>${scripts.join(
''
)}`
);
return data;
};
// Load in our HTML file from our build
fs.readFile(
path.resolve(__dirname, '../build/index.html'),
'utf8',
(err, htmlData) => {
...
The entire process, including transpilation, and the time necessary to start the express server takes quite a while. My latency could be anywhere between 100ms for a warm lambda, all the way to around 3 seconds.
Is there some low-hanging fruit improvements that I could apply to my code?
Hi I am getting always error in production when I try to deploy to webserver.
I am deploying everything to /var/myapp folder so basically in my server I have this paths:
/var/myapp/client/dist/client/browser folder where there is index.html and
/var/myapp/client/dist/client/server where there is the main.js file
I am running on server node /var/myapp/client/dist/client/server/main.js
Please help with the right configuration on server.ts file
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
import 'localstorage-polyfill';
global.localStorage;
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const distFolder = join(process.cwd(), '/var/myapp/client/dist/client/browser');
const indexHtml = existsSync(join(distFolder, 'index.html')) ? 'index.html' : 'index';
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
function run() {
const port = 4000;
// Start up the Node server
const server = app();
server.listen(port, '127.0.0.1', function() {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
Angular universal deployment issue content is displayed but no css.
I am getting those errors:
runtime-es2015.3f64214655012374b6e0.js:1 Failed to load module script: The server responded with a non-JavaScript MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
polyfills-es2015.629687f397387c194377.js:1 Failed to load module script: The server responded with a non-JavaScript MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
main-es2015.a772fe1bf7606c343730.js:1 Failed to load module script: The server responded with a non-JavaScript MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
I'm able to dynamically render html for server side rendering using angular 8 and angular universal when I view page source BUT my issue is that I am rendering the dynamic html before the api request has finished loading so I can't see the response in the dynamic html from the view page source - please let me know if you require any further info. I run this command:
i.e. npm run build:ssr && npm run serve:ssr
and when I look at the logs and change the url I get a [NetworkError] in the console but the app still runs as expected, I need to figure out a way to load the api response and then render the html into the page view source after the request has finished but I've run out of ideas.
Hopefully one of you guys can help,
Thanks
Server.ts
import 'zone.js/dist/zone-node';
import 'localstorage-polyfill';
import { join } from 'path';
import * as express from 'express';
const compression = require('compression');
const sessionStorage = require('sessionstorage');
const DIST_FOLDER = join(process.cwd(), 'dist');
const domino = require('domino');
const fs = require('fs');
const template = fs.readFileSync('./dist/browser/index.html').toString();
const win = domino.createWindow(template);
const proxy = require('http-proxy-middleware');
const cors = require('cors');
const helmet = require('helmet');
Object.assign(global, domino.impl);
(global as any)['KeyboardEvent'] = domino.impl.Event;
global['window'] = win;
global['Node'] = win.Node;
global['navigator'] = win.navigator;
global['Event'] = win.Event;
global['KeyboardEvent'] = win.Event;
global['MouseEvent'] = win.Event;
global['Event']['prototype'] = win.Event.prototype;
global['document'] = win.document;
global['sessionStorage'] = sessionStorage;
global['localStorage'] = localStorage;
// Express server
const app = express();
const PORT = process.env.PORT || 4200;
const {AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap} = require('./dist/server/main');
app.use(cors());
app.use(compression());
// express-engine
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
// Protect website from Clickjacking attack
app.use(helmet.frameguard());
app.use(helmet.xssFilter());
// Proxy API Endpoints
app.use('/api/profileProxy', proxy(
{
target: 'http://xxxxxxx1:9004', // target host
changeOrigin: true, // needed for virtual hosted sites
// ws: true, // proxy websockets
pathRewrite: {
'^/api/profileProxy': ''
}
}
));
app.use('/api/searchProxy', proxy(
{
target: 'http://xxxxxx.160:9005', // target host
changeOrigin: true, // needed for virtual hosted sites
// ws: true, // proxy websockets
pathRewrite: {
'^/api/searchProxy': ''
}
}
));
app.get('/sitemap1.xml', function (req, res, next) {
const file = `${DIST_FOLDER}/sitemap1.xml`;
fs.exists(file, function (exists) {
if (exists) {
res.sendFile(file);
} else {
res.status(404).send('404');
}
});
});
app.get('/robots.txt', function (req, res, next) {
const file = `${DIST_FOLDER}/robots.txt`;
fs.exists(file, function (exists) {
if (exists) {
res.sendFile(file);
} else {
res.status(404).send('404');
}
});
});
// All regular routes use the Universal engine
app.get('*', (req, res) => {
console.time(`GET: ${req.originalUrl}`);
console.log(`req-QQQQQQQQQQ: ${req.originalUrl}`);
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
console.timeEnd(`GET: ${req.originalUrl}`);
console.log(`req-timeEnd: ${req.originalUrl}`);
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
``
What you are describing is normal behaviour. Only your original request (e.h. when you type in the site's adress) in the URL bar will be prerendered using angular universal. This prerendered html is what you can see if you use View Source (or Ctrl +U)
Once the page is displayed, the client side angular app takes over. When you click on the search button, it'll perform an API call and get results, which will update the page. You'll see that html using the Html inspector from your browser's debugging tool, but it will have no impact on the original HTML that has been sent by the server
for people that may come across this issue - I was able to display the data from the api request (in my case the initial search results) in the view page source for SSR by making a GET request in the app.component of my app - thanks
I have checked several tutorials and walkthroughs on how to add SSR (server-side rendering) to existing angular CLI & Express projects.
I was able to run node with SSR but I'm unable to make API calls.
I wish to run one node process that can serve static file (Angular with SSR) and dynamic content with API calls which access SQLITE DB using KNEX.
This is my server.js file
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '#angular/core';
import * as express from 'express';
import { join } from 'path';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Express server
const app = express();
const PORT = process.env.PORT || 3000;
const DIST_FOLDER = join(process.cwd(), 'bin/dist');
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./bin/dist/server/main');
// Express Engine
import { ngExpressEngine } from '#nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '#nguniversal/module-map-ngfactory-loader';
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, ''));
// import * as apiS from './server/index';
// apiS.load(app);
// TODO: implement data requests securely
// app.get('/api/*', (req, res) => {
// res.status(404).send('data requests are not supported');
// });
import * as LoadRoutes from './server/boot/routes';
LoadRoutes.load(app);
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, '')));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});
Is it even possible to accomplish SSR + API calls from the same node process and if so can someone post here answer?
It's already there. Just uncomment these lines and extend the API routes with Express router:
app.get('/api/*', (req, res) => {
res.status(404).send('data requests are not supported');
});
Use the router file instead:
app.use('/api/*', appRoutes);
Every URL starting with api/ will be served by backend APIs.
I am using angular 6 and implemented the angular universal for server side page rendering,
Everything works fine but I am getting continues error in my console.
ERROR [Error]
ERROR [Error]
And I have declared window as global variable still I am getting errors like this
Unhandled Promise rejection: window.some_function is not a
function
my server.ts file
//
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '#angular/core';
import * as express from 'express';
import { join } from 'path';
// Express Engine
import { ngExpressEngine } from '#nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '#nguniversal/module-map-ngfactory-loader';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// node run port
const port = 6065;
// Express server
const app = express();
const PORT = port || process.env.PORT;
const DIST_FOLDER = join(process.cwd(), 'dist');
const domino = require('domino');
const fs = require('fs');
const MockBrowser = require('mock-browser').mocks.MockBrowser;
const mock = new MockBrowser();
const template = fs
.readFileSync(join(DIST_FOLDER, 'browser', 'index.html'))
.toString();
// Make all Domino types available as types in the global env.
Object.assign(global, domino.impl);
(global as any)['KeyboardEvent'] = domino.impl.Event;
const win = domino.createWindow(template);
win.Object = Object;
win.Math = Math;
global['window'] = win;
global['document'] = win.document;
global['navigator'] = mock.getNavigator();
global['branch'] = null;
global['object'] = win.object;
global['HTMLElement'] = win.HTMLElement;
global['DOMTokenList'] = win.DOMTokenList;
global['Node'] = win.Node;
global['Text'] = win.Text;
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// TODO: implement data requests securely
app.get('/api/*', (req, res) => {
res.status(404).send('data requests are not supported');
});
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});
// Start up the Node server
app.listen(PORT, '0.0.0.0', () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});
I had the same problem and started debugging server.js in my Visual Studio Code and what I had found: an error messages was caused by http requests from TranslateHttpLoader (I'm using ngx-translate), which was trying to load .json-files with translations. After some googling I found this thread where you can find how to enable server-side translation loading. After that error messages disappeared.