提交 a54bd740 编写于 作者: Q qiang

chore: Merge branch 'next' into jzhmcoo1-next

# Conflicts:
#	pnpm-lock.yaml
...@@ -21,7 +21,7 @@ jobs: ...@@ -21,7 +21,7 @@ jobs:
fail-fast: false fail-fast: false
name: 'e2e: node-${{ matrix.node_version }}, ${{ matrix.os }}' name: 'e2e: node-${{ matrix.node_version }}, ${{ matrix.os }}'
env: env:
CYPRESS_CACHE_FOLDER: '~/.cache/Cypress' CYPRESS_CACHE_FOLDER: ${{ github.workspace }}/node_modules/.cache/Cypress
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
...@@ -29,13 +29,12 @@ jobs: ...@@ -29,13 +29,12 @@ jobs:
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v2.0.1 uses: pnpm/action-setup@v2.0.1
with: with:
version: 6.15.1 version: 8
- name: Set node version to ${{ matrix.node_version }} - name: Set node version to ${{ matrix.node_version }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node_version }} node-version: ${{ matrix.node_version }}
cache: 'pnpm'
- run: pnpm install - run: pnpm install
...@@ -54,7 +53,7 @@ jobs: ...@@ -54,7 +53,7 @@ jobs:
npx cypress version --component node npx cypress version --component node
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@v4 uses: cypress-io/github-action@v5
with: with:
install: false install: false
start: npm run dev:ssr start: npm run dev:ssr
......
{ {
"private": true, "private": true,
"version": "3.0.0-alpha-3071220230324001", "version": "3.0.0-alpha-3080220230428002",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
], ],
...@@ -40,13 +40,13 @@ ...@@ -40,13 +40,13 @@
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.10", "@babel/core": "^7.21.3",
"@babel/preset-env": "^7.16.11", "@babel/preset-env": "^7.20.2",
"@dcloudio/types": "3.3.2", "@dcloudio/types": "3.3.2",
"@dcloudio/uni-api": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-api": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-app": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-app": "3.0.0-alpha-3080220230428002",
"@jest/types": "^29.0.3", "@jest/types": "^29.0.3",
"@microsoft/api-extractor": "^7.33.6", "@microsoft/api-extractor": "^7.34.5",
"@rollup/plugin-alias": "^4.0.2", "@rollup/plugin-alias": "^4.0.2",
"@rollup/plugin-babel": "^6.0.3", "@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-commonjs": "^24.0.0",
...@@ -56,10 +56,10 @@ ...@@ -56,10 +56,10 @@
"@rollup/plugin-strip": "^3.0.2", "@rollup/plugin-strip": "^3.0.2",
"@rollup/plugin-terser": "^0.2.1", "@rollup/plugin-terser": "^0.2.1",
"@types/jest": "^29.2.3", "@types/jest": "^29.2.3",
"@types/node": "^18.11.18", "@types/node": "^18.16.2",
"@types/sass": "~1.43.1", "@types/sass": "~1.43.1",
"@typescript-eslint/parser": "^5.44.0", "@typescript-eslint/parser": "^5.59.1",
"@vitejs/plugin-vue": "^4.1.0", "@vitejs/plugin-vue": "^4.2.1",
"@vitejs/plugin-vue-jsx": "^3.0.1", "@vitejs/plugin-vue-jsx": "^3.0.1",
"@vue/compiler-sfc": "3.2.47", "@vue/compiler-sfc": "3.2.47",
"@vue/reactivity": "3.2.47", "@vue/reactivity": "3.2.47",
...@@ -67,6 +67,7 @@ ...@@ -67,6 +67,7 @@
"@vue/runtime-dom": "3.2.47", "@vue/runtime-dom": "3.2.47",
"@vue/shared": "3.2.47", "@vue/shared": "3.2.47",
"core-js": "^2.6.12", "core-js": "^2.6.12",
"cypress": "^10.7.0",
"enquirer": "^2.3.6", "enquirer": "^2.3.6",
"eslint": "^8.28.0", "eslint": "^8.28.0",
"execa": "^5.1.1", "execa": "^5.1.1",
...@@ -75,10 +76,10 @@ ...@@ -75,10 +76,10 @@
"lint-staged": "^10.5.3", "lint-staged": "^10.5.3",
"mini-types": "^0.1.7", "mini-types": "^0.1.7",
"minimist": "^1.2.5", "minimist": "^1.2.5",
"miniprogram-api-typings": "^3.4.4", "miniprogram-api-typings": "^3.9.1",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"rollup": "^3.7.0", "rollup": "^3.20.6",
"rollup-plugin-jscc": "^2.0.0", "rollup-plugin-jscc": "^2.0.0",
"rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0", "rollup-plugin-node-globals": "^1.4.0",
...@@ -86,9 +87,9 @@ ...@@ -86,9 +87,9 @@
"semver": "^7.3.5", "semver": "^7.3.5",
"simple-git-hooks": "^2.8.0", "simple-git-hooks": "^2.8.0",
"terser": "^5.4.0", "terser": "^5.4.0",
"ts-jest": "^29.0.3", "ts-jest": "^29.1.0",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vite": "4.2.1", "vite": "4.3.5",
"vue": "3.2.47", "vue": "3.2.47",
"vue-router": "^4.1.6", "vue-router": "^4.1.6",
"yorkie": "^2.0.0" "yorkie": "^2.0.0"
...@@ -98,4 +99,4 @@ ...@@ -98,4 +99,4 @@
}, },
"packageManager": "pnpm@8.2.0", "packageManager": "pnpm@8.2.0",
"name": "uni-app-next" "name": "uni-app-next"
} }
\ No newline at end of file
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
}, },
"devDependencies": { "devDependencies": {
"@dcloudio/vite-plugin-uni": "../../vite-plugin-uni", "@dcloudio/vite-plugin-uni": "../../vite-plugin-uni",
"vite": "^4.2.1" "vite": "^4.3.5"
}, },
"resolutions": { "resolutions": {
"@dcloudio/uni-app-vite": "../../uni-app-vite" "@dcloudio/uni-app-vite": "../../uni-app-vite"
......
...@@ -48,5 +48,7 @@ declare namespace NodeJS { ...@@ -48,5 +48,7 @@ declare namespace NodeJS {
__VUE_PROD_DEVTOOLS__?: 'true' __VUE_PROD_DEVTOOLS__?: 'true'
__VUE_DEVTOOLS_HOST__: string __VUE_DEVTOOLS_HOST__: string
__VUE_DEVTOOLS_PORT__: string __VUE_DEVTOOLS_PORT__: string
UNI_APP_X?: 'true' | 'false'
} }
} }
...@@ -19,7 +19,7 @@ declare module '@vue/runtime-core' { ...@@ -19,7 +19,7 @@ declare module '@vue/runtime-core' {
type LifecycleHook = Function[] | null type LifecycleHook = Function[] | null
type UniLifecycleHookInstance = { type UniLifecycleHookInstance = {
[name in (typeof UniLifecycleHooks)[number]]: LifecycleHook [name in typeof UniLifecycleHooks[number]]: LifecycleHook
} }
interface ComponentInternalInstance extends UniLifecycleHookInstance { interface ComponentInternalInstance extends UniLifecycleHookInstance {
__isUnload: boolean __isUnload: boolean
......
{ {
"private": true, "private": true,
"name": "@dcloudio/size-check", "name": "@dcloudio/size-check",
"version": "3.0.0-alpha-3071220230324001", "version": "3.0.0-alpha-3080220230428002",
"dependencies": { "dependencies": {
"@dcloudio/uni-app": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-app": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-h5": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-h5": "3.0.0-alpha-3080220230428002",
"vue": "3.2.47", "vue": "3.2.47",
"vue-i18n": "9.1.9", "vue-i18n": "9.1.9",
"vuex": "^4.1.0" "vuex": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-cli-shared": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-components": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-components": "3.0.0-alpha-3080220230428002",
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-3071220230324001" "@dcloudio/vite-plugin-uni": "3.0.0-alpha-3080220230428002"
} }
} }
{ {
"private": true, "private": true,
"name": "@dcloudio/uni-api", "name": "@dcloudio/uni-api",
"version": "3.0.0-alpha-3071220230324001", "version": "3.0.0-alpha-3080220230428002",
"description": "@dcloudio/uni-api", "description": "@dcloudio/uni-api",
"sideEffects": false, "sideEffects": false,
"module": "src/index.ts", "module": "src/index.ts",
...@@ -18,6 +18,6 @@ ...@@ -18,6 +18,6 @@
"@vue/shared": "3.2.47" "@vue/shared": "3.2.47"
}, },
"devDependencies": { "devDependencies": {
"@dcloudio/uni-shared": "3.0.0-alpha-3071220230324001" "@dcloudio/uni-shared": "3.0.0-alpha-3080220230428002"
} }
} }
...@@ -8,6 +8,6 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } ...@@ -8,6 +8,6 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var appVite__default = /*#__PURE__*/_interopDefault(appVite); var appVite__default = /*#__PURE__*/_interopDefault(appVite);
var appUVue__default = /*#__PURE__*/_interopDefault(appUVue); var appUVue__default = /*#__PURE__*/_interopDefault(appUVue);
var index = [process.env.UNI_UVUE === 'true' ? appUVue__default.default : appVite__default.default]; var index = [process.env.UNI_APP_X === 'true' ? appUVue__default.default : appVite__default.default];
module.exports = index; module.exports = index;
...@@ -1394,23 +1394,30 @@ const initI18nScanCodeMsgsOnce = /*#__PURE__*/ once(() => { ...@@ -1394,23 +1394,30 @@ const initI18nScanCodeMsgsOnce = /*#__PURE__*/ once(() => {
}); });
const initI18nStartSoterAuthenticationMsgsOnce = /*#__PURE__*/ once(() => { const initI18nStartSoterAuthenticationMsgsOnce = /*#__PURE__*/ once(() => {
const name = 'uni.startSoterAuthentication.'; const name = 'uni.startSoterAuthentication.';
const keys = ['authContent']; const keys = ['authContent', 'waitingContent'];
{ {
useI18n().add(LOCALE_EN, normalizeMessages(name, keys, ['Fingerprint recognition']), false); useI18n().add(LOCALE_EN, normalizeMessages(name, keys, [
'Fingerprint recognition',
'Unrecognizable',
]), false);
} }
{ {
useI18n().add(LOCALE_ES, normalizeMessages(name, keys, ['Reconocimiento de huellas dactilares']), false); useI18n().add(LOCALE_ES, normalizeMessages(name, keys, [
'Reconocimiento de huellas dactilares',
'Irreconocible',
]), false);
} }
{ {
useI18n().add(LOCALE_FR, normalizeMessages(name, keys, [ useI18n().add(LOCALE_FR, normalizeMessages(name, keys, [
"Reconnaissance de l'empreinte digitale", "Reconnaissance de l'empreinte digitale",
'Méconnaissable',
]), false); ]), false);
} }
{ {
useI18n().add(LOCALE_ZH_HANS, normalizeMessages(name, keys, ['指纹识别中...']), false); useI18n().add(LOCALE_ZH_HANS, normalizeMessages(name, keys, ['指纹识别中...', '无法识别']), false);
} }
{ {
useI18n().add(LOCALE_ZH_HANT, normalizeMessages(name, keys, ['指紋識別中...']), false); useI18n().add(LOCALE_ZH_HANT, normalizeMessages(name, keys, ['指紋識別中...', '無法識別']), false);
} }
}); });
...@@ -1572,7 +1579,7 @@ function initPageInternalInstance(openType, url, pageQuery, meta, eventChannel, ...@@ -1572,7 +1579,7 @@ function initPageInternalInstance(openType, url, pageQuery, meta, eventChannel,
meta, meta,
openType, openType,
eventChannel, eventChannel,
statusBarStyle: titleColor === '#000000' ? 'dark' : 'light', statusBarStyle: titleColor === '#ffffff' ? 'light' : 'dark',
}; };
} }
...@@ -1802,21 +1809,21 @@ function showPage({ context = {}, url, data = {}, style = {}, onMessage, onClose ...@@ -1802,21 +1809,21 @@ function showPage({ context = {}, url, data = {}, style = {}, onMessage, onClose
const invokeOnCallback = (name, res) => UniServiceJSBridge.emit('api.' + name, res); const invokeOnCallback = (name, res) => UniServiceJSBridge.emit('api.' + name, res);
let invokeViewMethodId = 1; let invokeViewMethodId = 1;
function publishViewMethodName() { function publishViewMethodName(pageId) {
return getCurrentPageId() + '.' + INVOKE_VIEW_API; return (pageId || getCurrentPageId()) + '.' + INVOKE_VIEW_API;
} }
const invokeViewMethod = (name, args, pageId, callback) => { const invokeViewMethod = (name, args, pageId, callback) => {
const { subscribe, publishHandler } = UniServiceJSBridge; const { subscribe, publishHandler } = UniServiceJSBridge;
const id = callback ? invokeViewMethodId++ : 0; const id = callback ? invokeViewMethodId++ : 0;
callback && subscribe(INVOKE_VIEW_API + '.' + id, callback, true); callback && subscribe(INVOKE_VIEW_API + '.' + id, callback, true);
publishHandler(publishViewMethodName(), { id, name, args }, pageId); publishHandler(publishViewMethodName(pageId), { id, name, args }, pageId);
}; };
const invokeViewMethodKeepAlive = (name, args, callback, pageId) => { const invokeViewMethodKeepAlive = (name, args, callback, pageId) => {
const { subscribe, unsubscribe, publishHandler } = UniServiceJSBridge; const { subscribe, unsubscribe, publishHandler } = UniServiceJSBridge;
const id = invokeViewMethodId++; const id = invokeViewMethodId++;
const subscribeName = INVOKE_VIEW_API + '.' + id; const subscribeName = INVOKE_VIEW_API + '.' + id;
subscribe(subscribeName, callback); subscribe(subscribeName, callback);
publishHandler(publishViewMethodName(), { id, name, args }, pageId); publishHandler(publishViewMethodName(pageId), { id, name, args }, pageId);
return () => { return () => {
unsubscribe(subscribeName); unsubscribe(subscribeName);
}; };
...@@ -14151,7 +14158,7 @@ const startSoterAuthentication = defineAsyncApi(API_START_SOTER_AUTHENTICATION, ...@@ -14151,7 +14158,7 @@ const startSoterAuthentication = defineAsyncApi(API_START_SOTER_AUTHENTICATION,
4: () => { 4: () => {
if (waiting) { if (waiting) {
clearTimeout(waitingTimer); clearTimeout(waitingTimer);
waiting.setTitle('无法识别'); waiting.setTitle(t('uni.startSoterAuthentication.waitingContent'));
waitingTimer = setTimeout(() => { waitingTimer = setTimeout(() => {
waiting && waiting.setTitle(authenticateMessage); waiting && waiting.setTitle(authenticateMessage);
}, 1000); }, 1000);
......
var CALL_METHOD_ERROR,hasOwnProperty=Object.prototype.hasOwnProperty,isUndef=function(v){return null==v},isArray=Array.isArray,cacheStringFunction=function(fn){var cache=Object.create(null);return function(str){return cache[str]||(cache[str]=fn(str))}},hyphenateRE=/\B([A-Z])/g,hyphenate=cacheStringFunction((function(str){return str.replace(hyphenateRE,"-$1").toLowerCase()})),camelizeRE=/-(\w)/g,camelize=cacheStringFunction((function(str){return str.replace(camelizeRE,(function(_,c){return c?c.toUpperCase():""}))})),capitalize=cacheStringFunction((function(str){return str.charAt(0).toUpperCase()+str.slice(1)})),PATH_RE=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;function getPaths(path,data){if(isArray(path))return path;if(data&&(val=data,key=path,hasOwnProperty.call(val,key)))return[path];var val,key,res=[];return path.replace(PATH_RE,(function(match,p1,offset,string){return res.push(offset?string.replace(/\\(\\)?/g,"$1"):p1||match),string})),res}function getDataByPath(data,path){var dataPath,paths=getPaths(path,data);for(dataPath=paths.shift();!isUndef(dataPath);){if(null==(data=data[dataPath]))return;dataPath=paths.shift()}return data}function getPageId(page){return page.__wxWebviewId__?page.__wxWebviewId__:page.privateProperties?page.privateProperties.slaveId:page.$page?page.$page.id:void 0}function getPagePath(page){return page.route||page.uri}function getPageQuery(page){return page.options||page.$page&&page.$page.options||{}}function parsePage(page){return{id:getPageId(page),path:getPagePath(page),query:getPageQuery(page)}}function getPageVm(id){var page=function(id){return getCurrentPages().find((function(page){return getPageId(page)===id}))}(id);return page&&page.$vm}function findComponentVm(vm,nodeId){var res;return vm&&(!function(vm,nodeId){return function(vm){if(vm._$weex)return vm._uid;if(vm._$id)return vm._$id;var parent_1=function(vm){for(var parent=vm.$parent;parent;){if(parent._$id)return parent;parent=parent.$parent}}(vm);if(!vm.$parent)return"-1";var vnode=vm.$vnode,context=vnode.context;return context&&context!==parent_1&&context._$id?context._$id+";"+parent_1._$id+","+vnode.data.attrs._i:parent_1._$id+","+vnode.data.attrs._i}(vm)===nodeId}(vm,nodeId)?vm.$children.find((function(child){return res=findComponentVm(child,nodeId)})):res=vm),res}function getComponentVm(pageId,nodeId){var pageVm=getPageVm(pageId);return pageVm&&findComponentVm(pageVm,nodeId)}function getData(vm,path){var data;return vm&&(data=path?getDataByPath(vm.$data,path):Object.assign({},vm.$data)),Promise.resolve({data:data})}function setData(vm,data){return vm&&Object.keys(data).forEach((function(name){vm[name]=data[name]})),Promise.resolve()}function callMethod(vm,method,args){return new Promise((function(resolve,reject){if(!vm)return reject(CALL_METHOD_ERROR.VM_NOT_EXISTS);if(!vm[method])return reject(CALL_METHOD_ERROR.VM_NOT_EXISTS);var obj,ret=vm[method].apply(vm,args);!(obj=ret)||"object"!=typeof obj&&"function"!=typeof obj||"function"!=typeof obj.then?resolve({result:ret}):ret.then((function(res){resolve({result:res})}))}))}!function(CALL_METHOD_ERROR){CALL_METHOD_ERROR.VM_NOT_EXISTS="VM_NOT_EXISTS",CALL_METHOD_ERROR.METHOD_NOT_EXISTS="METHOD_NOT_EXISTS"}(CALL_METHOD_ERROR||(CALL_METHOD_ERROR={}));var SYNC_APIS=["stopRecord","getRecorderManager","pauseVoice","stopVoice","pauseBackgroundAudio","stopBackgroundAudio","getBackgroundAudioManager","createAudioContext","createInnerAudioContext","createVideoContext","createCameraContext","createMapContext","canIUse","startAccelerometer","stopAccelerometer","startCompass","stopCompass","hideToast","hideLoading","showNavigationBarLoading","hideNavigationBarLoading","navigateBack","createAnimation","pageScrollTo","createSelectorQuery","createCanvasContext","createContext","drawCanvas","hideKeyboard","stopPullDownRefresh","arrayBufferToBase64","base64ToArrayBuffer"],originUni={},SYNC_API_RE=/Sync$/,MOCK_API_BLACKLIST_RE=/^on|^off/;function isSyncApi(method){return SYNC_API_RE.test(method)||-1!==SYNC_APIS.indexOf(method)}var App$1={getPageStack:function(){return Promise.resolve({pageStack:getCurrentPages().map((function(page){return parsePage(page)}))})},getCurrentPage:function(){var pages=getCurrentPages(),len=pages.length;return new Promise((function(resolve,reject){len?resolve(parsePage(pages[len-1])):reject(Error("getCurrentPages().length=0"))}))},callUniMethod:function(params){var method=params.method,args=params.args;return new Promise((function(resolve,reject){if(!uni[method])return reject(Error("uni."+method+" not exists"));if(isSyncApi(method))return resolve({result:uni[method].apply(uni,args)});var params=[Object.assign({},args[0]||{},{success:function(result){setTimeout((function(){resolve({result:result})}),"pageScrollTo"===method?350:0)},fail:function(res){reject(Error(res.errMsg.replace(method+":fail ","")))}})];uni[method].apply(uni,params)}))},mockUniMethod:function(params){var method=params.method;if(!uni[method])throw Error("uni."+method+" not exists");if(!function(method){return!MOCK_API_BLACKLIST_RE.test(method)}(method))throw Error("You can't mock uni."+method);var mockFn,result=params.result,functionDeclaration=params.functionDeclaration;return isUndef(result)&&isUndef(functionDeclaration)?(originUni[method]&&(uni[method]=originUni[method],delete originUni[method]),Promise.resolve()):(mockFn=isUndef(functionDeclaration)?isSyncApi(method)?function(){return result}:function(params){setTimeout((function(){result.errMsg&&-1!==result.errMsg.indexOf(":fail")?params.fail&&params.fail(result):params.success&&params.success(result),params.complete&&params.complete(result)}),4)}:function(){for(var args=[],_i=0;_i<arguments.length;_i++)args[_i]=arguments[_i];return new Function("return "+functionDeclaration)().apply(mockFn,args.concat(params.args))},mockFn.origin=originUni[method]||uni[method],originUni[method]||(originUni[method]=uni[method]),uni[method]=mockFn,Promise.resolve())}},Page$1={getData:function(params){return getData(getPageVm(params.pageId),params.path)},setData:function(params){return setData(getPageVm(params.pageId),params.data)},callMethod:function(params){var _a,err=((_a={})[CALL_METHOD_ERROR.VM_NOT_EXISTS]="Page["+params.pageId+"] not exists",_a[CALL_METHOD_ERROR.METHOD_NOT_EXISTS]="page."+params.method+" not exists",_a);return new Promise((function(resolve,reject){callMethod(getPageVm(params.pageId),params.method,params.args).then((function(res){return resolve(res)})).catch((function(type){reject(Error(err[type]))}))}))}};function getNodeId(params){return params.nodeId||params.elementId}var Element$1={getData:function(params){return getData(getComponentVm(params.pageId,getNodeId(params)),params.path)},setData:function(params){return setData(getComponentVm(params.pageId,getNodeId(params)),params.data)},callMethod:function(params){var _a,nodeId=getNodeId(params),err=((_a={})[CALL_METHOD_ERROR.VM_NOT_EXISTS]="Component["+params.pageId+":"+nodeId+"] not exists",_a[CALL_METHOD_ERROR.METHOD_NOT_EXISTS]="component."+params.method+" not exists",_a);return new Promise((function(resolve,reject){callMethod(getComponentVm(params.pageId,nodeId),params.method,params.args).then((function(res){return resolve(res)})).catch((function(type){reject(Error(err[type]))}))}))}}; var CALL_METHOD_ERROR,hasOwnProperty=Object.prototype.hasOwnProperty,isUndef=function(v){return null==v},isArray=Array.isArray,cacheStringFunction=function(fn){var cache=Object.create(null);return function(str){return cache[str]||(cache[str]=fn(str))}},hyphenateRE=/\B([A-Z])/g,hyphenate=cacheStringFunction((function(str){return str.replace(hyphenateRE,"-$1").toLowerCase()})),camelizeRE=/-(\w)/g,camelize=cacheStringFunction((function(str){return str.replace(camelizeRE,(function(_,c){return c?c.toUpperCase():""}))})),capitalize=cacheStringFunction((function(str){return str.charAt(0).toUpperCase()+str.slice(1)})),PATH_RE=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;function getPaths(path,data){if(isArray(path))return path;if(data&&(val=data,key=path,hasOwnProperty.call(val,key)))return[path];var val,key,res=[];return path.replace(PATH_RE,(function(match,p1,offset,string){return res.push(offset?string.replace(/\\(\\)?/g,"$1"):p1||match),string})),res}function getDataByPath(data,path){var dataPath,paths=getPaths(path,data);for(dataPath=paths.shift();!isUndef(dataPath);){if(null==(data=data[dataPath]))return;dataPath=paths.shift()}return data}function getPageId(page){return page.__wxWebviewId__?page.__wxWebviewId__:page.privateProperties?page.privateProperties.slaveId:page.$page?page.$page.id:void 0}function getPagePath(page){return page.route||page.uri}function getPageQuery(page){return page.options||page.$page&&page.$page.options||{}}function parsePage(page){return{id:getPageId(page),path:getPagePath(page),query:getPageQuery(page)}}function getPageVm(id){var page=function(id){return getCurrentPages().find((function(page){return getPageId(page)===id}))}(id);return page&&page.$vm}function findComponentVm(vm,nodeId){var res;return vm&&(!function(vm,nodeId){return function(vm){if(vm._$weex)return vm._uid;if(vm._$id)return vm._$id;var parent_1=function(vm){for(var parent=vm.$parent;parent;){if(parent._$id)return parent;parent=parent.$parent}}(vm);if(!vm.$parent)return"-1";var vnode=vm.$vnode,context=vnode.context;return context&&context!==parent_1&&context._$id?context._$id+";"+parent_1._$id+","+vnode.data.attrs._i:parent_1._$id+","+vnode.data.attrs._i}(vm)===nodeId}(vm,nodeId)?vm.$children.find((function(child){return res=findComponentVm(child,nodeId)})):res=vm),res}function getComponentVm(pageId,nodeId){var pageVm=getPageVm(pageId);return pageVm&&findComponentVm(pageVm,nodeId)}function getData(vm,path){var data;return vm&&(data=path?getDataByPath(vm.$data,path):Object.assign({},vm.$data)),Promise.resolve({data:data})}function setData(vm,data){return vm&&Object.keys(data).forEach((function(name){vm[name]=data[name]})),Promise.resolve()}function callMethod(vm,method,args){return new Promise((function(resolve,reject){if(!vm)return reject(CALL_METHOD_ERROR.VM_NOT_EXISTS);if(!vm[method])return reject(CALL_METHOD_ERROR.VM_NOT_EXISTS);var obj,ret=vm[method].apply(vm,args);!(obj=ret)||"object"!=typeof obj&&"function"!=typeof obj||"function"!=typeof obj.then?resolve({result:ret}):ret.then((function(res){resolve({result:res})}))}))}!function(CALL_METHOD_ERROR){CALL_METHOD_ERROR.VM_NOT_EXISTS="VM_NOT_EXISTS",CALL_METHOD_ERROR.METHOD_NOT_EXISTS="METHOD_NOT_EXISTS"}(CALL_METHOD_ERROR||(CALL_METHOD_ERROR={}));var SYNC_APIS=["stopRecord","getRecorderManager","pauseVoice","stopVoice","pauseBackgroundAudio","stopBackgroundAudio","getBackgroundAudioManager","createAudioContext","createInnerAudioContext","createVideoContext","createCameraContext","createMapContext","canIUse","startAccelerometer","stopAccelerometer","startCompass","stopCompass","hideToast","hideLoading","showNavigationBarLoading","hideNavigationBarLoading","navigateBack","createAnimation","pageScrollTo","createSelectorQuery","createCanvasContext","createContext","drawCanvas","hideKeyboard","stopPullDownRefresh","arrayBufferToBase64","base64ToArrayBuffer"],originUni={},SYNC_API_RE=/Sync$/,MOCK_API_BLACKLIST_RE=/^on|^off/;function isSyncApi(method){return SYNC_API_RE.test(method)||-1!==SYNC_APIS.indexOf(method)}var App$1={getPageStack:function(){return Promise.resolve({pageStack:getCurrentPages().map((function(page){return parsePage(page)}))})},getCurrentPage:function(){var pages=getCurrentPages(),len=pages.length;return new Promise((function(resolve,reject){len?resolve(parsePage(pages[len-1])):reject(Error("getCurrentPages().length=0"))}))},callUniMethod:function(params){var method=params.method,args=params.args;return new Promise((function(resolve,reject){if(!uni[method])return reject(Error("uni."+method+" not exists"));if(isSyncApi(method))return resolve({result:uni[method].apply(uni,args)});var params=[Object.assign({},args[0]||{},{success:function(result){setTimeout((function(){resolve({result:result})}),"pageScrollTo"===method?350:0)},fail:function(res){reject(Error(res.errMsg.replace(method+":fail ","")))}})];uni[method].apply(uni,params)}))},mockUniMethod:function(params){var method=params.method;if(!uni[method])throw Error("uni."+method+" not exists");if(!function(method){return!MOCK_API_BLACKLIST_RE.test(method)}(method))throw Error("You can't mock uni."+method);var mockFn,result=params.result,functionDeclaration=params.functionDeclaration;return isUndef(result)&&isUndef(functionDeclaration)?(originUni[method]&&(uni[method]=originUni[method],delete originUni[method]),Promise.resolve()):(mockFn=isUndef(functionDeclaration)?isSyncApi(method)?function(){return result}:function(params){setTimeout((function(){result.errMsg&&-1!==result.errMsg.indexOf(":fail")?params.fail&&params.fail(result):params.success&&params.success(result),params.complete&&params.complete(result)}),4)}:function(){for(var args=[],_i=0;_i<arguments.length;_i++)args[_i]=arguments[_i];return new Function("return "+functionDeclaration)().apply(mockFn,args.concat(params.args))},mockFn.origin=originUni[method]||uni[method],originUni[method]||(originUni[method]=uni[method]),uni[method]=mockFn,Promise.resolve())},captureScreenshot:function(){return new Promise((function(resolve,reject){var pages=getCurrentPages(),len=pages.length;if(len){var page=pages[len-1];if(page){var webview=page.$getAppWebview(),bitmap_1=new plus.nativeObj.Bitmap("captureScreenshot","captureScreenshot.png");webview.draw(bitmap_1,(function(res){var data=bitmap_1.toBase64Data().replace("data:image/png;base64,","");bitmap_1.clear(),resolve({data:data})}),(function(err){reject(Error("captureScreenshot fail: "+err.message))}),{drawContent:!0})}}else reject(Error("getCurrentPage fail."))}))}},Page$1={getData:function(params){return getData(getPageVm(params.pageId),params.path)},setData:function(params){return setData(getPageVm(params.pageId),params.data)},callMethod:function(params){var _a,err=((_a={})[CALL_METHOD_ERROR.VM_NOT_EXISTS]="Page["+params.pageId+"] not exists",_a[CALL_METHOD_ERROR.METHOD_NOT_EXISTS]="page."+params.method+" not exists",_a);return new Promise((function(resolve,reject){callMethod(getPageVm(params.pageId),params.method,params.args).then((function(res){return resolve(res)})).catch((function(type){reject(Error(err[type]))}))}))}};function getNodeId(params){return params.nodeId||params.elementId}var Element$1={getData:function(params){return getData(getComponentVm(params.pageId,getNodeId(params)),params.path)},setData:function(params){return setData(getComponentVm(params.pageId,getNodeId(params)),params.data)},callMethod:function(params){var _a,nodeId=getNodeId(params),err=((_a={})[CALL_METHOD_ERROR.VM_NOT_EXISTS]="Component["+params.pageId+":"+nodeId+"] not exists",_a[CALL_METHOD_ERROR.METHOD_NOT_EXISTS]="component."+params.method+" not exists",_a);return new Promise((function(resolve,reject){callMethod(getComponentVm(params.pageId,nodeId),params.method,params.args).then((function(res){return resolve(res)})).catch((function(type){reject(Error(err[type]))}))}))}};
/*! ***************************************************************************** /*! *****************************************************************************
Copyright (c) Microsoft Corporation. Copyright (c) Microsoft Corporation.
......
"use strict";var t=require("fs"),e=require("debug"),s=require("postcss-selector-parser"),r=require("fs-extra"),i=require("licia/dateFormat"),a=require("path"),n=require("util"),o=require("licia/sleep");function l(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var c=l(t),u=l(e),d=l(s),h=l(r),p=l(i),m=l(o);function y(t){t.walk((t=>{if("tag"===t.type){const e=t.value;t.value="page"===e?"body":"uni-"+e}}))}u.default("automator:devtool");const f=["Page.getElement","Page.getElements","Element.getElement","Element.getElements"];const g=/^win/.test(process.platform);function w(t){try{return require(t)}catch(e){return require(require.resolve(t,{paths:[process.cwd()]}))}}const $=u.default("automator:launcher"),M=n.promisify(c.default.readdir),v=n.promisify(c.default.stat);async function P(t){const e=await M(t);return(await Promise.all(e.map((async e=>{const s=a.resolve(t,e);return(await v(s)).isDirectory()?P(s):s})))).reduce(((t,e)=>t.concat(e)),[])}class E{constructor(t){this.id=t.id,this.app=t.executablePath,this.appid=t.appid||"HBuilder",this.package=t.package||"io.dcloud.HBuilder"}shouldPush(){return this.exists(this.FILE_APP_SERVICE).then((()=>($(`${p.default("yyyy-mm-dd HH:MM:ss:l")} ${this.FILE_APP_SERVICE} exists`),!1))).catch((()=>($(`${p.default("yyyy-mm-dd HH:MM:ss:l")} ${this.FILE_APP_SERVICE} not exists`),!0)))}push(t){return P(t).then((e=>{const s=e.map((e=>{const s=(t=>g?t.replace(/\\/g,"/"):t)(a.join(this.DIR_WWW,a.relative(t,e)));return $(`${p.default("yyyy-mm-dd HH:MM:ss:l")} push ${e} ${s}`),this.pushFile(e,s)}));return Promise.all(s)})).then((t=>!0))}get FILE_APP_SERVICE(){return`${this.DIR_WWW}/app-service.js`}}const S=u.default("automator:simctl");function A(t){const e=parseInt(t);return e>9?String(e):"0"+e}class _ extends E{constructor(){super(...arguments),this.bundleVersion=""}async init(){const t=w("node-simctl").Simctl;this.tool=new t({udid:this.id});try{await this.tool.bootDevice()}catch(t){}await this.initSDCard(),S(`${p.default("yyyy-mm-dd HH:MM:ss:l")} init ${this.id}`)}async initSDCard(){const t=await this.tool.appInfo(this.package);S(`${p.default("yyyy-mm-dd HH:MM:ss:l")} appInfo ${t}`);const e=t.match(/DataContainer\s+=\s+"(.*)"/);if(!e)return Promise.resolve("");const s=t.match(/CFBundleVersion\s+=\s+(.*);/);if(!s)return Promise.resolve("");this.sdcard=e[1].replace("file:",""),this.bundleVersion=s[1],S(`${p.default("yyyy-mm-dd HH:MM:ss:l")} install ${this.sdcard}`)}async version(){return Promise.resolve(this.bundleVersion)}formatVersion(t){const e=t.split(".");return 3!==e.length?t:e[0]+A(e[1])+A(e[2])}async install(){return S(`${p.default("yyyy-mm-dd HH:MM:ss:l")} install ${this.app}`),await this.tool.installApp(this.app),await this.tool.grantPermission(this.package,"all"),await this.initSDCard(),Promise.resolve(!0)}async start(){try{await this.tool.terminateApp(this.package)}catch(t){}try{await this.tool.launchApp(this.package)}catch(t){console.error(t)}return Promise.resolve(!0)}async exit(){return await this.tool.terminateApp(this.package),await this.tool.shutdownDevice(),Promise.resolve(!0)}async captureScreenshot(){return Promise.resolve(await this.tool.getScreenshot())}exists(t){return h.default.existsSync(t)?Promise.resolve(!0):Promise.reject(Error(`${t} not exists`))}pushFile(t,e){return Promise.resolve(h.default.copySync(t,e))}get DIR_WWW(){return`${this.sdcard}/Documents/Pandora/apps/${this.appid}/www/`}}const H=w("adbkit"),x=u.default("automator:adb");class D extends E{async init(){if(this.tool=H.createClient(),!this.id){const t=await this.tool.listDevices();if(!t.length)throw Error("Device not found");this.id=t[0].id}this.sdcard=(await this.shell(this.COMMAND_EXTERNAL)).trim(),x(`${p.default("yyyy-mm-dd HH:MM:ss:l")} init ${this.id} ${this.sdcard}`)}version(){return this.shell(this.COMMAND_VERSION).then((t=>{const e=t.match(/versionName=(.*)/);return e&&e.length>1?e[1]:""}))}formatVersion(t){return t}async install(){let t=!0;try{const e=(await this.tool.getProperties(this.id))["ro.build.version.release"].split(".")[0];parseInt(e)<6&&(t=!1)}catch(t){}if(x(`${p.default("yyyy-mm-dd HH:MM:ss:l")} install ${this.app} permission=${t}`),t){const t=w("adbkit/lib/adb/command.js"),e=t.prototype._send;t.prototype._send=function(t){return 0===t.indexOf("shell:pm install -r ")&&(t=t.replace("shell:pm install -r ","shell:pm install -r -g "),x(`${p.default("yyyy-mm-dd HH:MM:ss:l")} ${t} `)),e.call(this,t)}}return this.tool.install(this.id,this.app).then((()=>this.init()))}start(){return this.exit().then((()=>this.shell(this.COMMAND_START)))}exit(){return this.shell(this.COMMAND_STOP)}captureScreenshot(){return this.tool.screencap(this.id).then((t=>new Promise((e=>{const s=[];t.on("data",(function(t){s.push(t)})),t.on("end",(function(){e(Buffer.concat(s).toString("base64"))}))}))))}exists(t){return this.tool.stat(this.id,t)}pushFile(t,e){return this.tool.push(this.id,t,e)}shell(t){return x(`${p.default("yyyy-mm-dd HH:MM:ss:l")} SEND ► ${t}`),this.tool.shell(this.id,t).then(H.util.readAll).then((t=>{const e=t.toString();return x(`${p.default("yyyy-mm-dd HH:MM:ss:l")} ◀ RECV ${e}`),e}))}get DIR_WWW(){return`/storage/emulated/0/Android/data/${this.package}/apps/${this.appid}/www`}get COMMAND_EXTERNAL(){return"echo $EXTERNAL_STORAGE"}get COMMAND_VERSION(){return`dumpsys package ${this.package}`}get COMMAND_STOP(){return`am force-stop ${this.package}`}get COMMAND_START(){return`am start -n ${this.package}/io.dcloud.PandoraEntry --es ${this.appid} --ez needUpdateApp false --ez reload true`}}const b=u.default("automator:devtool");let C,I=!1;const N={android:/android_version=(.*)/,ios:/iphone_version=(.*)/};const R={"Tool.close":{reflect:async()=>{}},"App.exit":{reflect:async()=>C.exit()},"App.enableLog":{reflect:()=>Promise.resolve()},"App.captureScreenshot":{reflect:async(t,e)=>{const s=await C.captureScreenshot(e);return b(`App.captureScreenshot ${s.length}`),{data:s}}}};!function(t){f.forEach((e=>{t[e]=function(t){return{reflect:async(e,s)=>e(t,s,!1),params:t=>(t.selector&&(t.selector=d.default(y).processSync(t.selector)),t)}}(e)}))}(R);const O={devtools:{name:"App",paths:[],required:["manifest.json","app-service.js"],validate:async function(t,e){t.platform=(t.platform||process.env.UNI_OS_NAME).toLocaleLowerCase(),Object.assign(t,t[t.platform]),C=function(t,e){return"ios"===t?new _(e):new D(e)}(t.platform,t),await C.init();const s=await C.version();if(s){if(t.version){const e=C.formatVersion(function(t,e){if(t.endsWith(".txt"))try{const s=c.default.readFileSync(t).toString().match(N[e]);if(s)return s[1]}catch(t){console.error(t)}return t}(t.version,t.platform));b(`version: ${s}`),b(`newVersion: ${e}`),e!==s&&(I=!0)}}else I=!0;if(I){if(!t.executablePath)throw Error(`app-plus->${t.platform}->executablePath is not provided`);if(!c.default.existsSync(t.executablePath))throw Error(`${t.executablePath} not exists`)}return t},create:async function(t,e,s){I&&await C.install(),(I||s.compiled||await C.shouldPush())&&(await C.push(t),await m.default(1e3)),await C.start()}},adapter:R};module.exports=O; "use strict";var t=require("fs"),e=require("debug"),s=require("licia/sleep"),i=require("postcss-selector-parser"),r=require("fs-extra"),a=require("licia/dateFormat"),n=require("path"),o=require("util");function l(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var c=l(t),u=l(e),d=l(s),h=l(i),p=l(r),m=l(a);function y(t){t.walk((t=>{if("tag"===t.type){const e=t.value;t.value="page"===e?"body":"uni-"+e}}))}u.default("automator:devtool");const f=["Page.getElement","Page.getElements","Element.getElement","Element.getElements"];const v=/^win/.test(process.platform);function g(t){try{return require(t)}catch(e){return require(require.resolve(t,{paths:[process.cwd()]}))}}const w=u.default("automator:launcher"),$=o.promisify(c.default.readdir),M=o.promisify(c.default.stat);async function P(t){const e=await $(t);return(await Promise.all(e.map((async e=>{const s=n.resolve(t,e);return(await M(s)).isDirectory()?P(s):s})))).reduce(((t,e)=>t.concat(e)),[])}class E{constructor(t){this.isX=!1,"true"===process.env.UNI_APP_X&&(this.isX=!0),this.id=t.id,this.app=t.executablePath,this.appid=t.appid||process.env.UNI_APP_ID||"HBuilder",this.package=t.package||(this.isX?"io.dcloud.uniappx":"io.dcloud.HBuilder"),this.activity=t.activity||(this.isX?"io.dcloud.uniapp.UniAppActivity":"io.dcloud.PandoraEntry")}shouldPush(){return this.exists(this.FILE_APP_SERVICE).then((()=>(w(`${m.default("yyyy-mm-dd HH:MM:ss:l")} ${this.FILE_APP_SERVICE} exists`),!1))).catch((()=>(w(`${m.default("yyyy-mm-dd HH:MM:ss:l")} ${this.FILE_APP_SERVICE} not exists`),!0)))}push(t){return P(t).then((e=>{const s=e.map((e=>{const s=(t=>v?t.replace(/\\/g,"/"):t)(n.join(this.DIR_WWW,n.relative(t,e)));return w(`${m.default("yyyy-mm-dd HH:MM:ss:l")} push ${e} ${s}`),this.pushFile(e,s)}));return Promise.all(s)})).then((t=>!0))}get FILE_APP_SERVICE(){return`${this.DIR_WWW}/app-service.js`}}const A=u.default("automator:simctl");function _(t){const e=parseInt(t);return e>9?String(e):"0"+e}class S extends E{constructor(){super(...arguments),this.bundleVersion=""}async init(){const t=g("node-simctl").Simctl;this.tool=new t({udid:this.id});try{await this.tool.bootDevice()}catch(t){}await this.initSDCard(),A(`${m.default("yyyy-mm-dd HH:MM:ss:l")} init ${this.id}`)}async initSDCard(){const t=await this.tool.appInfo(this.package);A(`${m.default("yyyy-mm-dd HH:MM:ss:l")} appInfo ${t}`);const e=t.match(/DataContainer\s+=\s+"(.*)"/);if(!e)return Promise.resolve("");const s=t.match(/CFBundleVersion\s+=\s+(.*);/);if(!s)return Promise.resolve("");this.sdcard=e[1].replace("file:",""),this.bundleVersion=s[1],A(`${m.default("yyyy-mm-dd HH:MM:ss:l")} install ${this.sdcard}`)}async version(){return Promise.resolve(this.bundleVersion)}formatVersion(t){const e=t.split(".");return 3!==e.length?t:e[0]+_(e[1])+_(e[2])}async install(){return A(`${m.default("yyyy-mm-dd HH:MM:ss:l")} install ${this.app}`),await this.tool.installApp(this.app),await this.tool.grantPermission(this.package,"all"),await this.initSDCard(),Promise.resolve(!0)}async start(){try{await this.tool.terminateApp(this.package)}catch(t){}try{await this.tool.launchApp(this.package)}catch(t){console.error(t)}return Promise.resolve(!0)}async exit(){return await this.tool.terminateApp(this.package),await this.tool.shutdownDevice(),Promise.resolve(!0)}async captureScreenshot(){return Promise.resolve(await this.tool.getScreenshot())}exists(t){return p.default.existsSync(t)?Promise.resolve(!0):Promise.reject(Error(`${t} not exists`))}pushFile(t,e){return Promise.resolve(p.default.copySync(t,e))}get DIR_WWW(){return`${this.sdcard}/Documents/Pandora/apps/${this.appid}/www/`}}const x=g("adbkit"),H=u.default("automator:adb");class D extends E{async init(){if(this.tool=x.createClient(),!this.id){const t=await this.tool.listDevices();if(!t.length)throw Error("Device not found");this.id=t[0].id}this.sdcard=(await this.shell(this.COMMAND_EXTERNAL)).trim(),H(`${m.default("yyyy-mm-dd HH:MM:ss:l")} init ${this.id} ${this.sdcard}`)}version(){return this.shell(this.COMMAND_VERSION).then((t=>{const e=t.match(/versionName=(.*)/);return e&&e.length>1?e[1]:""}))}formatVersion(t){return t}async install(){let t=!0;try{const e=(await this.tool.getProperties(this.id))["ro.build.version.release"].split(".")[0];parseInt(e)<6&&(t=!1)}catch(t){}if(H(`${m.default("yyyy-mm-dd HH:MM:ss:l")} install ${this.app} permission=${t}`),t){const t=g("adbkit/lib/adb/command.js"),e=t.prototype._send;t.prototype._send=function(t){return 0===t.indexOf("shell:pm install -r ")&&(t=t.replace("shell:pm install -r ","shell:pm install -r -g "),H(`${m.default("yyyy-mm-dd HH:MM:ss:l")} ${t} `)),e.call(this,t)}}return this.tool.install(this.id,this.app).then((()=>this.init()))}start(){return this.exit().then((()=>this.shell(this.COMMAND_START)))}exit(){return this.shell(this.COMMAND_STOP)}captureScreenshot(){return this.tool.screencap(this.id).then((t=>new Promise((e=>{const s=[];t.on("data",(function(t){s.push(t)})),t.on("end",(function(){e(Buffer.concat(s).toString("base64"))}))}))))}exists(t){return this.tool.stat(this.id,t)}pushFile(t,e){return this.tool.push(this.id,t,e)}shell(t){return H(`${m.default("yyyy-mm-dd HH:MM:ss:l")} SEND ► ${t}`),this.tool.shell(this.id,t).then(x.util.readAll).then((t=>{const e=t.toString();return H(`${m.default("yyyy-mm-dd HH:MM:ss:l")} ◀ RECV ${e}`),e}))}get DIR_WWW(){return`/storage/emulated/0/Android/data/${this.package}/apps/${this.appid}/www`}get COMMAND_EXTERNAL(){return"echo $EXTERNAL_STORAGE"}get COMMAND_VERSION(){return`dumpsys package ${this.package}`}get COMMAND_STOP(){return`am force-stop ${this.package}`}get COMMAND_START(){return`am start -n ${this.package}/${this.activity} --es ${this.appid} --ez needUpdateApp false --ez reload true`}}const I=u.default("automator:devtool");let b,C=!1;const N={android:/android_version=(.*)/,ios:/iphone_version=(.*)/};const R={"Tool.close":{reflect:async()=>{}},"App.exit":{reflect:async()=>b.exit()},"App.enableLog":{reflect:()=>Promise.resolve()}};!function(t){f.forEach((e=>{t[e]=function(t){return{reflect:async(e,s)=>e(t,s,!1),params:t=>(t.selector&&(t.selector=h.default(y).processSync(t.selector)),t)}}(e)}))}(R);const O={devtools:{name:"App",paths:[],required:["manifest.json","app-service.js"],validate:async function(t,e){t.platform=(t.platform||process.env.UNI_OS_NAME).toLocaleLowerCase(),Object.assign(t,t[t.platform]),b=function(t,e){return"ios"===t?new S(e):new D(e)}(t.platform,t),await b.init();const s=await b.version();if(s){if(t.version){const e=b.formatVersion(function(t,e){if(t.endsWith(".txt"))try{const s=c.default.readFileSync(t).toString().match(N[e]);if(s)return s[1]}catch(t){console.error(t)}return t}(t.version,t.platform));I(`version: ${s}`),I(`newVersion: ${e}`),e!==s&&(C=!0)}}else C=!0;if(C){if(!t.executablePath)throw Error(`app-plus->${t.platform}->executablePath is not provided`);if(!c.default.existsSync(t.executablePath))throw Error(`${t.executablePath} not exists`)}return t},create:async function(t,e,s){C&&await b.install(),(C||s.compiled||await b.shouldPush())&&(await b.push(t),await d.default(1e3)),await b.start()}},adapter:R};module.exports=O;
{ {
"name": "@dcloudio/uni-app-plus", "name": "@dcloudio/uni-app-plus",
"version": "3.0.0-alpha-3071220230324001", "version": "3.0.0-alpha-3080220230428002",
"description": "@dcloudio/uni-app-plus", "description": "@dcloudio/uni-app-plus",
"files": [ "files": [
"dist", "dist",
...@@ -29,20 +29,20 @@ ...@@ -29,20 +29,20 @@
"main": "dist/uni.compiler.js" "main": "dist/uni.compiler.js"
}, },
"dependencies": { "dependencies": {
"@dcloudio/uni-app-uts": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-app-uts": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-app-vite": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-app-vite": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-app-vue": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-app-vue": "3.0.0-alpha-3080220230428002",
"debug": "^4.3.3", "debug": "^4.3.3",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"licia": "^1.29.0", "licia": "^1.29.0",
"postcss-selector-parser": "^6.0.6" "postcss-selector-parser": "^6.0.6"
}, },
"devDependencies": { "devDependencies": {
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-cli-shared": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-components": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-components": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-h5": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-h5": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-i18n": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-i18n": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-shared": "3.0.0-alpha-3071220230324001", "@dcloudio/uni-shared": "3.0.0-alpha-3080220230428002",
"@types/pako": "1.0.2", "@types/pako": "1.0.2",
"@vue/compiler-sfc": "3.2.47", "@vue/compiler-sfc": "3.2.47",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
...@@ -50,4 +50,4 @@ ...@@ -50,4 +50,4 @@
"postcss": "^8.4.21", "postcss": "^8.4.21",
"vue": "3.2.47" "vue": "3.2.47"
} }
} }
\ No newline at end of file
import appVite from '@dcloudio/uni-app-vite' import appVite from '@dcloudio/uni-app-vite'
import appUVue from '@dcloudio/uni-app-uts' import appUVue from '@dcloudio/uni-app-uts'
export default [process.env.UNI_UVUE === 'true' ? appUVue : appVite] export default [process.env.UNI_APP_X === 'true' ? appUVue : appVite]
...@@ -207,7 +207,7 @@ export const startSoterAuthentication = ...@@ -207,7 +207,7 @@ export const startSoterAuthentication =
4: () => { 4: () => {
if (waiting) { if (waiting) {
clearTimeout(waitingTimer) clearTimeout(waitingTimer)
waiting.setTitle('无法识别') waiting.setTitle(t('uni.startSoterAuthentication.waitingContent'))
waitingTimer = setTimeout(() => { waitingTimer = setTimeout(() => {
waiting && waiting.setTitle(authenticateMessage) waiting && waiting.setTitle(authenticateMessage)
}, 1000) }, 1000)
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`stringifyMap basic 1`] = `"new Map<string, any>([["color","#7A7E83"],["selectedColor","#3cc51f"],["borderStyle","black"],["backgroundColor","#ffffff"],["list",[new Map<string, any>([["pagePath","pages/component/index"],["iconPath","static/image/icon_component.png"],["selectedIconPath","static/image/icon_component_HL.png"],["text","组件"]]),new Map<string, any>([["pagePath","pages/API/index"],["iconPath","static/image/icon_API.png"],["selectedIconPath","static/image/icon_API_HL.png"],["text","接口"]])]]])"`;
...@@ -5,13 +5,17 @@ describe('compiler:codegen', () => { ...@@ -5,13 +5,17 @@ describe('compiler:codegen', () => {
assert(`<view/>`, `createElementVNode("view")`) assert(`<view/>`, `createElementVNode("view")`)
assert( assert(
`<view style="width:100px;height:100px;"/>`, `<view style="width:100px;height:100px;"/>`,
`createElementVNode("view", new Map<string,any>([["style", "width:100px;height:100px;"]]))` `createElementVNode("view", new Map<string, any | null>([["style", "width:100px;height:100px;"]]))`
)
assert(
`<text>{{msg}}</text>`,
`createElementVNode("text", null, toDisplayString(_ctx.msg), 1 /* TEXT */)`
) )
}) })
test(`function:kotlin`, () => { test(`function:kotlin`, () => {
assert( assert(
`<view/>`, `<view/>`,
`@Suppress("UNUSED_PARAMETER") function PagesIndexIndexRender(ctx: PagesIndexIndex): VNode | null {\n return createElementVNode("view")\n}`, `@Suppress("UNUSED_PARAMETER") function PagesIndexIndexRender(_ctx: PagesIndexIndex): VNode | null {\n return createElementVNode("view")\n}`,
{ {
targetLanguage: 'kotlin', targetLanguage: 'kotlin',
mode: 'function', mode: 'function',
......
import { stringifyMap } from '../src/plugins/utils'
describe('stringifyMap', () => {
test(`basic`, () => {
expect(
stringifyMap({
color: '#7A7E83',
selectedColor: '#3cc51f',
borderStyle: 'black',
backgroundColor: '#ffffff',
list: [
{
pagePath: 'pages/component/index',
iconPath: 'static/image/icon_component.png',
selectedIconPath: 'static/image/icon_component_HL.png',
text: '组件',
},
{
pagePath: 'pages/API/index',
iconPath: 'static/image/icon_API.png',
selectedIconPath: 'static/image/icon_API_HL.png',
text: '接口',
},
],
})
).toMatchSnapshot()
})
})
import { compile } from '../src/plugins/uvue/code/template/compiler/index' import { compile } from '../src/plugins/uvue/compiler/index'
import { CompilerOptions } from '../src/plugins/uvue/code/template/compiler/options' import { CompilerOptions } from '../src/plugins/uvue/compiler/options'
export function assert( export function assert(
template: string, template: string,
......
import { assert } from '../testUtils'
describe('compiler: component', () => {
test('template component', () => {
assert(
`<view><Foo /></view>`,
`@Suppress("UNUSED_PARAMETER") function PagesIndexIndexRender(_ctx: PagesIndexIndex): VNode | null {
const _component_Foo = resolveComponent("Foo")
return createElementVNode("view", null, [
createVNode(_component_Foo)
])
}`,
{
targetLanguage: 'kotlin',
mode: 'function',
}
)
})
})
import { assert } from '../testUtils'
describe('compiler: transform interpolation', () => {
test('transform interpolation', () => {
assert(`<text>foo</text>`, `createElementVNode("text", null, "foo")`)
assert(
`<text>{{ foo }} bar {{ baz }}</text>`,
`createElementVNode("text", null, toDisplayString(_ctx.foo) + " bar " + toDisplayString(_ctx.baz), 1 /* TEXT */)`
)
})
})
import { assert } from '../testUtils'
describe('compiler: transform tap to click', () => {
test('transform tap to click', () => {
assert(
`<text @click="click">hello</text>`,
`createElementVNode("text", new Map<string, any | null>([["onClick", _ctx.click]]), "hello", 8 /* PROPS */, ["onClick"])`
)
assert(
`<text @tap="click">hello</text>`,
`createElementVNode("text", new Map<string, any | null>([["onClick", _ctx.click]]), "hello", 8 /* PROPS */, ["onClick"])`
)
assert(
`<view @tap="click">hello</view>`,
`createElementVNode("view", new Map<string, any | null>([["onClick", _ctx.click]]), [
createElementVNode("text", null, "hello")
], 8 /* PROPS */, ["onClick"])`
)
assert(
`<button @tap="click">hello</button>`,
`createVNode(_component_button, new Map<string, any | null>([["onClick", _ctx.click]]), new Map<string, any | null>([
["default", ((): any[] => ["hello"])],
["_", 1 /* STABLE */]
]), 8 /* PROPS */, ["onClick"])`
)
})
})
import { assert } from '../testUtils'
describe('compiler: transform text', () => {
test('transform text', () => {
assert(`<text>hello</text>`, `createElementVNode("text", null, "hello")`)
assert(
`<view>hello</view>`,
`createElementVNode("view", null, [
createElementVNode("text", null, "hello")
])`
)
assert(
`<view><text>hello</text></view>`,
`createElementVNode("view", null, [
createElementVNode("text", null, "hello")
])`
)
assert(
`<view>aaa{{bbb}}ccc</view>`,
`createElementVNode("view", null, [
createElementVNode("text", null, "aaa" + toDisplayString(_ctx.bbb) + "ccc", 1 /* TEXT */)
])`
)
assert(
`<view>aaa{{bbb}}<view>ccc{{ddd}}</view>{{eee}}fff<text>{{ggg}}</text></view>`,
`createElementVNode("view", null, [
createElementVNode("text", null, "aaa" + toDisplayString(_ctx.bbb), 1 /* TEXT */),
createElementVNode("view", null, [
createElementVNode("text", null, "ccc" + toDisplayString(_ctx.ddd), 1 /* TEXT */)
]),
createElementVNode("text", null, toDisplayString(_ctx.eee) + "fff", 1 /* TEXT */),
createElementVNode("text", null, toDisplayString(_ctx.ggg), 1 /* TEXT */)
])`
)
})
})
import { assert } from '../testUtils'
describe('compiler:v-bind', () => {
test('basic', () => {
assert(
`<view v-bind:id="id"/>`,
`createElementVNode("view", new Map<string, any | null>([["id", _ctx.id]]), null, 8 /* PROPS */, ["id"])`
)
})
test('dynamic arg', () => {
assert(
`<view v-bind:[id]="id"/>`,
`createElementVNode("view", normalizeProps(new Map<string, any | null>([[_ctx.id !== null ? _ctx.id : \"\", _ctx.id]])), null, 16 /* FULL_PROPS */)`
)
})
test('.camel modifier', () => {
assert(
`<view v-bind:foo-bar.camel="id"/>`,
`createElementVNode(\"view\", new Map<string, any | null>([[\"fooBar\", _ctx.id]]), null, 8 /* PROPS */, [\"fooBar\"])`
)
})
test('.camel modifier w/ dynamic arg', () => {
assert(
`<view v-bind:[foo].camel="id"/>`,
`createElementVNode(\"view\", normalizeProps(new Map<string, any | null>([[camelize(_ctx.foo !== null ? _ctx.foo : \"\"), _ctx.id]])), null, 16 /* FULL_PROPS */)`
)
})
test('.prop modifier', () => {
assert(
`<view v-bind:className.prop="className"/>`,
`createElementVNode(\"view\", new Map<string, any | null>([[\".className\", _ctx.className]]), null, 8 /* PROPS */, [\".className\"])`
)
})
test('.prop modifier w/ dynamic arg', () => {
assert(
`<view v-bind:[fooBar].prop="className"/>`,
'createElementVNode("view", normalizeProps(new Map<string, any | null>([[`.${_ctx.fooBar !== null ? _ctx.fooBar : ""}`, _ctx.className]])), null, 16 /* FULL_PROPS */)'
)
})
test('.prop modifier (shorthand)', () => {
assert(
`<view .className="className"/>`,
'createElementVNode("view", new Map<string, any | null>([[".className", _ctx.className]]), null, 8 /* PROPS */, [".className"])'
)
})
test('.attr modifier', () => {
assert(
`<view v-bind:foo-bar.attr="id"/>`,
'createElementVNode("view", new Map<string, any | null>([["^foo-bar", _ctx.id]]), null, 8 /* PROPS */, ["^foo-bar"])'
)
})
test('simple expression', () => {
assert(
`<view v-bind:class="{'box': true}"></view>`,
`createElementVNode("view", new Map<string, any | null>([
["class", normalizeClass(new Map<string, any | null>([['box', true]]))]
]))`
)
})
test('simple expression with array', () => {
assert(
`<view v-bind:class="[classA, {classB: true, classC: false}]"></view>`,
`createElementVNode("view", new Map<string, any | null>([
["class", normalizeClass([_ctx.classA, new Map<string, any | null>([[classB, true],[ classC, false]])])]
]), null, 2 /* CLASS */)`
)
})
test('simple expression with object', () => {
assert(
`<view :style="{color: true ? 'blue' : 'red'}"></view>`,
"createElementVNode(\"view\", new Map<string, any | null>([[\"style\", new Map<string, any | null>([['color', true ? 'blue' : 'red']])]]))"
)
})
})
import { assert } from '../testUtils'
describe('compiler:v-for', () => {
test('number expression', () => {
assert(
`<text v-for="item in 10" :key="item" />`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(10, (item, _, _):VNode => {
return createElementVNode("text", new Map<string, any | null>([["key", item]]))
}), 64 /* STABLE_FRAGMENT */)`
)
})
test('value', () => {
assert(
`<text v-for="(item) in items" />`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (item, _, _):VNode => {
return createElementVNode("text")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('value and key', () => {
assert(
`<text v-for="(item, index) in [1,2,3]" :key="index" />`,
`createElementVNode(Fragment, null, RenderHelpers.renderList([1,2,3], (item, index, _):VNode => {
return createElementVNode("text", new Map<string, any | null>([["key", index]]))
}), 64 /* STABLE_FRAGMENT */)`
)
})
test('value, key and index', () => {
assert(
`<text v-for="(item, key, index) in {a:'a',b:'b'}" :key="index" />`,
`createElementVNode(Fragment, null, RenderHelpers.renderList({a:'a',b:'b'}, (item, key, index):VNode => {
return createElementVNode("text", new Map<string, any | null>([["key", index]]))
}), 64 /* STABLE_FRAGMENT */)`
)
})
test('array de-structured value', () => {
assert(
'<text v-for="([ id, value ]) in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, ([ id, value ], _, _):VNode => {
return createElementVNode(\"text\")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('object de-structured value', () => {
assert(
'<text v-for="({ id, value }) in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, ({ id, value }, _, _):VNode => {
return createElementVNode("text")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('skipped value', () => {
assert(
'<text v-for="(,key,index) in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (_, key, index):VNode => {
return createElementVNode("text")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('skipped key', () => {
assert(
'<text v-for="(value,,index) in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (value, _, index):VNode => {
return createElementVNode("text")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('skipped value & key', () => {
assert(
'<text v-for="(,,index) in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (_, _, index):VNode => {
return createElementVNode("text")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('skipped value and key', () => {
assert(
'<text v-for="(,,index) in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (_, _, index):VNode => {
return createElementVNode("text")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('unbracketed value', () => {
assert(
'<text v-for="item in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (item, _, _):VNode => {
return createElementVNode(\"text\")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('unbracketed value and key', () => {
assert(
'<text v-for="item, key in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (item, key, _):VNode => {
return createElementVNode(\"text\")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('unbracketed value and key', () => {
assert(
'<text v-for="value, key, index in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (value, key, index):VNode => {
return createElementVNode(\"text\")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('unbracketed skipped key', () => {
assert(
'<text v-for="value, , index in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (value, _, index):VNode => {
return createElementVNode(\"text\")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('unbracketed skipped value and key', () => {
assert(
'<text v-for=", , index in items" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (_, _, index):VNode => {
return createElementVNode(\"text\")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('source complex expression', () => {
assert(
'<text v-for="i in list.concat([foo])" />',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.list.concat([_ctx.foo]), (i, _, _):VNode => {
return createElementVNode(\"text\")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('should not prefix v-for alias', () => {
assert(
'<text v-for="i in list">{{ i }}{{ j }}</text>',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.list, (i, _, _):VNode => {
return createElementVNode("text", null, toDisplayString(i) + toDisplayString(_ctx.j), 1 /* TEXT */)
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('should not prefix v-for aliases (multiple)', () => {
assert(
'<text v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</text>',
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.list, (i, j, k):VNode => {
return createElementVNode("text", null, toDisplayString(i + j + k) + toDisplayString(_ctx.l), 1 /* TEXT */)
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('should prefix id outside of v-for', () => {
assert(
'<text><text v-for="i in list" />{{ i }}</text>',
`createElementVNode("text", null, [
createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.list, (i, _, _):VNode => {
return createElementVNode("text")
}), 256 /* UNKEYED_FRAGMENT */),
toDisplayString(_ctx.i)
])`
)
})
test('nested v-for', () => {
assert(
`<view v-for="i in list">
<text v-for="i in list">{{ i + j }}</text>{{ i }}
</view>`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.list, (i, _, _):VNode => {
return createElementVNode(\"view\", null, [
createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.list, (i, _, _):VNode => {
return createElementVNode(\"text\", null, toDisplayString(i + _ctx.j), 1 /* TEXT */)
}), 256 /* UNKEYED_FRAGMENT */),
createElementVNode("text", null, toDisplayString(i), 1 /* TEXT */)
])
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('v-for aliases w/ complex expressions', () => {
assert(
`<text v-for="({ foo = bar, baz: [qux = quux] }) in list">
{{ foo + bar + baz + qux + quux }}
</text>`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.list, ({ foo = _ctx.bar, baz: [qux = _ctx.quux] }, _, _):VNode => {
return createElementVNode(\"text\", null, toDisplayString(foo + _ctx.bar + _ctx.baz + qux + _ctx.quux), 1 /* TEXT */)
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('element v-for key expression prefixing', () => {
assert(
`<text v-for="item in items" :key="itemKey(item)">test</text>`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (item, _, _):VNode => {
return createElementVNode(\"text\", new Map<string, any | null>([
[\"key\", _ctx.itemKey(item)]
]), \"test\")
}), 128 /* KEYED_FRAGMENT */)`
)
})
test('template v-for key no prefixing on attribute key', () => {
assert(
`<template v-for="item in items" key="key">test</template>`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (item, _, _):VNode => {
return createElementVNode(Fragment, new Map<string, any | null>([[\"key\", \"key\"]]), [\"test\"], 64 /* STABLE_FRAGMENT */)
}), 128 /* KEYED_FRAGMENT */)`
)
})
test('template v-for key injection with single child', () => {
assert(
`<template v-for="item in items" :key="item.id"><text :id="item.id" /></template>`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (item, _, _):VNode => {
return createElementVNode(\"text\", new Map<string, any | null>([
[\"key\", item.id],
[\"id\", item.id]
]), null, 8 /* PROPS */, [\"id\"])
}), 128 /* KEYED_FRAGMENT */)`
)
})
test('v-for on <slot/>', () => {
assert(
`<slot v-for="item in items"></slot>`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (item, _, _):VNode => {
return renderSlot(_ctx.$slots, \"default\")
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
test('keyed v-for', () => {
assert(
`<text v-for="(item) in items" :key="item" />`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (item, _, _):VNode => {
return createElementVNode(\"text\", new Map<string, any | null>([[\"key\", item]]))
}), 128 /* KEYED_FRAGMENT */)`
)
})
test('keyed template v-for', () => {
assert(
`<template v-for="(item) in items" :key="item"><text/></template>`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.items, (item, _, _):VNode => {
return createElementVNode(\"text\", new Map<string, any | null>([[\"key\", item]]))
}), 128 /* KEYED_FRAGMENT */)`
)
})
test('v-if + v-for', () => {
assert(
`<view v-if="ok" v-for="i in list"/>`,
`isTrue(_ctx.ok)
? createElementVNode(Fragment, new Map<string, any | null>([[\"key\", 0]]), RenderHelpers.renderList(_ctx.list, (i, _, _):VNode => {
return createElementVNode(\"view\")
}), 256 /* UNKEYED_FRAGMENT */)
: createCommentVNode(\"v-if\", true)`
)
})
test('v-if + v-for on <template>', () => {
assert(
`<template v-if="ok" v-for="i in list"/>`,
`isTrue(_ctx.ok)
? createElementVNode(Fragment, new Map<string, any | null>([[\"key\", 0]]), RenderHelpers.renderList(_ctx.list, (i, _, _):VNode => {
return createElementVNode(Fragment, null, [], 64 /* STABLE_FRAGMENT */)
}), 256 /* UNKEYED_FRAGMENT */)
: createCommentVNode(\"v-if\", true)`
)
})
test('v-for on element with custom directive', () => {
assert(
`<view v-for="i in list" v-foo/>`,
`createElementVNode(Fragment, null, RenderHelpers.renderList(_ctx.list, (i, _, _):VNode => {
return withDirectives(createElementVNode(\"view\", null, null, 512 /* NEED_PATCH */), [
[_directive_foo]
])
}), 256 /* UNKEYED_FRAGMENT */)`
)
})
})
import { assert } from '../testUtils'
describe('compiler:v-if', () => {
test('basic v-if', () => {
assert(
`<view v-if="ok"/>`,
`isTrue(_ctx.ok)\n ? createElementVNode("view", new Map<string, any | null>([["key", 0]]))\n : createCommentVNode("v-if", true)`
)
})
test('template v-if', () => {
assert(
`<template v-if="ok"><view/>hello<text/></template>`,
`isTrue(_ctx.ok)
? createElementVNode(Fragment, new Map<string, any | null>([[\"key\", 0]]), [
createElementVNode(\"view\"),
\"hello\",
createElementVNode(\"text\")
], 64 /* STABLE_FRAGMENT */)
: createCommentVNode(\"v-if\", true)`
)
})
test('component v-if', () => {
assert(
`<Component v-if="ok"></Component>`,
`isTrue(_ctx.ok)
? createVNode(_component_Component, new Map<string, any | null>([[\"key\", 0]]))
: createCommentVNode(\"v-if\", true)`
)
})
test('v-if + v-else', () => {
assert(
`
<view>
<text v-if="ok"/>
<text v-else/>
</view>
`,
`createElementVNode("view", null, [
isTrue(_ctx.ok)
? createElementVNode("text", new Map<string, any | null>([["key", 0]]))
: createElementVNode("text", new Map<string, any | null>([["key", 1]]))
])`
)
})
test('v-if + v-else-if', () => {
assert(
`
<view>
<text v-if="ok"/>
<text v-else-if="orNot"/>
<text v-else/>
</view>
`,
`createElementVNode("view", null, [
isTrue(_ctx.ok)
? createElementVNode("text", new Map<string, any | null>([["key", 0]]))
: isTrue(_ctx.orNot)
? createElementVNode("text", new Map<string, any | null>([["key", 1]]))
: createElementVNode("text", new Map<string, any | null>([["key", 2]]))
])`
)
})
test('v-if + v-else-if + v-else', () => {
assert(
`
<view>
<text v-if="ok"/>
<text v-else-if="orNot">v-else-if</text>
<text v-else>v-else</text>
</view>
`,
`createElementVNode("view", null, [
isTrue(_ctx.ok)
? createElementVNode("text", new Map<string, any | null>([["key", 0]]))
: isTrue(_ctx.orNot)
? createElementVNode("text", new Map<string, any | null>([["key", 1]]), "v-else-if")
: createElementVNode("text", new Map<string, any | null>([["key", 2]]), "v-else")
])`
)
})
test('comment between branches', () => {
assert(
`
<view v-if="ok"/>
<!--foo-->
<view v-else-if="orNot"/>
<!--bar-->
<text v-else>v-else</text>
`,
`isTrue(_ctx.ok)
? createElementVNode(\"view\", new Map<string, any | null>([[\"key\", 0]]))
: isTrue(_ctx.orNot)
? createElementVNode(\"view\", new Map<string, any | null>([[\"key\", 1]]))
: createElementVNode(\"text\", new Map<string, any | null>([[\"key\", 2]]), \"v-else\")`
)
})
test('template v-if w/ single <slot/> child', () => {
assert(
`<template v-if="ok"><slot/></template>`,
`isTrue(_ctx.ok)
? renderSlot(_ctx.$slots, \"default\", new Map<string, any | null>([[\"key\", 0]]))
: createCommentVNode(\"v-if\", true)`
)
})
test('v-if on <slot/>', () => {
assert(
`<slot v-if="ok"></slot>`,
`isTrue(_ctx.ok)
? renderSlot(_ctx.$slots, \"default\", new Map<string, any | null>([[\"key\", 0]]))
: createCommentVNode(\"v-if\", true)`
)
})
test('multiple v-if that are sibling nodes should have different keys', () => {
assert(
`<view><view v-if="ok"/><view v-if="orNot"/></view>`,
`createElementVNode("view", null, [
isTrue(_ctx.ok)
? createElementVNode("view", new Map<string, any | null>([["key", 0]]))
: createCommentVNode("v-if", true),
isTrue(_ctx.orNot)
? createElementVNode("view", new Map<string, any | null>([["key", 1]]))
: createCommentVNode("v-if", true)
])`
)
})
test('increasing key: v-if + v-else-if + v-else', () => {
assert(
`<view><view v-if="ok"/><view v-else/><view v-if="another"/><view v-else-if="orNot"/><view v-else/></view>`,
`createElementVNode("view", null, [
isTrue(_ctx.ok)
? createElementVNode("view", new Map<string, any | null>([["key", 0]]))
: createElementVNode("view", new Map<string, any | null>([["key", 1]])),
isTrue(_ctx.another)
? createElementVNode("view", new Map<string, any | null>([["key", 2]]))
: isTrue(_ctx.orNot)
? createElementVNode("view", new Map<string, any | null>([["key", 3]]))
: createElementVNode("view", new Map<string, any | null>([["key", 4]]))
])`
)
})
test('key injection (only v-bind)', () => {
assert(
`<view v-if="ok" v-bind="obj"/>`,
`isTrue(_ctx.ok)
? createElementVNode(\"view\", normalizeProps(mergeProps(new Map<string, any | null>([[\"key\", 0]]), _ctx.obj)), null, 16 /* FULL_PROPS */)
: createCommentVNode(\"v-if\", true)`
)
})
test('key injection (before v-bind)', () => {
assert(
`<view v-if="ok" id="foo" v-bind="obj"/>`,
`isTrue(_ctx.ok)
? createElementVNode(\"view\", mergeProps(new Map<string, any | null>([
[\"key\", 0],
[\"id\", \"foo\"]
]), _ctx.obj), null, 16 /* FULL_PROPS */)
: createCommentVNode(\"v-if\", true)`
)
})
test('key injection (after v-bind)', () => {
assert(
`<view v-if="ok" v-bind="obj" id="foo"/>`,
`isTrue(_ctx.ok)
? createElementVNode(\"view\", mergeProps(new Map<string, any | null>([[\"key\", 0]]), _ctx.obj, new Map<string, any | null>([[\"id\", \"foo\"]])), null, 16 /* FULL_PROPS */)
: createCommentVNode(\"v-if\", true)`
)
})
test('avoid duplicate keys', () => {
assert(
`<view v-if="ok" key="custom_key" v-bind="obj"/>`,
`isTrue(_ctx.ok)
? createElementVNode(\"view\", mergeProps(new Map<string, any | null>([[\"key\", \"custom_key\"]]), _ctx.obj), null, 16 /* FULL_PROPS */)
: createCommentVNode(\"v-if\", true)`
)
})
test('with spaces between branches', () => {
assert(
`<view><text v-if="ok">1</text> <text v-else-if="orNot">2</text> <text v-else>3</text></view>`,
`createElementVNode("view", null, [
isTrue(_ctx.ok)
? createElementVNode("text", new Map<string, any | null>([["key", 0]]), "1")
: isTrue(_ctx.orNot)
? createElementVNode("text", new Map<string, any | null>([["key", 1]]), "2")
: createElementVNode("text", new Map<string, any | null>([["key", 2]]), "3")
])`
)
})
test('v-on with v-if', () => {
assert(
`<view v-on="{ click: clickEvent }" v-if="true">w/ v-if</view>`,
`isTrue(true)
? createElementVNode(\"view\", mergeProps(new Map<string, any | null>([[\"key\", 0]]), toHandlers(new Map<string, any | null>([["click",_ctx.clickEvent] ]), true)), [
createElementVNode(\"text\", null, \"w/ v-if\")
], 16 /* FULL_PROPS */)
: createCommentVNode(\"v-if\", true)`
)
})
})
import { assert } from '../testUtils'
describe('compiler:v-model', () => {
test('input v-model', () => {
assert(
`<input type="text" v-model="title" />`,
`createElementVNode("input", new Map<string, any | null>([
["type", "text"],
["modelValue", _ctx.title],
["onInput", ($event: InputEvent): any => {_ctx.title = $event.detail.value;
return $event.detail.value;}]
]), null, 40 /* PROPS, HYDRATE_EVENTS */, ["modelValue", "onInput"])`
)
})
test('textarea v-model', () => {
assert(
`<textarea v-model="title" />`,
`createElementVNode("textarea", new Map<string, any | null>([
["modelValue", _ctx.title],
["onInput", ($event: InputEvent): any => {_ctx.title = $event.detail.value;
return $event.detail.value;}]
]), null, 40 /* PROPS, HYDRATE_EVENTS */, ["modelValue", "onInput"])`
)
})
})
import { assert } from '../testUtils'
describe('compiler:v-on', () => {
test('basic', () => {
assert(
`<text v-on:click="() => console.log('v-on:click')"/>`,
`createElementVNode("text", new Map<string, any | null>([
["onClick", () => console.log('v-on:click')]
]), null, 8 /* PROPS */, ["onClick"])`
)
assert(
`<text v-on:click="onClick"/>`,
`createElementVNode("text", new Map<string, any | null>([["onClick", _ctx.onClick]]), null, 8 /* PROPS */, ["onClick"])`
)
})
test('dynamic arg', () => {
assert(
`<text v-on:[event]="handler"/>`,
`createElementVNode("text", new Map<string, any | null>([[toHandlerKey(_ctx.event), _ctx.handler]]), null, 16 /* FULL_PROPS */)`
)
})
test('dynamic arg with complex exp', () => {
assert(
`<text v-on:[event(foo)]="handler"/>`,
`createElementVNode("text", new Map<string, any | null>([[toHandlerKey(_ctx.event(_ctx.foo)), _ctx.handler]]), null, 16 /* FULL_PROPS */)`
)
})
test('shorthand', () => {
assert(
`<text @click="() => console.warn('@click')"/>`,
`createElementVNode("text", new Map<string, any | null>([
["onClick", () => console.warn('@click')]
]), null, 8 /* PROPS */, ["onClick"])`
)
})
test('inline statement handler', () => {
assert(
`<text @click="count++"/>`,
`createElementVNode("text", new Map<string, any | null>([
["onClick", () => {_ctx.count++}]
]), null, 8 /* PROPS */, ["onClick"])`
)
})
test('should handle multi-line statement', () => {
assert(
`<text @click="\nfoo();\nbar()\n"/>`,
`createElementVNode(\"text\", new Map<string, any | null>([
[\"onClick\", () => {
_ctx.foo();
_ctx.bar()
}]
]), null, 8 /* PROPS */, [\"onClick\"])`
)
assert(
`<text @click="a.get('b' + c)()"/>`,
`createElementVNode("text", new Map<string, any | null>([
[\"onClick\", () => {_ctx.a.get('b' + _ctx.c)()}]
]), null, 8 /* PROPS */, [\"onClick\"])`
)
})
// test('inline statement with argument $event', () => {
// assert(
// `<text @click="foo($event)"/>`,
// `Inline statement cannot use $event, please use a function expression instead.`
// )
// })
// test('should NOT wrap as function if expression is already function expression', () => {
// assert(`<text @click="$event => foo($event)"/>`, ``)
// })
// test('should NOT wrap as function if expression is already function expression (with Typescript)', () => {
// assert(`<text @click="(e: any): any => foo(e)"/>`, ``)
// })
// test('should NOT wrap as function if expression is already function expression (with newlines)', () => {
// assert(
// `<text @click="
// $event => {
// foo($event)
// }
// "/>`,``
// )
// })
// test('should NOT wrap as function if expression is already function expression (with newlines + function keyword)', () => {
// assert(
// `<text @click="
// function($event) {
// foo($event)
// }
// "/>`,
// ``
// )
// })
test('should NOT wrap as function if expression is complex member expression', () => {
assert(
`<text @click="a['b' + c]"/>`,
`createElementVNode("text", new Map<string, any | null>([
[\"onClick\", _ctx.a['b' + _ctx.c]]
]), null, 8 /* PROPS */, [\"onClick\"])`
)
})
test('case conversion for vnode hooks', () => {
assert(
`<text v-on:vue:mounted="onMount" @vue:before-update="onBeforeUpdate" />`,
`createElementVNode("text", new Map<string, any | null>([
["onVnodeMounted", _ctx.onMount],
["onVnodeBeforeUpdate", _ctx.onBeforeUpdate]
]), null, 8 /* PROPS */, ["onVnodeMounted", "onVnodeBeforeUpdate"])`
)
})
test('inline function expression handler', () => {
assert(
`<text v-on:click="() => foo()" />`,
`createElementVNode("text", new Map<string, any | null>([
["onClick", () => _ctx.foo()]
]), null, 8 /* PROPS */, ["onClick"])`
)
})
test('object syntax', () => {
assert(
`<text v-on="{mousedown: doThis, mouseup: doThat}"/>`,
`createElementVNode("text", toHandlers(new Map<string, any | null>([["mousedown",_ctx.doThis],["mouseup",_ctx.doThat]]), true), null, 16 /* FULL_PROPS */)`
)
})
test('empty object syntax', () => {
assert(
`<text v-on="{ }"/>`,
`createElementVNode("text", toHandlers(new Map<string, any | null>([]), true), null, 16 /* FULL_PROPS */)`
)
})
test('simple object syntax', () => {
assert(
`<text v-on="{'a':'aaa'}"/>`,
`createElementVNode("text", toHandlers(new Map<string, any | null>([['a','aaa']]), true), null, 16 /* FULL_PROPS */)`
)
})
})
import { assert } from '../testUtils'
describe('compiler:v-show', () => {
test('template v-show', () => {
assert(
`<view v-show="a"></view>`,
`withDirectives(createElementVNode("view", null, null, 512 /* NEED_PATCH */), [
[vShow, _ctx.a]
])`
)
})
})
import { assert } from '../testUtils'
describe('compiler: slot', () => {
test('component with slot', () => {
assert(
`<view><slot data="data"></slot></view>`,
`@Suppress("UNUSED_PARAMETER") function PagesIndexIndexRender(_ctx: PagesIndexIndex): VNode | null {
return createElementVNode("view", null, [
renderSlot(_ctx.$slots, "default", new Map<string, any | null>([["data", "data"]]))
])
}`,
{
targetLanguage: 'kotlin',
mode: 'function',
}
)
})
test('template component with slot', () => {
assert(
`<view><Foo @click="test">test</Foo></view>`,
`@Suppress("UNUSED_PARAMETER") function PagesIndexIndexRender(_ctx: PagesIndexIndex): VNode | null {
const _component_Foo = resolveComponent("Foo")
return createElementVNode("view", null, [
createVNode(_component_Foo, new Map<string, any | null>([["onClick", _ctx.test]]), new Map<string, any | null>([
["default", ((): any[] => [
createElementVNode("text", null, "test")
])],
["_", 1 /* STABLE */]
]), 8 /* PROPS */, ["onClick"])
])
}`,
{
targetLanguage: 'kotlin',
mode: 'function',
}
)
})
test('slot in text', () => {
assert(
`<view><text><slot/></text></view>`,
`@Suppress("UNUSED_PARAMETER") function PagesIndexIndexRender(_ctx: PagesIndexIndex): VNode | null {
return createElementVNode("view", null, [
createElementVNode("text", null, [
renderSlot(_ctx.$slots, "default")
])
])
}`,
{
targetLanguage: 'kotlin',
mode: 'function',
}
)
})
test('scoped slots', () => {
assert(
`<view><Foo><template v-slot="props"><text>msg: {{props.msg}}</text></template></Foo></view>`,
`@Suppress("UNUSED_PARAMETER") function PagesIndexIndexRender(_ctx: PagesIndexIndex): VNode | null {
const _component_Foo = resolveComponent("Foo")
return createElementVNode("view", null, [
createVNode(_component_Foo, null, new Map<string, any | null>([
["default", ((props: Map<string, any | null>): any[] => [
createElementVNode("text", null, "msg: " + toDisplayString(props.msg), 1 /* TEXT */)
])],
["_", 1 /* STABLE */]
]))
])
}`,
{
targetLanguage: 'kotlin',
mode: 'function',
}
)
})
test('scoped slots shorthand', () => {
assert(
`<view><Foo><template #default="props"><text>msg: {{props.msg}}</text></template></Foo></view>`,
`@Suppress("UNUSED_PARAMETER") function PagesIndexIndexRender(_ctx: PagesIndexIndex): VNode | null {
const _component_Foo = resolveComponent("Foo")
return createElementVNode("view", null, [
createVNode(_component_Foo, null, new Map<string, any | null>([
["default", ((props: Map<string, any | null>): any[] => [
createElementVNode("text", null, "msg: " + toDisplayString(props.msg), 1 /* TEXT */)
])],
["_", 1 /* STABLE */]
]))
])
}`,
{
targetLanguage: 'kotlin',
mode: 'function',
}
)
})
})
import { assert } from '../testUtils'
describe('compiler:v-text', () => {
test('template v-text', () => {
assert(
`<text v-text="a"/>`,
`createElementVNode("text", new Map<string, any | null>([
["value", toDisplayString(_ctx.a)]
]), null, 8 /* PROPS */, ["value"])`
)
})
})
declare const _default: () => import("vite").Plugin[]; declare const _default: () => import("vite").Plugin[];
export default _default; export default _default;
export { genClassName } from './plugins/utils';
export { transformVue } from './plugins/uvue/index';
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.transformVue = exports.genClassName = void 0;
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
const plugins_1 = require("./plugins"); const plugins_1 = require("./plugins");
const css_1 = require("./plugins/css");
const mainUTS_1 = require("./plugins/mainUTS"); const mainUTS_1 = require("./plugins/mainUTS");
const manifestJson_1 = require("./plugins/manifestJson"); const manifestJson_1 = require("./plugins/manifestJson");
const pagesJson_1 = require("./plugins/pagesJson"); const pagesJson_1 = require("./plugins/pagesJson");
...@@ -9,10 +12,16 @@ const uvue_1 = require("./plugins/uvue"); ...@@ -9,10 +12,16 @@ const uvue_1 = require("./plugins/uvue");
exports.default = () => { exports.default = () => {
return [ return [
(0, pre_1.uniPrePlugin)(), (0, pre_1.uniPrePlugin)(),
(0, uni_cli_shared_1.uniUTSPlugin)({ x: true }),
(0, plugins_1.uniAppUTSPlugin)(), (0, plugins_1.uniAppUTSPlugin)(),
(0, uvue_1.uniAppUVuePlugin)(),
(0, mainUTS_1.uniAppMainPlugin)(), (0, mainUTS_1.uniAppMainPlugin)(),
(0, manifestJson_1.uniAppManifestPlugin)(), (0, manifestJson_1.uniAppManifestPlugin)(),
(0, pagesJson_1.uniAppPagesPlugin)(), (0, pagesJson_1.uniAppPagesPlugin)(),
(0, css_1.uniAppCssPlugin)(),
(0, uvue_1.uniAppUVuePlugin)(),
]; ];
}; };
var utils_1 = require("./plugins/utils");
Object.defineProperty(exports, "genClassName", { enumerable: true, get: function () { return utils_1.genClassName; } });
var index_1 = require("./plugins/uvue/index");
Object.defineProperty(exports, "transformVue", { enumerable: true, get: function () { return index_1.transformVue; } });
import { JSONArray, JSONObject } from 'com.alibaba.fastjson'
import {
ViewToTempFilePathSuccess,
ViewToTempFilePathFail
} from 'io.dcloud.uniapp.runtime'
import { getCurrentPages } from 'io.dcloud.uts.framework'
import { parsePage } from './util.uts'
export type getPageStackParams = {
callback: (res: any) => void
}
export const getPageStack = (params: getPageStackParams): void => {
params.callback({
pageStack: getCurrentPages().map((page: BasePage): UTSJSONObject => {
return parsePage(page)
})
})
}
export type getCurrentPageParams = {
callback: (res: any) => void
}
function _getCurrentPage(): BasePage | null {
const pages = getCurrentPages()
return pages.length > 0 ? pages[pages.length - 1] : null
}
export const getCurrentPage = (params: getCurrentPageParams): void => {
const page = _getCurrentPage()
params.callback({ page: page !== null ? parsePage(page) : null })
}
export type CallUniMethodParams = {
method: string
args: JSONArray
callback: (res: any) => void
}
export const callUniMethod = (params: CallUniMethodParams): void => {
const method = params.method
const args = params.args
const callback = params.callback
let animationType: string = 'pop-in'
let animationDuration: number = 300
let arg = {} as JSONObject
if (args.size > 0) {
arg = args[0] as JSONObject
}
const success = (result: any) => {
const timeout = method == 'pageScrollTo' ? 350 : 0
setTimeout(() => {
callback(result)
}, timeout)
}
const fail = (err: any) => {
callback(err)
}
switch (method) {
case 'navigateTo':
if (arg['animationType'] !== null) {
animationType = arg['animationType'] as string
}
if (arg['animationDuration'] !== null) {
animationDuration = arg['animationDuration'] as number
}
uni.navigateTo({
url: arg['url'] as string,
animationType,
animationDuration,
success,
fail
})
break
case 'redirectTo':
uni.redirectTo({
url: arg['url'] as string,
success,
fail
})
break
case 'reLaunch':
uni.reLaunch({
url: arg['url'] as string,
success,
fail
})
break
case 'navigateBack':
animationType = 'pop-out'
if (arg['animationType'] !== null) {
animationType = arg['animationType'] as string
}
if (arg['animationDuration'] !== null) {
animationDuration = arg['animationDuration'] as number
}
uni.navigateBack({
animationType,
animationDuration,
success,
fail
})
break
default:
callback({ errMsg: 'uni.' + method + ' not exists' })
break
}
}
export type captureScreenshotParams = {
id?: string | null,
path: string
callback: (res: any) => void
}
export const captureScreenshot = (params: captureScreenshotParams): void => {
const callback = params.callback
const currentPage = _getCurrentPage()
if (currentPage !== null) {
currentPage.$viewToTempFilePath({
id: params.id,
path: params.path,
success: (res: ViewToTempFilePathSuccess) => {
callback({
errMsg: 'screenshot:ok',
tempFilePath: res.tempFilePath,
data: UTSAndroid.dcloud_private_getFileBase64(res.tempFilePath)
})
},
fail: (err: ViewToTempFilePathFail) => {
callback(err)
}
})
} else {
callback({
errMsg: `captureScreenshot:fail, currentPage is not found.`
})
}
}
// @ts-ignore
import { JSONObject } from 'com.alibaba.fastjson'
import { getData, setData, getPageVm } from './util.uts'
export type getDataParams = {
pageId: number
path: string
callback: (res: any) => void
}
export const pageGetData = (params: getDataParams): void => {
const callback = params.callback
const result = getData(getPageVm(params.pageId)!, params.path)
callback(result)
}
export type setDataParams = {
pageId: number
data: JSONObject
callback: (res?: any | null) => void
}
export const pageSetData = (params: setDataParams): void => {
const pageId = params.pageId
const callback = params.callback
const page = getPageVm(pageId)
if (page !== null) {
setData(page, params.data)
callback(null)
} else {
callback({ errMsg: `setData:fail, Page:${pageId} is not found.` })
}
}
import { JSONObject } from 'com.alibaba.fastjson'
import { getCurrentPages } from 'io.dcloud.uts.framework'
function getPageId(page: BasePage): number {
return page.$.uid
}
function getPagePath(page: BasePage): string {
return page.route
}
function getPageQuery(page: BasePage): VueComponentOptions {
return page.$options
}
function getPageById(id: number): BasePage | null {
const pages = getCurrentPages()
let result: BasePage | null = null
pages.forEach((page: BasePage) => {
if (getPageId(page) === id) {
result = page
}
})
return result
}
export function getPageVm(id: number): BasePage | null {
return getPageById(id)
}
export function getData(
vm: BasePage | null,
path?: string
): Map<string, any | null> {
let data = new Map<string, any | null>()
if (vm !== null) {
// TODO: path
if(path !== null){
data.set("errMsg", `getData:fail, path:${path} is not supported.`)
}else{
data = vm.$data
}
}
return data
}
export function setData(vm: BasePage, data: JSONObject): void {
for (const key in data) {
// @ts-ignore
const _key = key.key
// @ts-ignore
const value = key.value
vm.$data.set(_key, value)
}
}
export function parsePage(page: BasePage): UTSJSONObject {
return {
id: getPageId(page),
path: getPagePath(page),
query: getPageQuery(page),
} as UTSJSONObject
}
\ No newline at end of file
import { JSONArray, JSONObject } from 'com.alibaba.fastjson'
import { SocketTask, SendSocketMessageOptions } from 'uts.sdk.modules.DCloudUniWebsocket'
import {
callUniMethod,
CallUniMethodParams,
captureScreenshot,
captureScreenshotParams,
getPageStack,
getPageStackParams,
getCurrentPage,
getCurrentPageParams
} from './apis/App.uts'
import {
pageGetData,
getDataParams,
pageSetData,
setDataParams
} from './apis/Page.uts'
let socketTask: SocketTask | null = null
const wsEndpoint = process.env.UNI_AUTOMATOR_WS_ENDPOINT
function send(data: any) {
socketTask?.send({ data: JSON.stringify(data) } as SendSocketMessageOptions)
}
function onMessage(msg: string) {
const json = JSON.parse(msg)!
const method = json['method'] as string
const params = json['params'] as JSONObject
const path = params['path'] !== null ? (params['path'] as string) : ''
const res = new Map<string, any | null>([['id', json['id'] as string]])
try {
const callback = (result?: any | null) => {
if (result !== null) {
res.set('result', result)
}
send(res)
}
if (method.startsWith('App.')) {
switch (method) {
case 'App.callUniMethod':
const method = params['method'] as string
const args = params['args'] as JSONArray
callUniMethod({
method,
args,
callback
} as CallUniMethodParams)
break
case 'App.captureScreenshot':
const id = params['id'] !== null ? (params['id'] as string) : null
captureScreenshot({ id, path, callback } as captureScreenshotParams)
break
case 'App.getPageStack':
getPageStack({ callback } as getPageStackParams)
break
case 'App.getCurrentPage':
getCurrentPage({ callback } as getCurrentPageParams)
break
}
} else if (method.startsWith('Page.')) {
const pageId = params['pageId'] as number
const data =
params['data'] !== null
? (params['data'] as JSONObject)
: ({} as JSONObject)
switch (method) {
case 'Page.getData':
pageGetData({ pageId, path, callback } as getDataParams)
break
case 'Page.setData':
pageSetData({ pageId, data, callback } as setDataParams)
break
}
}
} catch (error) {
res.set('error', error)
send(res)
}
}
export function initAutomator() {
socketTask = uni.connectSocket({
url: wsEndpoint
});
socketTask!.onMessage((res) => {onMessage(res.data as string)})
socketTask!.onOpen((_) => {
console.warn("automator.onOpen")
})
socketTask!.onError((err) => {
console.warn(`automator.onError: ${JSON.stringify(err)}`);
})
socketTask!.onClose((_) => {
console.warn("automator.onClose");
})
}
{ {
"name": "@dcloudio/uni-app-uts", "name": "@dcloudio/uni-app-uts",
"version": "3.0.0-alpha-3071220230324001", "version": "3.0.0-alpha-3080220230428002",
"description": "uni-app-uts", "description": "uni-app-uts",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
...@@ -19,7 +19,12 @@ ...@@ -19,7 +19,12 @@
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3071220230324001", "@babel/parser": "^7.16.4",
"@babel/types": "^7.20.7",
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-i18n": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-nvue-styler": "3.0.0-alpha-3080220230428002",
"@dcloudio/uni-shared": "3.0.0-alpha-3080220230428002",
"@rollup/pluginutils": "^4.2.0", "@rollup/pluginutils": "^4.2.0",
"@vue/compiler-core": "3.2.47", "@vue/compiler-core": "3.2.47",
"@vue/compiler-sfc": "3.2.47", "@vue/compiler-sfc": "3.2.47",
...@@ -27,10 +32,11 @@ ...@@ -27,10 +32,11 @@
"debug": "^4.3.3", "debug": "^4.3.3",
"es-module-lexer": "^1.2.1", "es-module-lexer": "^1.2.1",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"picocolors": "^1.0.0",
"source-map": "^0.6.1" "source-map": "^0.6.1"
}, },
"devDependencies": { "devDependencies": {
"@types/debug": "^4.1.7", "@types/debug": "^4.1.7",
"@types/fs-extra": "^9.0.13" "@types/fs-extra": "^9.0.13"
} }
} }
\ No newline at end of file
import { uniUTSPlugin } from '@dcloudio/uni-cli-shared'
import { uniAppUTSPlugin } from './plugins' import { uniAppUTSPlugin } from './plugins'
import { uniAppCssPlugin } from './plugins/css'
import { uniAppMainPlugin } from './plugins/mainUTS' import { uniAppMainPlugin } from './plugins/mainUTS'
import { uniAppManifestPlugin } from './plugins/manifestJson' import { uniAppManifestPlugin } from './plugins/manifestJson'
import { uniAppPagesPlugin } from './plugins/pagesJson' import { uniAppPagesPlugin } from './plugins/pagesJson'
...@@ -7,10 +9,15 @@ import { uniAppUVuePlugin } from './plugins/uvue' ...@@ -7,10 +9,15 @@ import { uniAppUVuePlugin } from './plugins/uvue'
export default () => { export default () => {
return [ return [
uniPrePlugin(), uniPrePlugin(),
uniUTSPlugin({ x: true }),
uniAppUTSPlugin(), uniAppUTSPlugin(),
uniAppUVuePlugin(),
uniAppMainPlugin(), uniAppMainPlugin(),
uniAppManifestPlugin(), uniAppManifestPlugin(),
uniAppPagesPlugin(), uniAppPagesPlugin(),
uniAppCssPlugin(),
uniAppUVuePlugin(),
] ]
} }
export { genClassName } from './plugins/utils'
export { transformVue } from './plugins/uvue/index'
import type { Plugin, ResolvedConfig } from 'vite'
import path from 'path'
import colors from 'picocolors'
import {
commonjsProxyRE,
cssLangRE,
cssPlugin,
cssPostPlugin,
formatAtFilename,
generateCodeFrame,
insertBeforePlugin,
normalizePath,
parseVueRequest,
resolveMainPathOnce,
} from '@dcloudio/uni-cli-shared'
import { parse } from '@dcloudio/uni-nvue-styler'
import { genClassName, isVue } from './utils'
export function uniAppCssPlugin(): Plugin {
const mainUTS = resolveMainPathOnce(process.env.UNI_INPUT_DIR)
let resolvedConfig: ResolvedConfig
const name = 'uni:app-uvue-css'
return {
name,
apply: 'build',
configResolved(config) {
resolvedConfig = config
const uvueCssPostPlugin = cssPostPlugin(config, {
isJsCode: true,
platform: process.env.UNI_PLATFORM,
chunkCssFilename(id: string) {
if (id === mainUTS) {
return 'App.vue.style.uts'
}
const { filename } = parseVueRequest(id)
if (isVue(filename)) {
return normalizePath(
path.relative(process.env.UNI_INPUT_DIR, filename) + '.style.uts'
)
}
},
async chunkCssCode(filename, cssCode) {
const { code, messages } = await parse(cssCode, {
filename,
logLevel: 'ERROR',
map: true,
ts: true,
type: 'uvue',
})
messages.forEach((message) => {
if (message.type === 'error') {
let msg = `[plugin:uni:app-uvue-css] ${message.text}`
if (message.line && message.column) {
msg += `\n${generateCodeFrame(cssCode, {
line: message.line,
column: message.column,
}).replace(/\t/g, ' ')}`
}
msg += `\n${formatAtFilename(filename)}`
resolvedConfig.logger.error(colors.red(msg))
}
})
return `export const ${genClassName(
filename.replace('.style.uts', '')
)}Styles = [${code}]`
},
})
// 增加 css plugins
insertBeforePlugin(cssPlugin(config), name, config)
const plugins = config.plugins as Plugin[]
const index = plugins.findIndex((p) => p.name === 'uni:app-uvue')
plugins.splice(index, 0, uvueCssPostPlugin)
},
async transform(source, filename) {
if (!cssLangRE.test(filename) || commonjsProxyRE.test(filename)) {
return
}
// 仅做校验使用
const { messages } = await parse(source, {
filename,
logLevel: 'WARNING',
map: true,
ts: true,
noCode: true,
type: 'uvue',
})
messages.forEach((message) => {
if (message.type === 'warning') {
let msg = `[plugin:uni:app-uvue-css] ${message.text}`
if (message.line && message.column) {
msg += `\n${generateCodeFrame(source, {
line: message.line,
column: message.column,
}).replace(/\t/g, ' ')}`
}
msg += `\n${formatAtFilename(filename)}`
resolvedConfig.logger.warn(colors.yellow(msg))
}
})
return { code: source }
},
}
}
import path from 'path' import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { import {
UniViteCopyPluginOptions,
UniVitePlugin,
emptyDir, emptyDir,
initI18nOptions,
normalizeNodeModules,
normalizePath, normalizePath,
parseManifestJsonOnce, parseManifestJsonOnce,
parseVueRequest, parseVueRequest,
resolveMainPathOnce, resolveMainPathOnce,
resolveUTSCompiler, resolveUTSCompiler,
utsPlugins,
} from '@dcloudio/uni-cli-shared' } from '@dcloudio/uni-cli-shared'
import { compileI18nJsonStr } from '@dcloudio/uni-i18n'
import type { Plugin } from 'vite' import type { Plugin } from 'vite'
import { parseImports, uvueOutDir } from './utils' import { parseImports, uvueOutDir } from './utils'
...@@ -39,7 +45,7 @@ const REMOVED_PLUGINS = [ ...@@ -39,7 +45,7 @@ const REMOVED_PLUGINS = [
'vite:reporter', 'vite:reporter',
] ]
export function uniAppUTSPlugin(): Plugin { export function uniAppUTSPlugin(): UniVitePlugin {
const inputDir = process.env.UNI_INPUT_DIR const inputDir = process.env.UNI_INPUT_DIR
const outputDir = process.env.UNI_OUTPUT_DIR const outputDir = process.env.UNI_OUTPUT_DIR
const mainUTS = resolveMainPathOnce(inputDir) const mainUTS = resolveMainPathOnce(inputDir)
...@@ -56,6 +62,7 @@ export function uniAppUTSPlugin(): Plugin { ...@@ -56,6 +62,7 @@ export function uniAppUTSPlugin(): Plugin {
return { return {
name: 'uni:app-uts', name: 'uni:app-uts',
apply: 'build', apply: 'build',
uni: createUniOptions(),
config() { config() {
return { return {
base: '/', // 强制 base base: '/', // 强制 base
...@@ -68,17 +75,34 @@ export function uniAppUTSPlugin(): Plugin { ...@@ -68,17 +75,34 @@ export function uniAppUTSPlugin(): Plugin {
formats: ['cjs'], formats: ['cjs'],
}, },
rollupOptions: { rollupOptions: {
external: ['vue', 'vuex', 'pinia'], external(source) {
if (['vue', 'vuex', 'pinia'].includes(source)) {
return true
}
// 相对目录
if (source.startsWith('@/') || source.startsWith('.')) {
return false
}
if (path.isAbsolute(source)) {
return false
}
// android 系统库,三方库
if (source.includes('.')) {
return true
}
return false
},
}, },
}, },
} }
}, },
configResolved(config) { configResolved(config) {
const len = config.plugins.length const plugins = config.plugins as Plugin[]
const len = plugins.length
for (let i = len - 1; i >= 0; i--) { for (let i = len - 1; i >= 0; i--) {
const plugin = config.plugins[i] const plugin = plugins[i]
if (REMOVED_PLUGINS.includes(plugin.name)) { if (REMOVED_PLUGINS.includes(plugin.name)) {
;(config.plugins as any).splice(i, 1) plugins.splice(i, 1)
} }
} }
}, },
...@@ -87,25 +111,45 @@ export function uniAppUTSPlugin(): Plugin { ...@@ -87,25 +111,45 @@ export function uniAppUTSPlugin(): Plugin {
if (!filename.endsWith('.uts')) { if (!filename.endsWith('.uts')) {
return return
} }
const isMainUTS = normalizePath(id) === mainUTS // 仅处理 uts 文件
const fileName = path.relative(inputDir, id) // 忽略 uni-app-uts/lib/automator/index.uts
this.emitFile({ if (!filename.includes('uni-app-uts')) {
type: 'asset', const isMainUTS = normalizePath(id) === mainUTS
fileName: normalizeFilename(fileName, isMainUTS), const fileName = path.relative(inputDir, id)
source: normalizeCode(code, isMainUTS), this.emitFile({
}) type: 'asset',
fileName: normalizeFilename(fileName, isMainUTS),
source: normalizeCode(code, isMainUTS),
})
}
code = await parseImports(code) code = await parseImports(code)
return code return code
}, },
async writeBundle() { async writeBundle() {
await resolveUTSCompiler().compileApp( const res = await resolveUTSCompiler().compileApp(
path.join(tempOutputDir, 'index.uts'), path.join(tempOutputDir, 'index.uts'),
{ {
inputDir: tempOutputDir, inputDir: tempOutputDir,
outputDir: outputDir, outputDir: outputDir,
package: 'uni.' + (manifestJson.appid || '').replace(/_/g, ''), package: 'uni.' + (manifestJson.appid || '').replace(/_/g, ''),
sourceMap: true,
uni_modules: [...utsPlugins],
} }
) )
if (res) {
const files: string[] = []
if (process.env.UNI_APP_UTS_CHANGED_FILES) {
try {
files.push(...JSON.parse(process.env.UNI_APP_UTS_CHANGED_FILES))
} catch (e) {}
}
if (res.changed && res.changed.length) {
files.push(...res.changed)
}
process.env.UNI_APP_UTS_CHANGED_FILES = JSON.stringify([
...new Set(files),
])
}
}, },
} }
} }
...@@ -114,16 +158,59 @@ function normalizeFilename(filename: string, isMain = false) { ...@@ -114,16 +158,59 @@ function normalizeFilename(filename: string, isMain = false) {
if (isMain) { if (isMain) {
return 'index.uts' return 'index.uts'
} }
return filename return normalizeNodeModules(filename)
} }
function normalizeCode(code: string, isMain = false) { function normalizeCode(code: string, isMain = false) {
if (!isMain) { if (!isMain) {
return code return code
} }
const automatorCode = process.env.UNI_AUTOMATOR_WS_ENDPOINT
? 'initAutomator();'
: ''
return ` return `
export function main() { ${code}
createPage(__uniRoutes[0]) export function main(app: IApp) {
defineAppConfig();
definePageRoutes();
${automatorCode}
(createApp()['app'] as VueApp).mount(app);
} }
` `
} }
function createUniOptions(): UniVitePlugin['uni'] {
return {
copyOptions() {
const platform = process.env.UNI_PLATFORM
const inputDir = process.env.UNI_INPUT_DIR
const outputDir = process.env.UNI_OUTPUT_DIR
const targets: UniViteCopyPluginOptions['targets'] = []
// 自动化测试时,不启用隐私政策
if (!process.env.UNI_AUTOMATOR_WS_ENDPOINT) {
targets.push({
src: 'androidPrivacy.json',
dest: outputDir,
transform(source) {
const options = initI18nOptions(platform, inputDir, false, true)
if (!options) {
return
}
return compileI18nJsonStr(source.toString(), options)
},
})
const debugFilename = '__nvue_debug__'
if (fs.existsSync(path.resolve(inputDir, debugFilename))) {
targets.push({
src: debugFilename,
dest: outputDir,
})
}
}
return {
assets: ['hybrid/html/**/*', 'uni_modules/*/hybrid/html/**/*'],
targets,
}
},
}
}
import { normalizePath, resolveMainPathOnce } from '@dcloudio/uni-cli-shared' import {
MANIFEST_JSON_UTS,
PAGES_JSON_UTS,
normalizePath,
resolveMainPathOnce,
} from '@dcloudio/uni-cli-shared'
import type { Plugin } from 'vite' import type { Plugin } from 'vite'
import { parseImports } from './utils' import { parseImports } from './utils'
export const MANIFEST_JSON_UTS = 'manifest-json-uts'
export const PAGES_JSON_UTS = 'pages-json-uts'
export function uniAppMainPlugin(): Plugin { export function uniAppMainPlugin(): Plugin {
const mainUTS = resolveMainPathOnce(process.env.UNI_INPUT_DIR) const mainUTS = resolveMainPathOnce(process.env.UNI_INPUT_DIR)
return { return {
...@@ -17,6 +19,7 @@ export function uniAppMainPlugin(): Plugin { ...@@ -17,6 +19,7 @@ export function uniAppMainPlugin(): Plugin {
return ` return `
import './${MANIFEST_JSON_UTS}' import './${MANIFEST_JSON_UTS}'
import './${PAGES_JSON_UTS}' import './${PAGES_JSON_UTS}'
${code}
export default 'main.uts' export default 'main.uts'
` `
} }
......
...@@ -2,8 +2,7 @@ import path from 'path' ...@@ -2,8 +2,7 @@ import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import type { OutputAsset } from 'rollup' import type { OutputAsset } from 'rollup'
import type { Plugin } from 'vite' import type { Plugin } from 'vite'
import { parseJson } from '@dcloudio/uni-cli-shared' import { MANIFEST_JSON_UTS, parseJson } from '@dcloudio/uni-cli-shared'
import { MANIFEST_JSON_UTS } from './mainUTS'
import { ENTRY_FILENAME } from './utils' import { ENTRY_FILENAME } from './utils'
function isManifest(id: string) { function isManifest(id: string) {
...@@ -35,6 +34,9 @@ export function uniAppManifestPlugin(): Plugin { ...@@ -35,6 +34,9 @@ export function uniAppManifestPlugin(): Plugin {
}, },
transform(code, id) { transform(code, id) {
if (isManifest(id)) { if (isManifest(id)) {
this.addWatchFile(
path.resolve(process.env.UNI_INPUT_DIR, 'manifest.json')
)
manifestJson = parseJson(code) manifestJson = parseJson(code)
return `export default 'manifest.json'` return `export default 'manifest.json'`
} }
...@@ -53,8 +55,27 @@ export class UniAppConfig extends AppConfig { ...@@ -53,8 +55,27 @@ export class UniAppConfig extends AppConfig {
override versionCode: string = "${manifestJson.versionCode || ''}" override versionCode: string = "${manifestJson.versionCode || ''}"
constructor() {} constructor() {}
} }
export * from './App.vue.style.uts'
` `
} }
fs.outputFileSync(
path.resolve(process.env.UNI_OUTPUT_DIR, 'manifest.json'),
JSON.stringify(
{
id: manifestJson.appid || '',
name: manifestJson.name || '',
description: manifestJson.description || '',
version: {
name: manifestJson.versionName || '',
code: manifestJson.versionCode || '',
},
'uni-app-x': manifestJson['uni-app-x'] || {},
app: manifestJson.app || {},
},
null,
2
)
)
}, },
} }
} }
import path from 'path' import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { normalizePagesJson } from '@dcloudio/uni-cli-shared' import {
PAGES_JSON_UTS,
normalizeUniAppXAppPagesJson,
} from '@dcloudio/uni-cli-shared'
import type { OutputAsset } from 'rollup' import type { OutputAsset } from 'rollup'
import type { Plugin } from 'vite' import type { Plugin } from 'vite'
import { PAGES_JSON_UTS } from './mainUTS' import { ENTRY_FILENAME, genClassName, stringifyMap } from './utils'
import { ENTRY_FILENAME, genClassName } from './utils'
function isPages(id: string) { function isPages(id: string) {
return id.endsWith(PAGES_JSON_UTS) return id.endsWith(PAGES_JSON_UTS)
...@@ -19,6 +21,8 @@ export function uniAppPagesPlugin(): Plugin { ...@@ -19,6 +21,8 @@ export function uniAppPagesPlugin(): Plugin {
) )
let imports: string[] = [] let imports: string[] = []
let routes: string[] = [] let routes: string[] = []
let globalStyle: string = 'new Map()'
let tabBar: string = 'null'
return { return {
name: 'uni:app-pages', name: 'uni:app-pages',
apply: 'build', apply: 'build',
...@@ -34,16 +38,28 @@ export function uniAppPagesPlugin(): Plugin { ...@@ -34,16 +38,28 @@ export function uniAppPagesPlugin(): Plugin {
}, },
transform(code, id) { transform(code, id) {
if (isPages(id)) { if (isPages(id)) {
const pagesJson = normalizePagesJson(code, process.env.UNI_PLATFORM) this.addWatchFile(path.resolve(process.env.UNI_INPUT_DIR, 'pages.json'))
const pagesJson = normalizeUniAppXAppPagesJson(code)
imports = [] imports = []
routes = [] routes = []
pagesJson.pages.forEach((page) => { pagesJson.pages.forEach((page, index) => {
const className = genClassName(page.path) const className = genClassName(page.path)
let isQuit = index === 0
imports.push(page.path) imports.push(page.path)
routes.push( routes.push(
`{ path: "${page.path}", component: ${className}Class, meta: { isQuit: true, navigationBar: { titleText: "uni-app" } as PageNavigationBar } as PageMeta } as PageRoute` `{ path: "${
page.path
}", component: ${className}Class, meta: { isQuit: ${isQuit} } as PageMeta, style: ${stringifyPageStyle(
page.style
)} } as PageRoute`
) )
}) })
if (pagesJson.globalStyle) {
globalStyle = stringifyPageStyle(pagesJson.globalStyle)
}
if (pagesJson.tabBar) {
tabBar = stringifyMap(pagesJson.tabBar)
}
return `${imports.map((p) => `import './${p}.uvue'`).join('\n')} return `${imports.map((p) => `import './${p}.uvue'`).join('\n')}
export default 'pages.json'` export default 'pages.json'`
} }
...@@ -57,12 +73,23 @@ export default 'pages.json'` ...@@ -57,12 +73,23 @@ export default 'pages.json'`
${imports ${imports
.map((p) => { .map((p) => {
const className = genClassName(p) const className = genClassName(p)
return `import ${className}Class from './${p}.uvue'` return `import ${className}Class from './${p}.uvue?type=page'`
}) })
.join('\n')} .join('\n')}
const __uniRoutes = [${routes.join(',\n')}] function definePageRoutes() {
${routes.map((route) => `__uniRoutes.push(${route})`).join('\n')}
}
function defineAppConfig(){
__uniConfig.entryPagePath = '/${imports[0]}'
__uniConfig.globalStyle = ${globalStyle}
__uniConfig.tabBar = ${tabBar}
}
` `
} }
}, },
} }
} }
function stringifyPageStyle(pageStyle: UniApp.PagesJsonPageStyle) {
return stringifyMap(pageStyle)
}
import path from 'path' import path from 'path'
import { init, parse } from 'es-module-lexer' import { init, parse } from 'es-module-lexer'
import { normalizePath, removeExt } from '@dcloudio/uni-cli-shared' import { normalizePath, removeExt } from '@dcloudio/uni-cli-shared'
import { camelize, capitalize } from '@vue/shared' import {
camelize,
capitalize,
isArray,
isPlainObject,
isString,
} from '@vue/shared'
export const ENTRY_FILENAME = 'index.uts' export const ENTRY_FILENAME = 'index.uts'
...@@ -19,8 +25,32 @@ export function uvueOutDir() { ...@@ -19,8 +25,32 @@ export function uvueOutDir() {
return path.join(process.env.UNI_OUTPUT_DIR, '../.uvue') return path.join(process.env.UNI_OUTPUT_DIR, '../.uvue')
} }
export function genClassName(fileName: string) { export function genClassName(fileName: string, prefix: string = 'Gen') {
return capitalize( return (
camelize(removeExt(normalizePath(fileName).replace(/\//g, '-'))) prefix +
capitalize(camelize(removeExt(normalizePath(fileName).replace(/\//g, '-'))))
) )
} }
export function isVue(filename: string) {
return filename.endsWith('.vue') || filename.endsWith('.uvue')
}
export function stringifyMap(obj: unknown) {
return serialize(obj, true)
}
function serialize(obj: unknown, ts: boolean = false): string {
if (isString(obj)) {
return `"${obj}"`
} else if (isPlainObject(obj)) {
const entries = Object.entries(obj).map(
([key, value]) => `[${serialize(key, ts)},${serialize(value, ts)}]`
)
return `new Map${ts ? '<string, any>' : ''}([${entries.join(',')}])`
} else if (isArray(obj)) {
return `[${obj.map((item) => serialize(item, ts)).join(',')}]`
} else {
return String(obj)
}
}
import { SFCScriptBlock } from '@vue/compiler-sfc' import { SFCDescriptor } from '@vue/compiler-sfc'
export function genScript( export function genScript(
script: SFCScriptBlock | null, { script }: SFCDescriptor,
{ filename }: { filename: string } _options: { filename: string }
) { ) {
const parentClass = filename === 'App' ? 'BaseApp' : 'BasePage'
if (!script) { if (!script) {
return ` return `
class ${filename} extends ${parentClass} { export default {}
constructor() {}
render(ctx: ${filename}): VNode | null {
return ${filename}Render(ctx)
}
}
export default UTSAndroid.getKotlinClass(${filename})
` `
} }
return ( return (
'\n'.repeat(script.loc.start.line - 1) + '\n'.repeat(script.loc.start.line - 1) +
` `${script.content}
class ${filename} extends ${parentClass} {
constructor() {}
render(ctx: ${filename}): VNode | null {
return ${filename}Render(ctx)
}
}
export default UTSAndroid.getKotlinClass(${filename})
` `
) )
} }
import { SFCStyleBlock } from '@vue/compiler-sfc' import type { SFCBlock, SFCDescriptor } from '@vue/compiler-sfc'
import type { PluginContext, TransformPluginContext } from 'rollup'
import { ResolvedOptions, setSrcDescriptor } from '../descriptorCache'
export function genStyle( export function genStyle(
styles: SFCStyleBlock[], _: SFCDescriptor,
{ filename }: { filename: string } { className }: { className: string; filename: string }
) { ) {
return `export const ${filename}Styles = []` return `/*${className}Styles*/`
}
export async function genJsStylesCode(
descriptor: SFCDescriptor,
pluginContext: PluginContext
) {
let stylesCode = ``
if (descriptor.styles.length) {
for (let i = 0; i < descriptor.styles.length; i++) {
const style = descriptor.styles[i]
if (style.src) {
await linkSrcToDescriptor(style.src, descriptor, pluginContext)
}
const src = style.src || descriptor.filename
// do not include module in default query, since we use it to indicate
// that the module needs to export the modules json
const attrsQuery = attrsToQuery(style.attrs, 'css')
const srcQuery = style.src ? '&src=true' : ''
const query = `?vue&type=style&index=${i}${srcQuery}`
const styleRequest = src + query + attrsQuery
stylesCode += `\nimport ${JSON.stringify(styleRequest)}`
}
}
return stylesCode
}
/**
* For blocks with src imports, it is important to link the imported file
* with its owner SFC descriptor so that we can get the information about
* the owner SFC when compiling that file in the transform phase.
*/
async function linkSrcToDescriptor(
src: string,
descriptor: SFCDescriptor,
pluginContext: PluginContext
) {
const srcFile =
(await pluginContext.resolve(src, descriptor.filename))?.id || src
// #1812 if the src points to a dep file, the resolved id may contain a
// version query.
setSrcDescriptor(srcFile.replace(/\?.*$/, ''), descriptor)
}
// these are built-in query parameters so should be ignored
// if the user happen to add them as attrs
const ignoreList = ['id', 'index', 'src', 'type', 'lang', 'module', 'scoped']
function attrsToQuery(
attrs: SFCBlock['attrs'],
langFallback?: string,
forceLangFallback = false
): string {
let query = ``
for (const name in attrs) {
const value = attrs[name]
if (!ignoreList.includes(name)) {
query += `&${encodeURIComponent(name)}${
value ? `=${encodeURIComponent(value)}` : ``
}`
}
}
if (langFallback || attrs.lang) {
query +=
`lang` in attrs
? forceLangFallback
? `&lang.${langFallback}`
: `&lang.${attrs.lang}`
: `&lang.${langFallback}`
}
return query
}
export async function transformStyle(
code: string,
descriptor: SFCDescriptor,
index: number,
options: ResolvedOptions,
pluginContext: TransformPluginContext,
filename: string
) {
const block = descriptor.styles[index]
// vite already handles pre-processors and CSS module so this is only
// applying SFC-specific transforms like scoped mode and CSS vars rewrite (v-bind(var))
const result = await options.compiler.compileStyleAsync({
filename: descriptor.filename,
id: `data-v-${descriptor.id}`,
isProd: true,
source: code,
})
if (result.errors.length) {
result.errors.forEach((error: any) => {
if (error.line && error.column) {
error.loc = {
file: descriptor.filename,
line: error.line + block.loc.start.line,
column: error.column,
}
}
pluginContext.error(error)
})
return null
}
return {
code: result.code,
map: null,
}
} }
import { SFCDescriptor } from '@vue/compiler-sfc'
import { compile } from '../compiler'
import { CompilerOptions } from '../compiler/options'
import { genRenderFunctionDecl } from '../compiler/utils'
export function genTemplate(
{ template }: SFCDescriptor,
options: CompilerOptions
) {
if (!template) {
return { code: genRenderFunctionDecl(options) + ` { return null }` }
}
return compile(template.content, options)
}
import { SFCTemplateBlock } from '@vue/compiler-sfc'
import { compile } from './compiler'
import { CompilerOptions } from './compiler/options'
import { genRenderFunctionDecl } from './utils'
export function genTemplate(
template: SFCTemplateBlock | null,
options: CompilerOptions
) {
if (!template) {
return genRenderFunctionDecl(options) + ` { return null }`
}
return compile(template.content, options).code
}
import { CompilerOptions } from './compiler/options'
export function genRenderFunctionDecl({
targetLanguage,
filename,
}: CompilerOptions): string {
return `${
targetLanguage === 'kotlin' ? '@Suppress("UNUSED_PARAMETER") ' : ''
}function ${filename}Render(ctx: ${filename}): VNode | null`
}
...@@ -23,7 +23,7 @@ import { ...@@ -23,7 +23,7 @@ import {
TemplateChildNode, TemplateChildNode,
TextNode, TextNode,
VNodeCall, VNodeCall,
WITH_CTX, // WITH_CTX,
WITH_DIRECTIVES, WITH_DIRECTIVES,
advancePositionWithMutation, advancePositionWithMutation,
getVNodeBlockHelper, getVNodeBlockHelper,
...@@ -31,10 +31,19 @@ import { ...@@ -31,10 +31,19 @@ import {
helperNameMap, helperNameMap,
isSimpleIdentifier, isSimpleIdentifier,
locStub, locStub,
toValidAssetId,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { CodegenOptions, CodegenResult } from './options' import { CodegenOptions, CodegenResult } from './options'
import { isArray, isString, isSymbol } from '@vue/shared' import { isArray, isString, isSymbol } from '@vue/shared'
import { genRenderFunctionDecl } from '../utils' import { genRenderFunctionDecl } from './utils'
import {
IS_TRUE,
RENDER_LIST,
RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
TO_HANDLERS,
} from './runtimeHelpers'
import { object2Map } from './utils'
type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
...@@ -59,6 +68,7 @@ function createCodegenContext( ...@@ -59,6 +68,7 @@ function createCodegenContext(
targetLanguage, targetLanguage,
mode = 'default', mode = 'default',
prefixIdentifiers = false, prefixIdentifiers = false,
bindingMetadata = {},
sourceMap = false, sourceMap = false,
filename = '', filename = '',
}: CodegenOptions }: CodegenOptions
...@@ -67,6 +77,7 @@ function createCodegenContext( ...@@ -67,6 +77,7 @@ function createCodegenContext(
targetLanguage, targetLanguage,
mode, mode,
prefixIdentifiers, prefixIdentifiers,
bindingMetadata,
sourceMap, sourceMap,
filename, filename,
source: ast.loc.source, source: ast.loc.source,
...@@ -84,7 +95,7 @@ function createCodegenContext( ...@@ -84,7 +95,7 @@ function createCodegenContext(
if (context.map) { if (context.map) {
if (node) { if (node) {
let name let name
if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) { if (node.type === NodeTypes.SIMPLE_EXPRESSION) {
const content = node.content.replace(/^_ctx\./, '') const content = node.content.replace(/^_ctx\./, '')
if (content !== node.content && isSimpleIdentifier(content)) { if (content !== node.content && isSimpleIdentifier(content)) {
name = content name = content
...@@ -146,9 +157,26 @@ export function generate( ...@@ -146,9 +157,26 @@ export function generate(
options: CodegenOptions options: CodegenOptions
): CodegenResult { ): CodegenResult {
const context = createCodegenContext(ast, options) const context = createCodegenContext(ast, options)
const { mode, deindent, indent, push } = context const { mode, deindent, indent, push, newline } = context
if (mode === 'function') { if (mode === 'function') {
push(genRenderFunctionDecl(options) + ` {`) push(genRenderFunctionDecl(options) + ` {`)
// generate asset resolution statements
if (ast.components.length) {
newline()
genAssets(ast.components, 'component', context)
if (ast.directives.length || ast.temps > 0) {
newline()
}
}
if (ast.directives.length) {
genAssets(ast.directives, 'directive', context)
if (ast.temps > 0) {
newline()
}
}
if (ast.components.length || ast.directives.length || ast.temps) {
newline()
}
indent() indent()
push(`return `) push(`return `)
} }
...@@ -168,6 +196,32 @@ export function generate( ...@@ -168,6 +196,32 @@ export function generate(
} }
} }
function genAssets(
assets: string[],
type: 'component' | 'directive',
{ helper, push, newline }: CodegenContext
) {
const resolver = helper(
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
)
for (let i = 0; i < assets.length; i++) {
let id = assets[i]
// potential component implicit self-reference inferred from SFC filename
const maybeSelfReference = id.endsWith('__self')
if (maybeSelfReference) {
id = id.slice(0, -6)
}
push(
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
maybeSelfReference ? `, true` : ``
})`
)
if (i < assets.length - 1) {
newline()
}
}
}
function isText(n: string | CodegenNode) { function isText(n: string | CodegenNode) {
return ( return (
isString(n) || isString(n) ||
...@@ -224,6 +278,7 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) { ...@@ -224,6 +278,7 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
return return
} }
if (isSymbol(node)) { if (isSymbol(node)) {
context.push(context.helper(node))
return return
} }
switch (node.type) { switch (node.type) {
...@@ -320,15 +375,18 @@ function genExpressionAsPropertyKey( ...@@ -320,15 +375,18 @@ function genExpressionAsPropertyKey(
) { ) {
const { push } = context const { push } = context
if (node.type === NodeTypes.COMPOUND_EXPRESSION) { if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
push(`[`) // dynamic arg genObjectExpression have added []
// push(`[`)
genCompoundExpression(node, context) genCompoundExpression(node, context)
push(`]`) // push(`]`)
} else if (node.isStatic) { } else if (node.isStatic) {
// only quote keys if necessary // only quote keys if necessary
const text = JSON.stringify(node.content) const text = JSON.stringify(node.content)
push(text, node) push(text, node)
} else { } else {
push(`[${node.content}]`, node) // dynamic arg genObjectExpression have added []
// push(`[${node.content}]`, node)
push(`${node.content}`, node)
} }
} }
...@@ -389,21 +447,76 @@ function genCallExpression(node: CallExpression, context: CodegenContext) { ...@@ -389,21 +447,76 @@ function genCallExpression(node: CallExpression, context: CodegenContext) {
const { push, helper } = context const { push, helper } = context
const callee = isString(node.callee) ? node.callee : helper(node.callee) const callee = isString(node.callee) ? node.callee : helper(node.callee)
push(callee + `(`, node) push(callee + `(`, node)
if (callee === helper(RENDER_LIST)) {
genRenderList(node)
}
if (callee === helper(TO_HANDLERS)) {
genToHandlers(node, push)
}
genNodeList(node.arguments, context) genNodeList(node.arguments, context)
push(`)`) push(`)`)
} }
function genRenderList(node: CallExpression) {
node.arguments.forEach((argument: any) => {
if (argument.type === NodeTypes.JS_FUNCTION_EXPRESSION) {
argument.returnType = 'VNode'
}
})
}
function genToHandlers(
node: CallExpression,
push: (code: string, node?: CodegenNode) => void
) {
push(`new Map<string, any | null>([`)
const argument = node.arguments[0]
if (
(argument as CompoundExpressionNode)?.type === NodeTypes.COMPOUND_EXPRESSION
) {
;(argument as CompoundExpressionNode).children.forEach(
(child: any, index: number) => {
if (isString(child)) {
if (
index ===
(argument as CompoundExpressionNode).children.length - 1
) {
;(argument as CompoundExpressionNode).children[index] =
child.replace('}', '])')
} else {
;(argument as CompoundExpressionNode).children[index] = child
.replace('{', '["')
.replace(',', ',["')
.replace(':', '",')
.replaceAll(' ', '')
}
} else {
child.content = child.content += ']'
}
}
)
}
if (
(argument as SimpleExpressionNode)?.type === NodeTypes.SIMPLE_EXPRESSION
) {
;(argument as SimpleExpressionNode).content =
object2Map((argument as SimpleExpressionNode).content, false) + '])'
}
}
function genObjectExpression(node: ObjectExpression, context: CodegenContext) { function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
const { push, indent, deindent, newline } = context const { push, indent, deindent, newline } = context
const { properties } = node const { properties } = node
if (!properties.length) { if (!properties.length) {
push(`new Map<string,any>()`, node) push(`new Map<string, any | null>()`, node)
return return
} }
const multilines = const multilines =
properties.length > 1 || properties.length > 1 ||
properties.some((p) => p.value.type !== NodeTypes.SIMPLE_EXPRESSION) properties.some((p) => p.value.type !== NodeTypes.SIMPLE_EXPRESSION)
push(`new Map<string,any>([`) push(`new Map<string, any | null>([`)
multilines && indent() multilines && indent()
for (let i = 0; i < properties.length; i++) { for (let i = 0; i < properties.length; i++) {
const { key, value } = properties[i] const { key, value } = properties[i]
...@@ -436,7 +549,8 @@ function genFunctionExpression( ...@@ -436,7 +549,8 @@ function genFunctionExpression(
const { params, returns, body, newline, isSlot } = node const { params, returns, body, newline, isSlot } = node
if (isSlot) { if (isSlot) {
// wrap slot functions with owner context // wrap slot functions with owner context
push(`_${helperNameMap[WITH_CTX]}(`) // push(`_${helperNameMap[WITH_CTX]}(`)
push('(')
} }
push(`(`, node) push(`(`, node)
if (isArray(params)) { if (isArray(params)) {
...@@ -444,7 +558,19 @@ function genFunctionExpression( ...@@ -444,7 +558,19 @@ function genFunctionExpression(
} else if (params) { } else if (params) {
genNode(params, context) genNode(params, context)
} }
push(`) => `) if ((node as any).returnType) {
push(`):${(node as any).returnType} => `)
} else {
if (isSlot) {
if (params) {
push(`: Map<string, any | null>): any[] => `)
} else {
push(`): any[] => `)
}
} else {
push(`) => `)
}
}
if (newline || body) { if (newline || body) {
push(`{`) push(`{`)
indent() indent()
...@@ -476,16 +602,13 @@ function genConditionalExpression( ...@@ -476,16 +602,13 @@ function genConditionalExpression(
) { ) {
const { test, consequent, alternate, newline: needNewline } = node const { test, consequent, alternate, newline: needNewline } = node
const { push, indent, deindent, newline } = context const { push, indent, deindent, newline } = context
push(`${context.helper(IS_TRUE)}(`)
if (test.type === NodeTypes.SIMPLE_EXPRESSION) { if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
const needsParens = !isSimpleIdentifier(test.content)
needsParens && push(`(`)
genExpression(test, context) genExpression(test, context)
needsParens && push(`)`)
} else { } else {
push(`(`)
genNode(test, context) genNode(test, context)
push(`)`)
} }
push(`)`)
needNewline && indent() needNewline && indent()
context.indentLevel++ context.indentLevel++
needNewline || push(` `) needNewline || push(` `)
......
...@@ -3,16 +3,27 @@ import { ...@@ -3,16 +3,27 @@ import {
baseParse, baseParse,
trackSlotScopes, trackSlotScopes,
trackVForSlotScopes, trackVForSlotScopes,
transformBind,
transformElement, transformElement,
transformExpression, transformExpression,
transformModel,
transformOn,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { isAppUVueNativeTag } from '@dcloudio/uni-shared'
import { transformTapToClick } from '@dcloudio/uni-cli-shared'
import './runtimeHelpers'
import { CodegenResult, CompilerOptions } from './options' import { CodegenResult, CompilerOptions } from './options'
import { generate } from './codegen' import { generate } from './codegen'
import { DirectiveTransform, NodeTransform, transform } from './transform' import { DirectiveTransform, NodeTransform, transform } from './transform'
import { transformIf } from './transforms/vIf'
import { transformFor } from './transforms/vFor'
import { transformModel } from './transforms/vModel'
import { transformShow } from './transforms/vShow'
import { transformVText } from './transforms/vText'
import { transformInterpolation } from './transforms/transformInterpolation'
import { transformText } from './transforms/transformText'
import { transformOn } from './transforms/vOn'
import { transformBind } from './transforms/vBind'
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
export type TransformPreset = [ export type TransformPreset = [
NodeTransform[], NodeTransform[],
...@@ -24,16 +35,24 @@ export function getBaseTransformPreset( ...@@ -24,16 +35,24 @@ export function getBaseTransformPreset(
): TransformPreset { ): TransformPreset {
return [ return [
[ [
transformIf,
transformFor,
// order is important // order is important
trackVForSlotScopes, trackVForSlotScopes,
transformExpression, transformExpression,
transformSlotOutlet,
transformElement, transformElement,
trackSlotScopes, trackSlotScopes,
transformText,
transformTapToClick,
transformInterpolation,
] as any, ] as any,
{ {
on: transformOn, on: transformOn,
bind: transformBind, bind: transformBind,
model: transformModel, model: transformModel,
show: transformShow,
text: transformVText,
} as any, } as any,
] ]
} }
...@@ -43,8 +62,8 @@ export function compile( ...@@ -43,8 +62,8 @@ export function compile(
options: CompilerOptions options: CompilerOptions
): CodegenResult { ): CodegenResult {
const ast = baseParse(template, { const ast = baseParse(template, {
isCustomElement(tag) { isNativeTag(tag) {
return true return isAppUVueNativeTag(tag)
}, },
}) })
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset( const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
......
import { CompilerError } from '@vue/compiler-core' import { BindingMetadata, CompilerError } from '@vue/compiler-core'
import { RawSourceMap } from 'source-map' import { RawSourceMap } from 'source-map'
import { DirectiveTransform, NodeTransform } from './transform' import { DirectiveTransform, NodeTransform } from './transform'
...@@ -9,6 +9,11 @@ interface SharedTransformCodegenOptions { ...@@ -9,6 +9,11 @@ interface SharedTransformCodegenOptions {
* @default false * @default false
*/ */
prefixIdentifiers?: boolean prefixIdentifiers?: boolean
/**
* Optional binding metadata analyzed from script - used to optimize
* binding access when `prefixIdentifiers` is enabled.
*/
bindingMetadata?: BindingMetadata
/** /**
* Filename for source map generation. * Filename for source map generation.
* Also used for self-recursive reference in templates * Also used for self-recursive reference in templates
...@@ -56,6 +61,16 @@ export interface TransformOptions ...@@ -56,6 +61,16 @@ export interface TransformOptions
* Used by some transforms that expects only native elements * Used by some transforms that expects only native elements
*/ */
isCustomElement?: (tag: string) => boolean | void isCustomElement?: (tag: string) => boolean | void
/**
* SFC scoped styles ID
*/
scopeId?: string | null
/**
* Indicates this SFC template has used :slotted in its styles
* Defaults to `true` for backwards compatibility - SFC tooling should set it
* to `false` if no `:slotted` usage is detected in `<style>`
*/
slotted?: boolean
} }
export type CompilerOptions = TransformOptions & CodegenOptions export type CompilerOptions = TransformOptions & CodegenOptions
......
import { registerRuntimeHelpers } from '@vue/compiler-core'
export const IS_TRUE = Symbol(`isTrue`)
export const V_SHOW = Symbol(`vShow`)
export const RENDER_LIST = Symbol(`renderList`)
export const FRAGMENT = Symbol(`Fragment`)
export const OPEN_BLOCK = Symbol(`openBlock`)
export const RESOLVE_COMPONENT = Symbol(`resolveComponent`)
export const RESOLVE_DIRECTIVE = Symbol(`resolveDirective`)
export const RENDER_SLOT = Symbol(`renderSlot`)
export const TO_HANDLERS = Symbol(`toHandlers`)
registerRuntimeHelpers({
[IS_TRUE]: 'isTrue',
[V_SHOW]: 'vShow',
[RENDER_LIST]: 'RenderHelpers.renderList',
[FRAGMENT]: 'Fragment',
[RESOLVE_COMPONENT]: 'resolveComponent',
[RESOLVE_DIRECTIVE]: 'resolveDirective',
[RENDER_SLOT]: `renderSlot`,
[TO_HANDLERS]: `toHandlers`,
})
...@@ -4,6 +4,7 @@ import { ...@@ -4,6 +4,7 @@ import {
ConstantTypes, ConstantTypes,
DirectiveNode, DirectiveNode,
ElementNode, ElementNode,
ElementTypes,
ExpressionNode, ExpressionNode,
JSChildNode, JSChildNode,
NodeTypes, NodeTypes,
...@@ -17,6 +18,7 @@ import { ...@@ -17,6 +18,7 @@ import {
createCacheExpression, createCacheExpression,
helperNameMap, helperNameMap,
isSlotOutlet, isSlotOutlet,
isVSlot,
makeBlock, makeBlock,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { NOOP, camelize, capitalize, isArray, isString } from '@vue/shared' import { NOOP, camelize, capitalize, isArray, isString } from '@vue/shared'
...@@ -105,6 +107,8 @@ export function createTransformContext( ...@@ -105,6 +107,8 @@ export function createTransformContext(
prefixIdentifiers = false, prefixIdentifiers = false,
nodeTransforms = [], nodeTransforms = [],
directiveTransforms = {}, directiveTransforms = {},
scopeId = null,
slotted = true,
isBuiltInComponent = NOOP, isBuiltInComponent = NOOP,
isCustomElement = NOOP, isCustomElement = NOOP,
onError = defaultOnError, onError = defaultOnError,
...@@ -117,10 +121,13 @@ export function createTransformContext( ...@@ -117,10 +121,13 @@ export function createTransformContext(
targetLanguage, targetLanguage,
selfName: nameMatch && capitalize(camelize(nameMatch[1])), selfName: nameMatch && capitalize(camelize(nameMatch[1])),
prefixIdentifiers, prefixIdentifiers,
bindingMetadata: {},
nodeTransforms, nodeTransforms,
directiveTransforms, directiveTransforms,
isBuiltInComponent, isBuiltInComponent,
isCustomElement, isCustomElement,
scopeId,
slotted,
onError, onError,
onWarn, onWarn,
...@@ -163,7 +170,8 @@ export function createTransformContext( ...@@ -163,7 +170,8 @@ export function createTransformContext(
} }
}, },
helperString(name) { helperString(name) {
return `_${helperNameMap[context.helper(name)]}` // return `_${helperNameMap[context.helper(name)]}`
return `${helperNameMap[context.helper(name)]}`
}, },
replaceNode(node) { replaceNode(node) {
if (!context.currentNode) { if (!context.currentNode) {
...@@ -245,6 +253,7 @@ export function transform(root: RootNode, options: TransformOptions) { ...@@ -245,6 +253,7 @@ export function transform(root: RootNode, options: TransformOptions) {
const context = createTransformContext(root, options) const context = createTransformContext(root, options)
traverseNode(root, context) traverseNode(root, context)
createRootCodegen(root, context) createRootCodegen(root, context)
root.components = [...context.components]
} }
export function isSingleElementRoot( export function isSingleElementRoot(
...@@ -378,3 +387,37 @@ export function traverseNode( ...@@ -378,3 +387,37 @@ export function traverseNode(
exitFns[i]() exitFns[i]()
} }
} }
export function createStructuralDirectiveTransform(
name: string | RegExp,
fn: StructuralDirectiveTransform
): NodeTransform {
const matches = isString(name)
? (n: string) => n === name
: (n: string) => name.test(n)
return (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
const { props } = node
// structural directive transforms are not concerned with slots
// as they are handled separately in vSlot.ts
if (node.tagType === ElementTypes.TEMPLATE && props.some(isVSlot)) {
return
}
const exitFns = []
for (let i = 0; i < props.length; i++) {
const prop = props[i]
if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {
// structural directives are removed to avoid infinite recursion
// also we remove them *before* applying so that it can further
// traverse itself in case it moves the node around
props.splice(i, 1)
i--
const onExit = fn(node, prop, context)
if (onExit) exitFns.push(onExit)
}
}
return exitFns
}
}
}
// - Parse expressions in templates into compound expressions so that each
// identifier gets more accurate source-map locations.
//
// - Prefix identifiers with `_ctx.` or `$xxx` (for known binding types) so that
// they are accessed from the right source
//
// - This transform is only applied in non-browser builds because it relies on
// an additional JavaScript parser. In the browser, there is no source-map
// support and the code is wrapped in `with (this) { ... }`.
import { NodeTransform, TransformContext } from '../transform'
import { makeMap, hasOwn, isString } from '@vue/shared'
import { Node, Identifier } from '@babel/types'
import { parse } from '@babel/parser'
import {
advancePositionWithClone,
BindingTypes,
CompoundExpressionNode,
ConstantTypes,
createCompoundExpression,
createSimpleExpression,
ExpressionNode,
isSimpleIdentifier,
isStaticProperty,
isStaticPropertyKey,
NodeTypes,
SimpleExpressionNode,
walkIdentifiers,
} from '@vue/compiler-core'
import { createCompilerError, ErrorCodes } from '../errors'
const GLOBALS_WHITE_LISTED = `Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,
decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,
Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console`
const isGloballyWhitelisted = /*#__PURE__*/ makeMap(GLOBALS_WHITE_LISTED)
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
export const transformExpression: NodeTransform = (node, context) => {
if (node.type === NodeTypes.INTERPOLATION) {
node.content = processExpression(
node.content as SimpleExpressionNode,
context
)
} else if (node.type === NodeTypes.ELEMENT) {
// handle directives on element
for (let i = 0; i < node.props.length; i++) {
const dir = node.props[i]
// do not process for v-on & v-for since they are special handled
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
const exp = dir.exp
const arg = dir.arg
// do not process exp if this is v-on:arg - we need special handling
// for wrapping inline statements.
if (
exp &&
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
!(dir.name === 'on' && arg)
) {
dir.exp = processExpression(
exp,
context,
// slot args must be processed as function params
dir.name === 'slot'
)
}
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
dir.arg = processExpression(arg, context)
}
}
}
}
}
interface PrefixMeta {
prefix?: string
isConstant: boolean
start: number
end: number
scopeIds?: Set<string>
}
// Important: since this function uses Node.js only dependencies, it should
// always be used with a leading !__BROWSER__ check so that it can be
// tree-shaken from the browser build.
export function processExpression(
node: SimpleExpressionNode,
context: TransformContext,
// some expressions like v-slot props & v-for aliases should be parsed as
// function params
asParams = false,
// v-on handler values may contain multiple statements
asRawStatements = false,
localVars: Record<string, number> = Object.create(context.identifiers)
): ExpressionNode {
if (!context.prefixIdentifiers || !node.content.trim()) {
return node
}
const { bindingMetadata } = context
const rewriteIdentifier = (raw: string, parent?: Node, id?: Identifier) => {
const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw]
if (type && type.startsWith('setup')) {
// setup bindings in non-inline mode
return `$setup.${raw}`
} else if (type === BindingTypes.PROPS_ALIASED) {
return `$props['${bindingMetadata.__propsAliases![raw]}']`
} else if (type) {
return `$${type}.${raw}`
}
// fallback to ctx
return `_ctx.${raw}`
}
// fast path if expression is a simple identifier.
const rawExp = node.content
// bail constant on parens (function invocation) and dot (member access)
const bailConstant = rawExp.indexOf(`(`) > -1 || rawExp.indexOf('.') > 0
if (isSimpleIdentifier(rawExp)) {
const isScopeVarReference = context.identifiers[rawExp]
const isAllowedGlobal = isGloballyWhitelisted(rawExp)
const isLiteral = isLiteralWhitelisted(rawExp)
if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) {
// const bindings exposed from setup can be skipped for patching but
// cannot be hoisted to module scope
if (bindingMetadata[node.content] === BindingTypes.SETUP_CONST) {
node.constType = ConstantTypes.CAN_SKIP_PATCH
}
node.content = rewriteIdentifier(rawExp)
} else if (!isScopeVarReference) {
if (isLiteral) {
node.constType = ConstantTypes.CAN_STRINGIFY
} else {
node.constType = ConstantTypes.CAN_HOIST
}
}
return node
}
let ast: any
// exp needs to be parsed differently:
// 1. Multiple inline statements (v-on, with presence of `;`): parse as raw
// exp, but make sure to pad with spaces for consistent ranges
// 2. Expressions: wrap with parens (for e.g. object expressions)
// 3. Function arguments (v-for, v-slot): place in a function argument position
const source = asRawStatements
? ` ${rawExp} `
: `(${rawExp})${asParams ? `=>{}` : ``}`
try {
ast = parse(source, {
// plugins: context.expressionPlugins
}).program
} catch (e: any) {
context.onError(
createCompilerError(
ErrorCodes.X_INVALID_EXPRESSION,
node.loc,
undefined,
e.message
)
)
return node
}
type QualifiedId = Identifier & PrefixMeta
const ids: QualifiedId[] = []
const parentStack: Node[] = []
const knownIds: Record<string, number> = Object.create(context.identifiers)
walkIdentifiers(
ast,
(node, parent, _, isReferenced, isLocal) => {
if (isStaticPropertyKey(node, parent!)) {
return
}
const needPrefix = isReferenced && canPrefix(node)
if (needPrefix && !isLocal) {
if (isStaticProperty(parent!) && parent.shorthand) {
// property shorthand like { foo }, we need to add the key since
// we rewrite the value
;(node as QualifiedId).prefix = `${node.name}: `
}
node.name = rewriteIdentifier(node.name, parent, node)
ids.push(node as QualifiedId)
} else {
// The identifier is considered constant unless it's pointing to a
// local scope variable (a v-for alias, or a v-slot prop)
if (!(needPrefix && isLocal) && !bailConstant) {
;(node as QualifiedId).isConstant = true
}
// also generate sub-expressions for other identifiers for better
// source map support. (except for property keys which are static)
ids.push(node as QualifiedId)
}
},
true, // invoke on ALL identifiers
parentStack,
knownIds
)
// We break up the compound expression into an array of strings and sub
// expressions (for identifiers that have been prefixed). In codegen, if
// an ExpressionNode has the `.children` property, it will be used instead of
// `.content`.
const children: CompoundExpressionNode['children'] = []
ids.sort((a, b) => a.start - b.start)
ids.forEach((id, i) => {
// range is offset by -1 due to the wrapping parens when parsed
const start = id.start - 1
const end = id.end - 1
const last = ids[i - 1]
const leadingText = rawExp.slice(last ? last.end - 1 : 0, start)
if (leadingText.length || id.prefix) {
children.push(leadingText + (id.prefix || ``))
}
const source = rawExp.slice(start, end)
children.push(
createSimpleExpression(
id.name,
false,
{
source,
start: advancePositionWithClone(node.loc.start, source, start),
end: advancePositionWithClone(node.loc.start, source, end),
},
id.isConstant ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
)
)
if (i === ids.length - 1 && end < rawExp.length) {
children.push(rawExp.slice(end))
}
})
let ret
if (children.length) {
ret = createCompoundExpression(children, node.loc)
} else {
ret = node
ret.constType = bailConstant
? ConstantTypes.NOT_CONSTANT
: ConstantTypes.CAN_STRINGIFY
}
ret.identifiers = Object.keys(knownIds)
return ret
}
function canPrefix(id: Identifier) {
// skip whitelisted globals
if (isGloballyWhitelisted(id.name)) {
return false
}
// special case for webpack compilation
if (id.name === 'require') {
return false
}
return true
}
export function stringifyExpression(exp: ExpressionNode | string): string {
if (isString(exp)) {
return exp
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
return exp.content
} else {
return (exp.children as (ExpressionNode | string)[])
.map(stringifyExpression)
.join('')
}
}
import { NodeTransform } from '../transform'
import {
NodeTypes,
CompoundExpressionNode,
// createCallExpression,
// CallExpression,
// ElementTypes,
createCompoundExpression,
TextNode,
InterpolationNode,
TemplateChildNode,
} from '@vue/compiler-core'
// import { CREATE_TEXT } from '../runtimeHelpers'
export function isText(
node: TemplateChildNode
): node is TextNode | InterpolationNode {
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
}
// Merge adjacent text nodes and expressions into a single expression
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
export const transformInterpolation: NodeTransform = (node, context) => {
if (
node.type === NodeTypes.ROOT ||
node.type === NodeTypes.ELEMENT ||
node.type === NodeTypes.FOR ||
node.type === NodeTypes.IF_BRANCH
) {
// perform the transform on node exit so that all expressions have already
// been processed.
return () => {
const children = node.children
let currentContainer: CompoundExpressionNode | undefined = undefined
// let hasText = false
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child)) {
// hasText = true
for (let j = i + 1; j < children.length; j++) {
const next = children[j]
if (isText(next)) {
if (!currentContainer) {
currentContainer = children[i] = createCompoundExpression(
[child],
child.loc
)
}
// merge adjacent text node into current
currentContainer.children.push(` + `, next)
children.splice(j, 1)
j--
} else {
currentContainer = undefined
break
}
}
}
}
// if (
// !hasText ||
// // if this is a plain element with a single text child, leave it
// // as-is since the runtime has dedicated fast path for this by directly
// // setting textContent of the element.
// // for component root it's always normalized anyway.
// (children.length === 1 &&
// (node.type === NodeTypes.ROOT ||
// (node.type === NodeTypes.ELEMENT &&
// node.tagType === ElementTypes.ELEMENT &&
// // #3756
// // custom directives can potentially add DOM elements arbitrarily,
// // we need to avoid setting textContent of the element at runtime
// // to avoid accidentally overwriting the DOM elements added
// // by the user through custom directives.
// !node.props.find(
// p =>
// p.type === NodeTypes.DIRECTIVE &&
// !context.directiveTransforms[p.name]
// ) &&
// // in compat mode, <template> tags with no special directives
// // will be rendered as a fragment so its children must be
// // converted into vnodes.
// (node.tag !== 'template'))))
// ) {
// return
// }
// pre-convert text nodes into createTextVNode(text) calls to avoid
// runtime normalization.
// for (let i = 0; i < children.length; i++) {
// const child = children[i]
// if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
// const callArgs: CallExpression['arguments'] = []
// // createTextVNode defaults to single whitespace, so if it is a
// // single space the code could be an empty call to save bytes.
// if (child.type !== NodeTypes.TEXT || child.content !== ' ') {
// callArgs.push(child)
// }
// // mark dynamic text with flag so it gets patched inside a block
// if (
// !context.ssr &&
// getConstantType(child, context) === ConstantTypes.NOT_CONSTANT
// ) {
// callArgs.push(
// PatchFlags.TEXT +
// (__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.TEXT]} */` : ``)
// )
// }
// children[i] = {
// type: NodeTypes.TEXT_CALL,
// content: child,
// loc: child.loc,
// codegenNode: createCallExpression(
// context.helper(CREATE_TEXT),
// callArgs
// )
// }
// }
// }
}
}
}
import { NodeTransform, TransformContext } from '../transform'
import {
NodeTypes,
CallExpression,
createCallExpression,
createFunctionExpression,
ExpressionNode,
SlotOutletNode,
buildProps,
createCompilerError,
ErrorCodes,
} from '@vue/compiler-core'
import { isSlotOutlet, isStaticArgOf, isStaticExp } from '@vue/compiler-core'
import { PropsExpression } from '@vue/compiler-core'
import { RENDER_SLOT } from '../runtimeHelpers'
import { camelize } from '@vue/shared'
export const transformSlotOutlet: NodeTransform = (node, context) => {
if (isSlotOutlet(node)) {
const { children, loc } = node
const { slotName, slotProps } = processSlotOutlet(node, context)
const slotArgs: CallExpression['arguments'] = [
context.prefixIdentifiers ? `_ctx.$slots` : `$slots`,
slotName,
'{}',
'undefined',
'true',
]
let expectedLen = 2
if (slotProps) {
slotArgs[2] = slotProps
expectedLen = 3
}
if (children.length) {
slotArgs[3] = createFunctionExpression([], children, false, false, loc)
expectedLen = 4
}
if (context.scopeId && !context.slotted) {
expectedLen = 5
}
slotArgs.splice(expectedLen) // remove unused arguments
;(node as any).codegenNode = createCallExpression(
context.helper(RENDER_SLOT),
slotArgs,
loc
)
}
}
interface SlotOutletProcessResult {
slotName: string | ExpressionNode
slotProps: PropsExpression | undefined
}
export function processSlotOutlet(
node: SlotOutletNode,
context: TransformContext
): SlotOutletProcessResult {
let slotName: string | ExpressionNode = `"default"`
let slotProps: PropsExpression | undefined = undefined
const nonNameProps = []
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (p.type === NodeTypes.ATTRIBUTE) {
if (p.value) {
if (p.name === 'name') {
slotName = JSON.stringify(p.value.content)
} else {
p.name = camelize(p.name)
nonNameProps.push(p)
}
}
} else {
if (p.name === 'bind' && isStaticArgOf(p.arg, 'name')) {
if (p.exp) slotName = p.exp
} else {
if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {
p.arg.content = camelize(p.arg.content)
}
nonNameProps.push(p)
}
}
}
if (nonNameProps.length > 0) {
const { props, directives } = buildProps(
node,
context as any,
nonNameProps,
false,
false
)
slotProps = props
if (directives.length) {
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
directives[0].loc
)
)
}
}
return {
slotName,
slotProps,
}
}
import { isElementNode } from '@dcloudio/uni-cli-shared'
import {
CompoundExpressionNode,
ElementNode,
ElementTypes,
InterpolationNode,
NodeTypes,
TemplateChildNode,
TextCallNode,
TextNode,
} from '@vue/compiler-core'
import { NodeTransform } from '../transform'
function isTextNode({ tag }: ElementNode) {
return tag === 'text' || tag === 'u-text' || tag === 'button'
}
function isTextElement(node: TemplateChildNode) {
return node.type === NodeTypes.ELEMENT && node.tag === 'text'
}
function isText(
node: TemplateChildNode
): node is
| TextNode
| TextCallNode
| InterpolationNode
| CompoundExpressionNode {
const { type } = node
return (
type === NodeTypes.TEXT ||
type === NodeTypes.TEXT_CALL ||
type === NodeTypes.INTERPOLATION ||
type === NodeTypes.COMPOUND_EXPRESSION
)
}
export const transformText: NodeTransform = (node, _) => {
if (!isElementNode(node)) {
return
}
if (isTextNode(node)) {
return
}
const { children } = node
if (!children.length) {
return
}
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isTextElement(child)) {
parseText(child as ElementNode)
}
let currentContainer: ElementNode | undefined = undefined
if (isText(child)) {
if (!currentContainer) {
currentContainer = children[i] = createText(node, child)
}
for (let j = i + 1; j < children.length; j++) {
const next = children[j]
if (isText(next)) {
// 合并相邻的文本节点
currentContainer.children.push(next)
children.splice(j, 1)
j--
} else {
currentContainer = undefined
break
}
}
}
}
}
/*
1. 转换 \\n 为 \n
2. u-text 下仅支持 slot 及 文本节点
*/
function parseText(node: ElementNode) {
if (node.children.length) {
let firstTextChild
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i]
if (isText(child) && typeof (child as TextNode).content === 'string') {
if (!firstTextChild) {
firstTextChild = child
;(firstTextChild as TextNode).content = (
firstTextChild as TextNode
).content.replace(/\\n/g, '\n')
} else {
;(firstTextChild as TextNode).content += (
child as TextNode
).content.replace(/\\n/g, '\n')
node.children.splice(i, 1)
i--
}
} else if (child.type === 3) {
node.children.splice(i, 1)
i--
} else {
firstTextChild = null
}
}
}
}
function createText(
parent: ElementNode,
node: TextNode | TextCallNode | InterpolationNode | CompoundExpressionNode
): ElementNode {
return {
tag: 'text',
type: NodeTypes.ELEMENT,
tagType: ElementTypes.ELEMENT,
props: [],
isSelfClosing: false,
children: [node],
codegenNode: undefined,
ns: parent.ns,
loc: node.loc,
}
}
import { DirectiveTransform } from '../transform'
import {
createObjectProperty,
createSimpleExpression,
ExpressionNode,
NodeTypes,
} from '@vue/compiler-core'
import { createCompilerError, ErrorCodes } from '../errors'
import { camelize } from '@vue/shared'
import { CAMELIZE } from '@vue/compiler-core'
import { objectExp, object2Map } from '../utils'
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-bind
// *with* args.
export const transformBind: DirectiveTransform = (dir, _node, context) => {
const { exp, modifiers, loc } = dir
const arg = dir.arg!
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
arg.children.unshift(`(`)
arg.children.push(`) || ""`)
} else if (!arg.isStatic) {
arg.content = `${arg.content} !== null ? ${arg.content} : ""`
}
// .sync is replaced by v-model:arg
if (modifiers.includes('camel')) {
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
if (arg.isStatic) {
arg.content = camelize(arg.content)
} else {
arg.content = `${context.helperString(CAMELIZE)}(${arg.content})`
}
} else {
arg.children.unshift(`${context.helperString(CAMELIZE)}(`)
arg.children.push(`)`)
}
}
if (!false) {
if (modifiers.includes('prop')) {
injectPrefix(arg, '.')
}
if (modifiers.includes('attr')) {
injectPrefix(arg, '^')
}
}
if (
!exp ||
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
) {
context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
return {
props: [createObjectProperty(arg, createSimpleExpression('', true, loc))],
}
}
if ((exp as any).content && objectExp.test((exp as any).content)) {
;(exp as any).content = object2Map(exp)
} else if ((exp as any).children) {
;(exp as any).children.forEach(
(child: ExpressionNode | string, index: number) => {
if (typeof child === 'string' && objectExp.test(child)) {
;(exp as any).children[index] = object2Map(child)
}
}
)
}
return {
props: [createObjectProperty(arg, exp)],
}
}
const injectPrefix = (arg: ExpressionNode, prefix: string) => {
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
if (arg.isStatic) {
arg.content = prefix + arg.content
} else {
arg.content = `\`${prefix}\${${arg.content}}\``
}
} else {
arg.children.unshift(`'${prefix}' + (`)
arg.children.push(`)`)
}
}
import {
NodeTypes,
ExpressionNode,
createSimpleExpression,
SourceLocation,
SimpleExpressionNode,
createCallExpression,
createFunctionExpression,
createObjectExpression,
createObjectProperty,
ForCodegenNode,
RenderSlotCall,
SlotOutletNode,
ElementNode,
DirectiveNode,
ForNode,
PlainElementNode,
createVNodeCall,
VNodeCall,
ForRenderListExpression,
BlockCodegenNode,
ForIteratorExpression,
ConstantTypes,
// createBlockStatement,
// createCompoundExpression
} from '@vue/compiler-core'
import { createCompilerError, ErrorCodes } from '../errors'
import {
getInnerRange,
findProp,
isTemplateNode,
isSlotOutlet,
injectProp,
getVNodeBlockHelper,
getVNodeHelper,
// findDir
} from '@vue/compiler-core'
import {
RENDER_LIST,
OPEN_BLOCK,
FRAGMENT,
// IS_MEMO_SAME
} from '../runtimeHelpers'
import { processExpression } from './transformExpression'
// import { validateBrowserExpression } from '../validateExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
import {
TransformContext,
createStructuralDirectiveTransform,
} from '../transform'
export const transformFor = createStructuralDirectiveTransform(
'for',
(node, dir, context) => {
const { helper, removeHelper } = context
return processFor(node, dir, context, (forNode) => {
// create the loop render function expression now, and add the
// iterator on exit after all children have been traversed
const renderExp = createCallExpression(helper(RENDER_LIST), [
forNode.source,
]) as ForRenderListExpression
const isTemplate = isTemplateNode(node)
// const memo = findDir(node, 'memo')
const keyProp = findProp(node, `key`)
const keyExp =
keyProp &&
(keyProp.type === NodeTypes.ATTRIBUTE
? createSimpleExpression(keyProp.value!.content, true)
: keyProp.exp!)
const keyProperty = keyProp ? createObjectProperty(`key`, keyExp!) : null
// if (!__BROWSER__ && isTemplate) {
if (isTemplate) {
// #2085 / #5288 process :key and v-memo expressions need to be
// processed on `<template v-for>`. In this case the node is discarded
// and never traversed so its binding expressions won't be processed
// by the normal transforms.
// if (memo) {
// memo.exp = processExpression(
// memo.exp! as SimpleExpressionNode,
// context
// )
// }
if (keyProperty && keyProp!.type !== NodeTypes.ATTRIBUTE) {
keyProperty.value = processExpression(
keyProperty.value as SimpleExpressionNode,
context
)
}
}
const isStableFragment =
forNode.source.type === NodeTypes.SIMPLE_EXPRESSION &&
forNode.source.constType > ConstantTypes.NOT_CONSTANT
const fragmentFlag = isStableFragment
? PatchFlags.STABLE_FRAGMENT
: keyProp
? PatchFlags.KEYED_FRAGMENT
: PatchFlags.UNKEYED_FRAGMENT
forNode.codegenNode = createVNodeCall(
context as any,
helper(FRAGMENT),
undefined,
renderExp,
fragmentFlag +
// (__DEV__ ? ` /* ${PatchFlagNames[fragmentFlag]} */` : ``),
` /* ${PatchFlagNames[fragmentFlag]} */`,
undefined,
undefined,
true /* isBlock */,
!isStableFragment /* disableTracking */,
false /* isComponent */,
node.loc
) as ForCodegenNode
return () => {
// finish the codegen now that all children have been traversed
let childBlock: BlockCodegenNode
const { children } = forNode
// check <template v-for> key placement
// if ((__DEV__ || !__BROWSER__) && isTemplate) {
if (isTemplate) {
node.children.some((c) => {
if (c.type === NodeTypes.ELEMENT) {
const key = findProp(c, 'key')
if (key) {
context.onError(
createCompilerError(
ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT,
key.loc
)
)
return true
}
}
})
}
const needFragmentWrapper =
children.length !== 1 || children[0].type !== NodeTypes.ELEMENT
const slotOutlet = isSlotOutlet(node)
? node
: isTemplate &&
node.children.length === 1 &&
isSlotOutlet(node.children[0])
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
: null
if (slotOutlet) {
// <slot v-for="..."> or <template v-for="..."><slot/></template>
childBlock = slotOutlet.codegenNode as RenderSlotCall
if (isTemplate && keyProperty) {
// <template v-for="..." :key="..."><slot/></template>
// we need to inject the key to the renderSlot() call.
// the props for renderSlot is passed as the 3rd argument.
injectProp(childBlock, keyProperty, context as any)
}
} else if (needFragmentWrapper) {
// <template v-for="..."> with text or multi-elements
// should generate a fragment block for each loop
childBlock = createVNodeCall(
context as any,
helper(FRAGMENT),
keyProperty ? createObjectExpression([keyProperty]) : undefined,
node.children,
PatchFlags.STABLE_FRAGMENT +
// (__DEV__
// ? ` /* ${PatchFlagNames[PatchFlags.STABLE_FRAGMENT]} */`
// : ``),
` /* ${PatchFlagNames[PatchFlags.STABLE_FRAGMENT]} */`,
undefined,
undefined,
true,
undefined,
false /* isComponent */
)
} else {
// Normal element v-for. Directly use the child's codegenNode
// but mark it as a block.
childBlock = (children[0] as PlainElementNode)
.codegenNode as VNodeCall
if (isTemplate && keyProperty) {
injectProp(childBlock, keyProperty, context as any)
}
if (childBlock.isBlock !== !isStableFragment) {
if (childBlock.isBlock) {
// switch from block to vnode
removeHelper(OPEN_BLOCK)
removeHelper(getVNodeBlockHelper(false, childBlock.isComponent))
} else {
// switch from vnode to block
removeHelper(getVNodeHelper(false, childBlock.isComponent))
}
}
childBlock.isBlock = !isStableFragment
if (childBlock.isBlock) {
helper(OPEN_BLOCK)
helper(getVNodeBlockHelper(false, childBlock.isComponent))
} else {
helper(getVNodeHelper(false, childBlock.isComponent))
}
}
// if (memo) {
// const loop = createFunctionExpression(
// createForLoopParams(forNode.parseResult, [
// createSimpleExpression(`_cached`)
// ])
// )
// loop.body = createBlockStatement([
// createCompoundExpression([`const _memo = (`, memo.exp!, `)`]),
// createCompoundExpression([
// `if (_cached`,
// ...(keyExp ? [` && _cached.key === `, keyExp] : []),
// ` && ${context.helperString(
// IS_MEMO_SAME
// )}(_cached, _memo)) return _cached`
// ]),
// createCompoundExpression([`const _item = `, childBlock as any]),
// createSimpleExpression(`_item.memo = _memo`),
// createSimpleExpression(`return _item`)
// ])
// renderExp.arguments.push(
// loop as ForIteratorExpression,
// createSimpleExpression(`_cache`),
// createSimpleExpression(String(context.cached++))
// )
// } else {
renderExp.arguments.push(
createFunctionExpression(
createForLoopParams(forNode.parseResult),
childBlock,
true /* force newline */
) as ForIteratorExpression
)
// }
}
})
}
)
// target-agnostic transform used for both Client and SSR
export function processFor(
node: ElementNode,
dir: DirectiveNode,
context: TransformContext,
processCodegen?: (forNode: ForNode) => (() => void) | undefined
) {
if (!dir.exp) {
context.onError(
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc)
)
return
}
const parseResult = parseForExpression(
// can only be simple expression because vFor transform is applied
// before expression transform.
dir.exp as SimpleExpressionNode,
context
)
if (!parseResult) {
context.onError(
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc)
)
return
}
const { addIdentifiers, removeIdentifiers, scopes } = context
const { source, value, key, index } = parseResult
if (key === undefined) {
parseResult.key = {
constType: 2,
content: '_',
isStatic: false,
loc: value?.loc!,
type: 4,
}
}
if (index === undefined) {
parseResult.index = {
constType: 2,
content: '_',
isStatic: false,
loc: value?.loc!,
type: 4,
}
}
const forNode: ForNode = {
type: NodeTypes.FOR,
loc: dir.loc,
source,
valueAlias: value,
keyAlias: key,
objectIndexAlias: index,
parseResult,
children: isTemplateNode(node) ? node.children : [node],
}
context.replaceNode(forNode)
// bookkeeping
scopes.vFor++
// if (!__BROWSER__ && context.prefixIdentifiers) {
if (context.prefixIdentifiers) {
// scope management
// inject identifiers to context
value && addIdentifiers(value)
key && addIdentifiers(key)
index && addIdentifiers(index)
}
const onExit = processCodegen && processCodegen(forNode)
return () => {
scopes.vFor--
// if (!__BROWSER__ && context.prefixIdentifiers) {
if (context.prefixIdentifiers) {
value && removeIdentifiers(value)
key && removeIdentifiers(key)
index && removeIdentifiers(index)
}
if (onExit) onExit()
}
}
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
// This regex doesn't cover the case if key or index aliases have destructuring,
// but those do not make sense in the first place, so this works in practice.
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g
export interface ForParseResult {
source: ExpressionNode
value: ExpressionNode | undefined
key: ExpressionNode | undefined
index: ExpressionNode | undefined
}
export function parseForExpression(
input: SimpleExpressionNode,
context: TransformContext
): ForParseResult | undefined {
const loc = input.loc
const exp = input.content
const inMatch = exp.match(forAliasRE)
if (!inMatch) return
const [, LHS, RHS] = inMatch
const result: ForParseResult = {
source: createAliasExpression(
loc,
RHS.trim(),
exp.indexOf(RHS, LHS.length)
),
value: undefined,
key: undefined,
index: undefined,
}
// if (!__BROWSER__ && context.prefixIdentifiers) {
if (context.prefixIdentifiers) {
result.source = processExpression(
result.source as SimpleExpressionNode,
context
)
}
// if (__DEV__ && __BROWSER__) {
// validateBrowserExpression(result.source as SimpleExpressionNode, context)
// }
let valueContent = LHS.trim().replace(stripParensRE, '').trim()
const trimmedOffset = LHS.indexOf(valueContent)
const iteratorMatch = valueContent.match(forIteratorRE)
if (iteratorMatch) {
valueContent = valueContent.replace(forIteratorRE, '').trim()
const keyContent = iteratorMatch[1].trim()
let keyOffset: number | undefined
if (keyContent) {
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
result.key = createAliasExpression(loc, keyContent, keyOffset)
// if (!__BROWSER__ && context.prefixIdentifiers) {
if (context.prefixIdentifiers) {
result.key = processExpression(result.key, context, true)
}
// if (__DEV__ && __BROWSER__) {
// validateBrowserExpression(
// result.key as SimpleExpressionNode,
// context,
// true
// )
// }
}
if (iteratorMatch[2]) {
const indexContent = iteratorMatch[2].trim()
if (indexContent) {
result.index = createAliasExpression(
loc,
indexContent,
exp.indexOf(
indexContent,
result.key
? keyOffset! + keyContent.length
: trimmedOffset + valueContent.length
)
)
// if (!__BROWSER__ && context.prefixIdentifiers) {
if (context.prefixIdentifiers) {
result.index = processExpression(result.index, context, true)
}
// if (__DEV__ && __BROWSER__) {
// validateBrowserExpression(
// result.index as SimpleExpressionNode,
// context,
// true
// )
// }
}
}
}
if (valueContent) {
result.value = createAliasExpression(loc, valueContent, trimmedOffset)
// if (!__BROWSER__ && context.prefixIdentifiers) {
if (context.prefixIdentifiers) {
result.value = processExpression(result.value, context, true)
}
// if (__DEV__ && __BROWSER__) {
// validateBrowserExpression(
// result.value as SimpleExpressionNode,
// context,
// true
// )
// }
}
return result
}
function createAliasExpression(
range: SourceLocation,
content: string,
offset: number
): SimpleExpressionNode {
return createSimpleExpression(
content,
false,
getInnerRange(range, offset, content.length)
)
}
export function createForLoopParams(
{ value, key, index }: ForParseResult,
memoArgs: ExpressionNode[] = []
): ExpressionNode[] {
return createParamsList([value, key, index, ...memoArgs])
}
function createParamsList(
args: (ExpressionNode | undefined)[]
): ExpressionNode[] {
let i = args.length
while (i--) {
if (args[i]) break
}
return args
.slice(0, i + 1)
.map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false))
}
import { PatchFlags, PatchFlagNames } from '@vue/shared'
import {
AttributeNode,
BlockCodegenNode,
CREATE_COMMENT,
CacheExpression,
ConstantTypes,
DirectiveNode,
ElementNode,
ElementTypes,
FRAGMENT,
IfBranchNode,
IfConditionalExpression,
IfNode,
MemoExpression,
NodeTypes,
SimpleExpressionNode,
TextNode,
createCallExpression,
createConditionalExpression,
createObjectExpression,
createObjectProperty,
createSimpleExpression,
createVNodeCall,
findDir,
findProp,
getMemoedVNodeCall,
injectProp,
locStub,
makeBlock,
} from '@vue/compiler-core'
import {
TransformContext,
createStructuralDirectiveTransform,
traverseNode,
} from '../transform'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// #1587: We need to dynamically increment the key based on the current
// node's sibling nodes, since chained v-if/else branches are
// rendered at the same depth
const siblings = context.parent!.children
let i = siblings.indexOf(ifNode)
let key = 0
while (i-- >= 0) {
const sibling = siblings[i]
if (sibling && sibling.type === NodeTypes.IF) {
key += sibling.branches.length
}
}
// Exit callback. Complete the codegenNode when all children have been
// transformed.
return () => {
if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
context
) as IfConditionalExpression
} else {
// attach this branch's codegen node to the v-if root.
const parentCondition = getParentCondition(ifNode.codegenNode!)
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
context
)
}
}
})
}
)
// target-agnostic transform used for both Client and SSR
export function processIf(
node: ElementNode,
dir: DirectiveNode,
context: TransformContext,
processCodegen?: (
node: IfNode,
branch: IfBranchNode,
isRoot: boolean
) => (() => void) | undefined
) {
if (
dir.name !== 'else' &&
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
) {
const loc = dir.exp ? dir.exp.loc : node.loc
context.onError(
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
)
dir.exp = createSimpleExpression(`true`, false, loc)
}
if (context.prefixIdentifiers && dir.exp) {
// dir.exp can only be simple expression because vIf transform is applied
// before expression transform.
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
}
if (dir.name === 'if') {
const branch = createIfBranch(node, dir)
const ifNode: IfNode = {
type: NodeTypes.IF,
loc: node.loc,
branches: [branch],
}
context.replaceNode(ifNode)
if (processCodegen) {
return processCodegen(ifNode, branch, true)
}
} else {
// locate the adjacent v-if
const siblings = context.parent!.children
let i = siblings.indexOf(node)
while (i-- >= -1) {
const sibling = siblings[i]
if (sibling && sibling.type === NodeTypes.COMMENT) {
context.removeNode(sibling)
continue
}
if (
sibling &&
((sibling.type === NodeTypes.TEXT && !sibling.content.trim().length) ||
// handle tag text but type = NodeTypes.ELEMENT
(sibling.type === NodeTypes.ELEMENT &&
sibling.tag === 'text' &&
!(sibling.children[0] as TextNode).content.trim().length))
) {
context.removeNode(sibling)
continue
}
if (sibling && sibling.type === NodeTypes.IF) {
// Check if v-else was followed by v-else-if
if (
dir.name === 'else-if' &&
sibling.branches[sibling.branches.length - 1].condition === undefined
) {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
// move the node to the if node's branches
context.removeNode()
const branch = createIfBranch(node, dir)
// check if user is forcing same key on different branches
const key = branch.userKey
if (key) {
sibling.branches.forEach(({ userKey }) => {
if (isSameKey(userKey, key)) {
context.onError(
createCompilerError(
ErrorCodes.X_V_IF_SAME_KEY,
branch.userKey!.loc
)
)
}
})
}
sibling.branches.push(branch)
const onExit = processCodegen && processCodegen(sibling, branch, false)
// since the branch was removed, it will not be traversed.
// make sure to traverse here.
traverseNode(branch, context)
// call on exit
if (onExit) onExit()
// make sure to reset currentNode after traversal to indicate this
// node has been removed.
context.currentNode = null
} else {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
break
}
}
}
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
const isTemplateIf = node.tagType === ElementTypes.TEMPLATE
return {
type: NodeTypes.IF_BRANCH,
loc: node.loc,
condition: dir.name === 'else' ? undefined : dir.exp,
children: isTemplateIf && !findDir(node, 'for') ? node.children : [node],
userKey: findProp(node, `key`),
isTemplateIf,
}
}
function createCodegenNodeForBranch(
branch: IfBranchNode,
keyIndex: number,
context: TransformContext
): IfConditionalExpression | BlockCodegenNode | MemoExpression {
if (branch.condition) {
return createConditionalExpression(
branch.condition,
createChildrenCodegenNode(branch, keyIndex, context),
// make sure to pass in asBlock: true so that the comment node call
// closes the current block.
createCallExpression(context.helper(CREATE_COMMENT), ['"v-if"', 'true'])
) as IfConditionalExpression
} else {
return createChildrenCodegenNode(branch, keyIndex, context)
}
}
function createChildrenCodegenNode(
branch: IfBranchNode,
keyIndex: number,
context: TransformContext
): BlockCodegenNode | MemoExpression {
const { helper } = context
const keyProperty = createObjectProperty(
`key`,
createSimpleExpression(
`${keyIndex}`,
false,
locStub,
ConstantTypes.CAN_HOIST
)
)
const { children } = branch
const firstChild = children[0]
const needFragmentWrapper =
children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) {
if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
// optimize away nested fragments when child is a ForNode
const vnodeCall = firstChild.codegenNode!
injectProp(vnodeCall, keyProperty, context as any)
return vnodeCall
} else {
let patchFlag = PatchFlags.STABLE_FRAGMENT
let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
// check if the fragment actually contains a single valid child with
// the rest being comments
return createVNodeCall(
context as any,
helper(FRAGMENT),
createObjectExpression([keyProperty]),
children,
patchFlag + ` /* ${patchFlagText} */`,
undefined,
undefined,
true,
false,
false /* isComponent */,
branch.loc
)
}
} else {
const ret = (firstChild as ElementNode).codegenNode as
| BlockCodegenNode
| MemoExpression
const vnodeCall = getMemoedVNodeCall(ret)
// Change createVNode to createBlock.
if (vnodeCall.type === NodeTypes.VNODE_CALL) {
makeBlock(vnodeCall, context as any)
}
// inject branch key
injectProp(vnodeCall, keyProperty, context as any)
return ret
}
}
function isSameKey(
a: AttributeNode | DirectiveNode | undefined,
b: AttributeNode | DirectiveNode
): boolean {
if (!a || a.type !== b.type) {
return false
}
if (a.type === NodeTypes.ATTRIBUTE) {
if (a.value!.content !== (b as AttributeNode).value!.content) {
return false
}
} else {
// directive
const exp = a.exp!
const branchExp = (b as DirectiveNode).exp!
if (exp.type !== branchExp.type) {
return false
}
if (
exp.type !== NodeTypes.SIMPLE_EXPRESSION ||
exp.isStatic !== (branchExp as SimpleExpressionNode).isStatic ||
exp.content !== (branchExp as SimpleExpressionNode).content
) {
return false
}
}
return true
}
function getParentCondition(
node: IfConditionalExpression | CacheExpression
): IfConditionalExpression {
while (true) {
if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
node = node.alternate
} else {
return node
}
} else if (node.type === NodeTypes.JS_CACHE_EXPRESSION) {
node = node.value as IfConditionalExpression
}
}
}
import {
transformModel as baseTransform,
DirectiveTransform,
isStaticExp,
NodeTypes,
} from '@vue/compiler-core'
import { isString } from '@vue/shared'
const tags = ['input', 'textarea']
export const transformModel: DirectiveTransform = (dir, node, context) => {
const result = baseTransform(dir, node, context)
// 将 input,textarea 的 onUpdate:modelValue 事件转换为 onInput
if (tags.includes(node.tag) && result.props.length >= 2) {
const key = result.props[1].key
let value = result.props[1].value
if (value.type === NodeTypes.JS_CACHE_EXPRESSION) {
value = value.value
}
if (
isStaticExp(key) &&
key.content === 'onUpdate:modelValue' &&
value.type === NodeTypes.COMPOUND_EXPRESSION
) {
key.content = 'onInput'
// 调整函数类型及返回表达式,适配 uts
value.children = value.children.map((child) => {
if (isString(child)) {
if (child === '$event => ((') {
return '($event: InputEvent): any => {'
} else if (child === ') = $event)') {
return ' = $event.detail.value;\nreturn $event.detail.value;}'
}
}
return child
})
}
}
return result
}
import { DirectiveTransform, DirectiveTransformResult } from '../transform'
import {
createCompoundExpression,
createObjectProperty,
createSimpleExpression,
DirectiveNode,
ElementTypes,
ExpressionNode,
NodeTypes,
SimpleExpressionNode,
hasScopeRef,
isMemberExpression,
TO_HANDLER_KEY,
} from '@vue/compiler-core'
import { camelize, toHandlerKey } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
const fnExpRE =
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
export interface VOnDirectiveNode extends DirectiveNode {
// v-on without arg is handled directly in ./transformElements.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-on
// *with* args.
arg: ExpressionNode
// exp is guaranteed to be a simple expression here because v-on w/ arg is
// skipped by transformExpression as a special case.
exp: SimpleExpressionNode | undefined
}
export const transformOn: DirectiveTransform = (
dir,
node,
context,
augmentor
) => {
const { loc, modifiers, arg } = dir as VOnDirectiveNode
if (!dir.exp && !modifiers.length) {
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
}
let eventName: ExpressionNode
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
if (arg.isStatic) {
let rawName = arg.content
// TODO deprecate @vnodeXXX usage
if (rawName.startsWith('vue:')) {
rawName = `vnode-${rawName.slice(4)}`
}
const eventString =
node.tagType !== ElementTypes.ELEMENT ||
rawName.startsWith('vnode') ||
!/[A-Z]/.test(rawName)
? // for non-element and vnode lifecycle event listeners, auto convert
// it to camelCase. See issue #2249
toHandlerKey(camelize(rawName))
: // preserve case for plain element listeners that have uppercase
// letters, as these may be custom elements' custom events
`on:${rawName}`
eventName = createSimpleExpression(eventString, true, arg.loc)
} else {
// #2388
eventName = createCompoundExpression([
`${context.helperString(TO_HANDLER_KEY)}(`,
arg,
`)`,
])
}
} else {
// already a compound expression.
eventName = arg
eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`)
eventName.children.push(`)`)
}
// handler processing
let exp: ExpressionNode | undefined = dir.exp as
| SimpleExpressionNode
| undefined
if (exp && !exp.content.trim()) {
exp = undefined
}
// @ts-ignore
let shouldCache: boolean = context.cacheHandlers && !exp && !context.inVOnce
if (exp) {
// @ts-ignore
const isMemberExp = isMemberExpression(exp.content, context)
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
const hasMultipleStatements = exp.content.includes(`;`)
// process the expression since it's been skipped
if (context.prefixIdentifiers) {
isInlineStatement && context.addIdentifiers(`$event`)
exp = dir.exp = processExpression(
exp,
context,
false,
hasMultipleStatements
)
isInlineStatement && context.removeIdentifiers(`$event`)
// with scope analysis, the function is hoistable if it has no reference
// to scope variables.
shouldCache =
// @ts-ignore
context.cacheHandlers &&
// unnecessary to cache inside v-once
!context.inVOnce &&
// runtime constants don't need to be cached
// (this is analyzed by compileScript in SFC <script setup>)
!(exp.type === NodeTypes.SIMPLE_EXPRESSION && exp.constType > 0) &&
// #1541 bail if this is a member exp handler passed to a component -
// we need to use the original function to preserve arity,
// e.g. <transition> relies on checking cb.length to determine
// transition end handling. Inline function is ok since its arity
// is preserved even when cached.
!(isMemberExp && node.tagType === ElementTypes.COMPONENT) &&
// bail if the function references closure variables (v-for, v-slot)
// it must be passed fresh to avoid stale values.
!hasScopeRef(exp, context.identifiers)
// If the expression is optimizable and is a member expression pointing
// to a function, turn it into invocation (and wrap in an arrow function
// below) so that it always accesses the latest value when called - thus
// avoiding the need to be patched.
if (shouldCache && isMemberExp) {
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
exp.content = `${exp.content} && ${exp.content}(...args)`
} else {
exp.children = [...exp.children, ` && `, ...exp.children, `(...args)`]
}
}
}
if (isInlineStatement || (shouldCache && isMemberExp)) {
// wrap inline statement in a function expression
if (isInlineStatement && exp.loc.source.includes('$event')) {
throw new Error(
'Inline statement cannot use $event, please use a function expression instead.'
)
}
exp = createCompoundExpression([
`${
isInlineStatement ? `()` : `${``}(...args)`
// } => ${hasMultipleStatements ? `{` : `(`}`,
} => ${`{`}`,
exp,
// hasMultipleStatements ? `}` : `)`
`}`,
])
}
}
let ret: DirectiveTransformResult = {
props: [
createObjectProperty(
eventName,
exp || createSimpleExpression(`() => {}`, false, loc)
),
],
}
// apply extended compiler augmentor
if (augmentor) {
ret = augmentor(ret)
}
if (shouldCache) {
// cache handlers so that it's always the same handler being passed down.
// this avoids unnecessary re-renders when users use inline handlers on
// components.
ret.props[0].value = context.cache(ret.props[0].value)
}
// mark the key as handler for props normalization check
ret.props.forEach((p) => (p.key.isHandlerKey = true))
return ret
}
import { DirectiveTransform } from '@vue/compiler-core'
import { V_SHOW } from '../runtimeHelpers'
export const transformShow: DirectiveTransform = (dir, node, context) => {
return {
props: [],
needRuntime: context.helper(V_SHOW),
}
}
import {
DirectiveTransform,
createObjectProperty,
createSimpleExpression,
TO_DISPLAY_STRING,
createCallExpression,
getConstantType,
} from '@vue/compiler-core'
// import { createDOMCompilerError, DOMErrorCodes } from '../errors'
export const transformVText: DirectiveTransform = (dir, node, context) => {
const { exp, loc } = dir
// if (!exp) {
// context.onError(
// createDOMCompilerError(DOMErrorCodes.X_V_TEXT_NO_EXPRESSION, loc)
// )
// }
// if (node.children.length) {
// context.onError(
// createDOMCompilerError(DOMErrorCodes.X_V_TEXT_WITH_CHILDREN, loc)
// )
// node.children.length = 0
// }
return {
props: [
createObjectProperty(
createSimpleExpression(`value`, true),
exp
? getConstantType(exp, context) > 0
? exp
: createCallExpression(
context.helperString(TO_DISPLAY_STRING),
[exp],
loc
)
: createSimpleExpression('', true)
),
],
}
}
import { CompilerOptions } from './options'
import {
ExpressionNode,
SimpleExpressionNode,
ConstantTypes,
} from '@vue/compiler-core'
import { isString } from '@vue/shared'
export function genRenderFunctionDecl({
targetLanguage,
filename,
}: CompilerOptions): string {
return `${
targetLanguage === 'kotlin' ? '@Suppress("UNUSED_PARAMETER") ' : ''
}function ${filename}Render(_ctx: ${filename}): VNode | null`
}
export const objectExp = /\{.*\}/g
export function object2Map(exp: ExpressionNode | string, wrap = true) {
const content = isString(exp) ? exp : (exp as SimpleExpressionNode).content
const matched = content.match(objectExp)![0]
const keyValues = matched.replace(/\{|\}/g, '').split(',')
let mapConstructor = wrap ? 'new Map<string, any | null>([' : ''
keyValues.forEach((keyValue: string, index: number) => {
const colonIndex = keyValue.indexOf(':')
const key = needAddQuotes(exp, keyValue)
? `'${keyValue.substring(0, colonIndex)}'`
: keyValue.substring(0, colonIndex)
const value = keyValue.substring(colonIndex + 1)
if (key && value) {
mapConstructor += `[${key},${value}]`
if (index < keyValues.length - 1) {
mapConstructor += ','
}
}
})
mapConstructor += wrap ? '])' : ''
return content.replace(matched, mapConstructor)
}
function needAddQuotes(
exp: ExpressionNode | string,
keyValue: string
): boolean {
return (
!isString(exp) &&
(exp as SimpleExpressionNode).constType === ConstantTypes.CAN_STRINGIFY &&
!keyValue.startsWith("'") &&
!keyValue.startsWith('"')
)
}
...@@ -9,6 +9,8 @@ export interface ResolvedOptions { ...@@ -9,6 +9,8 @@ export interface ResolvedOptions {
compiler: typeof _compiler compiler: typeof _compiler
root: string root: string
sourceMap: boolean sourceMap: boolean
targetLanguage?: 'kotlin' | 'swift' | 'javascript'
classNamePrefix?: string
} }
// compiler-sfc should be exported so it can be re-used // compiler-sfc should be exported so it can be re-used
......
import path from 'path' import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { normalizePath, parseVueRequest } from '@dcloudio/uni-cli-shared'
import type { Plugin } from 'vite' import type { Plugin } from 'vite'
import type { SFCBlock, SFCDescriptor, SFCParseResult } from '@vue/compiler-sfc'
import type { TransformPluginContext } from 'rollup'
import { isString } from '@vue/shared'
import { normalizePath, parseVueRequest } from '@dcloudio/uni-cli-shared'
import { ResolvedOptions, createDescriptor } from './descriptorCache' import {
ResolvedOptions,
createDescriptor,
getDescriptor,
getSrcDescriptor,
} from './descriptorCache'
import { createRollupError } from './error' import { createRollupError } from './error'
import { genClassName, parseImports } from '../utils' import { genClassName, isVue, parseImports } from '../utils'
import { genScript } from './code/script' import { genScript } from './code/script'
import { genTemplate } from './code/template' import { genTemplate } from './code/template'
import { genStyle } from './code/style' import { genJsStylesCode, genStyle, transformStyle } from './code/style'
function resolveAppVue(inputDir: string) { function resolveAppVue(inputDir: string) {
const appUVue = path.resolve(inputDir, 'app.uvue') const appUVue = path.resolve(inputDir, 'app.uvue')
...@@ -24,6 +34,7 @@ export function uniAppUVuePlugin(): Plugin { ...@@ -24,6 +34,7 @@ export function uniAppUVuePlugin(): Plugin {
sourceMap: false, sourceMap: false,
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
compiler: require('@vue/compiler-sfc'), compiler: require('@vue/compiler-sfc'),
targetLanguage: process.env.UNI_UVUE_TARGET_LANGUAGE,
} }
const appVue = resolveAppVue(process.env.UNI_INPUT_DIR) const appVue = resolveAppVue(process.env.UNI_INPUT_DIR)
...@@ -34,50 +45,177 @@ export function uniAppUVuePlugin(): Plugin { ...@@ -34,50 +45,177 @@ export function uniAppUVuePlugin(): Plugin {
return { return {
name: 'uni:app-uvue', name: 'uni:app-uvue',
apply: 'build', apply: 'build',
transform(code, id) { async resolveId(id) {
const { filename } = parseVueRequest(id) // serve sub-part requests (*?vue) as virtual modules
const isVue = filename.endsWith('.vue') || filename.endsWith('.uvue') if (parseVueRequest(id).query.vue) {
if (!isVue) { return id
return
} }
// prev descriptor is only set and used for hmr },
const { descriptor, errors } = createDescriptor(filename, code, options)
if (errors.length) { load(id) {
errors.forEach((error) => const { filename, query } = parseVueRequest(id)
this.error(createRollupError(filename, error)) // select corresponding block for sub-part virtual modules
) if (query.vue) {
return null if (query.src) {
return fs.readFileSync(filename, 'utf-8')
}
const descriptor = getDescriptor(filename, options)!
let block: SFCBlock | null | undefined
if (query.type === 'style') {
block = descriptor.styles[query.index!]
} else if (query.index != null) {
block = descriptor.customBlocks[query.index]
}
if (block) {
return {
code: block.content,
map: block.map as any,
}
}
} }
const isApp = isAppVue(id) },
const fileName = path.relative(process.env.UNI_INPUT_DIR, id) async transform(code, id) {
const className = genClassName(fileName) const { filename, query } = parseVueRequest(id)
// 生成 script 文件 if (!isVue(filename)) {
this.emitFile({ return
type: 'asset',
fileName,
source:
genScript(descriptor.script, { filename: className }) +
'\n' +
genStyle(descriptor.styles, { filename: className }) +
'\n' +
(!isApp
? genTemplate(descriptor.template, {
targetLanguage: process.env.UNI_UVUE_TARGET_LANGUAGE as
| 'kotlin'
| 'swift',
mode: 'function',
filename: className,
})
: ''),
})
const content = descriptor.script?.content
if (content) {
return parseImports(content)
} }
return { if (!query.vue) {
code: 'export default {}', // main request
const { errors, uts, js } = await transformVue(
code,
filename,
options,
this,
isAppVue
)
if (errors.length) {
errors.forEach((error) =>
this.error(createRollupError(filename, error))
)
return null
}
const fileName = path.relative(process.env.UNI_INPUT_DIR, filename)
this.emitFile({
type: 'asset',
fileName,
source: uts,
})
return {
code: js,
}
} else {
// sub block request
const descriptor = query.src
? getSrcDescriptor(filename)!
: getDescriptor(filename, options)!
if (query.type === 'style') {
return transformStyle(
code,
descriptor,
Number(query.index),
options,
this,
filename
)
}
} }
}, },
generateBundle(_, bundle) {
// 遍历vue文件,填充style,尽量减少全局变量
Object.keys(bundle).forEach((name) => {
const file = bundle[name]
if (
file &&
file.type === 'asset' &&
file.fileName !== 'App.vue' &&
isVue(file.fileName) &&
isString(file.source)
) {
const fileName = normalizePath(file.fileName)
const classNameComment = `/*${genClassName(
fileName,
options.classNamePrefix
)}Styles*/`
if (file.source.includes(classNameComment)) {
const styleAssetName = fileName + '.style.uts'
const styleAsset = bundle[styleAssetName]
if (
styleAsset &&
styleAsset.type === 'asset' &&
isString(styleAsset.source)
) {
file.source = file.source.replace(
classNameComment,
styleAsset.source.replace('export ', '')
)
delete bundle[styleAssetName]
}
}
}
})
},
}
}
interface TransformVueResult {
errors: SFCParseResult['errors']
uts?: string
js?: string
descriptor: SFCDescriptor
}
export async function transformVue(
code: string,
filename: string,
options: ResolvedOptions,
pluginContext: TransformPluginContext | undefined,
isAppVue: (id: string) => boolean = () => false
): Promise<TransformVueResult> {
if (!options.compiler) {
options.compiler = require('@vue/compiler-sfc')
}
// prev descriptor is only set and used for hmr
const { descriptor, errors } = createDescriptor(filename, code, options)
if (errors.length) {
return { errors, descriptor }
}
const isApp = isAppVue(filename)
const fileName = path.relative(options.root, filename)
const className = genClassName(fileName, options.classNamePrefix)
let templateCode = ''
if (!isApp) {
const templateResult = genTemplate(descriptor, {
targetLanguage: options.targetLanguage as any,
mode: 'function',
filename: className,
prefixIdentifiers: true,
sourceMap: true,
})
templateCode = templateResult.code
}
// 生成 script 文件
let utsCode =
genScript(descriptor, { filename: className }) +
'\n' +
genStyle(descriptor, { filename: fileName, className }) +
'\n'
utsCode += templateCode
let jsCode = ''
const content = descriptor.script?.content
if (content) {
jsCode += await parseImports(content)
}
if (descriptor.styles.length) {
jsCode += '\n' + (await genJsStylesCode(descriptor, pluginContext!))
}
jsCode += `\nexport default "${className}"`
return {
errors: [],
uts: utsCode,
js: jsCode,
descriptor,
} }
} }
...@@ -103,19 +103,15 @@ export function render(_ctx, _cache) { ...@@ -103,19 +103,15 @@ export function render(_ctx, _cache) {
`; `;
exports[`app-nvue: compiler <view>hello{{a}}<view>aaa{{a}}</view>{{b}}</view> 1`] = ` exports[`app-nvue: compiler <view>hello{{a}}<view>aaa{{a}}</view>{{b}}</view> 1`] = `
"import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" "import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = { renderWhole: true } const _hoisted_1 = { renderWhole: true }
const _hoisted_2 = /*#__PURE__*/_createElementVNode("u-text", null, "hello", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createElementVNode("u-text", null, "aaa", -1 /* HOISTED */)
export function render(_ctx, _cache) { export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("view", _hoisted_1, [ return (_openBlock(), _createElementBlock("view", _hoisted_1, [
_hoisted_2, _createElementVNode("u-text", null, "hello" + _toDisplayString(_ctx.a), 1 /* TEXT */),
_createElementVNode("u-text", null, _toDisplayString(_ctx.a), 1 /* TEXT */),
_createElementVNode("view", null, [ _createElementVNode("view", null, [
_hoisted_3, _createElementVNode("u-text", null, "aaa" + _toDisplayString(_ctx.a), 1 /* TEXT */)
_createElementVNode("u-text", null, _toDisplayString(_ctx.a), 1 /* TEXT */)
]), ]),
_createElementVNode("u-text", null, _toDisplayString(_ctx.b), 1 /* TEXT */) _createElementVNode("u-text", null, _toDisplayString(_ctx.b), 1 /* TEXT */)
])) ]))
......
此差异已折叠。
此差异已折叠。
{ {
"name": "@dcloudio/uni-app-vue", "name": "@dcloudio/uni-app-vue",
"version": "3.0.0-alpha-3071220230324001", "version": "3.0.0-alpha-3080220230428002",
"description": "@dcloudio/uni-app-vue", "description": "@dcloudio/uni-app-vue",
"main": "dist/service.runtime.esm.dev.js", "main": "dist/service.runtime.esm.dev.js",
"module": "dist/service.runtime.esm.dev.js", "module": "dist/service.runtime.esm.dev.js",
...@@ -19,6 +19,6 @@ ...@@ -19,6 +19,6 @@
}, },
"gitHead": "33e807d66e1fe47e2ee08ad9c59247e37b8884da", "gitHead": "33e807d66e1fe47e2ee08ad9c59247e37b8884da",
"devDependencies": { "devDependencies": {
"@dcloudio/uni-shared": "3.0.0-alpha-3071220230324001" "@dcloudio/uni-shared": "3.0.0-alpha-3080220230428002"
} }
} }
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册