提交 f2a941d0 编写于 作者: fxy060608's avatar fxy060608

wip(uts): bundle

上级 1c8a4f21
...@@ -10,18 +10,6 @@ module.exports = { ...@@ -10,18 +10,6 @@ module.exports = {
__GLOBAL__: false, __GLOBAL__: false,
__VUE_OPTIONS_API__: true, __VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false, __VUE_PROD_DEVTOOLS__: false,
uni: {
requireNativePlugin(name) {
return {
invokeSync(args, callback) {
console.log(`invoke`, JSON.stringify(args))
},
invokeAsync(args, callback) {
console.log(`invokeAsync`, JSON.stringify(args))
},
}
},
},
}, },
coverageDirectory: 'coverage', coverageDirectory: 'coverage',
coverageReporters: ['html', 'lcov', 'text'], coverageReporters: ['html', 'lcov', 'text'],
......
import IUniLogin from '../interface' export function login(name: string, pwd: string) {
import { OnConnect, RegisterOptions } from '../interface' return { name, pwd }
}
export default class UniLogin implements IUniLogin { export class User {
//@UniJSMethod({ uiThread: true }) async login(name: string, pwd: string) {
return { name, pwd }
override login(name, code) {
console.log(name, code)
}
// {"name":"fxy","callback1":{__type__:"fun",id:1},"callback2":{__type__:"fun",id:1}}
// => RegisterOptions
async register(opts: RegisterOptions) {
opts.callback1(true)
opts.callback2(true)
opts.abc.callback3(true)
} }
override onConnect(callback: OnConnect) {}
} }
export type RegisterOptions = { export declare function login(name: string, pwd: string): void
name: string export declare class User {
callback1: (res: boolean) => void login(name: string, pwd: string): void
callback2: (res: boolean) => void
abc: {
callback3: (res: boolean) => void
}
}
export type OnConnect = () => void
export default interface IUniLogin {
login: (name: string, code: number) => void
register: (opts: RegisterOptions) => Promise<void>
onConnect: (callback: OnConnect) => void
} }
package index; package index;
import ...interface.interface as IUniLogin; fun login(name: String, pwd: String): UtsJSONObject {
import ...interface.OnConnect; return object : () {
import ...interface.RegisterOptions; var name = name
open class UniLogin : IUniLogin { var pwd = pwd
override fun login(name, code) { };
console.log(name, code); }
} open class User {
open fun async register(opts: RegisterOptions) { open fun async login(name: String, pwd: String) {
opts.callback1(true); return object : () {
opts.callback2(true); var name = name
opts.abc.callback3(true); var pwd = pwd
};
} }
override fun onConnect(callback: OnConnect) {}
} }
...@@ -7,6 +7,8 @@ declare namespace NodeJS { ...@@ -7,6 +7,8 @@ declare namespace NodeJS {
UNI_NODE_ENV: 'production' | 'development' | 'test' UNI_NODE_ENV: 'production' | 'development' | 'test'
UNI_PLATFORM: UniApp.PLATFORM UNI_PLATFORM: UniApp.PLATFORM
UNI_SUB_PLATFORM: 'quickapp-webview-huawei' | 'quickapp-webview-union' UNI_SUB_PLATFORM: 'quickapp-webview-huawei' | 'quickapp-webview-union'
UNI_APP_PLATFORM: 'app-android' | 'app-ios'
UNI_INPUT_DIR: string UNI_INPUT_DIR: string
UNI_OUTPUT_DIR: string UNI_OUTPUT_DIR: string
UNI_CLI_CONTEXT: string UNI_CLI_CONTEXT: string
......
...@@ -11468,6 +11468,8 @@ const API_GET_SYSTEM_SETTING = 'getSystemSetting'; ...@@ -11468,6 +11468,8 @@ const API_GET_SYSTEM_SETTING = 'getSystemSetting';
const API_GET_APP_AUTHORIZE_SETTING = 'getAppAuthorizeSetting'; const API_GET_APP_AUTHORIZE_SETTING = 'getAppAuthorizeSetting';
const API_OPEN_APP_AUTHORIZE_SETTING = 'openAppAuthorizeSetting';
const API_GET_STORAGE = 'getStorage'; const API_GET_STORAGE = 'getStorage';
const GetStorageProtocol = { const GetStorageProtocol = {
key: { key: {
...@@ -14125,6 +14127,19 @@ const getAppAuthorizeSetting = defineSyncApi(API_GET_APP_AUTHORIZE_SETTING, () = ...@@ -14125,6 +14127,19 @@ const getAppAuthorizeSetting = defineSyncApi(API_GET_APP_AUTHORIZE_SETTING, () =
return appAuthorizeSetting; return appAuthorizeSetting;
}); });
const openAppAuthorizeSetting = defineAsyncApi(API_OPEN_APP_AUTHORIZE_SETTING, (_, { resolve, reject }) => {
const { openAppAuthorizeSetting } = weex.requireModule('plus');
const fn = openAppAuthorizeSetting;
fn((ret) => {
if (ret.type === 'success') {
resolve();
}
else {
reject();
}
});
});
const getImageInfo = defineAsyncApi(API_GET_IMAGE_INFO, (options, { resolve, reject }) => { const getImageInfo = defineAsyncApi(API_GET_IMAGE_INFO, (options, { resolve, reject }) => {
const path = TEMP_PATH + '/download/'; const path = TEMP_PATH + '/download/';
plus.io.getImageInfo(extend(options, { plus.io.getImageInfo(extend(options, {
...@@ -15962,7 +15977,9 @@ const chooseLocation = defineAsyncApi(API_CHOOSE_LOCATION, (options, { resolve, ...@@ -15962,7 +15977,9 @@ const chooseLocation = defineAsyncApi(API_CHOOSE_LOCATION, (options, { resolve,
let result; let result;
const page = showPage({ const page = showPage({
url: '__uniappchooselocation', url: '__uniappchooselocation',
data: options, data: extend({}, options, {
locale: getLocale(),
}),
style: { style: {
// @ts-expect-error // @ts-expect-error
animationType: options.animationType || 'slide-in-bottom', animationType: options.animationType || 'slide-in-bottom',
...@@ -15999,7 +16016,9 @@ const chooseLocation = defineAsyncApi(API_CHOOSE_LOCATION, (options, { resolve, ...@@ -15999,7 +16016,9 @@ const chooseLocation = defineAsyncApi(API_CHOOSE_LOCATION, (options, { resolve,
const openLocation = defineAsyncApi(API_OPEN_LOCATION, (data, { resolve, reject }) => { const openLocation = defineAsyncApi(API_OPEN_LOCATION, (data, { resolve, reject }) => {
showPage({ showPage({
url: '__uniappopenlocation', url: '__uniappopenlocation',
data, data: extend({}, data, {
locale: getLocale(),
}),
style: { style: {
titleNView: { titleNView: {
type: 'transparent', type: 'transparent',
...@@ -19227,6 +19246,7 @@ var uni$1 = { ...@@ -19227,6 +19246,7 @@ var uni$1 = {
getWindowInfo: getWindowInfo, getWindowInfo: getWindowInfo,
getSystemSetting: getSystemSetting, getSystemSetting: getSystemSetting,
getAppAuthorizeSetting: getAppAuthorizeSetting, getAppAuthorizeSetting: getAppAuthorizeSetting,
openAppAuthorizeSetting: openAppAuthorizeSetting,
getImageInfo: getImageInfo, getImageInfo: getImageInfo,
getVideoInfo: getVideoInfo, getVideoInfo: getVideoInfo,
previewImage: previewImage, previewImage: previewImage,
......
import { normalizeArg, initModule } from '../module' import {
normalizeArg,
initUtsProxyFunction,
initUtsProxyClass,
} from '../src/uts'
describe('uts-module', () => { describe('uts-module', () => {
test('normalize args', () => { test('normalize args', () => {
...@@ -33,27 +37,47 @@ describe('uts-module', () => { ...@@ -33,27 +37,47 @@ describe('uts-module', () => {
success: 6, success: 6,
}) })
}) })
test(`invoke`, () => { test(`initProxyFunction`, () => {
const wifi = initModule('wifi', { ;[true, false].forEach((async) => {
preparePermission: { async: true }, const preparePermission = initUtsProxyFunction({
}) pkg: 'testPlugin',
/** cls: '',
* {"module":"wifi","method":"preparePermission","params":[{"name":"foo","age":10,"success":7,"fail":8},9]} method: 'preparePermission',
*/ async,
wifi.preparePermission( })
{ /**
name: 'foo', * {"package":"testPlugin","class":"","method":"preparePermission","params":[{"name":"foo","age":10,"success":7,"fail":8},9]}
age: 10, */
success(res: any) { preparePermission(
console.log('success', res) {
}, family: {
fail(res: any) { father: 'f',
console.log('fail', res) mother: 'm',
},
name: 'foo',
age: 10,
success(res: any) {
console.log('success', res)
},
fail(res: any) {
console.log('fail', res)
},
}, },
(res: any) => {
console.log('callback', res)
}
)
})
})
test(`initProxyClass`, () => {
const WifiManager = initUtsProxyClass({
pkg: 'testPlugin',
cls: 'WifiManager',
methods: {
preparePermission: {},
}, },
(res: any) => { })
console.log('callback', res) const wifi = new WifiManager()
} wifi.preparePermission(1, 2, 3, () => {})
)
}) })
}) })
...@@ -11,12 +11,12 @@ function assertKey(key, shallow = false) { ...@@ -11,12 +11,12 @@ function assertKey(key, shallow = false) {
throw new Error(`${shallow ? 'shallowSsrRef' : 'ssrRef'}: You must provide a key.`); throw new Error(`${shallow ? 'shallowSsrRef' : 'ssrRef'}: You must provide a key.`);
} }
} }
function proxy(target, track, trigger) { function proxy$1(target, track, trigger) {
return new Proxy(target, { return new Proxy(target, {
get(target, prop) { get(target, prop) {
track(); track();
if (shared.isObject(target[prop])) { if (shared.isObject(target[prop])) {
return proxy(target[prop], track, trigger); return proxy$1(target[prop], track, trigger);
} }
return Reflect.get(target, prop); return Reflect.get(target, prop);
}, },
...@@ -48,7 +48,7 @@ const ssrServerRef = (value, key, shallow = false) => { ...@@ -48,7 +48,7 @@ const ssrServerRef = (value, key, shallow = false) => {
get: () => { get: () => {
track(); track();
if (!shallow && shared.isObject(value)) { if (!shallow && shared.isObject(value)) {
return proxy(value, track, customTrigger); return proxy$1(value, track, customTrigger);
} }
return value; return value;
}, },
...@@ -135,8 +135,91 @@ const onNavigationBarSearchInputConfirmed = ...@@ -135,8 +135,91 @@ const onNavigationBarSearchInputConfirmed =
const onNavigationBarSearchInputFocusChanged = const onNavigationBarSearchInputFocusChanged =
/*#__PURE__*/ createHook(uniShared.ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED); /*#__PURE__*/ createHook(uniShared.ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED);
let callbackId = 1;
let proxy;
const callbacks = {};
function normalizeArg(arg) {
if (typeof arg === 'function') {
const id = callbackId++;
callbacks[id] = arg;
return id;
}
else if (shared.isPlainObject(arg)) {
Object.keys(arg).forEach((name) => {
arg[name] = normalizeArg(arg[name]);
});
}
return arg;
}
function isProxyInvokeCallbackResponse(res) {
return !!res.name;
}
function initUtsProxyFunction({ pkg, cls, method, async, }) {
const invokeCallback = ({ id, name, params, keepAlive, }) => {
const callback = callbacks[id];
if (callback) {
callback(...params);
if (!keepAlive) {
delete callbacks[id];
}
}
else {
console.error(`${pkg}${cls ? '.' + cls : ''}.${method} ${name} is not found`);
}
};
return (...args) => {
if (!proxy) {
proxy = uni.requireNativePlugin('ProxyModule');
}
const params = args.map((arg) => normalizeArg(arg));
const invokeArgs = { package: pkg, class: cls, method, params };
if (async) {
return new Promise((resolve, reject) => {
proxy.invokeAsync(invokeArgs, (res) => {
if (isProxyInvokeCallbackResponse(res)) {
invokeCallback(res);
}
else {
if (res.errMsg) {
reject(res.errMsg);
}
else {
resolve(res.params);
}
}
});
});
}
return proxy.invokeSync(invokeArgs, invokeCallback);
};
}
function initUtsProxyClass({ pkg, cls, methods, }) {
return class ProxyClass {
constructor() {
const target = {};
return new Proxy(this, {
get(_, method) {
if (!target[method]) {
if (shared.hasOwn(methods, method)) {
target[method] = initUtsProxyFunction({
pkg,
cls,
method,
async: methods[method].async,
});
}
return target[method];
}
},
});
}
};
}
exports.getCurrentSubNVue = getCurrentSubNVue; exports.getCurrentSubNVue = getCurrentSubNVue;
exports.getSsrGlobalData = getSsrGlobalData; exports.getSsrGlobalData = getSsrGlobalData;
exports.initUtsProxyClass = initUtsProxyClass;
exports.initUtsProxyFunction = initUtsProxyFunction;
exports.onAddToFavorites = onAddToFavorites; exports.onAddToFavorites = onAddToFavorites;
exports.onBackPress = onBackPress; exports.onBackPress = onBackPress;
exports.onError = onError; exports.onError = onError;
......
...@@ -35,6 +35,10 @@ export declare function getCurrentSubNVue(): any; ...@@ -35,6 +35,10 @@ export declare function getCurrentSubNVue(): any;
export declare function getSsrGlobalData(): any; export declare function getSsrGlobalData(): any;
export declare function initUtsProxyClass({ pkg, cls, methods, }: ProxyClassOptions): any;
export declare function initUtsProxyFunction({ pkg, cls, method, async, }: ProxyFunctionOptions): (...args: unknown[]) => any;
declare type LaunchOption = LaunchShowOption; declare type LaunchOption = LaunchShowOption;
declare interface LaunchShowOption { declare interface LaunchShowOption {
...@@ -161,6 +165,26 @@ declare interface PageScrollOption { ...@@ -161,6 +165,26 @@ declare interface PageScrollOption {
scrollTop: number; scrollTop: number;
} }
declare interface ProxyBaseOptions {
pkg: string;
cls: string;
method: string;
}
declare interface ProxyClassOptions {
pkg: string;
cls: string;
methods: {
[name: string]: {
async?: boolean;
};
};
}
declare interface ProxyFunctionOptions extends ProxyBaseOptions {
async?: boolean;
}
declare interface ReferrerInfo { declare interface ReferrerInfo {
appId: string; appId: string;
extraData?: any; extraData?: any;
......
import { shallowRef, ref, getCurrentInstance, isInSSRComponentSetup, injectHook } from 'vue'; import { shallowRef, ref, getCurrentInstance, isInSSRComponentSetup, injectHook } from 'vue';
import { hasOwn, isString } from '@vue/shared'; import { hasOwn, isString, isPlainObject } from '@vue/shared';
import { sanitise, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, UNI_SSR, ON_SHOW, ON_HIDE, ON_LAUNCH, ON_ERROR, ON_THEME_CHANGE, ON_PAGE_NOT_FOUND, ON_UNHANDLE_REJECTION, ON_INIT, ON_LOAD, ON_READY, ON_UNLOAD, ON_RESIZE, ON_BACK_PRESS, ON_PAGE_SCROLL, ON_TAB_ITEM_TAP, ON_REACH_BOTTOM, ON_PULL_DOWN_REFRESH, ON_SAVE_EXIT_STATE, ON_SHARE_TIMELINE, ON_ADD_TO_FAVORITES, ON_SHARE_APP_MESSAGE, ON_NAVIGATION_BAR_BUTTON_TAP, ON_NAVIGATION_BAR_SEARCH_INPUT_CHANGED, ON_NAVIGATION_BAR_SEARCH_INPUT_CLICKED, ON_NAVIGATION_BAR_SEARCH_INPUT_CONFIRMED, ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED } from '@dcloudio/uni-shared'; import { sanitise, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, UNI_SSR, ON_SHOW, ON_HIDE, ON_LAUNCH, ON_ERROR, ON_THEME_CHANGE, ON_PAGE_NOT_FOUND, ON_UNHANDLE_REJECTION, ON_INIT, ON_LOAD, ON_READY, ON_UNLOAD, ON_RESIZE, ON_BACK_PRESS, ON_PAGE_SCROLL, ON_TAB_ITEM_TAP, ON_REACH_BOTTOM, ON_PULL_DOWN_REFRESH, ON_SAVE_EXIT_STATE, ON_SHARE_TIMELINE, ON_ADD_TO_FAVORITES, ON_SHARE_APP_MESSAGE, ON_NAVIGATION_BAR_BUTTON_TAP, ON_NAVIGATION_BAR_SEARCH_INPUT_CHANGED, ON_NAVIGATION_BAR_SEARCH_INPUT_CLICKED, ON_NAVIGATION_BAR_SEARCH_INPUT_CONFIRMED, ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED } from '@dcloudio/uni-shared';
function getSSRDataType() { function getSSRDataType() {
...@@ -103,4 +103,85 @@ const onNavigationBarSearchInputConfirmed = ...@@ -103,4 +103,85 @@ const onNavigationBarSearchInputConfirmed =
const onNavigationBarSearchInputFocusChanged = const onNavigationBarSearchInputFocusChanged =
/*#__PURE__*/ createHook(ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED); /*#__PURE__*/ createHook(ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED);
export { getCurrentSubNVue, getSsrGlobalData, onAddToFavorites, onBackPress, onError, onHide, onInit, onLaunch, onLoad, onNavigationBarButtonTap, onNavigationBarSearchInputChanged, onNavigationBarSearchInputClicked, onNavigationBarSearchInputConfirmed, onNavigationBarSearchInputFocusChanged, onPageNotFound, onPageScroll, onPullDownRefresh, onReachBottom, onReady, onResize, onSaveExitState, onShareAppMessage, onShareTimeline, onShow, onTabItemTap, onThemeChange, onUnhandledRejection, onUnload, requireNativePlugin, resolveEasycom, shallowSsrRef, ssrRef }; let callbackId = 1;
let proxy;
const callbacks = {};
function normalizeArg(arg) {
if (typeof arg === 'function') {
const id = callbackId++;
callbacks[id] = arg;
return id;
}
else if (isPlainObject(arg)) {
Object.keys(arg).forEach((name) => {
arg[name] = normalizeArg(arg[name]);
});
}
return arg;
}
function isProxyInvokeCallbackResponse(res) {
return !!res.name;
}
function initUtsProxyFunction({ pkg, cls, method, async, }) {
const invokeCallback = ({ id, name, params, keepAlive, }) => {
const callback = callbacks[id];
if (callback) {
callback(...params);
if (!keepAlive) {
delete callbacks[id];
}
}
else {
console.error(`${pkg}${cls ? '.' + cls : ''}.${method} ${name} is not found`);
}
};
return (...args) => {
if (!proxy) {
proxy = uni.requireNativePlugin('ProxyModule');
}
const params = args.map((arg) => normalizeArg(arg));
const invokeArgs = { package: pkg, class: cls, method, params };
if (async) {
return new Promise((resolve, reject) => {
proxy.invokeAsync(invokeArgs, (res) => {
if (isProxyInvokeCallbackResponse(res)) {
invokeCallback(res);
}
else {
if (res.errMsg) {
reject(res.errMsg);
}
else {
resolve(res.params);
}
}
});
});
}
return proxy.invokeSync(invokeArgs, invokeCallback);
};
}
function initUtsProxyClass({ pkg, cls, methods, }) {
return class ProxyClass {
constructor() {
const target = {};
return new Proxy(this, {
get(_, method) {
if (!target[method]) {
if (hasOwn(methods, method)) {
target[method] = initUtsProxyFunction({
pkg,
cls,
method,
async: methods[method].async,
});
}
return target[method];
}
},
});
}
};
}
export { getCurrentSubNVue, getSsrGlobalData, initUtsProxyClass, initUtsProxyFunction, onAddToFavorites, onBackPress, onError, onHide, onInit, onLaunch, onLoad, onNavigationBarButtonTap, onNavigationBarSearchInputChanged, onNavigationBarSearchInputClicked, onNavigationBarSearchInputConfirmed, onNavigationBarSearchInputFocusChanged, onPageNotFound, onPageScroll, onPullDownRefresh, onReachBottom, onReady, onResize, onSaveExitState, onShareAppMessage, onShareTimeline, onShow, onTabItemTap, onThemeChange, onUnhandledRejection, onUnload, requireNativePlugin, resolveEasycom, shallowSsrRef, ssrRef };
...@@ -2,3 +2,4 @@ export * from './ssr' ...@@ -2,3 +2,4 @@ export * from './ssr'
export * from './api' export * from './api'
export * from './utils' export * from './utils'
export * from './apiLifecycle' export * from './apiLifecycle'
export { initUtsProxyClass, initUtsProxyFunction } from './uts'
declare const uni: { import { isPlainObject, hasOwn } from '@vue/shared'
requireNativePlugin(name: string): { invoke: Function } declare const uni: any
}
const moduleName = '__MODULE_NAME__'
const moduleDefine = '__MODULE_DEFINE__' as unknown as Record<
string,
ModuleMethodDefine
>
interface ModuleMethodDefine {
async?: boolean
}
export default initModule(moduleName, moduleDefine)
let callbackId = 1 let callbackId = 1
let proxy: any
const objectToString = Object.prototype.toString
const toTypeString = (value: unknown): string => objectToString.call(value)
const isPlainObject = (val: unknown): val is object =>
toTypeString(val) === '[object Object]'
const callbacks: Record<string, Function> = {} const callbacks: Record<string, Function> = {}
export function normalizeArg(arg: unknown) { export function normalizeArg(arg: unknown) {
if (typeof arg === 'function') { if (typeof arg === 'function') {
...@@ -55,12 +34,30 @@ function isProxyInvokeCallbackResponse( ...@@ -55,12 +34,30 @@ function isProxyInvokeCallbackResponse(
): res is ProxyInvokeCallbackResponse { ): res is ProxyInvokeCallbackResponse {
return !!(res as ProxyInvokeCallbackResponse).name return !!(res as ProxyInvokeCallbackResponse).name
} }
function moduleGetter(
proxy: any, interface ProxyBaseOptions {
module: string, pkg: string
method: string, cls: string
defines: ModuleMethodDefine method: string
) { }
interface ProxyFunctionOptions extends ProxyBaseOptions {
async?: boolean
}
interface InvokeArgs {
package: string
class: string
method: string
params: unknown[]
}
export function initUtsProxyFunction({
pkg,
cls,
method,
async,
}: ProxyFunctionOptions) {
const invokeCallback = ({ const invokeCallback = ({
id, id,
name, name,
...@@ -74,13 +71,19 @@ function moduleGetter( ...@@ -74,13 +71,19 @@ function moduleGetter(
delete callbacks[id] delete callbacks[id]
} }
} else { } else {
console.error(`${module}.${method} ${name} is not found`) console.error(
`${pkg}${cls ? '.' + cls : ''}.${method} ${name} is not found`
)
} }
} }
return (...args: unknown[]) => { return (...args: unknown[]) => {
if (!proxy) {
proxy = uni.requireNativePlugin('ProxyModule') as any
}
const params = args.map((arg) => normalizeArg(arg)) const params = args.map((arg) => normalizeArg(arg))
const invokeArgs = { module, method, params } const invokeArgs: InvokeArgs = { package: pkg, class: cls, method, params }
if (defines.async) { if (async) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
proxy.invokeAsync(invokeArgs, (res: ProxyInvokeResponse) => { proxy.invokeAsync(invokeArgs, (res: ProxyInvokeResponse) => {
if (isProxyInvokeCallbackResponse(res)) { if (isProxyInvokeCallbackResponse(res)) {
...@@ -99,24 +102,39 @@ function moduleGetter( ...@@ -99,24 +102,39 @@ function moduleGetter(
} }
} }
export function initModule( interface ProxyClassOptions {
name: string, pkg: string
defines: Record<string, ModuleMethodDefine>, cls: string
proxyModuleName = 'ProxyModule' methods: {
) { [name: string]: {
let proxy: any async?: boolean
const moduleProxy: Record<string, Function> = {} }
for (const method in defines) { }
Object.defineProperty(moduleProxy, method, { }
enumerable: true,
configurable: true, export function initUtsProxyClass({
get: () => { pkg,
if (!proxy) { cls,
proxy = uni.requireNativePlugin(proxyModuleName) methods,
} }: ProxyClassOptions): any {
return moduleGetter(proxy, name, method, defines[method]) return class ProxyClass {
}, constructor() {
}) const target: Record<string, Function> = {}
return new Proxy(this, {
get(_, method) {
if (!target[method as string]) {
if (hasOwn(methods, method)) {
target[method] = initUtsProxyFunction({
pkg,
cls,
method,
async: methods[method].async,
})
}
return target[method as string]
}
},
})
}
} }
return moduleProxy
} }
...@@ -19,6 +19,22 @@ function resolveWithSymlinks(id: string, basedir: string): string { ...@@ -19,6 +19,22 @@ function resolveWithSymlinks(id: string, basedir: string): string {
extensions, extensions,
// necessary to work with pnpm // necessary to work with pnpm
preserveSymlinks: true, preserveSymlinks: true,
pathFilter(pkg, filepath, relativePath) {
if (pkg.dcloudext && (pkg.dcloudext as any).type === 'native-uts') {
if (
process.env.UNI_APP_PLATFORM === 'app-android' ||
process.env.UNI_APP_PLATFORM === 'app-ios'
) {
const file = process.env.UNI_APP_PLATFORM + '/index.uts'
if (
fs.existsSync(path.join(filepath.replace(relativePath, ''), file))
) {
return file
}
}
}
return relativePath
},
}) })
} }
......
[
{
"input": {
"module.ts": "lib/module.js"
},
"treeshake": false,
"compilerOptions": {
"module": "ESNext"
}
}
]
const moduleName = '__MODULE_NAME__';
const moduleDefine = '__MODULE_DEFINE__';
var module = initModule(moduleName, moduleDefine);
let callbackId = 1;
const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
const callbacks = {};
function normalizeArg(arg) {
if (typeof arg === 'function') {
const id = callbackId++;
callbacks[id] = arg;
return id;
}
else if (isPlainObject(arg)) {
Object.keys(arg).forEach((name) => {
;
arg[name] = normalizeArg(arg[name]);
});
}
return arg;
}
function isProxyInvokeCallbackResponse(res) {
return !!res.name;
}
function moduleGetter(proxy, module, method, defines) {
const invokeCallback = ({ id, name, params, keepAlive, }) => {
const callback = callbacks[id];
if (callback) {
callback(...params);
if (!keepAlive) {
delete callbacks[id];
}
}
else {
console.error(`${module}.${method} ${name} is not found`);
}
};
return (...args) => {
const params = args.map((arg) => normalizeArg(arg));
const invokeArgs = { module, method, params, async: !!defines.async };
if (defines.async) {
return new Promise((resolve, reject) => {
proxy.invoke(invokeArgs, (res) => {
if (isProxyInvokeCallbackResponse(res)) {
invokeCallback(res);
}
else {
if (res.errMsg) {
reject(res.errMsg);
}
else {
resolve(res.params);
}
}
});
});
}
return proxy.invoke(invokeArgs, invokeCallback);
};
}
function initModule(name, defines, proxyModuleName = 'ProxyModule') {
let proxy;
const moduleProxy = {};
for (const method in moduleDefine) {
Object.defineProperty(moduleProxy, method, {
enumerable: true,
configurable: true,
get: () => {
if (!proxy) {
proxy = uni.requireNativePlugin(proxyModuleName);
}
return moduleGetter(proxy, name, method, defines[method]);
},
});
}
return moduleProxy;
}
export { module as default, initModule, normalizeArg };
import type { Plugin } from 'vite' import type { Plugin } from 'vite'
import fs from 'fs'
import path from 'path' import path from 'path'
import { normalizePath, parseVueRequest } from '@dcloudio/uni-cli-shared' import { camelize } from '@vue/shared'
import { import {
ExportDefaultDeclaration, normalizePath,
parseVueRequest,
requireResolve,
} from '@dcloudio/uni-cli-shared'
import {
ClassDeclaration,
FunctionDeclaration,
Module, Module,
TsFunctionType, TsFunctionType,
TsInterfaceDeclaration,
TsType, TsType,
TsTypeAliasDeclaration,
TsTypeAnnotation, TsTypeAnnotation,
} from '../../types/types' } from '../../types/types'
// 需要区分 android,iOS
export function uniUtsV1Plugin(): Plugin { export function uniUtsV1Plugin(): Plugin {
let moduleCode: string // 目前仅支持 app-android
process.env.UNI_APP_PLATFORM = 'app-android'
return { return {
name: 'uni:uts-v1', name: 'uni:uts-v1',
apply: 'build', apply: 'build',
enforce: 'pre', enforce: 'pre',
resolveId(id, importer) {
if (isUtsModuleRoot(id)) {
return requireResolve(
id,
(importer && path.dirname(importer)) || process.env.UNI_INPUT_DIR
)
}
},
async transform(code, id, opts) { async transform(code, id, opts) {
if (opts && opts.ssr) { if (opts && opts.ssr) {
return return
...@@ -24,23 +40,19 @@ export function uniUtsV1Plugin(): Plugin { ...@@ -24,23 +40,19 @@ export function uniUtsV1Plugin(): Plugin {
if (path.extname(filename) !== '.uts') { if (path.extname(filename) !== '.uts') {
return return
} }
const moduleName = parseModuleId(filename) const pkg = parsePackage(filename)
if (!moduleName) { if (!pkg) {
return return
} }
// 懒加载 uts 编译器 // 懒加载 uts 编译器
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
const { parse } = require('@dcloudio/uts') const { parse } = require('@dcloudio/uts')
const ast = await parse(code) const ast = await parse(code)
if (!moduleCode) { code = `
moduleCode = fs.readFileSync( import { initUtsProxyClass, initUtsProxyFunction } from '@dcloudio/uni-app'
path.resolve(__dirname, '../../lib/module.js'), const pkg = '${pkg}'
'utf8' ${genProxyCode(ast)}
) `
}
code = moduleCode
.replace(`__MODULE_NAME__`, moduleName)
.replace(`'__MODULE_DEFINE__'`, JSON.stringify(parseModuleDefines(ast)))
// TODO compile uts // TODO compile uts
return code return code
...@@ -48,59 +60,147 @@ export function uniUtsV1Plugin(): Plugin { ...@@ -48,59 +60,147 @@ export function uniUtsV1Plugin(): Plugin {
} }
} }
function parseModuleId(filepath: string) { // 仅限 uni_modules/test-plugin 格式
function isUtsModuleRoot(id: string) {
const parts = normalizePath(id).split('/')
if (parts[parts.length - 2] === 'uni_modules') {
return true
}
return false
}
function parsePackage(filepath: string) {
const parts = normalizePath(filepath).split('/') const parts = normalizePath(filepath).split('/')
const index = parts.findIndex((part) => part === 'uni_modules') const index = parts.findIndex((part) => part === 'uni_modules')
if (index > -1) { if (index > -1) {
return parts[index + 1] return camelize(parts[index + 1])
} }
return '' return ''
} }
function parseModuleDefines(ast: Module) { function genProxyFunctionCode(
const module: Record<string, { async: boolean }> = {} method: string,
const defaultDecl = ast.body.find( async: boolean,
(item) => item.type === 'ExportDefaultDeclaration' isDefault: boolean = false
) as ExportDefaultDeclaration ) {
if (!defaultDecl || defaultDecl.decl.type !== 'TsInterfaceDeclaration') { if (isDefault) {
return 'only support `export default interface Module {}`' return `export default initUtsProxyFunction({ pkg, cls: '', method: '${method}', async: ${async} })`
} }
const body = defaultDecl.decl.body.body return `export const ${method} = initUtsProxyFunction({ pkg, cls: '', method: '${method}', async: ${async} })`
body.forEach((item) => { }
if (item.type === 'TsPropertySignature') {
const { key, typeAnnotation } = item function genProxyClassCode(
if (key.type !== 'Identifier') { cls: string,
return methods: Record<string, any>,
} isDefault: boolean = false
if (!typeAnnotation) { ) {
return if (isDefault) {
} return `export default initUtsProxyClass({ pkg, cls: '${cls}', methods: ${JSON.stringify(
const functionType = typeAnnotation.typeAnnotation methods
if (!isFunctionType(functionType)) { )} })`
return }
return `export const ${cls} = initUtsProxyClass({ pkg, cls: '${cls}', methods: ${JSON.stringify(
methods
)} })`
}
function genTsTypeAliasDeclarationCode(decl: TsTypeAliasDeclaration) {
if (isFunctionType(decl.typeAnnotation)) {
return genProxyFunctionCode(
decl.id.value,
isReturnPromise(decl.typeAnnotation.typeAnnotation)
)
}
}
function genTsInterfaceDeclarationCode(
decl: TsInterfaceDeclaration,
isDefault: boolean = false
) {
const cls = decl.id.value
const methods: Record<string, { async?: boolean }> = {}
decl.body.body.forEach((item) => {
if (item.type === 'TsMethodSignature') {
if (item.key.type === 'Identifier') {
methods[item.key.value] = {
async: isReturnPromise(item.typeAnn),
}
} }
const methodName = key.value }
module[methodName] = { })
async: isReturnPromise(functionType.typeAnnotation), return genProxyClassCode(cls, methods, isDefault)
}
function genFunctionDeclarationCode(
decl: FunctionDeclaration,
isDefault: boolean = false
) {
return genProxyFunctionCode(
decl.identifier.value,
decl.async || isReturnPromise(decl.returnType),
isDefault
)
}
function genClassDeclarationCode(
decl: ClassDeclaration,
isDefault: boolean = false
) {
const cls = decl.identifier.value
const methods: Record<string, { async?: boolean }> = {}
decl.body.forEach((item) => {
if (item.type === 'ClassMethod') {
if (item.key.type === 'Identifier') {
methods[item.key.value] = {
async:
item.function.async || isReturnPromise(item.function.returnType),
}
} }
} else if (item.type === 'TsMethodSignature') { }
if (item.key.type !== 'Identifier') { })
return return genProxyClassCode(cls, methods, isDefault)
}
function genProxyCode({ body }: Module) {
const codes: string[] = []
body.forEach((item) => {
let code: string | undefined
if (item.type === 'ExportDeclaration') {
const decl = item.declaration
switch (decl.type) {
case 'FunctionDeclaration':
code = genFunctionDeclarationCode(decl, false)
break
case 'ClassDeclaration':
code = genClassDeclarationCode(decl, false)
break
case 'TsTypeAliasDeclaration':
code = genTsTypeAliasDeclarationCode(decl)
break
case 'TsInterfaceDeclaration':
code = genTsInterfaceDeclarationCode(decl, false)
break
} }
const methodName = item.key.value } else if (item.type === 'ExportDefaultDeclaration') {
module[methodName] = { if (item.decl.type === 'TsInterfaceDeclaration') {
async: item.typeAnn ? isReturnPromise(item.typeAnn) : false, code = genTsInterfaceDeclarationCode(item.decl, true)
} }
} }
if (code) {
codes.push(code)
}
}) })
return module return codes.join(`\n`)
} }
function isFunctionType(type: TsType): type is TsFunctionType { function isFunctionType(type: TsType): type is TsFunctionType {
return type.type === 'TsFunctionType' return type.type === 'TsFunctionType'
} }
function isReturnPromise({ typeAnnotation }: TsTypeAnnotation) { function isReturnPromise(anno?: TsTypeAnnotation) {
if (!anno) {
return false
}
const { typeAnnotation } = anno
return ( return (
typeAnnotation.type === 'TsTypeReference' && typeAnnotation.type === 'TsTypeReference' &&
typeAnnotation.typeName.type === 'Identifier' && typeAnnotation.typeName.type === 'Identifier' &&
......
import { resolve } from 'path' import { resolve } from 'path'
import type { UtsOptions, UtsParseOptions, UtsResult } from './types' import type {
UtsBundleOptions,
UtsOptions,
UtsParseOptions,
UtsResult,
} from './types'
import { normalizePath } from './utils' import { normalizePath } from './utils'
const bindingsOverride = process.env['UTS_BINARY_PATH'] const bindingsOverride = process.env['UTS_BINARY_PATH']
...@@ -65,6 +70,10 @@ export function toSwift(options: UtsOptions): Promise<UtsResult> { ...@@ -65,6 +70,10 @@ export function toSwift(options: UtsOptions): Promise<UtsResult> {
.then((res: string) => JSON.parse(res)) .then((res: string) => JSON.parse(res))
} }
export function bundle(options: UtsBundleOptions): Promise<UtsResult> {
return bindings.bundle(toBuffer(options)).then((res: string) => res)
}
function toBuffer(t: any): Buffer { function toBuffer(t: any): Buffer {
return Buffer.from(JSON.stringify(t)) return Buffer.from(JSON.stringify(t))
} }
...@@ -5,11 +5,12 @@ import glob from 'fast-glob' ...@@ -5,11 +5,12 @@ import glob from 'fast-glob'
import chokidar from 'chokidar' import chokidar from 'chokidar'
import { toKotlin, toSwift } from './api' import { toKotlin, toSwift } from './api'
import type { import {
UtsInputOptions, UtsInputOptions,
UtsOptions, UtsOptions,
UtsOutputOptions, UtsOutputOptions,
UtsResult, UtsResult,
UtsTarget,
} from './types' } from './types'
import { import {
printDone, printDone,
...@@ -19,10 +20,7 @@ import { ...@@ -19,10 +20,7 @@ import {
timeEnd, timeEnd,
} from './utils' } from './utils'
export enum UtsTarget { export { UtsTarget } from './types'
KOTLIN = 'kotlin',
SWIFT = 'swift',
}
export type UtsMode = 'dev' | 'build' export type UtsMode = 'dev' | 'build'
...@@ -299,7 +297,7 @@ function buildFile( ...@@ -299,7 +297,7 @@ function buildFile(
}) })
} }
export { parse } from './api' export { parse, bundle } from './api'
export function runDev(target: UtsTarget, opts: ToOptions) { export function runDev(target: UtsTarget, opts: ToOptions) {
opts = parseOptions('dev', target, opts) opts = parseOptions('dev', target, opts)
......
export enum UtsTarget {
KOTLIN = 'kotlin',
SWIFT = 'swift',
}
export interface UtsParserConfig { export interface UtsParserConfig {
/** /**
* Defaults to `false` * Defaults to `false`
...@@ -33,3 +37,11 @@ export interface UtsResult { ...@@ -33,3 +37,11 @@ export interface UtsResult {
time?: number time?: number
error?: Error error?: Error
} }
export interface UtsBundleOptions {
target: UtsTarget
entry: Record<string, string>
output: {
path: string
}
}
...@@ -3,3 +3,15 @@ ...@@ -3,3 +3,15 @@
console.log('publishHandler', JSON.stringify(args)) console.log('publishHandler', JSON.stringify(args))
}, },
} }
;(global as any).uni = {
requireNativePlugin(name: string) {
return {
invokeSync(args: unknown, callback: unknown) {
console.log(`invoke`, JSON.stringify(args))
},
invokeAsync(args: unknown, callback: unknown) {
console.log(`invokeAsync`, JSON.stringify(args))
},
}
},
}
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const { parse, runBuild, UtsTarget } = require('../packages/uts/dist') const { parse, runBuild, bundle, UtsTarget } = require('../packages/uts/dist')
const projectDir = path.resolve(__dirname, '../packages/playground/uts') const projectDir = path.resolve(__dirname, '../packages/playground/uts')
const start = Date.now() const start = Date.now()
parse( parse(
fs.readFileSync( fs.readFileSync(
path.resolve(projectDir, 'uni_modules/test-uniplugin/interface.uts'), path.resolve(
projectDir,
'uni_modules/test-uniplugin/app-android/index.uts'
),
'utf8' 'utf8'
) )
).then((res) => { ).then((res) => {
...@@ -14,6 +17,23 @@ parse( ...@@ -14,6 +17,23 @@ parse(
console.log(JSON.stringify(res)) console.log(JSON.stringify(res))
}) })
// bundle({
// entry: {
// 'test-uniplugin': path.resolve(
// projectDir,
// 'uni_modules/test-uniplugin/app-android/index.uts'
// ),
// },
// output: {
// path: path.resolve(
// projectDir,
// 'unpackage/dist/app-plus/uni_modules/test-uniplugin/bundle'
// ),
// },
// }).then((res) => {
// console.log(res)
// })
// uts // uts
runBuild(UtsTarget.KOTLIN, { runBuild(UtsTarget.KOTLIN, {
silent: false, silent: false,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册