I'm trying to use prerender-node and a private prerender server to prerender an Angular2/Express app. If I try to target es5 in my tsconfig.json, the prerender server throws this error:
ReferenceError: Can't find variable: Map
undefined:1521 in KeyRegistry
:1540
:7
If I try to target es6 (including node_modules/typescript/lib/lib.es6.d.ts
in the files
array), the prerender server throws this error:
SyntaxError: Unexpected token 'const'
http://localhost:3000/app.js:50 in eval
http://localhost:3000/app.js:50
http://localhost:3000/app.js:20 in __webpack_require__
http://localhost:3000/app.js:40
I'm guessing I need to include some sort of polyfill in order for this to work, but I don't have the first idea what to include or where to include it.
Here's my webpack config in case that helps:
var webpack = require('webpack');
var path = require('path');
var rootDir = path.resolve();
module.exports =
{
target: 'node',
entry: rootDir + "/dist/client/app/bootstrap.js",
output: {
path: rootDir + "/dist/client", publicPath: '/', filename: "app.js",
pathinfo: true
},
devtool: 'eval',
resolve: {
root: rootDir + "/dist/client/app",
extensions: ['', '.js']
},
module: {
loaders: [
{
test: /\.ts/, loaders: ['ts-loader'], exclude: /node_modules/
}
]
}
};
And my client tsconfig in case that helps:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": false,
"outDir": "../../dist/client",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true
},
"files": ["app/bootstrap.ts", "app/vendor.ts", "app/Window.ts", "tests/index.ts", "../../node_modules/typescript/lib/lib.es6.d.ts"]
}
If I change my webpack config to target web
instead of node
, and comment out server.use(prerender.removeScriptTags());
in the prerender server, the request hits the prerender server every time and no errors are thrown, but nothing is prerendered, either. Seems closer than before, though, so I thought I'd update.
Prerender doesn't seem to execute any Angular code. If I set window.prerenderReady = false
in a script tag in the head in my index.html, and then try to set it to true again when my root component is instantiated, the prerender server times out:
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'my-app',
template: `
<div id="main"> all the things </div>
`
})
export class AppComponent implements OnInit
{
constructor(){}
ngOnInit()
{
// Prerender never executes this code before it times out
window.prerenderReady = true;
console.info('Prerender Ready');
}
}
I managed to get this working with the following changes:
Use target: 'web'
in Webpack config.
Use target: 'es5'
and add node_modules/typescript/lib/lib.es6.d.ts
to files array in tsconfig.json. This prevents compile-time errors on es6 stuff.
Apparently, despite targeting web and es5, Webpack still leaves es6 styntax around that PhantomJs can't deal with. I don't love the idea of including babel in a Typescript project, but this is the only thing I've found that works: require('babel-polyfill');
BEFORE any application code or vendor code that may use any es6 syntax.
Set window.prerenderReady = false;
in the head
of the document and then set it to true
when you instantiate the root application component:
export class ApplicationComponent implements OnInit
{
ngOnInit()
{
window.prerenderReady = true;
}
}
NOTE: for the above to work in Typescript you'll need to make sure the property prerenderReady
exists on type Window. E.g.:
interface Window
{
prerenderReady:boolean;
}
On the server side, make sure you configure prerender-node BEFORE any routes are configured. E.g.:
export function routeConfig(app:express.Application):void
{
// Prerender
app.use(require('prerender-node').set('prerenderServiceUrl', CONFIG.prerenderServiceUrl));
// Angular routes should return index.html
app.route(CONFIG.angularRoutes)
.get((req:Request, res:Response, next:NextFunction) =>
{
return res.sendFile(`${app.get('appPath')}/client/index.html`);
});
}
Hope this helps someone :)