Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions android/dimina/src/main/kotlin/com/didi/dimina/Dimina.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,22 @@ class Dimina private constructor(context: Context) {
// 配置类
class DiminaConfig private constructor(builder: Builder) {
val debugMode: Boolean = builder.debugMode
val apiNamespaces: List<String> = builder.apiNamespaces

class Builder {
var debugMode: Boolean = false
internal var apiNamespaces: MutableList<String> = mutableListOf()

fun setDebugMode(debugMode: Boolean): Builder {
this.debugMode = debugMode
return this
}

fun addApiNamespace(name: String): Builder {
apiNamespaces.add(name)
return this
}

fun build(): DiminaConfig {
return DiminaConfig(this)
}
Expand All @@ -66,6 +73,8 @@ class Dimina private constructor(context: Context) {
return config.debugMode
}

fun getApiNamespaces(): List<String> = config.apiNamespaces

private val appContext: Context = context
private lateinit var config: DiminaConfig

Expand Down
7 changes: 7 additions & 0 deletions android/dimina/src/main/kotlin/com/didi/dimina/core/JsCore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ class JsCore {
* @param scriptPath The JavaScript code to evaluate
* @return The result of the evaluation
*/
fun evaluate(script: String): JSValue {
if (!isInitialized()) {
return JSValue.createError("Engine not initialized")
}
return jsEngine.evaluate(script)
}

fun evaluateFromFile(scriptPath: String): JSValue {
if (!isInitialized()) {
return JSValue.createError("Engine not initialized")
Expand Down
12 changes: 12 additions & 0 deletions android/dimina/src/main/kotlin/com/didi/dimina/core/MiniApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Context
import com.didi.dimina.Dimina
import com.didi.dimina.api.ApiRegistry
import com.didi.dimina.api.AsyncResult
import com.didi.dimina.api.BaseApiHandler
import com.didi.dimina.api.SyncResult
import com.didi.dimina.api.base.AppEventApi
import com.didi.dimina.api.base.BaseAPI
Expand Down Expand Up @@ -133,6 +134,13 @@ class MiniApp private constructor() {
} else {
LogUtils.d(tag, "Skipping JSSDK update check, last check was recent")
}
// Inject custom API namespaces before loading service.js
val namespaces = Dimina.getInstance().getApiNamespaces()
if (namespaces.isNotEmpty()) {
val json = namespaces.joinToString(",") { "\"$it\"" }
evaluate("globalThis.__diminaApiNamespaces = [$json]")
}

evaluateFromFile(
File(
context.filesDir.absolutePath,
Expand Down Expand Up @@ -352,6 +360,10 @@ class MiniApp private constructor() {
LogUtils.d(tag, "Cleared all Bridge lists")
}

fun registerApi(handler: BaseApiHandler) {
handler.registerWith(apiRegistry)
}

fun destroy() {
// Clear API resources
apiRegistry.clear()
Expand Down
4 changes: 3 additions & 1 deletion fe/packages/container/src/core/jscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export class JSCore {
// 使用 Web Worker 创建逻辑线程
// 使用下面的形式会使 hash 失效
// this.worker = new Worker(new URL('@dimina/service', import.meta.url));
this.worker = new Worker(serviceURL, { type: 'classic' })
const namespaces = this.parent.getApiNamespaces?.() || []
const workerName = JSON.stringify({ apiNamespaces: namespaces })
this.worker = new Worker(serviceURL, { type: 'classic', name: workerName })

// 监听逻辑线程的消息
this.worker.onmessage = (e) => {
Expand Down
87 changes: 87 additions & 0 deletions fe/packages/service/__tests__/env.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

// Mock all heavy dependencies — we only care about the namespace logic
const mockGlobalApi = { __mock: true }

vi.mock('@dimina/common', () => ({
modDefine: vi.fn(),
modRequire: vi.fn(),
}))
vi.mock('../src/api', () => ({ default: mockGlobalApi }))
vi.mock('../src/instance/component/component-module', () => ({ ComponentModule: { type: 'component' } }))
vi.mock('../src/instance/page/page-module', () => ({ PageModule: { type: 'page' } }))
vi.mock('../src/core/loader', () => ({ default: { createAppModule: vi.fn(), createModule: vi.fn() } }))
vi.mock('../src/core/router', () => ({ default: { stack: vi.fn(() => []) } }))
vi.mock('../src/core/runtime', () => ({ default: { app: null } }))

describe('env.js API namespace registration', () => {
beforeEach(() => {
// Clean up namespace-related globals before each test
delete globalThis.__diminaApiNamespaces
delete globalThis.qd
delete globalThis.myapp
delete globalThis.dd
delete globalThis.wx
// Simulate Worker's self (not available in Node)
globalThis.self = { name: '' }
})

afterEach(() => {
vi.resetModules()
})

it('should set dd and wx to globalApi', async () => {
await import('../src/core/env.js')

expect(globalThis.dd).toBe(mockGlobalApi)
expect(globalThis.wx).toBe(mockGlobalApi)
})

it('should register namespaces from __diminaApiNamespaces', async () => {
globalThis.__diminaApiNamespaces = ['qd', 'myapp']

await import('../src/core/env.js')

expect(globalThis.qd).toBe(mockGlobalApi)
expect(globalThis.myapp).toBe(mockGlobalApi)
// dd and wx should still work
expect(globalThis.dd).toBe(mockGlobalApi)
expect(globalThis.wx).toBe(mockGlobalApi)
})

it('should not create extra globals when __diminaApiNamespaces is empty', async () => {
globalThis.__diminaApiNamespaces = []

await import('../src/core/env.js')

expect(globalThis.qd).toBeUndefined()
})

it('should fall back to self.name when __diminaApiNamespaces is not set', async () => {
globalThis.name = JSON.stringify({ apiNamespaces: ['qd'] })

await import('../src/core/env.js')

expect(globalThis.qd).toBe(mockGlobalApi)
})

it('should ignore invalid JSON in globalThis.name', async () => {
globalThis.name = 'not-json'

await import('../src/core/env.js')

expect(globalThis.qd).toBeUndefined()
// Core globals should still work
expect(globalThis.wx).toBe(mockGlobalApi)
})

it('should prefer __diminaApiNamespaces over self.name', async () => {
globalThis.__diminaApiNamespaces = ['qd']
globalThis.name = JSON.stringify({ apiNamespaces: ['myapp'] })

await import('../src/core/env.js')

expect(globalThis.qd).toBe(mockGlobalApi)
expect(globalThis.myapp).toBeUndefined()
})
})
12 changes: 11 additions & 1 deletion fe/packages/service/src/core/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ class Env {
}

init() {
globalThis.dd = globalThis.wx = globalApi
// Register API namespaces (dd, wx are built-in; custom ones from config)
let customNamespaces = globalThis.__diminaApiNamespaces || []
if (customNamespaces.length === 0 && globalThis.name) {
try {
const config = JSON.parse(globalThis.name)
customNamespaces = config.apiNamespaces || []
} catch (e) {}
}
for (const name of ['dd', 'wx', ...customNamespaces]) {
globalThis[name] = globalApi
}
globalThis.modRequire = modRequire
globalThis.modDefine = modDefine
globalThis.global = {}
Expand Down
11 changes: 9 additions & 2 deletions harmony/dimina/src/main/ets/DApp/DMPApp.ets
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { DMPNavigatorManager } from '../Navigator/DMPNavigatorManager'
import { DRouter } from '../Navigator/DRouter'
import { DMPAppLifecycle } from './DMPAppLifecycle'
import { DMPDeviceUtil } from '../Utils/DMPDeviceUtils'

export type DMPBundleUpdateCallback = () => void;
//小程序应用实例
export class DMPApp {
Expand All @@ -39,6 +38,7 @@ export class DMPApp {
private onUpdateResult?: DMPBundleUpdateCallback
private _container: DMPContainer = new DMPContainer(this)
private static _context: common.UIAbilityContext
private static _apiNamespaces: string[] = []
private _containerBridges: DMPBridges = new DMPBridges()
private static _entryContext: DMPEntryContext
private _appModuleManager: DMPAppModuleManager = new DMPAppModuleManager(this)
Expand Down Expand Up @@ -122,9 +122,10 @@ export class DMPApp {
return DMPApp._entryContext.getWindowStage();
}

static init(context: DMPEntryContext) {
static init(context: DMPEntryContext, options?: { apiNamespaces?: string[] }) {
DMPApp._entryContext = context;
DMPApp._context = DMPApp._entryContext.getContext();
DMPApp._apiNamespaces = options?.apiNamespaces ?? [];
ImageKnife.with(DMPApp._entryContext.getContext())
context.getWindowStage().on('windowStageEvent', DMPAppLifecycle.onWindowStageEvent);
DMPDeviceUtil.prepareSafeAreaAndDisplayWH(DMPApp._entryContext.getContext())
Expand Down Expand Up @@ -354,6 +355,12 @@ export class DMPApp {

private async startEngineService() {
DMPLogger.d(Tags.LAUNCH, "startEngineService start");
// Inject custom API namespaces before loading service.js
const namespaces = DMPApp._apiNamespaces
if (namespaces.length > 0) {
const json = namespaces.map((n: string) => `"${n}"`).join(",")
this._service.executeScript(`globalThis.__diminaApiNamespaces = [${json}]`)
}
const serviceJsPath = await this._bundleManager.requestServiceJsUri();
await this._service.loadFileUri(serviceJsPath);
DMPLogger.i(Tags.LAUNCH, "startEngineService end");
Expand Down
6 changes: 6 additions & 0 deletions iOS/dimina/DiminaKit/App/DMPApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ public class DMPApp {

public func loadBundle() async {
print("loadBundle")
// Inject custom API namespaces before loading service.js
let namespaces = DMPAppManager.sharedInstance().apiNamespaces
if !namespaces.isEmpty {
let json = namespaces.map { "\"\($0)\"" }.joined(separator: ",")
await service?.evaluateScript("globalThis.__diminaApiNamespaces = [\(json)]")
}
await service?.loadFile(path: DMPSandboxManager.sdkServicePath())
await service?.loadFile(path: DMPSandboxManager.appServicePath(appId: appId))

Expand Down
11 changes: 8 additions & 3 deletions iOS/dimina/DiminaKit/App/DMPAppManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@

public class DMPAppManager {
private static let instance = DMPAppManager()

private var appPools: [Int: DMPApp] = [:]
private var appIndex: Int = 0

public private(set) var apiNamespaces: [String] = []

private init() {}

public static func sharedInstance() -> DMPAppManager {
return instance
}

public func setup(apiNamespaces: [String] = []) {
self.apiNamespaces = apiNamespaces
}

func getApp(appIndex: Int) -> DMPApp? {
return appPools[appIndex]
Expand Down
Loading