Last active
February 5, 2023 13:56
-
-
Save michelepatrassi/242f1f0a867af99918977ea64787fcee to your computer and use it in GitHub Desktop.
Angular Universal Server Side Rendering battle tested server.ts and webpack.server.config.js files 🦁(includes Firebase, FirebaseUI, Angular i18n and log rotation)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { RESPONSE, REQUEST } from '@nguniversal/express-engine/tokens'; | |
import { renderModuleFactory } from '@angular/platform-server'; | |
// These are important and needed before anything else | |
import 'zone.js/dist/zone-node'; | |
import 'reflect-metadata'; | |
import { enableProdMode, ValueProvider, FactoryProvider } from '@angular/core'; | |
// Import module map for lazy loading | |
import {provideModuleMap} from '@nguniversal/module-map-ngfactory-loader'; | |
import { registerLocaleData } from '@angular/common'; | |
// DOM libs required for Firebase | |
(global as any).WebSocket = require('ws'); | |
(global as any).XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; | |
import * as express from 'express'; | |
import * as morgan from 'morgan'; | |
import * as fs from 'fs'; | |
import rfs from 'rotating-file-stream'; | |
import { join } from 'path'; | |
// Faster server renders w/ Prod mode (dev mode never needed) | |
enableProdMode(); | |
// render icons on server side | |
import './resources/assets/ts/icons'; | |
// workaround for https://github.com/angular/angular-cli/issues/9975 | |
const languages = ['it']; | |
languages.forEach(lang => { | |
const locale = require(`@angular/common/locales/${lang}`).default; | |
registerLocaleData(locale, lang); | |
}); | |
// Express server | |
const app = express(); | |
const PORT = 8080; | |
const DIST_FOLDER = join(process.cwd(), 'dist'); | |
const LOG_FOLDER = join(DIST_FOLDER, 'log'); | |
const isCi = process.env.IS_CI || false; | |
// Our index.html we'll use as our template | |
const template = fs.readFileSync(join(DIST_FOLDER, 'app', 'index.html')).toString(); | |
// firebaseui needs a mock window and document to be compiled | |
// https://github.com/angular/universal/issues/830#issuecomment-345228799 | |
const domino = require('domino'); | |
const win = domino.createWindow(template); | |
win.process = process; // protobufjs fix to confirm node env: https://github.com/protobufjs/protobuf.js/blob/master/src/util/minimal.js#L55 | |
global['window'] = win; | |
global['document'] = win.document; | |
global['navigator'] = win.navigator; | |
global['requestAnimationFrame'] = function(callback, element) { | |
let lastTime = 0; | |
const currTime = new Date().getTime(); | |
const timeToCall = Math.max(0, 16 - (currTime - lastTime)); | |
const id = setTimeout(function() { callback(currTime + timeToCall); }, | |
timeToCall); | |
lastTime = currTime + timeToCall; | |
return id; | |
}; | |
global['cancelAnimationFrame'] = function(id) { | |
clearTimeout(id); | |
}; | |
// firebaseui fix: componentHandler is expected in the global scope | |
global['componentHandler'] = { | |
register: () => {} | |
}; | |
// * NOTE :: leave this as require() since this file is built Dynamically from webpack | |
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/universal/main'); | |
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine) | |
// Provide response token to enable 404: https://www.thecodecampus.de/blog/angular-universal-handle-404-set-status-codes/ | |
app.engine('html', (_, options, callback) => { | |
renderModuleFactory(AppServerModuleNgFactory, { | |
document: template, | |
url: options.req.url, | |
extraProviders: [ | |
provideModuleMap(LAZY_MODULE_MAP), | |
<ValueProvider>{ | |
provide: RESPONSE, | |
useValue: options.req.res, | |
}, | |
<FactoryProvider>{ | |
provide: REQUEST, | |
useFactory: () => options.req, deps: [], | |
}, | |
] | |
}).then(html => { | |
callback(null, html); | |
}); | |
}); | |
app.set('view engine', 'html'); | |
app.set('views', join(DIST_FOLDER, 'app')); | |
// Setup logging with rotation and the combined format (https://github.com/expressjs/morgan) | |
fs.existsSync(LOG_FOLDER) || fs.mkdirSync(LOG_FOLDER); | |
const accessLogStream = rfs('access.log', { | |
interval: '1d', // rotate daily | |
path: LOG_FOLDER | |
}); | |
app.use(morgan('combined', {stream: accessLogStream})); | |
// Data request should always go to backend | |
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, 'app'), { | |
maxAge: '1y' | |
})); | |
// All regular routes use the Universal engine | |
app.get('*', (req, res) => { | |
res.render('index', { req }); | |
}); | |
if (!isCi) { | |
// Start up the Node server | |
app.listen(PORT, () => { | |
console.log(`Node server listening on http://localhost:${PORT}`); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const path = require('path'); | |
const webpack = require('webpack'); | |
const regex = /firebase\/(app|firestore)/; | |
module.exports = { | |
entry: { server: './server.ts' }, | |
resolve: { extensions: ['.js', '.ts'] }, | |
target: 'node', | |
mode: 'none', | |
// this makes sure we include node_modules and other 3rd party libraries | |
externals: [/node_modules/, function(context, request, callback) { | |
// exclude firebase products from being bundled, so they will be loaded using require() at runtime. | |
// https://github.com/firebase/firebase-js-sdk/issues/1455#issuecomment-455712500 | |
if(regex.test(request)) { | |
return callback(null, 'commonjs ' + request); | |
} | |
callback(); | |
}], | |
output: { | |
path: path.join(__dirname, 'dist'), | |
filename: '[name].js' | |
}, | |
module: { | |
rules: [ | |
{ test: /\.ts$/, | |
loader: 'ts-loader', | |
options: { | |
configFile: "./resources/assets/ts/tsconfig.server.json" | |
} | |
} | |
] | |
}, | |
plugins: [ | |
// Temporary Fix for issue: https://github.com/angular/angular/issues/11580 | |
// for "WARNING Critical dependency: the request of a dependency is an expression" | |
new webpack.ContextReplacementPlugin( | |
/(.+)?angular(\\|\/)core(.+)?/, | |
path.join(__dirname, 'src'), // location of your src | |
{} // a map of your routes | |
), | |
new webpack.ContextReplacementPlugin( | |
/(.+)?express(\\|\/)(.+)?/, | |
path.join(__dirname, 'src'), | |
{} | |
), | |
// workaround for https://github.com/angular/angular-cli/issues/9975 | |
new webpack.ContextReplacementPlugin( | |
/(.+)?angular(\\|\/)common(\\|\/)locales/, | |
/(de-AT|de-CH|de|en-AU|en-CA|en-GB|en-IE|en-NZ|en|es-CL|es|es-MX|fr-BE|fr-CA|fr|it|nl-BE|nl|pl|pt)$/ | |
) | |
] | |
} |
hey @Liudmyla01, looks like some typing issue. Based on this one, can you give it a try with
var lastTime = 0;
global['requestAnimationFrame'] = function(callback)
{
const now = new Date().getTime();
var nextTime = Math.max(lastTime + 16, now);
return setTimeout(function() { callback(lastTime = nextTime); }, nextTime - now);
};
I'm not actively maintaining the script but maybe the above could help you based on your screenshot!
thanks, I will try it)
hey @Liudmyla01, looks like some typing issue. Based on this one, can you give it a try with
var lastTime = 0; global['requestAnimationFrame'] = function(callback) { const now = new Date().getTime(); var nextTime = Math.max(lastTime + 16, now); return setTimeout(function() { callback(lastTime = nextTime); }, nextTime - now); };
I'm not actively maintaining the script but maybe the above could help you based on your screenshot!
I tried it and now I'm getting this error
dont know how to solve it :|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, can you help me fix some bug
?