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

wip(uts): compiler

上级 089a3bc6
......@@ -5,6 +5,7 @@
"packages/*"
],
"scripts": {
"all": "npm run lint && npm run format && npm run build && npm run test",
"clean": "rm -rf node_modules **/*/node_modules && pnpm install",
"build": "node scripts/build.js",
"build:h5": "node scripts/build.js uni-app uni-cli-shared uni-h5 uni-i18n uni-stat uni-shared uni-h5-vite vite-plugin-uni",
......
......@@ -8,7 +8,6 @@ declare namespace NodeJS {
UNI_NODE_ENV: 'production' | 'development' | 'test'
UNI_PLATFORM: UniApp.PLATFORM
UNI_SUB_PLATFORM: 'quickapp-webview-huawei' | 'quickapp-webview-union'
UNI_APP_PLATFORM: 'android' | 'ios'
UNI_UTS_PLATFORM: 'app-android' | 'app-ios' | 'web' | UniApp.PLATFORM
UNI_INPUT_DIR: string
......
......@@ -120,12 +120,44 @@ export function resolveComponentsLibPath() {
return componentsLibPath
}
/**
* 解析 app 平台的 uts 插件,任意平台(android|ios)存在即可
* @param id
* @param importer
* @returns
*/
export function resolveUtsAppModule(id: string, importer: string) {
id = path.resolve(importer, id)
if (id.includes('utssdk') || id.includes('uni_modules')) {
const parts = normalizePath(id).split('/')
const parentDir = parts[parts.length - 2]
if (parentDir === 'uni_modules' || parentDir === 'utssdk') {
if (fs.existsSync(path.resolve(id, 'index.uts'))) {
return id
}
const resolvePlatformDir = (p: typeof process.env.UNI_UTS_PLATFORM) => {
return path.resolve(id, parentDir === 'uni_modules' ? 'utssdk' : '', p)
}
const extname = ['.uts']
if (resolveUtsFile(resolvePlatformDir('app-android'), extname)) {
return id
}
if (resolveUtsFile(resolvePlatformDir('app-ios'), extname)) {
return id
}
}
}
}
// 仅限 root/uni_modules/test-plugin | root/utssdk/test-plugin 格式
export function resolveUtsModule(
id: string,
importer: string,
platform: typeof process.env.UNI_UTS_PLATFORM
) {
if (process.env.UNI_PLATFORM === 'app') {
return
}
id = path.resolve(importer, id)
if (id.includes('utssdk') || id.includes('uni_modules')) {
const parts = normalizePath(id).split('/')
......@@ -134,10 +166,7 @@ export function resolveUtsModule(
const resolvePlatformDir = (p: typeof process.env.UNI_UTS_PLATFORM) => {
return path.resolve(id, parentDir === 'uni_modules' ? 'utssdk' : '', p)
}
// 未指定具体的平台
if (platform === 'app') {
platform = 'app-android'
}
let index = resolveUtsFile(resolvePlatformDir(platform))
if (index) {
return index
......@@ -146,12 +175,6 @@ export function resolveUtsModule(
if (fs.existsSync(index)) {
return index
}
// 如果是 android 或 ios,本平台没有,则查找一下另一个平台
if (platform === 'app-android') {
return resolveUtsFile(resolvePlatformDir('app-ios'))
} else if (platform === 'app-ios') {
return resolveUtsFile(resolvePlatformDir('app-android'))
}
}
}
}
......
......@@ -3992,25 +3992,10 @@ function _getPx(val) {
}
return Number(val) || 0;
}
function useMovableViewState(props2, trigger, rootRef) {
function useMovableViewLayout(rootRef, _scale, _adjustScale) {
const movableAreaWidth = vue.inject("movableAreaWidth", vue.ref(0));
const movableAreaHeight = vue.inject("movableAreaHeight", vue.ref(0));
const _isMounted = vue.inject("_isMounted", vue.ref(false));
const movableAreaRootRef = vue.inject("movableAreaRootRef");
vue.inject("addMovableViewContext", () => {
});
vue.inject("removeMovableViewContext", () => {
});
const xSync = vue.ref(_getPx(props2.x));
const ySync = vue.ref(_getPx(props2.y));
const scaleValueSync = vue.ref(Number(props2.scaleValue) || 1);
const width = vue.ref(0);
const height = vue.ref(0);
const minX = vue.ref(0);
const minY = vue.ref(0);
const maxX = vue.ref(0);
const maxY = vue.ref(0);
let _SFA = null;
const _offset = {
x: 0,
y: 0
......@@ -4019,29 +4004,57 @@ function useMovableViewState(props2, trigger, rootRef) {
x: 0,
y: 0
};
let _scale = 1;
let _translateX = 0;
let _translateY = 0;
const width = vue.ref(0);
const height = vue.ref(0);
const minX = vue.ref(0);
const minY = vue.ref(0);
const maxX = vue.ref(0);
const maxY = vue.ref(0);
function _updateBoundary() {
let x = 0 - _offset.x + _scaleOffset.x;
let _width = movableAreaWidth.value - width.value - _offset.x - _scaleOffset.x;
minX.value = Math.min(x, _width);
maxX.value = Math.max(x, _width);
let y = 0 - _offset.y + _scaleOffset.y;
let _height = movableAreaHeight.value - height.value - _offset.y - _scaleOffset.y;
minY.value = Math.min(y, _height);
maxY.value = Math.max(y, _height);
}
function _updateOffset() {
_offset.x = p(rootRef.value, movableAreaRootRef.value);
_offset.y = f(rootRef.value, movableAreaRootRef.value);
}
function _updateWH(scale) {
scale = scale || _scale.value;
scale = _adjustScale(scale);
let rect = rootRef.value.getBoundingClientRect();
height.value = rect.height / _scale.value;
width.value = rect.width / _scale.value;
let _height = height.value * scale;
let _width = width.value * scale;
_scaleOffset.x = (_width - width.value) / 2;
_scaleOffset.y = (_height - height.value) / 2;
}
return {
_updateBoundary,
_updateOffset,
_updateWH,
_scaleOffset,
minX,
minY,
maxX,
maxY
};
}
function useMovableViewTransform(rootRef, props2, _scaleOffset, _scale, maxX, maxY, minX, minY, _translateX, _translateY, _SFA, _FA, _adjustScale, trigger) {
const dampingNumber = vue.computed(() => {
let val = Number(props2.damping);
return isNaN(val) ? 20 : val;
});
const frictionNumber = vue.computed(() => {
let val = Number(props2.friction);
return isNaN(val) || val <= 0 ? 2 : val;
});
const scaleMinNumber = vue.computed(() => {
let val = Number(props2.scaleMin);
return isNaN(val) ? 0.5 : val;
});
const scaleMaxNumber = vue.computed(() => {
let val = Number(props2.scaleMax);
return isNaN(val) ? 10 : val;
});
const xMove = vue.computed(() => props2.direction === "all" || props2.direction === "horizontal");
const yMove = vue.computed(() => props2.direction === "all" || props2.direction === "vertical");
const _STD = new STD(1, 9 * Math.pow(dampingNumber.value, 2) / 40, dampingNumber.value);
new Friction(1, frictionNumber.value);
const xSync = vue.ref(_getPx(props2.x));
const ySync = vue.ref(_getPx(props2.y));
vue.watch(() => props2.x, (val) => {
xSync.value = _getPx(val);
});
......@@ -4054,78 +4067,7 @@ function useMovableViewState(props2, trigger, rootRef) {
vue.watch(ySync, (val) => {
_setY(val);
});
vue.watch(() => props2.disabled, () => {
__handleTouchStart();
});
vue.watch(() => props2.scaleValue, (val) => {
scaleValueSync.value = Number(val) || 0;
});
vue.watch(scaleValueSync, (val) => {
_setScaleValue(val);
});
vue.watch(scaleMinNumber, () => {
_setScaleMinOrMax();
});
vue.watch(scaleMaxNumber, () => {
_setScaleMinOrMax();
});
function FAandSFACancel() {
if (_SFA) {
_SFA.cancel();
}
}
function _setX(val) {
if (xMove.value) {
if (val + _scaleOffset.x === _translateX) {
return _translateX;
} else {
if (_SFA) {
_SFA.cancel();
}
_animationTo(val + _scaleOffset.x, ySync.value + _scaleOffset.y, _scale);
}
}
return val;
}
function _setY(val) {
if (yMove.value) {
if (val + _scaleOffset.y === _translateY) {
return _translateY;
} else {
if (_SFA) {
_SFA.cancel();
}
_animationTo(xSync.value + _scaleOffset.x, val + _scaleOffset.y, _scale);
}
}
return val;
}
function _setScaleMinOrMax() {
if (!props2.scale) {
return false;
}
_updateScale(_scale, true);
}
function _setScaleValue(scale) {
if (!props2.scale) {
return false;
}
scale = _adjustScale(scale);
_updateScale(scale, true);
return scale;
}
function __handleTouchStart() {
{
if (!props2.disabled) {
FAandSFACancel();
if (xMove.value)
;
if (yMove.value)
;
rootRef.value.style.willChange = "transform";
}
}
}
const _STD = new STD(1, 9 * Math.pow(dampingNumber.value, 2) / 40, dampingNumber.value);
function _getLimitXY(x, y) {
let outOfBounds = false;
if (x > maxX.value) {
......@@ -4152,63 +4094,24 @@ function useMovableViewState(props2, trigger, rootRef) {
outOfBounds
};
}
function _updateOffset() {
_offset.x = p(rootRef.value, movableAreaRootRef.value);
_offset.y = f(rootRef.value, movableAreaRootRef.value);
}
function _updateWH(scale) {
scale = scale || _scale;
scale = _adjustScale(scale);
let rect = rootRef.value.getBoundingClientRect();
height.value = rect.height / _scale;
width.value = rect.width / _scale;
let _height = height.value * scale;
let _width = width.value * scale;
_scaleOffset.x = (_width - width.value) / 2;
_scaleOffset.y = (_height - height.value) / 2;
}
function _updateBoundary() {
let x = 0 - _offset.x + _scaleOffset.x;
let _width = movableAreaWidth.value - width.value - _offset.x - _scaleOffset.x;
minX.value = Math.min(x, _width);
maxX.value = Math.max(x, _width);
let y = 0 - _offset.y + _scaleOffset.y;
let _height = movableAreaHeight.value - height.value - _offset.y - _scaleOffset.y;
minY.value = Math.min(y, _height);
maxY.value = Math.max(y, _height);
}
function _updateScale(scale, animat) {
if (props2.scale) {
scale = _adjustScale(scale);
_updateWH(scale);
_updateBoundary();
const limitXY = _getLimitXY(_translateX, _translateY);
const x = limitXY.x;
const y = limitXY.y;
if (animat) {
_animationTo(x, y, scale, "", true, true);
} else {
_requestAnimationFrame(function() {
_setTransform(x, y, scale, "", true, true);
});
}
function FAandSFACancel() {
if (_FA) {
_FA.cancel();
}
if (_SFA) {
_SFA.cancel();
}
}
function _adjustScale(scale) {
scale = Math.max(0.5, scaleMinNumber.value, scale);
scale = Math.min(10, scaleMaxNumber.value, scale);
return scale;
}
function _animationTo(x, y, scale, source, r, o) {
FAandSFACancel();
if (!xMove.value) {
x = _translateX;
x = _translateX.value;
}
if (!yMove.value) {
y = _translateY;
y = _translateY.value;
}
if (!props2.scale) {
scale = _scale;
scale = _scale.value;
}
let limitXY = _getLimitXY(x, y);
x = limitXY.x;
......@@ -4220,9 +4123,9 @@ function useMovableViewState(props2, trigger, rootRef) {
_STD._springX._solution = null;
_STD._springY._solution = null;
_STD._springScale._solution = null;
_STD._springX._endPosition = _translateX;
_STD._springY._endPosition = _translateY;
_STD._springScale._endPosition = _scale;
_STD._springX._endPosition = _translateX.value;
_STD._springY._endPosition = _translateY.value;
_STD._springScale._endPosition = _scale.value;
_STD.setEnd(x, y, scale, 1);
_SFA = g(_STD, function() {
let data = _STD.x();
......@@ -4236,15 +4139,15 @@ function useMovableViewState(props2, trigger, rootRef) {
}
function _setTransform(x, y, scale, source = "", r, o) {
if (!(x !== null && x.toString() !== "NaN" && typeof x === "number")) {
x = _translateX || 0;
x = _translateX.value || 0;
}
if (!(y !== null && y.toString() !== "NaN" && typeof y === "number")) {
y = _translateY || 0;
y = _translateY.value || 0;
}
x = Number(x.toFixed(1));
y = Number(y.toFixed(1));
scale = Number(scale.toFixed(1));
if (!(_translateX === x && _translateY === y)) {
if (!(_translateX.value === x && _translateY.value === y)) {
if (!r) {
trigger("change", {}, {
x: v(x, _scaleOffset.x),
......@@ -4254,11 +4157,11 @@ function useMovableViewState(props2, trigger, rootRef) {
}
}
if (!props2.scale) {
scale = _scale;
scale = _scale.value;
}
scale = _adjustScale(scale);
scale = +scale.toFixed(3);
if (o && scale !== _scale) {
if (o && scale !== _scale.value) {
trigger("scale", {}, {
x,
y,
......@@ -4268,9 +4171,244 @@ function useMovableViewState(props2, trigger, rootRef) {
let transform = "translateX(" + x + "px) translateY(" + y + "px) translateZ(0px) scale(" + scale + ")";
rootRef.value.style.transform = transform;
rootRef.value.style.webkitTransform = transform;
_translateX = x;
_translateY = y;
_scale = scale;
_translateX.value = x;
_translateY.value = y;
_scale.value = scale;
}
function _revise(source) {
let limitXY = _getLimitXY(_translateX.value, _translateY.value);
let x = limitXY.x;
let y = limitXY.y;
let outOfBounds = limitXY.outOfBounds;
if (outOfBounds) {
_animationTo(x, y, _scale.value, source);
}
return outOfBounds;
}
function _setX(val) {
if (xMove.value) {
if (val + _scaleOffset.x === _translateX.value) {
return _translateX;
} else {
if (_SFA) {
_SFA.cancel();
}
_animationTo(val + _scaleOffset.x, ySync.value + _scaleOffset.y, _scale.value);
}
}
return val;
}
function _setY(val) {
if (yMove.value) {
if (val + _scaleOffset.y === _translateY.value) {
return _translateY;
} else {
if (_SFA) {
_SFA.cancel();
}
_animationTo(xSync.value + _scaleOffset.x, val + _scaleOffset.y, _scale.value);
}
}
return val;
}
return {
FAandSFACancel,
_getLimitXY,
_animationTo,
_setTransform,
_revise,
dampingNumber,
xMove,
yMove,
xSync,
ySync,
_STD
};
}
function useMovableViewInit(props2, rootRef, trigger, _scale, _oldScale, _isScaling, _translateX, _translateY, _SFA, _FA) {
const scaleMinNumber = vue.computed(() => {
let val = Number(props2.scaleMin);
return isNaN(val) ? 0.5 : val;
});
const scaleMaxNumber = vue.computed(() => {
let val = Number(props2.scaleMax);
return isNaN(val) ? 10 : val;
});
const scaleValueSync = vue.ref(Number(props2.scaleValue) || 1);
vue.watch(scaleValueSync, (val) => {
_setScaleValue(val);
});
vue.watch(scaleMinNumber, () => {
_setScaleMinOrMax();
});
vue.watch(scaleMaxNumber, () => {
_setScaleMinOrMax();
});
vue.watch(() => props2.scaleValue, (val) => {
scaleValueSync.value = Number(val) || 0;
});
const {
_updateBoundary,
_updateOffset,
_updateWH,
_scaleOffset,
minX,
minY,
maxX,
maxY
} = useMovableViewLayout(rootRef, _scale, _adjustScale);
const {
FAandSFACancel,
_getLimitXY,
_animationTo,
_setTransform,
_revise,
dampingNumber,
xMove,
yMove,
xSync,
ySync,
_STD
} = useMovableViewTransform(rootRef, props2, _scaleOffset, _scale, maxX, maxY, minX, minY, _translateX, _translateY, _SFA, _FA, _adjustScale, trigger);
function _updateScale(scale, animat) {
if (props2.scale) {
scale = _adjustScale(scale);
_updateWH(scale);
_updateBoundary();
const limitXY = _getLimitXY(_translateX.value, _translateY.value);
const x = limitXY.x;
const y = limitXY.y;
if (animat) {
_animationTo(x, y, scale, "", true, true);
} else {
_requestAnimationFrame(function() {
_setTransform(x, y, scale, "", true, true);
});
}
}
}
function _beginScale() {
_isScaling.value = true;
}
function _updateOldScale(scale) {
_oldScale.value = scale;
}
function _adjustScale(scale) {
scale = Math.max(0.5, scaleMinNumber.value, scale);
scale = Math.min(10, scaleMaxNumber.value, scale);
return scale;
}
function _setScaleMinOrMax() {
if (!props2.scale) {
return false;
}
_updateScale(_scale.value, true);
_updateOldScale(_scale.value);
}
function _setScaleValue(scale) {
if (!props2.scale) {
return false;
}
scale = _adjustScale(scale);
_updateScale(scale, true);
_updateOldScale(scale);
return scale;
}
function _endScale() {
_isScaling.value = false;
_updateOldScale(_scale.value);
}
function _setScale(scale) {
if (scale) {
scale = _oldScale.value * scale;
_beginScale();
_updateScale(scale);
}
}
return {
_updateOldScale,
_endScale,
_setScale,
scaleValueSync,
_updateBoundary,
_updateOffset,
_updateWH,
_scaleOffset,
minX,
minY,
maxX,
maxY,
FAandSFACancel,
_getLimitXY,
_animationTo,
_setTransform,
_revise,
dampingNumber,
xMove,
yMove,
xSync,
ySync,
_STD
};
}
function useMovableViewState(props2, trigger, rootRef) {
const _isMounted = vue.inject("_isMounted", vue.ref(false));
vue.inject("addMovableViewContext", () => {
});
vue.inject("removeMovableViewContext", () => {
});
let _scale = vue.ref(1);
let _oldScale = vue.ref(1);
let _isScaling = vue.ref(false);
let _translateX = vue.ref(0);
let _translateY = vue.ref(0);
let _SFA = null;
let _FA = null;
const frictionNumber = vue.computed(() => {
let val = Number(props2.friction);
return isNaN(val) || val <= 0 ? 2 : val;
});
new Friction(1, frictionNumber.value);
vue.watch(() => props2.disabled, () => {
__handleTouchStart();
});
const {
_updateOldScale,
_endScale,
_setScale,
scaleValueSync,
_updateBoundary,
_updateOffset,
_updateWH,
_scaleOffset,
minX,
minY,
maxX,
maxY,
FAandSFACancel,
_getLimitXY,
_setTransform,
_revise,
dampingNumber,
xMove,
yMove,
xSync,
ySync,
_STD
} = useMovableViewInit(props2, rootRef, trigger, _scale, _oldScale, _isScaling, _translateX, _translateY, _SFA, _FA);
function __handleTouchStart() {
if (!_isScaling.value) {
if (!props2.disabled) {
FAandSFACancel();
if (xMove.value) {
_translateX.value;
}
if (yMove.value) {
_translateY.value;
}
rootRef.value.style.willChange = "transform";
}
}
}
function setParent() {
if (!_isMounted.value) {
......@@ -4281,12 +4419,11 @@ function useMovableViewState(props2, trigger, rootRef) {
_updateOffset();
_updateWH(scale);
_updateBoundary();
_translateX = xSync.value + _scaleOffset.x;
_translateY = ySync.value + _scaleOffset.y;
let limitXY = _getLimitXY(_translateX, _translateY);
let limitXY = _getLimitXY(xSync.value + _scaleOffset.x, ySync.value + _scaleOffset.y);
let x = limitXY.x;
let y = limitXY.y;
_setTransform(x, y, scale, "", true);
_updateOldScale(scale);
}
return {
setParent
......
此差异已折叠。
import type { Plugin } from 'vite'
import path from 'path'
import { isInHBuilderX, parseVueRequest } from '@dcloudio/uni-cli-shared'
import {
BindingIdentifier,
ClassDeclaration,
ClassExpression,
Expression,
FunctionDeclaration,
FunctionExpression,
Identifier,
Module,
Param,
TsTypeAnnotation,
VariableDeclaration,
} from '../../types/types'
import { getCompiler } from '../utils/compiler'
import {
createResolveTypeReferenceName,
resolvePackage,
} from '../utils/compiler/utils'
import { parseVueRequest, resolveUtsAppModule } from '@dcloudio/uni-cli-shared'
import { getCompiler, genProxyCode } from '../utils/compiler'
import { resolvePackage } from '../utils/compiler/utils'
import { resolvePlatformIndex, resolveRootIndex } from '../utils/compiler/code'
const UTSProxyRE = /\?uts-proxy$/
function isUTSProxy(id: string) {
return UTSProxyRE.test(id)
}
export function uniUtsV1Plugin(): Plugin {
let isFirst = true
return {
name: 'uni:uts-v1',
apply: 'build',
enforce: 'pre',
resolveId(id, importer) {
const module = resolveUtsAppModule(
id,
importer ? path.dirname(importer) : process.env.UNI_INPUT_DIR
)
if (module) {
// prefix the polyfill id with \0 to tell other plugins not to try to load or transform it
return '\0' + module + '?uts-proxy'
}
},
load(id) {
if (isUTSProxy(id)) {
return ''
}
},
async transform(code, id, opts) {
if (opts && opts.ssr) {
return
}
// 目前仅支持 app-android|app-ios
if (
process.env.UNI_UTS_PLATFORM !== 'app' &&
process.env.UNI_UTS_PLATFORM !== 'app-android' &&
process.env.UNI_UTS_PLATFORM !== 'app-ios'
) {
if (!isUTSProxy(id)) {
return
}
const { filename } = parseVueRequest(id)
if (path.extname(filename) !== '.uts') {
return
}
const { compile } = getCompiler(
process.env.UNI_UTS_PLATFORM === 'app-ios' ? 'swift' : 'kotlin'
)
const pkg = resolvePackage(filename)
const { filename: module } = parseVueRequest(id.replace('\0', ''))
const pkg = resolvePackage(module)
if (!pkg) {
return
}
// 懒加载 uts 编译器
// eslint-disable-next-line no-restricted-globals
const { parse } = require('@dcloudio/uts')
const ast = await parse(code, { noColor: isInHBuilderX() })
code = `
import { initUtsProxyClass, initUtsProxyFunction, initUtsPackageName, initUtsClassName } from '@dcloudio/uni-app'
const name = '${pkg.name}'
const is_uni_modules = ${pkg.is_uni_modules}
const pkg = initUtsPackageName(name, is_uni_modules)
const cls = initUtsClassName(name, is_uni_modules)
${genProxyCode(ast, createResolveTypeReferenceName(pkg.namespace, ast))}
`
// 平台不匹配,不走平台代码编译,仅生成js代码
if (
process.env.UNI_UTS_PLATFORM === 'app' ||
(process.env.UNI_UTS_PLATFORM === 'app-android' &&
id.includes('app-ios')) ||
(process.env.UNI_UTS_PLATFORM === 'app-ios' &&
id.includes('app-android'))
) {
return code
}
const res = await compile(id)
if (process.env.UNI_UTS_PLATFORM === 'app-android') {
if (!isFirst && res) {
const files = []
if (process.env.UNI_APP_CHANGED_DEX_FILES) {
try {
files.push(...JSON.parse(process.env.UNI_APP_CHANGED_DEX_FILES))
} catch (e) {}
code = await genProxyCode(module, pkg)
if (process.env.NODE_ENV !== 'development') {
// 生产模式 支持同时生成 android 和 ios 的 uts 插件
if (
process.env.UNI_UTS_PLATFORM === 'app-android' ||
process.env.UNI_UTS_PLATFORM === 'app'
) {
const filename =
resolvePlatformIndex('app-android', module, pkg) ||
resolveRootIndex(module)
if (filename) {
await getCompiler('kotlin').runProd(filename)
}
}
if (
process.env.UNI_UTS_PLATFORM === 'app-ios' ||
process.env.UNI_UTS_PLATFORM === 'app'
) {
const filename =
resolvePlatformIndex('app-ios', module, pkg) ||
resolveRootIndex(module)
if (filename) {
await getCompiler('swift').runProd(filename)
}
}
} else {
// dev 模式仅 android 支持
if (process.env.UNI_UTS_PLATFORM === 'app-android') {
const filename =
resolvePlatformIndex('app-android', module, pkg) ||
resolveRootIndex(module)
if (filename) {
const res = await getCompiler('kotlin').runDev(filename)
if (!isFirst && res) {
const files = []
if (process.env.UNI_APP_CHANGED_DEX_FILES) {
try {
files.push(
...JSON.parse(process.env.UNI_APP_CHANGED_DEX_FILES)
)
} catch (e) {}
}
files.push(res)
process.env.UNI_APP_CHANGED_DEX_FILES = JSON.stringify([
...new Set(files),
])
}
}
files.push(res)
process.env.UNI_APP_CHANGED_DEX_FILES = JSON.stringify([
...new Set(files),
])
}
}
return code
......@@ -93,253 +102,3 @@ ${genProxyCode(ast, createResolveTypeReferenceName(pkg.namespace, ast))}
},
}
}
function genProxyFunctionCode(
method: string,
async: boolean,
params: Parameter[],
isDefault: boolean = false
) {
if (isDefault) {
return `export default initUtsProxyFunction(${async}, { package: pkg, class: cls, name: '${method}', params: ${JSON.stringify(
params
)}})`
}
return `export const ${method} = initUtsProxyFunction(${async}, { package: pkg, class: cls, name: '${method}', params: ${JSON.stringify(
params
)}})`
}
function genProxyClassCode(
cls: string,
options: {
constructor: { params: Parameter[] }
methods: Record<string, any>
staticMethods: Record<string, any>
props: string[]
staticProps: string[]
},
isDefault: boolean = false
) {
if (isDefault) {
return `export default initUtsProxyClass({ package: pkg, class: '${cls}', ...${JSON.stringify(
options
)} })`
}
return `export const ${cls} = initUtsProxyClass({ package: pkg, class: '${cls}', ...${JSON.stringify(
options
)} })`
}
interface Parameter {
name: string
type: string
}
type ResolveTypeReferenceName = (name: string) => string
function resolveIdentifierType(
ident: BindingIdentifier,
resolveTypeReferenceName: ResolveTypeReferenceName
) {
if (ident.typeAnnotation) {
const { typeAnnotation } = ident.typeAnnotation
if (typeAnnotation.type === 'TsKeywordType') {
return typeAnnotation.kind
} else if (typeAnnotation.type === 'TsFunctionType') {
return 'UTSCallback'
} else if (
typeAnnotation.type === 'TsTypeReference' &&
typeAnnotation.typeName.type === 'Identifier'
) {
return resolveTypeReferenceName(typeAnnotation.typeName.value)
}
}
return ''
}
function resolveFunctionParams(
params: Param[],
resolveTypeReferenceName: ResolveTypeReferenceName
) {
const result: Parameter[] = []
params.forEach(({ pat }) => {
if (pat.type === 'Identifier') {
result.push({
name: pat.value,
type: resolveIdentifierType(
pat as BindingIdentifier,
resolveTypeReferenceName
),
})
} else {
result.push({ name: '', type: '' })
}
})
return result
}
function genFunctionDeclarationCode(
decl: FunctionDeclaration | FunctionExpression,
resolveTypeReferenceName: ResolveTypeReferenceName,
isDefault: boolean = false
) {
return genProxyFunctionCode(
decl.identifier!.value,
decl.async || isReturnPromise(decl.returnType),
resolveFunctionParams(decl.params, resolveTypeReferenceName),
isDefault
)
}
function genClassDeclarationCode(
decl: ClassDeclaration | ClassExpression,
resolveTypeReferenceName: ResolveTypeReferenceName,
isDefault: boolean = false
) {
const cls = decl.identifier!.value
const constructor: { params: Parameter[] } = { params: [] }
const methods: Record<string, { async?: boolean; params: Parameter[] }> = {}
const staticMethods: Record<
string,
{ async?: boolean; params: Parameter[] }
> = {}
const props: string[] = []
const staticProps: string[] = []
decl.body.forEach((item) => {
if (item.type === 'Constructor') {
constructor.params = resolveFunctionParams(
item.params as Param[],
resolveTypeReferenceName
)
} else if (item.type === 'ClassMethod') {
if (item.key.type === 'Identifier') {
const name = item.key.value
const value = {
async:
item.function.async || isReturnPromise(item.function.returnType),
params: resolveFunctionParams(
item.function.params,
resolveTypeReferenceName
),
}
if (item.isStatic) {
staticMethods[name] = value
} else {
methods[name] = value
}
}
} else if (item.type === 'ClassProperty') {
if (item.key.type === 'Identifier') {
if (item.isStatic) {
staticProps.push(item.key.value)
} else {
props.push(item.key.value)
}
}
}
})
return genProxyClassCode(
cls,
{ constructor, methods, staticMethods, props, staticProps },
isDefault
)
}
function genInitCode(expr: Expression) {
switch (expr.type) {
case 'BooleanLiteral':
return expr.value + ''
case 'NumericLiteral':
return expr.value + ''
case 'StringLiteral':
return expr.value
}
return ''
}
function genVariableDeclarationCode(decl: VariableDeclaration) {
// 目前仅支持 const 的 boolean,number,string
const lits = ['BooleanLiteral', 'NumericLiteral', 'StringLiteral']
if (
decl.kind === 'const' &&
!decl.declarations.find((d) => {
if (d.id.type !== 'Identifier') {
return true
}
if (!d.init) {
return true
}
const type = d.init.type
if (!lits.includes(type)) {
return true
}
return false
})
) {
return `export ${decl.kind} ${decl.declarations
.map((d) => `${(d.id as Identifier).value} = ${genInitCode(d.init!)}`)
.join(', ')}`
}
}
function genProxyCode(
{ body }: Module,
resolveTypeReferenceName: ResolveTypeReferenceName
) {
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,
resolveTypeReferenceName,
false
)
break
case 'ClassDeclaration':
code = genClassDeclarationCode(decl, resolveTypeReferenceName, false)
break
case 'VariableDeclaration':
code = genVariableDeclarationCode(decl)
break
}
} else if (item.type === 'ExportDefaultDeclaration') {
const decl = item.decl
if (decl.type === 'ClassExpression') {
if (decl.identifier) {
// export default class test{}
code = genClassDeclarationCode(decl, resolveTypeReferenceName, false)
}
} else if (decl.type === 'FunctionExpression') {
if (decl.identifier) {
code = genFunctionDeclarationCode(
decl,
resolveTypeReferenceName,
true
)
}
}
}
if (code) {
codes.push(code)
}
})
return codes.join(`\n`)
}
function isReturnPromise(anno?: TsTypeAnnotation) {
if (!anno) {
return false
}
const { typeAnnotation } = anno
return (
typeAnnotation.type === 'TsTypeReference' &&
typeAnnotation.typeName.type === 'Identifier' &&
typeAnnotation.typeName.value === 'Promise'
)
}
import fs from 'fs'
import path from 'path'
import { isInHBuilderX } from '@dcloudio/uni-cli-shared'
import {
BindingIdentifier,
ClassDeclaration,
ClassExpression,
Expression,
FunctionDeclaration,
FunctionExpression,
Identifier,
Module,
Param,
TsTypeAnnotation,
VariableDeclaration,
} from '../../../types/types'
import { createResolveTypeReferenceName } from './utils'
interface GenProxyCodeOptions {
is_uni_modules: boolean
name: string
namespace: string
}
export async function genProxyCode(
module: string,
options: GenProxyCodeOptions
) {
const { name, is_uni_modules } = options
return `
import { initUtsProxyClass, initUtsProxyFunction, initUtsPackageName, initUtsClassName } from '@dcloudio/uni-app'
const name = '${name}'
const is_uni_modules = ${is_uni_modules}
const pkg = initUtsPackageName(name, is_uni_modules)
const cls = initUtsClassName(name, is_uni_modules)
${genModuleCode(await parseModuleDecls(module, options))}
`
}
export function resolveRootIndex(module: string) {
const filename = path.resolve(module, 'index.uts')
return fs.existsSync(filename) && filename
}
export function resolvePlatformIndex(
platform: 'app-android' | 'app-ios',
module: string,
options: GenProxyCodeOptions
) {
const filename = path.resolve(
module,
options.is_uni_modules ? 'utssdk' : '',
platform,
'index.uts'
)
return fs.existsSync(filename) && filename
}
function genModuleCode(decls: ProxyDecl[]) {
const codes: string[] = []
decls.forEach((decl) => {
if (decl.type === 'Class') {
if (decl.isDefault) {
codes.push(
`export default initUtsProxyClass({ package: pkg, class: '${
decl.cls
}', ...${JSON.stringify(decl.options)} })`
)
} else {
codes.push(
`export const ${
decl.cls
} = initUtsProxyClass({ package: pkg, class: '${
decl.cls
}', ...${JSON.stringify(decl.options)} })`
)
}
} else if (decl.type === 'FunctionDeclaration') {
if (decl.isDefault) {
codes.push(
`export default initUtsProxyFunction(${
decl.async
}, { package: pkg, class: cls, name: '${
decl.method
}', params: ${JSON.stringify(decl.params)}})`
)
} else {
codes.push(
`export const ${decl.method} = initUtsProxyFunction(${
decl.async
}, { package: pkg, class: cls, name: '${
decl.method
}', params: ${JSON.stringify(decl.params)}})`
)
}
} else if (decl.type === 'VariableDeclaration') {
codes.push(
`export ${decl.kind} ${decl.declarations
.map((d) => `${(d.id as Identifier).value} = ${genInitCode(d.init!)}`)
.join(', ')}`
)
}
})
return codes.join(`\n`)
}
async function parseModuleDecls(module: string, options: GenProxyCodeOptions) {
// 优先合并 ios + android,如果没有,查找根目录 index.uts
const iosDecls = await parseFile(
resolvePlatformIndex('app-ios', module, options),
options
)
const androidDecls = await parseFile(
resolvePlatformIndex('app-android', module, options),
options
)
// 优先使用 app-ios,因为 app-ios 平台函数类型需要正确的参数列表
const decls = mergeDecls(androidDecls, iosDecls)
// 如果没有平台特有,查找 root index.uts
if (!decls.length) {
return await parseFile(resolveRootIndex(module), options)
}
return decls
}
function mergeDecls(from: ProxyDecl[], to: ProxyDecl[]) {
from.forEach((item) => {
if (item.type === 'Class') {
if (
!to.find((toItem) => toItem.type === 'Class' && toItem.cls === item.cls)
) {
to.push(item)
}
} else if (item.type === 'FunctionDeclaration') {
if (
!to.find(
(toItem) =>
toItem.type === 'FunctionDeclaration' &&
toItem.method === item.method
)
) {
to.push(item)
}
}
})
return to
}
async function parseFile(
filename: string | undefined | false,
options: GenProxyCodeOptions
) {
if (filename) {
return parseCode(fs.readFileSync(filename, 'utf8'), options.namespace)
}
return []
}
async function parseCode(code: string, namespace: string) {
// 懒加载 uts 编译器
// eslint-disable-next-line no-restricted-globals
const { parse } = require('@dcloudio/uts')
const ast = await parse(code, { noColor: isInHBuilderX() })
return parseAst(ast, createResolveTypeReferenceName(namespace, ast))
}
type ProxyDecl = ProxyFunctionDeclaration | ProxyClass | VariableDeclaration
interface ProxyFunctionDeclaration {
type: 'FunctionDeclaration'
method: string
async: boolean
params: Parameter[]
isDefault: boolean
}
interface ProxyClass {
type: 'Class'
cls: string
options: {
constructor: { params: Parameter[] }
methods: Record<string, any>
staticMethods: Record<string, any>
props: string[]
staticProps: string[]
}
isDefault: boolean
}
function parseAst(
{ body }: Module,
resolveTypeReferenceName: ResolveTypeReferenceName
) {
const decls: Array<
ProxyFunctionDeclaration | ProxyClass | VariableDeclaration
> = []
body.forEach((item) => {
if (item.type === 'ExportDeclaration') {
const decl = item.declaration
switch (decl.type) {
case 'FunctionDeclaration':
decls.push(
genFunctionDeclaration(decl, resolveTypeReferenceName, false)
)
break
case 'ClassDeclaration':
decls.push(genClassDeclaration(decl, resolveTypeReferenceName, false))
break
case 'VariableDeclaration':
const varDecl = genVariableDeclaration(decl)
if (varDecl) {
decls.push(varDecl)
}
break
}
} else if (item.type === 'ExportDefaultDeclaration') {
const decl = item.decl
if (decl.type === 'ClassExpression') {
if (decl.identifier) {
// export default class test{}
decls.push(genClassDeclaration(decl, resolveTypeReferenceName, false))
}
} else if (decl.type === 'FunctionExpression') {
if (decl.identifier) {
decls.push(
genFunctionDeclaration(decl, resolveTypeReferenceName, true)
)
}
}
}
})
return decls
}
function isReturnPromise(anno?: TsTypeAnnotation) {
if (!anno) {
return false
}
const { typeAnnotation } = anno
return (
typeAnnotation.type === 'TsTypeReference' &&
typeAnnotation.typeName.type === 'Identifier' &&
typeAnnotation.typeName.value === 'Promise'
)
}
function genProxyFunction(
method: string,
async: boolean,
params: Parameter[],
isDefault: boolean = false
): ProxyFunctionDeclaration {
return { type: 'FunctionDeclaration', method, async, params, isDefault }
}
function genProxyClass(
cls: string,
options: {
constructor: { params: Parameter[] }
methods: Record<string, any>
staticMethods: Record<string, any>
props: string[]
staticProps: string[]
},
isDefault: boolean = false
): ProxyClass {
return { type: 'Class', cls, options, isDefault }
}
interface Parameter {
name: string
type: string
}
type ResolveTypeReferenceName = (name: string) => string
function resolveIdentifierType(
ident: BindingIdentifier,
resolveTypeReferenceName: ResolveTypeReferenceName
) {
if (ident.typeAnnotation) {
const { typeAnnotation } = ident.typeAnnotation
if (typeAnnotation.type === 'TsKeywordType') {
return typeAnnotation.kind
} else if (typeAnnotation.type === 'TsFunctionType') {
return 'UTSCallback'
} else if (
typeAnnotation.type === 'TsTypeReference' &&
typeAnnotation.typeName.type === 'Identifier'
) {
return resolveTypeReferenceName(typeAnnotation.typeName.value)
}
}
return ''
}
function resolveFunctionParams(
params: Param[],
resolveTypeReferenceName: ResolveTypeReferenceName
) {
const result: Parameter[] = []
params.forEach(({ pat }) => {
if (pat.type === 'Identifier') {
result.push({
name: pat.value,
type: resolveIdentifierType(
pat as BindingIdentifier,
resolveTypeReferenceName
),
})
} else {
result.push({ name: '', type: '' })
}
})
return result
}
function genFunctionDeclaration(
decl: FunctionDeclaration | FunctionExpression,
resolveTypeReferenceName: ResolveTypeReferenceName,
isDefault: boolean = false
): ProxyFunctionDeclaration {
return genProxyFunction(
decl.identifier!.value,
decl.async || isReturnPromise(decl.returnType),
resolveFunctionParams(decl.params, resolveTypeReferenceName),
isDefault
)
}
function genClassDeclaration(
decl: ClassDeclaration | ClassExpression,
resolveTypeReferenceName: ResolveTypeReferenceName,
isDefault: boolean = false
): ProxyClass {
const cls = decl.identifier!.value
const constructor: { params: Parameter[] } = { params: [] }
const methods: Record<string, { async?: boolean; params: Parameter[] }> = {}
const staticMethods: Record<
string,
{ async?: boolean; params: Parameter[] }
> = {}
const props: string[] = []
const staticProps: string[] = []
decl.body.forEach((item) => {
if (item.type === 'Constructor') {
constructor.params = resolveFunctionParams(
item.params as Param[],
resolveTypeReferenceName
)
} else if (item.type === 'ClassMethod') {
if (item.key.type === 'Identifier') {
const name = item.key.value
const value = {
async:
item.function.async || isReturnPromise(item.function.returnType),
params: resolveFunctionParams(
item.function.params,
resolveTypeReferenceName
),
}
if (item.isStatic) {
staticMethods[name] = value
} else {
methods[name] = value
}
}
} else if (item.type === 'ClassProperty') {
if (item.key.type === 'Identifier') {
if (item.isStatic) {
staticProps.push(item.key.value)
} else {
props.push(item.key.value)
}
}
}
})
return genProxyClass(
cls,
{ constructor, methods, staticMethods, props, staticProps },
isDefault
)
}
function genInitCode(expr: Expression) {
switch (expr.type) {
case 'BooleanLiteral':
return expr.value + ''
case 'NumericLiteral':
return expr.value + ''
case 'StringLiteral':
return expr.value
}
return ''
}
function genVariableDeclaration(
decl: VariableDeclaration
): VariableDeclaration | undefined {
// 目前仅支持 const 的 boolean,number,string
const lits = ['BooleanLiteral', 'NumericLiteral', 'StringLiteral']
if (
decl.kind === 'const' &&
!decl.declarations.find((d) => {
if (d.id.type !== 'Identifier') {
return true
}
if (!d.init) {
return true
}
const type = d.init.type
if (!lits.includes(type)) {
return true
}
return false
})
) {
return decl
}
}
import { compileKotlin } from './kotlin'
import { compileSwift } from './swift'
import { runKotlinProd, runKotlinDev } from './kotlin'
import { runSwiftProd, runSwiftDev } from './swift'
export { genProxyCode } from './code'
export function getCompiler(type: 'kotlin' | 'swift') {
if (type === 'swift') {
return {
compile: compileSwift,
runProd: runSwiftProd,
runDev: runSwiftDev,
}
}
return {
compile: compileKotlin,
runProd: runKotlinProd,
runDev: runKotlinDev,
}
}
......@@ -16,7 +16,6 @@ import {
resolveAndroidDir,
resolvePackage,
resolveUTSPlatformFile,
UTSPlatformResourceOptions,
} from './utils'
import { Module } from '../../../types/types'
......@@ -37,7 +36,66 @@ function parseKotlinPackage(filename: string) {
}
}
export async function compileKotlin(filename: string) {
export async function runKotlinProd(filename: string) {
// 文件有可能是 app-ios 里边的,因为编译到 android 时,为了保证不报错,可能会去读取 ios 下的 uts
if (filename.includes('app-ios')) {
return
}
await compile(filename)
genUTSPlatformResource(filename, {
inputDir: process.env.UNI_INPUT_DIR,
outputDir: process.env.UNI_OUTPUT_DIR,
platform: 'app-android',
extname: '.kt',
})
}
export async function runKotlinDev(filename: string) {
// 文件有可能是 app-ios 里边的,因为编译到 android 时,为了保证不报错,可能会去读取 ios 下的 uts
if (filename.includes('app-ios')) {
return
}
await compile(filename)
const kotlinFile = resolveUTSPlatformFile(filename, {
inputDir: process.env.UNI_INPUT_DIR,
outputDir: process.env.UNI_OUTPUT_DIR,
platform: 'app-android',
extname: '.kt',
})
// 开发模式下,需要生成 dex
if (fs.existsSync(kotlinFile)) {
const compilerServer = getCompilerServer()
if (!compilerServer) {
return
}
const { getDefaultJar, getKotlincHome, compile } = compilerServer
// time = Date.now()
const jarFile = resolveJarPath(kotlinFile)
const options = {
kotlinc: resolveKotlincArgs(
kotlinFile,
getKotlincHome(),
getDefaultJar().concat(resolveLibs(filename))
),
d8: resolveD8Args(jarFile),
}
const res = await compile(options, process.env.UNI_INPUT_DIR)
// console.log('dex compile time: ' + (Date.now() - time) + 'ms')
if (res) {
try {
fs.unlinkSync(jarFile)
// 短期内先不删除,方便排查问题
// fs.unlinkSync(kotlinFile)
} catch (e) {}
const dexFile = resolveDexFile(jarFile)
if (fs.existsSync(dexFile)) {
return normalizePath(path.relative(process.env.UNI_OUTPUT_DIR, dexFile))
}
}
}
}
async function compile(filename: string) {
if (!process.env.UNI_HBUILDERX_PLUGINS) {
return
}
......@@ -67,49 +125,6 @@ export async function compileKotlin(filename: string) {
noColor: isInHBuilderX(),
},
})
// console.log('uts compile time: ' + (Date.now() - time) + 'ms')
const utsPlatformOptions: UTSPlatformResourceOptions = {
inputDir,
outputDir,
platform: 'app-android',
extname: '.kt',
}
if (process.env.NODE_ENV === 'production') {
genUTSPlatformResource(filename, utsPlatformOptions)
} else if (process.env.NODE_ENV === 'development') {
const kotlinFile = resolveUTSPlatformFile(filename, utsPlatformOptions)
// 开发模式下,需要生成 dex
if (fs.existsSync(kotlinFile)) {
const compilerServer = getCompilerServer()
if (!compilerServer) {
return
}
const { getDefaultJar, getKotlincHome, compile } = compilerServer
// time = Date.now()
const jarFile = resolveJarPath(kotlinFile)
const options = {
kotlinc: resolveKotlincArgs(
kotlinFile,
getKotlincHome(),
getDefaultJar().concat(resolveLibs(filename))
),
d8: resolveD8Args(jarFile),
}
const res = await compile(options, process.env.UNI_INPUT_DIR)
// console.log('dex compile time: ' + (Date.now() - time) + 'ms')
if (res) {
try {
fs.unlinkSync(jarFile)
// 短期内先不删除,方便排查问题
// fs.unlinkSync(kotlinFile)
} catch (e) {}
const dexFile = resolveDexFile(jarFile)
if (fs.existsSync(dexFile)) {
return normalizePath(path.relative(outputDir, dexFile))
}
}
}
}
}
function resolveKotlincArgs(filename: string, kotlinc: string, jars: string[]) {
......
......@@ -16,11 +16,23 @@ function parseSwiftPackage(filename: string) {
}
}
export async function compileSwift(filename: string) {
// 开发阶段不编译
if (process.env.NODE_ENV !== 'production') {
export async function runSwiftProd(filename: string) {
// 文件有可能是 app-android 里边的,因为编译到 ios 时,为了保证不报错,可能会去读取 android 下的 uts
if (filename.includes('app-android')) {
return
}
await compile(filename)
genUTSPlatformResource(filename, {
inputDir: process.env.UNI_INPUT_DIR,
outputDir: process.env.UNI_OUTPUT_DIR,
platform: 'app-ios',
extname: '.swift',
})
}
export async function runSwiftDev(_filename: string) {}
async function compile(filename: string) {
if (!process.env.UNI_HBUILDERX_PLUGINS) {
return
}
......@@ -44,11 +56,4 @@ export async function compileSwift(filename: string) {
noColor: isInHBuilderX(),
},
})
genUTSPlatformResource(filename, {
inputDir,
outputDir,
platform: 'app-ios',
extname: '.swift',
})
}
......@@ -170,14 +170,13 @@ export function initEnv(
function initUtsPlatform(options: CliOptions) {
if (options.platform === 'app-android') {
process.env.UNI_APP_PLATFORM = 'android'
process.env.UNI_UTS_PLATFORM = 'app-android'
options.platform = 'app'
} else if (options.platform === 'app-ios') {
process.env.UNI_APP_PLATFORM = 'ios'
process.env.UNI_UTS_PLATFORM = 'app-ios'
options.platform = 'app'
} else {
// 运行时,可能传入了 UNI_APP_PLATFORM = 'android'|'ios'
if (process.env.UNI_APP_PLATFORM === 'android') {
process.env.UNI_UTS_PLATFORM = 'app-android'
}
......@@ -191,8 +190,12 @@ function initUtsPlatform(options: CliOptions) {
if (options.platform === 'h5') {
process.env.UNI_UTS_PLATFORM = 'web'
}
if (!process.env.UNI_UTS_PLATFORM) {
process.env.UNI_UTS_PLATFORM = options.platform as any
// 非 app 平台,自动补充 UNI_UTS_PLATFORM
// app 平台,必须主动传入
if (options.platform !== 'app') {
if (!process.env.UNI_UTS_PLATFORM) {
process.env.UNI_UTS_PLATFORM = options.platform as any
}
}
}
......
......@@ -39,11 +39,13 @@ export function uniResolveIdPlugin(
path.join(id, BUILT_IN_MODULES[id as BuiltInModulesKey])
))
}
return resolveUtsModule(
id,
importer ? path.dirname(importer) : process.env.UNI_INPUT_DIR,
process.env.UNI_UTS_PLATFORM
)
if (process.env.UNI_PLATFORM !== 'app') {
return resolveUtsModule(
id,
importer ? path.dirname(importer) : process.env.UNI_INPUT_DIR,
process.env.UNI_UTS_PLATFORM
)
}
},
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册