关于javascript:所见即所得-iMove-在线执行代码探索

自身 iMove 的定位就是 “一个逻辑可复用的,面向函数的,流程可视化的 JavaScript 工具库。”

对开发者而言, iMove 恰好是能够实现这些指标的现实工具。动动鼠标,写一下节点函数,代码导出,放到具体工程里就能够间接应用,是不是很不便?

那么,为什么咱们还要做 iMove 在线执行代码性能呢?站在用户视角看,对于开发者来说,选中节点,右键在线执行代码是必要的。这样能够把 iMove 的工具属性做到极致,让开发者体验更好,操作更简略。

上次在《登上 Github 趋势榜,iMove 原理技术大揭秘!》一文中咱们卖了个关子,明天就和大家分享下 iMove 是如何实现在线执行节点代码的~

引子

故事还得从组内的一个小伙伴说起,过后她是第一次应用 iMove,询问我如何测试运行刚写好的代码。二话不说,我就在她电脑上一通操作:

  1. 装置 @imove/cli
  2. 在我的项目根目录执行命令: imove -d
  3. 找到我的项目中的组件,增加代码 logic.invoke('trigger')
  4. 启动我的项目,关上控制台面板,筛选 log  信息
  5. … …

“我就想运行下刚写好的代码,看看输入后果是什么…”

『步骤繁琐』、『操作麻烦』、『有上手老本』、『运行后果不直观』… 此刻,脑中第一工夫闪过的就是以上这些词汇,再看向小伙伴那纳闷的眼神,好像下一刻她就要被劝退了…

这可怎么行,体验必须优化,应用老本必须降下来!于是就有了右键在线执行代码的性能。

其实上述问题的诉求很简略: 写完节点的代码之后,旁边有个运行按钮,一点间接就能看到运行后果就行了。咱们来看下就是这么一个优化都能带来哪些扭转:

  • 无装置工具要求,0 上手老本: 代码在浏览器端就能运行,不再须要装置命令行工具,大大降低了学习老本。
  • 后果可视化,简单明了: 开发调试过程中最罕用到的就是日志,每次都要关上控制台极不不便,间接将节点的运行后果用可视化的形式展现进去,简略直观。
  • 在线 mock 输出,不便测试: 每个节点的代码都会有各种可能的输出,如果能够间接 mock 输出,测试效率将得以极大的晋升。
  • 测试用例可保留,保障代码品质: 除了不便测试节点代码之外,如果能够将成对的输出/输入作为测试用例加以保留,渐而造成齐备的测试用例集,将能进一步保障节点的代码品质。
  • … …

想法是很美妙,但具体该如何实现呢?接着往下看~

摸索 1:节点代码在哪运行

首先,要解决的第一个问题就是:节点代码在哪运行?

通过评估,咱们认为次要有两个抉择:

  1. 浏览器端间接运行节点代码;
  2. 本地起一个服务,将节点信息发送至本地,本地构建编译完之后将 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 做得就是这个事儿。

为了验证成果,咱们来做个试验比照,以下别离是浏览器从 unpkgjspm 上加载的 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 就是一个以锤炼团队为目标开源的我的项目,大家可能定义问题,可能解决问题,可能建设信念,可能激发技术激情,它的目标就达到了。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理