angular / angular-cli

CLI tool for Angular
https://cli.angular.io
MIT License
26.72k stars 11.98k forks source link

[Feature] Reduce dependencies on nodejs for the CommonEngine #25718

Open cromefire opened 1 year ago

cromefire commented 1 year ago

🚀 Feature request

What modules are relevant for this feature request?

Description

I'd like to use Angular server side outside of nodejs, because it's intended to be delivered via web frameworks or gateways (like Spring or ASP.NET Core), but those are often not in nodejs.

In my case I'd like to be able run Angular on something like GraalJS (GraalVM can run both JVM and JS) or maybe with some WASM-based JS engine to directly integrate with the backend applications.

The current state with something like GraalJS actually doesn't look too bad, but it's pretty hard to build drop-in replacements for all the things and debug all of that.

Describe the solution you'd like

I don't have the overview, whether this is even possible, but what I'd imagine ideally would be some sort of RuntimeAdapter interface that specifies all the host features required by the CommonEngine and then a NodeJsAdapter that is passed by default and implements everything using all the functions currently implemented using nodejs built-in modules and then one can optionally specify a custom adapter that works for their environment, whatever that may be.

Describe alternatives you've considered

Patching all the nodejs built-in modules with custom implementations is kinda maybe possible, but it's quite the project and kinda fragile and messy and definitely not safe from breaking changes.

Scope

This issue is about the core engine not depending on node.js. It is not about alternative platforms this engine could be used in nor is the goal for Angular to provide any Integrations with specific platforms. It is merely the option to have the APIs so that either all developers or the Angular team itself can even entertain to build those kinds of platform support.

cromefire commented 1 year ago

