soda-x / blog

Here is my blog
753 stars 37 forks source link

codesandbox - sandpack 、systemjs @0.21.x @3.x 、JSPM 2.0 、@pika/web 带来的一些思考以及借鉴意义 #21

Open soda-x opened 5 years ago

soda-x commented 5 years ago

codesandbox - sandpack

诞生的意义:web ide 的诞生提供了在浏览器端进行编码的可能性,到现在来看目前也并没有一个构建工具对 web 层进行构建有足够的兼容。

codesandbox 的 sandpack 并不是严格意义上的构建工具,它只是提供了一个 runtime 时的 transpile 流程。Transpile 层思路其实很简单,即进行依赖分析后,拿到依赖,把依赖给到专属 transpiler 进行代码转换。

这一块的优势我觉得有:

但最大的优势是:

缺点:

systemjs@0.21.x + systemjs-builder

这个方案是之前我做小程序构建优化的根基,基于这一层我实现了配置式的 preset,类 webpack loader等等。

其诞生的意义:传统意义上在浏览器端只能通过类似 requirejs 等方案来实现模块加载,其只能加载特定规范的模块,而随着 npm 在前端界的铺开,一个项目中可能存在多种模块规范,所以我觉得 systemjs 最初的亮点来源于,在 client 端实现了对 CJS,AMD,UMD,ES,GLOBAL模块的加载。

systemjs 并不是构建工具而其只是一个通用模块的加载器,与之配套的 systemjs-builder 才是一个构建工具。

我觉得这套方案的优势有:

缺点也很明显:

当然如上这些缺陷我可以舍去 client 端,通过 server 中间件来介入到本地 nodejs 环境来解决,通过其他七七八八的插件来构建另外一个闭环。

这种方案仍然可以继续深挖,如作者所说,无需为 systemjs 版本升级而感焦虑。但根本问题让我感觉不安的是 systemjs-builder,其无法满足大而多需求的研发场景,简单来讲就是上面说的,他做的太简单了,很多 api 都是 low level 的,虽然这点对像我这类开发者其实是友好的我能接受,但无法接受的是其缓存策略,并发性,依赖树维护上有一些缺陷,需要花非常多经历去改造内核。

systemjs@3.x

其实从 systemjs 2.0 开始,作者就已经放弃 systemjs-builder 了,另外也并没有继续往 universal loader 的方向继续做,而是专注在了构建一个最好的最轻量化的以及可被更好 hack 的浏览器端加载器上。为什么他可以这么做?这就是它诞生的意义了。

其诞生的意义:据统计约 85% 的用户 已经运行在了可支持 native-modules 的浏览器环境中了。所以作者认为我们可以进一步优化原有的应用开发和部署思路,甚至大有去除本地构建的势头。

来说一说优势,这 2.0 版本开始,systemjs 分为了三个部分:

s.js: 1.5KB,用以支持现有的 native-module 的工作流,并向下兼容到 IE11。 system.js: 3KB,在支持现有的 native-module 的工作流基础上进一步支持了即将发布的新标准,诸如import-mapsWASM extras: 提供 0.21.x 上的一些功能,诸如 AMD、Named Exports 等等的支持,以及我认为可玩性较高的 transform loader,这个本质上就是利用 fetch 函数做出更加 high level 的 loader、 preset 的概念。

应该说如上三点,基本可以 fallback 0.21.x 时代。作者让我们不要有版本顾虑应该也来源于此吧。

优势其实很明显,通过拆分,用户可以根据自己需求引入自己想要的工作流,一种更加面向未来的方式。细心的同学有没有发现作者其实刻意在避开 build 这个概念,其实他是把这个流程抛给了 rollup 或者 webpack,而这一点也正应了 systemjs 想要去的方向,专注在浏览器加载,即运行时。

来细细琢磨下作者的小心思,或许工作流可以这样:

