微信小程序工程接入
微信小程序工程接入
注意
本文档旨在说明Kuikly项目微信小程序相关工程的接入,接入之前您应该已经有了Kuikly的工程,包括了Android和iOS,如果您还没有Kuikly工程,请先参考编写第一个Kuikly页面进行工程创建
新建项目
新建App目录
首先我们要新建miniApp项目,我们以kuikly工程的示例项目为例进行说明。

我们有形如这样的目录结构,其中androidApp为安卓宿主APP,iosApp为iOS的宿主APP,shared则是平台无关的业务逻辑代码。 然后我们要在项目根目录下新建 miniApp 目录,目录结构如下:

build.gradle.kts:项目构建配置文件
kotlin:业务逻辑代码
dist:微信小程序壳工程
配置项目
接下来我们要将我们的miniApp项目加入到整体的项目依赖中
# 根目录 settings.gradle.kts
# 添加 miniApp 的依赖
include(":miniApp")
此时我们的 miniApp 被添加到项目依赖中,我们可以配置项目构建相关逻辑了。 然后我们在 miniApp 目录下新建 build.gradle.kts 文件,内容如下:
import java.nio.file.Paths
plugins {
// Import KMM plugin
kotlin("multiplatform")
}
kotlin {
// Build JS output for webApp
js(IR) {
// Build output supports browser
browser {
webpackTask {
// Final output executable JS filename
outputFileName = "miniApp.js"
webpackConfigApplier {
val tempConfigFile = File(projectDir.absolutePath, "/webpack.config.d/config.js")
tempConfigFile.parentFile.mkdirs()
// use node target
tempConfigFile.writeText("""
config.target = 'node';
""".trimIndent())
file(tempConfigFile.absolutePath)
}
}
commonWebpackConfig {
// Do not export global objects, only export necessary entry methods
output?.library = null
devtool = null
}
}
// Package render code and webApp code together and execute directly
binaries.executable()
}
sourceSets {
val jsMain by getting {
dependencies {
// Import web render
implementation(project(":core-render-web:base"))
implementation(project(":core-render-web:miniapp"))
}
}
}
}
// Business project path name
val businessPathName = "demo"
/**
* Copy locally built unified JS result to miniApp's dist/business directory
*/
fun copyLocalJSBundle(buildSubPath: String) {
// Output target path
val destDir = Paths.get(project.buildDir.absolutePath, "../",
"dist", "business").toFile()
if (!destDir.exists()) {
// Create directory if it doesn't exist
destDir.mkdirs()
} else {
// Remove original files if directory exists
destDir.deleteRecursively()
}
val sourceDir = Paths.get(
project.rootDir.absolutePath,
businessPathName,
"build", buildSubPath
).toFile()
// Copy files
project.copy {
// Copy js files from business build result
from(sourceDir) {
include("nativevue2.js")
}
into(destDir)
}
}
project.afterEvaluate {
tasks.register<Copy>("syncRenderProductionToDist") {
from("$buildDir/productionExecutable")
into("$projectDir/dist/lib")
include("**/*.js", "**/*.d.ts")
}
tasks.register<Copy>("syncRenderDevelopmentToDist") {
from("$buildDir/developmentExecutable")
into("$projectDir/dist/lib")
include("**/*.js", "**/*.d.ts")
}
tasks.register<Copy>("copyAssets") {
val assetsDir = Paths.get(
project.rootDir.absolutePath,
businessPathName,
"src/commonMain/assets"
).toFile()
from(assetsDir)
into("$projectDir/dist/assets")
include("**/**")
}
tasks.named("jsBrowserProductionWebpack") {
finalizedBy("syncRenderProductionToDist")
}
tasks.named("jsBrowserDevelopmentWebpack") {
finalizedBy("syncRenderDevelopmentToDist")
}
tasks.register("jsMiniAppProductionWebpack") {
group = "kuikly"
// First execute jsBrowserProductionWebpack build task
dependsOn("jsBrowserProductionWebpack")
// Then copy corresponding nativevue2.zip from business build result and copy nativevue2.js to miniApp's release directory
copyLocalJSBundle("productionExecutable")
}
tasks.register("jsMiniAppDevelopmentWebpack") {
group = "kuikly"
// First execute jsBrowserDevelopmentWebpack build task
dependsOn("jsBrowserDevelopmentWebpack")
// Then copy corresponding nativevue2.zip from business build result and copy nativevue2.js to miniApp's release directory
copyLocalJSBundle("developmentExecutable")
}
}
需要重点注意的是,build.gradle.kts 中有 kuikly core 和 core 注解的依赖引入,这里需要改为实际的版本,可以参考 shared 目录中 build.gradle.kts 中的依赖,使用该版本依赖即可。webRender 的版本目前先固定引入上面配置中的版本
完成后可以在 Build 中 同步一下新依赖

