From db03474b8b87aab3454ff7d5c1f4a5f044b4395c Mon Sep 17 00:00:00 2001 From: Daniel Ehrenberg Date: Fri, 17 May 2019 20:29:34 +0200 Subject: [PATCH] JSON module support This commit adds JSON modules as a single default export, with parse errors checked before instantiating the module graph. As infrastructure, this divides the "module script" concept into "JavaScript module scripts" and "JSON module scripts". Most of the spec's existing uses of "module script" become "JavaScript module script". JSON module scripts are fetched in the same way as JavaScript module scripts, e.g. with the "cors" mode and using strict MIME type checking. They use the Synthetic Module Record defined in https://github.com/heycam/webidl/pull/722. Closes #4315. Closes https://github.com/w3c/webcomponents/issues/770. --- source | 347 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 260 insertions(+), 87 deletions(-) diff --git a/source b/source index 9c83e682053..b5438897246 100644 --- a/source +++ b/source @@ -2914,6 +2914,9 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
  • converting to a sequence of Unicode scalar values
  • overload resolution algorithm
  • +
  • CreateSyntheticModule
  • +
  • SetSyntheticModuleExport
  • +
  • Synthetic Module Record
  • The Web IDL specification also defines the following types that are used in Web IDL fragments @@ -3017,7 +3020,8 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute @@toPrimitive, and @@toStringTag

  • Well-Known Intrinsic Objects, including - %ArrayPrototype% and + %ArrayPrototype%, + %JSONParse%, and %ObjProto_valueOf%
  • The FunctionBody production
  • @@ -3031,6 +3035,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute Record specification types
  • The Property Descriptor specification type
  • The Script Record specification type
  • +
  • The Cyclic Module Record specification type
  • The Source Text Module Record specification type and its Evaluate and Instantiate methods
  • @@ -3116,6 +3121,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute

    User agents that support JavaScript must also implement the BigInt proposal.

    + @@ -58008,11 +58014,14 @@ interface HTMLScriptElement : HTMLElement { redundantly setting it.

  • Setting the attribute to an ASCII case-insensitive match for the string - "module" means that the script is a module script, to be - interpreted according to the JavaScript Module top-level - production. Module scripts are not affected by the defer - attribute, but are affected by the async attribute - (regardless of the state of the src attribute).

  • + "module" means that the script is a module script. If it has + a JavaScript MIME type, or if the script is embedded inline, then it will be + interpreted as a JavaScript module script according to the JavaScript Module top-level production; if it has a JSON MIME + type, then it will be interpreted as a JSON module script. Module scripts + are not affected by the defer attribute, but are + affected by the async attribute (regardless of the state + of the src attribute).

  • Setting the attribute to any other value means that the script is a data block, which is not processed. None of the script attributes (except HTMLScriptElement : HTMLElement { scripts. By using a valid MIME type string now, you ensure that your data block will not ever be reinterpreted as a different script type, even in future user agents.

    -

    Classic scripts and module - scripts may either be embedded inline or may be imported from an external file using the - Classic scripts and JavaScript module scripts can be embedded inline, or be imported from an external + file using the src attribute, which if specified gives the URL of the external script resource to use. If src is specified, - it must be a valid non-empty URL potentially surrounded by spaces. The contents of - inline script elements, or the external script resource, must conform with the - requirements of the JavaScript specification's Script or Module productions, for classic - scripts and module scripts respectively.

    + it must be a valid non-empty URL potentially surrounded by spaces.

    + +

    The contents of inline script elements, or the external script resource, must + conform with the requirements of the JavaScript specification's Script or Module productions, for classic scripts and JavaScript module scripts respectively.

    + +

    The contents of the external script resource for JSON module + scripts must conform to the requirements of the JSON specification .

    When used to include data blocks, the data must be embedded inline, the format of the data must be given using the type @@ -58280,7 +58293,7 @@ o............A....e

    The following sample shows how a script element can be used to include an - external module script. + external JavaScript module script.

    <script type="module" src="app.mjs"></script>
    @@ -58291,37 +58304,37 @@ o............A....e

    Additionally, if code from another script element in the same Window imports the module from app.mjs (e.g. via import - "./app.mjs";), then the same module script created by the + "./app.mjs";), then the same JavaScript module script created by the former script element will be imported.

    -

    This example shows how to include a module script for modern user agents, and a - classic script for older user agents:

    +

    This example shows how to include a JavaScript module script for modern user + agents, and a classic script for older user agents:

    <script type="module" src="app.mjs"></script>
     <script nomodule src="classic-app-bundle.js"></script>
    -

    In modern user agents that support module scripts, the - script element with the nomodule attribute - will be ignored, and the script element with a type of "module" will be fetched and - evaluated (as a module script). Conversely, older user agents will ignore the +

    In modern user agents that support JavaScript module + scripts, the script element with the nomodule attribute will be ignored, and the script element with a type of "module", as that is an unknown script type for them — but they will have no - problem fetching and evaluating the other script element (as a classic - script), since they do not implement the nomodule attribute.

    + data-x="">module
    " will be fetched and evaluated (as a JavaScript module + script). Conversely, older user agents will ignore the script element with a + type of "module", as that is an + unknown script type for them — but they will have no problem fetching and evaluating the other + script element (as a classic script), since they do not implement the + nomodule attribute.

    The following sample shows how a script element can be used to write an inline - module script that performs a number of substitutions on the document's text, in - order to make for a more interesting reading experience (e.g. on a news site): JavaScript module script that performs a number of substitutions on the document's + text, in order to make for a more interesting reading experience (e.g. on a news site):

    <script type="module">
    @@ -58353,12 +58366,30 @@ o............A....e
      walkAllTextNodeDescendants(document.body, substitute);
     </script>
    -

    Some notable features gained by using a module script include the ability to import functions - from other JavaScript modules, strict mode by default, and how top-level declarations do not - introduce new properties onto the global object. Also note that no matter where - this script element appears in the document, it will not be evaluated until both - document parsing has complete and its dependency (dom-utils.mjs) has been - fetched and evaluated.

    +

    Some notable features gained by using a JavaScript module script include the ability to import + functions from other JavaScript modules, strict mode by default, and how top-level declarations + do not introduce new properties onto the global object. Also note that no matter + where this script element appears in the document, it will not be evaluated until + both document parsing has complete and its dependency (dom-utils.mjs) has + been fetched and evaluated.

    + +
    + +
    + +

    The following sample shows how to you can import a JSON module script from inside + a JavaScript module script:

    + +
    <script type="module">
    +import peopleInSpace from "http://api.open-notify.org/astros.json";
    +
    +const list = document.querySelector("#people-in-space");
    +for (const { craft, name } of peopleInSpace.people) {
    +  const li = document.createElement("li");
    +  li.textContent = `${name} / ${craft}`;
    +  list.append(li);
    +}
    +</script>
    @@ -58778,9 +58809,9 @@ o............A....e
    "module"
      -
    1. Let script be the result of creating a module script using - source text, settings object, base URL, and - options.

    2. +
    3. Let script be the result of creating a JavaScript module + script using source text, settings object, base + URL, and options.

    4. If this returns null, set the script's script to null and return; the script is ready.

    5. @@ -87471,7 +87502,7 @@ interface ApplicationCache : EventTarget {
      Definitions
      -

      A script is one of two possible A script is one of three possible structs. All scripts have:

      @@ -87482,10 +87513,22 @@ interface ApplicationCache : EventTarget {
      A record
      -

      Either a Script Record, for classic - scripts; a Source Text Module Record, for module scripts; or null. In the former two cases, it represents a parsed script; - null represents a failure parsing.

      +
      +

      One of the following:

      + +
        +
      • a script record, for classic + scripts;

      • + +
      • a Source Text Module Record, for JavaScript module scripts;

      • + +
      • a Synthetic Module Record, for JSON + module scripts; or

      • + +
      • null, representing a parsing failure.

      • +
      +
      A parse error
      @@ -87537,6 +87580,35 @@ interface ApplicationCache : EventTarget { data-x="concept-script">script. It has no additional items.

      +

      Module scripts can be classified into two types:

      + +
        +
      • A module script is a JavaScript module script if + its record is a Source Text Module + Record.

      • + +
      • +

        A module script is a JSON module script if its record is a Synthetic Module Record, and it + was created via the create a JSON module + script algorithm. JSON module scripts represent a parsed JSON document.

        + + +

        As JSON documents do not import dependent modules, and do not throw exceptions + on evaluation, the fetch options and + base URL of a JSON module script are + always null.

        +
      • +
      +

      The active script is determined by the following algorithm:

        @@ -88175,25 +88247,53 @@ interface ApplicationCache : EventTarget {
      1. response's status is not an ok status

      2. + + -
      3. -

        The result of extracting a MIME type from - response's header list is not a - JavaScript MIME type

        +
      4. +

        Let type be the result of extracting a + MIME type from response's header + list.

        -

        For historical reasons, fetching a - classic script does not include MIME type checking. In contrast, module scripts will - fail to load if they are not of a correct MIME type.

        -
      5. - +

        For historical reasons, fetching a + classic script does not include MIME type checking. In contrast, module scripts' + interpretation is driven by their MIME type, and they will fail to load if they are not of + a supported MIME type.

        -
      6. Let source text be the result of UTF-8 - decoding response's body.

      7. +
      8. +

        Let module script be null.

        + +

        If the resource does not have a MIME type which HTML knows how to handle + as a module, then module script will remain null, which is interpreted as + failure.

        +
      9. -
      10. Let module script be the result of creating a module script given - source text, module map settings object, response's url, and options.

      11. +
      12. +

        If type is a JavaScript MIME type, then:

        + +
          +
        1. Let source text be the result of UTF-8 + decoding response's body.

        2. + +
        3. Set module script to the result of creating a JavaScript module + script given source text, module map settings object, + response's url, and + options.

        4. +
        +
      13. + +
      14. +

        If type is a JSON MIME type, then:

        + +
          +
        1. Let source text be the result of UTF-8 + decoding response's body.

        2. + +
        3. Set module script to the result of creating a JSON module + script given source text and module map settings object.

        4. +
        +
      15. Set moduleMap[url] to module @@ -88220,7 +88320,8 @@ interface ApplicationCache : EventTarget {

      16. Let record be module script's record.

      17. -
      18. If record.[[RequestedModules]] is empty, +

      19. If record is not a Cyclic Module Record, or if + record.[[RequestedModules]] is empty, asynchronously complete this algorithm with module script.

      20. Let urls be a new empty list.

      21. @@ -88256,6 +88357,9 @@ interface ApplicationCache : EventTarget {
      22. Let options be the descendant script fetch options for module script's fetch options.

      23. +
      24. Assert: options is not null, as module script is a JavaScript + module script.

      25. +
      26. For each url in urls, perform the internal module script graph fetching procedure given url, @@ -88347,36 +88451,43 @@ interface ApplicationCache : EventTarget { then return moduleScript's parse error.

      27. -
      28. Let childSpecifiers be the value of moduleScript's record's [[RequestedModules]] internal slot.

      29. +
      30. +

        If moduleScript's record is a + Cyclic Module Record:

        -
      31. Let childURLs be the list obtained by calling - resolve a module specifier once for each item of childSpecifiers, given - moduleScript's base URL and that item. - (None of these will ever fail, as otherwise moduleScript would have been marked as - itself having a parse error.)

      32. +
          +
        1. Let childSpecifiers be the value of moduleScript's record's [[RequestedModules]] internal slot.

        2. -
        3. Let childModules be the list obtained by getting each value in moduleMap whose key is given by an - item of childURLs.

        4. +
        5. Let childURLs be the list obtained by calling + resolve a module specifier once for each item of childSpecifiers, given + moduleScript's base URL and that item. + (None of these will ever fail, as otherwise moduleScript would have been marked as + itself having a parse error.)

        6. -
        7. -

          For each childModule of - childModules:

          +
        8. Let childModules be the list obtained by getting each value in moduleMap whose key is given by an + item of childURLs.

        9. -
            -
          1. Assert: childModule is a module script (i.e., it is not "fetching" or null); by now all module - scripts in the graph rooted at moduleScript will have successfully been - fetched.

          2. +
          3. +

            For each childModule of + childModules:

            -
          4. If discoveredSet already contains - childModule, continue.

          5. +
              +
            1. Assert: childModule is a module script (i.e., it is not "fetching" or null); by now all module + scripts in the graph rooted at moduleScript will have successfully been + fetched.

            2. -
            3. Let childParseError be the result of finding the first parse - error given childModule and discoveredSet.

            4. +
            5. If discoveredSet already contains + childModule, continue.

            6. -
            7. If childParseError is not null, return childParseError.

            8. +
            9. Let childParseError be the result of finding the first parse + error given childModule and discoveredSet.

            10. + +
            11. If childParseError is not null, return childParseError.

            12. +
            +
          @@ -88440,10 +88551,10 @@ interface ApplicationCache : EventTarget {
        10. Return script.

        -

        To create a module script, given a - JavaScript string source, an environment settings object - settings, a URL baseURL, and some script fetch - options options:

        +

        To create a JavaScript module script, + given a JavaScript string source, an environment settings + object settings, a URL baseURL, and some script + fetch options options:

        1. If scripting is disabled for @@ -88519,6 +88630,53 @@ interface ApplicationCache : EventTarget {

        2. Return script.

        +

        To create a JSON module script, given a + string source and an environment settings object settings:

        + +
          +
        1. Let script be a new module script that this algorithm will + subsequently initialize.

        2. + +
        3. Set script's settings object to settings.

        4. + +
        5. Set script's base URL and + fetch options to null.

        6. + +
        7. Set script's parse error and + error to rethrow to null.

        8. + +
        9. +

          Let json be ? Call(%JSONParse%, undefined, + « source »).

          + +

          If this throws an exception, set script's parse error to that exception, and return + script.

          +
        10. + +
        11. Set script's record to the result + of creating a synthetic + module record with a default export of json with settings.

        12. + +
        13. Return script.

        14. +
        + +

        To create a synthetic module record with a default export of a JavaScript value + value with an environment settings object settings:

        + +
          +
        1. +

          Return CreateSyntheticModule(« "default" », the following steps, + settings's Realm, + value) with the following steps given module as an argument:

          + +
            +
          1. SetSyntheticModuleExport(module, "default", + module.[[HostDefined]]).
          2. +
          +
        2. +
        +
        Calling scripts

        To run a classic script given a classic script @@ -89631,6 +89789,9 @@ import "https://example.com/foo/../module2.mjs";

      33. Set base URL to referencing script's base URL.

      34. + +
      35. Assert: base URL is not null, as referencing script is a + classic script or a JavaScript module script.

      @@ -89704,6 +89865,10 @@ import "https://example.com/foo/../module2.mjs";
    6. Set fetch options to the descendant script fetch options for referencing script's fetch options.

    7. + +
    8. Assert: Neither base URL nor fetch options is null, as + referencing script is a classic script or a JavaScript module + script.

    As explained above for HostResolveImportedModule, in the common @@ -89769,6 +89934,10 @@ import "https://example.com/foo/../module2.mjs";

    1. Let module script be moduleRecord.[[HostDefined]].

    2. +
    3. Assert: module script's base + URL is not null, as module script is a JavaScript module + script.

    4. +
    5. Let urlString be module script's base URL, serialized.

    6. @@ -93124,6 +93293,9 @@ document.body.appendChild(frame)
    7. Let base URL be initiating script's base URL.

    8. +
    9. Assert: base URL is not null, as initiating script is a + classic script or a JavaScript module script.

    10. +
    11. Let fetch options be a script fetch options whose cryptographic nonce is initiating @@ -123371,7 +123543,6 @@ INSERT INTERFACES HERE Cyrille Tuzi, Daksh Shah, Dan Callahan, - Dan Ehrenberg, Dan Yoder, Dane Foster, Daniel Barclay, @@ -123381,6 +123552,7 @@ INSERT INTERFACES HERE Daniel Buchner, Daniel Cheng, Daniel Davis, + Daniel Ehrenberg, Daniel Glazman, Daniel Holbert, Daniel Peng, @@ -123856,6 +124028,7 @@ INSERT INTERFACES HERE 邱慕安 (Mu-An Chiou), Mukilan Thiyagarajan, Mustaq Ahmed, + Myles Borins, Nadia Heninger, NARUSE Yui, Navid Zolghadr,