1 of 15

Module fragments:

For stage 1

Daniel Ehrenberg

Igalia in partnership with

March 2021 TC39 meeting

@littledan

2 of 15

My hope:�Load ES modules�natively and efficiently

@littledan

3 of 15

Will module blocks solve this problem?

@littledan

4 of 15

No, because module blocks can't import each other

const countBlock = module {

let i = 0;

export function count() {

i++;

return i;

}

};

const uppercaseBlock = module {

export function uppercase(string) {

return string.toUpperCase();

}

};

const combinedBlock = module {

const { count } = await import(countBlock);

const { uppercase } =� await import(uppercaseBlock);

console.log(count()); // 1

console.log(uppercase("daniel")); // "DANIEL"

};

// ReferenceError as we can't close over count or uppercase!!

@littledan

5 of 15

Instead, we need to statically bind module map entries

module "#count" {

let i = 0;

export function count() {

i++;

return i;

}

}

module "#uppercase" {

export function uppercase(string) {

return string.toUpperCase();

}

}

module "#combined" {

import { count } from "#count";

import { uppercase } from "#uppercase";

console.log(count()); // 1

console.log(uppercase("daniel")); // "DANIEL"

};

@littledan

6 of 15

Isn't it better to use more general resource bundles?

  • I claimed this in my November 2020 presentation
  • But: resource bundles may be too slow
    • Resource bundles work at the "fetch"/network level -- heavier
    • Module fragments work at the module map level -- lighter
  • Applications contain:
    • 10-500 of chunks -- benefit from network level
    • 10k-100k source JS modules -- too many!
  • Resource bundles can scale up to chunks, not source modules
  • Module fragments could be lighter-weight
  • Intention: Investigate performance hypotheses in prototype implementations

@littledan

7 of 15

Relevance for content blocking

  • I want bundling to be compatible with content blocking
  • Content blockers are based on URL filtering
  • Many patterns (100k+)
  • If filters had to be applied for each source JS module, it's too much
  • Cheaper if filtering happens once for a big module with several fragments

@littledan

8 of 15

Individual resources

Bundling

JS module fragments

Resource bundles

🌊 Waterfall

🗐Per-resource overhead

🕹️Emulation cost

⇆ Inter-process communication

🗜️Compression

≡ Parallel processing

🚿 Streaming

🪓 Code splitting/chunking

💰 Caching

BROAD INACCURATE BRUSH, sorry

@littledan

9 of 15

Testing use case

  • Idea from James Kyle
  • Test framework runs #test directly
  • Production: tree shaking removes #test

// math.js

export function add(a, b) {

return a + b

}

export module "#test" {

import { add } from "#"

export function addTest() {

assert(add(35, 7) === 42)

}

}

@littledan

10 of 15

Do module fragments subsume module blocks?

  • No, because module fragments only work in ESM
    • ESM can be deployed sometimes, but it's a barrier
  • Whereas module blocks help with deploying workers, nested anywhere!
    • Scripts (which may be transpiled CJS)
    • Modules that are inline in HTML
    • eval
    • Deep inside nested functions/control flow
  • So, I think we want both proposals

  • Everyone seems to think they can be unified somehow. If they can be, great!
  • An interesting idea from Gus

@littledan

11 of 15

Why fragments? Why # ?

  • @littledan's plot: Put # everywhere in JavaScript :)

  • Previously discussed as "inline modules"
    • (term is ambiguous with module blocks)
  • Fragments ties in with URL/URI fragment identifiers
    • But the interpretation of module specifiers is host-dependent; does not need to be URL
  • Indicates a clear way to refer to a module fragment from a different file

@littledan

12 of 15

Exported vs local module fragments (#4)

// lib.js

// Only accessible within this file

module '#private' {

export function doSomethingPrivate() {}

}

// Accessible from outside the file.

export module '#public' {

import {doSomethingPrivate} from '#private';

export function publicFunction() {

doSomethingPrivate();

}

}

// usage.js

import {publicFunction} from "./lib.js#public";

@littledan

13 of 15

Concerns about using #

  • Some people don't seem to like the idea of #; I'm not so attached to it
  • Overlaps with Node.js package-local conditional imports
  • Overlaps with how you can already have a fragment in the Web's specifiers
  • I'm happy to consider other ideas for local, statically declared inline modules
  • An interesting idea from Gus

@littledan

14 of 15

HTML integration

  • Module map mechanics
    • module fragments are cached eagerly when the enclosing module is parsed
  • import.meta.url
    • https://path/to/enclosing-module.js#fragment
  • Early error on duplicate fragment identifier
  • Shadows the existing meaning of fragments
    • easy fallback to current semantics

@littledan

15 of 15

tl;dr

  • This isn't a new idea
    • Inline modules were discussed extensively in ES6
    • Thinking of them as "fragments" might be new, but it might be a bad idea
  • I thought it would be redundant to resource bundles
  • Now I think they're complimentary
  • Should we add module fragments to JavaScript?�
  • Proposed scope for Stage 1:�Inline modules that are in the module map somehow�(Not necessarily by using URL fragments exactly)�
  • Stage 1?
    • Anyone wanna be co-champion?

@littledan