development: 纯 es module,采用 native-module 加载方案即可,这一点似乎在实际工程中不可能做到,比如还有 node_modules;如果,你还是有不可逾越的障碍,比如兼容性问题,但还是需要继续享受 es module 的 live bindings,动态 import 等等,所以 s.js 就出来了,s.js 对于一般业务也够用了。 production: systemjs + rollup format system / systemjs + webpack library target system / rollup iife,简单来讲就是在生产环境下你可以继续选择 systemjs 这一套 shim native module 的流程,也可以配合一些打包工具,完整输出一个 one bundle,取决于用户。

但这个方案下的缺点是什么:

jspm@2.0

jspm 2.0 目前所处 beta 阶段,这个应该是作者想要彻彻底底想要实现 bundless 一种实现方式。作者巧妙的把最不确定的 node_modules 做了一层移花接木。作者约定安装依赖需要通过 jspm 进行,jspm 在进行依赖安装的时候会把会把所有的安装模块转换成 es,并且生成一个 map 文件,而该 map 文件即可以是 systemjs 2.x 3.x 中 system.js 所面向的未来的模块家在方案。作者为什么可以这么任性呢?这又是诞生的背景了。

其诞生的背景和意义:接触过 native-module 的同学可能知道,在浏览器端要实现模块加载,必须要显示的申明模块的类型,即需要有完整的模块路径,这样或多或少会比较麻烦。而背景是 Import maps 在 Chrome 74 中可以以实验性质开启,Import maps 它是什么呢,其意义又在于哪里呢。本质上来讲,Import maps 就是一个配置文件,该配置文件描述了某个依赖的 resolve 方式,某种意义上来讲,Import maps 给浏览器端带来了包管理。

jspm@2.0 的出现应该是应运 systemjs 2.0 而来,这样的思路非常顺。

优点非常明显:

缺点亦非常明显:

@pika/web

对于 @pika 系工具的了解,完全出于其作者在社区的一篇文章 A Future Without Webpack,这篇文章引发了一众大佬的挑战,挑战的内容就是 one bundle 和 bundless 之间到底差在哪。比较典型的一篇讨论帖在这 Performance Breakdown and Bundler DX/UX/Perf Validations。各自相互举证甚是精彩。

@pika/web 的核心点也来源于浏览器端的 native-module 的加载方式,做法上和 jspm 2.0 类似但有差异,即 jspm 2.0 还有一个完整的 x_modules 结构,只不过内部是被转换了,另外可以轻松使用 link 或者 fallback 到 node_modules。但是 pika 则比较偏激,它是对依赖用 rollup 进行了一次打包输出为某些个 ESM 模块,这种方式简单高效,但这并不符合实际的项目开发,实际项目开发中我们或许需要 link 调试 component,需要 monkey-patch,最不能接受 @pika/web 的一点是,起完全看齐未来,不像 jspm 中,可以借由 systemjs 有一套 fallback 的方案。

所以我对 @pika/web 的看法

给 Gravity 的启发

Gravity 底层原先采用 systemjs@0.21.x + systemjs-builder,至于如何落地,大家可以参考下这篇文章 小程序构建重构我的一些个人思考 。这种方式下的一些不足已经在上诉中总结了。

那 Gravity 接下来如何去做呢?是保持现状完善还是继续突破。Vue-cli,Angular-cli 中 Modern Mode 给我了更多的启发。所谓 Modern Mode 就是 native-module 加载方案,但是是一种阉割版的 native-module 加载方式,如同我给 umi 提的 Modern Mode PR。Modern Mode 让我相信它绝对是一个方向,但当前社区或许有点剑走偏锋,在我看来或多或少是因为作者的某些执念与洁癖。业务本质上是骨感的,有很多的历史背景,技术禁锢,如何切合业务,去业务之痛,同时又能看齐未来,这是我给 Gravity 的下一个命题。

而我们又在 web-ide 的浪潮之下,静观四周,似乎我们找不到一个真正面向 browser 端的构建方案,基本上大家都在往 docker 上走,Gravity 如何能突破这套技术架构,占得先机,给到更加优质的开发体验,这是我对 Gravity 的理想。

在反复验证和思考下(很抱歉我真的花了很多时间),我计划基于 systemjs@3.x 来构建一个全新的生态。

