Skip to content

Commit

Permalink
feat(platform-server): allow shimming the global env sooner
Browse files Browse the repository at this point in the history
`@angular/platform-server` provides the foundation for rendering an
Angular app on the server. In order to achieve that, it uses a
server-side DOM implementation (currently [domino][1]).

For rendering on the server to work as closely as possible to running
the app on the browser, we need to make DOM globals (such as `Element`,
`HTMLElement`, etc.), which are normally provided by the browser,
available as globals on the server as well.

Currently, `@angular/platform-server` achieves this by extending the
`global` object with the DOM implementation provided by `domino`. This
assignment happens in the [setDomTypes()][2] function, which is
[called in a `PLATFORM_INITIALIZER`][3]. While this works in most cases,
there are some scenarios where the DOM globals are needed sooner (i.e.
before initializing the platform). See, for example, #24551 and #39950
for more details on such issues.

This commit provides a way to solve this problem by exposing a
side-effect-ful entry-point (`@angular/platform-server/shims`), that
shims the `global` object with DOM globals. People will be able to
import this entry-point in their server-rendered apps before
bootstrapping the app (for example, in their `main.server.ts` file).
(See also [#39950 (comment)][4].)

In a future update, the [`universal` schematics][5] will include such an
import by default in newly generated projects.

[1]: https://www.npmjs.com/package/domino
[2]: https://github.com/angular/angular/blob/0fc8466f1be392917e0c/packages/platform-server/src/domino_adapter.ts#L17-L21
[3]: https://github.com/angular/angular/blob/0fc8466f1be392917e0c/packages/platform-server/src/server.ts#L33
[4]: #39950 (comment)
[5]: https://github.com/angular/angular-cli/blob/cc51432661eb4ab4b6a3/packages/schematics/angular/universal
  • Loading branch information
gkalpak committed Jan 26, 2021
1 parent 650d9fa commit f601f3f
Show file tree
Hide file tree
Showing 20 changed files with 171 additions and 5 deletions.
1 change: 1 addition & 0 deletions aio/tools/transforms/angular-api-package/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ module.exports =
'platform-browser-dynamic/index.ts',
'platform-browser-dynamic/testing/index.ts',
'platform-server/index.ts',
'platform-server/shims/index.ts',
'platform-server/testing/index.ts',
'platform-webworker/index.ts',
'platform-webworker-dynamic/index.ts',
Expand Down
2 changes: 1 addition & 1 deletion aio/tools/transforms/authors-package/api-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const packageMap = {
forms: ['forms/index.ts'],
'platform-browser': ['platform-browser/index.ts', 'platform-browser/animations/index.ts', 'platform-browser/testing/index.ts'],
'platform-browser-dynamic': ['platform-browser-dynamic/index.ts', 'platform-browser-dynamic/testing/index.ts'],
'platform-server': ['platform-server/index.ts', 'platform-server/testing/index.ts'],
'platform-server': ['platform-server/index.ts', 'platform-server/shims/index.ts', 'platform-server/testing/index.ts'],
router: ['router/index.ts', 'router/testing/index.ts', 'router/upgrade/index.ts'],
'service-worker': ['service-worker/index.ts'],
upgrade: ['upgrade/index.ts', 'upgrade/static/index.ts', 'upgrade/static/testing/index.ts']
Expand Down
Empty file.
1 change: 1 addition & 0 deletions integration/cli-elements-universal/src/main.server.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import '@angular/platform-server/shims';
import { enableProdMode } from '@angular/core';

import { environment } from './environments/environment';
Expand Down
2 changes: 2 additions & 0 deletions integration/typings_test_ts40/include-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import * as platformBrowserDynamicTesting from '@angular/platform-browser-dynami
import * as platformBrowserAnimations from '@angular/platform-browser/animations';
import * as platformBrowserTesting from '@angular/platform-browser/testing';
import * as platformServer from '@angular/platform-server';
import * as platformServerShims from '@angular/platform-server/shims';
import * as platformServerTesting from '@angular/platform-server/testing';
import * as router from '@angular/router';
import * as routerTesting from '@angular/router/testing';
Expand Down Expand Up @@ -56,6 +57,7 @@ export default {
platformBrowserDynamicTesting,
platformBrowserAnimations,
platformServer,
platformServerShims,
platformServerTesting,
router,
routerTesting,
Expand Down
2 changes: 2 additions & 0 deletions integration/typings_test_ts41/include-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import * as platformBrowserDynamicTesting from '@angular/platform-browser-dynami
import * as platformBrowserAnimations from '@angular/platform-browser/animations';
import * as platformBrowserTesting from '@angular/platform-browser/testing';
import * as platformServer from '@angular/platform-server';
import * as platformServerShims from '@angular/platform-server/shims';
import * as platformServerTesting from '@angular/platform-server/testing';
import * as router from '@angular/router';
import * as routerTesting from '@angular/router/testing';
Expand Down Expand Up @@ -56,6 +57,7 @@ export default {
platformBrowserDynamicTesting,
platformBrowserAnimations,
platformServer,
platformServerShims,
platformServerTesting,
router,
routerTesting,
Expand Down
1 change: 1 addition & 0 deletions packages/bazel/src/ng_package/ng_package.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ WELL_KNOWN_GLOBALS = {p: _global_name(p) for p in [
"@angular/forms",
"@angular/core/testing",
"@angular/core",
"@angular/platform-server/shims",
"@angular/platform-server/testing",
"@angular/platform-server",
"@angular/common/testing",
Expand Down
2 changes: 2 additions & 0 deletions packages/platform-server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ng_package(
name = "npm_package",
srcs = [
"package.json",
"//packages/platform-server/shims:package.json",
"//packages/platform-server/testing:package.json",
],
entry_point = ":index.ts",
Expand All @@ -44,6 +45,7 @@ ng_package(
],
deps = [
":platform-server",
"//packages/platform-server/shims",
"//packages/platform-server/testing",
],
)
Expand Down
10 changes: 9 additions & 1 deletion packages/platform-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@
"ng-update": {
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
},
"sideEffects": false,
"sideEffects": [
"./bundles/platform-server-shims.umd.js",
"./bundles/platform-server-shims.umd.min.js",
"./esm2015/shims/src/shims.js",
"./fesm2015/shims.js",
"./__ivy_ngcc__/bundles/platform-server-shims.umd.js",
"./__ivy_ngcc__/esm2015/shims/src/shims.js",
"./__ivy_ngcc__/fesm2015/shims.js"
],
"engines": {
"node": ">=8.0"
},
Expand Down
18 changes: 18 additions & 0 deletions packages/platform-server/shims/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
load("//tools:defaults.bzl", "ng_module")

package(default_visibility = ["//visibility:public"])

exports_files(["package.json"])

ng_module(
name = "shims",
srcs = glob(
[
"*.ts",
"src/**/*.ts",
],
),
deps = [
"//packages/platform-server",
],
)
1 change: 1 addition & 0 deletions packages/platform-server/shims/PACKAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Provides shims (such as DOM globals) for the server environment.
14 changes: 14 additions & 0 deletions packages/platform-server/shims/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

// This file is not used to build this module. It is only used during editing
// by the TypeScript language service and during build for verifcation. `ngc`
// replaces this file with production index.ts when it rewrites private symbol
// names.

export * from './public_api';
4 changes: 4 additions & 0 deletions packages/platform-server/shims/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "@angular/platform-server/shims",
"sideEffects": true
}
14 changes: 14 additions & 0 deletions packages/platform-server/shims/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

/**
* @module
* @description
* Entry point for all public APIs of this package.
*/
export * from './src/shims';
16 changes: 16 additions & 0 deletions packages/platform-server/shims/src/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {setDomTypes} from '@angular/platform-server/src/domino_adapter';

/**
* Apply the necessary shims to make DOM globals (such as `Element`, `HTMLElement`, etc.) available
* on the environment.
*/
export function init(): void {
setDomTypes();
}
17 changes: 17 additions & 0 deletions packages/platform-server/shims/src/shims.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

/**
* @module
* @description
* Entry point for all shimming APIs of the platform-server package.
*/

import {init} from './init';

init();
25 changes: 25 additions & 0 deletions packages/platform-server/shims/test/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
load("//tools/circular_dependency_test:index.bzl", "circular_dependency_test")

circular_dependency_test(
name = "circular_deps_test",
entry_point = "angular/packages/platform-server/shims/index.js",
deps = ["//packages/platform-server/shims"],
)

ts_library(
name = "test_lib",
testonly = True,
srcs = glob(["**/*.ts"]),
deps = [
"//packages/platform-server/shims",
],
)

jasmine_node_test(
name = "test",
bootstrap = ["//tools/testing:node_no_angular"],
deps = [
":test_lib",
],
)
39 changes: 39 additions & 0 deletions packages/platform-server/shims/test/init_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const domino = require('domino');
import {init} from '../src/init';

describe('init()', () => {
const globalClone = {...global};

afterEach(() => {
// Un-patch `global`.
const currentProps = Object.keys(global) as (keyof NodeJS.Global)[];
for (const prop of currentProps) {
if (globalClone.hasOwnProperty(prop)) {
(global as any)[prop] = globalClone[prop];
} else {
delete (global as any)[prop];
}
}
});

it('should load `domino.impl` onto `global`', () => {
expect(global).not.toEqual(jasmine.objectContaining(domino.impl));

init();
expect(global).toEqual(jasmine.objectContaining(domino.impl));
});

it('should define `KeyboardEvent` on `global`', () => {
expect((global as any).KeyboardEvent).not.toBe((domino.impl as any).Event);

init();
expect((global as any).KeyboardEvent).toBe((domino.impl as any).Event);
});
});
4 changes: 2 additions & 2 deletions packages/platform-server/src/domino_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ function _notImplemented(methodName: string) {
return new Error('This method is not implemented in DominoAdapter: ' + methodName);
}

function setDomTypes() {
// Make all Domino types available as types in the global env.
export function setDomTypes() {
// Make all Domino types available in the global env.
Object.assign(global, domino.impl);
(global as any)['KeyboardEvent'] = domino.impl.Event;
}
Expand Down
3 changes: 2 additions & 1 deletion test-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ System.config({
'domino': 'dist/all/@angular/empty.js',
'url': 'dist/all/@angular/empty.js',
'xhr2': 'dist/all/@angular/empty.js',
'@angular/platform-server/src/domino_adapter': 'dist/all/empty.js',
'@angular/platform-server/src/domino_adapter': 'dist/all/@angular/empty.js',
'angular-in-memory-web-api': 'dist/all/@angular/misc/angular-in-memory-web-api',
'rxjs': 'node_modules/rxjs',
},
Expand Down Expand Up @@ -64,6 +64,7 @@ System.config({
'@angular/platform-browser': {main: 'index.js', defaultExtension: 'js'},
'@angular/platform-browser-dynamic/testing': {main: 'index.js', defaultExtension: 'js'},
'@angular/platform-browser-dynamic': {main: 'index.js', defaultExtension: 'js'},
'@angular/platform-server/shims': {main: 'index.js', defaultExtension: 'js'},
'@angular/platform-server/testing': {main: 'index.js', defaultExtension: 'js'},
'@angular/platform-server': {main: 'index.js', defaultExtension: 'js'},
'@angular/private/testing': {main: 'index.js', defaultExtension: 'js'},
Expand Down

0 comments on commit f601f3f

Please sign in to comment.