Problem
When I am trying to render an Angular module with PlatformRef::bootstrapModule(...) I get an error:
ReferenceError: document is not defined
Obviously, Angular is attempting to use document in Node.js environment where it's not present (since it's not a browser environment).
Question
How do I work around the issue?
Code
Core Code
@Injectable()
export class MyDomRendererFactory2 implements RendererFactory2 {
  private renderer: MyRenderer;
  constructor(eventManager: EventManager) {
    this.renderer = new MyRenderer(eventManager);
  }
  createRenderer(hostElement: any, type: RendererType2): Renderer2 {
    return this.renderer;
  }
  begin?(): void { throw new Error("Method not implemented."); }
  end?(): void { throw new Error("Method not implemented."); }
  whenRenderingDone?(): Promise<any> { throw new Error("Method not implemented."); }
}
async function generate<M>(moduleType: Type<M>) {
  try {
    const extraProviders: StaticProvider[] = [
      { provide: Compiler, useFactory: compilerFactory => compilerFactory.createCompiler(), deps: [CompilerFactory] },
    ];
    const platformRef = platformDynamicServer(extraProviders);
    const moduleRef = await platformRef
      .bootstrapModule(
        moduleType,
        {
          providers: [
            { provide: ResourceLoader, useValue: new ResourceLoaderImpl(`src\\app`), deps: [ ] },
            {
              provide: RendererFactory2,
              useClass: MyDomRendererFactory2,
              deps: [EventManager],
            },
          ],
        });
    const appComponent = moduleRef.injector.get(AppComponent);
    console.info(appComponent.title.toString());
  } catch (error) {
    throw new Error(error.toString());
  }
}
Custom Renderer
At this point, it does nothing, basically.
@Injectable()
export class MyRenderer implements Renderer2 {
  data: { [key: string]: any; };
  destroy(): void { }
  createElement(name: string, namespace?: string) { }
  createComment(value: string) { }
  createText(value: string) { }
  destroyNode: (node: any) => void;
  appendChild(parent: any, newChild: any): void { }
  insertBefore(parent: any, newChild: any, refChild: any): void { }
  removeChild(parent: any, oldChild: any): void { }
  selectRootElement(selectorOrNode: any) { }
  parentNode(node: any) { }
  nextSibling(node: any) { }
  setAttribute(el: any, name: string, value: string, namespace?: string): void { }
  removeAttribute(el: any, name: string, namespace?: string): void { }
  addClass(el: any, name: string): void { }
  removeClass(el: any, name: string): void { }
  setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2): void { }
  removeStyle(el: any, style: string, flags?: RendererStyleFlags2): void { }
  setProperty(el: any, name: string, value: any): void { }
  setValue(node: any, value: string): void { }
  listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void { return null; }
  constructor(private eventManager: EventManager) {}
}
Custom Resource loader
Guesses the source file by "url" (which is not a URL actually) and reads all the text of the file.
@Injectable()
export class ResourceLoaderImpl extends ResourceLoader {
  constructor(private _cwd: string) {
    super();
  }
  get(resourceFileName: string): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      glob(`**/${resourceFileName}`, { cwd: this._cwd, }, (globError, matches) => {
        if (globError) reject(globError.toString());
        else
          readFile(path.join(this._cwd, matches[0]), (readFileError, data) => {
            if (readFileError) reject(readFileError);
            else resolve(data.toString());
          });
      });
    });
  }
}
Imports
import { ResourceLoader } from '@angular/compiler';
import { Compiler, CompilerFactory, Injectable, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2, StaticProvider, Type } from '@angular/core';
import { EventManager } from '@angular/platform-browser';
import { platformDynamicServer } from '@angular/platform-server';
import { readFile } from 'fs';
import * as glob from 'glob';
import * as path from 'path';
import 'zone.js';
import 'zone.js/dist/long-stack-trace-zone.js';
import { AppComponent } from './app/app.component';
import { AppModule } from './app/app.module';
UPDATE 1
I also tried to provide a custom document object like this:
import { JSDOM } from 'jsdom';
const jsdom = new JSDOM(`<html></html>`);
export function document() { return jsdom.window.document; }
by means of registering it with the DI:
{ provide: DOCUMENT, useFactory: document, deps: [] }
Still getting the same ReferenceError: document is not defined error.
UPDATE 2
The root of my problem is that RendererFactory2 does not get resolved to MyDomRendererFactory2. Instead, a standard NgModule's DomRendererFactory2 is being used... This is what I observe in providers during debugging:

 
    