feat(platform-server): allow shimming the global env sooner (#40559)
`@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/init`), 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]: https://github.com/angular/angular/issues/39950#issuecomment-747598403 [5]: https://github.com/angular/angular-cli/blob/cc51432661eb4ab4b6a3/packages/schematics/angular/universal PR Close #40559
This commit is contained in:
parent
177eab2260
commit
43ecf8a77b
@ -107,6 +107,7 @@ module.exports =
|
|||||||
'platform-browser-dynamic/index.ts',
|
'platform-browser-dynamic/index.ts',
|
||||||
'platform-browser-dynamic/testing/index.ts',
|
'platform-browser-dynamic/testing/index.ts',
|
||||||
'platform-server/index.ts',
|
'platform-server/index.ts',
|
||||||
|
'platform-server/init/index.ts',
|
||||||
'platform-server/testing/index.ts',
|
'platform-server/testing/index.ts',
|
||||||
'platform-webworker/index.ts',
|
'platform-webworker/index.ts',
|
||||||
'platform-webworker-dynamic/index.ts',
|
'platform-webworker-dynamic/index.ts',
|
||||||
|
@ -18,7 +18,7 @@ const packageMap = {
|
|||||||
forms: ['forms/index.ts'],
|
forms: ['forms/index.ts'],
|
||||||
'platform-browser': ['platform-browser/index.ts', 'platform-browser/animations/index.ts', 'platform-browser/testing/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-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/init/index.ts', 'platform-server/testing/index.ts'],
|
||||||
router: ['router/index.ts', 'router/testing/index.ts', 'router/upgrade/index.ts'],
|
router: ['router/index.ts', 'router/testing/index.ts', 'router/upgrade/index.ts'],
|
||||||
'service-worker': ['service-worker/index.ts'],
|
'service-worker': ['service-worker/index.ts'],
|
||||||
upgrade: ['upgrade/index.ts', 'upgrade/static/index.ts', 'upgrade/static/testing/index.ts']
|
upgrade: ['upgrade/index.ts', 'upgrade/static/index.ts', 'upgrade/static/testing/index.ts']
|
||||||
|
0
goldens/public-api/platform-server/init/init.d.ts
vendored
Normal file
0
goldens/public-api/platform-server/init/init.d.ts
vendored
Normal file
@ -1,3 +1,4 @@
|
|||||||
|
import '@angular/platform-server/init';
|
||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from '@angular/core';
|
||||||
|
|
||||||
import { environment } from './environments/environment';
|
import { environment } from './environments/environment';
|
||||||
|
@ -27,6 +27,7 @@ import * as platformBrowserDynamicTesting from '@angular/platform-browser-dynami
|
|||||||
import * as platformBrowserAnimations from '@angular/platform-browser/animations';
|
import * as platformBrowserAnimations from '@angular/platform-browser/animations';
|
||||||
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
||||||
import * as platformServer from '@angular/platform-server';
|
import * as platformServer from '@angular/platform-server';
|
||||||
|
import * as platformServerInit from '@angular/platform-server/init';
|
||||||
import * as platformServerTesting from '@angular/platform-server/testing';
|
import * as platformServerTesting from '@angular/platform-server/testing';
|
||||||
import * as router from '@angular/router';
|
import * as router from '@angular/router';
|
||||||
import * as routerTesting from '@angular/router/testing';
|
import * as routerTesting from '@angular/router/testing';
|
||||||
@ -56,6 +57,7 @@ export default {
|
|||||||
platformBrowserDynamicTesting,
|
platformBrowserDynamicTesting,
|
||||||
platformBrowserAnimations,
|
platformBrowserAnimations,
|
||||||
platformServer,
|
platformServer,
|
||||||
|
platformServerInit,
|
||||||
platformServerTesting,
|
platformServerTesting,
|
||||||
router,
|
router,
|
||||||
routerTesting,
|
routerTesting,
|
||||||
|
@ -27,6 +27,7 @@ import * as platformBrowserDynamicTesting from '@angular/platform-browser-dynami
|
|||||||
import * as platformBrowserAnimations from '@angular/platform-browser/animations';
|
import * as platformBrowserAnimations from '@angular/platform-browser/animations';
|
||||||
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
||||||
import * as platformServer from '@angular/platform-server';
|
import * as platformServer from '@angular/platform-server';
|
||||||
|
import * as platformServerInit from '@angular/platform-server/init';
|
||||||
import * as platformServerTesting from '@angular/platform-server/testing';
|
import * as platformServerTesting from '@angular/platform-server/testing';
|
||||||
import * as router from '@angular/router';
|
import * as router from '@angular/router';
|
||||||
import * as routerTesting from '@angular/router/testing';
|
import * as routerTesting from '@angular/router/testing';
|
||||||
@ -56,6 +57,7 @@ export default {
|
|||||||
platformBrowserDynamicTesting,
|
platformBrowserDynamicTesting,
|
||||||
platformBrowserAnimations,
|
platformBrowserAnimations,
|
||||||
platformServer,
|
platformServer,
|
||||||
|
platformServerInit,
|
||||||
platformServerTesting,
|
platformServerTesting,
|
||||||
router,
|
router,
|
||||||
routerTesting,
|
routerTesting,
|
||||||
|
@ -105,6 +105,7 @@ WELL_KNOWN_GLOBALS = {p: _global_name(p) for p in [
|
|||||||
"@angular/forms",
|
"@angular/forms",
|
||||||
"@angular/core/testing",
|
"@angular/core/testing",
|
||||||
"@angular/core",
|
"@angular/core",
|
||||||
|
"@angular/platform-server/init",
|
||||||
"@angular/platform-server/testing",
|
"@angular/platform-server/testing",
|
||||||
"@angular/platform-server",
|
"@angular/platform-server",
|
||||||
"@angular/common/testing",
|
"@angular/common/testing",
|
||||||
|
@ -30,6 +30,7 @@ ng_package(
|
|||||||
name = "npm_package",
|
name = "npm_package",
|
||||||
srcs = [
|
srcs = [
|
||||||
"package.json",
|
"package.json",
|
||||||
|
"//packages/platform-server/init:package.json",
|
||||||
"//packages/platform-server/testing:package.json",
|
"//packages/platform-server/testing:package.json",
|
||||||
],
|
],
|
||||||
entry_point = ":index.ts",
|
entry_point = ":index.ts",
|
||||||
@ -44,6 +45,7 @@ ng_package(
|
|||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
":platform-server",
|
":platform-server",
|
||||||
|
"//packages/platform-server/init",
|
||||||
"//packages/platform-server/testing",
|
"//packages/platform-server/testing",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
18
packages/platform-server/init/BUILD.bazel
Normal file
18
packages/platform-server/init/BUILD.bazel
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
load("//tools:defaults.bzl", "ng_module")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
exports_files(["package.json"])
|
||||||
|
|
||||||
|
ng_module(
|
||||||
|
name = "init",
|
||||||
|
srcs = glob(
|
||||||
|
[
|
||||||
|
"*.ts",
|
||||||
|
"src/**/*.ts",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
deps = [
|
||||||
|
"//packages/platform-server",
|
||||||
|
],
|
||||||
|
)
|
15
packages/platform-server/init/PACKAGE.md
Normal file
15
packages/platform-server/init/PACKAGE.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Initializes the server environment for rendering an Angular application.
|
||||||
|
|
||||||
|
For example, it provides shims (such as DOM globals) for the server environment.
|
||||||
|
|
||||||
|
The initialization happens as a [side effect of importing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#import_a_module_for_its_side_effects_only) the entry point (i.e. there are no specific exports):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import '@angular/platform-server/init';
|
||||||
|
```
|
||||||
|
|
||||||
|
<div class="alert is-important">
|
||||||
|
|
||||||
|
The import must come before any imports (direct or transitive) that rely on DOM built-ins being available.
|
||||||
|
|
||||||
|
</div>
|
14
packages/platform-server/init/index.ts
Normal file
14
packages/platform-server/init/index.ts
Normal file
@ -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
packages/platform-server/init/package.json
Normal file
4
packages/platform-server/init/package.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "@angular/platform-server/init",
|
||||||
|
"sideEffects": true
|
||||||
|
}
|
14
packages/platform-server/init/public_api.ts
Normal file
14
packages/platform-server/init/public_api.ts
Normal file
@ -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/init';
|
17
packages/platform-server/init/src/init.ts
Normal file
17
packages/platform-server/init/src/init.ts
Normal file
@ -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 initialization APIs of the platform-server package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {applyShims} from './shims';
|
||||||
|
|
||||||
|
applyShims();
|
16
packages/platform-server/init/src/shims.ts
Normal file
16
packages/platform-server/init/src/shims.ts
Normal file
@ -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 '../../src/domino_adapter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the necessary shims to make DOM globals (such as `Element`, `HTMLElement`, etc.) available
|
||||||
|
* on the environment.
|
||||||
|
*/
|
||||||
|
export function applyShims(): void {
|
||||||
|
setDomTypes();
|
||||||
|
}
|
25
packages/platform-server/init/test/BUILD.bazel
Normal file
25
packages/platform-server/init/test/BUILD.bazel
Normal file
@ -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/init/index.js",
|
||||||
|
deps = ["//packages/platform-server/init"],
|
||||||
|
)
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "test_lib",
|
||||||
|
testonly = True,
|
||||||
|
srcs = glob(["**/*.ts"]),
|
||||||
|
deps = [
|
||||||
|
"//packages/platform-server/init",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
jasmine_node_test(
|
||||||
|
name = "test",
|
||||||
|
bootstrap = ["//tools/testing:node_no_angular_es5"],
|
||||||
|
deps = [
|
||||||
|
":test_lib",
|
||||||
|
],
|
||||||
|
)
|
41
packages/platform-server/init/test/shims_spec.ts
Normal file
41
packages/platform-server/init/test/shims_spec.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* @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 {applyShims} from '../src/shims';
|
||||||
|
|
||||||
|
describe('applyShims()', () => {
|
||||||
|
if (isBrowser) return; // NODE only
|
||||||
|
|
||||||
|
const domino = require('domino');
|
||||||
|
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));
|
||||||
|
|
||||||
|
applyShims();
|
||||||
|
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);
|
||||||
|
|
||||||
|
applyShims();
|
||||||
|
expect((global as any).KeyboardEvent).toBe((domino.impl as any).Event);
|
||||||
|
});
|
||||||
|
});
|
@ -25,7 +25,15 @@
|
|||||||
"ng-update": {
|
"ng-update": {
|
||||||
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
|
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
|
||||||
},
|
},
|
||||||
"sideEffects": false,
|
"sideEffects": [
|
||||||
|
"./bundles/platform-server-init.umd.js",
|
||||||
|
"./bundles/platform-server-init.umd.min.js",
|
||||||
|
"./esm2015/init/src/init.js",
|
||||||
|
"./fesm2015/init.js",
|
||||||
|
"./__ivy_ngcc__/bundles/platform-server-init.umd.js",
|
||||||
|
"./__ivy_ngcc__/esm2015/init/src/init.js",
|
||||||
|
"./__ivy_ngcc__/fesm2015/init.js"
|
||||||
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
},
|
},
|
||||||
|
@ -14,8 +14,8 @@ function _notImplemented(methodName: string) {
|
|||||||
return new Error('This method is not implemented in DominoAdapter: ' + methodName);
|
return new Error('This method is not implemented in DominoAdapter: ' + methodName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDomTypes() {
|
export function setDomTypes() {
|
||||||
// Make all Domino types available as types in the global env.
|
// Make all Domino types available in the global env.
|
||||||
Object.assign(global, domino.impl);
|
Object.assign(global, domino.impl);
|
||||||
(global as any)['KeyboardEvent'] = domino.impl.Event;
|
(global as any)['KeyboardEvent'] = domino.impl.Event;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ System.config({
|
|||||||
'domino': 'dist/all/@angular/empty.js',
|
'domino': 'dist/all/@angular/empty.js',
|
||||||
'url': 'dist/all/@angular/empty.js',
|
'url': 'dist/all/@angular/empty.js',
|
||||||
'xhr2': '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',
|
'angular-in-memory-web-api': 'dist/all/@angular/misc/angular-in-memory-web-api',
|
||||||
'rxjs': 'node_modules/rxjs',
|
'rxjs': 'node_modules/rxjs',
|
||||||
},
|
},
|
||||||
@ -64,6 +64,7 @@ System.config({
|
|||||||
'@angular/platform-browser': {main: 'index.js', defaultExtension: 'js'},
|
'@angular/platform-browser': {main: 'index.js', defaultExtension: 'js'},
|
||||||
'@angular/platform-browser-dynamic/testing': {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-browser-dynamic': {main: 'index.js', defaultExtension: 'js'},
|
||||||
|
'@angular/platform-server/init': {main: 'index.js', defaultExtension: 'js'},
|
||||||
'@angular/platform-server/testing': {main: 'index.js', defaultExtension: 'js'},
|
'@angular/platform-server/testing': {main: 'index.js', defaultExtension: 'js'},
|
||||||
'@angular/platform-server': {main: 'index.js', defaultExtension: 'js'},
|
'@angular/platform-server': {main: 'index.js', defaultExtension: 'js'},
|
||||||
'@angular/private/testing': {main: 'index.js', defaultExtension: 'js'},
|
'@angular/private/testing': {main: 'index.js', defaultExtension: 'js'},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user