现代浏览器支持了ES Modules[1],也就是浏览器原生支持的 JavaScript 模块化方案。虽然考虑兼容性,我们还很少能够把 ES Modules 用于生产环境,但是在开发、测试、学习的场景中,ES Modules 发挥了越来越大的作用,比如构建工具Vite[2],就利用 ES Modules 来快速提供开发调试环境。React 和 Vue 框架的学习中,也都可以利用 ES Modules 不用安装本地构建工具,直接在浏览器上体验这些现代框架。
不过 ES Modules 有个局限性,就是它在浏览器里能够 import 指定 URL 的模块化 JS 代码,但是不能 import 自身 HTML 文件里的模块,比如:
<script type="module">
import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
</script>
我们没有办法做到下面这种:
<script type="module" id="foo">
export default {foo: 'foo'};
</script>
<script type="module" id="bar">
import foo from '#foo'; // 我想在这里引用上面的script标签里export的对象
</script>
但是如果能实现这种 inline-import,其实还挺有用的,这就意味着我们可以在像 CodePen 这样简单的 Playground 环境中使用多个 JavaScript 模块,而不用把它们先发布成在线的 JS 文件再 import。
不过要实现 inline-import,也不是那么容易。
思路上,我们可以借助Blob[3]对象来实现,Blob 对象有一些神奇的能力,我在前端冷知识系列中分享过一篇文章《超好用的 Blob 对象!》[4],有兴趣的同学可以去看一下。
言归正传,我们可以实现一个函数,将一段 JavaScript 文本创建成 Blob 对象,并返回 Blob 对象的 URL。
function getBlobURL(module) {
const jsCode = module.innerHTML;
const blob = new Blob([jsCode], {type: 'text/javascript'});
const blobURL = URL.createObjectURL(blob);
return blobURL;
}
接着我们实现一个 inlineImport 函数:
// 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;
};
上面这段代码不复杂,结合 getBlobURL,其核心就是从标签<script type="inline-module">
中获取 JavaScript 代码字符串然后生成 blobURL,并且将它缓存在 map 对象里,这样下次如果再 import,就直接从 map 缓存中取。取出的 blobURL,通过 ES Modules 原生的动态 import 方法加载。有了 inlineImport 函数之后,我们就可以这样用:
<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>
这样实现可以解决大部分问题,但是用起来还是不爽,因为这样只能动态 import。事实上,我们希望也能够以静态的方式 import,比如const foo = (await inlineImport('#foo')).default;
可以写成import foo from '#foo';
实际上这个也是可以实现的,要用到现代浏览器的另一个特性,importmap。
importmap 本来是为了解决 ES Modules 引入模块的别名问题,比如我们觉得下面的代码写得不爽,因为 import 的 URL 太长了。
<script type="module">
import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
</script>
可以改成:
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
<script type="module">
import {createApp} from 'vue';
</script>
也就是在前面加一个<scirpt type="importmap">
给要 import 的模块 URL 加一个别名就行了。
不过要注意,importmap 使用有限制,首先页面上只能有一个type="importmap"
的 Script 标签,多个是不支持的,另外 importmap 的位置要在所有<script type="module">
的元素出现之前。
那么,我们接着就可以利用生成 importmap 的思路来实现静态的 inline-import 了:
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();
}
这个函数的内容看起来稍微多一些,主要是处理 importmap 的规则,如果页面上已经有 importmap 标签,就不能再创建 importmap 了,要抛出异常,另外用户确实需要自己创建 importmap,我们可以让用户用<script type="inline-module-import">
代替,然后我们自己合并 JSON 数据,也就是代码逻辑里 externalMapEl 的这部分。最后,最核心的部分就是前面得到模块的 BlobURL,然后针对 id 和 BlobURL 生成 importMap,最终将 importMap 挂载到 HTML 文档中。
有了这个 setup 方法之后,我们已经可以用静态的 import 了,我在代码的最后,如果 script 标签上设置 setup 属性,那么就自动运行setup()
。
这样我们就可以这么写:
<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>
或者要用到自定义的 importmap 的时候可以这么写:
<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>
只是需要注意的是,<script src="https://unpkg.com/inline-module/index.js" setup></script>
这段必须出现在所有的type="inline-module"
的 script 标签之后,所有type="module"
的 script 标签之前。这样,我们就可以愉快地使用 inline-module 啦~有需要使用的同学,可以直接使用稀土掘金开源的 GitHub 仓库代码:github.com/xitu/inline…[5]有任何问题欢迎反馈~
[1]ES Modules: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules
[2]Vite: https://vitejs.dev/
[3]Blob: https://developer.mozilla.org/zh-CN/docs/Web/API/Blob
[4]《超好用的Blob对象!》: https://github.com/akira-cn/FE_You_dont_know/issues/12
[5]github.com/xitu/inline…: https://github.com/xitu/inline-module
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。