diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..854640b --- /dev/null +++ b/.github/workflows/build.yml @@ -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 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..43206c8 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -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/github-pages-deploy-action@v4.3.3 + with: + branch: gh-pages + folder: build + clean: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3cae16 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6e78ec9 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2b6a1d --- /dev/null +++ b/README.md @@ -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 diff --git a/package.json b/package.json new file mode 100644 index 0000000..a68a7db --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/spec.emu b/spec.emu new file mode 100644 index 0000000..b7a15de --- /dev/null +++ b/spec.emu @@ -0,0 +1,39 @@ + + + + + +
+title: Import Sync
+stage: -1
+contributors: Guy Bedford
+
+ + +

This is an emu-clause

+

This is an algorithm:

+ + 1. Let _proposal_ be *undefined*. + 1. If IsAccepted(_proposal_) is *true*, then + 1. Let _stage_ be *0*. + 1. Else, + 1. Let _stage_ be *-1*. + 1. Return ? ToString(_stage_). + +
+ + +

+ IsAccepted ( + _proposal_: an ECMAScript language value + ): a Boolean +

+
+
description
+
Tells you if the proposal was accepted
+
+ + 1. If _proposal_ is not a String, or is not accepted, return *false*. + 1. Return *true*. + +