This project was generated with Angular CLI version 7.1.1.
This is an initial test of server side rendering on top of Angular Ivy.
The base Ivy client project was generated by
ng new ivy --experimental-ivy
Build and run the Universal application
(To get the exactly pinned version in package-lock.json)
Running in prod mode (to see the actual JS payload sizes)
npm run start
Navigate to http://localhost:4200
Running in dev mode (for development)
npm run dev
Navigate to http://localhost:4200
The page will live reload when code is changed.
The example illustrates how Angular Ivy makes the following possible
- "Automatic" code splitting at component and route level
- Server-side Rendering(SSR) with Angular Ivy
- Selective client side rehydration based on user events / data binding changes
- Configurable load and prefetch strategies
- Data fetching and state transfer
- Server streaming?
All the things happening server side are at https://github.com/vikerman/ivy-universal/tree/master/server All the user code are in https://github.com/vikerman/ivy-universal/tree/master/src/ except for https://github.com/vikerman/ivy-universal/tree/master/src/lib which contains all the supporting code which would eventually be part of some NPM node module.
The project itself relies on nightly Ivy builds with no modifications.
(Though main.ts, main.server.ts and routes.ts are currently hand written, they would eventually be automatically generated by Angular CLI using Angular architect APIs by parsing the decorator metadata for each component. This would be the part that would make the code splitting and progressive bootstrapping "automatic".)
What's happening and how does it work?
The webpack magic comments in https://github.com/vikerman/ivy-universal/blob/master/src/main.ts ensures that each Angular Component in the components/ and pages/ folder gets a separate chunk. The default Angular CLI webpack settings ensures that common chunks are automatcially created for any code shared between these components.
On the server the page for the current route gets rendered(more on this later). The rendered HTML is sent to the client. On the client the only code that runs is a bootstrapping code. This code does the following:
- Setup a global event handler on the document in order to buffer events.
- Registers a shell Custom Element for every known component in the system. This does not load the actual component code. It just uses the component metadata to watch for property binding changes.
At this point no Angular specific code or component code has been loaded. (Current bootstrap size without polyfills: ~12 Kb)
When a user initiates an event (say click a button) - The global event handler walks up the DOM tree from the event target till it find what appears to be a component root. It then uses webpack loader to load the chunk corresponding to the component.
The component on bootstrapping rehydrates the DOM node sent by the server using the rehydration renderer. This lets the component become active without having to load the code for its child components because as of now they weren't required to be re-rendered.
Let's say the currently active component changes its state based on the click handler and that resulted in chaging the data binding for the child - The shell Custom Element setup for the child component recognizes that its input properties have changed. In reaction to this the child Custom Element fetches the chunk coresponding to its component. The child component in turn boots up by rehydrating on top its server generated nodes and the process continues.
This way we can have a progressive bootstrapping strategy that loads components only on demand.
This is just an illustration of one possible strategy and how rehydration of SSR nodes is useful - but with the current organization of components as individually loadable units we can now tune the load strategy that makes sense for the application. In the future configurable prefetch and load strategies will be added to the project.