为什么会是 systemjs@3.x?

虽然文章前边已经讲了,但还是再总结下

总结成一个字,稳。

所以接下来会基于 systemjs@3.x 重构一些实现,由于有 systemjs 0.21.x 的经验,相信会让这个流程变的可控和顺畅。

怎么做?

Gravity 后续会有三种形态,Modern ModeLegacy ModeOne bundle Mode,这三种模式分别对焦到不同的用户需求场景。

Modern Mode 是完全面向未来的方式,即完全采用 native-module 的加载方案,一期中还是会使用打包方案来解决 node_modules 问题,以及通过约束来解决自身模块加载问题;而二期,则会采用诸如 jspm 中 CJS2ES 配合 import-map,以及其他一些轻量化动态分析的方式来解决人为约束被动感。这一步的终态将会实现真正意义上的 bundless。

Legacy Mode 是一套面向未来的方式下的 polyfill 方案,即通过 systemjs 来实现,目前 systemjs 整体处于比较 low api level 的状态,设计中我会尝试把当前在 webpack 开发体验完全移植到 gravity 中来,同时这一步的实现会借鉴 codesandbox sandpack 的思路。言简意赅的来讲就 是Gravity 会基于 systemjs hook 以及 WHATWG Fetch 来封装出一个 Compiler 基类,对应 sandpack 中的 Compiler,用来处理文件的编译,如 babel、sass、less 等都是 Compiler 的实例实现;与此同时会把原先那套 webpack-like 的 loader 机制迁移过来,但不是中间件实现,而是通过纯 web;再通过 tapable 把 compilers 有机结合到一起形成 Preset,并适量提供一些插件化的能力,该 Preset 用以描述一种项目开发时形态的描述,这也是也延续 sandpack 的想法;还有非常重要的一点是设计一套缓存方案,该缓存方案要把本地文件系统和浏览器的 BFS相通,这一块的链接直接决定了 gravity 面向 web 研发的延续;另外还有通用类的,比如计算型任务 web worker管理,错误管理,配置管理等。 如上完成,大概 Gravity 的理想就可以实现了。可以把本地的研发能力通过 Legacy Mode 表达出来,或许细心的同学要问了,怎么没有构建,其实 Legacy Mode 可以是生产环境的终态,但当前 HTTP2 并不普及,以及离线方案广泛应用,这种 Legacy Mode 方案,更加适合于开发环境,基本做到次时代的 bundless 的概念。Legacy mode 的玩法可以有很多,我们可以基于 webpack 去构建出 target 为 system 的模块 又或者基于 rollup 去构建出 format 为 system 的模块,从而把多个工具生态串联起来,应用到这个模式中来。

One bundle Mode 本质上是对现实的妥协,当然我们的业务体量来讲,one bundle 的方式在生产环境下更占优,dev 环境下有劣势。要实现 one bundle 其实非常简单,我们可以直接使用 rollup 把 format 设置为 self-excuting 的模式即可,通常也叫做 iife。而此时对应的编译系统则会走到 rollup 生态。到这里可能就有同学会有疑惑,如何保证 dev 环境下的 compiler 和 rollup 生态下的 plugin 拥有一致性。要解决这一点其实非常简单,我们使用同一个编译的内核就行,举例来讲,比如 rollup 的 babel 插件用的也是 babel-core 等,我们也使用同样的 babel-core 即可。话粗理不粗。否则 codesandbox 早翻车了不是。

YikaJ commented 4 years ago

https://github.com/parcel-bundler/parcel/issues/1253 CodeSandbox 与 Parcel 合作,在 Browser 上除了可以 transform ,还可以 bundle 了。Awesome,期待越来越简化的工作流时代。

soda-x commented 4 years ago

@YikaJ 有没有兴趣一起来做?

YikaJ commented 4 years ago

@YikaJ 有没有兴趣一起来做?

这不期待着 Gravity 可以加入到社区大家一起建设嘛,design for the future

Roxyhuang commented 4 years ago

关注前端工程化已久,希望可以消化大佬的知识之后一起参与