探索Taro:跨平台开发的实践与原理

政采云技术 发表于 1年以前  | 总阅读数:643 次

引言

在如今不断增长的小程序市场中,小程序的数量迅速增多。这是因为小程序具有诸多优势,例如轻量化、便捷性和良好的用户体验,吸引了越来越多的开发者和企业加入这一领域。随着小程序的普及,各个行业都纷纷推出自己的小程序,以满足用户的多样化需求。

然而,正是因为小程序市场的多样性和快速发展,每个小程序客户端的 Api 差异也变得十分显著。不同的小程序平台为了满足自身的特殊需求和功能定位,往往会对 Api 进行定制和调整。这导致了各个小程序客户端之间的 Api 存在差异,不同平台的开发者需要针对不同的 Api 进行开发和适配。

对于开发者来说,针对不同平台重新开发一套小程序应用将变成一场无尽的噩梦。开发者需要熟悉并掌握每个客户端的api差异,编写大量重复的代码,并进行平台特定的调试和适配工作。这不仅增加了开发的工作量和时间成本,还容易导致错误和兼容性问题。

在这样的背景下,Taro 的出现为开发者提供了一种解决方案。它通过提供一套统一的开发框架和组件,使开发者能够编写一套代码,同时在多个小程序平台上运行。Taro 的编译工具能够将开发者的代码转换为不同平台所需的代码,从而实现跨平台的开发和适配,减轻了开发者的负担,提高了开发效率。

Taro是一套遵循 React 语法规范的多端统一开发框架(ps:Vue 语法也支持)。主要用于构建跨平台的小程序、H5和移动应用。市面上还存在其他的多端框架,包括但不限于:

  • uni-app:uni-app是 DCloud 推出的一款基于 Vue.js 的跨平台开发框架,可用于构建微信小程序、支付宝小程序、H5、App等多个平台的应用。
  • React Native:React Native 是由 Facebook 开发的框架,用于构建原生移动应用。它使用JavaScript和React语法,允许开发者通过一套代码同时在 iOS 和 Android 上构建应用。
  • Flutter:Flutter是由 Google 开发的UI工具包,用于构建跨平台的移动、Web 和桌面应用。它使用 Dart 编程语言,提供了丰富的UI组件和渲染能力。
  • Weex:Weex 是由阿里巴巴开发的跨平台开发框架,使用 Vue.js 语法,用于构建移动应用。它支持在 iOS、Android和 Web 上运行。
  • NativeScript:NativeScript 是由 Progress 开发的开源框架,用于构建原生移动应用。它支持使用 JavaScript 或 TypeScript 编写代码,并提供了访问原生 Api 的能力。

在上述的这些中,只有uni-app是支持小程序场景的,它占据了多端框架的半壁江山。

概括来讲,Taro的主要特点和优势,参照官方说法:“使用 Taro,我们可以只书写一套代码,再通过 Taro 的编译工具,将源代码分别编译出可以在不同端(微信小程序、H5、App 端等)运行的代码。”

一次编译,多端运行

这里需要解释一下“编译时配置”机制。官方说的“一次编译”,并不是真的打一个 dist 包,能跑遍所有的平台。

而是根据你想要运行的平台,用对应的指令,打出适合该平台运行的包。

举个例子:

微信小程序 编译命令 yarn build:weapp
百度小程序 编译命令 yarn build:swan
支付宝小程序 编译命令 yarn build:alipay
H5 编译命令 yarn build:h5
RN 编译命令 yarn build:rn --platform ios
……

所以我们需要真正关心的,其实是针对目标的平台,Taro 都做了哪些事。下面以微信小程序为例子:

Taro 框架内置了对应的编译器和构建工具,在 @tarojs/plugin-platform-weapp 微信小程序平台插件中。在此注册微信小程序平台的配置项。

//taro-weapp/src/index.ts

//在此注册微信小程序平台
ctx.registerPlatform({
  name: 'weapp',
  useConfigName: 'mini',
  async fn ({ config }) {
    const program = new Weapp(ctx, config, options || {})
    await program.start()
  }
})

预先定义一个名为微信小程序的 Template 模版类,该类继承自 UnRecursiveTemplate。其主要功能是处理 Taro 框架中的模板相关操作,并根据特定需求进行定制。

//taro-weapp/src/template.ts

export class Template extends UnRecursiveTemplate {
  ...

  //构建wxs模板
  buildXsTemplate () {
    return '<wxs module="xs" src="./utils.wxs" />'
  }

