Skip to content
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

Open
annevk opened this issue Apr 8, 2016 · 95 comments
Open

Introduce pointer to <script> element in module scripts #1013

annevk opened this issue Apr 8, 2016 · 95 comments

Comments

@annevk
Copy link
Member

@annevk annevk commented Apr 8, 2016

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.

@domenic
Copy link
Member

@domenic domenic commented Apr 8, 2016

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 (currently the only metaproperty in the language is new.target; this would introduce a few more):

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.

domenic added a commit that referenced this issue Apr 11, 2016
They no longer set document.currentScript, or fire beforescriptexecute
and afterscriptexecute events.

Closes #997. See #1013 for a future alternative to
document.currentScript, and #943 for more discussion of the events.
domenic added a commit that referenced this issue Apr 13, 2016
They set document.currentScript to null, and no longer fire
beforescriptexecute and afterscriptexecute events.

Closes #997. See #1013 for a future alternative to
document.currentScript, and #943 for more discussion of the events.
domenic added a commit that referenced this issue Apr 14, 2016
They set document.currentScript to null, and no longer fire
beforescriptexecute and afterscriptexecute events.

Closes #997. See #1013 for a future alternative to
document.currentScript, and #943 for more discussion of the events.
annevk added a commit that referenced this issue Apr 15, 2016
Make module scripts modify less global script state. They set document.currentScript to null, and no longer fire beforescriptexecute and afterscriptexecute events.

Closes #997. See #1013 for a future alternative to document.currentScript, and #943 for more discussion of the events.
annevk added a commit that referenced this issue Apr 22, 2016
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.
@annevk annevk mentioned this issue Apr 22, 2016
domenic added a commit that referenced this issue Apr 22, 2016
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.
annevk added a commit that referenced this issue Apr 23, 2016
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.
domenic added a commit that referenced this issue Apr 26, 2016
This makes <script> elements work when used in shadow trees.

Note that document.currentScript is set to null while running a <script>
in a shadow tree; see #1013 for details.

This takes care of most of #762, but it remains to make the "load" event
scoped, so we'll leave that issue open for now.
@matthewp
Copy link

@matthewp matthewp commented Oct 26, 2016

Is it correct to say that document.currentScript is not set, even for module scripts with no imports like:

<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 import.currentScript personally; I think introducing a special meta would invite questions like whether it is mutable or not. Maybe a better name came be thought of than currentScript (is it really current?) like ownerScript perhaps.

@domenic
Copy link
Member

@domenic domenic commented Oct 26, 2016

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.

@domenic
Copy link
Member

@domenic domenic commented Nov 10, 2016

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";
@guybedford
Copy link

@guybedford guybedford commented Jan 12, 2017

import.currentScript sounds ideal here. With the dynamic import landed, perhaps a spec here can start to gain momentum. Landing features such as these soon will help significantly to avoid fragmentation of the adoption path features.

Note also feature detection here also seems tricky. I assume typeof import.currentScript wouldn't work, so it would just fall back to a syntax error if it isn't supported?

@Constellation
Copy link
Member

@Constellation Constellation commented Feb 9, 2017

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.

@domenic
Copy link
Member

@domenic domenic commented Feb 22, 2017

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 <script> element (which could be null if no <script> is currently executing).

How does that sound to implementers? /cc @whatwg/modules

@bmeck
Copy link

@bmeck bmeck commented Feb 22, 2017

"js:context" looks fine but we are open to bikeshedding from Node.js side.

@bicknellr
Copy link
Contributor

@bicknellr bicknellr commented Feb 22, 2017

Maybe it would be worth carving out the entire js: protocol-like-prefix for future stuff like this?

@domenic
Copy link
Member

@domenic domenic commented Feb 22, 2017

It effectively already is carved out, since fetching any URL that starts with js: will fail per today's specs.

@ajklein
Copy link
Contributor

@ajklein ajklein commented Feb 22, 2017

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).

@rniwa
Copy link
Collaborator

@rniwa rniwa commented Feb 22, 2017

That seems like such a hack. There is a proposal for global which is encountering some compatibility issue. Perhaps global should also live there?

@bmeck
Copy link

@bmeck bmeck commented Feb 22, 2017

@rniwa how is it a hack if it needs context to the environment it was invoked from (in this case, the location of import or import())?

@domenic
Copy link
Member

@domenic domenic commented Feb 22, 2017

I don't think global makes sense to put in the context-specific module, but people have proposed putting it in a built-in module before. I'd suggest discussing that in the global repo.

@rniwa, putting global aside, I can't tell if you think the import { url } from "js:context" proposal is good or not?

@rniwa
Copy link
Collaborator

@rniwa rniwa commented Feb 23, 2017

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"?

@bmeck
Copy link

@bmeck bmeck commented Feb 23, 2017

