Skip to content
This repository was archived by the owner on Sep 20, 2019. It is now read-only.

Can't use ES5 with v1.0.0-rc.6 #737

Closed
nstepien opened this issue Mar 16, 2017 · 23 comments
Closed

Can't use ES5 with v1.0.0-rc.6 #737

nstepien opened this issue Mar 16, 2017 · 23 comments
Assignees

Comments

@nstepien
Copy link

nstepien commented Mar 16, 2017

Hello,

So I'm trying out v1.0.0-rc.6 with the intent of supporting IE11 and better, but I'm facing a few issues:

  • webcomponents-lite.js does not support ES5 code
  • which leads me to try webcomponents-es5-loader.js, but it ends up loading webcomponents-lite.js anyway.
  • I tried loading webcomponents-ce-es5.js out of curiosity, but its use of arrow functions is not ES5 friendly...

Would it be possible to provide a version of webcomponents-lite.js with ES5 support?

Thank you.

@CaptainCodeman
Copy link

You may have something not setup quite correctly - it's doing the correct es6 vs es5 loading for me across all browsers (Chrome, FF, Safari, Edge, IE11).

How are you building / serving / testing?

@nstepien
Copy link
Author

I've installed it via npm i webcomponents/webcomponentsjs#v1.0.0-rc.6, and tested locally by serving the files straight from node_modules.

I tried manually building now and here are my observations:

  • The arrow functions in ce-es5 are gone.
  • Loading webcomponents-lite.js on Chrome gives me: Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.
    Additionally loading webcomponents-ce-es5.js does not help.
  • Loading webcomponents-lite.js on Firefox seems to actually work fine at first glance.
  • Loading webcomponents-lite.js on IE11 is giving me Unable to get property 'cloneNode' of undefined or null reference when trying to clone a template's content. Which is an unrelated issue.

@CaptainCodeman
Copy link

When you say "serving from" ... how - using polymer serve or something else? It sounds like the files are mismatched for some reason.

When you use polymer serve it does the choosing of what polyfills to load and any transpilation if necessary, so things usually "just work" in whatever browser you use. Chrome get's es6 versions, IE11 would get es5 versions for instance.

When you build, by default you are choosing to either compile or not compile. So you are making a choice on whether you are going to ship an es6 version (leaving out IE11 / Edge) or an es5 version (imposing a slight perf penalty for Chrome etc...)

But another option is to add two build configs, one doing the compile and one not and make the decision either on the client or on the server which built files to reference (i.e. doing what polyserv does but without it running). I'm just experimenting with this now but it seems to work great and gives the optimal results.

example polymer.json to create the two builds:

  "builds": [
    {
      "name": "es6",
      "bundle": false,
      "js": { "compile": false, "minify": false },
      "css": { "minify": false },
      "html": { "minify": false },
      "addServiceWorker": false,
      "insertPrefetchLinks": false
    },
    {
      "name": "es5",
      "bundle": false,
      "js": { "compile": true, "minify": false },
      "css": { "minify": false },
      "html": { "minify": false },
      "addServiceWorker": false,
      "insertPrefetchLinks": false
    }
  ],

@nstepien
Copy link
Author

Sorry I'm not using Polymer. I'm serving files using gulp-live-server, my project uses custom elements v1, no framework, so I'm serving ES5 to all browsers. By "manually building" I meant building the webcomponentjs polyfills (npm i and running gulp).

I've been using webcomponents.js 0.7.x, and document-register-element to use custom elements v1, and I was hoping to replace both with webcomponents.js v1.

@CaptainCodeman
Copy link

It works fine with a pure / direct webcomponent (i.e. no polymer) but the same compilation steps are needed.

It sounds like you're compiling the webcomponents polyfills for some reason but not your component, that's not going to work.

@nstepien
Copy link
Author

Yes I would have to serve ES6 for Chrome, and no polyfills, but then I'd lose uglify minification, and add complexity for building/serving. So I was hoping that webcomponents.js would do the necessary shimming for Chrome when serving ES5 classes.

I only tried building the polyfills because the ES5 files use ES6 syntax (I mean the files in the repo itself). https://github.com/webcomponents/webcomponentsjs/blob/master/webcomponents-ce-es5.js#L4

I am transpiling my code to ES5, I'm not sure what you mean by "compiling [...] your component".

@CaptainCodeman
Copy link

I'd stick to using the built packages, there's really little reason to build your own unless you are working on them. You are looking at the sources, the pre-built es5 files are already in es5.

You should setup a hosted example / repo somewhere that people can look at.

@RevillWeb
Copy link

Hi,

First of all I would like to thank everyone involved in the latest polyfills, superb work! 😃 I really appreciate it (for what that's worth).

I've experienced (what I assume) exactly the same issue with a simple vanilla Web Component. I think it might be a slight misunderstanding as to how the CE ES5 polyfill works.

Basically I want to ship an ES5 version of my simple component called rw-star-rating to all browsers (Chrome, FF, Safari, Edge, IE11) so what I expected to work after reading the documentation was as follows:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Rw Star Rating</title>
    <script src="bower_components/webcomponentsjs/webcomponents-es5-loader.js"></script>
    <script src="dist/rw-star-rating.js"></script> <!-- ES5 transpiled with Babel -->
    </style>
</head>
<body>
    <rw-star-rating></rw-star-rating>
</body>
</html>

In IE11 this works fine but in Chrome you get:

Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function

The reason I expected this to work is that the documentation here shows the same example except the main difference is that the Web Component is being loaded as a HTML Import, not via a script file which is more common with vanilla Web Components.

Basically the trick here is that you need to load dist/rw-star-rating.js once WebComponentsReady has been fired. For example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Rw Star Rating</title>
    <script src="bower_components/webcomponentsjs/webcomponents-es5-loader.js"></script>
</head>
<body>
    <rw-star-rating></rw-star-rating>
    <script>
        window.addEventListener('WebComponentsReady', function() {
            var $script = document.createElement("script");
            $script.src = "dist/rw-star-rating.js";
            document.head.appendChild($script);
        });
    </script>
</body>
</html>

Once you've done that everything works! I hope this stops anyone else being caught out like I was 👍

@CaptainCodeman
Copy link

Ha, I think we posted the same core findings within minutes of each other, linking them up for reference:

Polymer/polymer#4440 (comment)

@valdrinkoshi
Copy link
Contributor

@RevillWeb since webcomponents-es5-loader.js also polyfills html imports, you could just create an html document rw-star-rating.html that contains your transpiled script, and import it in the main document.
Like this, you wouldn't need to wait for WebComponentsReady ;)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Rw Star Rating</title>
    <script src="bower_components/webcomponentsjs/webcomponents-es5-loader.js"></script>
    <!-- contains <script src="dist/rw-star-rating.js"></script>  -->
    <link rel="import" href="rw-star-rating.html">
    </style>
</head>
<body>
    <rw-star-rating></rw-star-rating>
</body>
</html>

@CaptainCodeman
Copy link

You can get errors doing that.

If you're on a browser that already supports html-imports then the <rw-star-rating> element can be processed before the webcomponent polyfill / es6 shim loads which are loaded asynchronously by the webcomponents-es5-loader.js script.

It's essential to wait for the WebComponentsReady event.

@FredKSchott
Copy link
Contributor

I just wanted to share what I've learned while digging into this issues through serving bundled, ES5-compiled code in Chrome. This is a common combination and the issue is affecting a lot of people in the CLI: https://github.com/Polymer/polymer-cli/issues/605

It looks like the two loaders call document.head.appendChild(newScript) to load the correct bundle. The problem is that in Chrome (and others?), a script appended to head does not necessarily respect the execution order of the document. It will actually execute after all scripts/imports already on the page, even those in body. For bundled applications, this means that the webcomponents bundle is executed AFTER the entire application.

Repro instructions:

npm install -g polymer-cli@next
polymer init polymer-2-starter-kit
polymer build --js-compile --bundle
polymer serve
# set breakpoints in the webcomponentsjs loader, my-app.html, and the webcomponentsjs bundle
# notice that the execution order is: loader, my-app, bundle

This is most obvious in the bundled case because a large portion of the app is executed before the bundle. However this doesn't seem to affect most unbundled applications. My best guess is that other imports end up giving the appended loader enough time to load and execute.

tl;dr: document.head.appendChild(newScript) does not let us guarantee that application code will execute after the polyfill.

@CaptainCodeman
Copy link

I suspect the reason unbundled apps may not see it quite as much is that unless they are using http/2, the chance of some affected code running before the polyfill is pushed back somewhat (because of the cumulative latency from the chain of html imports).

But, and kind of related because it effectively speeds up the "get" of the imports, when a service-worker is involved this triggers the issue more frequently.

@nstepien
Copy link
Author

I wish we could just do

<script src="webcomponents-lite.js" defer></script>
<script src="app.js" defer></script>

That way we don't need to wait for an event before downloading the app.js.

@CaptainCodeman
Copy link

You can load the webcomponents-loader async no problem, just as long as you don't have anything that depends on it directly in the page as an html import. The native support in Chrome (from webcomponents v0) means Chrome will download and execute it often before the polyfills have loaded.

Right now you have to wait for the event as the trigger that the app code can be processed.

@nstepien
Copy link
Author

Sure but I still need to rely on the WebComponentsReady before doing basically anything.

@ChadKillingsworth
Copy link

document.write fixes the issue.

@dfreedm dfreedm self-assigned this Mar 20, 2017
@dfreedm
Copy link
Contributor

dfreedm commented Mar 21, 2017

Hmm, I'd really like to avoid using document.write(), as browsers really want that feature to go away, but we do want to make the loader work correctly for compiled es5 code.

@notwaldorf WDYT about using document.write()?

@FredKSchott
Copy link
Contributor

FredKSchott commented Mar 21, 2017

yea, specifically document.write might not even solve this problem... see last note on page https://developer.mozilla.org/en-US/docs/Web/API/Document/write

Starting in 55, Chrome will not execute <script> elements injected via document.write() in case of an HTTP cache miss for users on a 2G connection.

Injecting and executing a script is exactly what we're trying to do.

@CaptainCodeman
Copy link

There are a few more clauses for when those restrictions apply, more details here:

https://developers.google.com/web/updates/2016/08/removing-document-write
WICG/interventions#17

@valdrinkoshi
Copy link
Contributor

valdrinkoshi commented Mar 21, 2017

The only difference between webcomponents-s5-loader and webcomponents-loader is that on chrome and STP the es5 loader will load the native shim.
Have you tried including the native-shim and then the loader?

<script src="../custom-elements/src/native-shim.js"></script>
<script src="../webcomponentsjs/webcomponents-loader.js"></script>

The gambit is: only the shim is needed synchronously, all the rest can be polyfilled async.

@dfreedm
Copy link
Contributor

dfreedm commented Mar 21, 2017

@valdrinkoshi and I have been talking, and I think requiring users that compile their elements to use the native shim (with a better name), is the right approach.

Basically, the decision of which loader to use is the same as whether to include native-shim script or not.

By providing the native-shim as a bundle (something like custom-elements-es5-adapter), we can keep the loader relatively clean.

@valdrinkoshi
Copy link
Contributor

We've provided the custom-elements-es5-adapter.js, which should be loaded before declaring a new es5-style Custom Element 👌

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants