-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce pointer to <script> element in module scripts #1013
Comments
I think this would belong in the "module meta" idea. It was detailed in more detail somewhere, but alluded to in whatwg/loader#38. @dherman might know more. The idea would be something like: import { currentScript, url } from this module; At other times people have suggested using metaproperties on import.meta.currentScript; // or just import.currentScript? Unfortunately the base extensibility hook here kind of needs a TC39 proposal for someone to champion, which is a whole process. |
This makes <script> elements work when used in shadow trees. The beforescriptexecute and afterscriptexecute events won’t work, since they are already disabled for modules and only makes sense together with currentScript, which cannot be made to work for shadow scripts. See #1013 for details. Fixes #762.
This makes <script> elements work when used in shadow trees. The beforescriptexecute and afterscriptexecute events won’t work, since they are already disabled for modules and only makes sense together with currentScript, which cannot be made to work for shadow scripts. See #1013 for details. Fixes #762.
This makes <script> elements work when used in shadow trees. The beforescriptexecute and afterscriptexecute events won’t work, since they are already disabled for modules and only makes sense together with currentScript, which cannot be made to work for shadow scripts. See #1013 for details. Fixes #762.
Is it correct to say that <script type="module">
document.currentScript == null;
</script> Even if it were set in this case, I'd still want something similar to currentScript within imported modules. One use-case I have is to insert a template directly after the module script. I like a variant of |
That's correct. It should really be current; I don't see why it wouldn't be. We're still kind of waiting on TC39 to figure out the import metaproperty stuff though. |
It's worth mentioning that we have a path forward here without waiting on TC39, which is to reserve a special module name. So then we'd do something like import { currentScript, url } from "js:context"; |
Note also feature detection here also seems tricky. I assume |
It's not directly related to this, I would like to note that I would like to have the similar thing in the module script in workers. See detailed thing why I would like to get it even in the workers. in tc39/proposal-dynamic-import#37. |
I think we should spec something here soon that people are willing to ship. Node.js is also interested in getting agreement on this. I'd like to propose import { url, currentScript } from "js:context"; to get the current script URL (which could be that of a HTML file for inline scripts), and the current How does that sound to implementers? /cc @whatwg/modules |
|
Maybe it would be worth carving out the entire |
It effectively already is carved out, since fetching any URL that starts with |
Hate to tie this in to other bike-shedding, but it would be good if whatever naming scheme we choose matches TC39's naming scheme for internal modules (if they do arise). |
That seems like such a hack. There is a proposal for |
@rniwa how is it a hack if it needs context to the environment it was invoked from (in this case, the location of |
I don't think @rniwa, putting |
I don't like it. Conceptually, what you're trying to figure out the information about the current module, not about "js:context". Why is the url of "js:context" not "js:context"? |
@rniwa you are grabbing an export of "js:context" named |
"js:context" is a contextual module specifier. I can see the confusion because it looks like a URL and not a specifier. Given that it is a specifier distinct to the module loading it, what does it resolve to? What's the algorithm? Let's pretend for a second that the algorithm resolves to "js:context:https://example.com/foo.js" where "https://example.com/foo.js" is the importing module. Would this mean that, I could do: import { url, currentScript } from "js:context:https://example.com/bar.js" To grab another module's url/currentScript? Or would this be restricted? |
alternative proposition would be to imply the scope in 'document.getRootNode()' call. If the call originated from script within shadow dom, return its ownerElement instead of global document object. |
Okay, people commenting on this thread need to read my comments in #1013 (comment) and #1013 (comment) Because a given module gets executed exactly once regardless of how many times it gets referenced / imported, I don't think returning a single element is enough for address many use cases enumerated here. We need a mechanism to enumerate all script elements which references a given module. |
With a function that is triggered when the script element is encountered, it would suffice to set a property for the duration of that function call, or have some other function return the script tag reference for the duration of the trigger function call. The trigger function would be responsible for storing that ref if needed. |
This issue is not just applicable to I have a widget where all the css, markup and logic is contained in a single html file. This widget is meant to be injected as a custom element into third party pages that I have no control over. Without a quick reference to the shadow root I have to jump through hoops to pass my own reference in. My current workaround is to have the page owner include a single script tag at the location they want the widget to appear. This script adds a small helper framework to the document head which includes a global function that I call from within the widget script. This global function adds an event listener to the document body that executes the provided callback function. The initial loader script then replaces itself with a custom element and when the Maybe I'm overcomplicating things, but that's a pretty convoluted dance to go through all because there is no Here's a link to the repo containing my demo if anyone wants to see exactly what I'm doing. https://github.com/besworks/widget-injector |
This integrates with the stage 3 import.meta proposal located at https://github.com/tc39/proposal-import-meta/. This is based on https://github.com/tc39/proposal-import-meta/blob/f5d39bc471a5bf2791708f9a3fec943380d9e3ee/HTML%20Integration.md although it only includes the easier part, import.meta.url. import.meta.scriptElement is still being discussed, at whatwg#1013, and as such is excluded for now.
Why can't we just know the current node the script is in? Especially if it's the top level script being included... certainly for imports of imported scripts the currentNode shouldn't apply... 'use import.meta' is only helpful if the source of <html>
<head>
</head>
<body>
<div id="profileContainer"></div>
</body>
<script type="module">
const rootNode = document.currentScript.parentNode;
/* get something... */ rootNode.querySelector("#profileContainer")
</script>
</html> Otherwise I guess I can select all scripts and somehow figure out if one is 'me'? |
@d3x0r you say "top-level script", but read this comment (and there's others in the thread above) as to why that's a fuzzy concept here—that top-level script is only evaluated once, no matter how many times you include it: <script type="module" src="entrypoint.js"></script>
<script type="module" src="entrypoint.js"></script>
<script type="module"> import './entrypoint.js'; </script> So what's the Yes, for the inline case I guess there'll only ever be one script. But at that point it seems like you already know "where you are". Just my 2c. |
the first two are their own tag; the third, entrypoint.js doesn't get the script... https://github.com/d3x0r/sack.vfs/tree/master/tests/objstore/userDb/ui/profile Is what I was playing with.
The popup library The index.html will eventually be some other service, which just includes "login.js" sort of script, and provide a 'login-with' sort of functionality... so there's no telling what the outside framing will really be. certainly I can hear people saying 'don't do that' without a single answer to 'why' so... religious opinions aside... that's what I was attempting to do. As a workaround |
I'm not sure what do you mean there. In the example given, |
I would also like a way to get the ShadowRoot object from with in the shadow DOM's own scripts. Wasn't this the whole point of encapsulation? Am I missing something? I honestly don't know. |
@whaaaley and others, maybe this will help you out. I can't remember where/when I came up with this method but it definitely belongs in this thread :
This runs any script elements in the shadowRoot of a custom element with the host object as the scope. Here's a more thorough example using a closed shadowRoot. |
@besworks the number of reasons that’s unsound are too many to enumerate. it may suit a particular case but should not be promoted as a general solution. |
@bathos, obviously the correct solution in most cases would be to simply not use script tags inside your shadow dom at all but for those few cases that it's necessary (like importing a clunky old library) this trick is like magic. The snippet above is clearly a simplified demonstration. As you can see in the jsfiddle I used #private class properties and methods to make sure this cannot be executed from outside the component. It also works with a closed shadow root so there's no worry about script injection that way. Can you think of any other ways this could be abused? |
The reason I want this is because I've been experimenting with different ways to load random components built with modern frameworks into shadow roots. Assuming you're using some kind of virtual dom framework in the parent application you could do something like this with a global. function React () {
// eslint-disable-next-line no-return-assign
return <ShadowRoot id='react'>
{window._reactRef = <div id='root'></div>}
<script type='module'>
{`
import 'https://unpkg.com/react@18.1.0/umd/react.production.min.js'
import 'https://unpkg.com/react-dom@18.1.0/umd/react-dom.production.min.js'
const root = ReactDOM.createRoot(window._reactRef.node)
root.render(
React.createElement(React.StrictMode, {},
React.createElement('div', {}, 'hello from react')
)
)
`}
</script>
</ShadowRoot>
} I started to wonder though. Why is I mean don't mind me. I don't know much about the details of the module system, but ergonomics wise, and for symmetry with how you'd do this without shadowDOM, this makes sense to me over something to do with the import statement. Maybe this is naive, but to me it sounds to me like |
@whaaaley, I've done some experimenting with various ways to inject scripts into shadowRoots as well and just posted a very thorough overview of this problem in WICG/webcomponents#717 (comment) I believe this discussion more appropriately belongs in that thread, even though it's closed. The |
Kind've, basically The advantage of having a separate thing, is that It would be plausible to add If HTML modules ever get implemented, there would be a similar inline-only script feature ( |
|
I'm down for any solution that will give me a path to get access to the shadow root document. Is there any chance we can get access to the current document in |
@besworks I read your post in the other thread. I can see both ideas being correct. However, I think the path of least resistance is probably slapping more stuff onto |
I agree that |
@besworks The host document or the host node? 🤔 There's some stuff I could do with a host node to get some desired behavior at least. But not with a host document. |
Here's an example of what I mean :
You might expect that after the script was appended to the shadowRoot it would no longer be part of the host document. But this not the case. The shadowRoot is not actually a separate Document but rather a DocumentFragment contained as a property of the host element. We have full access to the host document scope and every element in it but no access to the scope the script is actually connected to, unless you drill down into it from outside, which you can't do if you use a closed shadowRoot. So, even if you were to access the hypothetical One technique I experimented with involved building a virtual document to process my scripts in with the intention of appending the output to the shadowRoot afterwards. This didn't work out because according to section 8.1.3.4 of the spec
Which is exactly the case when using So, with all that in mind, the best workaround I've come up with, is the spooky dark magic method that always seems to ruffle everyone's feathers. But it gets the job done and, if used responsibly, overcomes all of these problems. |
I for one want to |
not sure if it's entirely related, but adding a use case here anyway. i'm trying to write some isolated code examples for a documentation site. i wanted to use declarative shadow dom for this, so styles and IDs do not leak out. but i also wanted to include some scripts as part of demos e.g. <div class="example">
<template shadowrootmode="open">
<style>
button {
color: red;
}
</style>
<button>click me</button>
<script>
// how to select the button?
</script>
</template>
</div> e.g. here i wanted to log a click on the button. but there's no easy way to get a reference to the button which doesn't completely detract from the example itself. it would be nice if there were something like |
There was an offline discussion about
document.currentScript
and shadow trees and the suggestion came up to solve it in a different way for module scripts so that the global object would not be involved and the value would only be accessible to the script currently executing.The text was updated successfully, but these errors were encountered: