Skip to content

Commit 19770cc

Browse files
committed
Initial commit with fleshed-out README
0 parents  commit 19770cc

File tree

8 files changed

+224
-0
lines changed

8 files changed

+224
-0
lines changed

.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
insert_final_newline = true
6+
trim_trailing_whitespace = true
7+
charset = utf-8
8+
indent_style = space
9+
indent_size = 2

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/node_modules/
2+
/out/
3+
/npm-debug.log
4+
5+
deploy_key

.travis.yml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
language: node_js
2+
node_js:
3+
- stable
4+
script:
5+
- npm test
6+
- bash ./deploy.sh
7+
env:
8+
global:
9+
- ENCRYPTION_LABEL: "TODO"
10+
- COMMIT_AUTHOR_EMAIL: "[email protected]"

LICENSE.txt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright © 2016 Domenic Denicola
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# import()
2+
3+
This is a work-in-progress repository for a proposal for adding a "function-like" `import()` module loading syntactic form to JavaScript. It has not yet been formally proposed to the committee, but has been discussed with the module-loading community in [whatwg/loader#149](https://github.com/whatwg/loader/issues/149). You can view the in-progress [spec draft](https://domenic.github.io/proposal-import-function/) and take part in the discussions on the [issue tracker](https://github.com/domenic/proposal-import-function/issues).
4+
5+
## Motivation and use cases
6+
7+
The existing syntactic forms for importing modules are static declarations. They accept a string literal as the module specifier, and introduce bindings into the local scope via a pre-runtime "linking" process. This is a great design for the 90% case, and supports important use cases such as static analysis, bundling tools, and tree shaking.
8+
9+
However, it's also desirable to be able to dynamically load parts of a JavaScript application at runtime. This could be because of factors only known at runtime (such as the user's language), or for performance reasons (not loading code until it is likely to be used). Such dynamic code-loading has a long history, especially on the web, but also in Node.js (to delay startup costs). The existing `import` syntax does not support such use cases.
10+
11+
Truly dynamic code loading also enables advanced scenarios, such as racing multiple modules against each other and choosing the first to successfully load.
12+
13+
## Proposed solution
14+
15+
This proposal adds an `import(specifier)` syntactic form, which acts in many ways like a function (but see below). It returns a promise for the module namespace object of the requested module, which is created after fetching, instantiating, and evaluating all of the module's dependencies, as well as the module itself.
16+
17+
Here `specifier` will be interpreted the same way as in an `import` declaration (i.e., the same strings will work in both places). However, while `specifier` is a string it is not necessarily a string literal; thus code like `` import(`./language-packs/${navigator.language}.js`) `` will work—something impossible to accomplish with the usual `import` declarations.
18+
19+
`import()` is proposed to work in both scripts and modules. This gives script code an easy asynchronous entry point into the module world, allowing it to start running module code.
20+
21+
Like the existing JavaScript module specification, the exact mechanism for retrieving the module is left up to the host environment (e.g., web browsers or Node.js). This is done by introducing a new host-environment-implemented abstract operation, HostFetchImportedModule, in addition to reusing and slightly tweaking the existing HostResolveImportedModule.
22+
23+
## Alternative solutions explored
24+
25+
There are a number of other ways of potentially accomplishing the above use cases. Here we explain why we believe `import()` is the best possibility.
26+
27+
### Using host-specific mechanisms
28+
29+
It's possible to dynamically load modules in certain host environments, such as web browsers, by abusing host-specific mechanisms for doing so. Using HTML's `<script type="module">`, the following code would give similar functionality to `import()`:
30+
31+
```js
32+
33+
function importModule(url) {
34+
return new Promise((resolve, reject) => {
35+
const script = document.createElement("script");
36+
const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
37+
script.type = "module";
38+
script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;
39+
40+
script.onload = () => {
41+
resolve(window[tempGlobal]);
42+
delete window[tempGlobal];
43+
script.remove();
44+
};
45+
46+
script.onerror = () => {
47+
reject(new Error("Failed to load module script with URL " + url));
48+
delete window[tempGlobal];
49+
script.remove();
50+
};
51+
52+
document.documentElement.appendChild(script);
53+
});
54+
}
55+
```
56+
57+
However, this has a number of deficiencies, apart from the obvious ugliness of creating a temporary global variable and inserting a `<script>` element into the document tree only to remove it later.
58+
59+
The most obvious is that it takes a URL, not a module specifier; furthermore, that URL is relative to the document's URL, and not to the script executing. This introduces a needless impedance mismatch for developers, as they need to switch contexts when using the different ways of importing modules, and it makes relative URLs a potential bug-farm.
60+
61+
Another clear problem is that this is host-specific. Node.js code cannot use the above function, and would have to invent its own, which probably would have different semantics (based, likely, on filenames instead of URLs). This leads to non-portable code.
62+
63+
Finally, it isn't standardized, meaning people will need to pull in or write their own version each time they want to add dynamic code loading to their app. This could be fixed by adding it as a standard method in HTML (`window.importModule`), but if we're going to standardize something, let's instead standardize `import()`, which is nicer for the above reasons.
64+
65+
### An actual function
66+
67+
Drafts of the [Loader](https://whatwg.github.io/loader/) ideas collection have at various times had actual functions (not just function-like syntactic forms) named `System.import()` or `System.loader.import()` or similar, which accomplish the same use cases.
68+
69+
The biggest problem here, as previously noted by the spec's editors, is how to interpret the specifier argument to these functions. Since these are just functions, which are the same across the entire Realm and do not vary per script or module, the function must interpret its argument the same no matter from where it is called. (Unless something truly weird like stack inspection is implemented.) So likely this runs into similar problems as the document base URL issue for the `importModule` function above, where relative module specifiers become a bug farm and mismatch any nearby `import` declarations.
70+
71+
## Relation to existing work
72+
73+
So far module work has taken place in three spaces:
74+
75+
- [The JavaScript specification](https://tc39.github.io/ecma262/#sec-modules), which mostly defines the syntax of modules, defering to the host environment via [HostResolveImportedModule](https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule);
76+
- [The HTML Standard](https://html.spec.whatwg.org/multipage/), which defines [`<script type="module">`](https://html.spec.whatwg.org/multipage/scripting.html#the-script-element), how to [fetch modules](https://html.spec.whatwg.org/multipage/webappapis.html#fetching-scripts), and fulfills its duties as a host environment by specifying [HostResolveImportedModule](https://html.spec.whatwg.org/multipage/webappapis.html#hostresolveimportedmodule(referencingmodule,-specifier)) on top of those foundations;
77+
- [The Loader specification](https://whatwg.github.io/loader/), which is a collection of interesting ideas prototyping ways of creating a runtime-configurable loading pipeline and creating modules reflectively (i.e. not from source text)
78+
79+
This proposal would be a small expansion of the existing JavaScript and HTML capabilities, using the same framework of specifying syntactic forms in the JavaScript specification, which delegate to the host environment for their heavy lifting. HTML's infrastructure for fetching and resolving modules would be leveraged to define its side of the story. Similarly, Node.js would supply its own definitions for HostFetchImportedModule and HostResolveImportedModule to make this proposal work there.
80+
81+
The ideas in the Loader specification would largely stay the same, although probably this would either supplant the current `System.loader.import()` proposal or make `System.loader.import()` a lower-level version that is used in specialized circumstances. The Loader specification would continue to work on prototyping more general ideas for pluggable loading pipelines and reflective modules, which over time could be used to generalize HTML and Node's host-specific pipelines.
82+
83+
Concretely, this repository is intended as a TC39 proposal to advance through the stages process, specifying the `import()` syntax and the relevant host environment hooks. It will likely also contain an outline of or link to the proposed changes to HTML as a host environment.

deploy.sh

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/bin/bash
2+
set -e # Exit with nonzero exit code if anything fails
3+
4+
SOURCE_BRANCH="master"
5+
TARGET_BRANCH="gh-pages"
6+
7+
function doCompile {
8+
npm run spec
9+
}
10+
11+
# Pull requests and commits to other branches shouldn't try to deploy, just build to verify
12+
if [[ "$TRAVIS_PULL_REQUEST" != "false" || "$TRAVIS_BRANCH" != "$SOURCE_BRANCH" ]]; then
13+
echo "Skipping deploy; just doing a build."
14+
doCompile
15+
exit 0
16+
fi
17+
18+
# Save some useful information
19+
REPO=`git config remote.origin.url`
20+
SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:}
21+
SHA=`git rev-parse --verify HEAD`
22+
23+
# Get the deploy key by using Travis's stored variables to decrypt deploy_key.enc
24+
ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key"
25+
ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv"
26+
ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR}
27+
ENCRYPTED_IV=${!ENCRYPTED_IV_VAR}
28+
openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in deploy_key.enc -out deploy_key -d
29+
chmod 600 deploy_key
30+
eval `ssh-agent -s`
31+
ssh-add deploy_key
32+
33+
# Clone the existing gh-pages for this repo into out/
34+
# Create a new empty branch if gh-pages doesn't exist yet (should only happen on first deply)
35+
git clone $REPO out
36+
cd out
37+
git checkout $TARGET_BRANCH || git checkout --orphan $TARGET_BRANCH
38+
cd ..
39+
40+
# Clean out existing contents
41+
rm -rf out/**/* || exit 0
42+
43+
# Run our compile script
44+
doCompile
45+
46+
# Now let's go have some fun with the cloned repo
47+
cd out
48+
git config user.name "Travis CI"
49+
git config user.email "$COMMIT_AUTHOR_EMAIL"
50+
51+
# If there are no changes to the compiled out (e.g. this is a README update) then just bail.
52+
if [[ -z "$(git status --porcelain)" ]]; then
53+
echo "No changes to the output on this push; exiting."
54+
exit 0
55+
fi
56+
57+
# Commit the "changes", i.e. the new version.
58+
# The delta will show diffs between new and old versions.
59+
git add --all .
60+
git commit -m "Deploy to GitHub Pages: ${SHA}"
61+
62+
# Now that we're all set up, we can push.
63+
git push $SSH_REPO $TARGET_BRANCH

package.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"private": true,
3+
"name": "proposal-import-function",
4+
"description": "import() proposal for JavaScript (build setup only)",
5+
"author": "Domenic Denicola <[email protected]> (https://domenic.me/)",
6+
"license": "MIT",
7+
"repository": "domenic/proposal-import-function",
8+
"scripts": {
9+
"spec": "ecmarkup spec.html out/index.html",
10+
"watch": "ecmarkup --watch spec.html out/index.html"
11+
},
12+
"devDependencies": {
13+
"ecmarkup": "^3.5.1"
14+
}
15+
}

spec.html

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<pre class="metadata">
2+
title: import()
3+
status: proposal
4+
stage: -1
5+
location: https://domenic.github.io/proposal-import-function/
6+
copyright: false
7+
contributors: Domenic Denicola
8+
</pre>
9+
<script src="https://bterlson.github.io/ecmarkup/ecmarkup.js" defer></script>
10+
<link rel="stylesheet" href="https://bterlson.github.io/ecmarkup/elements.css">
11+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/solarized_light.min.css">
12+
13+
<emu-intro>
14+
<h1>Introduction</h1>
15+
16+
<p>Background explanatory material for this specification can be found in the <a href="https://github.com/domenic/proposal-import-function">domenic/proposal-import-function</a> repository. See also the <a href="https://github.com/tc39/proposal-import-function/issues">issues</a>.</p>
17+
</emu-intro>
18+

0 commit comments

Comments
 (0)