  //创建小程序组件
  createMiniComponents (components): any {
    const result = super.createMiniComponents(components)

    // PageMeta & NavigationBar
    this.transferComponents['page-meta'] = result['page-meta']
    this.transferComponents['navigation-bar'] = result['navigation-bar']
    delete result['page-meta']
    delete result['navigation-bar']

    return result
  }

  //替换属性名称
  replacePropName (name: string, value: string, componentName: string, componentAlias) {
    ...
  }

  //构建wxs模板中与焦点相关的方法,根据插件选项判断是否启用键盘附件功能,并返回相应的字符串
  buildXSTepFocus (nn: string) {
    ...
  }

  //修改模板结果的方法,根据节点名称和插件选项对模板进行修改。
  modifyTemplateResult = (res: string, nodeName: string, _, children) => {
    ...
  }

  //构建页面模板的方法,根据基础路径和页面配置生成页面模板字符串。
  buildPageTemplate = (baseTempPath: string, page) => {
    ...
  }
}

Taro 的编译工具,根据所选择的平台,转换成对应平台所需的代码。使用 ctx.applyPlugins ,去调用相应平台的插件处理函数,其中 platform 参数指定对应的平台:

//taro-cli/src/build.ts

...
await ctx.applyPlugins(hooks.ON_BUILD_START)
await ctx.applyPlugins({
  name: platform,
  opts: {
    config: {
      ...config,
      isWatch,
      mode: isProduction ? 'production' : 'development',
      blended,
      isBuildNativeComp,
      newBlended,
      async modifyWebpackChain (chain, webpack, data) {
        await ctx.applyPlugins({
          name: hooks.MODIFY_WEBPACK_CHAIN,
          initialVal: chain,
          opts: {
            chain,
            webpack,
            data
          }
        })
      },
...

除此之外呢,代码转换过程,还涉及:

  • 语法转换:Taro 支持使用类似于 React 的 JSX 语法进行开发,它将 JSX 代码转换为不同平台所支持的语法,如小程序的 WXML、React Native 的组件等。
  • 样式转换:Taro 支持使用 CSS 预处理器编写样式,例如 Sass、Less 等。编译过程中,Taro 将这些样式文件转换为不同平台所支持的样式表,如小程序的 WXSS、H5 的 CSS 等。

在编译过程中,Taro 还会执行:

  • 静态资源处理:Taro 会处理项目中的静态资源文件,如图片、字体等,将其转换为适用于不同平台的格式,并进行压缩和优化。
  • 文件复制:Taro 会将一些不需要编译的文件直接复制到输出目录中,如项目配置文件、静态页面等。
  • 文件合并与分割:Taro 会根据配置和代码中的引用关系,将多个文件进行合并或分割,以提高代码加载性能。
  • 代码压缩与混淆:Taro 可以对生成的代码进行压缩和混淆,以减小文件体积和提高执行效率。

跨平台适配和差异处理

不通平台的api或多或少,总有一些差异。Taro如何实现api的适配和差异化处呢?

Taro 通过适配层和条件编译等机制实现 api 的适配和差异化处理。

它提供了一套统一的 api 接口,开发者可以在代码中使用这些 api,而 Taro 在编译过程中会将这些 api 转换为适用于各个平台的具体实现。

getLocation 为例。

如果我们要使用定位功能,在 Taro 中只需要在项目中使用 Taro 提供的 api getLocation :

Taro.getLocation().then(res => {
  console.log(res.latitude, res.longitude);
});

在编译过程中,Taro 会根据目标平台的差异,将这段代码转换为适用于不同平台的具体实现。

对于微信小程序来说,转换为微信小程序的 wx.getLocation,同时保留原始的参数和回调函数:

wx.getLocation().then(res => {
  console.log(res.latitude, res.longitude);
});

而对于支付宝小程序而言,Taro 则会将其转换为支付宝小程序的 my.getLocation,同样保留原始的参数和回调函数:

my.getLocation().then(res => {
  console.log(res.latitude, res.longitude);
});

如此,Taro 在编译过程中根据目标平台的差异,将统一的 api 转换为各个平台所支持的具体 api。在这段代码中,processApis 函数接收一个 api 集合作为参数,并对其中的每个api进行处理:

//shared/native-apis.ts

function processApis (taro, global, config: IProcessApisIOptions = {}) {
  ...
  apis.forEach(key => {
    if (_needPromiseApis.has(key)) {
      const originKey = key
      taro[originKey] = (options: Record<string, any> | string = {}, ...args) => {
        let key = originKey

        // 第一个参数 options 为字符串,单独处理
        if (typeof options === 'string') {
          ...
        }

        // 改变 key 或 option 字段,如需要把支付宝标准的字段对齐微信标准的字段
        if (config.transformMeta) {
          ...
        }
    ...

        // 为页面跳转相关的 api 设置一个随机数作为路由参数。为了给 runtime 区分页面。
        setUniqueKeyToRoute(key, options)

        // Promise 化:将原本的异步回调形式转换为返回Promise对象的形式,使api的调用更加方便且符合现代JavaScript的异步处理方式。
        const p: any = new Promise((resolve, reject) => {
          obj.success = res => {
            config.modifyAsyncResult?.(key, res)
            options.success?.(res)
            if (key === 'connectSocket') {
              resolve(
                Promise.resolve().then(() => task ? Object.assign(task, res) : res)
              )
            } else {
              resolve(res)
            }
          }
          obj.fail = res => {
            options.fail?.(res)
            reject(res)
          }
          obj.complete = res => {
            options.complete?.(res)
          }
          if (args.length) {
            task = global[key](obj, ...args)
          } else {
            task = global[key](obj)
          }
        })

        // 给 promise 对象挂载属性
        if (['uploadFile', 'downloadFile'].includes(key)) {
          ...
        }
        return p
      }
    } else {
      ...
    }
  })
  ...
}

ps:虽然 Taro 提供了一套统一的 api 接口,但某些平台可能不支持特定的功能或特性。可能需要使用条件编译来调用平台特定的 api,以处理特定平台的差异。

跨平台UI组件库

当我们使用 Taro 去编写多端项目,需要使用 Taro 提供的 View 等Taro组件。

因为,这些Taro组件,在不同平台上会被转换为相应的原生组件或元素。

举个例子,下面的代码中,我们使用Taro提供的Image,View,Text组件创建视图:

import Taro from '@tarojs/taro';
import { View, Text, Image } from '@tarojs/components';

function MyComponent() {
  return (
    <View>
      <Text>Hello</Text>
      <Image src="path/to/image.png" />
    </View>
  );
}

在编译生成过程中,Taro 会根据目标平台的差异将组件转换为适用于各个平台的具体组件。比如View 组件会被转换为微信小程序的 view 组件。对H5来说,View 组件会被转换为 <div> 元素。

在微信小程序中:

<view>
  <text>Hello</text>
  <image src="path/to/image.png"></image>
</view>

在 H5 中:

<div>
  <span>Hello</span>
  <img src="path/to/image.png" />
</div>

这样,我们可以使用相同的代码编写视图,也就是官方说的只要写一套代码的意思。

通过抽象层、平台适配、跨平台编译等处理,Taro其实已经为多端组件库的实现铺平了道路。如果你要做一个 Taro-UI 那样适应自己的多端组件库。直接使用Taro提供的基础组件去搭建复杂组件即可。

反向转换

如果你说,你以前做过一个微信小程序,现在老板要你平行移植到支付宝等小程序中。来不及重构代码的话,反向转换也许能救一救急。反向转换,故名思义就是将小程序转换为Taro项目。

相关的代码在 @tarojs/cli-convertor 包中,核心逻辑在 parseAst 中,生成 AST 树,遍历处理对应的内容:

//taro-cli-convertor/src/index.ts

parseAst ({ ast, sourceFilePath, outputFilePath, importStylePath, depComponents, imports = [] }: IParseAstOptions): {
    ast: t.File
    scriptFiles: Set<string>
  } {
    ...
    // 转换后js页面的所有自定义标签
    const scriptComponents: string[] = []
    ...
    traverse(ast, {
      Program: {
        enter (astPath) {
          astPath.traverse({
            //对类的遍历和判断
            ClassDeclaration (astPath){...},
           //表达式
            ClassExpression (astPath) {...},
            //导出
            ExportDefaultDeclaration (astPath) {...},
            //导入
            ImportDeclaration (astPath) {...},
            //调用
            CallExpression (astPath) {...},
            //检查节点的 object 属性是否为标识符 wx,如果是,则将 object 修改为标识符 Taro,并设置一个标志变量 needInsertImportTaro 为 true。这段代码可能是将 wx 替换为 Taro,以实现对 Taro 框架的兼容性处理。
            MemberExpression (astPath) {...},
            //检查节点的 property 属性是否为标识符 dataset,如果是,则将 object 修改为一个 getTarget 函数的调用表达式,传递了两个参数 object 和标识符 Taro。它还创建了一个导入语句,将 getTarget 函数引入,并将其赋值给一个对象模式。这段代码可能是对可选链式调用中的 dataset 属性进行处理,引入了 getTarget 函数来实现相应的转换。
            OptionalMemberExpression (astPath) {...},
            // 获取js界面所有用到的自定义标签,不重复
            JSXElement (astPath) {...},
            // 处理this.data.xx = XXX 的情况,因为此表达式在taro暂不支持, 转为setData
            // 将this.data.xx=XX 替换为 setData()
            AssignmentExpression (astPath) {...}
          })
        },
        exit (astPath) {...}
      },
    })
  ...
    return {
      ast,
      scriptFiles,
    }
  }

ps:尽管官方提供了反向转换这一种工具,但是目前还是有局限性的。并不是所有的小程序都支持反向转换,目前只有微信小程序。且并不是所有的原生 api 都可以被转换,需要注意。希望后续该功能能够继续扩大,完善。

性能优化——预渲染(Prerender)

为什么需要 Prerender?官方给出了解释:

Taro Next 在一个页面加载时需要经历以下步骤:

框架(React/Nerv/Vue)把页面渲染到虚拟 DOM 中

Taro 运行时把页面的虚拟 DOM 序列化为可渲染数据,并使用 setData() 驱动页面渲染

小程序本身渲染序列化数据

和原生小程序或编译型小程序框架相比,步骤 1 和 步骤 2 是多余的。如果页面的业务逻辑代码没有性能问题的话,大多数性能瓶颈出在步骤 2 的 setData() 上:由于初始化渲染是页面的整棵虚拟 DOM 树,数据量比较大,因此 setData() 需要传递一个比较大的数据,导致初始化页面时会一段白屏的时间。这样的情况通常发生在页面初始化渲染的 wxml 节点数比较大或用户机器性能较低时发生。

Taro 预渲染的工作原理是,在构建阶段使用服务器端渲染(SSR)的技术,将页面组件渲染成静态 HTML 文件,并将其保存在静态文件目录中。然后,当客户端请求该页面时,直接返回预渲染的静态 HTML,而不是动态生成页面。

通过在构建阶段将页面渲染为静态 HTML 文件,以提升首次加载速度、改善用户体验和优化搜索引擎的索引。

使用方式:

//config/index.js 或 /config/dev.js 或 /config/prod.js

const config = {
  ...
  mini: {
    prerender: {
      match: 'pages/shop/**', // 所有以 `pages/shop/` 开头的页面都参与 prerender
      include: ['pages/any/way/index'], // `pages/any/way/index` 也会参与 prerender
      exclude: ['pages/shop/index/index'] // `pages/shop/index/index` 不用参与 prerender
    }
  }
};

module.exports = config

更多使用详见官网文档。

总结

经过上面粗浅的分析,我们可以初步了解 Taro 的整套运作机制。以下是对其运作机制的总结:

  1. 代码转换和条件编译:Taro 通过将代码转换和条件编译应用于源代码,生成适用于目标平台的代码。这使得我们可以使用一套代码编写多个平台的应用程序。
  2. 抽象层和平台适配层:Taro 提供了一个抽象层和平台适配层来处理代码转换过程,确保 api 在不同平台上的兼容性。这使得我们可以在不同的平台上使用相同的 api 进行开发。
  3. Taro 自定义组件和多端适应性:Taro 的内置组件天然适应框架,这意味着我们可以构建适用于多个平台的组件库,如 Taro UI。这样可以提高开发效率并实现跨平台的一致性。
  4. 反向转换:反向转换是一种逆向思路,试图通过将已有的应用程序转换为 Taro 代码来实现跨平台。然而,反向转换存在不稳定性和局限性,并且对于维护者来说收益有限。
  5. 预渲染(Prerender)作为性能优化选择:Taro 提供了预渲染(Prerender)技术作为一种性能优化选择。预渲染可以在构建过程中生成静态 HTML 页面,以提升首次加载速度和优化搜索引擎的索引。这是一种有效的性能优化手段。

参考文献

https://taro-docs.jd.com/docs/

本文由微信公众号政采云技术原创,哈喽比特收录。
文章来源:https://mp.weixin.qq.com/s/VFbGS3R0GuO0nDU6EkxiQg

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
为Electron程序添加运行时日志 5年以前  |  20446次阅读
Node.js下通过配置host访问URL 5年以前  |  5919次阅读
用 esbuild 让你的构建压缩性能翻倍 4年以前  |  5827次阅读
 目录