A tiny, static AMD API implementation that allows including AMD modules in regular script tags.
SAMD is currently experimental/awaiting feedback.
- 1.3 KB minified & gzipped
- No dynamic loading; all scripts are loaded by the browser
- Works in every browser alive
- Supports
<script defer>
&<script async>
- Allows progressive optimizations without impact on development
When combined with TypeScript,
SAMD may be used as an alternative to traditional JS bundling
(e.g. webpack
or rollup
).
This is the main use case for SAMD, as described below.
An example project can be inspected under /example
.
SAMD enables inclusion of UMD/AMD modules in regular script
tags:
<script src="https://unpkg.com/samd@0/dist/samd.js" crossorigin></script>
<script async src="https://unpkg.com/moment@2/moment.js" crossorigin></script>
<script async src="https://unpkg.com/lodash@4/lodash.js" crossorigin></script>
<!-- more UMD/AMD dependencies... -->
<script>
require(['moment', 'lodash'], function (moment, _) {
// use dependencies
});
</script>
No additional script loading or special markup is required.
Given a regular ES6/TypeScript project, with NPM dependencies and TypeScript configuration in place, TypeScript is able to produce one-file AMD builds with wide browser compatibility out-of-the-box:
tsc --outFile scripts/dist/app.js --target ES5 --module AMD
This generates an ES5 AMD bundle of the project, not including NPM modules but
explicitly referencing NPM imports as AMD dependencies.
Note that TypeScript is able to handle JS/JSX projects by using --allowJs
.
This application bundle can be used directly in a website by including SAMD and UMD builds of the application dependencies from e.g. unpkg:
<div id="root"></div>
<script src="https://unpkg.com/samd@0/dist/samd.js" crossorigin></script>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<!-- more UMD/AMD dependencies... -->
<script src="scripts/dist/app.js"></script>
<script>
// assuming the entry point is index.ts/index.js and it exports an init() function:
require(["scripts/index"], function (index) {
index.init();
});
</script>
SAMD infers module IDs from script src
attributes, allowing
the original NPM imports to be resolved from the application bundle.
This setup is sufficient for development:
- It works in modern browsers.
- Application code is based on modern JS/TypeScript with ES6 imports.
- TypeScript's watch mode may be used to continuously build the application bundle.
- Source maps are supported.
Note that CommonJS-only dependencies cannot be used. Dependencies must provide UMD/AMD builds (most popular libraries do, though).
In production environments, the respective production/minified builds of dependencies should be used. Polyfills may be included using e.g. polyfill.io. For further optimization, the application bundle may be minified through terser.
<script src="https://polyfill.io/v3/polyfill.min.js?features=fetch,Promise" crossorigin></script>
<script src="https://unpkg.com/samd@0/dist/samd.min.js" crossorigin></script>
<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script>
<script src="scripts/dist/app.min.js"></script>
It is reasonable to serve separate HTML documents for development and production environments. Employ templating techniques at build-time or on server-side as needed.
Because of unpkg
's CDN, this setup is fairly performant when using popular
libraries. It is also widely compatible regarding browser support, depending on
the chosen TypeScript target.
If vendor dependencies need to be self-hosted, it is recommended
to copy the respective builds from node_modules
to e.g. scripts/vendor
.
Compiling the set of polyfills is out of scope for this document.
<script src="scripts/vendor/polyfills.min.js"></script>
<script src="scripts/vendor/samd.min.js"></script>
<script src="scripts/vendor/react.production.min.js"></script>
<script src="scripts/vendor/react-dom.production.min.js"></script>
<script src="scripts/dist/app.min.js"></script>
It is encouraged to automate copying and other build steps through one or
more shell scripts (see build.sh
in the example project).
Remember that SAMD infers AMD module IDs from script filenames
(after last /
, before first .
, while still allowing @org/package
).
When served with HTTP/2, this setup is sufficiently performant for a wide range of websites.
When having many vendor dependencies or when HTTP/2 is unavailable,
all scripts may be concatenated into a single file
(optionally running it through terser
again).
<script src="scripts/bundle.min.js"></script>
However, because UMD modules usually define anonymous modules, SAMD is not able to identify individual module IDs of a bundle without user input, so bundling cannot work by simple concatenation.
For this use case, SAMD ships with a CLI to infer and inject module IDs into
given UMD module files (see build.sh
in the example project).
This CLI can also be used for concatenation.
samd -o scripts/dist/bundle.js \
scripts/vendor/polyfills.min.js \
scripts/vendor/samd.min.js \
scripts/vendor/react.production.min.js \
scripts/vendor/react-dom.production.min.js \
scripts/dist/app.min.js
terser scripts/dist/bundle.js -o scripts.dist.bundle.js
Note that this only has to be done in bundling scenarios.
SAMD is only useful for applications. Packages for the web should be distributed as UMD for maximum compatibility.
The presented TypeScript/SAMD-based workflow has useful properties and is fully compatible with the web platform.
- Works with modern ES6/TypeScript projects, with little implications.
- Wide browser compatibility (even Internet Explorer)
- Loading of scripts is handled and optimized by the browser.
- Production optimizations are progressive and don't impact development.
- Bundling is (almost) reduced to simple concatenation.
- Bundling does not require configuration; simple shell scripts are sufficient and provide additional flexibility.
At any point of the workflow, it is advised to measure page load performance and user experience, and only proceed optimizing if needed.
- Dependencies must provide AMD/UMD builds.
- Multiple versions of dependencies are not supported.
- CommonJS is not supported.
- No tree-shaking.
- Relative module dependencies are not supported (yet).