diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7eb3add06..89f149886 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,43 @@
+# [1.11.0-beta.6](https://github.com/analogjs/analog/compare/v1.11.0-beta.5...v1.11.0-beta.6) (2024-12-30)
+
+### Bug Fixes
+
+- **vite-plugin-nitro:** normalize outputPaths for app hosting ([09b1fa5](https://github.com/analogjs/analog/commit/09b1fa57a5dd038d5631febe5091311c0d9e1050))
+
+# [1.11.0-beta.5](https://github.com/analogjs/analog/compare/v1.11.0-beta.4...v1.11.0-beta.5) (2024-12-30)
+
+### Features
+
+- **vite-plugin-nitro:** add support for Firebase App Hosting deployment ([#1529](https://github.com/analogjs/analog/issues/1529)) ([3657bf1](https://github.com/analogjs/analog/commit/3657bf17c1f2c3ee03bd008de008b6fdf80b2795))
+
+# [1.11.0-beta.4](https://github.com/analogjs/analog/compare/v1.11.0-beta.3...v1.11.0-beta.4) (2024-12-28)
+
+### Bug Fixes
+
+- **vite-plugin-angular:** invalidation fixes for HMR/live reload ([#1526](https://github.com/analogjs/analog/issues/1526)) ([7b783d9](https://github.com/analogjs/analog/commit/7b783d9d114f5050a14786254aab6d3f198cc893))
+
+# [1.11.0-beta.3](https://github.com/analogjs/analog/compare/v1.11.0-beta.2...v1.11.0-beta.3) (2024-12-27)
+
+### Features
+
+- **vite-plugin-angular:** introduce support for Angular v19 HMR/live reload ([#1523](https://github.com/analogjs/analog/issues/1523)) ([0602a8f](https://github.com/analogjs/analog/commit/0602a8f79ae3c16897c966f3defe7ac3309c32a6))
+
+# [1.11.0-beta.2](https://github.com/analogjs/analog/compare/v1.11.0-beta.1...v1.11.0-beta.2) (2024-12-26)
+
+### Features
+
+- **vitest-angular:** add UI and coverage options to test builder ([#1521](https://github.com/analogjs/analog/issues/1521)) ([026b3dc](https://github.com/analogjs/analog/commit/026b3dce2f5cfe07da65922496b4c366642b3788))
+
+# [1.11.0-beta.1](https://github.com/analogjs/analog/compare/v1.10.3...v1.11.0-beta.1) (2024-12-20)
+
+### Bug Fixes
+
+- **vitest-angular:** reuse vitest server in watch mode for build-test ([#1519](https://github.com/analogjs/analog/issues/1519)) ([724d1f1](https://github.com/analogjs/analog/commit/724d1f13caa55c6fc315321ef75d29eff9b96e41))
+
+### Features
+
+- **router:** introduce support for Analog Server Components ([#1518](https://github.com/analogjs/analog/issues/1518)) ([44289b0](https://github.com/analogjs/analog/commit/44289b0008a9a62288d22866ec089f48fa502d80))
+
## [1.10.3](https://github.com/analogjs/analog/compare/v1.10.2...v1.10.3) (2024-12-17)
### Bug Fixes
diff --git a/apps/analog-app/src/app/app.config.server.ts b/apps/analog-app/src/app/app.config.server.ts
index 8b5a665d1..0da63b09b 100644
--- a/apps/analog-app/src/app/app.config.server.ts
+++ b/apps/analog-app/src/app/app.config.server.ts
@@ -1,15 +1,10 @@
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
-import {
- provideServerRendering,
- ɵSERVER_CONTEXT as SERVER_CONTEXT,
-} from '@angular/platform-server';
+import { provideServerRendering } from '@angular/platform-server';
+
import { appConfig } from './app.config';
const serverConfig: ApplicationConfig = {
- providers: [
- provideServerRendering(),
- { provide: SERVER_CONTEXT, useValue: 'ssr-analog' },
- ],
+ providers: [provideServerRendering()],
};
export const config = mergeApplicationConfig(appConfig, serverConfig);
diff --git a/apps/analog-app/src/app/pages/client/(client).page.ts b/apps/analog-app/src/app/pages/client/(client).page.ts
new file mode 100644
index 000000000..2e22540b0
--- /dev/null
+++ b/apps/analog-app/src/app/pages/client/(client).page.ts
@@ -0,0 +1,31 @@
+import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
+import { ServerOnly } from '@analogjs/router';
+
+@Component({
+ standalone: true,
+ imports: [ServerOnly],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
Client Component
+
+
+
+
+
+
+
+
+ `,
+})
+export default class ClientComponent {
+ props = signal({ name: 'Brandon', count: 0 });
+ props2 = signal({ name: 'Brandon', count: 4 });
+
+ update() {
+ this.props.update((data) => ({ ...data, count: ++data.count }));
+ }
+
+ log($event: object) {
+ console.log({ outputs: $event });
+ }
+}
diff --git a/apps/analog-app/src/app/pages/goodbye.page.analog b/apps/analog-app/src/app/pages/goodbye.page.analog
new file mode 100644
index 000000000..e8fcf7f42
--- /dev/null
+++ b/apps/analog-app/src/app/pages/goodbye.page.analog
@@ -0,0 +1,9 @@
+
+
+
+ Goodbye on the client
+
+
+
\ No newline at end of file
diff --git a/apps/analog-app/src/app/pages/server/(server).page.ts b/apps/analog-app/src/app/pages/server/(server).page.ts
new file mode 100644
index 000000000..1aef0a8ea
--- /dev/null
+++ b/apps/analog-app/src/app/pages/server/(server).page.ts
@@ -0,0 +1,11 @@
+import { RouteMeta } from '@analogjs/router';
+
+import { ServerOnly } from '@analogjs/router';
+
+export const routeMeta: RouteMeta = {
+ data: {
+ component: 'hello',
+ },
+};
+
+export default ServerOnly;
diff --git a/apps/analog-app/src/main.server.ts b/apps/analog-app/src/main.server.ts
index 8e3db29a7..3715f0e1e 100644
--- a/apps/analog-app/src/main.server.ts
+++ b/apps/analog-app/src/main.server.ts
@@ -1,31 +1,8 @@
import 'zone.js/node';
-import { enableProdMode } from '@angular/core';
-import { bootstrapApplication } from '@angular/platform-browser';
-import { renderApplication } from '@angular/platform-server';
-import { provideServerContext } from '@analogjs/router/server';
-import type { ServerContext } from '@analogjs/router/tokens';
+import '@angular/platform-server/init';
+import { render } from '@analogjs/router/server';
import { config } from './app/app.config.server';
import { AppComponent } from './app/app.component';
-if (import.meta.env.PROD) {
- enableProdMode();
-}
-
-export function bootstrap() {
- return bootstrapApplication(AppComponent, config);
-}
-
-export default async function render(
- url: string,
- document: string,
- serverContext: ServerContext
-) {
- const html = await renderApplication(bootstrap, {
- document,
- url,
- platformProviders: [provideServerContext(serverContext)],
- });
-
- return html;
-}
+export default render(AppComponent, config);
diff --git a/apps/analog-app/src/server/components/goodbye.ag b/apps/analog-app/src/server/components/goodbye.ag
new file mode 100644
index 000000000..e7584039d
--- /dev/null
+++ b/apps/analog-app/src/server/components/goodbye.ag
@@ -0,0 +1,3 @@
+
+ Goodbye from the server
+
\ No newline at end of file
diff --git a/apps/analog-app/src/server/components/hello.ts b/apps/analog-app/src/server/components/hello.ts
new file mode 100644
index 000000000..bb6f9f1d6
--- /dev/null
+++ b/apps/analog-app/src/server/components/hello.ts
@@ -0,0 +1,34 @@
+import { ChangeDetectionStrategy, Component, computed } from '@angular/core';
+
+import {
+ injectStaticOutputs,
+ injectStaticProps,
+} from '@analogjs/router/server';
+
+@Component({
+ selector: 'app-hello',
+ standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+ Hello From the Server
+
+ Props: {{ json() }}
+
+ Time: {{ Date.now().toString() }}
+ `,
+ styles: `
+ h3 {
+ color: blue;
+ }
+ `,
+})
+export default class HelloComponent {
+ Date = Date;
+ props = injectStaticProps();
+ outputs = injectStaticOutputs<{ loaded: boolean }>();
+ json = computed(() => JSON.stringify(this.props));
+
+ ngOnInit() {
+ this.outputs.set({ loaded: true });
+ }
+}
diff --git a/apps/analog-app/tsconfig.app.json b/apps/analog-app/tsconfig.app.json
index b3bc45583..c0b4e6bcf 100644
--- a/apps/analog-app/tsconfig.app.json
+++ b/apps/analog-app/tsconfig.app.json
@@ -10,6 +10,7 @@
"include": [
"src/**/*.d.ts",
"src/app/pages/**/*.page.ts",
+ "src/server/components/**/*.ts",
"src/server/middleware/**/*.ts"
],
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
diff --git a/apps/analog-app/vite.config.ts b/apps/analog-app/vite.config.ts
index 35eab6b95..920c32039 100644
--- a/apps/analog-app/vite.config.ts
+++ b/apps/analog-app/vite.config.ts
@@ -32,14 +32,18 @@ export default defineConfig(({ mode }) => {
additionalPagesDirs: ['/libs/shared/feature'],
additionalAPIDirs: ['/libs/shared/feature/src/api'],
prerender: {
- routes: ['/', '/cart', '/shipping'],
+ routes: ['/', '/cart', '/shipping', '/client'],
sitemap: {
host: base,
},
},
vite: {
inlineStylesExtension: 'scss',
+ experimental: {
+ supportAnalogFormat: true,
+ },
},
+ liveReload: true,
}),
nxViteTsPaths(),
visualizer() as Plugin,
diff --git a/apps/blog-app/src/main.server.ts b/apps/blog-app/src/main.server.ts
index e2ce90d90..3715f0e1e 100644
--- a/apps/blog-app/src/main.server.ts
+++ b/apps/blog-app/src/main.server.ts
@@ -1,31 +1,8 @@
import 'zone.js/node';
-import { enableProdMode } from '@angular/core';
-import { bootstrapApplication } from '@angular/platform-browser';
-import { renderApplication } from '@angular/platform-server';
-import { provideServerContext } from '@analogjs/router/server';
-import { ServerContext } from '@analogjs/router/tokens';
+import '@angular/platform-server/init';
+import { render } from '@analogjs/router/server';
import { config } from './app/app.config.server';
import { AppComponent } from './app/app.component';
-if (import.meta.env.PROD) {
- enableProdMode();
-}
-
-export function bootstrap() {
- return bootstrapApplication(AppComponent, config);
-}
-
-export default async function render(
- url: string,
- document: string,
- serverContext: ServerContext
-) {
- const html = await renderApplication(bootstrap, {
- document,
- url,
- platformProviders: [provideServerContext(serverContext)],
- });
-
- return html;
-}
+export default render(AppComponent, config);
diff --git a/apps/docs-app/docs/features/deployment/providers.md b/apps/docs-app/docs/features/deployment/providers.md
index f199c0b97..c499eedf6 100644
--- a/apps/docs-app/docs/features/deployment/providers.md
+++ b/apps/docs-app/docs/features/deployment/providers.md
@@ -5,6 +5,137 @@ import TabItem from '@theme/TabItem';
Analog supports deployment to many providers with little or no additional configuration using [Nitro](https://nitro.unjs.io) as its underlying server engine. You can find more providers in the [Nitro deployment docs](https://nitro.unjs.io/deploy).
+## Zerops
+
+:::info
+[Zerops](https://zerops.io) is the **official** deployment partner for AnalogJS.
+:::
+
+Analog supports deploying both static and server-side rendered apps to [Zerops](https://zerops.io) with a simple configuration file.
+
+> One Zerops project can contain multiple Analog projects. See example repositories for [static](https://github.com/zeropsio/recipe-analog-static) and [server-side rendered](https://github.com/zeropsio/recipe-analog-nodejs) Analog apps for a quick start.
+
+### Static (SSG) Analog app
+
+If your project is not SSG Ready, set up your project for [Static Site Generation](/docs/features/server/static-site-generation).
+
+#### 1. Create a project in Zerops
+
+Projects and services can be added either through a [Project add](https://app.zerops.io/dashboard/project-add) wizard or imported using a YAML structure:
+
+```yml
+project:
+ name: recipe-analog
+services:
+ - hostname: app
+ type: static
+```
+
+This creates a project called `recipe-analog` with a Zerops Static service called `app`.
+
+#### 2. Add zerops.yml configuration
+
+To tell Zerops how to build and run your site, add a `zerops.yml` to your repository:
+
+```yml
+zerops:
+ - setup: app
+ build:
+ base: nodejs@20
+ buildCommands:
+ - pnpm i
+ - pnpm build
+ deployFiles:
+ - public
+ - dist/analog/public/~
+ run:
+ base: static
+```
+
+#### 3. [Trigger the build & deploy pipeline](#build--deploy-your-code)
+
+### Server-side rendered (SSR) Analog app
+
+If your project is not SSR Ready, set up your project for [Server Side Rendering](/docs/features/server/server-side-rendering).
+
+#### 1. Create a project in Zerops
+
+Projects and services can be added either through a [Project add](https://app.zerops.io/dashboard/project-add) wizard or imported using a YAML structure:
+
+```yml
+project:
+ name: recipe-analog
+services:
+ - hostname: app
+ type: nodejs@20
+```
+
+This creates a project called `recipe-analog` with a Zerops Node.js service called `app`.
+
+#### 2. Add zerops.yml configuration
+
+To tell Zerops how to build and run your site, add a `zerops.yml` to your repository:
+
+```yml
+zerops:
+ - setup: app
+ build:
+ base: nodejs@20
+ buildCommands:
+ - pnpm i
+ - pnpm build
+ deployFiles:
+ - public
+ - node_modules
+ - dist
+ run:
+ base: nodejs@20
+ ports:
+ - port: 3000
+ httpSupport: true
+ start: node dist/analog/server/index.mjs
+```
+
+#### 3. [Trigger the build & deploy pipeline](#build-deploy-your-code)
+
+---
+
+### Build & deploy your code
+
+#### Trigger the pipeline by connecting the service with your GitHub / GitLab repository
+
+Your code can be deployed automatically on each commit or a new tag by connecting the service with your GitHub / GitLab repository. This connection can be set up in the service detail.
+
+#### Trigger the pipeline using Zerops CLI (zcli)
+
+You can also trigger the pipeline manually from your terminal or your existing CI/CD by using Zerops CLI.
+
+1. Install the Zerops CLI.
+
+```bash
+# To download the zcli binary directly,
+# use https://github.com/zeropsio/zcli/releases
+npm i -g @zerops/zcli
+```
+
+2. Open [Settings > Access Token Management](https://app.zerops.io/settings/token-management) in the Zerops app and generate a new access token.
+
+3. Log in using your access token with the following command:
+
+```bash
+zcli login
+```
+
+4. Navigate to the root of your app (where `zerops.yml` is located) and run the following command to trigger the deploy:
+
+```bash
+zcli push
+```
+
+#### Trigger the pipeline using GitHub / Gitlab
+
+You can also check out [Github Integration](https://docs.zerops.io/references/github-integration) / [Gitlab Integration](https://docs.zerops.io/references/gitlab-integration) in [Zerops Docs](https://docs.zerops.io/) for git integration.
+
## Netlify
Analog supports deploying on [Netlify](https://netlify.com/) with minimal configuration.
@@ -161,9 +292,17 @@ BUILD_PRESET=cloudflare-pages npm run build
npx wrangler pages dev ./dist/analog/public
```
-## Firebase
+## Firebase App Hosting
+
+Analog supports [Firebase App Hosting](https://firebase.google.com/docs/app-hosting) with no additional configuration out of the box.
+
+**Note**: You need to be on the **Blaze plan** to deploy Analog applications with Firebase App Hosting.
-Analog supports [Firebase Hosting](https://firebase.google.com/docs/hosting) with Cloud Functions out of the box.
+Follow the [Getting Started instructions](https://firebase.google.com/docs/app-hosting/get-started#step-1:) to connect your GitHub repository to Firebase App Hosting.
+
+## Firebase Hosting
+
+Analog supports [Firebase Hosting](https://firebase.google.com/docs/hosting) with Cloud Functions and [Firebase App Hosting](https://firebase.google.com/docs/app-hosting) out of the box.
See a [Sample Repo](https://github.com/brandonroberts/analog-angular-firebase-example) with Firebase configured
@@ -462,132 +601,3 @@ jobs:
echo "DRY_RUN_OPTION=$DRY_RUN_OPTION"
npx angular-cli-ghpages --no-silent --dir="${{env.TARGET_DIR}}" $CNAME_OPTION $DRY_RUN_OPTION
```
-
-## Zerops
-
-Analog supports deploying both static and server-side rendered apps to [Zerops](https://zerops.io) with a simple configuration file.
-
-:::info
-One Zerops project can contain multiple Analog projects. See example repositories for [static](https://github.com/zeropsio/recipe-analog-static) and [server-side rendered](https://github.com/zeropsio/recipe-analog-nodejs) Analog apps for a quick start.
-:::
-
-### Static (SSG) Analog app
-
-If your project is not SSG Ready, set up your project for [Static Site Generation](/docs/features/server/static-site-generation).
-
-#### 1. Create a project in Zerops
-
-Projects and services can be added either through a [Project add](https://app.zerops.io/dashboard/project-add) wizard or imported using a YAML structure:
-
-```yml
-project:
- name: recipe-analog
-services:
- - hostname: app
- type: static
-```
-
-This creates a project called `recipe-analog` with a Zerops Static service called `app`.
-
-#### 2. Add zerops.yml configuration
-
-To tell Zerops how to build and run your site, add a `zerops.yml` to your repository:
-
-```yml
-zerops:
- - setup: app
- build:
- base: nodejs@20
- buildCommands:
- - pnpm i
- - pnpm build
- deployFiles:
- - public
- - dist/analog/public/~
- run:
- base: static
-```
-
-#### 3. [Trigger the build & deploy pipeline](#build--deploy-your-code)
-
-### Server-side rendered (SSR) Analog app
-
-If your project is not SSR Ready, set up your project for [Server Side Rendering](/docs/features/server/server-side-rendering).
-
-#### 1. Create a project in Zerops
-
-Projects and services can be added either through a [Project add](https://app.zerops.io/dashboard/project-add) wizard or imported using a YAML structure:
-
-```yml
-project:
- name: recipe-analog
-services:
- - hostname: app
- type: nodejs@20
-```
-
-This creates a project called `recipe-analog` with a Zerops Node.js service called `app`.
-
-#### 2. Add zerops.yml configuration
-
-To tell Zerops how to build and run your site, add a `zerops.yml` to your repository:
-
-```yml
-zerops:
- - setup: app
- build:
- base: nodejs@20
- buildCommands:
- - pnpm i
- - pnpm build
- deployFiles:
- - public
- - node_modules
- - dist
- run:
- base: nodejs@20
- ports:
- - port: 3000
- httpSupport: true
- start: node dist/analog/server/index.mjs
-```
-
-#### 3. [Trigger the build & deploy pipeline](#build-deploy-your-code)
-
----
-
-### Build & deploy your code
-
-#### Trigger the pipeline by connecting the service with your GitHub / GitLab repository
-
-Your code can be deployed automatically on each commit or a new tag by connecting the service with your GitHub / GitLab repository. This connection can be set up in the service detail.
-
-#### Trigger the pipeline using Zerops CLI (zcli)
-
-You can also trigger the pipeline manually from your terminal or your existing CI/CD by using Zerops CLI.
-
-1. Install the Zerops CLI.
-
-```bash
-# To download the zcli binary directly,
-# use https://github.com/zeropsio/zcli/releases
-npm i -g @zerops/zcli
-```
-
-2. Open [Settings > Access Token Management](https://app.zerops.io/settings/token-management) in the Zerops app and generate a new access token.
-
-3. Log in using your access token with the following command:
-
-```bash
-zcli login
-```
-
-4. Navigate to the root of your app (where `zerops.yml` is located) and run the following command to trigger the deploy:
-
-```bash
-zcli push
-```
-
-#### Trigger the pipeline using Github / Gitlab
-
-You can also check out [Github Integration](https://docs.zerops.io/references/github-integration) / [Gitlab Integration](https://docs.zerops.io/references/gitlab-integration) in [Zerops Docs](https://docs.zerops.io/) for git integration.
diff --git a/apps/ng-app/vite.config.ts b/apps/ng-app/vite.config.ts
index 1bfd46716..4c6292004 100644
--- a/apps/ng-app/vite.config.ts
+++ b/apps/ng-app/vite.config.ts
@@ -20,6 +20,7 @@ export default defineConfig(({ mode }) => ({
analog({
ssr: false,
static: true,
+ liveReload: true,
vite: {
experimental: {
supportAnalogFormat: true,
diff --git a/apps/trpc-app/src/main.server.ts b/apps/trpc-app/src/main.server.ts
index 29ad24c06..4db4761f6 100644
--- a/apps/trpc-app/src/main.server.ts
+++ b/apps/trpc-app/src/main.server.ts
@@ -1,20 +1,8 @@
import 'zone.js/node';
-import { enableProdMode } from '@angular/core';
-import { renderApplication } from '@angular/platform-server';
-import { AppComponent } from './app/app.component';
-import { bootstrapApplication } from '@angular/platform-browser';
-import { config } from './app.config.server';
-
-if (import.meta.env.PROD) {
- enableProdMode();
-}
+import '@angular/platform-server/init';
+import { render } from '@analogjs/router/server';
-const bootstrap = () => bootstrapApplication(AppComponent, config);
+import { config } from './app.config.server';
+import { AppComponent } from './app/app.component';
-export default async function render(url: string, document: string) {
- const html = await renderApplication(bootstrap, {
- document,
- url,
- });
- return html;
-}
+export default render(AppComponent, config);
diff --git a/package.json b/package.json
index 1f0daf544..fc4530aa7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "analogjs-platform",
- "version": "1.10.3",
+ "version": "1.11.0-beta.6",
"license": "MIT",
"type": "module",
"scripts": {
diff --git a/packages/astro-angular/package.json b/packages/astro-angular/package.json
index 96a395ee0..05920d1cf 100644
--- a/packages/astro-angular/package.json
+++ b/packages/astro-angular/package.json
@@ -1,6 +1,6 @@
{
"name": "@analogjs/astro-angular",
- "version": "1.10.3",
+ "version": "1.11.0-beta.6",
"description": "Use Angular components within Astro",
"type": "module",
"author": "Brandon Roberts ",
@@ -32,7 +32,7 @@
"url": "https://github.com/sponsors/brandonroberts"
},
"dependencies": {
- "@analogjs/vite-plugin-angular": "^1.10.3"
+ "@analogjs/vite-plugin-angular": "^1.11.0-beta.6"
},
"peerDependencies": {
"@angular-devkit/build-angular": ">=16.0.0",
diff --git a/packages/content/package.json b/packages/content/package.json
index 1ae10f49c..4cc6b8bb7 100644
--- a/packages/content/package.json
+++ b/packages/content/package.json
@@ -1,6 +1,6 @@
{
"name": "@analogjs/content",
- "version": "1.10.3",
+ "version": "1.11.0-beta.6",
"description": "Content Rendering for Analog",
"type": "module",
"author": "Brandon Roberts ",
diff --git a/packages/create-analog/package.json b/packages/create-analog/package.json
index 52e144afb..910f2f1b7 100644
--- a/packages/create-analog/package.json
+++ b/packages/create-analog/package.json
@@ -1,6 +1,6 @@
{
"name": "create-analog",
- "version": "1.10.3",
+ "version": "1.11.0-beta.6",
"type": "module",
"license": "MIT",
"author": "Brandon Roberts",
diff --git a/packages/create-analog/template-angular-v16/package.json b/packages/create-analog/template-angular-v16/package.json
index 01728b623..6b128b7b3 100644
--- a/packages/create-analog/template-angular-v16/package.json
+++ b/packages/create-analog/template-angular-v16/package.json
@@ -15,8 +15,8 @@
"test": "ng test"
},
"dependencies": {
- "@analogjs/content": "^1.10.3",
- "@analogjs/router": "^1.10.3",
+ "@analogjs/content": "^1.11.0-beta.6",
+ "@analogjs/router": "^1.11.0-beta.6",
"@angular/animations": "^16.2.0",
"@angular/common": "^16.2.0",
"@angular/compiler": "^16.2.0",
@@ -38,9 +38,9 @@
"zone.js": "~0.13.0"
},
"devDependencies": {
- "@analogjs/platform": "^1.10.3",
- "@analogjs/vite-plugin-angular": "^1.10.3",
- "@analogjs/vitest-angular": "^1.10.3",
+ "@analogjs/platform": "^1.11.0-beta.6",
+ "@analogjs/vite-plugin-angular": "^1.11.0-beta.6",
+ "@analogjs/vitest-angular": "^1.11.0-beta.6",
"@angular-devkit/build-angular": "^16.2.0",
"@angular/cli": "^16.2.0",
"@angular/compiler-cli": "^16.2.0",
diff --git a/packages/create-analog/template-angular-v17/package.json b/packages/create-analog/template-angular-v17/package.json
index 699877393..a2a53a459 100644
--- a/packages/create-analog/template-angular-v17/package.json
+++ b/packages/create-analog/template-angular-v17/package.json
@@ -15,8 +15,8 @@
"test": "ng test"
},
"dependencies": {
- "@analogjs/content": "^1.10.3",
- "@analogjs/router": "^1.10.3",
+ "@analogjs/content": "^1.11.0-beta.6",
+ "@analogjs/router": "^1.11.0-beta.6",
"@angular/animations": "^17.2.0",
"@angular/common": "^17.2.0",
"@angular/compiler": "^17.2.0",
@@ -38,9 +38,9 @@
"zone.js": "~0.14.0"
},
"devDependencies": {
- "@analogjs/platform": "^1.10.3",
- "@analogjs/vite-plugin-angular": "^1.10.3",
- "@analogjs/vitest-angular": "^1.10.3",
+ "@analogjs/platform": "^1.11.0-beta.6",
+ "@analogjs/vite-plugin-angular": "^1.11.0-beta.6",
+ "@analogjs/vitest-angular": "^1.11.0-beta.6",
"@angular-devkit/build-angular": "^17.2.0",
"@angular/cli": "^17.2.0",
"@angular/compiler-cli": "^17.2.0",
diff --git a/packages/create-analog/template-angular-v18/package.json b/packages/create-analog/template-angular-v18/package.json
index 2aed2b40c..b2bd10ca7 100644
--- a/packages/create-analog/template-angular-v18/package.json
+++ b/packages/create-analog/template-angular-v18/package.json
@@ -15,8 +15,8 @@
},
"private": true,
"dependencies": {
- "@analogjs/content": "^1.10.3",
- "@analogjs/router": "^1.10.3",
+ "@analogjs/content": "^1.11.0-beta.6",
+ "@analogjs/router": "^1.11.0-beta.6",
"@angular/animations": "^18.0.0",
"@angular/build": "^18.0.0",
"@angular/common": "^18.0.0",
@@ -38,9 +38,9 @@
"zone.js": "~0.14.3"
},
"devDependencies": {
- "@analogjs/platform": "^1.10.3",
- "@analogjs/vite-plugin-angular": "^1.10.3",
- "@analogjs/vitest-angular": "^1.10.3",
+ "@analogjs/platform": "^1.11.0-beta.6",
+ "@analogjs/vite-plugin-angular": "^1.11.0-beta.6",
+ "@analogjs/vitest-angular": "^1.11.0-beta.6",
"@angular/cli": "^18.0.0",
"@angular/compiler-cli": "^18.0.0",
"jsdom": "^22.0.0",
diff --git a/packages/create-analog/template-blog/package.json b/packages/create-analog/template-blog/package.json
index e05c5bf9a..42d5b440d 100644
--- a/packages/create-analog/template-blog/package.json
+++ b/packages/create-analog/template-blog/package.json
@@ -15,8 +15,8 @@
},
"private": true,
"dependencies": {
- "@analogjs/content": "^1.10.3",
- "@analogjs/router": "^1.10.3",
+ "@analogjs/content": "^1.11.0-beta.6",
+ "@analogjs/router": "^1.11.0-beta.6",
"@angular/animations": "^19.0.0",
"@angular/common": "^19.0.0",
"@angular/compiler": "^19.0.0",
@@ -36,9 +36,9 @@
"zone.js": "~0.15.0"
},
"devDependencies": {
- "@analogjs/platform": "^1.10.3",
- "@analogjs/vite-plugin-angular": "^1.10.3",
- "@analogjs/vitest-angular": "^1.10.3",
+ "@analogjs/platform": "^1.11.0-beta.6",
+ "@analogjs/vite-plugin-angular": "^1.11.0-beta.6",
+ "@analogjs/vitest-angular": "^1.11.0-beta.6",
"@angular-devkit/build-angular": "^19.0.0",
"@angular/build": "^19.0.0",
"@angular/cli": "^19.0.0",
diff --git a/packages/create-analog/template-blog/src/main.server.ts b/packages/create-analog/template-blog/src/main.server.ts
index f365e2b63..d3f23684e 100644
--- a/packages/create-analog/template-blog/src/main.server.ts
+++ b/packages/create-analog/template-blog/src/main.server.ts
@@ -1,32 +1,8 @@
import 'zone.js/node';
import '@angular/platform-server/init';
-import { enableProdMode } from '@angular/core';
-import { bootstrapApplication } from '@angular/platform-browser';
-import { renderApplication } from '@angular/platform-server';
-import { provideServerContext } from '@analogjs/router/server';
-import { ServerContext } from '@analogjs/router/tokens';
+import { render } from '@analogjs/router/server';
__APP_COMPONENT_IMPORT__
import { config } from './app/app.config.server';
-if (import.meta.env.PROD) {
- enableProdMode();
-}
-
-export function bootstrap() {
- return bootstrapApplication(__APP_COMPONENT__, config);
-}
-
-export default async function render(
- url: string,
- document: string,
- serverContext: ServerContext
-) {
- const html = await renderApplication(bootstrap, {
- document,
- url,
- platformProviders: [provideServerContext(serverContext)],
- });
-
- return html;
-}
+export default render(__APP_COMPONENT__, config);
diff --git a/packages/create-analog/template-latest/package.json b/packages/create-analog/template-latest/package.json
index 8420656a6..1f25e68e4 100644
--- a/packages/create-analog/template-latest/package.json
+++ b/packages/create-analog/template-latest/package.json
@@ -15,8 +15,8 @@
},
"private": true,
"dependencies": {
- "@analogjs/content": "^1.10.3",
- "@analogjs/router": "^1.10.3",
+ "@analogjs/content": "^1.11.0-beta.6",
+ "@analogjs/router": "^1.11.0-beta.6",
"@angular/animations": "^19.0.0",
"@angular/common": "^19.0.0",
"@angular/compiler": "^19.0.0",
@@ -37,9 +37,9 @@
"zone.js": "~0.15.0"
},
"devDependencies": {
- "@analogjs/platform": "^1.10.3",
- "@analogjs/vite-plugin-angular": "^1.10.3",
- "@analogjs/vitest-angular": "^1.10.3",
+ "@analogjs/platform": "^1.11.0-beta.6",
+ "@analogjs/vite-plugin-angular": "^1.11.0-beta.6",
+ "@analogjs/vitest-angular": "^1.11.0-beta.6",
"@angular-devkit/build-angular": "^19.0.0",
"@angular/build": "^19.0.0",
"@angular/cli": "^19.0.0",
diff --git a/packages/create-analog/template-latest/src/main.server.ts b/packages/create-analog/template-latest/src/main.server.ts
index f365e2b63..d3f23684e 100644
--- a/packages/create-analog/template-latest/src/main.server.ts
+++ b/packages/create-analog/template-latest/src/main.server.ts
@@ -1,32 +1,8 @@
import 'zone.js/node';
import '@angular/platform-server/init';
-import { enableProdMode } from '@angular/core';
-import { bootstrapApplication } from '@angular/platform-browser';
-import { renderApplication } from '@angular/platform-server';
-import { provideServerContext } from '@analogjs/router/server';
-import { ServerContext } from '@analogjs/router/tokens';
+import { render } from '@analogjs/router/server';
__APP_COMPONENT_IMPORT__
import { config } from './app/app.config.server';
-if (import.meta.env.PROD) {
- enableProdMode();
-}
-
-export function bootstrap() {
- return bootstrapApplication(__APP_COMPONENT__, config);
-}
-
-export default async function render(
- url: string,
- document: string,
- serverContext: ServerContext
-) {
- const html = await renderApplication(bootstrap, {
- document,
- url,
- platformProviders: [provideServerContext(serverContext)],
- });
-
- return html;
-}
+export default render(__APP_COMPONENT__, config);
diff --git a/packages/create-analog/template-minimal/package.json b/packages/create-analog/template-minimal/package.json
index 8420656a6..1f25e68e4 100644
--- a/packages/create-analog/template-minimal/package.json
+++ b/packages/create-analog/template-minimal/package.json
@@ -15,8 +15,8 @@
},
"private": true,
"dependencies": {
- "@analogjs/content": "^1.10.3",
- "@analogjs/router": "^1.10.3",
+ "@analogjs/content": "^1.11.0-beta.6",
+ "@analogjs/router": "^1.11.0-beta.6",
"@angular/animations": "^19.0.0",
"@angular/common": "^19.0.0",
"@angular/compiler": "^19.0.0",
@@ -37,9 +37,9 @@
"zone.js": "~0.15.0"
},
"devDependencies": {
- "@analogjs/platform": "^1.10.3",
- "@analogjs/vite-plugin-angular": "^1.10.3",
- "@analogjs/vitest-angular": "^1.10.3",
+ "@analogjs/platform": "^1.11.0-beta.6",
+ "@analogjs/vite-plugin-angular": "^1.11.0-beta.6",
+ "@analogjs/vitest-angular": "^1.11.0-beta.6",
"@angular-devkit/build-angular": "^19.0.0",
"@angular/build": "^19.0.0",
"@angular/cli": "^19.0.0",
diff --git a/packages/create-analog/template-minimal/src/main.server.ts b/packages/create-analog/template-minimal/src/main.server.ts
index f365e2b63..d3f23684e 100644
--- a/packages/create-analog/template-minimal/src/main.server.ts
+++ b/packages/create-analog/template-minimal/src/main.server.ts
@@ -1,32 +1,8 @@
import 'zone.js/node';
import '@angular/platform-server/init';
-import { enableProdMode } from '@angular/core';
-import { bootstrapApplication } from '@angular/platform-browser';
-import { renderApplication } from '@angular/platform-server';
-import { provideServerContext } from '@analogjs/router/server';
-import { ServerContext } from '@analogjs/router/tokens';
+import { render } from '@analogjs/router/server';
__APP_COMPONENT_IMPORT__
import { config } from './app/app.config.server';
-if (import.meta.env.PROD) {
- enableProdMode();
-}
-
-export function bootstrap() {
- return bootstrapApplication(__APP_COMPONENT__, config);
-}
-
-export default async function render(
- url: string,
- document: string,
- serverContext: ServerContext
-) {
- const html = await renderApplication(bootstrap, {
- document,
- url,
- platformProviders: [provideServerContext(serverContext)],
- });
-
- return html;
-}
+export default render(__APP_COMPONENT__, config);
diff --git a/packages/nx-plugin/src/generators/app/files/template-angular-v18/src/main.server.ts__template__ b/packages/nx-plugin/src/generators/app/files/template-angular-v18/src/main.server.ts__template__
index 2b6d4d14b..3715f0e1e 100644
--- a/packages/nx-plugin/src/generators/app/files/template-angular-v18/src/main.server.ts__template__
+++ b/packages/nx-plugin/src/generators/app/files/template-angular-v18/src/main.server.ts__template__
@@ -1,32 +1,8 @@
import 'zone.js/node';
import '@angular/platform-server/init';
-import { enableProdMode } from '@angular/core';
-import { bootstrapApplication } from '@angular/platform-browser';
-import { renderApplication } from '@angular/platform-server';
-import { provideServerContext } from '@analogjs/router/server';
-import { ServerContext } from '@analogjs/router/tokens';
+import { render } from '@analogjs/router/server';
import { config } from './app/app.config.server';
import { AppComponent } from './app/app.component';
-if (import.meta.env.PROD) {
- enableProdMode();
-}
-
-export function bootstrap() {
- return bootstrapApplication(AppComponent, config);
-}
-
-export default async function render(
- url: string,
- document: string,
- serverContext: ServerContext
-) {
- const html = await renderApplication(bootstrap, {
- document,
- url,
- platformProviders: [provideServerContext(serverContext)],
- });
-
- return html;
-}
+export default render(AppComponent, config);
diff --git a/packages/platform/package.json b/packages/platform/package.json
index cca3a04ab..bf785c0ed 100644
--- a/packages/platform/package.json
+++ b/packages/platform/package.json
@@ -1,6 +1,6 @@
{
"name": "@analogjs/platform",
- "version": "1.10.3",
+ "version": "1.11.0-beta.6",
"description": "The fullstack meta-framework for Angular",
"type": "module",
"author": "Brandon Roberts ",
@@ -29,8 +29,8 @@
},
"dependencies": {
"nitropack": "^2.10.0",
- "@analogjs/vite-plugin-angular": "^1.10.3",
- "@analogjs/vite-plugin-nitro": "^1.10.3",
+ "@analogjs/vite-plugin-angular": "^1.11.0-beta.6",
+ "@analogjs/vite-plugin-nitro": "^1.11.0-beta.6",
"vitefu": "^0.2.5"
},
"peerDependencies": {
diff --git a/packages/platform/src/lib/deps-plugin.ts b/packages/platform/src/lib/deps-plugin.ts
index cea9f4a9d..1d913f110 100644
--- a/packages/platform/src/lib/deps-plugin.ts
+++ b/packages/platform/src/lib/deps-plugin.ts
@@ -69,7 +69,6 @@ export function depsPlugin(options?: Options): Plugin[] {
return pkgJson['module'] && pkgJson['module'].includes('fesm');
},
});
-
return pkgConfig;
},
},
diff --git a/packages/platform/src/lib/options.ts b/packages/platform/src/lib/options.ts
index 129909309..c1ce56665 100644
--- a/packages/platform/src/lib/options.ts
+++ b/packages/platform/src/lib/options.ts
@@ -37,6 +37,12 @@ export interface Options {
index?: string;
workspaceRoot?: string;
content?: ContentPluginOptions;
+
+ /**
+ * Enables Angular's HMR during development
+ */
+ liveReload?: boolean;
+
/**
* Additional page paths to include
*/
diff --git a/packages/platform/src/lib/platform-plugin.ts b/packages/platform/src/lib/platform-plugin.ts
index 5417f3cd6..e5130c954 100644
--- a/packages/platform/src/lib/platform-plugin.ts
+++ b/packages/platform/src/lib/platform-plugin.ts
@@ -36,6 +36,7 @@ export function platformPlugin(opts: Options = {}): Plugin[] {
),
],
additionalContentDirs: platformOptions.additionalContentDirs,
+ liveReload: platformOptions.liveReload,
...(opts?.vite ?? {}),
}),
serverModePlugin(),
diff --git a/packages/platform/src/lib/router-plugin.ts b/packages/platform/src/lib/router-plugin.ts
index fbcd52d29..c31c291c2 100644
--- a/packages/platform/src/lib/router-plugin.ts
+++ b/packages/platform/src/lib/router-plugin.ts
@@ -87,9 +87,9 @@ export function routerPlugin(options?: Options): Plugin[] {
);
let result = code.replace(
- 'let ANALOG_ROUTE_FILES = {};',
+ 'ANALOG_ROUTE_FILES = {};',
`
- let ANALOG_ROUTE_FILES = {${routeFiles.map(
+ ANALOG_ROUTE_FILES = {${routeFiles.map(
(module) =>
`"${module.replace(root, '')}": () => import('${module}')`
)}};
@@ -97,9 +97,9 @@ export function routerPlugin(options?: Options): Plugin[] {
);
result = result.replace(
- 'let ANALOG_CONTENT_ROUTE_FILES = {};',
+ 'ANALOG_CONTENT_ROUTE_FILES = {};',
`
- let ANALOG_CONTENT_ROUTE_FILES = {${contentRouteFiles.map(
+ ANALOG_CONTENT_ROUTE_FILES = {${contentRouteFiles.map(
(module) =>
`"${module.replace(
root,
@@ -133,9 +133,9 @@ export function routerPlugin(options?: Options): Plugin[] {
);
const result = code.replace(
- 'let ANALOG_PAGE_ENDPOINTS = {};',
+ 'ANALOG_PAGE_ENDPOINTS = {};',
`
- let ANALOG_PAGE_ENDPOINTS = {${endpointFiles.map(
+ ANALOG_PAGE_ENDPOINTS = {${endpointFiles.map(
(module) =>
`"${module.replace(root, '')}": () => import('${module}')`
)}};
diff --git a/packages/router/package.json b/packages/router/package.json
index f33095b71..bfb5fd11a 100644
--- a/packages/router/package.json
+++ b/packages/router/package.json
@@ -1,6 +1,6 @@
{
"name": "@analogjs/router",
- "version": "1.10.3",
+ "version": "1.11.0-beta.6",
"description": "Filesystem-based routing for Angular",
"type": "module",
"author": "Brandon Roberts ",
@@ -24,7 +24,7 @@
"url": "https://github.com/sponsors/brandonroberts"
},
"peerDependencies": {
- "@analogjs/content": "^1.10.3",
+ "@analogjs/content": "^1.11.0-beta.6",
"@angular/core": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"@angular/router": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
diff --git a/packages/router/server/src/index.ts b/packages/router/server/src/index.ts
index c875fc0a3..0eb71ead4 100644
--- a/packages/router/server/src/index.ts
+++ b/packages/router/server/src/index.ts
@@ -1 +1,7 @@
export { provideServerContext } from './provide-server-context';
+export { injectStaticProps, injectStaticOutputs } from './tokens';
+export {
+ serverComponentRequest,
+ renderServerComponent,
+} from './server-component-render';
+export { render } from './render';
diff --git a/packages/router/server/src/render.ts b/packages/router/server/src/render.ts
new file mode 100644
index 000000000..d051196b2
--- /dev/null
+++ b/packages/router/server/src/render.ts
@@ -0,0 +1,59 @@
+import {
+ ApplicationConfig,
+ Provider,
+ Type,
+ enableProdMode,
+} from '@angular/core';
+import { bootstrapApplication } from '@angular/platform-browser';
+import { renderApplication } from '@angular/platform-server';
+import type { ServerContext } from '@analogjs/router/tokens';
+
+import { provideServerContext } from './provide-server-context';
+import {
+ serverComponentRequest,
+ renderServerComponent,
+} from './server-component-render';
+
+if (import.meta.env.PROD) {
+ enableProdMode();
+}
+
+/**
+ * Returns a function that accepts the navigation URL,
+ * the root HTML, and server context.
+ *
+ * @param rootComponent
+ * @param config
+ * @param platformProviders
+ * @returns Promise
+ */
+export function render(
+ rootComponent: Type,
+ config: ApplicationConfig,
+ platformProviders: Provider[] = []
+) {
+ function bootstrap() {
+ return bootstrapApplication(rootComponent, config);
+ }
+
+ return async function render(
+ url: string,
+ document: string,
+ serverContext: ServerContext
+ ) {
+ if (serverComponentRequest(serverContext)) {
+ return await renderServerComponent(url, serverContext);
+ }
+
+ const html = await renderApplication(bootstrap, {
+ document,
+ url,
+ platformProviders: [
+ provideServerContext(serverContext),
+ platformProviders,
+ ],
+ });
+
+ return html;
+ };
+}
diff --git a/packages/router/server/src/server-component-render.ts b/packages/router/server/src/server-component-render.ts
new file mode 100644
index 000000000..94016f034
--- /dev/null
+++ b/packages/router/server/src/server-component-render.ts
@@ -0,0 +1,167 @@
+import { ApplicationConfig, Type } from '@angular/core';
+import { bootstrapApplication } from '@angular/platform-browser';
+import {
+ reflectComponentType,
+ ɵConsole as Console,
+ APP_ID,
+} from '@angular/core';
+import {
+ provideServerRendering,
+ renderApplication,
+ ɵSERVER_CONTEXT as SERVER_CONTEXT,
+} from '@angular/platform-server';
+import { ServerContext } from '@analogjs/router/tokens';
+import { createEvent, readBody, getHeader } from 'h3';
+
+import { provideStaticProps } from './tokens';
+
+type ComponentLoader = () => Promise>;
+
+export function serverComponentRequest(serverContext: ServerContext) {
+ const serverComponentId = getHeader(
+ createEvent(serverContext.req, serverContext.res),
+ 'X-Analog-Component'
+ );
+
+ if (
+ !serverComponentId &&
+ serverContext.req.url &&
+ serverContext.req.url.startsWith('/_analog/components')
+ ) {
+ const componentId = serverContext.req.url.split('/')?.[3];
+
+ return componentId;
+ }
+
+ return serverComponentId;
+}
+
+const components = import.meta.glob([
+ '/src/server/components/**/*.{ts,analog,ag}',
+]);
+
+export async function renderServerComponent(
+ url: string,
+ serverContext: ServerContext,
+ config?: ApplicationConfig
+) {
+ const componentReqId = serverComponentRequest(serverContext) as string;
+ const { componentLoader, componentId } = getComponentLoader(componentReqId);
+
+ if (!componentLoader) {
+ return new Response(`Server Component Not Found ${componentId}`, {
+ status: 404,
+ });
+ }
+
+ const component = ((await componentLoader()) as any)[
+ 'default'
+ ] as Type;
+
+ if (!component) {
+ return new Response(`No default export for ${componentId}`, {
+ status: 422,
+ });
+ }
+
+ const mirror = reflectComponentType(component);
+ const selector = mirror?.selector.split(',')?.[0] || 'server-component';
+ const event = createEvent(serverContext.req, serverContext.res);
+ const body = (await readBody(event)) || {};
+ const appId = `analog-server-${selector.toLowerCase()}-${new Date().getTime()}`;
+
+ const bootstrap = () =>
+ bootstrapApplication(component, {
+ providers: [
+ provideServerRendering(),
+ provideStaticProps(body),
+ { provide: SERVER_CONTEXT, useValue: 'analog-server-component' },
+ {
+ provide: APP_ID,
+ useFactory() {
+ return appId;
+ },
+ },
+ ...(config?.providers || []),
+ ],
+ });
+
+ const html = await renderApplication(bootstrap, {
+ url,
+ document: `<${selector}>${selector}>`,
+ platformProviders: [
+ {
+ provide: Console,
+ useFactory() {
+ return {
+ warn: () => {},
+ log: () => {},
+ };
+ },
+ },
+ ],
+ });
+
+ const outputs = retrieveTransferredState(html, appId);
+ const responseData: { html: string; outputs: Record } = {
+ html,
+ outputs,
+ };
+
+ return new Response(JSON.stringify(responseData), {
+ headers: {
+ 'X-Analog-Component': 'true',
+ },
+ });
+}
+
+function getComponentLoader(componentReqId: string): {
+ componentLoader: ComponentLoader | undefined;
+ componentId: string;
+} {
+ let _componentId = `/src/server/components/${componentReqId.toLowerCase()}`;
+ let componentLoader: ComponentLoader | undefined = undefined;
+ let componentId = _componentId;
+
+ if (components[`${_componentId}.ts`]) {
+ componentId = `${_componentId}.ts`;
+ componentLoader = components[componentId] as ComponentLoader;
+ } else if (components[`${componentId}.analog`]) {
+ componentId = `${_componentId}.analog`;
+ componentLoader = components[componentId] as ComponentLoader;
+ } else if (components[`${componentId}.ag`]) {
+ componentId = `${_componentId}.ag`;
+ componentLoader = components[componentId] as ComponentLoader;
+ }
+
+ return { componentLoader, componentId };
+}
+
+function retrieveTransferredState(
+ html: string,
+ appId: string
+): Record {
+ const regex = new RegExp(
+ `