I'm looking for an approach to accessing assets in the /assets/
folder that is used to build the content in a component when prerendering an application. I'm using Angular 14 and the @nguniversal/express-engine
package. I can't seem to get static assets to be read in the app when running npm run prerender
.
I've seen the discussion at #858 however as the last comment points out this won't work when prerendering.
I have a minimal example of what I mean here: https://stackblitz.com/edit/angular-ivy-dxb32y?file=src%2Fapp%2Fapp.service.ts
You see my service turns the path into an absolute URL:
public getContents(path: string): Observable<string> {
if (isPlatformServer(this.platformId) && path.includes('./')) {
path = `http://localhost:4200/${path.replace('./', '')}`
}
return this.http.get(path, {
observe: 'body',
responseType: 'text',
});
}
And the ssr:dev
command serves this content correctly.
However, under prerender
I get the following error:
⠸ Prerendering 1 route(s) to C:\Users\***\preloading\dist\preloading\browser...ERROR HttpErrorResponse {
headers: HttpHeaders {
normalizedNames: Map(0) {},
lazyUpdate: null,
headers: Map(0) {}
},
status: 0,
statusText: 'Unknown Error',
url: 'http://localhost:4200/assets/file.txt',
ok: false,
name: 'HttpErrorResponse',
message: 'Http failure response for http://localhost:4200/assets/file.txt: 0 Unknown Error',
I've tried a few things, such as:
prerender
fs
to read the static assets however these node modules can't be found during the prerender
stage:if (isPlatformServer(this.platformId) && path.includes('./')) {
import("fs")
path = `http://localhost:4200/${path.replace('./', '')}`
}
Gives:
✔ Browser application bundle generation complete.
⠦ Generating server application bundles (phase: sealing)...
./src/app/app.service.ts:14:8-20 - Error: Module not found: Error: Can't resolve 'fs' in 'C:\Users\***\preloading\src\app'
Error: src/app/app.service.ts:12:14 - error TS2307: Cannot find module 'fs' or its corresponding type declarations.
12 import("fs")
Any other ideas at all about what I can do?
So I managed to crack this using the relatively hacky solution of running both ng serve
and npm run prerender
using a node script:
https://stackblitz.com/edit/angular-ivy-uy7wy9?file=prerender.js
var error = false;
function sleep(miliseconds) {
console.log(`Sleeping for ${miliseconds} ms`);
if (miliseconds == 0)
return Promise.resolve();
return new Promise(resolve => setTimeout(() => resolve(), miliseconds))
}
async function run() {
try {
console.log("Running Angular server");
var proc = require('child_process').spawn('ng', ['serve']);
await sleep(20000)
console.log("Running prerender");
var prerender = require('child_process').spawn('npm', ['run', 'prerender']);
var prerenderTimeoutSeconds = 120;
var timeoutObject;
var timeoutResolve;
var timeoutReject;
var timeout = new Promise((resolve, reject) => {
timeoutResolve = resolve;
timeoutReject = reject;
timeoutObject = setTimeout(() => {
console.log('Timed out, killing prerender');
try {
prerender.kill("SIGKILL")
reject(Error("Timed out running prerender"))
} catch (e) {
console.error(e)
reject(Error('Cannot kill prerender'));
}
}, prerenderTimeoutSeconds * 1000)
});
prerender.stdout.on('data', (data) => {
console.log(`prerender stdout: ${data}`);
});
prerender.stderr.on('data', (data) => {
console.error(`prerender stderr: ${data}`);
});
prerender.on('close', (code) => {
clearTimeout(timeoutObject);
console.log(`prerender exited with code ${code}`)
if (code === 0) {
timeoutResolve()
} else {
timeoutReject(Error(`prerender exited with code ${code}`));
}
});
await timeout
} catch (err) {
console.error(err);
console.error(err.stack);
error = true;
} finally {
if (proc) {
console.log("Killing Angular server");
var angularKilled = proc.kill("SIGKILL")
console.log(`kill -9 on Angular success [${angularKilled}]`)
}
}
}
(async () => await run())();
if (error) {
throw new Error("Exception during execution")
}