此时我们项目配置完成,接下来进行 webRender 的接入
WebRender接入
miniApp 项目配置完成后,即可进行 webRender 的接入。
实现代理接口
我们首先在 miniApp/src/jsMain/kotlin 下新建 KuiklyWebRenderViewDelegator.kt 文件,内容如下:
// KuiklyWebRenderViewDelegator.kt
import com.tencent.kuikly.core.render.web.IKuiklyRenderExport
import com.tencent.kuikly.core.render.web.runtime.miniapp.expand.KuiklyRenderViewDelegator
import com.tencent.kuikly.core.render.web.expand.KuiklyRenderViewDelegatorDelegate
import com.tencent.kuikly.core.render.web.runtime.miniapp.core.Transform
import components.KRMyView
import components.KRWebView
import dom.MiniWebViewElement
import module.HRBridgeModule
import module.HRCacheModule
/**
* Implement the delegate interface provided by Web Render
*/
class KuiklyWebRenderViewDelegator : KuiklyRenderViewDelegatorDelegate {
// mini render delegate
val delegate = KuiklyRenderViewDelegator(this)
/**
* Register custom modules
*/
override fun registerExternalModule(kuiklyRenderExport: IKuiklyRenderExport) {
// Register bridge module
kuiklyRenderExport.moduleExport(HRBridgeModule.MODULE_NAME) {
HRBridgeModule()
}
kuiklyRenderExport.moduleExport(HRCacheModule.MODULE_NAME) {
HRCacheModule()
}
super.registerExternalModule(kuiklyRenderExport)
}
override fun registerExternalRenderView(kuiklyRenderExport: IKuiklyRenderExport) {
super.registerExternalRenderView(kuiklyRenderExport)
// Add template alias for custom views
Transform.addComponentsAlias(
MiniWebViewElement.NODE_NAME,
MiniWebViewElement.componentsAlias
)
// Register custom views to enable miniapp <web-view>
kuiklyRenderExport.renderViewExport(KRWebView.VIEW_NAME, {
KRWebView()
})
// Register custom views
kuiklyRenderExport.renderViewExport(KRMyView.VIEW_NAME, {
KRMyView()
})
}
}
本文件用于实现 webRender 提供的代理接入,传入 web 页面初始化相关参数,以及进行自定义模块/自定义view的注册等。
自定义模块
KuiklyWebRenderViewDelegator中依赖的HRBridgeModule内容如下:
// module/HRBridgeModule.kt
package module
import com.tencent.kuikly.core.render.web.export.KuiklyRenderBaseModule
import com.tencent.kuikly.core.render.web.ktx.KuiklyRenderCallback
import com.tencent.kuikly.core.render.web.nvi.serialization.json.JSONException
import com.tencent.kuikly.core.render.web.nvi.serialization.json.JSONObject
import com.tencent.kuikly.core.render.web.runtime.miniapp.core.NativeApi
import com.tencent.kuikly.core.render.web.utils.Log
import kotlin.js.json
/**
* Bridge interface module used by business side
*/
class HRBridgeModule : KuiklyRenderBaseModule() {
override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
return when (method) {
"toast" -> {
showToast(params)
}
else -> {
Log.error("$method not found")
callback?.invoke("{}")
}
}
}
private fun showToast(params: String?) {
if (params == null) {
return
}
try {
val data = JSONObject(params)
val icon = mapOf(
1 to "success",
2 to "error",
3 to "none"
)[data.optInt("mode")] ?: "none"
// NativeApi.plat.showToast = wx.showToast
NativeApi.plat.showToast(
json(
"title" to data.optString("content"),
"icon" to icon
)
)
} catch (e: JSONException) {
console.error("toast json parse error", e)
}
}
companion object {
const val MODULE_NAME = "HRBridgeModule"
}
}
如果要实现自定义扩展模块,可以参考HRBridgeModule的实现
package components
import com.tencent.kuikly.core.render.web.export.IKuiklyRenderViewExport
import com.tencent.kuikly.core.render.web.runtime.miniapp.dom.MiniDivElement
import com.tencent.kuikly.core.render.web.runtime.miniapp.dom.MiniSpanElement
import org.w3c.dom.Element
class KRMyView : IKuiklyRenderViewExport {
private val div = MiniDivElement()
override val ele: Element
get() = div.unsafeCast<Element>()
private val innerText = MiniSpanElement()
init {
ele.appendChild(innerText.unsafeCast<Element>())
}
override fun setProp(propKey: String, propValue: Any): Boolean {
return when (propKey) {
MESSAGE -> {
innerText.textContent = propValue.unsafeCast<String>()
return true
}
else -> super.setProp(propKey, propValue)
}
}
companion object {
const val MESSAGE = "message"
const val VIEW_NAME = "KRMyView"
}
}
如果要实现自定义view,可以参考这个KRMyView
页面接入
以上步骤完成后,即可进行页面的接入,在 kotlin 目录下新建 Main.kt,内容如下:
import com.tencent.kuikly.core.render.web.collection.FastMutableMap
import com.tencent.kuikly.core.render.web.ktx.SizeI
import com.tencent.kuikly.core.render.web.runtime.miniapp.MiniDocument
import com.tencent.kuikly.core.render.web.runtime.miniapp.core.App
import com.tencent.kuikly.core.render.web.runtime.miniapp.core.NativeApi
const val TAG = "Main"
fun main() {
App.onShow {
console.log(TAG, "app show")
}
App.onLaunch {
console.log(TAG, "app launch")
}
App.onHide {
console.log(TAG, "app hide")
}
}
/**
* Mini program page entry, use renderView delegate method to initialize and create renderView
*/
@JsName(name = "renderView")
@JsExport
@ExperimentalJsExport
fun renderView(json: dynamic) {
// Write to global render function
val renderParams = FastMutableMap<String, dynamic>(json)
// View size
var size: SizeI? = null
if (json.width != null && json.height != null) {
size = SizeI(json.width.unsafeCast<Int>(), json.height.unsafeCast<Int>())
}
MiniDocument.initPage(renderParams) { pageId: Int, pageName: String, paramsMap: FastMutableMap<String, Any> ->
val systemInfo = NativeApi.plat.getSystemInfoSync()
val isAndroid = systemInfo.platform == "android"
val params = paramsMap["param"].unsafeCast<FastMutableMap<String, Any>>()
params["is_wx_mp"] = "true"
paramsMap["platform"] = if (isAndroid) "android" else "iOS"
paramsMap["isIOS"] = !isAndroid
paramsMap["isIphoneX"] = !isAndroid && systemInfo.safeArea.top > 30
KuiklyWebRenderViewDelegator().delegate.onAttach(
pageId,
pageName,
paramsMap,
size,
)
}
}
/**
* Register callback methods on the mini program App object, needs to be called in the app.js of the mini program
*/
@JsName(name = "initApp")
@JsExport
@ExperimentalJsExport
fun initApp(options: dynamic = js("{}")) {
App.initApp(options)
}
小程序模版的配置,内容可以参考dist目录 此时我们项目接入已经完成,如有问题也可以参考我们github中的miniApp项目内容进行调整
项目启动
# 运行 shared 项目 dev server 服务器,没有安装 npm 包则先 npm install 安装一下依赖
npm run serve
# 构建 shared 项目 Debug 版
./gradlew :shared:packLocalJsBundleDebug
然后构建 miniApp 项目
# 运行 miniApp 服务器 Debug 版
./gradlew :miniApp:jsMiniAppDevelopmentWebpack
# 复制业务的assets文件到小程序目录
./gradlew :miniApp:copyAssets
构建 release 版本
# 首先构建业务 Bundle
./gradlew :demo:packLocalJSBundleRelease
# 然后构建 miniApp
./gradlew :miniApp:jsMiniAppProductionWebpack
使用微信小程序开发者工具打开miniApp下的dist目录,根据你的实际页面,修改app.json里面的pages数组和在pages里新建对应的页面。 然后编译即可看到微信小程序的效果 (如果发现Demo路由页没有成功跳转页面, 可以检查是否有在 miniApp/dist/pages 下新建页面模版,可以通过dist/create_page.sh脚本来生成)
// 例如demo里存在router的Page, 就需要在app.json的pages数组里添加 "pages/router/index", 同时在pages的目录里新建router目录补充和pages/index目录一样的内容
// pages/index/index.js内容
var render = require('../../lib/miniApp.js')
render.renderView({
// 这里的pageName是最高优先级,如果没配置,会去拿微信小程序启动参数里的page_name,如果都没有会报错
// 建议微信小程序的第一个页面必须配置pageName
// pageName: "router",
statusBarHeight: 0 // 如果要全屏,需要把状态栏高度设置为0
})
补充说明
微信小程序平台逻辑处理
如果业务逻辑中需要对来自微信小程序平台的进行特殊逻辑处理,可以在Main.kt文件业务入口中,添加业务代码中通过 pageData.params.optString("is_wx_mp") == "1" 来进行处理,比如微信小程序登录等等
特殊样式设置
由于微信小程序的某些特性比如滚动条是否隐藏必须通过 WXSS 来设置,因此无法通过编程实现,所以目前是通过在微信小程序壳工程里的app.wxss里添加对应样式实现