analog
analog copied to clipboard
[Proposal] Infer vite's dev-server port on SSR to leverage [ngSrc]'s full path automatically
Which scope/s are relevant/related to the feature request?
platform
Information
Hello,
Currently, when working with SSR locally on Vite DevServer, there's no way to infer the baseURL to prepend requests for the assets given that Domino can't figure out what's going on.
When debugging Angular's source code, window.location.href is resolved to / which is used to construct the asset url like new URL(assetPath, window.location.href). Right now, if you just throw a relative path like /assets/image.png the server will throw an exception and that's it.
In production things are easier because we can provide the imageLoader, no big deal.
Right now, if you dont supply a full path like http://localhost:{PORT}/assets/image.png, SSR will throw an error when using img[ngSrc].
Describe any alternatives/workarounds you're currently using
What I do today for development purposes
- Tell Vite's server that we want to work with specific port and it should be strict.
- If we're in
development, provide an environment variable likeDEV_PORT.
file: vite.config.ts
// ...redacted
server: {
port: 4202,
strictPort: true
},
define: {
...(mode === 'development'
? {
'import.meta.env.DEV_PORT': '4202'
}
: {}),
'import.meta.vitest': mode !== 'production'
}
// ...redacted
- In my
assetPathfunction, test if we're running inSSRand if there's aDEV_PORTdefined and if so, return the full local url so SSR can successfully fetch the image.
file: assets-path.ts
export const assetPath = (file: string) => {
if (import.meta.env.SSR && !!import.meta.env?.['DEV_PORT']) {
return `http://localhost:${import.meta.env.DEV_PORT}/assets/${file}`;
}
return `/assets/${file}`;
};
This works but it's hackish (at least for me).
Proposal
Some ideas came to mind:
-
Vite's analog-plugin could leverage some of this and provide the
DEV_PORTautomatically like I'm doing, ports can be randomly assigned, so this would make things less hardcoded. -
Or analog-plugin could receive a new property like:
{ ssrBaseUrl: 'INFER' | (string & {}) }. 2.1 if a string is passed, its supposed to be a full base url like: 'http://localhost:4200' or; 2.2 if 'INFER' is passed, analog plugin could somehow infer internally the dev-server port and somehow inject the valuehttp://localhost:{VITE_DEV_PORT}intowindow.location.hrefin Domino (or some other magic) so everything would work out of the box.
These are my main ideas, I'm happy to hear about you guys :)
PS: I can't provide any PR for this today because I have 29 days to finish a gigant project, but I felt it was worth it to bring this discussion here.
Renato
I would be willing to submit a PR to fix this issue
- [ ] Yes
- [X] No
I had this same problem, but the problem I could see was the url param in the server method inside of main.server.ts, that URL should be absolute, not only the URL path; seeing here https://github.com/analogjs/analog/blob/beta/packages/vite-plugin-nitro/src/lib/plugins/dev-server-plugin.ts#L51, the req and res object are sent in a third parameter, and I was able to solve the ngSrc problem with this update:
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.server';
import { renderApplication } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';
import { IncomingMessage } from 'node:http';
if (import.meta.env.PROD) {
enableProdMode();
}
const bootstrap = () => bootstrapApplication(AppComponent, config);
export default async function render(
_path: string,
document: string,
{ req }: { req: IncomingMessage & { originalUrl: string } },
) {
const protocol = getRequestProtocol(req);
const { originalUrl, headers } = req;
return await renderApplication(bootstrap, {
document,
url: `${protocol}://${headers.host}${originalUrl}`,
});
}
export function getRequestProtocol(
req: IncomingMessage,
opts: { xForwardedProto?: boolean } = {},
) {
if (
opts.xForwardedProto !== false &&
req.headers['x-forwarded-proto'] === 'https'
) {
return 'https';
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (req.connection as any)?.encrypted ? 'https' : 'http';
}
Interesting find @osnoser1! If this works when deployed also, we could probably provide this in the object with req and res as baseUrl. This could be an opportunity to use it server side also with an interceptor so you don't have to prefix HttpClient requests with the full baseUrl
Awesome 😄
I'm fixing the problems I found when integrating analog with the current app I'm working on. When I deploy it, I'll test it and let you know if this change worked