Skip to content

Commit

Permalink
initial concept
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed Nov 12, 2024
0 parents commit c2b146a
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Build spec

on: [pull_request, push]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: ljharb/actions/node/install@main
name: 'nvm install lts/* && npm install'
with:
node-version: lts/*
- run: npm run build
23 changes: 23 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Deploy gh-pages

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: ljharb/actions/node/install@main
name: 'nvm install lts/* && npm install'
with:
node-version: lts/*
- run: npm run build
- uses: JamesIves/[email protected]
with:
branch: gh-pages
folder: build
clean: true
46 changes: 46 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules
jspm_packages

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

# Only apps should have lockfiles
yarn.lock
package-lock.json
npm-shrinkwrap.json
pnpm-lock.yaml

# Build directory
build
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 ECMA TC39 and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
127 changes: 127 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Import Sync

## Status

Champion: Guy Bedford
Stage: -1

## Problem Statement

When modules are already fully loaded into the module registry, it would be useful to support a
synchronous import function to allow loading of dynamic specifier expressions in synchronous function
paths.

## Background

As we enter the maturity stage of native ESM being adopted throughout the JS ecosystem, with features
like `require(esm)` in Node.js unlocking upgrade paths for many, some remaining ergonomic issues
remain on the migration path from CommonJS to ES modules.

In particular CommonJS users in Node.js are used to being able to synchronously dynamically require
modules, without that causing them to have to convert their codepaths to async.

[Defer Import Eval](https://github.com/tc39/proposal-defer-import-eval) solves one of these issues in
allowing synchronous lazy loading of modules. It does this by effectively separating the async aspects
of the module loading pipeline from the sync aspects to allow a [synchronous evaluation function](https://github.com/tc39/proposal-defer-import-eval?tab=readme-ov-file#semantics)
on the deferred namespace.

Even with this proposal supported, there still remains a gap for dynamic lazy loading when the specifier
is not known in advance, since a dynamic `import.defer(specifier)` is still an asynchronous function.

Using the exact same semantics as the synchronous evaluation already defined in the Defer Import Eval
proposal, we can provide an explicit hook for a synchronous import in JavaScript solving this ergonomic
problem for JavaScript developers.

Previously one of the major blockers in the early stages of the ESM specification to enabling a feature like this was the question of asynchronous resolution. _It has since turned out that all JS environments implement
synchronous module resolution._ As a result, and given this constraint, a synchronous import is possible.

## Proposal

We propose to expose an explicit synchronous import function for ES modules, as a direct extension of the synchronous execution behaviour already defined by the [Defer Import Eval][] proposal:

```js
// synchronously import a module if it is available synchronously
const ns = import.sync('./mod.js');
```

The major design of the proposal is a new `Error` which is thrown when a module is not synchronously
available, or uses top-level await.

Whether a module is synchronously available would otherwise be a host-determined property.

## Use Cases

### Getting an Already-Loaded Module

On the web, if a module has already been loaded before, it can always be available synchronously.

In this way `import.sync()` can behave like a registry getter function:

```js
import 'app';

// this will always work if 'app' has been loaded previously
const app = import.sync('app');
```

### Conditional Loading

Just like with dynamic import, with a synchronous import, it's possible to check if a module or builtin is available, but synchronously.

For example, checking if host builtins are available:

```js
try {
let fs = import.sync('node:fs');
} catch {}

if (fs) {
// Use node:fs, only if it is available
}
```

Or a library that conditionally binds to a framework dependency:

```js
let react;
try {
react = import.sync('react');
} catch {}

if (react) {
// Bind to the React framework, if available
}
```

### Synchronous Loading of ModuleExpressions & ModuleDeclarations

Importing module expressions without TLA and async dependencies can be supported:

```js
// immediately logs 'hello world'
import.sync(module {
console.log('hello world');
})
```

Similarly for module declarations:

```js
module dep {
console.log('hi');
}

module x {
import dep;
}

// logs 'hi', since both modules are synchronously available
const instance = import.sync(x);
```

## FAQ

_Post an [issue](https://github.com/guybedford/proposal-import-sync/issues)._

[Defer Import Eval]: https://github.com/tc39/proposal-defer-import-eval
[ESM Phase Imports]: https://github.com/tc39/proposal-esm-phase-imports
23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"private": true,
"name": "template-for-proposals",
"description": "A repository template for ECMAScript proposals.",
"scripts": {
"start": "npm run build-loose -- --watch",
"build": "npm run build-loose -- --strict",
"build-loose": "node -e 'fs.mkdirSync(\"build\", { recursive: true })' && ecmarkup --load-biblio @tc39/ecma262-biblio --verbose spec.emu build/index.html --lint-spec"
},
"homepage": "https://github.com/tc39/template-for-proposals#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/tc39/template-for-proposals.git"
},
"license": "MIT",
"devDependencies": {
"@tc39/ecma262-biblio": "^2.1.2775",
"ecmarkup": "^20.0.0"
},
"engines": {
"node": ">= 12"
}
}
39 changes: 39 additions & 0 deletions spec.emu
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!doctype html>
<meta charset="utf8">
<link rel="stylesheet" href="./spec.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/github.min.css">
<script src="./spec.js"></script>
<pre class="metadata">
title: Import Sync
stage: -1
contributors: Guy Bedford
</pre>

<emu-clause id="sec-demo-clause">
<h1>This is an emu-clause</h1>
<p>This is an algorithm:</p>
<emu-alg>
1. Let _proposal_ be *undefined*.
1. If IsAccepted(_proposal_) is *true*, then
1. Let _stage_ be *0*<sub>ℤ</sub>.
1. Else,
1. Let _stage_ be *-1*<sub>ℤ</sub>.
1. Return ? ToString(_stage_).
</emu-alg>
</emu-clause>

<emu-clause id="sec-is-accepted" type="abstract operation">
<h1>
IsAccepted (
_proposal_: an ECMAScript language value
): a Boolean
</h1>
<dl class="header">
<dt>description</dt>
<dd>Tells you if the proposal was accepted</dd>
</dl>
<emu-alg>
1. If _proposal_ is not a String, or is not accepted, return *false*.
1. Return *true*.
</emu-alg>
</emu-clause>

0 comments on commit c2b146a

Please sign in to comment.