Multi Module Files

Rule: A file that imports others, should still be valid when replacing those lines with their file contents

Rule: A module system should be simple enough to string together with awk/grep/sed

C/C++

            
//add.c
int add(int a, int b) {
  return a + b;
}

//main.c
#include "add.c"
int main() {
  printf( "%d", add(1, 1) );
  return 0;
}
            
          

C/C++ - single file

            
//add.c
int add(int a, int b) {
  return a + b;
}

//main.c
#include "add.h"
int main() {
  printf( "%d", add(1, 1) );
  return 0;
}
            
          

gcc -e main.c

            # 1 "main.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 331 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "main.c" 2
# 1 "./add.c" 1
int add(int a, int b) {
  return a + b;
}
# 2 "main.c" 2
int main() {
  printf( "%d", add(1, 1) );
  return 0;
}
            
          

Rust

            
//add.rs
pub fn add(a: i32, b: i32) -> i32 {
  a + b
}

//main.rs
mod add;
fn main() {
  println!("{}", add::add(1, 1));
}
            
          

Rust - single file

            
//main.rs
mod add {
    pub fn add(a: i32, b: i32) -> i32 {
      a + b
    }
}
fn main() {
  println!("{}", add::add(1, 1));
}
            
          

Ruby

            
# add.rb
def add(a, b)
  a + b
end

# main.rb
require_relative "add.rb"

def main
  puts add(1, 1)
end
            
          

Ruby - single file

            
# main.rb
def add(a, b)
  a + b
end

def main
  puts add(1, 1)
end
            
          

JavaScript

            
//add.js
export default function add(a, b) {
  return a + b
}

//main.js
import add from 'add.js'

function main() {
  console.log(add(1, 1))
}
            
          

JavaScript - Single File

            
//main.js
/*export default (?) */ function add(a, b) {
  return a + b
}

function main() {
  console.log(add(1, 1))
}
            
          

JavaScript

            
//add.js
export default function add() { return a + b }

//main.js
import plus from "add.js"
function main() {
  return plus(1, 1)
}
            
          

JavaScript

            
//add.js
export default function add() { return a + b }
function add() { throw Error('not this one') }

//main.js
import add from "add.js"
function main() {
  return add(1, 1)
}
            
          

Why is this a problem?

  • ES6 Modules fine for authoring, but not web-shippable
  • H2 might solve this, but adds more complexity
  • Still relying on AMD or custom module "registries"
  • Vendor.js still a thing
  • All we need is a hook to inject modules!

What about Rollup/Webpack?

  • Webpack still uses its own custom registry
  • Webpack CommonsChunk helpful - but creates thrashing of vendor modules
  • Rollup just landed code-splitting (ala CommonsChunk)
  • Still has thrashing problems, due to inherent treeshaking

JavaScript - New Single File Mode?

            
//main.js
module "add" {
  export default function add(a, b) {
    return a + b
  }
}

import add from "add"

function main() {
  return add(1, 1)
}
            
          

High Level Semantics

  • module injects dependency
  • ignores upper scope
  • can import/export inside of itself
  • cannot declare modules inside itself
  • decouples modules from single files
  • `module` not allowed after `import`
  • `module` scopes resolved at end
  • works with import() and import

Old amd example

            
//vendor.js
define("add", function(exports) {
  exports.default = (a, b) => a + b
})
define("sub", function(exports) {
  exports.default = (a, b) => a - b
})

//main.js
define(["add", "sub"], function ({add, sub}) {
  function main() {
    add(sub(2, 1), 1)
  }
})
            
          

New module example

            
//vendor.js
module "sub" {
  export default function sub(a, b) { return a - b }
}
module "add" {
  export default function add(a, b) { return a + b }
}

//main.js
import add from "add"
import sub from "sub"
function main() {
  add(sub(2, 1), 1)
}
            
          

More complex example

            module "math" {
  import add from "add"
  import sub from "sub"
  export {sub, add}
}
module "sub" {
  export default function sub(a, b) { return a - b }
}
module "add" {
  export default function add(a, b) { return a + b }
}
import {add,sub} from "math"
            
          

Questions?