Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
soda-x opened this issue Jun 12, 2019 · 4 comments

Comments

@soda-x
Copy link
Owner

soda-x commented Jun 12, 2019

codesandbox - sandpack

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

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

这一块的优势我觉得有:

  • 实现了 HMR
  • npm 管理,类 stackbiltz turbo cdn
  • 支持大部分编译器,如 vue,babel,ts,css,sass,less,stylus,等等
  • 平行编译,利用 web worker(大型计算型操作均可使用 web worker 替代本地的子进程方式)
  • 按需编译
  • 类 webpack 的 loader 的语法支持
  • preset 的概念
  • 使用 cra 的 error overlay
  • bfs 的支持
  • 高效缓存

但最大的优势是:

  • Web friendly,就这一点就可以吊打当前所有采用 docker 方案做 web ide 预览的性能

缺点:

  • 当前这一部分代码目前作者并没有进行很好的抽离,sandpack 逻辑是耦合预览,编辑器
  • 只是一个运行时,没有生产环境的支持

systemjs@0.21.x + systemjs-builder

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

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

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

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

  • 基于 WHATWG 相关标准行事,具有一定的未来方向性
  • 通用的模块加载方案符合前端工程的需求
  • 模块加载方式可基于配置式,system.config.js 可用于描述模块的加载形式
  • loader API,可提供文件个性化加载能力
  • 可独立运行在 client 端

缺点也很明显:

  • 由于遵循浏览器标准,在 client 不支持 nodejs 模块的 resolve 算法
  • 不支持 browser 模块 resolve 算法
  • 不支持 nodejs buildin module 的 shim
  • 官方一些插件对于计算型任务的性能优化不足
  • 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,取决于用户。

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

  • hook API 实在是太 low level 了,甚至说有点丑陋
  • systemjs 2.0 以上急于脱离 WHATWG Fetch 步子迈得有点大,总有点扯着蛋的感觉
  • 基本没有生态,所有的坑需要自己趟平,无经验者谨慎开车
  • 无 bundle 流程,这一点倒是可以背靠 rollup / webpack

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 而来,这样的思路非常顺。

优点非常明显:

  • 这就是未来,未来或许真的就是一个 bundless 的世界。
  • 作者考虑了各种 fallback 兼容性的 case

缺点亦非常明显:

  • 新标准存在的不确定性,也不确定浏览器端最终会以什么方式实现,作为本地开发环境尝鲜可以
  • jspm 2.0 直接在安装时 transform node_modules 下所有的文件 *md -> es 的方式,总觉得会有隐患,只是个人感觉,可保持跟进
  • jspm 遵循 npm,npm 在 install 中速率优势不明显,外加 transform 时间,所以花在安装的时间会有比较显著的增长,如果说是 yarn 或者 cnpm/tnpm/pnpm 用户那感觉就更明显了
  • 缺乏真正的生产验证
  • 最要命的是缺乏真正的工作流,比如如何使用json 文件,图片资源文件,样式文件等,由于这些并不属于 native-module 的一个部分,所以目前尚缺合理的工作流来支撑真实的业务使用,如果说你之前是 rollup 或者说是 webpack 的用户,你的使用惯性将会被得到非常大的挑战。

@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 的看法

  • 不支持非 ESM 模块
  • 面向未来,步子迈得够大,灵魂跟不太上,就基于当前作者对于历史的态度,我不太建议大家使用
  • 没有文档,根本没有办法参与进去

给 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 本质上是 native-module 加载方案的 fallback
  • systemjs extra 可以补足业务使用的多样性
  • WHATWG Fetch 的存在,可以让纯基于 web 研发成为可能提供更多的可玩性

总结成一个字,稳。

所以接下来会基于 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
Copy link

YikaJ commented Dec 9, 2019

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

@soda-x
Copy link
Owner Author

soda-x commented Dec 9, 2019

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

@YikaJ
Copy link

YikaJ commented Dec 10, 2019

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

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

@Roxyhuang
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants