wean:重新实现小程序架构

2,465 阅读5分钟

大家晚上好,俺是大朋友132,经过不懈的努力,wean 终于开源了

github.com/ctripcorp/w…

突然有种此时无声胜有声的感慨……

重要更新:改名了,不叫 wepack 了,新名字叫 wean,中文名伟岸

背景和痛点

随着大家项目复杂度的升高,基于 taro + wean 开发小程序的方式,一层层 loader 和 plugin,将开发链路大大延长

过长的开发链路,除了打包时间变慢,更重要的是严重影响力调试,测试,维护

除此之外,case by case 的 AST 转译方式,擅自扭曲语法语义,也会很不地道的行为

今年是打包工具大乱斗的一年,vite,snowpack 等工具出一个火一个

微信图片_20210315110205.png

wean

所以我就写了 wean

wean 旨在专门打包微信小程序,wepack 之于小程序可以类比为 metro 之于 RN

通俗的讲,它可以将微信项目打包成可执行的 js,css 文件,但和同类工具相比,它的中心是打包而不是转译

这有很多好处:

小程序引擎

你可以将 wean 的产物放到你们公司的 APP webview 上,这样就可以称为专属小程序系统

wean 虽然和传统小程序引擎不同,他没有使用双线程的架构,但它使用了微前端模拟沙箱环境,这对用户而言并无大碍

过去我一直在研究小程序引擎,但是我发现双线程的模型维护成本太高了,搞来搞去到头来还不如直接跑 webview

另外,wean 是微信小程序标准的重新实现,在实现过程中,我有机会解决一些微信残留的历史包袱

wean 是一个更现代的小程序子集

开箱即用

这也是首当其冲的,所谓开箱即用,0配置,简单说就是缩短链路,至少不能多次转译打包了

这一点也是我备受质疑的原因,很多人都告诉我,我们 webpack 用的好好的,我们连 vite 都不需要,更不需要 wean

诚然,目前国内 90% 的项目都是 webpack 主打的,我不打算推翻任何人

只是条条大道通罗马,我选择短路

跨双端

还要一个明显的好处就是,wepack 可以跨微信和 h5 端,虽然如果只是跨端的话没必要做到这个程度,但确实跨端是一个不可争议的业务场景

这个是我接下来要努力解决的事情了,毕竟刚刚也说了,公司永远都是 业务维护先于架构设计 有意思的东西

总体架构

为了写 wepack,我研究了几乎所有的打包工具,发现了许多有意思的事情,接下来我挑着说一下 面向数据结构编程

68747470733a2f2f692e6c6f6c692e6e65742f323032312f30332f31312f3466764a685a326c6255676d736a312e706e67.png

上面这张图是 wepack 的架构图,中间有一棵树叫做 ADT,通俗讲就是

我将微信整个项目用一棵树进行描述,然后使用 AST 分析每个文件,将有用信息放到这棵树上

然后其他的所有的操作,比如 tree shaking 就都可以围绕这棵树进行下去了

不会转译 AST 不会 case by case 不会扭曲语义

所以同样是编译,wepack 不会出现 taro2 那种应接不暇的情况

实际上,所有的打包工具都有类似的数据结构,比如 webpack 的 dependency graph,rollup 的 module graph 等等

所有的轮子都是面向数据结构编程,case by case 终究是不归路

tree shaking

在说 tree shaking 之前,先说一下常规的打包方式

v2-f01b879b40028d4940c4c2b3e27523ae_720w.jpg

传统的打包方式是利用 mapping + require 来实现的,因为需要实现一个 runtime 的函数,所以它无法被静态分析和优化

所以大家想到了一个办法,也就是作用域提升,就是将所有的函数都拍平,重命名

v2-c70685ef907b730cf583522f15e2ea98_720w.jpg

这样就很容易静态分析和 tree shaking 了

wepack 做的事粗粒度的 tree shaking,也就是 import 和 export 级别的,rollup 可以做到 scope 级别,terser 还会有常量传播的优化

module federation

MF 是 webpack5 的新 feature,你可以理解为 runtime 版本的 code splitting

import('app/a.js')
// 会被编译成
const remotes = {
  'app/a.js' : code
}
function ensure(key){
  return remotes[key]
}

原理非常简单,就是将 import() <import/> <template/> @import 这类特殊的标识编译为 ensure 函数,然后从全局 mapping 中拿代码

这个思路可真的帮了大忙,wxml 的语法糖比较乱,module federation 可以统一概括它们

wxml 编译器

说白了就是一个 html-parser,但我选择自己写,因为 wxml 比较骚,用现成的我不放心

和 homo 不一样,wepack 的 parser 我用了比较常规的写法,没有用 single pass

这是因为未来我可能写多个 generator,比如直接生成支付宝,百度,字节等等

对编译原理有兴趣的也可以看看

scoped css

因为有了编译方案,所以 scoped css 我们有了新的选择

wepack 使用类似 vue 的 data 前缀的方式进行 scoped css 的支持

input

<style>
.example{
    color:red;
}
</style>

<div>scoped</div>
output

<style>
.example[data-scoped-5558831a] {
  color: red;
}
</style>

<div class="example" data-scoped-5558831a>scoped</div>

效果

比想象中的好,也比想象中的坏

v2-c40a92137125009f04e054d7ed83062a_720w.jpg

好的是,简单项目需要改动的不多,也就调整一下css样式

比如这个图虫项目,就是 @飘香豆腐 写的小程序,很轻松就可以转出来

但是我们公司的项目就很复杂了,对我来说是个很大的挑战

但是我必须得搞出来,因为事实就是如此,一个轮子的作用,比起服务新业务,解决历史报复才是真正的刚需

接下来我会全身心投入到公司业务中,转成功一个项目,就意味着铲平一座 shi 山

这很难,但这就是刚需

总结

说实话 wepack 我思考的比写的代码更多,哪怕现在我开源出来了,代码仍旧很不老道

我接受吐槽,但希望更多的人能够提出建设性的意见和建议

因为这就是开源的意义,wepack 的模型目前没人做,所以它的架构并不稳定,需要大家一起探索

因为我还是要处理公司业务为主,所以对于架构的调整思路,只能寄希望于外界反馈啦!

以上,望天,我有写了这么多

最后再放一次连接的啦

github.com/ctripcorp/w…

欢迎 star,欢迎 discusssions 刷屏!