@rniwa you are grabbing an export of "js:context" named url, I don't understand why you would think it is the url of "js:context" given people can do things like export let url = "foo" in modules. Would a name different from url be sufficient?

@matthewp
Copy link

@matthewp matthewp commented Feb 23, 2017

"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?

@domenic
Copy link
Member

@domenic domenic commented Sep 14, 2017

Agreed use cases will be useful. I've asked @justinfagnani to comment for Polymer which has plans to use this.

I anticipate most people won't be reusing a module script within multiple graphs on the page, so for their cases these issues won't matter that much. But I welcome correction from users.


Another issue raised by @nyaxt is that any design of import.meta.scriptElement implies holding a reference to the script element basically forever. Since the script element then has a pointer back to the Document (and that to Window), it keeps the entire DOM tree alive. I guess we could make it throw/return null if the page is navigated...

@matthewp
Copy link

@matthewp matthewp commented Sep 14, 2017

Is it being an Array not an option?

@domenic
Copy link
Member

@domenic domenic commented Sep 14, 2017

@matthewp one that mutates dynamically over time? (Also, what if someone pushes into it?) It seems a bit strange, especially since the script is only evaluated once, so it doesn't really have multiple script elements that actually affected the world, just one that did and a bunch of other no-ops.

Do you have a use case where a mutating-over-time array would work much better?

@matthewp
Copy link

@matthewp matthewp commented Sep 14, 2017

Probably a live NodeList instead of an array. I don't have any use-cases in favor of a list here, just the normal use cases for currentScript like @rniwa mentioned above. I use it to get the ownerDocument some times, but this is probably overly defensive.

I only mentioned a list structure to solve the problem of scriptElement possibly changing. I'd prefer a list that mutates over the reference changing to a different element. If it referred to only the first element, that would make sense to me as well.

@justinfagnani
Copy link

@justinfagnani justinfagnani commented Sep 16, 2017

So my use-cases would be around finding <template>s and other DOM resources associated with an element, test, etc. We have at times used more declarative styles of element and test definition where a script automatically finds its container or sibling. I know other projects have used currentScript to read attributes off the script element itself.

domenic added a commit that referenced this issue Oct 18, 2017
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 #1013, and as
such is excluded for now.
@rniwa
Copy link
Collaborator

@rniwa rniwa commented Oct 19, 2017

For finding templates, do you only care about the first script element which imports a given module script? Or do you care about every script element that imports your script?

@justinfagnani
Copy link

@justinfagnani justinfagnani commented Oct 19, 2017

Every script. The case would look something like this, simplified:

<x-declarative-element>
  <template>...</template>
  <script type="module">
    import {findTemplate} from '../x-element/x-element.js';
    const template = findTemplate(import.meta);
  </script>
</x-declarative-element>

Where findTemplate would use import.meta.scriptElement to find it's container, or sibling, etc.

@domenic domenic mentioned this issue Oct 20, 2017
3 of 5 tasks complete
domenic added a commit that referenced this issue Nov 7, 2017
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 #1013, and as
such is excluded for now.
@tomalec
Copy link

@tomalec tomalec commented Dec 14, 2017

I have also a use case for Shadow DOM. To do any DOM related scripting scoped to the shadow root <script> element is in.
And to let the script "avoid affecting the document tree" and work with actually encapsulated one.

<button>Pink like a deco umbrella</button>
<div>
  <!--shadowroot-->
    <red-door>I want it painted black</red-door>
    <script type="module">
      import {paintitblack} from '../stones.js';
      paintitblack(import.meta.scriptElement.getRootNode().querySelector('*'));
    </script>
  <!--/shadow-root-->
</div>

More at w3c/webcomponents#717

@Jamesernator
Copy link

@Jamesernator Jamesernator commented Dec 21, 2017

Something that seems to be the prevailing use-case is to access things near the script tag (whether shadow DOM/document/siblings/etc), but given that modules are only executed once per unique URL I think we'd also need another flag on script[type="module"] tags for these sort've cases.

For example perhaps a unique attribute that causes the script to be executed as if it were a unique script (similar to how every inline script is unique).

e.g. In this example the script would be executed twice, once with the first script tag and again with the second script tag, effectively as if they were both written as inline script tags containing the contents of the script they were loading (although a nice bonus is it would only need to fetch once):

<script unique type="module" src="./someScriptToBeExecutedAgainstCurrentDocumentOrShadowRoot.js"></script>

<div>
   <!--Shadowroot-->
   <script unique type="module" src="./someScriptToBe..."></script>
   <!--/ShadowRoot-->
</div>

Of course this could just be solved by using inline scripts directly as that's effectively what my suggestion is equivalent to, but it leads to quite a bit of boilerplate e.g.:

<script type="module">
    import someCodeToBeExecuted from "./someCodeToBeExecuted.js";
    someCodeToBeExecuted(import.meta.scriptElement);
