angular/angular-cli

CommonEngine.render should be able to read documentFilePath and publicPath from network path and not only file path

eze-nonso opened this issue · 9 comments

Which @angular/* package(s) are relevant/related to the feature request?

platform-server

Description

At the moment, only file paths can be passed to CommonEngine.render as valid locations of index.html to render and public path of referenced assets.

This is a bottleneck for development experience in complex projects. In a large multirepo project for example, it is non-trivial to make a performant ssr-dev-server implementation, since it is required to watch for changes, rebuild and write to file. It would be convenience and efficient to be able to resuse, for example, an existing in-memory optimized dev-server to serve files to CommonEngine in dev workflow.

Also, since Fetch now exists in Node V17 above, it would mean a significant improvement for very few lines of code

Proposed solution

I should be able to do something like

const html = await commonEngine.render({
   bootstrap: AppServerModule,
   documentFilepath: 'localhost:4000/deploy-path', // any network path or file path
   publicPath: 'localhost:4000/public-path' // any network path or file path,
   url,
});

Alternatives considered

It is possible to manually fetch the indexHtml, but since the details of fetching static assets references from within the indexHtml is abstracted within publicPath, this approach is not feasible

The ssr-dev-server is considered legacy and has been replaced with the new Vite-based dev-server when using the application builder, which offers a better developer experience and significantly improved edit-refresh time.

You can replace documentFilepath with the document option, allowing you to provide the contents of your index.html file.

@alan-agius4
For the first part, with the ssr-dev-server I could maintain one ssr server application for multiple angular apps in a monorepo, but the application builder seems to force you to maintain a single server per application. But that is by the way

For the second part, I have tried the document option, but of course the index.html references other assets, which are then presumably resolved via filesystem by the renderer

With the ssr-dev-server I could maintain one ssr server application for multiple angular apps in a monorepo,

Can you elaborate a bit more on this? I am not fully grasping what you mean by one server application to multiple angular apps.

With the ssr-dev-server I could maintain one ssr server application for multiple angular apps in a monorepo,

Can you elaborate a bit more on this? I am not fully grasping what you mean by one server application to multiple angular apps.

I have an nx monorepo of angular apps all part of a single website. To render them all server-side, I use another application, basically a node server, that dynamically bootstraps requested AppModule based on route.
With the new application builder, this kind of setup is difficult to achieve - one node server that server-renders multiple angular apps

We do not expect the ssr-dev-server being used across multiple apps. It's surprising that it functions in this capacity, given its reliance on both server and browser app.

We've always assumed a one-to-one relationship between the server and browser apps. This concept is akin to a production environment where each Node.js process typically manages a single application.

We do not expect the ssr-dev-server being used across multiple apps. It's surprising that it functions in this capacity, given its reliance on both server and browser app.

We've always assumed a one-to-one relationship between the server and browser apps. This concept is akin to a production environment where each Node.js process typically manages a single application.

@alan-agius4 the documentation actually provides that option - in the following, we can dynamically bootstrap a module to render. In the discontinued universal documentation - for advanced usage, we can also pass in a module to bootstrap per request.

server.get('*', (req, res, next) => {
  const {protocol, originalUrl, baseUrl, headers} = req;

  commonEngine
      .render({
        bootstrap,
        documentFilePath: indexHtml,
        url: `${protocol}://${headers.host}${originalUrl}`,
        publicPath: browserDistFolder,
        providers: [{provide: [APP_BASE_HREF](https://angular.io/api/common/APP_BASE_HREF), useValue: req.baseUrl}],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
});

So it does seem that the server was intentionally designed to allow for dynamic routing and configuration, and this is very convenient and should not go away IMO.

To make ssr-dev-server work, I made a custom builder to build the browser targets of the apps to be server-rendered, and passed that to the browser target, while the server target just ran the server builder @angular-devkit/build-angular:server.

And about the subject matter of this thread, I don't know if I've explained well how commonEngine.render does not allow for a case where the index file (that has static asset references within) is to be fetched over the network

You could use an alternative module, but within the same compilation. However, in your scenario, the Angular server app and the Common Engine reside in bundles originating from distinct compilations. Consequently, this discrepancy in DI tokens instances raises my surprise regarding the functionality working as intended in the first place.

You could use an alternative module, but within the same compilation. However, in your scenario, the Angular server app and the Common Engine reside in bundles originating from distinct compilations. Consequently, this discrepancy in DI tokens instances raises my surprise regarding the functionality working as intended in the first place.

My entire setup sits behind a .NET server, when I request an SSR page, the .NET server makes a POST request to my node instance with app providers and other details, that server then dynamically bootstraps the necessary appModule, and knows the location of the deployed files.

So I don't understand the compilation restriction, AFAIK, and from the docs, you can render any app with the right information from any server that can call the commonEngine render API