[w3c/webcomponents] The way to access current script or current root, from Shadow DOM (#717)

Since 
> `Document.currentScript` **must** return `null` if the `script` element is in a _shadow tree_. See Issue https://github.com/w3c/webcomponents/issues/477.

There is no way for a script to access the elements in its own root. That, in my opinion, breaks the idea of encapsulation, and for sure renders script elements inside shadow root useless.

At least the scripts that have something to do with the shadow root they are in.

### Why do I think it breaks encapsulation?
For me, Shadow **DOM** was the way to encapsulate a bunch of **any** HTML elements into a separate tree. Therefore, (in V0) I used `<div>` elements that does not obfuscate the global tree, `<style>` elements that affects only elements inside and does not bleed outside, `<script>` elements that could work with smaller document fragment, where I can use small set of scoped IDs, `querySelect` smaller tree and so on.

JS was never strictly encapsulated as it is working in the global context. That could be good and bad but I don't want that to be part of this discussion.
However, I was at least **able** to use a **convention** to support it. I could even force it by auditing, linting the scripts I put into my shadow, make sure they do not access document or global variables.

Right now, there is no (\*) way to access this encapsulated/scoped document fragment from the script, and they only can access, `querySelect` global document. That means `<script>`s in "encapsulated" tree are able to access global tree, but cannot access encapsulated tree.

Compare such shadow roots:
- pre-477
```html
<button>Horn</button>
<script>
    document.currentScript.getRootNode().querySelector('button').addEventListener('click',()=>{alert('beep')});
</script>
```
- post-477 1.
```html
<button>Horn</button>
<script>
    document.querySelector('button').addEventListener('click',()=>{alert('beep')});
</script>
```
- post-477 2.
```html
<button>Horn</button>
<script>
    document.querySelector('#shadow-host1').shadowRoot.querySelector('#shadow-host2').shadowRoot
    /*....*/
    .addEventListener('click',()=>{alert('beep')});
</script>
```

The pre-477/v0 case was working perfectly, looked pretty clean. The only downside is that you had to remember to use `document.currentScript.getRootNode()` instead of `document`, but as I stated above this could be good and bad that you can make this conscious decision.

Then post-477 it's rather impossible to achieve such behavior. The first case simply doesn't work and most probably would make a random button beep.
The second one may work as long as you
 - really know where you are - usually, you don't,
 - all your shadow-including ancestors are open - **disallows you to use closed shadow roots**,
 - stamp it only once, unless you fix the traversing to the root every time you stamp it.

Also, it's extremely ugly and bug-prone.
What is worse it requires you to pierce through all the shadow roots.

// @rniwa maybe you would be interested in a problem that disallows closed shadows and requires piercing

### "Having `currentScript` allows you to expose shadow root and break encapsulation again"
It does, but you can expose it in many other ways which are not disallowed. For example with custom elements, like in https://github.com/w3c/webcomponents/issues/477#issuecomment-351697502

### (\*) "You can use custom element"
I can, but I think it's overkill. Consider the costs of cognitive load for developers, maintenance, performance, loading yet another polyfill of defining a custom element, creating an instance, considering all CE reactions just to access an element in _my own scope_ and do something simple with it just once.
Imagine using full CustomElements machinery as an IIFE.

### More use cases
I would say: all cases when you would like to do any kind of scripting in scoped encapsulated DOM. Especially those which are specific, unique, or just too simple, so where running CE machinery does not make sense.

If extrapolating the example above is not enough, and somebody would like to see more. I'm happy to provide more real-life, production-based use cases. But here I'd like to avoid blurring the picture and making the post to long to read.


### Solutions

1. Restore `currentScript` and use `getRootNode()`.
  It's a simple solution from the perspective of web developer/ shadow root author. It's the same `getRootNode` that is used for any other custom or native element.
  I believe it should be also simple from implementers perspective, as it was there already in V0.
2. Introduce `currentRoot` as [proposed by](https://github.com/w3c/webcomponents/issues/477#issuecomment-205305796) @hayatoito.
  If for some reason `currentScript` is really so evil and worse from CE's `this.getRootNode()`, or external `element.shadowRoot`, that it has to be blocked. I think it's pretty self-explanatory and straightforward API, that would be also nice sugar on top of `document.currentScript.getRootNode()`. But I would expect some problems with such context-awareness, both in author developer experience as in implementation.




-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/webcomponents/issues/717

Received on Thursday, 14 December 2017 18:46:07 UTC