</script>
<div>
   <!--ShadowRoot-->
   <script type="module">
      import someCodeToBeExecuted from "./someCodeToBeExecuted.js";
      someCodeToBeExecuted(import.meta.scriptElement);
   </script>
   <!--/ShadowRoot-->
</div>
@mk-pmb
Copy link

@mk-pmb mk-pmb commented Dec 27, 2017

I often use a reference to the script tag to

  • read its innerHTML where I put configuration data in a block comment.
  • insert DOM nodes before the script tag
  • then remove the script tag to work around some edge cases when saving the page and loading it from disk again.

Since I like to use weak netbooks while traveling, I fear that having a long-lived reference to the script tag will invite implementation scenarios where memory is wasted without good reason. I'd thus prefer some getter function so scripts have to opt-in to the reference, and any script that doesn't care, won't stall garbage collection.

@mk-pmb
Copy link

@mk-pmb mk-pmb commented Dec 27, 2017

For example perhaps a unique attribute

I consider that name confusing, as it's conceptually near "once", so it could just as well mean its opposite. How about "again", "run-again", "init-again" or sth. like that?
Update: Nope, that's confusing when it appears on the first script tag for that URL. Can't use "nocache" either because that might be interpreted as being about the transport layer.

@sohrabsaran
Copy link

@sohrabsaran sohrabsaran commented Aug 18, 2018

Leaving aside implementation challenges for the moment, it is not clear that what is semantically incorrect about reusing 'document.currentScript'?
(Searched this thread for the word 'semantic'. Also went through the opening post and it did not give much clue).

@mk-pmb
Copy link

@mk-pmb mk-pmb commented Aug 19, 2018

Meanwhile, for the "unique" attribute idea, I came to the conclusion that an "all or nothing" decision on whether to run the module again probably is suboptimal. In most use cases, we'd probably want the performance benefits that we can get from having the module cached and having the JS engine able to reuse optimization data that it might have collected on the module's behavior.

To avoid the boilerplate described by @Jamesernator, I suggest we make an attribute that specifies a name of an export that should be invoked as a function. Omitting the value or having it be the empty string should use the default export. I don't know a good name for that attribute so I suggest initfunc and hope "call" until someone can come up with a better name.

Update 1: Maybe it should be on… (onparse? onencounter?) so that various HTML sanitizer libs can detect it as potentially evil. Then again, it would be on a script tag that has an src attribute. In an ideal world, it wouldn't matter.
Update 2: Probably neither onparse nor onload, as they could sound related to the module instead of the script tag.

Update 3: FUD is a bad reason for an on prefix, and it's misleading because the existing on… attributes are to carry JS code, not just an identifier. How about just call? (Because "init" would imply an assumption about what the func does, and func is redundant.)

@nektro
Copy link

@nektro nektro commented Oct 13, 2018

What's the timeline on import.meta.scriptElement being available / why not just make document.currentScript a parent/childless reference? To not X/Y problem why I'm looking for this, I was really looking for document.currentScript.dataset

@tomalec
Copy link

@tomalec tomalec commented Oct 15, 2018

why not just make document.currentScript a parent/childless reference?

Because current <script> may be placed in a shadow root, then another script would get access to this shadow root, what breaks the encapsulation. See w3c/webcomponents#717 (comment)

We a module needs a way to get the reference to the <script> element used in shadow root.

@sashafirsov
Copy link

@sashafirsov sashafirsov commented Nov 1, 2018

I guess another use case is worth to be mentioned here. From the script inside of shadow root there is a need to get some elements. Note the script could be defined outside either loaded dynamically or inline(via slot).
The script then need to access its owner element and than do the query. Pseudo-code:

let scriptNode = document.currentScript
,     parentComponent = recursiveUpLookup(scriptNode) // or scriptNode.getRootNode()
,     chart = parentComponent.querySelector('.chart');
// draw chart here

At this stage there is no way to identify where the script is called from as document.currentScript is null.

@sashafirsov
Copy link

@sashafirsov sashafirsov commented Nov 1, 2018

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.

@rniwa
Copy link
Collaborator

@rniwa rniwa commented Nov 6, 2018

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.

@mk-pmb
Copy link

@mk-pmb mk-pmb commented Nov 6, 2018

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.

@besworks
Copy link

@besworks besworks commented Nov 10, 2018

This issue is not just applicable to <script type="module">. Any script element inside a shadow root, whether it is a module or not, should have some way to access that shadow root. Consider this use case:

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 connectedCallback is triggered a bubbling event is fired that executes the widget logic.

Maybe I'm overcomplicating things, but that's a pretty convoluted dance to go through all because there is no document.currentShadowRoot or similar property...

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

alice added a commit to alice/html that referenced this issue Jan 8, 2019
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.