Announcing ES Module Shims 2.0
Refer
Source
: Announcing ES Module Shims 2.0
Author
: Guy Bedford
Translator
: SenaoXi
Release Time
: February 24, 2025
Copyright Statement
Translation and Republication Notice:
This translation is provided for educational and informational purposes only. All intellectual property rights, including copyright, remain with the original author and/or publisher. This translation maintains the integrity of the original content while making it accessible to chinese readers.
Modifications Disclosure:
- This is a complete and faithful translation of the original content with no substantive modifications.
- This translation includes minor adaptations to improve clarity for chinese readers while preserving all essential information and viewpoints.
- Sections marked with [†] contain supplementary explanations added by the translator to provide cultural or technical context.
Rights Reservation:
If you are the copyright holder and believe this translation exceeds fair use guidelines, please contact us at email. We are committed to respecting intellectual property rights and will promptly address any legitimate concerns.
ES Module Shims 2.0
is now officially released, a comprehensive 13KB
lightweight polyfill
that supports import maps
, multiple import maps
, CSS and JSON imports
, WebAssembly
modules, and Source Phase
imports.
If you're not familiar with what these features are, they will be explained in detail below. But first, I want to highlight an important new feature in version 2.0
: TypeScript
type erasure support.
TypeScript Type Erasure Support
Why compile TypeScript
in the browser? The context is that ES Module Shims
is a very fast module polyfill
designed to fill in all new module features on browsers that support basic module systems. Therefore, the TC39
type annotations proposal fits perfectly within this project's polyfill
definition. Additionally, by using the TypeScript
variant defined in the Amaro
project from Node.js
in the browser, providing direct source-by-source rewriting (type erasure or erasable syntax), we actually get a very fast workflow.
It implements a buildless
workflow - TypeScript
as the final piece, providing a streamlined Web
development approach without build tools, Node.js
, or npm
.
Here's a practical example, a Vue
component written in TypeScript
:
import { defineComponent } from 'vue';
import style from './user-card.css' with { type: 'css' };
document.adoptedStyleSheets.push(style);
export interface User {
name: string;
age: number;
}
export default defineComponent({
props: {
user: {
type: Object as () => User,
required: true
}
},
template: `<div class="user-card">{{ user.name }} <span class="age">({{ user.age }})</span></div>`
});
In the code above, we're not only using TypeScript
but also the newly supported CSS Module Scripts
feature to modularly load the component's CSS
:
.user-card {
padding: 1.2rem;
border-radius: 16px;
margin: 1rem;
font: 500 18px system-ui;
width: 300px;
background: linear-gradient(135deg, #eee 0%, #fafafa 100%);
box-shadow: 2px 5px 7px rgba(100, 100, 255, 0.2);
transition: transform 0.2s ease;
cursor: pointer;
}
.user-card:hover {
transform: translateY(-2px);
}
.age {
color: #726497;
}
Now, using ES Module Shims
, we can run this application with a single HTML
file and static files, with polyfill
support for both CSS Module Scripts
and TypeScript
, without any build process or additional steps:
<!doctype html>
<!-- Load ES Module Shims from your chosen CDN -->
<script
async
src="https://ga.jspm.io/npm:es-module-shims@2.0.9/dist/es-module-shims.js"
></script>
<!-- Enable TypeScript and CSS import features (by default only import maps are polyfilled) -->
<script type="esms-options">
{ "polyfillEnable": ["typescript", "css-modules"] }
</script>
<!-- Set up dependencies in the import map -->
<script type="importmap">
{
"imports": {
"vue": "https://ga.jspm.io/npm:vue@3.5.13/dist/vue.esm-browser.prod.js"
}
}
</script>
<div id="app">
<user-card v-for="user in users" :key="user.name" :user="user" />
</div>
<!-- ES Module Shims will find this and handle the rest -->
<script type="module" lang="ts">
import { createApp } from 'vue';
import UserCard, { type User } from './user-card.ts';
createApp({
setup() {
const users: User[] = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 }
];
return { users };
}
})
.component('user-card', UserCard)
.mount('#app');
</script>
You can view the complete example here.
Support for Multiple Import Maps
Anyone who has used import maps
for a while will know the dreaded "An import map is added after module script load was triggered." error. Thanks to Yoav Weiss
's hard work, we now have support for multiple import maps
in the latest version of Chrome
.
ES Module Shims 2.0
includes a polyfill
for this feature that detects when multiple import maps
are used, then checks if modules depend on mappings that exist in the new import map
but not in the old one, following the standard polyfill
failure semantics. In practice, we can polyfill
multiple import maps
on top of single import map
support, now able to share native module loaders and registries when possible, just as we polyfill
import maps
themselves based on non-import map
module support in older browsers.
Additionally, by using mutation observers on the body
and head
tags, we can also detect when import maps
are dynamically injected, then apply the same polyfill
for dynamic loading workflows (provided new dynamic imports are loaded through the global importShim()
polyfill
).
Wasm
Modules and Source
Semantic Import Specification
Typically, WebAssembly
is loaded using patterns like fetch('./module.wasm').then(WebAssembly.compileStreaming).then(...)
to get a WebAssembly.Module
object for initialization.
In these workflows, getting the URL
for the Wasm
binary isn't always straightforward due to baseURL
semantics. Additionally, build tools may struggle to work well with this kind of code, often requiring runtime configuration of binary paths.
With the source
semantic import specification, we can now directly import Wasm
binaries in a portable way, which ES Module Shims
fully supports when enabled:
<!doctype html>
<script
async
src="https://ga.jspm.io/npm:es-module-shims@2.0.9/dist/es-module-shims.js"
></script>
<!-- Enable the WebAssembly and Source Phase features -->
<script type="esms-options">
{ "polyfillEnable": ["wasm-modules", "source-phase"] }
</script>
<script type="module">
import source mod from './module.wasm';
const { fn } = new WebAssembly.Instance(mod, ...options...);
</script>
Finally, if you're still curious about how ES Module Shims
works, I previously wrote an article about how ES Module Shims became a production import maps polyfill
.