angular: bug(elements): importing `createCustomElement` breaks SSR

I’m submitting a…


[x] Bug report  

Current behavior

Importing createCustomElement throws an error during angular universal server side rendering.

Note: it appears that simply importing the function throws the error. Currently, even the following code throws an error during SSR:

import {createCustomElement} from '@angular/elements';
. . .
export class AppModule {
  constructor() {
    if (false as any) {
      createCustomElement
    }
  }
}

Error:

var elProto = Element.prototype;
              ^

ReferenceError: Element is not defined
    at Object.<anonymous> (/user/apps/tests/universal-starter/dist/server.js:126911:15)
    at __webpack_require__ (/user/apps/tests/universal-starter/dist/server.js:20:30)
    at Object.@angular/elements (/user/apps/tests/universal-starter/dist/server.js:126796:18)
    at __webpack_require__ (/user/apps/tests/universal-starter/dist/server.js:126326:30)
    at Object../src/app/app.module.ts (/user/apps/tests/universal-starter/dist/server.js:126453:18)
    at __webpack_require__ (/user/apps/tests/universal-starter/dist/server.js:126326:30)
    at Object../src/app/app.server.module.ngfactory.js (/user/apps/tests/universal-starter/dist/server.js:126504:11)
    at __webpack_require__ (/user/apps/tests/universal-starter/dist/server.js:126326:30)
    at Object../src/main.server.ts (/user/apps/tests/universal-starter/dist/server.js:126712:37)
    at __webpack_require__ (/user/apps/tests/universal-starter/dist/server.js:126326:30)

Expected behavior

Ideally, custom elements could be rendered via SSR normally, but, at minimum, it’d expect the ability to import createCustomElement and then conditionally call it using isPlatformBrowser() so that the custom element was only defined + registered on the client side.

Minimal reproduction of the problem with instructions

A reproduction of the issue can be found here: https://github.com/thefliik/universal-starter/tree/elements-ssr-bug. Simply clone the repo and run yarn build:ssr && yarn serve:ssr.

The repo was made by cloning the @angular/universal-starter repo, adding @angular/elements to project.json, and updating app.module.ts with:

import {createCustomElement} from '@angular/elements';

...

export class AppModule {
  constructor() {
    /**
     * # Note
     * 
     * Remove this statement and no errors are thrown. I *think* this is because
     * tree shaking will remove the `createCustomElement` import statement without
     * this line.
     */
    if (false as any) {
      createCustomElement
    }
  }
}

What is the motivation / use case for changing the behavior?

To allow custom elements built with @angular/elements to be used in a SSR app.

My specific use case for angular elements is as a work around for issue #14324 (& #17168).

Environment


Angular CLI: 6.0.0
Node: 8.9.1
OS: darwin x64
Angular: 6.0.5
... animations, common, compiler, core, elements, forms, http
... platform-browser, platform-browser-dynamic, platform-server
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.6.0
@angular-devkit/build-angular     0.6.0
@angular-devkit/build-optimizer   0.6.0
@angular-devkit/core              0.6.0
@angular-devkit/schematics        0.6.0
@angular/cli                      6.0.0
@angular/compiler-cli             6.0.0
@angular/language-service         6.0.0
@ngtools/webpack                  6.0.0
@schematics/angular               0.6.0
@schematics/update                0.6.0
rxjs                              6.0.0
typescript                        2.7.2
webpack                           4.6.0

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 3
  • Comments: 19 (16 by maintainers)

Commits related to this issue

Most upvoted comments

As a work around, you can conditionally import createCustomElement with const { createCustomElement } = require('@angular/elements').

For example:

  constructor(private injector: Injector, @Inject(PLATFORM_ID) platformId: Object) {
    if (isPlatformBrowser(platformId)) {
      const { createCustomElement } = require('@angular/elements');

      // register the custom element with the browser.
      customElements.define(
        'app-calendar-breadcrumb',
        createCustomElement(CalendarBreadcrumbComponent, {injector: this.injector})
      );
    }
  }

@ericmartinezr thanks for the suggestion. I had tried the solution suggested in that issue before reporting this and, while it does eliminate the var elProto = Element.prototype; error, it results in a new error.

Update (the new error is…)

Adding:

const domino = require('domino');
Object.assign(global, domino.impl);
(global as any)['KeyboardEvent'] = domino.impl.Event;

to server.ts (which I think is similar to #24116, @gkalpak ?) eliminates the var elProto = Element.prototype; error but instead results in the following error:

    function __() { this.constructor = d; }
                                     ^

TypeError: Cannot assign to read only property 'constructor' of object '[object Object]'
    at new __ (/user/apps/tests/universal-starter/dist/server.js:20056:38)
    at __extends (/user/apps/tests/universal-starter/dist/server.js:20057:80)
    at /user/apps/tests/universal-starter/dist/server.js:125488:60
    at Object.<anonymous> (/user/apps/tests/universal-starter/dist/server.js:125498:2)
    at __webpack_require__ (/user/apps/tests/universal-starter/dist/server.js:23:30)
    at Object.@angular/elements (/user/apps/tests/universal-starter/dist/server.js:125004:18)
    at __webpack_require__ (/user/apps/tests/universal-starter/dist/server.js:124552:30)
    at Object../src/app/app.module.ts (/user/apps/tests/universal-starter/dist/server.js:124714:18)
    at __webpack_require__ (/user/apps/tests/universal-starter/dist/server.js:124552:30)
    at Object../src/app/app.server.module.ngfactory.js (/user/apps/tests/universal-starter/dist/server.js:124767:11)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

This error is raised under the same conditions as before (i.e. simply importing createCustomElement() throws the error).

It’s actually a problem of Angular CLI, for server-side rendering for custom elements, it should always use "target": "es2015" rather than requiring the polyfills (which is not compatible with node).

But currently Angular CLI will always skip bundle of @angular packages and forwarding to UMD bundles, which are es5 code which doesn’t fulfill Custom Elements requirement (real class type).