自身 iMove
的定位就是“一个逻辑可复用的,面向函数的,流程可视化的 JavaScript 工具库。”
对开发者而言,iMove
恰好是能够实现这些指标的现实工具。动动鼠标,写一下节点函数,代码导出,放到具体工程里就能够间接应用,是不是很不便?
那么,为什么咱们还要做 iMove
在线执行代码性能呢?站在用户视角看,对于开发者来说,选中节点,右键在线执行代码是必要的。这样能够把 iMove
的工具属性做到极致,让开发者体验更好,操作更简略。
上次在《登上 Github 趋势榜,iMove 原理技术大揭秘!》一文中咱们卖了个关子,明天就和大家分享下 iMove 是如何实现在线执行节点代码的~
引子
故事还得从组内的一个小伙伴说起,过后她是第一次应用 iMove
,询问我如何测试运行刚写好的代码。二话不说,我就在她电脑上一通操作:
- 装置
@imove/cli
- 在我的项目根目录执行命令:
imove -d
- 找到我的项目中的组件,增加代码
logic.invoke('trigger')
- 启动我的项目,关上控制台面板,筛选
log
信息 - … …
“我就想运行下刚写好的代码,看看输入后果是什么 …”
『步骤繁琐』、『操作麻烦』、『有上手老本』、『运行后果不直观』… 此刻,脑中第一工夫闪过的就是以上这些词汇,再看向小伙伴那纳闷的眼神,好像下一刻她就要被劝退了 …
这可怎么行,体验必须优化,应用老本必须降下来!于是就有了右键在线执行代码的性能。
其实上述问题的诉求很简略: 写完节点的代码之后,旁边有个运行按钮,一点间接就能看到运行后果就行了。咱们来看下就是这么一个优化都能带来哪些扭转:
- 无装置工具要求,0 上手老本: 代码在浏览器端就能运行,不再须要装置命令行工具,大大降低了学习老本。
- 后果可视化,简单明了: 开发调试过程中最罕用到的就是日志,每次都要关上控制台极不不便,间接将节点的运行后果用可视化的形式展现进去,简略直观。
- 在线 mock 输出,不便测试: 每个节点的代码都会有各种可能的输出,如果能够间接
mock
输出,测试效率将得以极大的晋升。 - 测试用例可保留,保障代码品质: 除了不便测试节点代码之外,如果能够将成对的输出 / 输入作为测试用例加以保留,渐而造成齐备的测试用例集,将能进一步保障节点的代码品质。
- … …
想法是很美妙,但具体该如何实现呢?接着往下看~
摸索 1:节点代码在哪运行
首先,要解决的第一个问题就是:节点代码在哪运行?
通过评估,咱们认为次要有两个抉择:
- 浏览器端间接运行节点代码;
- 本地起一个服务,将节点信息发送至本地,本地构建编译完之后将
bundle
发送回浏览器端执行。
对于以上两个计划,iMove
抉择了前者,理由很简略:在线运行节点代码的初心就是为了升高上手老本,如果还须要本地起一个服务,使用者势必又要学习一个新命令,这无形中又进步了应用门槛。
然而,要想在浏览器中间接运行节点代码,还是有很多妨碍,并非一个 eval
就完事了~
摸索 2:如何运行 import/export
看过 iMove
的出码就晓得,流程图中的每个节点代码最终都会被编译成一个独自的 js
文件。因而,每个节点是反对 import
其余 npm
包的,这也是为什么不能间接调用 eval
的起因。
1)浏览器原生反对 ES Module
当然,兴许聪慧的你早已想到浏览器是反对 native ES module
的,包含之前挺火的 Vite 其实底层原理也是基于这个。为此,咱们先来简略介绍下如何让 import/export
代码在浏览器中跑起来~
先来看 MDN
上的 官网文档:
能够看到,其实古代浏览器早就反对了,因而咱们能够不必思考兼容性问题。除此之外,持续浏览文档你会发现要让浏览器反对 ES Module
很要害的一点是要在 script
标签上增加 type="module"
属性。来看一个例子:
# 文件目录
index.html
main.mjs
<!--index.html-->
<script type="module">
import sayHello from './main.js';
say('Hello iMove!');
</script>
// main.js
const say = (words) => console.log(words);
留神: 运行上述例子时不要在本地间接关上 index.html
文件,因为浏览器会默认用 file://
协定关上,申请 main.js
资源时会跨域。该问题能够在本地开一个 http
服务解决,或在 codesandbox 上尝试也可。
如上所示,要让浏览器跑 ES Module
代码也太简略了,能够说简直没有老本,但问题真解决了吗?能够在代码中加一行 import get from 'lodash.get'
试试。
能够看到控制台报错了,原来是 lodash.get
被浏览器当做相对路径加载了,其实浏览器是反对 http
门路加载的,为此咱们能够将其改成 import get from 'https://unpkg.com/lodash.get'
。
可怜的是,控制台还是报错了。谬误的起因是 lodash.get
这个 package
遵循的是 cjs
标准,但浏览器只认 esm
标准,所以无奈解析并执行这个 package
代码。
2)SystemJS 尝试
既然浏览器原生只反对 esm
标准的代码,那是否有方法反对 cjs
标准的代码呢?通过一番调研,咱们发现了 SystemJS
这个库(传送门:https://github.com/systemjs/systemjs)。
据其介绍,它就是一款用来解决浏览器运行 ES Module
的工具。然而当咱们依照其文档摸索一番后,发现加载 lodash.get
包时还是失败了,而且报的是雷同的谬误 … 好在咱们在它的 issue
区另有播种,发现了这么一个帖子:ES Modules and CommonJS?(PS:由此可见,该问题还是普遍存在的)
依据官网回复的内容,咱们能够提取以下几点要害信息:
- 0.21 版本之前的
SystemJS
是反对cjs
标准的,然而目前曾经不再反对。 - 之前的版本反对
cjs
次要是因为:之前的做法是先下载代码字符串,用正则匹配require
解析依赖,最初才执行代码;而当初的编译解析工作依附的是浏览器本身。 - 以前的这种解法其实存在性能隐患,故新版本中将不再思考。
为此,咱们又找到 0.21
版本的文档:https://github.com/systemjs/systemjs/blob/0.21/docs/module-formats.md
能够看到该版本的 SystemJS
仿佛能够满足咱们的需要,但最终 iMove
并没有采纳。因为官网曾经不举荐这种形式而且也不再保护,所以这条路又断了 …
3)新一代 JS 模块 CDN 托管
上述的问题仿佛走进了死胡同,但认真想来问题的实质不就是 『浏览器不反对 cjs
标准代码』 吗?
可是反过来想,浏览器为什么要反对 cjs
标准代码呢?SystemJS
之前做的 cjs to esm
这个工作为什么要在浏览器侧做,而不是放在 CDN
上实现呢?狼叔的这篇《2021 再看 Deno(CDN for JavaScript modules 的思考)》讲的很分明,转换这事儿其实放在 CDN
上来做更适合~
简略介绍一下,jspm 做得就是这个事儿。
为了验证成果,咱们来做个试验比照,以下别离是浏览器从 unpkg
和 jspm
上加载的 lodash.get
截图:
摸索 3:多文件合并成单文件执行
前文提到的『如何运行 import/export 问题』看起来仿佛曾经完满解决,但实操起来咱们却发现还是脱漏了一个细节:iMove
的出码是一个多文件的组织模式,因而浏览器将会以相对路径的模式引入其余文件,这意味着还需一个 http
服务来提供这些资源的加载,这是咱们不能承受的。
如何解?最简略的方法其实就是将 多文件合并成单文件执行,这样人造地就毁灭了相对路径的问题。咱们再来看个例子:
// a.js
import get from 'lodash.get';
const obj = {
text: 'a',
say: () => console.log(get(obj, 'text'))
};
export default obj;
// b.js
import get from 'lodash.get';
const obj = {
text: 'b',
say: () => console.log(get(obj, 'text'))
};
export default obj;
// main.js
import a from './a';
import b from './b';
a.say();
b.say();
上述是合并前的多文件组织模式,要合并它们其实并不难,只需注意以下 2 点:
import
某个文件时,该文件的代码须要立刻执行- 不同文件中的全局变量名可能雷同,须要解决命名净化的问题
// merged.js
const run = async () => {const modA = await (async () => {const get = (await import('https://jspm.dev/lodash.get')).default;
const obj = {
text: 'a',
say: () => console.log(get(obj, 'text'))
};
return obj;
})();
const modB = await (async () => {const get = (await import('https://jspm.dev/lodash.get')).default;
const obj = {
text: 'b',
say: () => console.log(get(obj, 'text'))
};
return obj;
})();
modA.say();
modB.say();};
run();
须要留神的是原来的 import
因为合并的起因呈现在了函数体当中,所以须要应用 dynamic import
的形式来加载网络包。剩下的只有用 正则表达式匹配替换 或者 AST
转换 都能实现对应的成果,本文就不再开展详述。
摸索 4:Script 间如何传值通信
行文到这,能够说是『万事俱备,只欠东风』了。通过方才的多文件合并,咱们只需运行代码,获取后果展现即可。这里须要留神的是,因为待执行的代码遵循 esm
标准,如果想用 eval
执行代码就有一个前提:以后代码必须也在 type="module"
的 script
标签下。不过咱们能够换个思路,不必 eval
也能执行字符串代码:
const script = document.createElement('script');
script.type = 'module';
script.text = 'code here';
document.body.appendChild(script);
如上所示,咱们能够通过动静插入 script
标签的形式来执行代码。可是随之而来的问题又变成了:咱们该如何在代码运行完之后触发回调拿到后果展现呢?何况当初这两段代码还是在两个 script
标签下。
最容易想到的方法就是用相似 jsonp
的形式,一方在全局 window
下注册惟一办法,另一方在代码执行完之后调用该办法。在这里,咱们介绍一种更优雅的形式,能够用事件通信的形式来解决~
MDN
官网文档传送门:Custom Events
// 监听事件 document.addEventListener(‘customEvent’, function (evt) {console.log(evt.detail); }, false); // 发送事件 document.dispatchEvent(new CustomEvent(‘customEvent’, {detail: {text: ‘iMove’} }));
总结
本文先后介绍了 iMove 在线运行节点代码这一路所踩过的坑,最终次要还是依附 http-import
的思维来解决问题。无疑,deno
扭转了大家的对包治理的认识。自身 deno
够小,试错成本低,它确确实实引领了一个潮流方向。这个改良虽说不算新,但反应的确很好,大略是天下人苦 npm
(npm 开玩笑的说法是:你怕吗)久已,用法简略,高效,甚至是衍生出很多对于 CDN for JavaScript modules 的思考。如果你有更好的想法,欢送一起交换。
另外,做 iMove
在线执行代码性能是用户视角做的决定,小伙伴们都十分认可这个决策,并在落地过程中,可能摸索出一种全新的形式,这是十分值得称赞的。自身 iMove
就是一个以锤炼团队为目标开源的我的项目,大家可能定义问题,可能解决问题,可能建设信念,可能激发技术激情,它的目标就达到了。