Modern browsers support ES Modules, a modular scheme of JavaScript that browsers natively support. For compatibility, ES Modules are rarely used in production environments, but they play an increasingly important role in development, testing, and learning scenarios. For example, the build tool Vite uses ES Modules to quickly provide a development and debugging environment. Learn about the React and Vue frameworks and use ES Modules to experience these modern frameworks directly in a browser without installing native build tools.
ES Modules has a limitation, that is, it can import modular JS code from the specified URL in the browser, but it can’t import Modules from its own HTML file, for example:
<script type="module">
import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
</script>
Copy the code
We can’t do the following:
<script type="module" id="foo">
export default {foo: 'foo'};
</script>
<script type="module" id="bar">
import foo from '#foo'; // I want to reference the export object in the script tag above
</script>
Copy the code
However, it would be useful to implement inline-import, which means we can use multiple JavaScript modules in a simple Playground environment like CodePen without having to publish them as online JS files and then import them.
However, implementing inline-import is not that easy.
The idea is to use Blob objects to do this. Blob objects have some amazing capabilities, as I shared in the Front End Trivia article “Super Handy Blob Objects!” If you are interested, you can go and have a look.
Without further ado, we can implement a function that creates a block of JavaScript text as a Blob object and returns the URL of the Blob object.
function getBlobURL(module) {
const jsCode = module.innerHTML;
const blob = new Blob([jsCode], {type: 'text/javascript'});
const blobURL = URL.createObjectURL(blob);
return blobURL;
}
Copy the code
Next we implement an inlineImport function:
// https://github.com/WICG/import-maps
const map = {imports: {}, scopes: {}};
window.inlineImport = async (moduleID) => {
const {imports} = map;
let blobURL = null;
if(moduleID in imports) blobURL = imports[moduleID];
else {
const module = document.querySelector(`script[type="inline-module"]${moduleID}`);
if(module) {
blobURL = getBlobURL(module); imports[moduleID] = blobURL; }}if(blobURL) {
const result = await import(blobURL);
return result;
}
return null;
};
Copy the code
The above code is not complicated, combined with getBlobURL, the core of which is to get the JavaScript code string from the tag
With the inlineImport function, we can use it like this:
<script type="inline-module" id="foo">
const foo = 'bar';
export default {foo};
</script>
<script src="https://unpkg.com/inline-module/index.js"></script>
<script type="module">
const foo = (await inlineImport('#foo')).default;
console.log(foo); // {foo: 'bar'}
</script>
Copy the code
This implementation solves most of the problems, but it still sucks because you can only import dynamically. In fact, we want to be able to import statically as well, such as const foo = (await inlineImport(‘#foo’)).default; Import foo from ‘#foo’;
This is actually possible using another feature of modern browsers, ImportMap.
Importmap was originally intended to solve the problem of introducing aliases for Modules in ES Modules. For example, we found the following code uncomfortable because the IMPORT URL was too long.
<script type="module">
import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
</script>
Copy the code
Can be changed to:
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"}}</script>
<script type="module">
import {createApp} from 'vue';
</script>
Copy the code
to alias the URL of the module to import.
Note, however, that there are restrictions on the use of importMap. First, there can only be one Script tag with type=”importmap” on the page. Multiple Script tags are not supported, and the position of the importmap must be before all < Script type=”module”> elements.
We can then implement static inline-import using the same method used to generate importMap:
const currentScript = document.currentScript || document.querySelector('script');
function setup() {
const modules = document.querySelectorAll('script[type="inline-module"]');
const importMap = {};
[...modules].forEach((module) = > {
const {id} = module;
if(id) {
importMap[` #${id}`] = getBlobURL(module); }});const importMapEl = document.querySelector('script[type="importmap"]');
if(importMapEl) {
// map = JSON.parse(mapEl.innerHTML);
throw new Error('Cannot setup after importmap is set. Use <script type="inline-module-importmap"> instead.');
}
const externalMapEl = document.querySelector('script[type="inline-module-importmap"]');
if(externalMapEl) {
const externalMap = JSON.parse(externalMapEl.textContent);
Object.assign(map.imports, externalMap.imports);
Object.assign(map.scopes, externalMap.scopes);
}
Object.assign(map.imports, importMap);
const mapEl = document.createElement('script');
mapEl.setAttribute('type'.'importmap');
mapEl.textContent = JSON.stringify(map);
currentScript.after(mapEl);
}
if(currentScript.hasAttribute('setup')) {
setup();
}
Copy the code
If you already have an ImportMap tag on the page, you can’t create an ImportMap anymore, you throw an exception, and you really need to create an ImportMap yourself. We can ask the user to use
With this setup method, we are ready to use static imports. At the end of the code, if the setup property is set on the script tag, setup() is automatically run.
So we can write:
<script type="inline-module" id="foo">
const foo = 'bar';
export default {foo};
</script>
<script src="https://unpkg.com/inline-module/index.js" setup></script>
<script type="module">
import foo from '#foo';
console.log(foo); // {foo: 'bar'}
</script>
Copy the code
Or to use a custom importMap you can write:
<script type="inline-module-importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"}}</script>
<script type="inline-module" id="foo">
const foo = 'bar';
export default foo;
</script>
<script src="https://unpkg.com/inline-module/index.js" setup></script>
<script type="module">
import foo from '#foo'
console.log(foo);
import {createApp} from 'vue';
console.log(createApp);
</script>
Copy the code
Just be aware that, this paragraph must appear after all script tags of type=”inline-module”, Before all script tags of type=”module”.
This way, we can happily use inline-module
If you need to use it, you can directly use the GitHub repository code of rare earth mining: github.com/xitu/inline…
Any question is welcome to feedback ~