闽公网安备 35020302035485号
//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 支持使用 CSS 预处理器编写样式,例如 Sass、Less 等。编译过程中,Taro 将这些样式文件转换为不同平台所支持的样式表,如小程序的 WXSS、H5 的 CSS 等。
代码压缩与混淆:Taro 可以对生成的代码进行压缩和混淆,以减小文件体积和提高执行效率。
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,以处理特定平台的差异。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-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 都可以被转换,需要注意。希望后续该功能能够继续扩大,完善。//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
更多使用详见官网文档。