So after some more work I actually got it running (and it's actually running surprisingly fast, only CSS inlining is WIP) and the following is what I needed to patch:

And probably more issues I haven't found yet.

Also queueMicrotask, setTimeout, clearTimeout, setInterval and clearInterval need to be patched, but as they are so universal, I think it's fair to make the Engine responsible for bringing those.

To be clear I'm not advocating for everyone enduser needing to bring this on their own or bloating the package for everyone with huge replacements, but what might make sense is to take those dependencies out of the CommonEngine and change it's signature to something like:

export interface EngineAdapter {
    readPublicFile(relativePath: string): Promise<string>;
    // ...

export declare class CommonEngine {
    constructor(bootstrap?: Type<{}> | (() => Promise<ApplicationRef>) | undefined, adapter: EngineAdapter, providers?: StaticProvider[]);
}

and then having some package like @angularuniversal/nodejs-adapter that provides @angularuniversal/express-engine with an easy to use implementation of that Adapter so there are no breaking changes for the normal user. For a bit less modularity, without breaking the CommonEngine API one could also maybe include that NodeJsEngine in the common package, but that makes it harder probably. The biggest problems are going to be the dependencies on node for dependencies though, so the feasibility should probably be checked before trying.

I'll try and get my adapter into a not-that-messy state in the coming days and get it open source, that it might be more obvious where the problems and risks lie, with the current non-decoupled approach.

angular-robot[bot] commented 1 year ago

This feature request is now candidate for our backlog! In the next phase, the community has 60 days to upvote. If the request receives more than 20 upvotes, we'll move it to our consideration list.

You can find more details about the feature request process in our documentation.

angular-robot[bot] commented 11 months ago

Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.

Find more details about Angular's feature request process in our documentation.

MickL commented 10 months ago

This feature request is CRUCIAL to keep Angular alive, I dont think it needs a community voting. Frameworks like Next or Nuxt are overtaking React and Vue and that is also because more hosters (e.g. Vercel, Cloudflare Pages, Netlify) easily allow SSR on their serverless functions. Angular Universal however still cant be used on serverless functions like Cloudflare Workers.

From my personal perspective this missing feature is the death of Angular, interesting article about this: https://dev.to/jdgamble555/angular-universal-is-the-problem-not-angular-5801

alan-agius4 commented 10 months ago

I do agree that we need to reduce dependencies on Node.js APIs, although most of these come from critters.

That being said, you can do server side rendering with cloudflare workers https://developers.cloudflare.com/pages/framework-guides/deploy-an-angular-site/

MickL commented 10 months ago

That doc is for a normal Angular site, not a Angular Universal SSR app

alan-agius4 commented 10 months ago

npm create cloudflare@latest my-angular-app -- --framework=angular will create and configure an Angular SSR application with all the necessary things to be deployed on a cloudflare worker.

cromefire commented 10 months ago

They seem to shim the NodeJS API, maybe a good workaround for now: https://github.com/cloudflare/workers-sdk/blob/36d497829972667852bbcaacebcb22124d73d765/packages/create-cloudflare/src/frameworks/angular/templates/tools/bundle.mjs#L19 The code looks too complicated though to burden the average developer with all of that. Seems like if the CommonEngine had no NodeJS dependencies, their code could be far more resilient and simpler.

MickL commented 10 months ago

npm create cloudflare@latest my-angular-app -- --framework=angular will create and configure an Angular SSR application

Sorry but where do you see SSR and / or Angular Universal?

Bildschirmfoto 2023-10-16 um 18 34 41

<img width="514" alt="Bildschirmfoto 2023-10-16 um 18 33 54" src="https://github.com/angular/angular-cli/assets/5103210/184535a4-7554-4c2b-8900-89b785306e96">

Angular Universal is using @nguniversal/express-engine and Express alone is not possible on Cloudflare Workers.

Btw the initial issue is from 2020: https://github.com/angular/universal/issues/1740

cromefire commented 10 months ago

I think you might have to add SSR yourself, because I see references to it in the source code of the CLI, but the documentation is really confusing... Would be much better if it was as easy as just adding a cloudflare engine or so, but because of the dependencies they probably would need some elaborate schematics for that.

MickL commented 10 months ago

Maybe they tried to make it work without success but in the docs there is no word to Angular Universal or SSR and the command that @alan-agius4 stated creates just a regular Angular app like @angular/cli new does.

alan-agius4 commented 10 months ago

@MickL, in the above screenshot, I am not even seeing any chances done by CloudFlare such as the creation of the tools directory.

Here's what I am seeing, after running the above mentioned command.

tree -L 2
.
`-- my-angular-app
    |-- angular.json
    |-- node_modules
    |-- package.json
    |-- package-lock.json
    |-- README.md
    |-- src
    |-- tools
    |-- tsconfig.app.json
    |-- tsconfig.json
    |-- tsconfig.server.json
    |-- tsconfig.spec.json
    `-- yarn.lock
MickL commented 10 months ago

Another thing that is not working is Vercel Edge Functions:

Error: The Edge Function "api/index" is referencing unsupported modules:

  • dist/server/main.js: crypto, fs, http, https, net, os, path, querystring, stream, string_decoder, timers, tty, url, zlib, node:fs, node:path
jdgamble555 commented 10 months ago

This is definitely a huge problem. If Angular Universal (not Angular) does not support Edge Function deployment, which is a Deno deployment, it will never truly be able to compete and adopted by people coming from other frameworks.

J

oriollpz commented 10 months ago

Any news on how to deploy an Angular +16 SSR app into Vercel/Denodeploy/Cloudfare?

piyushpranjal03 commented 10 months ago

I recently deployed angular ssr 16 on EC2 with Github Actions and previously deployed on vercel using this guide. It was working fine on vercel but the Initial sever response time was around 2 -3 second. So I migrated from there after 1 day

MickL commented 10 months ago

Angular Universal runs on Vercel but not on Serverless Edge Workers like Vercel Edge Functions, Deno Deploy or Cloudflare Workers.

jdgamble555 commented 10 months ago

@piyushpranjal03 - Hi Pi, I wrote that guide :)

The reason it takes 2-3 seconds, is because Vercel itself is Serverless using AWS Lambdas under the hood. It only takes 2-3 second on the first load of the bucket, which is the cold start time. AWS Lambdas are way faster than Cloud Functions (although paying more for faster servers could change that).

Vercel Edge, on the other hand, does not use serverless functions, but the Edge Network, which would load extremely fast. However, Angular Universal will not load on the edge due to the Express dependencies (the point of this post).

Fix this problem, and Angular Universal can run on the Edge (no cold starts or serverless), and it would be extremely fast on a CDN... just like you can do with literally every other SSR framework. (technically the edge is considered serverless without cold starts, but completely different infrastructure)

J

piyushpranjal03 commented 10 months ago

Thanks for the guide @jdgamble555 . Yeah now I'll move my app to cloudflare directly will wait until the proper SSR deployment is not available there.

joseph-simpson commented 10 months ago

npm create cloudflare@latest my-angular-app does work now (it didn't work for me the last time I tried some weeks ago).

main.server.ts looks like this:

import "zone.js/dist/zone-node";
import "@angular/platform-server/init";

import { bootstrapApplication } from "@angular/platform-browser";
import { renderApplication } from "@angular/platform-server";

import { AppComponent } from "./app/app.component";
import { config } from "./app/app.config.server";

interface Env {
    ASSETS: { fetch: typeof fetch };
}

// We attach the Cloudflare `fetch()` handler to the global scope
// so that we can export it when we process the Angular output.
// See tools/bundle.mjs
(globalThis as any).__workerFetchHandler = async function fetch(
    request: Request,
    env: Env
) {
    const url = new URL(request.url);
    console.log("render SSR", url.href);

    // Get the root `index.html` content.
    const indexUrl = new URL("/", url);
    const indexResponse = await env.ASSETS.fetch(new Request(indexUrl));
    const document = await indexResponse.text();

    const content = await renderApplication(
        () => bootstrapApplication(AppComponent, config),
        { document, url: url.pathname }
    );
    // console.log("rendered SSR", content);
    return new Response(content, indexResponse);
};

and the tools folder contains the Cloudflare shims/magic to make it work.

Watching the Angular 17 announcement video I was excited to see just how committed the Angular team are to SSR (and SSG), including non-node environments on the edge. Especially with Angular Universal being rolled into core and a better DX.

cromefire commented 10 months ago

the tools folder contains the Cloudflare shims/magic to make it work

Yeah well, that's the big honey, that that isn't needed anymore, as it doesn't sound too stable of a foundation...

MickL commented 10 months ago

I was excited to see just how committed the Angular team are to SSR (and SSG)

They NEED to be. Angular is already left behind and I dont see them catching up to Next and Nuxt anymore. After 7 years of being an Angular developer I just tried out Vue and Nuxt for the first time and I am simply amazed by how easy it is (no classes, no DI, auto imports, runs everywhere, file based routing, build a server rest API directly in Nuxt, etc. etc.) and also by the eco system around it. Nuxt built their own server h5 to reduce serverless cold start time and a server engine Nitro to be able to deploy it everywhere (article here) while Angular is still using Express that doesnt even work on edge workers.

cromefire commented 10 months ago

no classes, no DI

Well but that's why there are different frameworks to choose from. I find both of them to be very important to me for big enterprise applications we develop. But yes SSR on the edge is a must.

piyushpranjal03 commented 9 months ago

Hi @alan-agius4 any updates on this? ETA or anything?

alan-agius4 commented 9 months ago

The @angular/ssr at the moment common engine indeed has a strict requirement for Node.js due to Critical CSS inlining. However, this does not mean that Angular SSR does not work on Edge workers. Edge workers are supported and in fact we did recently work with both Netlify and CloudFlare to support Angular version 17.

We will continue to improve Edge worker compatibility and integrations with providers in the coming versions.

jdgamble555 commented 9 months ago

@alan-agius4 - I think a lot of people use Vercel for their projects, which is why this is a problem for me specifically. Perhaps there could be a deno, bun, and node build version respectively. I think vite handles this automatically, so this is really a esbuild problem and should be fixed there I believe. That way it could be removed all together in angular/ssr, and just added to the build process.

J

alan-agius4 commented 9 months ago

@jdgamble555, we actually did reach out to Vercel, to work with them to support SSR. Unfortunately in the near term they do not have a priority to add support for Angular SSR/SSG.

I think you might have sent an incorrect vite link above, as it links to CSS processing.

cromefire commented 9 months ago

Perhaps there could be a deno, bun, and node build version respectively.

Would be good if it's modular enough though to support other stuff as well. I'd want to use it from GraalVM, which works differently again. Or maybe you'd want to use it as a portable WASM/WASI module or something like that. It needs to be flexible.

Not saying that support for those targets needs to be in this repo, but it should be flexible enough that you can add support for those targets on your own with a few lines of code+implementations for FS access and HTTP(S).

jdgamble555 commented 9 months ago

@alan-agius4 - The CSS preprocessing will inline the CSS in the page. It is actually automatic, but yes uses postcss.

Vite is pre-configured to support CSS @import inlining via postcss-import.

Yeah, I think esbuild somehow should be more versatile and support any build environment, but I think ssr should be separate from the build process like vite... probably (definitely not my specialty). As long as the environment problem gets solved, how it is solved is irrelevant to me.

As far as Vercel:

When writing my article on deploying to Vercel, and actually wanting to deploy to Vercel for my blog back then, I actually reached out to them.

They told me they don't support any 3rd party integrations, as it is up to the Framework creator and the community to create adapters that work with Vercel deployments. However, they had a specific problem with Node.js server, quoting them from December 28, 2021:

Unfortunately, there are no plans to add this since this is using a Serverless Function to start your server on Vercel, instead of a Node.js server, making this not ideal.

I proved them wrong by making it work, as I honestly think there was an ignorance on Vercel's behalf. They literally told me that since I did not have an adapter like nuxt vercel-build, they would not add it to the list. I did not want to take the time to build one, and frankly just switched away from Angular at that time due to the long build times for Angular Universal.

I think you guys could build one now easily, but it would not work for Edge functions until the Node.js dependencies are removed, which would solve all problems at hand.

J

naveedahmed1 commented 9 months ago

I am receiving below error when trying my app on cloudflare:

http
X [ERROR] ERROR Error: Dynamic require of "http" is not supported
jdgamble555 commented 9 months ago

@alan-agius4 - Are you guys going to be able to work on Vercel? As I mentioned above, you would need to build a Vercel adapter like the nuxt-adapter. Again, getting rid of these dependencies all together and opening up the code to run on any JS environment would be ideal and would probably make Vercel more excited to support Angular SSR.

J

naveedahmed1 commented 9 months ago

Just wanted to update that this error was related to my application.

While adding provideHttpClient to the providers we also need to use the withFetch option so that it works with Cloudflare workers.

Using below fixed the issue:

provideHttpClient(
  withFetch()
)

So, I can confirm Angular 17 works fine with Cloudflare!.

jdgamble555 commented 7 months ago

@alan-agius4 - Is this still on the radar so we can at least deploy to Vercel Edge?

J

ProximaB commented 7 months ago

Is there anywhere listed what edge functions providers are supported with ssr? Right now it is Cloudflare and Netlify?

Pii2 commented 7 months ago

Is there anywhere listed what edge functions providers are supported with ssr? Right now it is Cloudflare and Netlify?

Cloudflare works, but only without critical css inlining

jdgamble555 commented 6 months ago

@alan-agius4 - Are you guys still working on this for Vercel / other node-less environments?

jer-tx commented 6 months ago

we did recently work with both Netlify and CloudFlare to support Angular version 17.

@alan-agius4 Can you expand on this? There's zero documentation anywhere of how to make Pages work with SSR. It isn't clear within Pages how to make this work.

alan-agius4 commented 6 months ago

@jer-tx, please check out cloudflare docs. https://developers.cloudflare.com/pages/framework-guides/deploy-an-angular-site/

NB: you will need to run npm create cloudflare as it will setup several things for you. In case you have an existing project, you can move the generated file into your project.

nunoarruda commented 5 months ago

Been trying to deploy an Angular SSR app for several days now but encountering several issues, even with the Cloudflare version of Angular SSR. The lack of documentation is also not helping.

After addressing the node dependencies, I would also suggest the Angular team to try replacing the Express server with a more cross-platform/cross-runtime framework, if possible. This way Angular SSR could be deployed on Cloudflare, Fastly, Deno, Bun, AWS, Node.js, etc

I have 2 suggestions: Hono or Nitro.

cromefire commented 5 months ago

After addressing the node dependencies, I would also suggest the Angular team to try replacing the Express server with a more cross-platform/cross-runtime framework

That's not even necessary, in the common engine there's basically just a function to which you provide one URL and it'll return a rendered string. No need to "integrate" any other web server, it's all just configuration. And if you wanna simplify that, no one stops you from publishing an NPM package building on you favorite web server and the common engine, it's super easy.

jdgamble555 commented 5 months ago

@cromefire - This is unhelpful here. This needs to be part of the Angular/SSR package itself. As @nunoarruda said, there are many servers that don't support Angular out-of-the-box without changing to Vite etc (which no guide to do so btw). None of this is easy to do. Angular is the only mainstream Framework that doesn't work for basic deployment (NextJS, Remix, Nuxt, Astro, Solidstart, QwikCity, SvelteKit, Preact, Gatsby, etc) options. It's not just Edge Deployment that is the problem but all other options due to this issue request.

This should be built into the main code.

J

cromefire commented 5 months ago

@cromefire - This is unhelpful here. This needs to be part of the Angular/SSR package itself. As @nunoarruda said, there are many servers that don't support Angular out-of-the-box without changing to Vite etc (which no guide to do so btw). None of this is easy to do. Angular is the only mainstream Framework that doesn't work for basic deployment (NextJS, Remix, Nuxt, Astro, Solidstart, QwikCity, SvelteKit, Preact, Gatsby, etc) options. It's not just Edge Deployment that is the problem but all other options due to this issue request.

This should be built into the main code.

J

So what you are telling me is that something like

app.map("**", async ctx => {
    return await commonEngine.render(ctx.path);
});

is too hard? Please... I hope you see how silly that is...

If you're not able to make one function call in your framework of choice, you probably shouldn't use that framework. The problem here (and that's what the issue is about) is that that render call depends on node.js. Once that's fixed there's no reason for Angular to support absolutely everything (at least on the SSR side), even the express integration is barely worth a package (upon looking at the new code I see that the Angular team thought so as well and it seems like they have removed the integration in favor of just documenting how to use it from express)...

jdgamble555 commented 5 months ago

Yes, I have no idea what any of that does. I can easily guess, but I shouldn't have to configure all that. It certainly isn't in the docs. You're missing the point of this thread. Not to mention that doesn't cover the CSS problems etc.

J

cromefire commented 5 months ago

Yes, I have no idea what any of that does.

Then you should read the docs for the common engine & SSR or just find someone who has already built an integration or build and integration once yourself and publish it for everyone to use... If there's any problem at all it's documentation, which is completely out of scope for this issue.

You're missing the point of this thread.

You're missing the point of the issue. It has a very narrow, specified goal and that doesn't include any frameworks, but the CommonEngine specifically and is not supposed to devolve into a fight over how and which Integrations for SSR should be done. If you want to have that discussion, open a discussion or another issue.

but I shouldn't have to configure all that

Well if 3 loc (which like describing 3 loc as "all that" is really quite the stretch, try to configure an ASP.NET project with a DB or so, that could be described as "configure all that") is too much for you, I guess find another framework... Low code has very little code I heard...

naveedahmed1 commented 5 months ago

To deploy your Angular application with Server-Side Rendering (SSR) on Cloudflare Pages, follow these steps:

  1. Begin by initiating the Cloudflare project setup using the command npm create cloudflare@latest.

  2. Choose the desired directory or path where you wish to create your project.

  3. Select Website or Web App when prompted.

  4. Specify Angular as your preferred development framework during the setup process.

  5. Select the appropriate Angular CSS configuration for your project.

This will install all necessary dependencies.

  1. You'll be asked if you want to deploy the project to Cloudflare. Choosing Yes will initiate the creation of a Cloudflare project, deploy your application, and provide a deployment URL for testing purposes.

Alternatively, if you opt for No, you can deploy your project at a later stage using the npm run deploy command within your project directory.

To locally test your project, execute the command npm run start on your machine.

Familiarize yourself with few things:

server.ts File: This file is responsible for executing your Angular application. It contains a workerFetchHandler function, which leverages Angular's renderApplication function to launch your Angular app on the server. This process generates HTML markup corresponding to the current URL.

Tools Folder: Within this folder, you'll find a collection of scripts. These scripts are executed upon completion of the Angular build process. They handle tasks such as preparation and copying of files to the Cloudflare directory. Take a look at the scripts section in the packages.json file for further details.

_routes.json File in src Folder: This file serves to inform Cloudflare about routes requiring Server-Side Rendering (SSR) and those to be excluded from SSR. Routes designated for SSR are processed by Cloudflare Workers, while excluded routes are served directly by Cloudflare Pages.

Use withFetch in provideHttpClient If your app makes http calls, update provideHttpClient and include withFetch:

provideHttpClient(
  withFetch()
),

Custom HTTP Status Code or Headers: Should the need arise to set custom HTTP status codes or headers, you'll need to update the server.ts file. Pass ResponseInit as platform providers to configure these settings. Follow these steps:

Update the renderApplication function as illustrated below:

const responseInitOptions: ResponseInit = { status: 200, headers: headers };

const content = await renderApplication(bootstrap, {
  document,
 url: url.pathname,
  platformProviders: [
    { provide: RESPONSE_INIT_OPTIONS, useValue: responseInitOptions }
  ]
});

Define the injection token RESPONSE_INIT_OPTIONS in a TypeScript file:


import { InjectionToken } from '@angular/core';
export const RESPONSE_INIT_OPTIONS = new InjectionToken<ResponseInit>('RESPONSE_INIT_OPTIONS');

Inject RESPONSE_INIT_OPTIONS into your Angular app:

private readonly responseInitOptions = inject(RESPONSE_INIT_OPTIONS, {
  optional: true,
});

Set the HTTP status code:

this.responseInitOptions.status = statusCode;

Configure custom headers:

let newHeaders = {
  'X-Robots-Tag': 'noindex',
  'Content-Type': 'text/html; charset=utf-8'
};

this.responseInitOptions.headers = newHeaders;

Thus far, the sole obstacle I've encountered pertains to https://github.com/cloudflare/workers-sdk/issues/5262; notwithstanding, the overall procedure appears remarkably straightforward. My commendations to both the Angular and Cloudflare teams especially @alan-agius4 @petebacondarwin for facilitating such a seamless process.

Should you require any assistance, please do not hesitate to reach out.

nunoarruda commented 5 months ago

@naveedahmed1 for your information or for your curiosity I've also found this obstacle: https://github.com/cloudflare/workers-sdk/issues/5263

naveedahmed1 commented 5 months ago

@nunoarruda have you updated provideHttpClient and using withFetch:

 provideHttpClient(
  withFetch()
),

https requests are working fine with "prerender": false

nunoarruda commented 5 months ago

@naveedahmed1 it's an Angular app generated with create cloudflare, it already comes with withFetch configured:

providers: [provideHttpClient(withFetch()), provideRouter(routes), provideClientHydration()]

naveedahmed1 commented 5 months ago

Yes you are right, and this config seem to be working fine in my case with "prerender": false

jdgamble555 commented 5 months ago

Hey guys,

Thank you for the help. You can definitely keep stripping Angular to its core to get it to work in any scenario (it is just JS after all), but there are no docs for this (I have asked for docs many times btw), it is not easy (or you would see at least one article on how to deploy to Vercel Edge outside of Analog), and it is the only Framework that requires crazy custom configurations for basic deployment options (anything outside of NodeJS). Why do you think the Cloudflare deployment option is so custom. So yes, this is a problem when things like os, path, crypto, etc. being in the way by default.

I wrote an article about all this when it comes to deployment problems: The State of Angular SSR Deployment. You can deploy to Cloudflare, but the Inline Critical CSS does not work.

If you compare the examples, you can see a Cumulative Layout Shift (CLS) problem on the Cloudflare version because the Critical CSS is not loaded inline (from my understanding this is why). If Cloudflare now works let me know.

In a perfect would, you would just say:

But maybe we just need this:

etc, etc...

There would be options for serverless or edge. serverless would automatically code split into functions like NextJS does, or it would load immediately etc. There could be a mixture of options.

If you open a similar issue, it will probably get closed and point back to this issue FWI. This is the core of the deployment problem. This post is about SSR, not regular Angular problems.