问题
I've got an angular 7.2 app using open layers 5.3. I'm trying to set up angular universal for this app, but when I run the universal server (node dist/server.js
), I got some errors due to client only variables not being defined, such as window
.
webpack:///./node_modules/ol/has.js?:54
var DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;
^
ReferenceError: window is not defined
I tried using domino to mock these variables, but it just fails with another error, because of a canvas element
Error: NotYetImplemented
at HTMLCanvasElement.exports.nyi (/my/project/dist/server.js)
All the OL code is imported and used in a single MapComponent
component. My imports look like this in this component:
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
My main problem is that the mentionned errors occur as soon as I run the universal server, so even before trying to access the universal rendered website in a browser.
Consequently, using something like this to only instantiate MapComponent
when the app is running client side does not work, since the crash occurs before that
<app-map *ngIf="isBrowser"></app-map>
where the isBrowser
variabled is initialised in the component with the value of isPlatformmBrowser(platformId)
Any suggestions?
回答1:
This is how I control over SSR in my app,
my component side,
import { isPlatformBrowser } from '@angular/common';
import * as JQuery from "jquery";
constructor(
@Inject(PLATFORM_ID) public platformId){...}
ngOnInit() {
if (isPlatformBrowser(this.platformId)){
var $: any = jQuery; //for example, load jQuery if you are sure about platform is browser
}
}
For example, I'm showing loading bar while ssr is not browser.
my template side,
<ssr-loading *ngIf="platformId != 'browser'"></ssr-loading>
回答2:
Try to use Openlayer latest version "ol": "^5.3.1",
回答3:
The error I had was caused by the pixelworks
library, which is a dependency of OL 5.
This library instantiates a canvas and tries to get a 2d context from it, which is not supported by domino
var context = document.createElement('canvas').getContext('2d');
The context is not used straight away, but is created at a place where it is always instantiated.
Since I do not want to use OL when the code is executed server side, my solution was to modify the server.js
file during the build process to remove this instantiation.
sed -i "s/var context = document.createElement('canvas').getContext('2d');/var context = null;/" dist/server.js
Note: domino
is still needed because the modules exported from OL also try to access global variables, such as window
回答4:
Add to server.ts
const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync(path.join(__dirname, '.', 'dist', 'index.html')).toString();
const win = domino.createWindow(template);
const files = fs.readdirSync(`${process.cwd()}/dist-server`);
// const styleFiles = files.filter(file => file.startsWith('styles'));
// const hashStyle = styleFiles[0].split('.')[1];
// const style = fs.readFileSync(path.join(__dirname, '.', 'dist-server', `styles.${hashStyle}.bundle.css`)).toString();
global['window'] = win;
Object.defineProperty(win.document.body.style, 'transform', {
value: () => {
return {
enumerable: true,
configurable: true
};
},
});
global['document'] = win.document;
global['CSS'] = null;
// global['XMLHttpRequest'] = require('xmlhttprequest').XMLHttpRequest;
global['Prism'] = null;
来源:https://stackoverflow.com/questions/55120373/using-angular-7-universal-with-openlayers-5