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"