├── .gitignore ├── README.md ├── package.json ├── src ├── master │ ├── app │ │ ├── index.js │ │ └── life-cycle.js │ ├── index.js │ ├── navigator │ │ ├── history.js │ │ ├── index.js │ │ ├── slave-common-parts.js │ │ └── slave.js │ ├── page │ │ ├── index.js │ │ ├── life-cycle.js │ │ ├── page-prototype.js │ │ └── slave-events-router.js │ └── proccessors │ │ └── api-proccessor.js ├── slave │ ├── component-factory │ │ └── index.js │ └── index.js └── utils │ ├── code-process.js │ ├── communication │ └── index.js │ ├── data.js │ ├── events-emitter.js │ ├── index.js │ ├── loader.js │ ├── module.js │ ├── path.js │ ├── splitapp-accessory.js │ └── swan-events │ └── index.js ├── test ├── package.json ├── src │ ├── main.js │ ├── native │ │ ├── component │ │ │ ├── page.js │ │ │ └── view │ │ │ │ └── index.js │ │ ├── index.js │ │ └── utils │ │ │ ├── events-emitter.js │ │ │ ├── loader.js │ │ │ └── swan-events │ │ │ └── index.js │ └── wxs │ │ ├── helloworld │ │ ├── app.css │ │ ├── app.js │ │ ├── app.json │ │ └── pages │ │ │ ├── component │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── index.swan.js │ │ │ └── index.wxml │ │ │ └── text │ │ │ ├── text.css │ │ │ ├── text.js │ │ │ ├── text.swan.js │ │ │ └── text.wxml │ │ └── wechat-app-demo │ │ ├── app.css │ │ ├── app.js │ │ ├── app.json │ │ ├── image │ │ ├── arrowright.png │ │ ├── fix │ │ │ ├── bgm01.png │ │ │ ├── bgm02.png │ │ │ ├── bgm03.png │ │ │ ├── bgm11.png │ │ │ ├── bgm12.png │ │ │ ├── bgm13.png │ │ │ ├── bgm14.png │ │ │ ├── bgm15.png │ │ │ └── scroll-view.png │ │ ├── icon64_appwx_logo.png │ │ ├── pause.png │ │ ├── play.png │ │ ├── plus.png │ │ ├── record.png │ │ ├── resources │ │ │ ├── arrow.png │ │ │ ├── kind │ │ │ │ ├── canvas.png │ │ │ │ ├── content.png │ │ │ │ ├── form.png │ │ │ │ ├── interact.png │ │ │ │ ├── map.png │ │ │ │ ├── media.png │ │ │ │ ├── nav.png │ │ │ │ └── view.png │ │ │ └── pic.jpg │ │ ├── stop.png │ │ ├── trash.png │ │ ├── wechat.png │ │ └── wechatHL.png │ │ └── page │ │ └── component │ │ ├── component-pages │ │ └── wx-view │ │ │ ├── wx-view.css │ │ │ ├── wx-view.html │ │ │ └── wx-view.js │ │ ├── index.css │ │ └── index.js └── webpack.config.js ├── webpack.base.conf.js └── webpack.test.conf.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | package-lock.json 3 | node_modules/ 4 | dist/ 5 | output/ 6 | **/.DS_Store 7 | test/coverage/ 8 | .idea 9 | test/node_modules/ 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # swanvue-core 5 | 6 | 基于百度小程序swan-core,替换其中的MVVM框架san,使用vue的小程序框架。 7 | 8 | 由于百度小程序只开源了js部分,对于native部分没有开源,此项目的目标就是把未开源的部分还原回去,界面渲染框架使用vue 9 | 10 | ## 思路 11 | 12 | 1. mock native部分代码,运行swan-core代码 13 | 2. 渲染部分使用vue替换san 14 | 3. 添加自定义组件 15 | 4. 渲染与逻辑分离,创建两个web运行环境,native实现渲染与逻辑通信 16 | 4. 实现编译脚本,对小程序代码进行转译 17 | 18 | ## build 19 | ```shell 20 | // 编译出master, slave 21 | npm run build:test 22 | 23 | // 运行测试项目 24 | cd test 25 | npm start 26 | ``` 27 | 28 | ## 技术交流群 29 | 30 | 群名称:swanvue-core-交流群 31 | 32 | 群 号:733617647 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swanvue-core", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "build:test": "webpack --config webpack.test.conf.js" 7 | }, 8 | "author": "", 9 | "license": "MIT", 10 | "dependencies": { 11 | "vue": "^2.6.6" 12 | }, 13 | "devDependencies": { 14 | "babel-core": "^6.26.3", 15 | "babel-loader": "^7.1.5", 16 | "babel-plugin-istanbul": "^5.1.0", 17 | "babel-plugin-transform-class-properties": "^6.24.1", 18 | "babel-plugin-transform-decorators-legacy": "^1.3.5", 19 | "babel-plugin-transform-object-assign": "^6.22.0", 20 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 21 | "babel-preset-env": "^1.7.0", 22 | "copy-webpack-plugin": "^4.6.0", 23 | "jasmine": "^3.3.1", 24 | "jasmine-core": "^3.3.0", 25 | "karma": "^2.0.5", 26 | "karma-chrome-launcher": "^2.2.0", 27 | "karma-jasmine": "^2.0.1", 28 | "karma-webpack": "^3.0.5", 29 | "npm": "^6.8.0", 30 | "webpack": "^3.12.0", 31 | "webpack-merge": "^4.2.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/master/app/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | mixinLifeCycle 3 | } from './life-cycle'; 4 | import {getAppInfo} from '../../utils'; 5 | import swanEvents from '../../utils/swan-events'; 6 | 7 | /** 8 | * 绑定app的环境相关事件 9 | * 10 | * @param {Object} [appObject] - app对象的实例 11 | * @param {Object} [swaninterface] - swaninterface小程序底层接口 12 | */ 13 | const bindLifeCycleEvent = (appObject, swaninterface, lifeCycleEventEmitter) => { 14 | const appEventsToLifeCycle = ['onAppShow', 'onAppHide', 'onAppError', 'onPageNotFound']; 15 | 16 | appEventsToLifeCycle.forEach(eventName => { 17 | lifeCycleEventEmitter.onMessage(eventName, messages => { 18 | // 筛选出本次的onShow的对应参数 19 | let event = messages[0] ? messages[0].event : messages.event; 20 | if (appObject[`_${event.lcType}`]) { 21 | appObject[`_${event.lcType}`]({ 22 | event, 23 | appInfo: getAppInfo(swaninterface, true), 24 | type: event.lcType 25 | }); 26 | } 27 | }, { 28 | listenPreviousEvent: true 29 | }); 30 | }); 31 | 32 | swaninterface 33 | .bind('onLogin', event => { 34 | appObject['_onLogin']({ 35 | event, 36 | appInfo: getAppInfo(swaninterface, true), 37 | type: event.lcType 38 | }); 39 | }); 40 | swanEvents('masterPreloadInitBindingEnvironmentEvents'); 41 | }; 42 | 43 | 44 | /** 45 | * 获取所有的app操作方法(App/getApp) 46 | * 47 | * @param {Object} [swaninterface] - swan底层接口 48 | * @param {Object} [appLifeCycleEventEmitter] - app的数据流 49 | * @return {Object} 所有App相关方法的合集 50 | */ 51 | export const getAppMethods = (swaninterface, appLifeCycleEventEmitter, lifeCycleEventEmitter) => { 52 | let initedAppObject = null; 53 | 54 | const getApp = () => initedAppObject; 55 | 56 | const App = appObject => { 57 | // 将初始化之后的app对象,返回到上面,getApp时,可以访问 58 | // 获取app的相关信息,onLaunch是框架帮忙执行的,所以需要注入客户端信息 59 | const appInfo = getAppInfo(swaninterface, true); 60 | // global.monitorAppid = appInfo['appid']; 61 | // try { 62 | // global.rainMonitor.opts.appkey = appInfo['appid']; 63 | // global.rainMonitor.opts.cuid = appInfo['cuid']; 64 | // } catch (e) { 65 | // // avoid empty state 66 | // } 67 | initedAppObject = mixinLifeCycle(appObject, appLifeCycleEventEmitter); 68 | bindLifeCycleEvent(initedAppObject, swaninterface, lifeCycleEventEmitter); 69 | 70 | // 触发launch事件 71 | swanEvents('masterOnAppLaunchHookStart'); 72 | initedAppObject._onAppLaunch({ 73 | appInfo, 74 | event: {}, 75 | type: 'onAppLaunch' 76 | }); 77 | swanEvents('masterOnAppLaunchHookEnd'); 78 | return initedAppObject; 79 | }; 80 | 81 | return { 82 | App, 83 | getApp 84 | }; 85 | }; 86 | -------------------------------------------------------------------------------- /src/master/app/life-cycle.js: -------------------------------------------------------------------------------- 1 | import {processParam} from '../../utils/index'; 2 | 3 | const lifeCyclePrototype = { 4 | 5 | /** 6 | * 生命周期的函数中接收到的参数处理函数 7 | * 8 | * @param {Object} [data] - 待处理的数据 9 | * @return {Object} 处理后的数据 10 | */ 11 | _lifeCycleParamsHandle(data) { 12 | const appInfo = data && data.appInfo || {}; 13 | 14 | // 取出 appInfo 中的 path, query, scene, shareTicket 15 | let result = ['path', 'query', 'scene', 'shareTicket'] 16 | .reduce((prev, cur) => { 17 | prev[cur] = appInfo[cur] || ''; 18 | return prev; 19 | }, {}); 20 | 21 | // 如果是从小程序跳转来的,则增加引用信息 referrerInfo 22 | appInfo.srcAppId && (result.referrerInfo = appInfo.referrerInfo); 23 | 24 | return result; 25 | }, 26 | 27 | /** 28 | * onShow生命周期的参数的处理 29 | * @param {Object} data - 待处理的数据 30 | * @return {Object} 处理后的传给开发者的参数 31 | * @private 32 | */ 33 | _onAppShowLifeCycleParamsHandle(data) { 34 | const result = this._lifeCycleParamsHandle(data); 35 | const appInfo = data && data.appInfo || {}; 36 | 37 | // 对于 onShow,传递entryType 及 appURL信息,以增加场景触发标识参数 38 | // {string} entryType 值同showBy,有'user' | 'schema' | 'sys' 标识onShow的调起方式,'user'通过home前后台切换或者锁屏调起,'schema'是通过协议调起,'sys'为默认值(未覆盖到的打开场景) 39 | // {string=} appURL showBy为schema时存在,为调起协议的完整链接 40 | if (appInfo.showBy) { 41 | result.entryType = appInfo.showBy; 42 | if (appInfo.showBy === 'schema') { 43 | result.appURL = appInfo.appURL; 44 | } 45 | } 46 | return result; 47 | }, 48 | 49 | /** 50 | * 向事件流中发送生命周期消息 51 | * 52 | * @param {Object} [eventName] - 生命周期事件名称 53 | * @param {Object} [e] - 事件对象 54 | */ 55 | _sendAppLifeCycleMessage(eventName, e) { 56 | this._appLifeCycleEventEmitter.fireMessage({ 57 | type: 'ApplifeCycle', 58 | params: { 59 | eventName, 60 | e 61 | } 62 | }); 63 | }, 64 | 65 | /** 66 | * appLaunch生命周期,在app启动时即自执行 67 | * 68 | * @param {Object} [params] - appLaunch的生命周期函数 69 | */ 70 | _onAppLaunch(params) { 71 | try { 72 | processParam(params.appInfo); 73 | this.onLaunch && this.onLaunch(this._lifeCycleParamsHandle(params)); 74 | } 75 | catch (e) { 76 | console.error(e); 77 | } 78 | this._sendAppLifeCycleMessage('onLaunch', { 79 | e: params.appInfo 80 | }); 81 | }, 82 | 83 | /** 84 | * appShow生命周期,在app启动/前后台切换时触发 85 | * 86 | * @param {Object} [params] - appShow生命周期参数 87 | */ 88 | _onAppShow(params) { 89 | try { 90 | processParam(params.appInfo); 91 | this._sendAppLifeCycleMessage('onPreShow', {e: params.appInfo}); 92 | this.onShow && this.onShow(this._onAppShowLifeCycleParamsHandle(params)); 93 | } 94 | catch (e) { 95 | console.error(e); 96 | } 97 | this._sendAppLifeCycleMessage('onShow', { 98 | e: params.appInfo 99 | }); 100 | }, 101 | 102 | /** 103 | * appHide生命周期,在app前后台切换时触发 104 | * 105 | * @param {Object} [params] - appHide生命周期参数 106 | */ 107 | _onAppHide(params) { 108 | try { 109 | processParam(params.appInfo); 110 | this.onHide && this.onHide(this._lifeCycleParamsHandle(params)); 111 | } 112 | catch (e) { 113 | console.error(e); 114 | } 115 | this._sendAppLifeCycleMessage('onHide', { 116 | e: params.appInfo 117 | }); 118 | }, 119 | 120 | /** 121 | * appError生命周期,在app生命周期内,如果发生错误,即触发 122 | * 123 | * @param {Object} [params] - appError生命周期的参数 124 | */ 125 | _onAppError(params) { 126 | this.onError && this.onError(params.event); 127 | this._sendAppLifeCycleMessage('onError', { 128 | e: params.appInfo 129 | }); 130 | }, 131 | }; 132 | 133 | export const mixinLifeCycle = (appObject, appLifeCycleEventEmitter) => { 134 | return Object.assign(appObject, lifeCyclePrototype, { 135 | _appLifeCycleEventEmitter: appLifeCycleEventEmitter 136 | }); 137 | }; -------------------------------------------------------------------------------- /src/master/index.js: -------------------------------------------------------------------------------- 1 | 2 | import {getAppMethods} from './app'; 3 | import {Navigator} from './navigator'; 4 | import EventsEmitter from '../utils/events-emitter'; 5 | import {Page, slaveEventInit} from './page'; 6 | import {define, require} from '../utils/module'; 7 | import {apiProccess} from './proccessors/api-proccessor'; 8 | import Communicator from '../utils/communication'; 9 | import swanEvents from '../utils/swan-events'; 10 | 11 | export default class Master { 12 | constructor(context, swaninterface, swanComponents) { 13 | swanEvents('masterPreloadStart'); 14 | // this.handleError(context); 15 | this.swaninterface = swaninterface; 16 | this.swanComponents = swanComponents; 17 | this.pagesQueue = {}; 18 | this.navigator = new Navigator(swaninterface, context); 19 | this.communicator = new Communicator(swaninterface); 20 | swanEvents('masterPreloadCommunicatorListened'); 21 | // 22 | // this.swanEventsCommunicator = new EventsEmitter(); 23 | // this.virtualComponentFactory = new VirtualComponentFactory(swaninterface); 24 | // this.extension = new Extension(context, swaninterface); 25 | // 26 | // // perfAudit data hook 27 | // this.perfAudit = {}; 28 | // 29 | // 监听app、page所有生命周期事件 30 | this.bindLifeCycleEvents(); 31 | // // 监听所有的slave事件 32 | const allSlaveEventEmitters = slaveEventInit(this); 33 | // 34 | this.pageLifeCycleEventEmitter = allSlaveEventEmitters.pageLifeCycleEventEmitter; 35 | // 36 | // 装饰当前master的上下文(其实就是master的window,向上挂方法/对象) 37 | this.context = this.decorateContext(context); 38 | // 39 | // this.openSourceDebugger(); 40 | // // 监听appReady 41 | this.listenAppReady(); 42 | // // 适配环境 43 | // this.adaptEnvironment(); 44 | // // 解析宿主包 45 | // this.extension.use(this); 46 | swanEvents('masterPreloadGetMastermanager'); 47 | swanEvents('masterPreloadEnd'); 48 | 49 | } 50 | 51 | /** 52 | * 监听客户端的调起逻辑 53 | */ 54 | listenAppReady() { 55 | this.swaninterface.bind('AppReady', event => { 56 | console.log('master listener AppReady ', event); 57 | 58 | // if (event.devhook === 'true') { 59 | // if (swanGlobal) { 60 | // loader.loadjs('./swan-devhook/master.js'); 61 | // } else { 62 | // loader.loadjs('../swan-devhook/master.js'); 63 | // } 64 | // } 65 | swanEvents('masterActiveStart'); 66 | // 给三方用的,并非给框架用,请保留 67 | this.context.appConfig = event.appConfig; 68 | // 初始化master的入口逻辑 69 | this.initRender(event); 70 | // this.preLoadSubPackage(); 71 | }); 72 | } 73 | 74 | /** 75 | * 初始化渲染 76 | * 77 | * @param {Object} initEvent - 客户端传递的初始化事件对象 78 | * @param {string} initEvent.appConfig - 客户端将app.json的内容(json字符串)给前端用于处理 79 | * @param {string} initEvent.appPath - app在手机上的磁盘位置 80 | * @param {string} initEvent.wvID - 第一个slave的id 81 | * @param {string} initEvent.pageUrl - 第一个slave的url 82 | */ 83 | initRender(initEvent) { 84 | // 设置appConfig 85 | this.navigator.setAppConfig({ 86 | ...JSON.parse(initEvent.appConfig), 87 | ...{ 88 | appRootPath: initEvent.appPath 89 | } 90 | }); 91 | swanEvents('masterActiveInitRender'); 92 | 93 | // 压入initSlave 94 | this.navigator.pushInitSlave({ 95 | pageUrl: initEvent.pageUrl, 96 | slaveId: +initEvent.wvID, 97 | root: initEvent.root, 98 | preventAppLoad: initEvent.preventAppLoad 99 | }); 100 | 101 | this.appPath = initEvent.appPath; 102 | swanEvents('masterActivePushInitslave'); 103 | } 104 | 105 | /** 106 | * 绑定生命周期事件 107 | */ 108 | bindLifeCycleEvents() { 109 | this.lifeCycleEventEmitter = new EventsEmitter(); 110 | this.swaninterface.bind('lifecycle', event => { 111 | console.log('master listener lifecycle', event); 112 | this.lifeCycleEventEmitter.fireMessage({ 113 | type: event.lcType + (event.lcType === 'onShow' ? event.wvID : ''), 114 | event 115 | }); 116 | }); 117 | } 118 | 119 | /** 120 | * 装饰当前的上下文环境 121 | * 122 | * @param {Object} context - 待装饰的上下文 123 | * @return {Object} 装饰后的上下文 124 | */ 125 | decorateContext(context) { 126 | Object.assign(context, this.getAppMethods()); 127 | context.masterManager = this; 128 | context.define = define; 129 | context.require = require; 130 | // context.swaninterface = this.swaninterface; // 远程调试工具的依赖 131 | context.swan = this.decorateSwan(Object.assign(this.swaninterface.swan, context.swan || {})); 132 | // context.getCurrentPages = getCurrentPages; 133 | // context.global = {}; 134 | context.Page = Page; 135 | // 136 | // context.Component = this.virtualComponentFactory 137 | // .defineVirtualComponent.bind(this.virtualComponentFactory); 138 | // 139 | // context.Behavior = this.virtualComponentFactory 140 | // .defineBehavior.bind(this.virtualComponentFactory); 141 | // 142 | swanEvents('masterPreloadDecorateContext'); 143 | return context; 144 | } 145 | 146 | /** 147 | * 将导出给用户的swan进行封装,补充一些非端能力相关的框架层能力 148 | * 后续,向对外暴露的swan对象上,添加框架级方时,均在此处添加 149 | * 150 | * @param {Object} [originSwan] 未封装过的,纯boxjs导出的swan对象 151 | * @return {Object} 封装后的swan对象 152 | */ 153 | decorateSwan(originSwan) { 154 | return apiProccess(originSwan, { 155 | swanComponents: this.swanComponents, 156 | navigator: this.navigator, 157 | communicator: this.communicator, 158 | pageLifeCycleEventEmitter: this.pageLifeCycleEventEmitter, 159 | appLifeCycleEventEmitter: this.appLifeCycleEventEmitter, 160 | swanEventsCommunicator: this.swanEventsCommunicator, 161 | // hostShareParamsProccess: this.extension.hostShareParamsProccess.bind(this.extension), 162 | swaninterface: this.swaninterface 163 | }); 164 | } 165 | 166 | /** 167 | * 获取所有App级相关的方法 168 | * 169 | * @return {Object} 用户App的操作相关方法集合 170 | */ 171 | getAppMethods() { 172 | this.appLifeCycleEventEmitter = new EventsEmitter(); 173 | return getAppMethods( 174 | this.swaninterface, 175 | this.appLifeCycleEventEmitter, 176 | this.lifeCycleEventEmitter 177 | ); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/master/navigator/history.js: -------------------------------------------------------------------------------- 1 | 2 | import swanEvents from '../../utils/swan-events'; 3 | 4 | export default class History { 5 | constructor(initSlaves = []) { 6 | this.historyStack = initSlaves; 7 | swanEvents('masterPreloadCreateHistorystack') 8 | } 9 | /** 10 | * 将一个slave推入栈中 11 | * 12 | * @param {Object} slave 推入栈中的slave 13 | * @return {number} 推入后栈新的长度(同array.push) 14 | */ 15 | pushHistory(slave) { 16 | return this.historyStack.push(slave); 17 | } 18 | 19 | /** 20 | * 根据所给出的delta退栈 21 | * 22 | * @param {number} delta 退栈的层数 23 | * @return {Array} 退出的所有slave 24 | */ 25 | // popHistory(delta = 1) { 26 | // let poppedSlaves = []; 27 | // for (let i = 0; i < delta; i++) { 28 | // const currentHistoryPage = this.historyStack.pop(); 29 | // poppedSlaves.push(currentHistoryPage); 30 | // } 31 | // return poppedSlaves; 32 | // } 33 | 34 | /** 35 | * 替换栈顶的slave 36 | * 37 | * @param {object} slave 要被替换至栈顶的slave 38 | * @return {number} 推入后栈新的长度(同array.push) 39 | */ 40 | // replaceHistory(slave) { 41 | // this.historyStack.pop(); 42 | // return this.pushHistory(slave); 43 | // } 44 | 45 | /** 46 | * 获取栈顶的delta个元素 47 | * 48 | * @param {number} delta 获取的栈顶元素个数 49 | * @return {Array} 栈顶的delta个元素 50 | */ 51 | getTopSlaves(delta = 1) { 52 | return this.historyStack.slice(-1 * delta, this.historyStack.length); 53 | } 54 | 55 | /** 56 | * 获取整个栈 57 | */ 58 | // getAllSlaves() { 59 | // return this.historyStack; 60 | // } 61 | 62 | /** 63 | * 退栈到某一条件(到某一个slaveid的slave) 64 | * 65 | * @param {number} slaveId 退至的slave的id 66 | * @param {Function} everyCb 每退一个时候的回调 67 | */ 68 | // popTo(slaveId, everyCb) { 69 | // if (this.historyStack.some(item => item.isTheSlave(slaveId))) { 70 | // while (this.historyStack.length !== 0 71 | // && !this.getTopSlaves()[0].isTheSlave(slaveId) 72 | // ) { 73 | // const topSlave = this.historyStack.pop(); 74 | // topSlave.close(); 75 | // everyCb && everyCb(topSlave); 76 | // } 77 | // } 78 | // } 79 | 80 | /** 81 | * 清空栈,并逐个调用栈中的析构方法(close) 82 | * 83 | */ 84 | // clear() { 85 | // try { 86 | // this.historyStack.forEach(slave => slave.close()); 87 | // } 88 | // catch (e) { 89 | // console.error('析构出现错误:', e); 90 | // } 91 | // this.historyStack = []; 92 | // } 93 | 94 | /** 95 | * 判断栈中是否有某一个slave 96 | * 97 | * @param {number} slaveId 判断是否存在的slave的id 98 | * @return {boolean} 判断某一slave是否存在 99 | */ 100 | // hasTheSlave(slaveId) { 101 | // return this.historyStack.some(item => item.isTheSlave(slaveId)); 102 | // } 103 | 104 | /** 105 | * 从history栈中获取slave 106 | * @param {string} [slaveId] slave的id或者slave的uri 107 | * @param {boolean} [getSuperSlave] 获取slave的泛类型,还是具体的slave 108 | * @return {Object} slave对象 109 | */ 110 | seek(slaveId, getSuperSlave) { 111 | const superSlave = this.historyStack 112 | .find(item => item.isTheSlave(slaveId)); 113 | return getSuperSlave ? superSlave : superSlave && superSlave.findChild(slaveId); 114 | } 115 | 116 | /** 117 | * 对于页面栈中的所有节点进行遍历 118 | * 119 | * @param {Function} fn 每次遍历调用的函数 120 | */ 121 | // each(fn) { 122 | // this.historyStack.forEach(slave => { 123 | // fn && fn(slave); 124 | // }); 125 | // } 126 | } 127 | -------------------------------------------------------------------------------- /src/master/navigator/index.js: -------------------------------------------------------------------------------- 1 | import Slave from './slave'; 2 | import History from './history'; 3 | import splitAppAccessory from '../../utils/splitapp-accessory'; 4 | import {pathResolver, parseUrl} from '../../utils'; 5 | import swanEvents from '../../utils/swan-events'; 6 | 7 | export class Navigator { 8 | constructor(swaninterface, context) { 9 | this.history = new History(); 10 | this.swaninterface = swaninterface; 11 | this.context = context; 12 | } 13 | setAppConfig(appConfig) { 14 | // 第一次从客户端获取到appConfig 15 | this.appConfig = appConfig; 16 | } 17 | 18 | /** 19 | * 初始化第一个slave 20 | * @param {Object} [initParams] - 初始化的参数 21 | */ 22 | pushInitSlave(initParams) { 23 | // Route事件监听开启 24 | // this.listenRoute(); 25 | 26 | swanEvents('masterActiveCreateInitslave'); 27 | 28 | // 根据appConfig判断时候有appjs拆分逻辑 29 | // 如果包含splitAppJs字段,且不分包,则为拆分app.js 30 | // if (this.appConfig.splitAppJs && !this.appConfig.subPackages) { 31 | // splitAppAccessory.allJsLoaded = false; 32 | // } 33 | 34 | // 创建初始化slave 35 | this.initSlave = this.createInitSlave(initParams.pageUrl, this.appConfig); 36 | 37 | // slave的init调用 38 | this.initSlave 39 | .init(initParams) 40 | .then(initRes => { 41 | swanEvents('masterActiveCreateInitslaveEnd'); 42 | // 入栈 43 | this.history.pushHistory(this.initSlave); 44 | swanEvents('masterActivePushInitslaveEnd'); 45 | // 调用slave的onEnqueue生命周期函数 46 | this.initSlave.onEnqueue(); 47 | swanEvents('masterActiveOnqueueInitslave'); 48 | }); 49 | } 50 | 51 | /** 52 | * 产生初始化的slave的工厂方法 53 | * 54 | * @param {string} initUri 初始化的uri 55 | * @param {Object} appConfig 小程序配置的app.json中的配置内容 56 | * @return {Object} 一个slave或slaveSet 57 | */ 58 | createInitSlave(initUri, appConfig) { 59 | let tabBarList = []; 60 | try { 61 | tabBarList = appConfig.tabBar.list; 62 | } 63 | catch (e) {} 64 | const initPath = initUri.split('?')[0]; 65 | const currentIndex = tabBarList.findIndex(tab => tab.pagePath === initPath); 66 | const swaninterface = this.swaninterface; 67 | // if (tabBarList.length > 1 && currentIndex > -1) { 68 | // splitAppAccessory.tabBarList = tabBarList; 69 | // return new TabSlave({ 70 | // list: tabBarList, 71 | // currentIndex, 72 | // appConfig, 73 | // swaninterface 74 | // }); 75 | // } 76 | return new Slave({ 77 | uri: initUri, 78 | appConfig, 79 | swaninterface 80 | }); 81 | } 82 | 83 | /** 84 | * 跳转到下一页的方法 85 | * 86 | * @param {Object} [params] - 跳转参数 87 | * @return {Promise} 88 | */ 89 | navigateTo(params) { 90 | params.url = this.resolvePathByTopSlave(params.url); 91 | this.preCheckPageExist(params.url); 92 | const {url, slaveId} = params; 93 | const {appConfig, swaninterface} = this; 94 | const newSlave = new Slave({ 95 | uri: url, 96 | slaveId, 97 | appConfig, 98 | swaninterface 99 | }); 100 | // TODO: openinit openNext 判断有问题 101 | return newSlave.open(params) 102 | .then(res => { 103 | const slaveId = res.wvID; 104 | // navigateTo的第一步,将slave完全实例化 105 | newSlave.setSlaveId(slaveId); 106 | // navigateTo的第二步,讲slave推入栈 107 | this.history.pushHistory(newSlave); 108 | // navigateTo的第三步,调用slave的onEnqueue生命周期函数 109 | newSlave.onEnqueue(); 110 | return res; 111 | }) 112 | .catch(console.log); 113 | } 114 | 115 | /** 116 | * 将传入的path以页面栈顶层为相对路径 117 | * @param {string} path - 需要解析的相对路径 118 | * @return {string} 解析后的全路径 119 | */ 120 | resolvePathByTopSlave(path) { 121 | if (/^\//g.exec(path)) { 122 | return path.replace(/^\//g, ''); 123 | } 124 | const topSlaveUri = this.history.getTopSlaves()[0].getUri().replace(/[^\/]*$/g, ''); 125 | const uriStack = pathResolver(topSlaveUri, path, () => { 126 | console.error(`navigateTo:fail url "${path}"`); 127 | }); 128 | return uriStack.join('/').replace(/^\//g, ''); 129 | } 130 | 131 | /** 132 | * 前端预检查是否页面在配置项中 133 | * 134 | * @param {string} [url] - 跳转url 135 | * @return {boolean} 是否存在 136 | */ 137 | preCheckPageExist(url) { 138 | let parsed = parseUrl(url); 139 | url = parsed.pathname; 140 | // 如果pages中存在该页面,则通过 141 | if (this.appConfig.pages.includes(url)) { 142 | return true; 143 | } 144 | 145 | // 有使用Component构造器构造的页面,则通过 146 | if (masterManager 147 | && masterManager.pagesQueue 148 | && Object.keys(masterManager.pagesQueue).includes(url) 149 | ) { 150 | return true; 151 | } 152 | 153 | // 获取分包中的path 154 | let subPackagesPages = []; 155 | this.appConfig.subPackages 156 | && this.appConfig.subPackages.forEach(subPackage => { 157 | // 此处兼容两种配置 158 | let pages = subPackage.pages.map(page => 159 | (subPackage.root + '/' + page).replace('//', '/') 160 | ); 161 | subPackagesPages = subPackagesPages.concat(pages); 162 | }); 163 | 164 | // 如果分包的pages中存在该页面,则通过 165 | if (subPackagesPages.includes(url)) { 166 | return true; 167 | } 168 | 169 | // 不通过,走路由失败的逻辑 170 | this.handleNavigatorError(parsed); 171 | return false; 172 | } 173 | 174 | /** 175 | * 调用用户自定义onPageNotFound方法 176 | * 177 | * @param {string} [parsed] - 跳转url 178 | */ 179 | handleNavigatorError(parsed) { 180 | let app = this.context.getApp(); 181 | app && app._onPageNotFound({ 182 | appInfo: this.context.appInfo || {}, 183 | event: { 184 | page: parsed.pathname, 185 | query: parsed.query, 186 | isEntryPage: false 187 | }, 188 | type: 'onPageNotFound' 189 | }); 190 | } 191 | 192 | } -------------------------------------------------------------------------------- /src/master/navigator/slave-common-parts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file the slave's common constant variable or functions 3 | * @author houyu(houyu01@baidu.com) 4 | */ 5 | export const STATUS_MAP = { 6 | INITED: 1, 7 | CREATING: 2, 8 | CREATED: 3, 9 | CLOSED: 0 10 | }; 11 | -------------------------------------------------------------------------------- /src/master/navigator/slave.js: -------------------------------------------------------------------------------- 1 | import {STATUS_MAP} from './slave-common-parts'; 2 | import {createPageInstance, getInitDataAdvanced} from '../page'; 3 | import {getParams, loader, executeWithTryCatch} from '../../utils'; 4 | import splitAppAccessory from '../../utils/splitapp-accessory'; 5 | import swanEvents from '../../utils/swan-events'; 6 | 7 | export default class Slave { 8 | /** 9 | * Slave构造函数 10 | * 11 | * @param {string} uri slave的uri 12 | * @param {string} slaveId slave的唯一标识 13 | * @param {Object} navigationParams slave的唯一标识 14 | * @param {Object} swaninterface slave使用的swan-api 15 | */ 16 | constructor({ 17 | uri, 18 | slaveId = null, 19 | appConfig = {}, 20 | swaninterface = {} 21 | }) { 22 | this.uri = uri.split('?')[0]; 23 | this.accessUri = uri; 24 | this.slaveId = slaveId; 25 | this.status = STATUS_MAP.INITED; 26 | this.appConfig = appConfig; 27 | this.swaninterface = swaninterface; 28 | this.userPageInstance = {}; 29 | this.appRootPath = appConfig.appRootPath; 30 | /* globals masterManager */ 31 | this.loadJsCommunicator = masterManager.communicator; 32 | } 33 | 34 | /** 35 | * 初始化为第一个页面 36 | * 37 | * @param {Object} initParams 初始化的配置参数 38 | * @return {Promise} 返回初始化之后的Promise流 39 | */ 40 | init(initParams) { 41 | this.isFirstPage = true; 42 | return Promise 43 | .resolve(initParams) 44 | .then(initParams => { 45 | swanEvents('masterActiveInitAction'); 46 | if (!!initParams.preventAppLoad) { 47 | return initParams; 48 | } 49 | // const loadCommonJs = this.appConfig.splitAppJs 50 | // && !this.appConfig.subPackages 51 | // ? 'common.js' : 'app.js'; 52 | const loadCommonJs = 'app.js'; 53 | return loader 54 | .loadjs(`${this.appRootPath}/${loadCommonJs}`, 'masterActiveAppJsLoaded') 55 | .then(() => { 56 | return this.loadJs.call(this, initParams); 57 | }); 58 | }) 59 | .then(initParams => { 60 | this.uri = initParams.pageUrl.split('?')[0]; 61 | this.accessUri = initParams.pageUrl; 62 | this.slaveId = initParams.slaveId; 63 | // init的事件为客户端处理,确保是在slave加载完成之后,所以可以直接派发 64 | this.swaninterface.communicator.fireMessage({ 65 | type: `slaveLoaded${this.slaveId}`, 66 | message: {slaveId: this.slaveId} 67 | }); 68 | return initParams; 69 | }); 70 | } 71 | 72 | /** 73 | * 根据app.js拆分标识加载业务逻辑 74 | * 75 | * @param {Object} params 加载业务逻辑的参数 76 | * @return {Promise} 返回初始化之后的Promise流 77 | */ 78 | loadJs(params) { 79 | return new Promise(resolve => { 80 | // 如果有分包,且有子包的话 81 | // if (this.appConfig.splitAppJs && !this.appConfig.subPackages) { 82 | // this.loadFirst(params) 83 | // .then(() => resolve(params)); 84 | // } 85 | // // 如果没有分包,有root的时候,加载app.js 86 | // else if (!!params.root) { 87 | // loader.loadjs(`${this.appRootPath}/${params.root}/app.js`) 88 | // .then(() => resolve(params)); 89 | // } 90 | // // 如果均没有,则直接返回 91 | // else { 92 | resolve(params); 93 | // } 94 | }); 95 | } 96 | 97 | /** 98 | * 加载首页业务逻辑, 首屏渲染完成利用之前的通信方式sendLogMessage, 节省一次通信 99 | * 100 | * @param {Object} params 加载首屏页面业务逻辑的参数 101 | * @return {Promise} 返回初始化之后的Promise流 102 | */ 103 | loadFirst(params) { 104 | this.loadJsCommunicator 105 | .onMessage('slaveAttached', event => { 106 | return +event.slaveId === +this.slaveId 107 | && this.loadPages(params); 108 | }, {once: true}); 109 | 110 | const firstPageUri = params.pageUrl.split('?')[0]; 111 | if (splitAppAccessory.tabBarList.length > 0) { 112 | let pageUriList = splitAppAccessory.tabBarList 113 | .map(tab => { 114 | let pageUri = tab.pagePath.split('?')[0]; 115 | return pageUri; 116 | }); 117 | if (pageUriList.indexOf(firstPageUri) < 0) { 118 | pageUriList.push(firstPageUri); 119 | } 120 | return Promise 121 | .all(pageUriList.map(pageUri => { 122 | return loader.loadjs(`${this.appRootPath}/${pageUri}.js`); 123 | })); 124 | } 125 | else { 126 | return loader.loadjs(`${this.appRootPath}/${firstPageUri}.js`); 127 | } 128 | } 129 | 130 | /** 131 | * 加载其它所有pages业务逻辑 132 | * 133 | * @param {Object} params 加载首屏页面业务代码的参数 134 | */ 135 | loadPages(params) { 136 | Promise 137 | .all([ 138 | loader.loadjs(`${this.appRootPath}/pages.js`) 139 | ]) 140 | .then(() => { 141 | splitAppAccessory.allJsLoaded = true; 142 | typeof splitAppAccessory.routeResolve === 'function' 143 | && splitAppAccessory.routeResolve(); 144 | }); 145 | } 146 | 147 | /** 148 | * 入栈之后的生命周期方法 149 | * 150 | * @return {Object} 入栈之后,创建的本slave的页面实例对象 151 | */ 152 | onEnqueue() { 153 | return this.createPageInstance(); 154 | } 155 | 156 | /** 157 | * 判断slave当前状态 158 | * 159 | * @return {boolean} 当前状态 160 | */ 161 | isCreated() { 162 | return this.status === STATUS_MAP.CREATED; 163 | } 164 | 165 | /** 166 | * 将slave实例与用户的page对象进行绑定,一实例一对象,自己管理自己的页面对象 167 | * userPageInstance为用户(开发者)定义的页面对象 168 | * 169 | * @param {Object} userPageInstance 开发者设定的页面的生成实例 170 | */ 171 | setUserPageInstance(userPageInstance) { 172 | this.userPageInstance = userPageInstance; 173 | } 174 | 175 | /** 176 | * 创建页面实例,并且,当slave加载完成之后,向slave传递初始化data 177 | * 178 | * @return {Promise} 创建完成的事件流 179 | */ 180 | createPageInstance() { 181 | if (this.isCreated()) { 182 | return Promise.resolve(); 183 | } 184 | swanEvents('masterActiveCreatePageFlowStart', { 185 | uri: this.uri 186 | }); 187 | const userPageInstance = createPageInstance(this.accessUri, this.slaveId, this.appConfig); 188 | const query = userPageInstance.privateProperties.accessUri.split('?')[1]; 189 | this.setUserPageInstance(userPageInstance); 190 | 191 | try { 192 | swanEvents('masterPageOnLoadHookStart'); 193 | userPageInstance._onLoad(getParams(query)); 194 | swanEvents('masterPageOnLoadHookEnd'); 195 | } 196 | catch (e) { 197 | // avoid empty state 198 | } 199 | this.status = STATUS_MAP.CREATED; 200 | console.log(`Master 监听 slaveLoaded 事件,slaveId=${this.slaveId}`); 201 | return this.swaninterface.invoke('loadJs', { 202 | uri: this.uri, 203 | eventObj: { 204 | wvID: this.slaveId 205 | }, 206 | success: params => { 207 | swanEvents('masterActiveCreatePageFlowEnd'); 208 | swanEvents('masterActiveSendInitdataStart'); 209 | userPageInstance.privateMethod 210 | .sendInitData.call(userPageInstance, this.appConfig); 211 | swanEvents('masterActiveSendInitdataEnd'); 212 | } 213 | }); 214 | } 215 | 216 | /** 217 | * 判断当前slave是否某一特定slave 218 | * 219 | * @param {string} tag 表示某一slave的特殊标记uri/slaveId均可 220 | * @return {boolean} 是否是当前slave 221 | */ 222 | isTheSlave(tag) { 223 | return this.uri.split('?')[0] === ('' + tag).split('?')[0] 224 | || +this.slaveId === +tag; 225 | } 226 | 227 | /** 228 | * 在当前slave中查找slave,对于普通slave来讲,获取的就是自己 229 | * 230 | * @return {Object} 当前slave实例 231 | */ 232 | findChild() { 233 | return this; 234 | } 235 | 236 | /** 237 | * 页面打开逻辑open分支判断 238 | * 239 | * @param {Object} navigationParams 配置项 240 | * @return {Object} 打开页面以后返回对象 241 | */ 242 | open(navigationParams) { 243 | return new Promise(resolve => { 244 | if (splitAppAccessory.allJsLoaded) { 245 | resolve(); 246 | } 247 | else { 248 | splitAppAccessory.routeResolve = resolve; 249 | } 250 | }) 251 | .then(() => this.openPage(navigationParams)); 252 | } 253 | 254 | /** 255 | * 页面真正打开的执行逻辑 256 | * 257 | * @param {Object} navigationParams - 打开页面参数 258 | * @return {Object} 打开页面以后返回对象 259 | */ 260 | openPage(navigationParams) { 261 | this.status = STATUS_MAP.CREATING; 262 | let {data, componentsData} = getInitDataAdvanced(navigationParams.url); 263 | return new Promise((resolve, reject) => { 264 | this.swaninterface.invoke('navigateTo', { 265 | ...navigationParams, 266 | initData: {data, componentsData}, 267 | ...{ 268 | success: res => { 269 | executeWithTryCatch( 270 | navigationParams.success, 271 | null, 272 | 'success api execute error' 273 | ); 274 | resolve(res); 275 | }, 276 | fail: res => { 277 | executeWithTryCatch( 278 | navigationParams.fail, 279 | null, 280 | 'fail api execute error' 281 | ); 282 | reject(res); 283 | }, 284 | complete: res => { 285 | executeWithTryCatch( 286 | navigationParams.complete, 287 | null, 288 | 'complete api execute error' 289 | ); 290 | } 291 | } 292 | }); 293 | }) 294 | .then(res => { 295 | if (res.root) { 296 | return loader 297 | .loadjs(`${this.appRootPath}/${res.root}/app.js`) 298 | .then(() => res); 299 | } 300 | return res; 301 | }) 302 | .then(res => { 303 | this.slaveId = res.wvID; 304 | return res; 305 | }); 306 | } 307 | 308 | /** 309 | * 获取当前slave的开发者实例 310 | * 311 | * @return {Object} 开发者的slave实例 312 | */ 313 | getUserPageInstance() { 314 | return this.userPageInstance; 315 | } 316 | 317 | /** 318 | * 设置slave的id 319 | * 320 | * @param {string} slaveId slave的客户端给出的id 321 | * @return {Object} 当前slave的操作实例 322 | */ 323 | setSlaveId(slaveId) { 324 | // 如果新的slaveid与之前的slaveid不相等,证明本slave重新被创建,则进行一次重置 325 | if (+this.slaveId !== +slaveId) { 326 | this.status = STATUS_MAP.CREATING; 327 | } 328 | this.slaveId = slaveId; 329 | return this; 330 | } 331 | 332 | } -------------------------------------------------------------------------------- /src/master/page/index.js: -------------------------------------------------------------------------------- 1 | import {Data, getParams, deepClone, getAppInfo} from '../../utils'; 2 | import {getPagePrototypeInstance} from './page-prototype'; 3 | import EventsEmitter from '../../utils/events-emitter'; 4 | import SlaveEventsRouter from './slave-events-router'; 5 | import swanEvents from '../../utils/swan-events'; 6 | 7 | const pageLifeCycleEventEmitter = new EventsEmitter(); 8 | 9 | /** 10 | * slave的绑定事件初始化 11 | * 12 | * @param {Object} [master] - master全局变量实例 13 | * @return {Object} - 所有slave事件的事件流集合 14 | */ 15 | export const slaveEventInit = master => { 16 | const slaveEventsRouter = new SlaveEventsRouter( 17 | master, 18 | pageLifeCycleEventEmitter 19 | ); 20 | slaveEventsRouter.initbindingEvents(); 21 | return { 22 | 23 | pageLifeCycleEventEmitter 24 | }; 25 | }; 26 | 27 | /** 28 | * 初始化页面实例,附带页面原型,页面的创建函数 29 | * 30 | * @param {Object} [pageInstance] - 页面实例 31 | * @param {string} [slaveId] - 页面的slaveId 32 | * @param {string} [accessUri] - 页面的URL(带query) 33 | * @param {Object} [masterManager] - 小程序的全局挂载实例 34 | * @param {Object} [globalSwan] - 页面的swan接口对象(开发者用的那个swan) 35 | * @param {Object} [appConfig] - 页面配置对象(app.json中的配置内容) 36 | * @return {Object} - 创建页面实例 37 | */ 38 | const initUserPageInstance = (pageInstance, slaveId, accessUri, masterManager, globalSwan, appConfig) => { 39 | const swaninterface = masterManager.swaninterface; 40 | const appid = getAppInfo(swaninterface).appid; 41 | slaveId = '' + slaveId; 42 | 43 | // 获取page的原型方法单例,防止对每个page都生成方法集合 44 | const pagePrototype = getPagePrototypeInstance(masterManager, globalSwan, pageLifeCycleEventEmitter); 45 | const [route, query] = accessUri.split('?'); 46 | // merge page的原型方法 47 | Object.assign(pageInstance, 48 | { 49 | privateProperties: { 50 | slaveId, 51 | accessUri, 52 | raw: new Data(pageInstance.data), 53 | // share: new Share(swaninterface, appid, pageInstance, appConfig.pages[0]) 54 | }, 55 | route, 56 | options: getParams(query) 57 | }, 58 | pagePrototype 59 | ); 60 | swanEvents('masterActiveInitUserPageInstance', pageInstance); 61 | 62 | return pageInstance; 63 | }; 64 | 65 | /** 66 | * 克隆page对象方法 67 | * 68 | * @param {Object} [pagePrototype] - 开发者的页面原型 69 | * @return {Object} - 克隆出的页面实例 70 | */ 71 | const cloneSwanPageObject = (pagePrototype = {}) => { 72 | let newSwanObject = {}; 73 | pagePrototype.data = pagePrototype.data || {}; 74 | newSwanObject.data = JSON.parse(JSON.stringify(pagePrototype.data)); 75 | Object.keys(pagePrototype).filter(pageKey => pageKey !== 'data') 76 | .forEach(pageKey => { 77 | if (!isWriteProtected(pageKey)) { 78 | newSwanObject[pageKey] = deepClone(pagePrototype[pageKey]); 79 | } 80 | else { 81 | console.error(`关键字保护:${pageKey} is write-protected`); 82 | } 83 | }); 84 | return newSwanObject; 85 | }; 86 | 87 | // Page对象中,写保护的所有key 88 | const isWriteProtected = pageKey => { 89 | const protectedKeys = [ 90 | 'uri', 'setData', 'getData', 'shiftData', 91 | 'popData', 'unshiftData', 'spliceData', 92 | 'privateMethod', 'privateProperties' 93 | ]; 94 | return false; 95 | }; 96 | 97 | // 当页面打开时(即slave加载完毕,通知master时)master可以将页面对应的page对象进行实例化 98 | export const createPageInstance = (accessUri, slaveId, appConfig) => { 99 | swanEvents('masterActiveGetUserPageInstance'); 100 | // 过滤传过来的originUri,临时方案;后面和生成path做个统一的方法; 101 | const uriPath = accessUri.split('?')[0]; 102 | const userPageInstance = initUserPageInstance( 103 | cloneSwanPageObject(global.masterManager.pagesQueue[uriPath]), 104 | slaveId, 105 | accessUri, 106 | global.masterManager, 107 | global.swan, 108 | appConfig 109 | ); 110 | return userPageInstance; 111 | }; 112 | 113 | // 优化redirectTo和naviagteTo时的性能 114 | // 提前在调用路由端能力之前把initData准备好 115 | // 减少一次单独发送initData的端能力调用 116 | export const getInitDataAdvanced = accessUri => { 117 | swanEvents('master_active_get_salve_data_advanced'); 118 | const uriPath = accessUri.split('?')[0]; 119 | // 如果是在子包中的页面,直接返回,这种情况不做处理 120 | if (!global.masterManager.pagesQueue[uriPath]) { 121 | return { 122 | data: {}, 123 | componentsData: {} 124 | }; 125 | } 126 | // 因为获取页面原型的方法是单例,因此这里提前调用没有副作用 127 | const pagePrototype = getPagePrototypeInstance(global.masterManager, global.swan, pageLifeCycleEventEmitter); 128 | // 现获取页面本身的data 129 | let data = global.masterManager.pagesQueue[uriPath].data || {}; 130 | // 再获取自定义组件里面的data 131 | // let componentsData = pagePrototype 132 | // .privateMethod 133 | // .getCustomComponentsData(global.masterManager.pagesQueue[uriPath].usingComponents); 134 | let componentsData = {}; 135 | return { 136 | data, 137 | componentsData 138 | }; 139 | }; 140 | 141 | /** 142 | * 暴露给用户的 Page 方法 143 | * 144 | * @param {Object} [pageObj] - 开发者的page原型对象 145 | * @return {Object} - 开发者的page原型对象 146 | */ 147 | export const Page = pageObj => { 148 | const uri = global.__swanRoute; 149 | const usingComponents = global.usingComponents || []; 150 | const pageProto = {data: {}}; 151 | global.masterManager.pagesQueue[uri] = {...pageProto, ...pageObj, uri, usingComponents}; 152 | return global.masterManager.pagesQueue[uri]; 153 | }; -------------------------------------------------------------------------------- /src/master/page/life-cycle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 单个页面的生命周期管理 3 | * @author houyu(houyu01@baidu.com) 4 | */ 5 | import swanEvents from '../../utils/swan-events'; 6 | 7 | // const customComponentPageLifetimes = ['onShow', 'onHide']; 8 | 9 | /** 10 | * 触发页面中自定义组件的pageLifetimes (show|hide) 11 | * 12 | * @param {Object} customComponent - 当前自定义组件实例 13 | * @param {string} type - 事件类型 14 | * @param {Object} params - 页面onLoad的参数 15 | */ 16 | // const pageLifetimesExecutor = (customComponent, type, params) => { 17 | // if (customComponentPageLifetimes.includes(type)) { 18 | // const eventType = type.replace(/^(on)/, '').toLowerCase(); 19 | // customComponent.pageLifetimes 20 | // && customComponent.pageLifetimes[eventType] 21 | // && customComponent.pageLifetimes[eventType].call(customComponent, params); 22 | // } 23 | // }; 24 | 25 | /** 26 | * 自定义组件page化, 生命周期触发 27 | * 28 | * @param {Object} pageInstance - 页面实例 29 | * @param {string} type - 事件类型 30 | * @param {Object} params - 页面onLoad的参数 31 | */ 32 | // const customComponentLifeCycleExecutor = (pageInstance, type, params) => { 33 | // const customComponents = pageInstance.privateProperties.customComponents; 34 | // if (customComponents) { 35 | // for (let customComponentId in customComponents) { 36 | // const customComponent = customComponents[customComponentId]; 37 | // // 触发onLoad时自定义组件还没有创建好, 将触发onLoad时机放到onReady前 38 | // // 保持与page级生命周期(目前page未修复前onLoad -> onReady -> onShow)同时序 39 | // if (pageInstance._isCustomComponentPage 40 | // && pageInstance.route === customComponent.is 41 | // && type === 'onReady' 42 | // ) { 43 | // customComponent.onLoad 44 | // && customComponent.onLoad.call(customComponent, pageInstance._onLoadParams); 45 | // } 46 | // 47 | // pageLifetimesExecutor(customComponent, type, params); 48 | // 49 | // // 自定义组件当做页面使用时, 其生命周期的处理, 只有page化的该自定义组件享有页面级生命周期(methods内) 50 | // pageInstance._isCustomComponentPage 51 | // && pageInstance.route === customComponent.is 52 | // && customComponent[type] 53 | // && customComponent[type].call(customComponent, params); 54 | // } 55 | // } 56 | // }; 57 | 58 | /* eslint-disable fecs-camelcase */ 59 | const lifeCyclePrototype = { 60 | 61 | /** 62 | * onLoad生命周期,在页面入栈后既开始执行,在页面展现前既开始执行 63 | * 64 | * @param {Object} [params] - 页面onLoad的参数 65 | */ 66 | _onLoad(params) { 67 | try { 68 | this.onLoad && this.onLoad(params); 69 | this._onLoadParams = params; // 给自定义组件page化使用 70 | } 71 | catch (e) { 72 | console.error(e); 73 | } 74 | this._sendPageLifeCycleMessage('onLoad', params); 75 | }, 76 | 77 | /** 78 | * onReady生命周期,在页面渲染完成,并通知master之后执行 79 | * 80 | * @param {Object} [params] - 页面onReady的参数 81 | * 82 | */ 83 | _onReady(params) { 84 | try { 85 | this.onReady && this.onReady(params); 86 | // customComponentLifeCycleExecutor(this, 'onReady', params); 87 | } 88 | catch (e) { 89 | console.error(e); 90 | } 91 | this._sendPageLifeCycleMessage('onReady', params); 92 | }, 93 | 94 | /** 95 | * onShow生命周期,在页面展现出来后,但还未渲染前执行(或页面从后台切到前台,则执行) 96 | * 97 | * @param {Object} [params] - onShow生命周期的参数 98 | */ 99 | _onShow(params) { 100 | try { 101 | this._sendPageLifeCycleMessage('onPreShow', params); 102 | this.onShow && this.onShow(params); 103 | // customComponentLifeCycleExecutor(this, 'onShow', params); 104 | } 105 | catch (e) { 106 | console.error(e); 107 | } 108 | swanEvents('pageSwitchEnd', { 109 | slaveId: this.privateProperties.slaveId, 110 | timestamp: Date.now() + '' 111 | }); 112 | this._sendPageLifeCycleMessage('onShow', params); 113 | }, 114 | 115 | /** 116 | * onHide生命周期,在页面切换到后台,不在当前界时触发 117 | * 118 | * @param {Object} [params] - onHide生命周期的参数 119 | */ 120 | _onHide(params) { 121 | this.onHide && this.onHide(params); 122 | // customComponentLifeCycleExecutor(this, 'onHide', params); 123 | this._sendPageLifeCycleMessage('onHide', params); 124 | }, 125 | 126 | /** 127 | * onUnload生命周期,页面被卸载时执行(页面退栈) 128 | * 129 | * @param {Object} [params] - onUnload的生命周期参数 130 | */ 131 | _onUnload(params) { 132 | this.onUnload && this.onUnload(params); 133 | this._onHide(); 134 | // customComponentLifeCycleExecutor(this, 'onUnload', params); 135 | this._sendPageLifeCycleMessage('onUnload', params); 136 | }, 137 | 138 | /** 139 | * onForceReLaunch生命周期,小程序面板点重启时(强制relauch) 140 | * 141 | * @param {Object} params - onForceReLaunch的生命周期参数 142 | */ 143 | _onForceReLaunch(params) { 144 | this.onForceReLaunch && this.onForceReLaunch(params); 145 | this._sendPageLifeCycleMessage('onForceReLaunch', params); 146 | }, 147 | 148 | /** 149 | * 页面下拉刷新时执行 150 | * 151 | * @param {Object} [params] - 页面发生下拉刷新时的参数 152 | */ 153 | _pullDownRefresh(params) { 154 | this.onPullDownRefresh && this.onPullDownRefresh(params); 155 | // customComponentLifeCycleExecutor(this, 'onPullDownRefresh', params); 156 | this._sendPageLifeCycleMessage('onPullDownRefresh', params); 157 | }, 158 | 159 | _onTabItemTap(params) { 160 | const proccessedParams = [].concat(params)[0]; 161 | this.onTabItemTap && this.onTabItemTap(proccessedParams); 162 | // customComponentLifeCycleExecutor(this, 'onTabItemTap', params); 163 | this._sendPageLifeCycleMessage('onTabItemTap', params); 164 | }, 165 | 166 | // _share(params) { 167 | // this._sendPageLifeCycleMessage('beforeShare', params); 168 | // // 分享不需要清除之前postMessage过来的数据 169 | // this.privateProperties.share.shareAction(params) 170 | // .then(res => this._sendPageLifeCycleMessage('shareSuccess', res)) 171 | // .catch(err => this._sendPageLifeCycleMessage('shareFailed', err)); 172 | // this._sendPageLifeCycleMessage('shareAction', params); 173 | // }, 174 | 175 | _reachBottom(params) { 176 | this.onReachBottom && this.onReachBottom(params); 177 | // customComponentLifeCycleExecutor(this, 'onReachBottom', params); 178 | this._sendPageLifeCycleMessage('onReachBottom', params); 179 | }, 180 | 181 | _onPageScroll(params) { 182 | this.onPageScroll && this.onPageScroll(params); 183 | // customComponentLifeCycleExecutor(this, 'onPageScroll', params); 184 | this._sendPageLifeCycleMessage('onPageScroll', params); 185 | }, 186 | 187 | /** 188 | * 向事件流中发送生命周期通知,以便于下游使用 189 | * 190 | * @param {string} [eventName] - 发生的事件名称 191 | * @param {Object} [e] - 发生事件的参数 192 | */ 193 | _sendPageLifeCycleMessage(eventName, e = {}) { 194 | this._pageLifeCycleEventEmitter.fireMessage({ 195 | type: 'PagelifeCycle', 196 | params: { 197 | eventName, 198 | slaveId: this.privateProperties.slaveId, 199 | accessUri: this.privateProperties.accessUri, 200 | e 201 | } 202 | }); 203 | } 204 | }; 205 | 206 | /** 207 | * Page中的生命周期 208 | * @param {Object} [pagePrototype] - Page的prototype 209 | * @param {Object} [swaninterface] - swaninterface 210 | * @param {Object} [pageLifeCycleEventEmitter] - 页面生命周期的事件流对象 211 | * @return merge后的Page的prototype 212 | */ 213 | export const initLifeCycle = (mastermanager, pagePrototype, pageLifeCycleEventEmitter) => { 214 | const swaninterface = mastermanager.swaninterface; 215 | return Object.assign(pagePrototype, lifeCyclePrototype, { 216 | _pageLifeCycleEventEmitter: pageLifeCycleEventEmitter 217 | }); 218 | }; 219 | /* eslint-enable fecs-camelcase */ 220 | -------------------------------------------------------------------------------- /src/master/page/page-prototype.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file page类的原型对象 3 | * @author houyu(houyu01@baidu.com) 4 | */ 5 | import {initLifeCycle} from './life-cycle'; 6 | // import {builtInBehaviorsAction} from '../custom-component/inner-behaviors'; 7 | 8 | const noop = () => {}; 9 | const SETDATA_THROTTLE_TIME = 10; 10 | 11 | /** 12 | * 创建一个用户的page对象的原型单例 13 | * @param {Object} [masterManager] masterManager底层接口方法 14 | * @return {Object} page存放着所有page对象的原型方法的对象 15 | */ 16 | export const createPagePrototype = (masterManager, globalSwan) => { 17 | return { 18 | getData(path) { 19 | return this.privateProperties.raw.get(path); 20 | }, 21 | /** 22 | * 通用的,向slave传递的数据操作统一方法 23 | * 24 | * @param {Object} dataParams - 数据操作的参数 25 | * @param {string} dataParams.type - 数据操作的类型 26 | * @param {string} dataParams.path - 数据操作的路径值 27 | * @param {Object} dataParams.value - 数据操作的数据值 28 | * @param {Function} dataParams.cb - 数据操作后的回调 29 | * @param {Object} dataParams.options - 数据操作的额外选项 30 | */ 31 | sendDataOperation({ 32 | type, 33 | path, 34 | value, 35 | cb = noop, 36 | options 37 | }) { 38 | const { 39 | raw, 40 | slaveId 41 | } = this.privateProperties; 42 | const setObject = typeof path === 'object' ? path : { 43 | [path]: value 44 | }; 45 | cb = typeof cb === 'function' ? cb : noop; 46 | const callback = typeof value === 'function' ? value : cb; 47 | const pageUpdateStart = Date.now() + ''; 48 | 49 | // 暂时只优化自定义组件的数据设置,进行throttle 50 | if (type === 'setCustomComponent') { 51 | this.operationSet = this.operationSet || []; 52 | this.operationSet.push({ 53 | setObject, 54 | options, 55 | pageUpdateStart 56 | }); 57 | clearTimeout(this.operationTimmer); 58 | this.operationTimmer = setTimeout(() => { 59 | // 先set到本地,然后通知slave更新视图 60 | this.sendMessageToCurSlave({ 61 | slaveId, 62 | type: `${type}Data`, 63 | operationSet: this.operationSet 64 | }); 65 | this.operationSet = []; 66 | }, SETDATA_THROTTLE_TIME); 67 | } 68 | else { 69 | // 先set到本地,然后通知slave更新视图 70 | this.sendMessageToCurSlave({ 71 | type: `${type}Data`, 72 | slaveId, 73 | setObject, 74 | pageUpdateStart, 75 | options 76 | }); 77 | } 78 | // 更新data 79 | for (const path in setObject) { 80 | raw[type] && raw[type](path, setObject[path]); 81 | } 82 | this.nextTick(callback); 83 | }, 84 | 85 | sendMessageToCurSlave(message) { 86 | masterManager.communicator.sendMessage(this.privateProperties.slaveId, message); 87 | }, 88 | 89 | /** 90 | * 页面中挂载的setData操作方法,操作后,会传到slave,对视图进行更改 91 | * 92 | * @param {string|Object} [path] - setData的数据操作路径,或setData的对象{path: value} 93 | * @param {*} [value] - setData的操作值 94 | * @param {Function} [cb] - setData的回调函数 95 | */ 96 | setData(path, value, cb) { 97 | this.sendDataOperation({ 98 | type: 'set', 99 | path, 100 | value, 101 | cb 102 | }); 103 | }, 104 | // splice方法系列 105 | pushData(path, value, cb) { 106 | this.sendDataOperation({ 107 | type: 'push', 108 | path, 109 | value, 110 | cb 111 | }); 112 | }, 113 | popData(path, cb) { 114 | this.sendDataOperation({ 115 | type: 'pop', 116 | path, 117 | value: null, 118 | cb 119 | }); 120 | }, 121 | unshiftData(path, value, cb) { 122 | this.sendDataOperation({ 123 | type: 'unshift', 124 | path, 125 | value, 126 | cb 127 | }); 128 | }, 129 | shiftData(path, cb) { 130 | this.sendDataOperation({ 131 | type: 'shift', 132 | path, 133 | value: null, 134 | cb 135 | }); 136 | }, 137 | removeAtData(path, index, cb) { 138 | this.sendDataOperation({ 139 | type: 'remove', 140 | path, 141 | value: index, 142 | cb 143 | }); 144 | }, 145 | spliceData(path, args, cb) { 146 | this.sendDataOperation({ 147 | type: 'splice', 148 | path, 149 | value: args, 150 | cb 151 | }); 152 | }, 153 | 154 | createCanvasContext(...args) { 155 | return globalSwan.createCanvasContext.call(this, ...args); 156 | }, 157 | nextTick(fn) { 158 | masterManager.communicator 159 | .onMessage(`nextTick:${this.privateProperties.slaveId}`, () => fn(), { 160 | once: true 161 | }); 162 | }, 163 | 164 | /** 165 | * 页面级选择某个(id、class)全部的自定义组件 166 | * 167 | * @param {string} selector - 待选择的自定义组件id 168 | * @return {Array} - 所选的全部自定义组件集合 169 | */ 170 | selectAllComponents(selector) { 171 | return this.privateMethod 172 | .getComponentsFromList(this.privateProperties.customComponents, selector, '*'); 173 | }, 174 | 175 | /** 176 | * 页面级选择某个(id、class)第一个自定义组件 177 | * 178 | * @param {string} selector - 待选择的自定义组件id 179 | * @return {Object} - 自定义组件被拦截的export输出 | 所选的自定义组件实例 180 | */ 181 | selectComponent(selector) { 182 | const selectRes = this.selectAllComponents(selector)[0]; 183 | // 当自定义组件中包含内置behavior时, 进行拦截操作 184 | const exportRes = builtInBehaviorsAction('swanComponentExport', selectRes); 185 | return exportRes.isBuiltInBehavior ? exportRes.resData : selectRes; 186 | }, 187 | 188 | // page实例中的私有方法合集 189 | privateMethod: { 190 | 191 | /** 192 | * 发送初始数据到当前Page对应的slave上 193 | * 194 | * @param {Object} [appConfig] - 发送初始化数据时携带的appConfig信息 195 | */ 196 | sendInitData(appConfig) { 197 | masterManager.communicator.sendMessage( 198 | this.privateProperties.slaveId, 199 | { 200 | type: 'initData', 201 | path: 'initData', 202 | value: this.data, 203 | // extraMessage: { 204 | // componentsData: this.privateMethod.getCustomComponentsData 205 | // .call(this, this.usingComponents, masterManager.communicator) 206 | // }, 207 | slaveId: this.privateProperties.slaveId, 208 | appConfig 209 | } 210 | ); 211 | } 212 | } 213 | }; 214 | }; 215 | 216 | let pagePrototype = null; 217 | // 获取page的prototype的单例方法,节省初始化 218 | export const getPagePrototypeInstance = (masterManager, globalSwan, pageLifeCycleEventEmitter) => { 219 | if (!pagePrototype) { 220 | pagePrototype = createPagePrototype(masterManager, globalSwan); 221 | initLifeCycle(masterManager, pagePrototype, pageLifeCycleEventEmitter); 222 | } 223 | return pagePrototype; 224 | }; 225 | -------------------------------------------------------------------------------- /src/master/page/slave-events-router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 小程序slave中组件相关的事件的派发,到达master中 3 | * 需要转发给:开发者/私有对象/日志 进行对应处理 4 | * @author houyu(houyu01@baidu.com) 5 | */ 6 | import swanEvents from '../../utils/swan-events'; 7 | import Slave from '../navigator/slave'; 8 | // import TabSlave from '../navigator/tab-slave'; 9 | 10 | /** 11 | * slave的事件封装分发器,封装了大量的无逻辑公有方法 12 | * 13 | * @class 14 | */ 15 | export default class SlaveEventsRouter { 16 | constructor(masterManager, pageLifeCycleEventEmitter) { 17 | this.masterManager = masterManager; 18 | this.history = masterManager.navigator.history; 19 | this.slaveCommunicator = masterManager.communicator; 20 | this.pageLifeCycleEventEmitter = pageLifeCycleEventEmitter; 21 | swanEvents('masterPreloadConstructSlaveEventsRouter'); 22 | } 23 | 24 | /** 25 | * 初始化所有页面级相关事件的绑定 26 | */ 27 | initbindingEvents() { 28 | // this.bindPrivateEvents(); 29 | this.bindDeveloperEvents(); 30 | // this.bindEnvironmentEvents(); 31 | this.bindLifeCycleEvent(this.pageLifeCycleEventEmitter); 32 | swanEvents('masterPreloadInitBindingEvents'); 33 | } 34 | 35 | /** 36 | * 绑定开发者绑定的events 37 | */ 38 | bindDeveloperEvents() { 39 | this.slaveCommunicator.onMessage('event', event => { 40 | swanEvents('masterListenEvent', event); 41 | 42 | const eventOccurredPageObject = this.history.seek(event.slaveId).getUserPageInstance(); 43 | // if (event.customEventParams) { 44 | // const nodeId = event.customEventParams.nodeId; 45 | // const reflectComponent = eventOccurredPageObject 46 | // .privateProperties.customComponents[nodeId]; 47 | // if (reflectComponent[event.value.reflectMethod]) { 48 | // reflectComponent[event.value.reflectMethod] 49 | // .call(reflectComponent, event.value.e); 50 | // } 51 | // } 52 | // else 53 | if (eventOccurredPageObject[event.value.reflectMethod]) { 54 | eventOccurredPageObject[event.value.reflectMethod] 55 | .call(eventOccurredPageObject, event.value.e); 56 | } 57 | }); 58 | } 59 | 60 | /** 61 | * 调用发生事件的页面的同名方法 62 | * 63 | * @param {string} slaveId 想要派发的页面的slave的id 64 | * @param {string} methodName 事件名称 65 | * @param {Object|null} options 派发事件的可配置项 66 | * @param {...*} args 透传的事件参数 67 | * @return {*} 调用私有方法后,私有方法的返回值 68 | */ 69 | // callEventOccurredPageMethod(slaveId, methodName, options = {}, ...args) { 70 | // const occurredSlave = this.history.seek(slaveId); 71 | // if (occurredSlave) { 72 | // return occurredSlave.callPrivatePageMethod(methodName, options, ...args); 73 | // } 74 | // return null; 75 | // } 76 | 77 | /** 78 | * 向所有slave,派发事件 79 | * 80 | * @param {string} methodName 事件名称 81 | * @param {Object|null} options 发事件的可配置项 82 | * @param {...*} args 透传的事件参数 83 | */ 84 | // dispatchAllSlaveEvent(methodName, options = {}, ...args) { 85 | // this.history.each(slave => { 86 | // slave.callPrivatePageMethod(methodName, options, ...args); 87 | // }); 88 | // } 89 | 90 | /** 91 | * 框架使用的私有的事件的绑定 92 | */ 93 | // bindPrivateEvents() { 94 | // this.slaveCommunicator.onMessage('abilityMessage', event => { 95 | // if (event.value.type === 'rendered') { 96 | // return; 97 | // } 98 | // try { 99 | // this.callEventOccurredPageMethod(event.slaveId, event.value.type, {}, event.value.params); 100 | // } 101 | // catch (e) { 102 | // console.error(e); 103 | // } 104 | // }); 105 | // } 106 | 107 | /** 108 | * 保证onShow执行在onReady之前 109 | * 110 | * @param {string} currentSlaveId 需要触发的页面的slaveId 111 | */ 112 | // emitPageRender(currentSlaveId) { 113 | // this.slaveCommunicator.onMessage('abilityMessage', events => { 114 | // if (Object.prototype.toString.call(events) !== '[object Array]') { 115 | // events = [events]; 116 | // } 117 | // events.forEach(event => { 118 | // if (event.value.type === 'rendered' 119 | // && event.slaveId === currentSlaveId 120 | // ) { 121 | // this.callEventOccurredPageMethod( 122 | // event.slaveId, 123 | // event.value.type, 124 | // {}, 125 | // event.value.params 126 | // ); 127 | // } 128 | // }); 129 | // }, {listenPreviousEvent: true}); 130 | // } 131 | 132 | /** 133 | * 调用用户在page实例上挂载的方法 134 | * 135 | * @param {String} [slaveId] - 要调用的页面实例的slaveId 136 | * @param {String} [methodName] - 要调用的page实例上的方法名 137 | * @param {...*} [args] - 透传的事件参数 138 | * @return {*} 函数调用后的返回值 139 | */ 140 | callPageMethod(slaveId, methodName, ...args) { 141 | const occurredSlave = this.history.seek(slaveId); 142 | if (occurredSlave) { 143 | const occurredSlavePageObject = occurredSlave.userPageInstance; 144 | if (typeof occurredSlavePageObject[methodName] === 'function') { 145 | try { 146 | return occurredSlavePageObject[methodName](...args); 147 | } 148 | catch (e) { 149 | console.error(e); 150 | } 151 | } 152 | } 153 | return null; 154 | } 155 | 156 | /** 157 | * 绑定开发者绑定的events 158 | */ 159 | // bindDeveloperEvents() { 160 | // this.slaveCommunicator.onMessage('event', event => { 161 | // const eventOccurredPageObject = this.history.seek(event.slaveId).getUserPageInstance(); 162 | // if (event.customEventParams) { 163 | // const nodeId = event.customEventParams.nodeId; 164 | // const reflectComponent = eventOccurredPageObject 165 | // .privateProperties.customComponents[nodeId]; 166 | // if (reflectComponent[event.value.reflectMethod]) { 167 | // reflectComponent[event.value.reflectMethod] 168 | // .call(reflectComponent, event.value.e); 169 | // } 170 | // } 171 | // else if (eventOccurredPageObject[event.value.reflectMethod]) { 172 | // eventOccurredPageObject[event.value.reflectMethod] 173 | // .call(eventOccurredPageObject, event.value.e); 174 | // } 175 | // }); 176 | // } 177 | 178 | /** 179 | * 客户端触发的协议事件,非前端派发 180 | * 用于接收协议事件 181 | * 182 | */ 183 | // bindEnvironmentEvents() { 184 | // this.masterManager.swaninterface 185 | // .bind('sharebtn', event => { 186 | // this.callEventOccurredPageMethod(event.wvID, 'share', {}, event, 'menu'); 187 | // }) 188 | // .bind('accountChange', event => { 189 | // this.dispatchAllSlaveEvent('accountChange'); 190 | // }) 191 | // .bind('backtohome', ({url, from}) => { 192 | // let topSlave = this.history.getTopSlaves()[0]; 193 | // let currentSlaveUrl = ''; 194 | // if (topSlave instanceof Slave) { 195 | // currentSlaveUrl = topSlave.accessUri; 196 | // } 197 | // else if (topSlave instanceof TabSlave) { 198 | // currentSlaveUrl = topSlave.children[topSlave.currentIndex].accessUri; 199 | // } 200 | // if (from !== 'menu' && currentSlaveUrl === url) { 201 | // from === 'relaunch' && swanEvents('masterFePageShow'); 202 | // return; 203 | // } 204 | // 205 | // if (from === 'relaunch') { 206 | // swanEvents('masterOnNewScheme'); 207 | // } 208 | // this.masterManager.navigator.reLaunch({url: `/${url}`}); 209 | // }); 210 | // } 211 | 212 | bindLifeCycleEvent(pageLifeCycleEventEmitter) { 213 | 214 | // const pageEventsToLifeCycle = ['onHide', 'onTabItemTap']; 215 | // 需要稍后补执行的操作集合 216 | // let backPageEventToLifeCycles = { 217 | // onTabItemTap: Object.create(null) 218 | // }; 219 | // 确保onShow在onLoad之后 220 | pageLifeCycleEventEmitter.onMessage('PagelifeCycle', events => { 221 | if (Object.prototype.toString.call(events) !== '[object Array]') { 222 | events = [events]; 223 | } 224 | // 对于客户端事件做一次中转,因为前端有特殊逻辑处理(onShow必须在onLoad之后) 225 | const detectOnShow = event => { 226 | if (event.params.eventName === 'onLoad') { 227 | 228 | swanEvents('masterlifeCycleEventOnShow', event); 229 | 230 | this.masterManager.lifeCycleEventEmitter 231 | .onMessage('onShow' + event.params.slaveId, paramsQueue => { 232 | 233 | // 筛选出本次的onShow的对应参数 234 | paramsQueue = [].concat(paramsQueue); 235 | 236 | // onshow对应的slave 237 | const showedSlave = paramsQueue.filter(params => { 238 | return +params.event.wvID === +event.params.slaveId; 239 | }).slice(-1); 240 | 241 | // 获取对象中的event 242 | const e = showedSlave[0].event; 243 | 244 | // 执行对应slave的onShow 245 | this.callPageMethod(event.params.slaveId, '_onShow', {}, e); 246 | 247 | // 触发页面的onRender逻辑 248 | // this.emitPageRender(event.params.slaveId); 249 | 250 | }, {listenPreviousEvent: true}); 251 | } 252 | }; 253 | events.forEach(detectOnShow); 254 | }, {listenPreviousEvent: true}); 255 | 256 | // pageLifeCycleEventEmitter.onMessage('onTabItemTap', ({event, from}) => { 257 | // let wvID = event.wvID; 258 | // // 客户端触发onTabItemTap先于switchtab 259 | // // 进入新页面pageInstance在onTabItemTap时没有还ready,等待switchtab后内部触发处理 260 | // if (from === 'switchTab') { 261 | // let {type, tabIndexPage} = event; 262 | // let targetEvent = backPageEventToLifeCycles[type]; 263 | // if (targetEvent && targetEvent[tabIndexPage]) { 264 | // this.callPageMethod(wvID, '_' + type, targetEvent[tabIndexPage]); 265 | // backPageEventToLifeCycles[type] = Object.create(null); 266 | // } 267 | // return; 268 | // } 269 | // const targetSlave = this.history.seek(wvID); 270 | // if (targetSlave) { 271 | // // 原逻辑 272 | // this.callPageMethod(wvID, '_onTabItemTap', event); 273 | // } 274 | // else { 275 | // // 保存后续调用。 android对于新页面没有传wvID,所以根据index+pagePath进行统一保证 276 | // backPageEventToLifeCycles.onTabItemTap[event.index + event.pagePath] = event; 277 | // } 278 | // }, {listenPreviousEvent: true}); 279 | 280 | this.masterManager.lifeCycleEventEmitter.onMessage('onHide', e => { 281 | let event = e.event; 282 | this.callPageMethod(event.wvID, '_onHide', {}, event); 283 | }, {listenPreviousEvent: true}); 284 | 285 | // this.masterManager.swaninterface 286 | // .bind('onTabItemTap', event => { 287 | // pageLifeCycleEventEmitter.fireMessage({ 288 | // type: 'onTabItemTap', 289 | // event 290 | // }); 291 | // }); 292 | 293 | // this.masterManager.swaninterface 294 | // .bind('onForceReLaunch', event => { 295 | // let {slaveId, homePath, pagePath} = event; 296 | // let finalReLaunchPath = homePath; 297 | // let currentSlave = this.history.seek(slaveId); 298 | // if (currentSlave) { 299 | // const currentSlavePageObject = currentSlave.userPageInstance; 300 | // if (typeof currentSlavePageObject['onForceReLaunch'] === 'function') { 301 | // try { 302 | // finalReLaunchPath = currentSlavePageObject['onForceReLaunch']({homePath, pagePath}); 303 | // finalReLaunchPath = finalReLaunchPath.replace(/^\//g, ''); 304 | // this.masterManager.navigator.redirectTo({url: `/${finalReLaunchPath}`}); 305 | // return; 306 | // } 307 | // catch (e) { 308 | // finalReLaunchPath = homePath; 309 | // } 310 | // } 311 | // } 312 | // this.masterManager.navigator.reLaunch({ 313 | // url: `/${finalReLaunchPath}`, 314 | // force: true 315 | // }); 316 | // }); 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/master/proccessors/api-proccessor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 对于swan全局对象的处理 3 | * @author houyu(houyu01@baidu.com) 4 | */ 5 | 6 | // 预留,节后上线 7 | // import nextTick from '../../utils/next-tick'; 8 | 9 | /** 10 | * 防止在App与page的onShow中调用过多次数的login 11 | * @param {Object} originSwan 未decorate之前的swan 12 | * @param {Object} pageLifeCycleEventEmitter 页面的生命周期事件对象 13 | * @param {Object} appLifeCycleEventEmitter app的生命周期事件对象 14 | * @return {Function} login函数 15 | */ 16 | const lockLogin = (originSwan, pageLifeCycleEventEmitter, appLifeCycleEventEmitter) => { 17 | let isShowing = false; 18 | let loginTimes = 0; 19 | const originLogin = originSwan['login']; 20 | const lockEvents = ['onShow', 'onPreShow']; 21 | pageLifeCycleEventEmitter.onMessage('PagelifeCycle', ({params}) => { 22 | isShowing = lockEvents.indexOf(params.eventName) === 1; 23 | }); 24 | appLifeCycleEventEmitter.onMessage('ApplifeCycle', ({params}) => { 25 | isShowing = lockEvents.indexOf(params.eventName) === 1; 26 | }); 27 | const locker = { 28 | timesCheck(times, delay) { 29 | if (this['repeat' + times] === undefined) { 30 | this['repeat' + times] = 0; 31 | } 32 | if (this['repeat' + times] >= times) { 33 | return false; 34 | } 35 | if (this['repeat' + times] < 1) { 36 | setTimeout(() => { 37 | this['repeat' + times] = 0; 38 | }, delay); 39 | } 40 | this['repeat' + times]++; 41 | return true; 42 | }, 43 | enter(isShowing) { 44 | // 不在show中,可以任意发送 45 | // 如果在show中,则2秒内不得进入1次以上,30秒内不得进入2次以上 46 | return !isShowing || this.timesCheck(1, 2e3) && this.timesCheck(2, 3e4); 47 | } 48 | }; 49 | 50 | return params => { 51 | if (!locker.enter(isShowing)) { 52 | params.fail && params.fail(); 53 | return false; 54 | } 55 | return originLogin.call(originSwan, params); 56 | }; 57 | }; 58 | 59 | /** 60 | * 处理api的函数,对API进行swan-core本身的代理 61 | * @param {Object} originSwan 原始的api 62 | * @param {Object} context 装饰api使用的上下文 63 | * @param {Object} context.navigator 小程序的navigator对象 64 | * @param {Object} context.swanComponents 小程序的组件 65 | * @param {Object} context.pageLifeCycleEventEmitter 小程序page生命周期的事件流 66 | * @param {Object} context.appLifeCycleEventEmitter 小程序app生命周期的事件流 67 | * @param {Object} context.swanEventsCommunicator 小程序合并的统计事件流 68 | * @param {Object} context.hostShareParamsProccess 小程序自定义的宿主分享处理 69 | * @param {Object} context.communicator 小程序的slave-master通讯器 70 | * @param {Object} context.swaninterface 小程序的通用接口(包含swan与boxjs) 71 | * @return {Object} 处理后的api 72 | */ 73 | export const apiProccess = (originSwan, { 74 | navigator, 75 | swanComponents, 76 | pageLifeCycleEventEmitter, 77 | appLifeCycleEventEmitter, 78 | swanEventsCommunicator, 79 | hostShareParamsProccess, 80 | communicator, 81 | swaninterface 82 | }) => { 83 | const getSlaveId = () => navigator.history.getTopSlaves()[0].getSlaveId(); 84 | const operators = swanComponents.getContextOperators(swaninterface, communicator, getSlaveId); 85 | return Object.assign(originSwan, { 86 | navigateTo: navigator.navigateTo.bind(navigator), 87 | // navigateBack: navigator.navigateBack.bind(navigator), 88 | // redirectTo: navigator.redirectTo.bind(navigator), 89 | // switchTab: navigator.switchTab.bind(navigator), 90 | // reLaunch: navigator.reLaunch.bind(navigator), 91 | 92 | /** 93 | * 所有组件相关的操作API 94 | */ 95 | ...operators, 96 | 97 | /** 98 | * 开发者的自定义数据上报 99 | * @param {string} reportName 上报的自定义事件名称 100 | * @param {Object} reportParams 用户上报的自定义事件的参数 101 | * @return {*} 发送日志后的返回值 102 | */ 103 | reportAnalytics: (reportName, reportParams) => swanEventsCommunicator.fireMessage({ 104 | type: 'SwanEvents', 105 | params: { 106 | eventName: 'reportAnalytics', 107 | e: { 108 | reportName, 109 | reportParams 110 | } 111 | } 112 | }), 113 | /** 114 | * 宿主自定义的分享,取代直接的api的分享 115 | * @param {Object} userParams 调用openShare的小程序开发者传递的param 116 | */ 117 | openShare: (originShare => userParams => { 118 | // const appInfo = swaninterface.boxjs.data.get({name: 'swan-appInfoSync'}); 119 | // let proccessedParams = hostShareParamsProccess(userParams, appInfo); 120 | // return originShare.call(originSwan, proccessedParams); 121 | return ''; 122 | })(originSwan['openShare']), 123 | 124 | login: lockLogin(originSwan, pageLifeCycleEventEmitter, appLifeCycleEventEmitter), 125 | 126 | /** 127 | * 截图的调用回调 128 | * @param {Function} callback 截图后的回调 129 | */ 130 | onUserCaptureScreen: callback => { 131 | swaninterface.bind('onUserCaptureScreen', () => { 132 | typeof callback === 'function' && callback(); 133 | }); 134 | } 135 | }); 136 | }; 137 | -------------------------------------------------------------------------------- /src/slave/component-factory/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Communicator from '../../utils/communication'; 3 | import swanEvents from '../../utils/swan-events'; 4 | 5 | /** 6 | * san的组件工厂,生产组件类 7 | * @class 8 | */ 9 | export default class SanFactory { 10 | constructor(componentDefaultProps, behaviors) { 11 | this.behaviors = behaviors; 12 | this.componentDefaultProps = componentDefaultProps; 13 | this.componentInfos = {}; 14 | 15 | // 依赖池,所有的组件需要的依赖是工厂提供的 16 | const communicator = Communicator.getInstance(this.componentDefaultProps.swaninterface); 17 | this.dependenciesPool = { 18 | // san, 19 | communicator, 20 | ...this.componentDefaultProps 21 | }; 22 | } 23 | /** 24 | * 创建组件的工厂方法 25 | * 26 | * @param {Object} componentName - 创建组件的名称 27 | * @param {Object} options - 创建组件用的属性 28 | */ 29 | componentDefine(componentName, componentInfo = {}) { 30 | this.componentInfos[componentName] = { 31 | ...componentInfo 32 | }; 33 | } 34 | 35 | /** 36 | * 获取所有的注册过的组件 37 | * 38 | * @return {Object} 获取的当前注册过的所有组件 39 | */ 40 | getAllComponents() { 41 | return this.getComponents(); 42 | } 43 | /** 44 | * 获取所有注册过的组件 45 | * 46 | * @param {string|Array} componentName - 需要获取的component的名称 47 | * @return {Object} 获取的所有的注册的组件 48 | */ 49 | getComponents(componentName = this.getAllComponentsName()) { 50 | 51 | const componentNames = [].concat(componentName); 52 | 53 | const components = componentNames.reduce((componentSet, componentName) => { 54 | componentSet[componentName] = this.createComponents(componentName); 55 | return componentSet; 56 | }, {}); 57 | 58 | return typeof componentName === 'string' ? components[componentName] : components; 59 | } 60 | 61 | /** 62 | * 获取所有组件的名称 63 | * 64 | * @return {Array} 所有组件的名称集合 65 | */ 66 | getAllComponentsName() { 67 | return Object.keys(this.componentInfos); 68 | } 69 | 70 | /** 71 | * 关键类,百度小程序通过san创建san的组件,我改为使用vue来创建组件 72 | * 73 | * @param {string} componentName - 需要创建的组件的名称 74 | * @return {class} - 创建的组件 75 | */ 76 | createComponents(componentName) { 77 | 78 | const componentInfo = this.componentInfos[componentName]; 79 | // if (componentInfo && componentInfo.createdClass) { 80 | // return componentInfo.createdClass; 81 | // } 82 | 83 | // 获取超类名称 84 | const superComponentName = componentInfo.superComponent || ''; 85 | // 获取到当前组件的超类 86 | const superComponent = this.componentInfos[superComponentName] || {}; 87 | 88 | // 原始的组件的原型 89 | // const originComponentPrototype = componentInfo.componentPrototype; 90 | 91 | // 获取超类名称 92 | // const superComponentName = originComponentPrototype.superComponent || 'swan-component'; 93 | 94 | // 继承 95 | const mergedComponentOptions = this.mergeComponentOptions( 96 | superComponent.options, 97 | componentInfo.options 98 | ); 99 | 100 | const mergedDependencies = (superComponent.dependencies || []) 101 | .reduce((r, v, k) => { 102 | r.indexOf(v) < 0 && r.push(v); 103 | return r; 104 | }, (componentInfo.dependencies || [])); 105 | 106 | // 获取dependencies 107 | // const mergedDependencies = componentInfo.dependencies || []; 108 | // mergedDependencies.push(superComponent.dependencies); 109 | 110 | // 用merge好的proto来定义san组件(类)并返回 111 | const vueComponent = this.defineVueComponent(mergedDependencies, mergedComponentOptions); 112 | 113 | return vueComponent; 114 | 115 | // 返回修饰过的类 116 | // return this.behaviors(componentPrototype.behaviors || [], componentPrototype); 117 | } 118 | 119 | /** 120 | * 将两个组件的proto给merge为一个 121 | * 122 | * @param {Object} targetProto - 被merge的组件proto 123 | * @param {Object} mergeProto - 待merge入的组件proto 124 | * @return {Object} merge结果 125 | */ 126 | mergeComponentOptions = (targetOptions, mergeOptions) => { 127 | // merge传入的proto 128 | return Object.keys(mergeOptions) 129 | .reduce((mergedClassProto, propName) => { 130 | switch (propName) { 131 | case 'constructor': 132 | case 'detached': 133 | case 'created': 134 | mergedClassProto[propName] = function (options) { 135 | targetOptions[propName] && targetOptions[propName].call(this, options); 136 | mergeOptions[propName] && mergeOptions[propName].call(this, options); 137 | }; 138 | break; 139 | 140 | case 'computed': 141 | mergedClassProto['computed'] = Object.assign( 142 | {}, 143 | mergedClassProto[propName], 144 | mergeOptions[propName] 145 | ); 146 | break; 147 | 148 | default: 149 | mergedClassProto[propName] = mergeOptions[propName]; 150 | } 151 | return mergedClassProto; 152 | }, {...targetOptions}); 153 | } 154 | 155 | /** 156 | * 传入proto,定义san组件(官网标准的定义san组件方法) 157 | * 158 | * @param {Object} proto 组件类的方法表 159 | * @return {Function} san组件类 160 | */ 161 | defineVueComponent(dependencies, options) { 162 | 163 | // function Component(options) { 164 | // san.Component.call(this, options); 165 | // proto.constructor && proto.constructor.call(this, options); 166 | // } 167 | // 168 | // san.inherits(Component, san.Component); 169 | // 170 | // Object.keys(proto) 171 | // .forEach(propName => { 172 | // if (propName !== 'constructor') { 173 | // Component.prototype[propName] = proto[propName]; 174 | // } 175 | // }); 176 | 177 | console.log('defineVueComponent', options); 178 | 179 | const Component = Vue.extend(options); 180 | 181 | Component.getInitData = function(params){ 182 | options = { 183 | data: { 184 | 185 | } 186 | }; 187 | for (var k in params.value) { 188 | // this.data.set(); 189 | // this.$set(this.$data, k, params.value[k]); 190 | options.data[k] = params.value[k]; 191 | } 192 | return options; 193 | }; 194 | 195 | Component.slaveLoaded = function () { 196 | console.log(`Slave 发送 slaveLoaded 事件,slaveId=${window.slaveId}`); 197 | window.testutils.clientActions.dispatchEvent('slaveLoaded', { 198 | value: { 199 | status: 'loaded' 200 | }, 201 | slaveId: window.slaveId 202 | }); 203 | }; 204 | 205 | // const obj = { 206 | // component: Component, 207 | // getInitData: function(params){ 208 | // options = { 209 | // data: { 210 | // 211 | // } 212 | // }; 213 | // for (var k in params.value) { 214 | // // this.data.set(); 215 | // // this.$set(this.$data, k, params.value[k]); 216 | // options.data[k] = params.value[k]; 217 | // } 218 | // return options; 219 | // }, 220 | // slaveLoaded: function () { 221 | // console.log(`Slave 发送 slaveLoaded 事件,slaveId=${window.slaveId}`); 222 | // window.testutils.clientActions.dispatchEvent('slaveLoaded', { 223 | // value: { 224 | // status: 'loaded' 225 | // }, 226 | // slaveId: window.slaveId 227 | // }); 228 | // } 229 | // }; 230 | 231 | // 用组件依赖装饰组件原型,生成组件原型 232 | const componentPrototype = this.decorateComponentPrototype(dependencies); 233 | Object.keys(componentPrototype) 234 | .forEach(propName => { 235 | if (propName !== 'constructor') { 236 | Component[propName] = componentPrototype[propName]; 237 | Component.prototype[`$${propName}`] = componentPrototype[propName]; 238 | } 239 | }); 240 | 241 | return Component; 242 | } 243 | 244 | /** 245 | * 使用装饰器装饰组件原型 246 | * 247 | * @param {Object} componentPrototype 组件原型 248 | * @param {Object} componentInfo 组件原始传入的构造器 249 | * @return {Object} 装饰后的组件原型 250 | */ 251 | decorateComponentPrototype(dependencies) { 252 | 253 | // 所有的组件的依赖在依赖池寻找后的结果 254 | const newDependencies = dependencies.reduce((depends, depsName) => { 255 | depends[depsName] = this.dependenciesPool[depsName]; 256 | return depends; 257 | }, {}); 258 | 259 | // merge后的组件原型,可以用来注册san组件 260 | return newDependencies; 261 | } 262 | 263 | } 264 | 265 | /** 266 | * 获取component的生产工厂 267 | * 268 | * @param {Object} componentDefaultProps - 默认的组件的属性 269 | * @param {Object} componentProtos - 所有组件的原型 270 | * @param {Object} behaviors - 所有的组件装饰器 271 | * @return {Object} 初始化后的componentFactory 272 | */ 273 | export const getComponentFactory = (componentDefaultProps, component, behaviors) => { 274 | 275 | swanEvents('slavePreloadGetComponentFactory'); 276 | 277 | const sanFactory = new SanFactory(componentDefaultProps, behaviors); 278 | 279 | swanEvents('slavePreloadDefineComponentsStart'); 280 | 281 | Object.keys(component) 282 | .forEach(protoName => sanFactory.componentDefine(protoName, component[protoName])); 283 | 284 | swanEvents('slavePreloadDefineComponentsEnd'); 285 | 286 | return sanFactory; 287 | }; -------------------------------------------------------------------------------- /src/slave/index.js: -------------------------------------------------------------------------------- 1 | import {getComponentFactory} from './component-factory'; 2 | import swanEvents from '../utils/swan-events'; 3 | import {loader} from '../utils'; 4 | 5 | export default class Slave { 6 | constructor(global, swaninterface, swanComponents) { 7 | swanEvents('slavePreloadStart'); 8 | this.context = global; 9 | // this.context.require = require; 10 | // this.context.define = define; 11 | // this.context.san = san; 12 | // this.context.swan = swaninterface.swan; 13 | // this.context.swaninterface = swaninterface; // 远程调试用 14 | this.swaninterface = swaninterface; 15 | this.swanComponents = swanComponents; 16 | // this.openSourceDebugger(); 17 | // this.extension = new Extension(global, swaninterface); 18 | this.registerComponents(); 19 | this.listenPageReady(global); 20 | // this.extension.use(this); 21 | swanEvents('slavePreloadEnd'); 22 | } 23 | 24 | registerComponents() { 25 | const swaninterface = this.swaninterface; 26 | const {versionCompare, boxVersion} = this.swaninterface.boxjs.platform; 27 | const componentProtos = this.swanComponents.getComponents({ 28 | isIOS: false, 29 | versionCompare, 30 | boxVersion 31 | }); 32 | swanEvents('slavePreloadGetComponents'); 33 | const componentDefaultProps = {swaninterface}; 34 | const componentFactory = getComponentFactory(componentDefaultProps, 35 | {...componentProtos}, 36 | this.swanComponents.getBehaviorDecorators()); 37 | 38 | global.componentFactory = componentFactory; 39 | 40 | global.pageRender = (pageTemplate, templateComponents, customComponents, filters, modules) => { 41 | console.log('salve pageRender run...'); 42 | 43 | // 用于记录用户模板代码在执行pageRender之前的时间消耗,包括了pageContent以及自定义模板的代码还有filter在加载过程中的耗时 44 | // global.FeSlaveSwanJsParseEnd = Date.now(); 45 | let filtersObj = {}; 46 | // filters && filters.forEach(element => { 47 | // let func = element.func; 48 | // let module = element.module; 49 | // filtersObj[element.filterName] = (...args) => { 50 | // return modules[module][func](...args); 51 | // }; 52 | // }); 53 | 54 | global.isNewTemplate = true; 55 | swanEvents('slaveActivePageRender', pageTemplate); 56 | 57 | console.log('pageTemplate', pageTemplate); 58 | // 定义当前页面的组件 59 | componentFactory.componentDefine( 60 | 'page', 61 | { 62 | superComponent: 'super-page', 63 | options: { 64 | // template: `${pageTemplate}`, 65 | template: `
${pageTemplate}
`, 66 | // components: {...componentFactory.getComponents(), ...templateComponents, ...customComponents}, 67 | components: {...componentFactory.getComponents()} 68 | } 69 | }, 70 | ); 71 | 72 | const rootDiv = document.createElement('div'); 73 | rootDiv.setAttribute('id', 'root'); 74 | document.body.appendChild(rootDiv); 75 | 76 | swanEvents('slaveActiveDefineComponentPage'); 77 | // 获取page的组件类 78 | const Page = global.componentFactory.getComponents('page'); 79 | // 监听等待initData,进行渲染 80 | Page.communicator.onMessage('initData', params => { 81 | swanEvents('slaveActiveReceiveInitData', params); 82 | try { 83 | // 根据master传递的data,设定初始数据,并进行渲染 84 | const options = Page.getInitData(params); 85 | swanEvents('slaveActiveRenderStart'); 86 | 87 | const page = new Page(options); 88 | // 真正的页面渲染,发生在initData之后 89 | // 此处让页面真正挂载处于自定义组件成功引用其他自定义组件之后, 90 | // 引用其它自定义组件是在同一时序promise.resolve().then里执行, 故此处attach时, 自定义组件已引用完成 91 | setTimeout(() => { 92 | // page.attach(document.body); 93 | page.$mount('#root'); 94 | // 通知master加载首屏之后的逻辑 95 | page.$communicator.sendMessage( 96 | 'master', { 97 | type: 'slaveAttached', 98 | slaveId: page.slaveId 99 | } 100 | ); 101 | swanEvents('slaveActivePageAttached'); 102 | }, 0); 103 | 104 | } 105 | catch (e) { 106 | console.log(e); 107 | global.errorMsg['renderError'] = e; 108 | } 109 | }); 110 | // 调用页面对象的加载完成通知 111 | Page.slaveLoaded(); 112 | 113 | // 如果已经有端上传来的initData数据,直接渲染 114 | // if (global.advancedInitData) { 115 | // let initData = global.advancedInitData; 116 | // page.communicator.fireMessage({ 117 | // type: 'initData', 118 | // value: initData.data, 119 | // extraMessage: { 120 | // componentsData: initData.componentsData 121 | // } 122 | // }); 123 | // } 124 | 125 | swanEvents('slaveActiveJsParsed'); 126 | // if (global.PageComponent) { 127 | // Object.assign(global.PageComponent.components, customComponents); 128 | // } 129 | }; 130 | 131 | const compatiblePatch = () => { 132 | // global.PageComponent = global.componentFactory.getComponents('super-page'); 133 | // global.PageComponent.components = global.componentFactory.getComponents(); 134 | // global.PageComponent.stabilityLog = global.PageComponent.stabilityLog || new Function(); 135 | }; 136 | compatiblePatch(); 137 | 138 | /** 139 | * 修复浏览器兼容问题 140 | */ 141 | // const browserPatch = () => { 142 | // // 兼容部分安卓机划动问题 143 | // document.body.addEventListener('touchmove', () => {}); 144 | // }; 145 | // browserPatch(); 146 | } 147 | 148 | 149 | /** 150 | * 监听pageReady,触发整个入口的调起 151 | * @param {Object} [global] 全局对象 152 | */ 153 | listenPageReady(global) { 154 | swanEvents('slavePreloadListened'); 155 | // 控制是否开启预取initData的开关 156 | let advancedInitDataSwitch = false; 157 | this.swaninterface.bind('PageReady', event => { 158 | swanEvents('slaveActiveStart', { 159 | pageInitRenderStart: Date.now() + '' 160 | }); 161 | let initData = event.initData; 162 | if (initData) { 163 | try { 164 | initData = JSON.parse(initData); 165 | this.initData = initData; 166 | } 167 | catch (e) { 168 | initData = null; 169 | } 170 | } 171 | if (advancedInitDataSwitch) { 172 | global.advancedInitData = this.initData; 173 | } 174 | 175 | const appPath = event.appPath; 176 | const pagePath = event.pagePath.split('?')[0]; 177 | const onReachBottomDistance = event.onReachBottomDistance; 178 | 179 | // 给框架同学用的彩蛋 180 | const corePath = global.location.href 181 | .replace(/[^\/]*\/[^\/]*.html$/g, '') 182 | .replace(/^file:\/\//, ''); 183 | global.debugDev = `deployPath=${appPath}\ncorePath=${corePath}`; 184 | 185 | // 给框架同学使用的刷新彩蛋 186 | sessionStorage.setItem('debugInfo', `${appPath}|debug|${pagePath}`); 187 | 188 | // 供组件中拼接绝对路径使用的全局信息 189 | global.pageInfo = { 190 | appPath, 191 | pagePath, 192 | onReachBottomDistance 193 | }; 194 | // let loadHook = () => { 195 | // return loader.loadjs('../swan-devhook/slave.js').then(() => { 196 | // /* eslint-disable fecs-camelcase, no-undef */ 197 | // __san_devtool__.emit('san', san); 198 | // /* eslint-enable fecs-camelcase, no-undef */ 199 | // }); 200 | // }; 201 | 202 | let loadUserRes = () => { 203 | // 设置页面的基础路径为当前页面本应所在的路径 204 | // 行内样式等使用相对路径变成此值 205 | // setPageBasePath(`${appPath}/${pagePath}`); 206 | swanEvents('slaveActivePageLoadStart'); 207 | // 加载用户的资源 208 | Promise.all([ 209 | loader.loadcss(`${appPath}/app.css`, 'slaveActiveAppCssLoaded'), 210 | loader.loadcss(`${appPath}/${pagePath}.css`, 'slaveActivePageCssLoaded') 211 | ]) 212 | .catch(() => { 213 | console.warn('加载css资源出现问题,请检查css文件'); 214 | }) 215 | .then(() => { 216 | // todo: 兼容天幕,第一个等天幕同步后,干掉 217 | swanEvents('slaveActiveCssLoaded'); 218 | swanEvents('slaveActiveSwanJsStart'); 219 | loader.loadjs(`${appPath}/${pagePath}.swan.js`, 'slaveActiveSwanJsLoaded'); 220 | }); 221 | }; 222 | // (event.devhook === 'true' ? loadHook().then(loadUserRes).catch(loadUserRes) : loadUserRes()); 223 | loadUserRes(); 224 | }); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/utils/code-process.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 处理外部使用代码的工具函数封装 3 | * @author houyu(houyu01@baidu.com) 4 | */ 5 | 6 | /** 7 | * 运行代码时,需要捕获开发者问题并抛出 8 | * 9 | * @param {Function} fn 需要运行的function 10 | * @param {*} context 函数执行的上下文 11 | * @param {string} errorMessage 函数报错时现实的报错信息 12 | * @param {Array} args 需要传入执行函数的参数 13 | * @return {*} 被包裹函数的执行返回值 14 | */ 15 | export const executeWithTryCatch = (fn, context, errorMessage, args) => { 16 | if (!fn) { 17 | return null; 18 | } 19 | let execResult = undefined; 20 | try { 21 | execResult = fn.call(context, args); 22 | } 23 | catch (e) { 24 | console.error(errorMessage); 25 | throw Error(e); 26 | } 27 | return execResult; 28 | }; 29 | -------------------------------------------------------------------------------- /src/utils/communication/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file communication for two 'webviews', this is a symmetric action 3 | * @author houyu(houyu01@baidu.com) 4 | */ 5 | import EventsEmitter from '../events-emitter'; 6 | 7 | export default class Communicator extends EventsEmitter { 8 | constructor(swaninterface, options) { 9 | super(options); 10 | this.swaninterface = swaninterface; 11 | this.listen(options); 12 | } 13 | listen(options) { 14 | return this.swaninterface.invoke('onMessage', (...args) => { 15 | console.log('listen', this, args); 16 | this.fireMessage(...args); 17 | }, options); 18 | } 19 | sendMessage(slaveId, message = {}) { 20 | if (!message.type) { 21 | return Promise.reject({message: 'error'}); 22 | } 23 | // V8中slaveId为数字无法sendMessage 24 | /* globals swanGlobal */ 25 | // if (swanGlobal) { 26 | // slaveId += ''; 27 | // } 28 | return this.swaninterface.invoke('postMessage', slaveId, message); 29 | } 30 | // 私有的默认实例ID 31 | static communicatorId = 'Symbol_communicatorId'; 32 | // 获取Communicator的实例工厂 33 | static instanceMap = {}; 34 | // 工厂 35 | static getInstance(swaninterface, id = Communicator.communicatorId, options = {}) { 36 | if (!Communicator.instanceMap[id]) { 37 | Communicator.instanceMap[id] = new Communicator(swaninterface, options); 38 | } 39 | return Communicator.instanceMap[id]; 40 | } 41 | /** 42 | * 43 | * 根据 id 删除 Communicator 实例 44 | * 45 | * @param {string} id id 46 | */ 47 | static removeInstance(id) { 48 | Communicator.instanceMap[id] = null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/data.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 对于page对象的数据抽象 3 | * @author houyu(houyu01@baidu.com) 4 | */ 5 | 6 | export class Data { 7 | constructor(data) { 8 | this.raw = data; 9 | } 10 | 11 | /** 12 | * 使用path对数据进行get 13 | * @param {string} [exprPath] 读取数据的path 14 | * @return {Object} get获取的返回值 15 | */ 16 | get(exprPath) { 17 | if (!exprPath) { 18 | return this.raw; 19 | } 20 | return exprPath.replace(/\[(\d+?)\]/g, '.$1') 21 | .split('.') 22 | .reduce((obj, key, idx) => { 23 | return obj ? obj[key] : obj; 24 | }, this.raw); 25 | } 26 | 27 | /** 28 | * 使用path对数据进行set 29 | * @param {string} [exprPath] 属性路径 30 | * @param {*} [value] 变更属性值 31 | * @return {Object} 设定之后设定的整体数据 32 | */ 33 | set(exprPath, value) { 34 | if (!exprPath) { 35 | return this.raw; 36 | } 37 | const keys = exprPath.replace(/\[(\d+?)\]/g, '.$1').split('.'); 38 | keys.reduce((obj, key, idx) => { 39 | if (idx === keys.length - 1) { 40 | obj[key] = value; 41 | } 42 | else { 43 | if (typeof obj[key] === 'undefined') { 44 | obj[key] = {}; 45 | } 46 | return obj[key]; 47 | } 48 | }, this.raw); 49 | return this.raw; 50 | } 51 | 52 | /** 53 | * 使用path对数据项进行push操作 54 | * @param {string} [exprPath] 读取数据的path 55 | * @param {*} [value] 推入的数据项 56 | * @return {number|null} 设定后数组的长度 57 | */ 58 | push(exprPath, value) { 59 | let target = this.get(exprPath); 60 | if (target instanceof Array) { 61 | return target.push(value); 62 | } 63 | return null; 64 | } 65 | 66 | /** 67 | * 使用path对数据项进行pop操作 68 | * @param {string} [exprPath] 读取数据的path 69 | * @return {*} pop出数组的数据项 70 | */ 71 | pop(exprPath) { 72 | let target = this.get(exprPath); 73 | if (target instanceof Array) { 74 | return target.pop(); 75 | } 76 | return null; 77 | } 78 | 79 | /** 80 | * 使用path对数据项进行unshif操作 81 | * @param {string} [exprPath] 读取数据的path 82 | * @param {*} [value] 推入的数据项 83 | * @return {*} unshift出数组的数据项 84 | */ 85 | unshift(exprPath, value) { 86 | let target = this.get(exprPath); 87 | if (target instanceof Array) { 88 | return target.unshift(value); 89 | } 90 | return null; 91 | } 92 | 93 | /** 94 | * 使用path对数据项进行shift操作 95 | * @param {string} [exprPath] 操作数据的path 96 | * @return {*} 推出的数据项 97 | */ 98 | shift(exprPath) { 99 | let target = this.get(exprPath); 100 | if (target instanceof Array) { 101 | return target.shift(); 102 | } 103 | return null; 104 | } 105 | 106 | /** 107 | * 使用path对数据项进行removeAt操作 108 | * @param {string} [exprPath] 操作数据的path 109 | * @param {number} [index] 操作的数据位置 110 | * @return {*} 移除的数据项 111 | */ 112 | removeAt(exprPath, index) { 113 | let target = this.get(exprPath); 114 | if (target instanceof Array) { 115 | const newTarget = target.splice(index, 1); 116 | // this.set(exprPath, newTarget); 117 | return newTarget; 118 | } 119 | return null; 120 | } 121 | 122 | /** 123 | * 使用path对数据项进行splice操作 124 | * @param {string} [exprPath] 操作数据的path 125 | * @param {number} [args] splice对应的数据参数 126 | * @return {*} splice的操作返回值 127 | */ 128 | splice(exprPath, args) { 129 | let target = this.get(exprPath); 130 | if (target instanceof Array) { 131 | return [].splice.call(target, args); 132 | } 133 | return null; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/utils/events-emitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file base listen/fire lib,u can use it as a normal observer in your code 3 | * @author houyu(785798835@qq.com) 4 | */ 5 | 6 | /** 一组队列的抽象 */ 7 | class QueueSet { 8 | 9 | constructor() { 10 | this.queueSet = {}; 11 | } 12 | 13 | /** 14 | * 获取某一条具体队列 15 | * 16 | * @param {string} 需要获取的队列的名称 17 | * @return {Array} 获取到的某一个具体队列 18 | */ 19 | get(namespace) { 20 | if (!this.queueSet[namespace]) { 21 | this.queueSet[namespace] = []; 22 | } 23 | return this.queueSet[namespace]; 24 | } 25 | 26 | /** 27 | * 将某一元素推入具体的某一条队列中 28 | * 29 | * @param {string} namespace 要推入的队列名称 30 | * @param {*} 推入队列的元素 31 | */ 32 | pushTo(namespace, message) { 33 | this.get(namespace).push(message); 34 | } 35 | 36 | /** 37 | * 队列集合中是否含有某一个具体的队列 38 | * 39 | * @param {string} namespace - 队列的名称 40 | * @return {bool} 当前队列集合中是否有某一特定队列 41 | */ 42 | has(namespace) { 43 | return Object.prototype.toString.call(this.queueSet[namespace]) === '[object Array]'; 44 | } 45 | 46 | /** 47 | * 删除队列中的某一项元素 48 | * 49 | * @param {string} namespace - 队列名称 50 | * @param {*} element - 要删除的元素的引用 51 | */ 52 | del(namespace, element) { 53 | if (!element) { 54 | this.queueSet[namespace] = []; 55 | } 56 | else if (namespace === '*') { 57 | Object.keys(this.queueSet) 58 | .filter(namespace => this.has(namespace)) 59 | .forEach(queueName => { 60 | this.queueSet[queueName] = this.queueSet[queueName].filter(item => item.handler !== element); 61 | }); 62 | } 63 | else { 64 | this.queueSet[namespace] = this.queueSet[namespace].filter(item => item.handler !== element); 65 | } 66 | } 67 | } 68 | 69 | export default class EventsEmitter { 70 | 71 | constructor() { 72 | this.handlerQueueSet = new QueueSet(); 73 | this.messageQueueSet = new QueueSet(); 74 | } 75 | 76 | /** 77 | * 融合多条事件流成为一条 78 | * 79 | * @param {...Object} communicators - 需要融合的任意一组事件流 80 | * @return {Object} - 融合后的事件流 81 | */ 82 | static merge(...communicators) { 83 | const mergedEventsEmitter = new EventsEmitter(); 84 | [...communicators] 85 | .forEach(communicator => { 86 | communicator.onMessage('*', e => mergedEventsEmitter.fireMessage(e)); 87 | }); 88 | return mergedEventsEmitter; 89 | } 90 | 91 | /** 92 | * 派发事件 93 | * 94 | * @param {Object} message - 派发事件的对象 95 | * @return {Object} - this环境上下文 96 | */ 97 | fireMessage(message) { 98 | if (message && message.type && this.handlerQueueSet.get(message.type)) { 99 | 100 | this.messageQueueSet.pushTo(message.type, message); 101 | 102 | this.handlerQueueSet.get(message.type) 103 | .forEach(item => { 104 | this.handlerWrapper(item, message.type, message); 105 | }); 106 | 107 | this.handlerQueueSet.get('*') 108 | .forEach(item => { 109 | this.handlerWrapper(item, '*', message); 110 | }); 111 | } 112 | return this; 113 | } 114 | 115 | /** 116 | * 监听事件 117 | * 118 | * @param {string} type - 监听的事件名称 119 | * @param {Function} handler - 监听的事件回调 120 | * @param {Object} options - 监听的设置选项 121 | * @return {Object} this环境上下文 122 | */ 123 | onMessage(type, handler, options = {}) { 124 | 125 | if (Object.prototype.toString.call(type) === '[object Array]') { 126 | type.forEach(oneType => this.onMessage(oneType, handler, options)); 127 | return this; 128 | } 129 | 130 | this.handlerQueueSet.pushTo(type, { 131 | handler, 132 | once: options.once 133 | }); 134 | 135 | if (options.listenPreviousEvent === true && this.messageQueueSet.has(type)) { 136 | this.handlerWrapper( 137 | {handler, once: options.once}, 138 | type, 139 | this.messageQueueSet.get(type) 140 | ); 141 | } 142 | return this; 143 | } 144 | 145 | /** 146 | * 删除事件监听 147 | * 148 | * @param {string} type - 监听的事件名称 149 | * @param {Function} handler - 监听的事件回调 150 | * @return {Object} this环境上下文 151 | */ 152 | delHandler(type, handler) { 153 | this.handlerQueueSet.del(type, handler); 154 | return this; 155 | } 156 | 157 | /** 158 | * 执行handler的代理函数 159 | * 160 | * @param {Object} item - 事件流中存储的某个对象 161 | * @param {Function} item.handler - 事件流中某个对象中的事件接受处理者 162 | * @param {string} type - 事件的名称 163 | * @param {*} message - 需要传递给执行事件的参数 164 | * @return {bool} - 执行是否成功的结果 165 | */ 166 | handlerWrapper({handler, once}, type, message) { 167 | if (!handler) { 168 | return false; 169 | } 170 | handler.call(this, message); 171 | // 如果设定,用完即删 172 | if (once) { 173 | this.handlerQueueSet.del(type, handler); 174 | } 175 | return true; 176 | } 177 | } 178 | 179 | export {QueueSet}; 180 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import {Data} from './data'; 2 | import Loader from './loader'; 3 | // 旧调起协议特征 `://v19` 4 | const OLD_LAUNCH_SCHEME_REGEX = /:\/\/v[0-9]+/; 5 | const OLD_SCHEME_PARAMS_REGEX = /params=({.*})/; // params= 6 | 7 | // 新调起协议特征 `//swan/xxx/xxx?xxx` 8 | const NEW_SCHEME_PARAM_REGEX = /\/\/swan\/[0-9a-z_A-Z]+\/?(.*?)\?(.*)$/; 9 | const NEW_EXTRA_PARAM_REGEX = /(_baiduboxapp|callback|upgrade).*?(&|$)/g; 10 | 11 | export const loader = new Loader(); 12 | export {Data}; 13 | export {executeWithTryCatch} from './code-process'; 14 | export * from './path'; 15 | 16 | export const parseUrl = url => { 17 | let matchs = url.match(/(.*?)\?(.*)/); 18 | let result = { 19 | pathname: matchs ? matchs[1] : url, 20 | query: {} 21 | }; 22 | if (matchs) { 23 | let re = /([^&=]+)=([^&]*)/g; 24 | let m; 25 | while ((m = re.exec(matchs[2])) !== null) { 26 | result.query[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); 27 | } 28 | } 29 | return result; 30 | }; 31 | 32 | export const getParams = query => { 33 | if (!query) { 34 | return {}; 35 | } 36 | 37 | return (/^[?#]/.test(query) ? query.slice(1) : query) 38 | .split('&') 39 | .reduce((params, param) => { 40 | let [key, value] = param.split('='); 41 | try { 42 | params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : ''; 43 | } 44 | catch (e) { 45 | params[key] = value; 46 | } 47 | return params; 48 | }, {}); 49 | }; 50 | 51 | /** 52 | * 获取首页地址 53 | * @return {string} 首页地址 54 | */ 55 | function getIndexPath() { 56 | try { 57 | const appConfig = JSON.parse(global.appConfig); 58 | const index = appConfig.pages[0]; 59 | const tabBarIndex = appConfig.tabBar 60 | && appConfig.tabBar.list 61 | && appConfig.tabBar.list[0] 62 | && appConfig.tabBar.list[0].pagePath; 63 | 64 | return tabBarIndex || index; 65 | } 66 | catch (e) { 67 | console.error(e); 68 | return ''; 69 | } 70 | } 71 | 72 | /** 73 | * 从旧调起协议中获取params参数 74 | * 75 | * @param originScheme 调起协议 76 | * @return {Object} 从旧协议中提取出的参数对象 77 | * @return {{path: string, query: {}, extraData: *, navi: string} | {}} 78 | * path: 调起协议的path,;query: 调起协议的query; navi: 有值代表小程序之间的调起; extraData: 小程序之间的调起时传递的数据 79 | */ 80 | function getParamsFromOldScheme(originScheme) { 81 | let path; 82 | let query = {}; 83 | let navi = ''; 84 | let extraData = {}; 85 | 86 | // 获取协议字符串中的params字符串 87 | let paramsRegexResult = originScheme.match(OLD_SCHEME_PARAMS_REGEX); 88 | if (!paramsRegexResult) { 89 | return {}; 90 | } 91 | 92 | let paramsStr = paramsRegexResult[1]; 93 | 94 | // 解析params字符串,提取 path,query,navi,extraData 字段 95 | try { 96 | let paramsObj = JSON.parse(paramsStr); 97 | let fullPath = paramsObj.path || ''; 98 | extraData = paramsObj.extraData || {}; 99 | navi = paramsObj.navi || ''; 100 | 101 | // eg: home/index/index?id=2 102 | if (fullPath) { 103 | let pathRegexResult = fullPath.match(/(.*)\?(.*)/); 104 | path = pathRegexResult ? pathRegexResult[1] : fullPath; 105 | query = pathRegexResult ? getParams(pathRegexResult[2]) : {}; 106 | } 107 | else { 108 | // 默认首页地址作为path 109 | path = getIndexPath(); 110 | } 111 | } 112 | catch (e) { 113 | console.error(e); 114 | } 115 | 116 | return { 117 | path, 118 | query, 119 | navi, 120 | extraData 121 | }; 122 | } 123 | 124 | /** 125 | * 从新调起协议中获取 path 和 query 126 | * 127 | * @param originScheme 调起协议 128 | * @return {Object} path和query组成的对象 129 | */ 130 | function getParamsFromNewScheme(originScheme) { 131 | const scheme = originScheme.replace(NEW_EXTRA_PARAM_REGEX, ''); 132 | const paramsRegexResult = scheme.match(NEW_SCHEME_PARAM_REGEX); 133 | 134 | const path = paramsRegexResult ? paramsRegexResult[1] : ''; 135 | const query = paramsRegexResult ? getParams(paramsRegexResult[2]) : {}; 136 | 137 | return { 138 | path: path ? path : getIndexPath(), 139 | query 140 | }; 141 | } 142 | 143 | 144 | /** 145 | * 处理onAppLaunch、onAppShow、onAppHide的参数:从新旧调起协议中提取 path 和 query 和 extraData 146 | * 147 | * 旧调起协议格式 eg: 148 | * baiduboxapp://v19/swan/launch?params={"appKey":"xxx","path":"pages/home/home?id=3","extraData":{"foo":"baidu"},"appid":"xxx","navi":"naviTo","url":"pages/home/home?id=3"}&callback=_bdbox_js_328&upgrade=0 149 | * 150 | * 新调起协议格式 eg: 151 | * "baiduboxapp://swan//pages/home/home/?id=3&_baiduboxapp={"from":"","ext":{}}&callback=_bdbox_js_275&upgrade=0" 152 | * 153 | * @param {Object} appInfo 待处理的appInfo 154 | */ 155 | export const processParam = appInfo => { 156 | let originScheme = (appInfo && appInfo.appLaunchScheme) || ''; 157 | if (!originScheme) { 158 | return; 159 | } 160 | originScheme = decodeURIComponent(originScheme); 161 | 162 | // 从协议中获取 path,query,extraData,navi 163 | let params = {}; 164 | if (OLD_LAUNCH_SCHEME_REGEX.test(originScheme)) { 165 | params = getParamsFromOldScheme(originScheme); 166 | } 167 | else { 168 | params = getParamsFromNewScheme(originScheme); 169 | } 170 | appInfo = Object.assign(appInfo, params); 171 | 172 | // 新旧场景值的兼容,当是16为场景值的时候,取前8位 173 | let scene = appInfo.scene ? '' + appInfo.scene : ''; 174 | appInfo.scene = scene.length === 16 ? scene.slice(0, 8) : scene; 175 | 176 | // 如果是从小程序跳转来的,则增加引用信息referrerInfo 177 | appInfo.srcAppId && (appInfo.referrerInfo = { 178 | appId: appInfo.srcAppId, 179 | extraData: appInfo.extraData 180 | }); 181 | 182 | // 新增appURL字段用于app onShow透传给开发者 183 | appInfo.appURL = originScheme.replace(NEW_EXTRA_PARAM_REGEX, ''); 184 | }; 185 | 186 | let appInfoCache = null; 187 | /** 188 | * 获取App信息(包含:appId,scene,scheme) 189 | * 190 | * @param {Object} swaninterface - 端能力接口 191 | * @param {bool} [noCache=false] - 是否使用缓存的appInfo 192 | * @return {Object} - 获取得到的App信息 193 | */ 194 | export const getAppInfo = (swaninterface, noCache = false) => { 195 | if (noCache || !appInfoCache) { 196 | appInfoCache = swaninterface.boxjs.data.get({name: 'swan-appInfoSync'}); 197 | } 198 | return appInfoCache; 199 | }; 200 | 201 | /** 202 | * 深度拷贝逻辑,不同于lodash等库,但是与微信一致 203 | * @param {*} [originObj] 原对象 204 | * @return {Object|Array} 拷贝结果 205 | */ 206 | export const deepClone = originObj => { 207 | return deepAssign(Object.prototype.toString.call(originObj) === '[object Array]' ? [] : {}, originObj); 208 | }; 209 | 210 | /** 211 | * 深度assign的函数 212 | * @param {Object} targetObject 要被拷贝的目标对象 213 | * @param {Object} originObject 拷贝的源对象 214 | * @return {Object} merge后的对象 215 | */ 216 | export const deepAssign = (targetObject = {}, originObject) => { 217 | const originType = Object.prototype.toString.call(originObject); 218 | if (originType === '[object Array]') { 219 | targetObject = originObject.slice(0); 220 | return targetObject; 221 | } 222 | else if (originType === '[object Object]' && originObject.constructor === Object) { 223 | for (const key in originObject) { 224 | targetObject[key] = deepAssign(targetObject[key], originObject[key]); 225 | } 226 | return targetObject; 227 | } 228 | else if (originType === '[object Date]') { 229 | return new Date(originObj.getTime()); 230 | } 231 | else if (originType === '[object RegExp]') { 232 | const target = String(originObj); 233 | const lastIndex = target.lastIndexOf('/'); 234 | return new RegExp(target.slice(1, lastIndex), target.slice(lastIndex + 1)); 235 | } 236 | return originObject; 237 | }; -------------------------------------------------------------------------------- /src/utils/loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file load js or some other sources 3 | * @author houyu(houyu01@baidu.com) 4 | */ 5 | import swanEvents from './swan-events'; 6 | /* globals swanGlobal _naSwan */ 7 | // const doc = swanGlobal ? {} : document; 8 | const doc = window.document; 9 | export default class Loader { 10 | constructor(basePath = '') { 11 | this.basePath = basePath; 12 | this.loadedResource = { 13 | js: {}, 14 | css: {} 15 | }; 16 | } 17 | loadjs(src, action) { 18 | const loadPath = this.basePath + src; 19 | if (this.loadedResource.js[loadPath]) { 20 | return Promise.resolve(); 21 | } 22 | return new Promise((resolve, reject) => { 23 | // if (swanGlobal) { 24 | // try { 25 | // _naSwan.include(loadPath); 26 | // action && swanEvents(action); 27 | // } catch (e) { 28 | // reject(e); 29 | // console.error(e); 30 | // } 31 | // resolve(); 32 | // } else { 33 | const script = doc.createElement('script'); 34 | script.type = 'text/javascript'; 35 | script.src = loadPath; 36 | script.onload = () => { 37 | this.loadedResource.js[loadPath] = true; 38 | action && swanEvents(action); 39 | resolve(); 40 | }; 41 | script.onerror = reject; 42 | doc.head.appendChild(script); 43 | // } 44 | }); 45 | } 46 | loadcss(src, action) { 47 | const loadPath = this.basePath + src; 48 | if (this.loadedResource.js[loadPath]) { 49 | return Promise.resolve(); 50 | } 51 | return new Promise((resolve, reject) => { 52 | const link = document.createElement('link'); 53 | link.type = 'text/css'; 54 | link.rel = 'stylesheet'; 55 | link.href = loadPath; 56 | link.onload = () => { 57 | this.loadedResource.css[loadPath] = true; 58 | action && swanEvents(action); 59 | resolve(); 60 | }; 61 | link.onerror = reject; 62 | doc.head.appendChild(link); 63 | }); 64 | } 65 | // TODO other files type 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/module.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file module loader(define & require) 3 | * @author houyu(houyu01@baidu.com) 4 | */ 5 | import {pathResolver} from './path'; 6 | const MODULE_PRE_DEFINED = 1; 7 | const MODULE_DEFINED = 2; 8 | const global = window; 9 | let modModules = {}; 10 | 11 | const parseId = baseId => { 12 | const idStructure = baseId.match(/(.*)\/([^/]+)?$/); 13 | return (idStructure && idStructure[1]) || './'; 14 | }; 15 | 16 | const createLocalRequire = (baseId, require) => id => { 17 | const normalizedId = parseId(baseId); 18 | const paths = pathResolver(normalizedId, id, () => { 19 | throw new Error(`can't find module : ${id}`); 20 | }); 21 | const absId = paths.join('/').replace(/\.js$/g, ''); 22 | return require(absId); 23 | }; 24 | 25 | // 重写开发者使用的Function, 过滤new Function同时保留原型 26 | const safetyFn = () => { 27 | const fn = (...args) => { 28 | const len = args.length; 29 | if (len > 0 && 'return this' === args[len - 1]) { 30 | return function () { 31 | return {}; 32 | }; 33 | } 34 | }; 35 | fn.prototype = Function.prototype; 36 | Function.prototype.constructor = fn; 37 | return fn; 38 | }; 39 | 40 | export const require = id => { 41 | if (typeof id !== 'string') { 42 | throw new Error('require args must be a string'); 43 | } 44 | let mod = modModules[id]; 45 | if (!mod) { 46 | throw new Error('module "' + id + '" is not defined'); 47 | } 48 | 49 | if (mod.status === MODULE_PRE_DEFINED) { 50 | const factory = mod.factory; 51 | const house = { 52 | swan: global.swan || global.swaninterface.swan, 53 | swaninterface: global.swaninterface || global.masterManager.swaninterface, 54 | getApp: global.getApp 55 | } 56 | house.boxjs = house.swaninterface.boxjs; 57 | 58 | !mod.dependents.length && (mod.dependents = ['swan', 'getApp']) 59 | mod.dependents = mod.dependents.map(item => house[item]) 60 | 61 | let localModule = { 62 | exports: {} 63 | }; 64 | let factoryReturn = factory( 65 | createLocalRequire(id, require), 66 | localModule, 67 | localModule.exports, 68 | define, 69 | ...mod.dependents 70 | ); 71 | 72 | mod.exports = localModule.exports || factoryReturn; 73 | mod.status = MODULE_DEFINED; 74 | } 75 | return mod.exports; 76 | }; 77 | 78 | // define 定义 79 | export const define = (id, dependents, factory) => { 80 | if (typeof id !== 'string') { 81 | throw new Error('define args 0 must be a string'); 82 | } 83 | let _deps = dependents instanceof Array ? dependents : []; 84 | let _factory = typeof dependents === 'function' ? dependents : factory; 85 | 86 | //本地缓存中已经存在 87 | if (modModules[id]) { 88 | return; 89 | } 90 | 91 | modModules[id] = { 92 | status: MODULE_PRE_DEFINED, 93 | dependents: _deps, 94 | factory: _factory 95 | }; 96 | }; 97 | -------------------------------------------------------------------------------- /src/utils/path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file path路径相关的工具集 3 | * @author houyu(houyu01@baidu.com) 4 | */ 5 | 6 | export const pathResolver = (originPath, path, errorCb) => { 7 | const segs = (originPath + '/' + path).split('/'); 8 | return segs.reduce((resStack, seg) => { 9 | if (seg !== '' && seg !== '.') { 10 | if (seg === '..') { 11 | if (resStack.length === 0) { 12 | errorCb && errorCb(); 13 | } 14 | resStack.pop(); 15 | } 16 | else { 17 | resStack.push(seg); 18 | } 19 | } 20 | return resStack; 21 | }, []); 22 | }; 23 | 24 | /** 25 | * 在app工程里面的路径,需要替换path为绝对路径 26 | * 27 | * @param {string} basePath - 计算绝对路径时使用的基础路径 28 | * @param {string} pagePath - 计算绝对路径时使用的页面级路径 29 | * @param {string} path - 相对小程序的路径,会被变成绝对路径 30 | * @return {string} 计算出的文件的绝对路径 31 | */ 32 | export const absolutePathResolver = (basePath, pagePath, path) => { 33 | // 远程地址无需转换 34 | if (/^https?:\/\//.test(path)) { 35 | return path; 36 | } 37 | // 绝对路径的话,不用page路径 38 | const pageRoute = /^\//.test(path) ? '' : pagePath.replace(/[^\/]*$/g, ''); 39 | return '/' + pathResolver(`${basePath}/${pageRoute}`, path).join('/'); 40 | }; 41 | -------------------------------------------------------------------------------- /src/utils/splitapp-accessory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file splitApp功能全局状态对象 3 | * @author haoran@baidu.com 4 | */ 5 | 6 | export default { 7 | allJsLoaded: true, 8 | tabBarList: [], 9 | routeResolve: () => {} 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/swan-events/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file swan-events/index.js 3 | */ 4 | import EventsEmitter from '../events-emitter'; 5 | const global = window; 6 | global.swanEvents = global.swanEvents || new EventsEmitter(); 7 | 8 | export default function (eventName, data){ 9 | global.swanEvents.fireMessage({ 10 | type: 'TraceEvents', 11 | params: { 12 | eventName, 13 | data 14 | } 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "webpack-dev-server --open", 7 | "build": "webpack --config webpack.config.js" 8 | }, 9 | "devDependencies": { 10 | "babel-core": "^6.26.3", 11 | "babel-loader": "^7.1.5", 12 | "babel-preset-es2015": "^6.24.1", 13 | "clean-webpack-plugin": "^1.0.0", 14 | "copy-webpack-plugin": "^4.6.0", 15 | "css-loader": "^2.0.2", 16 | "file-loader": "^3.0.1", 17 | "html-webpack-plugin": "^3.2.0", 18 | "raw-loader": "^1.0.0", 19 | "style-loader": "^0.23.1", 20 | "vue-loader": "^15.4.2", 21 | "vue-router": "^3.0.2", 22 | "vue-template-compiler": "^2.5.21", 23 | "webpack": "^4.28.2", 24 | "webpack-cli": "^3.1.2", 25 | "webpack-dev-server": "^3.1.13" 26 | }, 27 | "dependencies": { 28 | "lodash": "^4.17.11" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/src/main.js: -------------------------------------------------------------------------------- 1 | import EventsEmitter from "./native/utils/events-emitter"; 2 | // 测试代码,输出Master,Slave里的日志 3 | window.swanEvents = new EventsEmitter(); 4 | window.swanEvents.onMessage('TraceEvents', function (message) { 5 | console.log(message.params.eventName, message.params.data); 6 | }); 7 | 8 | import Master from '../../dist/box/master/index.js'; 9 | import Native from './native/index.js'; 10 | 11 | const native = new Native(window); 12 | window.master = new Master(window, window.swanInterface, window.swanComponents); 13 | 14 | // Native应该提供的能力 15 | // TODO-ly 下载此小程序的App相关的配置文件,初始化App 16 | native.openWeChatApp('/wxs/helloworld'); 17 | -------------------------------------------------------------------------------- /test/src/native/component/page.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @file Page 类,在 Slave 中运行 4 | * @author houyu(houyu01@baidu.com) 5 | */ 6 | // import styles from './public/page.css'; 7 | // import {getSelectData} from './utils/dom/swanXml/parseDataUtil'; 8 | // import {eventProccesser, getValueSafety, EnviromentEvent} from './utils'; 9 | // import {accumulateDiff} from './utils/data-diff'; 10 | import swanEvents from '../utils/swan-events'; 11 | // import {addIntersectionObserver, removeIntersectionObserver} from './utils/dom/swanXml/intersection-listener'; 12 | // import {computeObserverIntersection} from './utils/dom/swanXml/intersection-calculator'; 13 | 14 | 15 | // { 16 | // dependencies: ['communicator'], 17 | // options:{ 18 | // methods: { 19 | // slaveJsLog: noop, 20 | // } 21 | // } 22 | // }, 23 | 24 | export default { 25 | 26 | dependencies: ['swaninterface', 'communicator'], 27 | 28 | options: { 29 | created: function(){ 30 | this.initMessagebinding(); 31 | }, 32 | methods: { 33 | 34 | /** 35 | * 执行用户绑定的事件 36 | * 37 | * @param {string} eventName 事件名称 38 | * @param {Object} $event 事件对象 39 | * @param {Function} reflectMethod 用户回调方法 40 | * @param {boolean} capture 是否事件捕获 41 | * @param {boolean} catchType 是否终止事件执行 42 | * @param {Object} customEventParams 用户绑定的事件集合 43 | */ 44 | eventHappen: function(eventName, $event, reflectMethod, capture, catchType, customEventParams) { 45 | swanEvents('slaveEventHappen', { 46 | eventName: eventName, 47 | }); 48 | 49 | if ($event && catchType === 'catch') { 50 | $event.stopPropagation && $event.stopPropagation(); 51 | (eventName === 'touchstart' || eventName === 'touchmove') 52 | && $event.preventDefault && $event.preventDefault(); 53 | } 54 | this.$communicator.sendMessage( 55 | 'master', 56 | { 57 | type: 'event', 58 | value: { 59 | eventType: eventName, 60 | reflectMethod, 61 | e: $event, //eventProccesser(eventName, $event) 62 | }, 63 | slaveId: this.slaveId, 64 | customEventParams 65 | } 66 | ); 67 | }, 68 | /** 69 | * 初始化事件绑定 70 | * @private 71 | */ 72 | initMessagebinding: function() { 73 | this.$communicator.onMessage( 74 | ['setData', 'pushData', 'popData', 'unshiftData', 'shiftData', 'removeAtData', 'spliceData'], 75 | params => { 76 | swanEvents('slaveDataEvent', params); 77 | const setObject = params.setObject || {}; 78 | const operationType = params.type.replace('Data', ''); 79 | if (operationType === 'set') { 80 | // TODO-ly 此处可以优化,使用Vue效率最高的方案 81 | for(var key in setObject){ 82 | this[key] = setObject[key]; 83 | } 84 | // let setDataDiff = accumulateDiff(this.data.get(), setObject); 85 | // setDataDiff && setDataDiff.reverse().forEach(ele => { 86 | // const {kind, rhs, path, item, index} = ele; 87 | // let dataPath = path.reduce((prev, cur) => `${prev}['${cur}']`); 88 | // // 将用户setData操作数组的时候,分解成san推荐的数组操作,上面reverse也是为了对数组进行增删改的时候顺序不乱 89 | // if (kind === 'A') { 90 | // if (item.kind === 'N') { 91 | // this.data.push(dataPath, item.rhs); 92 | // } 93 | // else if (item.kind === 'D') { 94 | // this.data.splice(dataPath, [index]); 95 | // } 96 | // } 97 | // else { 98 | // this.data.set(dataPath, rhs); 99 | // } 100 | // }); 101 | } 102 | // else { 103 | // for (let path in setObject) { 104 | // this.data[operationType](path, setObject[path]); 105 | // } 106 | // } 107 | // this.nextTick(() => { 108 | // this.sendAbilityMessage('nextTickReach'); 109 | // swanEvents('pageDataUpdate', { 110 | // slaveId: this.slaveId, 111 | // timestamp: params.pageUpdateStart 112 | // }); 113 | // }); 114 | } 115 | ); 116 | 117 | // this.communicator.onMessage('querySlaveSelector', params => { 118 | // const {selector, queryType, index, operation, fields, execId, contextId} = params.value; 119 | // const data = getSelectData({selector, queryType, operation, fields, contextId}); 120 | // 121 | // this.communicator.sendMessage( 122 | // 'master', 123 | // { 124 | // type: 'getSlaveSelector', 125 | // value: JSON.stringify({ 126 | // data, 127 | // index, 128 | // execId 129 | // }), 130 | // slaveId: this.slaveId 131 | // } 132 | // ); 133 | // }); 134 | 135 | // this.onRequestComponentObserver(); 136 | 137 | // 客户端向slave派发事件 138 | // this.communicator.onMessage('abilityMessage', e => { 139 | // this.communicator.fireMessage({ 140 | // type: `${e.value.type}_${e.value.params.id}`, 141 | // params: e.value.params 142 | // }); 143 | // }); 144 | 145 | // 客户端向slave派发双击标题栏事件 146 | // this.communicator.onMessage('scrollViewBackToTop', e => { 147 | // this.communicator.fireMessage({ 148 | // type: 'scrollView-backTotop' 149 | // }); 150 | // }); 151 | 152 | // this.swaninterface.bind('PullDownRefresh', e => { 153 | // // 参数 e 中包含 element 信息,导致部分 Android 机型使用系统内核时消息传递失败,因此只传 e.type 154 | // this.sendAbilityMessage('pullDownRefresh', { 155 | // type: e.type 156 | // }); 157 | // }); 158 | }, 159 | } 160 | }, 161 | 162 | constructor(options = {}) { 163 | // this.boxjs = this.swaninterface.boxjs; 164 | // this.swan = this.swaninterface.swan; 165 | // const slaveIdObj = this.boxjs.data.get({ 166 | // name: 'swan-slaveIdSync' 167 | // }); 168 | // if (!slaveIdObj) { 169 | // throw new Error('Can not get slave id from baiduapp.'); 170 | // } 171 | // this.slaveId = slaveIdObj.slaveId; 172 | // this.masterNoticeComponents = []; 173 | // this.browserPatch(); 174 | // this.initMessagebinding(); 175 | }, 176 | /* 177 | * 默认的初始化数据 178 | */ 179 | initData() { 180 | return {}; 181 | }, 182 | 183 | // slavePageRendered() { 184 | // if (this.masterNoticeComponents.length > 0) { 185 | // this.sendAbilityMessage('onPageRender', { 186 | // customComponents: this.masterNoticeComponents 187 | // }); 188 | // this.masterNoticeComponents = []; 189 | // } 190 | // this.communicator.fireMessage({ 191 | // type: 'slaveRendered' 192 | // }); 193 | // }, 194 | 195 | // slavePageUpdated() { 196 | // this.communicator.fireMessage({ 197 | // type: 'slaveUpdated' 198 | // }); 199 | // }, 200 | 201 | // updated() { 202 | // this.slavePageUpdated(); 203 | // this.slavePageRendered(); 204 | // }, 205 | 206 | // andrSendFP(fp, errorType = 'fe_first_paint_error') { 207 | // if (fp > 0) { 208 | // swanEvents('slaveFeFirstPaint', { 209 | // eventId: 'fe_first_paint', 210 | // errorType: errorType, 211 | // timeStamp: fp 212 | // }); 213 | // } else { 214 | // swanEvents('slaveFeFirstPaint', { 215 | // eventId: 'nreach', 216 | // errorType: errorType, 217 | // timeStamp: Date.now() 218 | // }); 219 | // } 220 | // }, 221 | // getFPTiming(timeGap) { 222 | // let paintMetrics = performance.getEntriesByType('paint'); 223 | // if (paintMetrics !== undefined && paintMetrics.length > 0) { 224 | // let fcp = paintMetrics.filter(entry => entry.name === 'first-contentful-paint'); 225 | // if (fcp.length >= 1) { 226 | // let fpTimeStamp = parseInt(timeGap + fcp[0].startTime, 10); 227 | // this.andrSendFP(fpTimeStamp, 'paint_entry_get'); 228 | // } else { 229 | // this.andrSendFP(-1, 'get_performance_paint_entry_empty'); 230 | // } 231 | // } else { 232 | // this.andrSendFP(-1, 'get_performance_paint_entry_error'); 233 | // } 234 | // }, 235 | // attached() { 236 | // 237 | // swanEvents('slaveActiveRenderEnd', { 238 | // slaveId: this.slaveId 239 | // }); 240 | // 241 | // if (this.swaninterface.boxjs.platform.isAndroid()) { 242 | // 243 | // if ('performance' in global) { 244 | // // 如果能获取到timeOrigin,则使用timeOrigin,否则使用Date.now 和performance.now 之间的差值 245 | // let timeGap = global.performance.timeOrigin || Date.now() - global.performance.now(); 246 | // 247 | // if ('PerformanceObserver' in global) { 248 | // // 如果有PerformanceObserver对象,则使用PerformanceOvbserver来监听 249 | // let observerPromise = new Promise((resolve, reject) => { 250 | // let observer = new global.PerformanceObserver(list => { 251 | // resolve(list); 252 | // }); 253 | // observer.observe({ 254 | // entryTypes: ['paint'] 255 | // }); 256 | // }).then(list => { 257 | // // 获取和首屏渲染相关的所有点,first-contentful-paint 258 | // let fcp = list.getEntries().filter(entry => entry.name === 'first-contentful-paint'); 259 | // if (fcp.length >= 1) { 260 | // // 如果有first-paint点,取first-contentful-paint 261 | // let fpTimeStamp = parseInt(timeGap + fcp[0].startTime, 10); 262 | // this.andrSendFP(fpTimeStamp, 'observer_get_fp'); 263 | // } else { 264 | // // 如果从Observer取不到任何有意义的first render点,从performance.getEntries('paint')获取前端渲染点 265 | // this.getFPTiming(timeGap); 266 | // } 267 | // }).catch(error => { 268 | // // 如果从resolve发生错误,从performance.getEntries('paint')获取前端渲染点 269 | // this.getFPTiming(timeGap); 270 | // }); 271 | // } else { 272 | // // 如果没有PerformanceObserver对象,延迟去2900ms从performance.getEntries('paint')获取前端渲染点 273 | // setTimeout(() => { 274 | // this.getFPTiming(timeGap); 275 | // }, 2900); 276 | // } 277 | // } else { 278 | // // 如果没有performance api,则表明前端取不到first render点,直接发送670性能点 279 | // this.andrSendFP(-1, 'fe_no_performance_api'); 280 | // } 281 | // } 282 | // 283 | // this.slavePageRendered(); 284 | // this.sendAbilityMessage('rendered', this.masterNoticeComponents); 285 | // this.sendAbilityMessage('nextTickReach'); 286 | // }, 287 | 288 | // messages: { 289 | // 'video:syncCurrentTime'({value: {target, id}}) { 290 | // this.videoMap = this.videoMap || {}; 291 | // this.videoMap[id] = target; 292 | // this.sendAbilityMessage('videoSyncMap', id); 293 | // }, 294 | // 295 | // abilityMessage({value: {eventType, eventParams}}) { 296 | // this.sendAbilityMessage(eventType, eventParams); 297 | // }, 298 | // 299 | // addMasterNoticeComponents({value: componentInfo}) { 300 | // this.masterNoticeComponents.push(componentInfo); 301 | // }, 302 | // 303 | // customComponentInnerUpdated() { 304 | // this.updated(); 305 | // } 306 | // }, 307 | 308 | /** 309 | * 发送abilityMessage 310 | * 311 | * @private 312 | * @param {string} eventType 事件名称 313 | * @param {Object} eventParams 事件参数 314 | */ 315 | // sendAbilityMessage(eventType, eventParams = {}) { 316 | // this.communicator.sendMessage( 317 | // 'master', 318 | // { 319 | // type: 'abilityMessage', 320 | // value: { 321 | // type: eventType, 322 | // params: eventParams 323 | // }, 324 | // slaveId: this.slaveId 325 | // } 326 | // ); 327 | // }, 328 | 329 | 330 | 331 | /** 332 | * slave加载完通知master开始加载slave的js 333 | * 334 | * @private 335 | */ 336 | // slaveLoaded() { 337 | // this.communicator.sendMessage( 338 | // 'master', 339 | // { 340 | // type: 'slaveLoaded', 341 | // value: { 342 | // status: 'loaded' 343 | // }, 344 | // slaveId: this.slaveId 345 | // } 346 | // ); 347 | // }, 348 | 349 | /** 350 | * slave加载完通知master开始加载slave的js 351 | * 352 | * @private 353 | */ 354 | // slaveJsLog() { 355 | // }, 356 | // TODO 兼容onReachBottom上拉触底触发两次的bug 357 | // enviromentBinded: false, 358 | /** 359 | * 360 | * 设置 page 的初始化数据 361 | * 362 | * @param {Object} Data 需要初始化的数据 363 | * @param {string} Data.value data 初始值,会通过 this.data.set 设置到当前 Page 对象 364 | * @param {string} Data.appConfig app.json中的内容 365 | */ 366 | // setInitData(params) { 367 | // // 如果fireMessage比onMessage先,在onMessage时会把消息队列里的整个数组丢过来 368 | // // 现在首屏,会执行两次initData的fireMessage,顺序为fire => on => fire 369 | // params = Object.prototype.toString.call(params) === '[object Array]' ? params[0] : params; 370 | // let {value, appConfig} = params; 371 | // for (let k in value) { 372 | // this.data.set(k, value[k]); 373 | // } 374 | // if (!this.enviromentBinded) { 375 | // this.enviromentBinded = true; 376 | // this.initPageEnviromentEvent(appConfig); 377 | // } 378 | // }, 379 | 380 | 381 | 382 | /** 383 | * 监听 requestComponentObserver 事件 384 | * 385 | * @return {undefined} 386 | */ 387 | // onRequestComponentObserver() { 388 | // let self = this; 389 | // let observerMap = {}; 390 | // 391 | // window.addEventListener('scroll', () => { 392 | // requestAnimationFrame(function () { 393 | // for (let observerId in observerMap) { 394 | // computeObserverIntersection(observerMap[observerId]); 395 | // } 396 | // }); 397 | // }, { 398 | // capture: true, 399 | // passive: true 400 | // }); 401 | // 402 | // this.communicator.onMessage('requestComponentObserver', params => { 403 | // switch (params.operationType) { 404 | // case 'add': 405 | // addIntersectionObserver(params, self.communicator, observerMap); 406 | // break; 407 | // case 'remove': 408 | // removeIntersectionObserver(params, self.communicator, observerMap); 409 | // break; 410 | // } 411 | // }); 412 | // }, 413 | 414 | /** 415 | * 416 | * 初始化页面绑定在宿主环境的相关的事件 417 | * 418 | * @private 419 | * @param {Object} appConfig app.json 配制文件中的内容 420 | */ 421 | // initPageEnviromentEvent(appConfig) { 422 | // const DEFAULT_BOTTOM_DISTANCE = 50; 423 | // const onReachBottomDistance = global.pageInfo.onReachBottomDistance 424 | // || getValueSafety(appConfig, 'window.onReachBottomDistance') 425 | // || DEFAULT_BOTTOM_DISTANCE; 426 | // const enviromentEvent = new EnviromentEvent(); 427 | // enviromentEvent 428 | // .enviromentListen('reachBottom', e => this.sendAbilityMessage('reachBottom'), {onReachBottomDistance}) 429 | // .enviromentListen('scroll', e => this.sendAbilityMessage('onPageScroll', e)); 430 | // }, 431 | // stabilityLog() { 432 | // }, 433 | // browserPatch() { 434 | // // 适配iPhonX样式 435 | // // iOS端bug,在预加载中调用getSystemInfoSync会抛出错误,故后移至此,待修复后挪走 436 | // const systemInfo = this.swaninterface.swan.getSystemInfoSync(); 437 | // if (systemInfo.model && (systemInfo.model.indexOf('iPhone X') > -1) 438 | // || (systemInfo.model === 'iPhone Simulator ' 439 | // && systemInfo.screenHeight === 812 440 | // && systemInfo.screenWidth === 375)) { 441 | // const platform = this.swaninterface.boxjs.platform 442 | // if (platform.isBox() && platform.boxVersion() 443 | // && platform.versionCompare(platform.boxVersion(), '10.13.0') < 0) { 444 | // return; 445 | // } 446 | // const styleEl = document.createElement('style'); 447 | // document.head.appendChild(styleEl); 448 | // const styleSheet = styleEl.sheet; 449 | // styleSheet.insertRule('.swan-security-padding-bottom {padding-bottom: 34px}'); 450 | // styleSheet.insertRule('.swan-security-margin-bottom {margin-bottom: 34px}'); 451 | // styleSheet.insertRule('.swan-security-fixed-bottom {bottom: 34px}'); 452 | // } 453 | // } 454 | }; -------------------------------------------------------------------------------- /test/src/native/component/view/index.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | options: { 4 | template: ` 5 |
6 | 7 |
8 | ` 9 | } 10 | }; -------------------------------------------------------------------------------- /test/src/native/index.js: -------------------------------------------------------------------------------- 1 | import Slave from "../../../dist/box/slaves"; 2 | import EventsEmitter from './utils/events-emitter'; 3 | import Loader from './utils/loader'; 4 | import swanEvents from "../../../src/utils/swan-events"; 5 | import Page from './component/page'; 6 | import View from './component/view/index'; 7 | 8 | const noop = function () {}; 9 | 10 | export default class Native { 11 | constructor(context){ 12 | // this.eventsEmitter = new EventsEmitter(); 13 | this.mockSwanInterface(context); 14 | this.mockSwanComponents(context); 15 | this.mockTestutils(context); 16 | // this.mockSlave(context); 17 | 18 | var obj = { 19 | fun: function (message) { 20 | console.log(message); 21 | } 22 | }; 23 | 24 | obj.fun('你好,测试'); 25 | 26 | } 27 | 28 | /** 29 | * 30 | * @param url 小程序的下载地址,这里为了测试,仅仅是其目录的地址 31 | */ 32 | openWeChatApp(url){ 33 | this.loader = new Loader(url); 34 | 35 | // 加载app.json 36 | this.loader.loadJson('app') 37 | .then((text) => { 38 | console.log(`${url}/app.json loader success`, text); 39 | 40 | this.appInfo = JSON.parse(text); 41 | 42 | // 发送'appReady'事件 43 | window.testutils.clientActions.appReady(url, '1', this.appInfo.pages[0], JSON.stringify(this.appInfo)); 44 | }) 45 | .catch(console.error); 46 | } 47 | 48 | mockSwanInterface(context) { 49 | context.swanInterface = { 50 | swan:{ 51 | request: noop 52 | }, 53 | communicator: new EventsEmitter(), 54 | boxjs:{ 55 | data:{ 56 | get:function(){ 57 | return { 58 | appid:123 59 | } 60 | } 61 | }, 62 | log:noop, 63 | platform: { 64 | versionCompare: noop, 65 | boxVersion: noop 66 | } 67 | }, 68 | 69 | bind:function(type, cb) { 70 | this.communicator.onMessage(type, cb); 71 | // document.addEventListener(type, cb, false); 72 | return this; 73 | }, 74 | unbind:function(type, cb) { 75 | this.communicator.delHandler(type, cb); 76 | // document.removeEventListener(type, cb, false); 77 | return this; 78 | }, 79 | invoke: function (type, ...args) { 80 | return this[type] && this[type](...args); 81 | // return new Promise.resolve().then(function (res) { 82 | // return this[type] && this[type](...args); 83 | // }); 84 | }, 85 | navigateTo: function (params) { 86 | console.log('navigateTo: ', params); 87 | return new Promise(function (resolve, reject) { 88 | const wvID = window.testutils.clientActions 89 | .createSlave(params.slaveActionMap, params.template, params.slaveHookJs); 90 | resolve({wvID}); 91 | params.success && params.success({wvID}); 92 | }); 93 | }, 94 | loadJs: function (params) { 95 | console.log('mock loadJs: ', params); 96 | this.bind('slaveLoaded', function (e) { 97 | console.log('mock listener slaveLoaded', e); 98 | if (+e.slaveId === +params.eventObj.wvID) { 99 | params.success(e); 100 | } 101 | }); 102 | }, 103 | postMessage: function (slaveId, message) { 104 | console.log('Page postMessage', slaveId, message); 105 | // if (slaveId === 'master') { 106 | window.testutils.clientActions.sendMasterMessage(message); 107 | // } 108 | // else { 109 | // document.getElementById(slaveId).contentWindow.postMessage(JSON.stringify(message), '*'); 110 | // return '123'; 111 | // } 112 | }, 113 | onMessage: function (callback) { 114 | console.log('swanInterface onMessage', this); 115 | this.bind('message', e => { 116 | console.log('swanInterface onMessage bind message', e); 117 | 118 | if (e.message) { 119 | let message = null; 120 | try { 121 | if (typeof e.message === 'object') { 122 | message = e.message; 123 | } 124 | else { 125 | message = JSON.parse(unescape(decodeURIComponent(e.message))); 126 | } 127 | } catch (event) { 128 | console.log(event); 129 | } 130 | callback && callback(message); 131 | } 132 | }); 133 | return this; 134 | } 135 | }; 136 | } 137 | 138 | mockSwanComponents(context) { 139 | context.swanComponents = { 140 | getContextOperators:noop, 141 | getComponentRecievers:noop, 142 | getComponents: function () { 143 | return { 144 | 'super-page': Page, 145 | 'view': View 146 | }; 147 | }, 148 | getBehaviorDecorators: function () { 149 | return function (behaviors, target) { 150 | return target; 151 | }; 152 | } 153 | }; 154 | } 155 | 156 | mockTestutils(context) { 157 | context.testutils = { 158 | clientActions: { 159 | dispatchEvent: function (type, params) { 160 | var event = {type: type}; 161 | for (var i in params) { 162 | event[i] = params[i]; 163 | } 164 | window.swanInterface.communicator.fireMessage(event); 165 | }, 166 | dispatchMessage: function (message) { 167 | var event = {type: 'message'}; 168 | event.message = message; 169 | // var event = new Event('message'); 170 | // event.message = message; 171 | // document.dispatchEvent(event); 172 | console.log('clientActions dispatchEvent', event); 173 | window.swanInterface.communicator.fireMessage(event); 174 | }, 175 | appReady: function (appPath, slaveId, pageUrl, appConfig) { 176 | console.log('mock appReady: ', slaveId, pageUrl); 177 | this.dispatchEvent('AppReady', { 178 | pageUrl: pageUrl, 179 | wvID: slaveId, 180 | appPath: appPath, 181 | appConfig: appConfig 182 | }); 183 | // this.appShow(); 184 | }, 185 | 186 | appShow: function () { 187 | this.dispatchEvent('lifecycle', { 188 | lcType: 'onAppShow' 189 | }); 190 | }, 191 | appHide: function () { 192 | this.dispatchEvent('lifecycle', { 193 | lcType: 'onAppHide' 194 | }); 195 | }, 196 | wvID: 2, 197 | createSlave: function (slaveActionMap, template, slaveHookJs) { 198 | console.log('mock createSlave: ', slaveActionMap, template, slaveHookJs); 199 | const wvID = this.wvID++; 200 | for (let actionKey in slaveActionMap) { 201 | this.bind(actionKey, function (e) { 202 | if (+e.slaveId === +wvID) { 203 | slaveActionMap[actionKey](e); 204 | } 205 | }); 206 | } 207 | 208 | // 延迟创建 209 | setTimeout(function () { 210 | window.slaveId = wvID; 211 | const slave = new Slave(window, window.swanInterface, window.swanComponents); 212 | console.log('mock salve=', slave, window.afterSlaveFrameWork); 213 | 214 | window.afterSlaveFrameWork && window.afterSlaveFrameWork(); 215 | global.pageRender && global.pageRender(template, [], []); 216 | window.testutils.clientActions.bind('initData', function (e) { 217 | window.testutils.clientActions.dispatchMessage(e); 218 | setTimeout(function () { 219 | window.afterSlave && window.afterSlave(); 220 | }, 1); 221 | }); 222 | }, 1000); 223 | 224 | return wvID; 225 | }, 226 | bind: function (type, cb) { 227 | console.log('TODO need add code'); 228 | window.swanInterface.communicator.onMessage('message', function (e) { 229 | var messageObj = e.message; 230 | if (typeof messageObj === 'string') { 231 | try { 232 | messageObj = JSON.parse(messageObj); 233 | } 234 | catch (e) { 235 | messageObj = e.message; 236 | } 237 | } 238 | console.log('bind ...', e); 239 | if (messageObj && messageObj.type && messageObj.type === type) { 240 | cb(messageObj); 241 | } 242 | }); 243 | }, 244 | sendMasterMessage: function (message) { 245 | message.slaveId = window.slaveId; 246 | // window.parent.postMessage(JSON.stringify(message), '*'); 247 | window.testutils.clientActions.dispatchMessage(message); 248 | }, 249 | } 250 | }; 251 | 252 | // TODO-ly 刘阳添加的模拟代码 253 | context.swanInterface.bind('AppReady', (event)=>{ 254 | console.log('listener AppReady ', event); 255 | 256 | context.swanInterface.communicator.onMessage(`slaveLoaded${event.wvID}`, (e)=>{ 257 | const slave = new Slave(window, window.swanInterface, window.swanComponents); 258 | window.slaveId = event.wvID; 259 | context.testutils.clientActions.dispatchEvent('PageReady', { 260 | initData: '', 261 | appPath: event.appPath, 262 | pagePath: event.pageUrl 263 | }); 264 | }); 265 | }); 266 | 267 | // TODO-ly 刘阳添加的模拟代码 268 | context.testutils.clientActions.bind('slaveAttached', event => { 269 | console.log('native listen slaveAttached send onShow event', event); 270 | context.testutils.clientActions.appShow(); 271 | context.testutils.clientActions.dispatchEvent('lifecycle', { 272 | lcType: 'onShow', 273 | wvID: event.slaveId 274 | }); 275 | }, {once: true}); 276 | } 277 | } -------------------------------------------------------------------------------- /test/src/native/utils/events-emitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file base listen/fire lib,u can use it as a normal observer in your code 3 | * @author houyu(785798835@qq.com) 4 | */ 5 | 6 | /** 一组队列的抽象 */ 7 | class QueueSet { 8 | 9 | constructor() { 10 | this.queueSet = {}; 11 | } 12 | 13 | /** 14 | * 获取某一条具体队列 15 | * 16 | * @param {string} 需要获取的队列的名称 17 | * @return {Array} 获取到的某一个具体队列 18 | */ 19 | get(namespace) { 20 | if (!this.queueSet[namespace]) { 21 | this.queueSet[namespace] = []; 22 | } 23 | return this.queueSet[namespace]; 24 | } 25 | 26 | /** 27 | * 将某一元素推入具体的某一条队列中 28 | * 29 | * @param {string} namespace 要推入的队列名称 30 | * @param {*} 推入队列的元素 31 | */ 32 | pushTo(namespace, message) { 33 | this.get(namespace).push(message); 34 | } 35 | 36 | /** 37 | * 队列集合中是否含有某一个具体的队列 38 | * 39 | * @param {string} namespace - 队列的名称 40 | * @return {bool} 当前队列集合中是否有某一特定队列 41 | */ 42 | has(namespace) { 43 | return Object.prototype.toString.call(this.queueSet[namespace]) === '[object Array]'; 44 | } 45 | 46 | /** 47 | * 删除队列中的某一项元素 48 | * 49 | * @param {string} namespace - 队列名称 50 | * @param {*} element - 要删除的元素的引用 51 | */ 52 | del(namespace, element) { 53 | if (!element) { 54 | this.queueSet[namespace] = []; 55 | } 56 | else if (namespace === '*') { 57 | Object.keys(this.queueSet) 58 | .filter(namespace => this.has(namespace)) 59 | .forEach(queueName => { 60 | this.queueSet[queueName] = this.queueSet[queueName].filter(item => item.handler !== element); 61 | }); 62 | } 63 | else { 64 | this.queueSet[namespace] = this.queueSet[namespace].filter(item => item.handler !== element); 65 | } 66 | } 67 | } 68 | 69 | export default class EventsEmitter { 70 | 71 | constructor() { 72 | this.handlerQueueSet = new QueueSet(); 73 | this.messageQueueSet = new QueueSet(); 74 | } 75 | 76 | /** 77 | * 融合多条事件流成为一条 78 | * 79 | * @param {...Object} communicators - 需要融合的任意一组事件流 80 | * @return {Object} - 融合后的事件流 81 | */ 82 | static merge(...communicators) { 83 | const mergedEventsEmitter = new EventsEmitter(); 84 | [...communicators] 85 | .forEach(communicator => { 86 | communicator.onMessage('*', e => mergedEventsEmitter.fireMessage(e)); 87 | }); 88 | return mergedEventsEmitter; 89 | } 90 | 91 | /** 92 | * 派发事件 93 | * 94 | * @param {Object} message - 派发事件的对象 95 | * @return {Object} - this环境上下文 96 | */ 97 | fireMessage(message) { 98 | if (message && message.type && this.handlerQueueSet.get(message.type)) { 99 | 100 | this.messageQueueSet.pushTo(message.type, message); 101 | 102 | this.handlerQueueSet.get(message.type) 103 | .forEach(item => { 104 | this.handlerWrapper(item, message.type, message); 105 | }); 106 | 107 | this.handlerQueueSet.get('*') 108 | .forEach(item => { 109 | this.handlerWrapper(item, '*', message); 110 | }); 111 | } 112 | return this; 113 | } 114 | 115 | /** 116 | * 监听事件 117 | * 118 | * @param {string} type - 监听的事件名称 119 | * @param {Function} handler - 监听的事件回调 120 | * @param {Object} options - 监听的设置选项 121 | * @return {Object} this环境上下文 122 | */ 123 | onMessage(type, handler, options = {}) { 124 | 125 | if (Object.prototype.toString.call(type) === '[object Array]') { 126 | type.forEach(oneType => this.onMessage(oneType, handler, options)); 127 | return this; 128 | } 129 | 130 | this.handlerQueueSet.pushTo(type, { 131 | handler, 132 | once: options.once 133 | }); 134 | 135 | if (options.listenPreviousEvent === true && this.messageQueueSet.has(type)) { 136 | this.handlerWrapper( 137 | {handler, once: options.once}, 138 | type, 139 | this.messageQueueSet.get(type) 140 | ); 141 | } 142 | return this; 143 | } 144 | 145 | /** 146 | * 删除事件监听 147 | * 148 | * @param {string} type - 监听的事件名称 149 | * @param {Function} handler - 监听的事件回调 150 | * @return {Object} this环境上下文 151 | */ 152 | delHandler(type, handler) { 153 | this.handlerQueueSet.del(type, handler); 154 | return this; 155 | } 156 | 157 | /** 158 | * 执行handler的代理函数 159 | * 160 | * @param {Object} item - 事件流中存储的某个对象 161 | * @param {Function} item.handler - 事件流中某个对象中的事件接受处理者 162 | * @param {string} type - 事件的名称 163 | * @param {*} message - 需要传递给执行事件的参数 164 | * @return {bool} - 执行是否成功的结果 165 | */ 166 | handlerWrapper({handler, once}, type, message) { 167 | if (!handler) { 168 | return false; 169 | } 170 | handler.call(this, message); 171 | // 如果设定,用完即删 172 | if (once) { 173 | this.handlerQueueSet.del(type, handler); 174 | } 175 | return true; 176 | } 177 | } 178 | 179 | export {QueueSet}; 180 | -------------------------------------------------------------------------------- /test/src/native/utils/loader.js: -------------------------------------------------------------------------------- 1 | 2 | // import swanEvents from './events-emitter'; 3 | /* globals swanGlobal _naSwan */ 4 | // const doc = swanGlobal ? {} : document; 5 | export default class Loader { 6 | constructor(basePath = '') { 7 | this.basePath = basePath; 8 | this.loadedResource = { 9 | js: {}, 10 | css: {} 11 | }; 12 | } 13 | loadjs(document, name) { 14 | const loadPath = this.basePath + '/' + name + '.js'; 15 | if (this.loadedResource.js[loadPath]) { 16 | return Promise.resolve(); 17 | } 18 | return new Promise((resolve, reject) => { 19 | // if (swanGlobal) { 20 | // try { 21 | // _naSwan.include(loadPath); 22 | // // action && swanEvents(action); 23 | // } catch (e) { 24 | // reject(e); 25 | // console.error(e); 26 | // } 27 | // resolve(); 28 | // } else { 29 | const script = document.createElement('script'); 30 | script.type = 'text/javascript'; 31 | script.src = loadPath; 32 | script.onload = () => { 33 | this.loadedResource.js[loadPath] = true; 34 | // action && swanEvents(action); 35 | resolve(); 36 | }; 37 | script.onerror = reject; 38 | document.head.appendChild(script); 39 | // } 40 | }); 41 | } 42 | loadcss(document, name) { 43 | const loadPath = this.basePath + '/' + name + '.wxss'; 44 | if (this.loadedResource.js[loadPath]) { 45 | return Promise.resolve(); 46 | } 47 | return new Promise((resolve, reject) => { 48 | const link = document.createElement('link'); 49 | link.type = 'text/css'; 50 | link.rel = 'stylesheet'; 51 | link.href = loadPath; 52 | link.onload = () => { 53 | this.loadedResource.css[loadPath] = true; 54 | // action && swanEvents(action); 55 | resolve(); 56 | }; 57 | link.onerror = reject; 58 | document.head.appendChild(link); 59 | }); 60 | } 61 | loadJson(name){ 62 | const loadPath = this.basePath + '/' + name + '.json'; 63 | return fetch(loadPath) 64 | .then(function(response) { 65 | return response.text(); 66 | }); 67 | } 68 | loadWxml(name){ 69 | const loadPath = this.basePath + '/' + name + '.wxml'; 70 | return fetch(loadPath) 71 | .then((response)=>{ 72 | return response.text(); 73 | }) 74 | .then((text)=>{ 75 | return { 76 | name: name, 77 | wxml: text 78 | }; 79 | }); 80 | } 81 | // TODO other files type 82 | } 83 | -------------------------------------------------------------------------------- /test/src/native/utils/swan-events/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file swan-events/index.js 3 | */ 4 | import EventsEmitter from '../events-emitter'; 5 | const global = window; 6 | global.swanEvents = global.swanEvents || new EventsEmitter(); 7 | 8 | export default function (eventName, data){ 9 | global.swanEvents.fireMessage({ 10 | type: 'TraceEvents', 11 | params: { 12 | eventName, 13 | data 14 | } 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /test/src/wxs/helloworld/app.css: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #fbf9fe; 3 | height: 100%; 4 | } 5 | .container { 6 | display: flex; 7 | flex-direction: column; 8 | min-height: 100%; 9 | justify-content: space-between; 10 | } 11 | .page-header { 12 | display: flex; 13 | font-size: 32rpx; 14 | color: #aaa; 15 | justify-content: center; 16 | margin-top: 50rpx; 17 | } 18 | .page-header-text { 19 | padding: 20rpx 40rpx; 20 | border-bottom: 1px solid #ccc; 21 | } 22 | 23 | .page-body { 24 | width: 100%; 25 | display: flex; 26 | flex-direction: column; 27 | align-items: center; 28 | flex-grow: 1; 29 | overflow-x: hidden; 30 | } 31 | .page-body-wrapper { 32 | margin-top: 100rpx; 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | width: 100%; 37 | } 38 | .page-body-wrapper form { 39 | width: 100%; 40 | } 41 | .page-body-wording { 42 | text-align: center; 43 | padding: 200rpx 100rpx; 44 | } 45 | .page-body-info { 46 | display: flex; 47 | flex-direction: column; 48 | align-items: center; 49 | background-color: #fff; 50 | margin-bottom: 50rpx; 51 | width: 100%; 52 | padding: 50rpx 0 150rpx 0; 53 | } 54 | .page-body-title { 55 | margin-bottom: 100rpx; 56 | font-size: 32rpx; 57 | } 58 | .page-body-text { 59 | font-size: 30rpx; 60 | line-height: 26px; 61 | color: #ccc; 62 | } 63 | .page-body-text-small { 64 | font-size: 24rpx; 65 | color: #000; 66 | margin-bottom: 100rpx; 67 | } 68 | .page-body-form { 69 | width: 100%; 70 | background-color: #fff; 71 | display: flex; 72 | flex-direction: column; 73 | width: 100%; 74 | border: 1px solid #eee; 75 | } 76 | .page-body-form-item { 77 | display: flex; 78 | align-items: center; 79 | margin-left: 10rpx; 80 | border-bottom: 1px solid #eee; 81 | height: 80rpx; 82 | } 83 | .page-body-form-key { 84 | width: 180rpx; 85 | } 86 | .page-body-form-value { 87 | flex-grow: 1; 88 | } 89 | 90 | .page-body-form-picker { 91 | display: flex; 92 | justify-content: space-between; 93 | height: 100rpx; 94 | align-items: center; 95 | font-size: 36rpx; 96 | margin-left: 20rpx; 97 | padding-right: 20rpx; 98 | border-bottom: 1px solid #eee; 99 | } 100 | .page-body-form-picker-value { 101 | color: #ccc; 102 | } 103 | 104 | .page-body-buttons { 105 | width: 100%; 106 | } 107 | .page-body-button { 108 | margin: 25rpx; 109 | } 110 | .page-body-button image { 111 | width: 150rpx; 112 | height: 150rpx; 113 | } 114 | .page-footer { 115 | text-align: center; 116 | color: #1aad19; 117 | font-size: 24rpx; 118 | margin: 20rpx 0; 119 | } 120 | 121 | .green{ 122 | color: #09BB07; 123 | } 124 | .red{ 125 | color: #F76260; 126 | } 127 | .blue{ 128 | color: #10AEFF; 129 | } 130 | .yellow{ 131 | color: #FFBE00; 132 | } 133 | .gray{ 134 | color: #C9C9C9; 135 | } 136 | 137 | .strong{ 138 | font-weight: bold; 139 | } 140 | 141 | .bc_green{ 142 | background-color: #09BB07; 143 | } 144 | .bc_red{ 145 | background-color: #F76260; 146 | } 147 | .bc_blue{ 148 | background-color: #10AEFF; 149 | } 150 | .bc_yellow{ 151 | background-color: #FFBE00; 152 | } 153 | .bc_gray{ 154 | background-color: #C9C9C9; 155 | } 156 | 157 | .tc{ 158 | text-align: center; 159 | } 160 | 161 | .page input{ 162 | padding: 10px 15px; 163 | background-color: #fff; 164 | } 165 | checkbox, radio{ 166 | margin-right: 5px; 167 | } 168 | 169 | .btn-area{ 170 | padding: 0 15px; 171 | } 172 | .btn-area button{ 173 | margin-top: 10px; 174 | margin-bottom: 10px; 175 | } 176 | 177 | .page { 178 | min-height: 100%; 179 | flex: 1; 180 | background-color: #FBF9FE; 181 | font-size: 16px; 182 | font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif; 183 | overflow: hidden; 184 | } 185 | .page__hd{ 186 | padding: 40px; 187 | } 188 | .page__title{ 189 | display: block; 190 | font-size: 20px; 191 | } 192 | .page__desc{ 193 | margin-top: 5px; 194 | font-size: 14px; 195 | color: #888888; 196 | } 197 | 198 | .section{ 199 | margin-bottom: 40px; 200 | } 201 | .section_gap{ 202 | padding: 0 15px; 203 | } 204 | .section__title{ 205 | margin-bottom: 8px; 206 | padding-left: 15px; 207 | padding-right: 15px; 208 | } 209 | .section_gap .section__title{ 210 | padding-left: 0; 211 | padding-right: 0; 212 | } 213 | .section__ctn{ 214 | 215 | } 216 | -------------------------------------------------------------------------------- /test/src/wxs/helloworld/app.js: -------------------------------------------------------------------------------- 1 | window.define("130", 2 | function (t, e, n, o, a, i, s, c, r, u, d, l, g, w, f, h) { 3 | Page({ 4 | data: { 5 | name: 'helloworld' 6 | }, 7 | onLoad: function () { 8 | console.log("Lifecycle Page onLoad"); 9 | }, 10 | onReady: function () { 11 | console.log("Lifecycle Page onReady"); 12 | }, 13 | onShow: function (e) { 14 | console.log("Lifecycle Page onShow"); 15 | }, 16 | onHide: function () { 17 | console.log("Lifecycle Page onHide"); 18 | }, 19 | onUnload: function () { 20 | console.log("Lifecycle Page onUnload"); 21 | } 22 | }) 23 | }); 24 | 25 | window.define("138", 26 | function(t, e, n, o, a, i, s, c, r, u, d, l, g, w, f, h) { 27 | var p = []; 28 | Page({ 29 | data: { 30 | text: "这是一段文字." 31 | }, 32 | onShow: function(t) { 33 | p = [] 34 | }, 35 | add: function(t) { 36 | p.push("其他文字"); 37 | this.setData({ 38 | text: "这是一段文字." + p.join(",") 39 | }) 40 | }, 41 | remove: function(t) { 42 | p.length > 0 && (p.pop(), this.setData({ 43 | text: "这是一段文字." + p.join(",") 44 | })) 45 | } 46 | }) 47 | }); 48 | 49 | window.define("193", 50 | function(t, e, n, o, a, i, s, c, r, u, d, l, g, w, f, h) { 51 | App({ 52 | onLaunch: function(t) { 53 | console.log("Lifecycle App onLaunch") 54 | }, 55 | onShow: function(t) { 56 | console.log("Lifecycle App onShow") 57 | } 58 | }) 59 | }); 60 | 61 | window.__swanRoute = "app"; 62 | window.usingComponents = []; 63 | require("193"); 64 | 65 | // window.__swanRoute = "page/component/index"; 66 | // window.usingComponents = []; 67 | // require("130"); 68 | 69 | window.__swanRoute = "pages/text/text"; 70 | window.usingComponents = []; 71 | require("138"); 72 | -------------------------------------------------------------------------------- /test/src/wxs/helloworld/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/text/text" 4 | ], 5 | "window": { 6 | "navigationBarTextStyle": "black", 7 | "navigationBarTitleText": "小程序演示", 8 | "navigationBarBackgroundColor": "#fbf9fe", 9 | "backgroundColor": "#fbf9fe" 10 | }, 11 | "networkTimeout": { 12 | "request": 10000, 13 | "connectSocket": 10000, 14 | "uploadFile": 10000, 15 | "downloadFile": 10000 16 | }, 17 | "debug": true 18 | } 19 | -------------------------------------------------------------------------------- /test/src/wxs/helloworld/pages/component/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/helloworld/pages/component/index.css -------------------------------------------------------------------------------- /test/src/wxs/helloworld/pages/component/index.js: -------------------------------------------------------------------------------- 1 | 2 | Page({ 3 | data:{ 4 | name: 'helloworld' 5 | } 6 | }); -------------------------------------------------------------------------------- /test/src/wxs/helloworld/pages/component/index.swan.js: -------------------------------------------------------------------------------- 1 | ((global)=>{ 2 | 3 | global.errorMsg = []; 4 | var templateComponents = Object.assign({}, {}); 5 | var param = {}; 6 | var filterArr = JSON.parse("[]"); 7 | try { 8 | filterArr && filterArr.forEach(function (item) { 9 | param[item.module] = eval(item.module) 10 | }); 11 | 12 | var pageContent = '
{{name}}
'; 13 | 14 | var renderPage = function (filters, modules) { 15 | // 路径与该组件映射 16 | // var customAbsolutePathMap = (global.componentFactory.getAllComponents(), {}); 17 | 18 | // 当前页面使用的自定义组件 19 | // const pageUsingComponentMap = JSON.parse("{}"); 20 | 21 | // 生成该页面引用的自定义组件 22 | // const customComponents = Object.keys(pageUsingComponentMap).reduce((customComponents, customName) => { 23 | // customComponents[customName] = customAbsolutePathMap[pageUsingComponentMap[customName]]; 24 | // return customComponents; 25 | // }, {}); 26 | global.pageRender(pageContent, templateComponents) 27 | }; 28 | 29 | renderPage(filterArr, param); 30 | } catch (e) { 31 | global.errorMsg['execError'] = e; 32 | throw e; 33 | } 34 | })(window); -------------------------------------------------------------------------------- /test/src/wxs/helloworld/pages/component/index.wxml: -------------------------------------------------------------------------------- 1 |
2 | {{name}} 3 |
-------------------------------------------------------------------------------- /test/src/wxs/helloworld/pages/text/text.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/helloworld/pages/text/text.css -------------------------------------------------------------------------------- /test/src/wxs/helloworld/pages/text/text.js: -------------------------------------------------------------------------------- 1 | var r = []; 2 | Page({ 3 | data: { 4 | text: "这是一段文字." 5 | }, 6 | onShow: function(t) { 7 | r = [] 8 | }, 9 | add: function(t) { 10 | r.push("其他文字"), 11 | this.setData({ 12 | text: "这是一段文字." + r.join(",") 13 | }) 14 | }, 15 | remove: function(t) { 16 | r.length > 0 && (r.pop(), this.setData({ 17 | text: "这是一段文字." + r.join(",") 18 | })) 19 | } 20 | }) -------------------------------------------------------------------------------- /test/src/wxs/helloworld/pages/text/text.swan.js: -------------------------------------------------------------------------------- 1 | ((global)=>{ 2 | 3 | global.errorMsg = []; 4 | var templateComponents = Object.assign({}, {}); 5 | var param = {}; 6 | var filterArr = JSON.parse("[]"); 7 | try { 8 | filterArr && filterArr.forEach(function (item) { 9 | param[item.module] = eval(item.module) 10 | }); 11 | 12 | var pageContent = ` 13 |
14 |
{{text}}
15 | 18 | 21 |
`; 22 | 23 | var renderPage = function (filters, modules) { 24 | // 路径与该组件映射 25 | // var customAbsolutePathMap = (global.componentFactory.getAllComponents(), {}); 26 | 27 | // 当前页面使用的自定义组件 28 | // const pageUsingComponentMap = JSON.parse("{}"); 29 | 30 | // 生成该页面引用的自定义组件 31 | // const customComponents = Object.keys(pageUsingComponentMap).reduce((customComponents, customName) => { 32 | // customComponents[customName] = customAbsolutePathMap[pageUsingComponentMap[customName]]; 33 | // return customComponents; 34 | // }, {}); 35 | global.pageRender(pageContent, templateComponents) 36 | }; 37 | 38 | renderPage(filterArr, param); 39 | } catch (e) { 40 | global.errorMsg['execError'] = e; 41 | throw e; 42 | } 43 | })(window); -------------------------------------------------------------------------------- /test/src/wxs/helloworld/pages/text/text.wxml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/helloworld/pages/text/text.wxml -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/app.css: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #fbf9fe; 3 | height: 100%; 4 | } 5 | .container { 6 | display: flex; 7 | flex-direction: column; 8 | min-height: 100%; 9 | justify-content: space-between; 10 | } 11 | .page-header { 12 | display: flex; 13 | font-size: 32rpx; 14 | color: #aaa; 15 | justify-content: center; 16 | margin-top: 50rpx; 17 | } 18 | .page-header-text { 19 | padding: 20rpx 40rpx; 20 | border-bottom: 1px solid #ccc; 21 | } 22 | 23 | .page-body { 24 | width: 100%; 25 | display: flex; 26 | flex-direction: column; 27 | align-items: center; 28 | flex-grow: 1; 29 | overflow-x: hidden; 30 | } 31 | .page-body-wrapper { 32 | margin-top: 100rpx; 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | width: 100%; 37 | } 38 | .page-body-wrapper form { 39 | width: 100%; 40 | } 41 | .page-body-wording { 42 | text-align: center; 43 | padding: 200rpx 100rpx; 44 | } 45 | .page-body-info { 46 | display: flex; 47 | flex-direction: column; 48 | align-items: center; 49 | background-color: #fff; 50 | margin-bottom: 50rpx; 51 | width: 100%; 52 | padding: 50rpx 0 150rpx 0; 53 | } 54 | .page-body-title { 55 | margin-bottom: 100rpx; 56 | font-size: 32rpx; 57 | } 58 | .page-body-text { 59 | font-size: 30rpx; 60 | line-height: 26px; 61 | color: #ccc; 62 | } 63 | .page-body-text-small { 64 | font-size: 24rpx; 65 | color: #000; 66 | margin-bottom: 100rpx; 67 | } 68 | .page-body-form { 69 | width: 100%; 70 | background-color: #fff; 71 | display: flex; 72 | flex-direction: column; 73 | width: 100%; 74 | border: 1px solid #eee; 75 | } 76 | .page-body-form-item { 77 | display: flex; 78 | align-items: center; 79 | margin-left: 10rpx; 80 | border-bottom: 1px solid #eee; 81 | height: 80rpx; 82 | } 83 | .page-body-form-key { 84 | width: 180rpx; 85 | } 86 | .page-body-form-value { 87 | flex-grow: 1; 88 | } 89 | 90 | .page-body-form-picker { 91 | display: flex; 92 | justify-content: space-between; 93 | height: 100rpx; 94 | align-items: center; 95 | font-size: 36rpx; 96 | margin-left: 20rpx; 97 | padding-right: 20rpx; 98 | border-bottom: 1px solid #eee; 99 | } 100 | .page-body-form-picker-value { 101 | color: #ccc; 102 | } 103 | 104 | .page-body-buttons { 105 | width: 100%; 106 | } 107 | .page-body-button { 108 | margin: 25rpx; 109 | } 110 | .page-body-button image { 111 | width: 150rpx; 112 | height: 150rpx; 113 | } 114 | .page-footer { 115 | text-align: center; 116 | color: #1aad19; 117 | font-size: 24rpx; 118 | margin: 20rpx 0; 119 | } 120 | 121 | .green{ 122 | color: #09BB07; 123 | } 124 | .red{ 125 | color: #F76260; 126 | } 127 | .blue{ 128 | color: #10AEFF; 129 | } 130 | .yellow{ 131 | color: #FFBE00; 132 | } 133 | .gray{ 134 | color: #C9C9C9; 135 | } 136 | 137 | .strong{ 138 | font-weight: bold; 139 | } 140 | 141 | .bc_green{ 142 | background-color: #09BB07; 143 | } 144 | .bc_red{ 145 | background-color: #F76260; 146 | } 147 | .bc_blue{ 148 | background-color: #10AEFF; 149 | } 150 | .bc_yellow{ 151 | background-color: #FFBE00; 152 | } 153 | .bc_gray{ 154 | background-color: #C9C9C9; 155 | } 156 | 157 | .tc{ 158 | text-align: center; 159 | } 160 | 161 | .page input{ 162 | padding: 10px 15px; 163 | background-color: #fff; 164 | } 165 | checkbox, radio{ 166 | margin-right: 5px; 167 | } 168 | 169 | .btn-area{ 170 | padding: 0 15px; 171 | } 172 | .btn-area button{ 173 | margin-top: 10px; 174 | margin-bottom: 10px; 175 | } 176 | 177 | .page { 178 | min-height: 100%; 179 | flex: 1; 180 | background-color: #FBF9FE; 181 | font-size: 16px; 182 | font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif; 183 | overflow: hidden; 184 | } 185 | .page__hd{ 186 | padding: 40px; 187 | } 188 | .page__title{ 189 | display: block; 190 | font-size: 20px; 191 | } 192 | .page__desc{ 193 | margin-top: 5px; 194 | font-size: 14px; 195 | color: #888888; 196 | } 197 | 198 | .section{ 199 | margin-bottom: 40px; 200 | } 201 | .section_gap{ 202 | padding: 0 15px; 203 | } 204 | .section__title{ 205 | margin-bottom: 8px; 206 | padding-left: 15px; 207 | padding-right: 15px; 208 | } 209 | .section_gap .section__title{ 210 | padding-left: 0; 211 | padding-right: 0; 212 | } 213 | .section__ctn{ 214 | 215 | } 216 | -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/app.js: -------------------------------------------------------------------------------- 1 | App({ 2 | onLaunch: function () { 3 | console.log('App Launch') 4 | }, 5 | onShow: function () { 6 | console.log('App Show') 7 | }, 8 | onHide: function () { 9 | console.log('App Hide') 10 | }, 11 | globalData: { 12 | hasLogin: false 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "page/component/index", 4 | "page/component/component-pages/wx-view/wx-view" 5 | ], 6 | "window": { 7 | "navigationBarTextStyle": "black", 8 | "navigationBarTitleText": "小程序演示", 9 | "navigationBarBackgroundColor": "#fbf9fe", 10 | "backgroundColor": "#fbf9fe" 11 | }, 12 | "tabBar": { 13 | "color": "#dddddd", 14 | "selectedColor": "#3cc51f", 15 | "borderStyle": "black", 16 | "backgroundColor": "#ffffff", 17 | "list": [{ 18 | "pagePath": "page/component/index", 19 | "iconPath": "image/wechat.png", 20 | "selectedIconPath": "image/wechatHL.png", 21 | "text": "组件" 22 | }, { 23 | "pagePath": "page/API/index/index", 24 | "iconPath": "image/wechat.png", 25 | "selectedIconPath": "image/wechatHL.png", 26 | "text": "接口" 27 | }] 28 | }, 29 | "networkTimeout": { 30 | "request": 10000, 31 | "connectSocket": 10000, 32 | "uploadFile": 10000, 33 | "downloadFile": 10000 34 | }, 35 | "debug": true 36 | } 37 | -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/arrowright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/arrowright.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/fix/bgm01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/fix/bgm01.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/fix/bgm02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/fix/bgm02.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/fix/bgm03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/fix/bgm03.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/fix/bgm11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/fix/bgm11.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/fix/bgm12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/fix/bgm12.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/fix/bgm13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/fix/bgm13.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/fix/bgm14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/fix/bgm14.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/fix/bgm15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/fix/bgm15.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/fix/scroll-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/fix/scroll-view.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/icon64_appwx_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/icon64_appwx_logo.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/pause.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/play.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/plus.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/record.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/resources/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/resources/arrow.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/resources/kind/canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/resources/kind/canvas.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/resources/kind/content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/resources/kind/content.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/resources/kind/form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/resources/kind/form.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/resources/kind/interact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/resources/kind/interact.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/resources/kind/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/resources/kind/map.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/resources/kind/media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/resources/kind/media.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/resources/kind/nav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/resources/kind/nav.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/resources/kind/view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/resources/kind/view.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/resources/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/resources/pic.jpg -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/stop.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/trash.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/wechat.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/image/wechatHL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handsomeliuyang/swanvue-core/6a72f5966b959dbb648eeba1d1449783f73804f2/test/src/wxs/wechat-app-demo/image/wechatHL.png -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/page/component/component-pages/wx-view/wx-view.css: -------------------------------------------------------------------------------- 1 | .flex-wrp{ 2 | height: 100px; 3 | display:flex; 4 | background-color: #FFFFFF; 5 | } 6 | .flex-item{ 7 | width: 100px; 8 | height: 100px; 9 | } 10 | -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/page/component/component-pages/wx-view/wx-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | wx-view 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | view 18 | 弹性框模型分为弹性容器以及弹性项目。当组件的display为flex或inline-flex时,该组件则为弹性容器,弹性容器的子组件为弹性项目。 19 | 20 | 21 | 22 | flex-direction: row 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | flex-direction: column 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | justify-content: flex-start 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | justify-content: flex-end 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | justify-content: center 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | justify-content: space-between 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | justify-content: space-around 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | align-items: flex-end 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | align-items: center 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 | 98 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/page/component/component-pages/wx-view/wx-view.js: -------------------------------------------------------------------------------- 1 | import Vue from '../../../../../../../../dist/wxavue.js' 2 | import './wx-view.css' 3 | 4 | export default Vue.Page({ 5 | data:{ 6 | 7 | }, 8 | template: ` 9 | 10 | 11 | view 12 | 弹性框模型分为弹性容器以及弹性项目。当组件的display为flex或inline-flex时,该组件则为弹性容器,弹性容器的子组件为弹性项目。 13 | 14 | 15 | 16 | flex-direction: row 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | flex-direction: column 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | justify-content: flex-start 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | justify-content: flex-end 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | justify-content: center 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | justify-content: space-between 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | justify-content: space-around 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | align-items: flex-end 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | align-items: center 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | ` 90 | }); 91 | -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/page/component/index.css: -------------------------------------------------------------------------------- 1 | .index{ 2 | background-color: #FBF9FE; 3 | font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif; 4 | flex: 1; 5 | min-height: 100%; 6 | font-size: 16px; 7 | } 8 | .head{ 9 | padding: 40px; 10 | } 11 | .body{ 12 | padding-left: 15px; 13 | padding-right: 15px; 14 | overflow: hidden; 15 | } 16 | .title{ 17 | font-size: 30px; 18 | } 19 | .desc{ 20 | margin-top: 5px; 21 | color: #888888; 22 | font-size: 14px; 23 | } 24 | 25 | .widgets__item{ 26 | margin-top: 10px; 27 | margin-bottom: 10px; 28 | background-color: #FFFFFF; 29 | overflow: hidden; 30 | border-radius: 2px; 31 | cursor: pointer; 32 | } 33 | .widgets__info{ 34 | display: flex; 35 | padding: 20px; 36 | align-items: center; 37 | flex-direction: row; 38 | } 39 | .widgets__info_show{ 40 | opacity: .4; 41 | } 42 | .widgets__info-name{ 43 | flex: 1; 44 | } 45 | .widgets__info-img{ 46 | width: 30px; 47 | height: 30px; 48 | } 49 | 50 | .widgets__list{ 51 | display: none; 52 | } 53 | .widgets__list_show{ 54 | display: block; 55 | } 56 | /* 57 | .widgets__list{ 58 | height: 0; 59 | overflow: hidden; 60 | } 61 | .widgets__list_show{ 62 | height: auto; 63 | } 64 | .widgets__list-inner{ 65 | opacity: 0; 66 | transform: translateY(-50%); 67 | transition: .3s; 68 | } 69 | .widgets__list-inner_show{ 70 | opacity: 1; 71 | transform: translateY(0); 72 | } 73 | */ 74 | 75 | .widget{ 76 | position: relative; 77 | padding-top: 13px; 78 | padding-bottom: 13px; 79 | padding-left: 20px; 80 | padding-right: 20px; 81 | } 82 | .widget__arrow{ 83 | position: absolute; 84 | top: 14px; 85 | right: 22px; 86 | width: 8px; 87 | height: 16px; 88 | } 89 | .widget__line{ 90 | content: " "; 91 | position: absolute; 92 | left: 20px; 93 | bottom: 0; 94 | right: 20px; 95 | height: 1rpx; 96 | background-color: #DFDFDF; 97 | } 98 | -------------------------------------------------------------------------------- /test/src/wxs/wechat-app-demo/page/component/index.js: -------------------------------------------------------------------------------- 1 | import Vue from '../../../../../../dist/wxavue.js' 2 | import './index.css' 3 | 4 | var type = [ 5 | 'view', 'content', 'form', 'interact', 'nav', 'media', 'map', 'canvas' 6 | ]; 7 | 8 | export default Vue.Page({ 9 | data: { 10 | viewShow: false, 11 | contentShow: false, 12 | formShow: false, 13 | interactShow: false, 14 | navShow: false, 15 | mediaShow: false, 16 | mapShow: false, 17 | canvasShow: false 18 | }, 19 | widgetsToggle: function (e) { 20 | console.log(e); 21 | 22 | var id = e.currentTarget.id, data = {}; 23 | for (var i = 0, len = type.length; i < len; ++i) { 24 | data[type[i] + 'Show'] = false; 25 | } 26 | data[id + 'Show'] = !this.data[id + 'Show']; 27 | this.setData(data); 28 | }, 29 | template: ` 30 | 31 | 32 | 小程序组件 33 | 这是展示小程序组件的DEMO。 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 内容分区 42 | 43 | 44 | 45 | 46 | 47 | view 48 | 49 | 50 | 51 | 52 | scroll-view 53 | 54 | 55 | 56 | 57 | swiper 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 内容 66 | 67 | 68 | 69 | 70 | text 71 | 72 | 73 | 74 | 75 | icon 76 | 77 | 78 | 79 | 80 | mask 81 | 82 | 83 | 84 | 85 | toast 86 | 87 | 88 | 89 | 90 | progress 91 | 92 | 93 | 94 | 95 | 96 | 97 | 表单组件 98 | 99 | 100 | 101 | 102 | button 103 | 104 | 105 | 106 | 107 | checkbox 108 | 109 | 110 | 111 | 112 | form 113 | 114 | 115 | 116 | 117 | input 118 | 119 | 120 | 121 | 122 | label 123 | 124 | 125 | 126 | 127 | picker 128 | 129 | 130 | 131 | 132 | radio 133 | 134 | 135 | 136 | 137 | slider 138 | 139 | 140 | 141 | 142 | switch 143 | 144 | 145 | 146 | 147 | 148 | 149 | 交互组件 150 | 151 | 152 | 153 | 154 | action-sheet 155 | 156 | 157 | 158 | 159 | modal 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 导航 168 | 169 | 170 | 171 | 172 | navigator 173 | 174 | 175 | 176 | 177 | 178 | 179 | 媒体 180 | 181 | 182 | 183 | 184 | 185 | image 186 | 187 | 188 | 189 | 190 | audio 191 | 192 | 193 | 194 | 195 | video 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | ` 205 | }); 206 | -------------------------------------------------------------------------------- /test/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | module.exports = { 7 | mode: 'development', 8 | entry: { 9 | main: './src/main.js' 10 | }, 11 | output: { 12 | path: path.resolve(__dirname, 'dist'), 13 | filename: "[name].js" 14 | }, 15 | // devtool: 'inline-source-map', 16 | devServer: { 17 | contentBase: path.join(__dirname, 'dist'), 18 | port: 9000, 19 | disableHostCheck: true 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.css$/, 25 | use: ['style-loader', 'css-loader'] 26 | }, 27 | { 28 | test: /\.js$/, 29 | exclude: [ 30 | /node_modules/, 31 | path.resolve(__dirname, "../dist/box/master/index.js"), 32 | path.resolve(__dirname, "../dist/box/slaves/index.js") 33 | ], 34 | use: { 35 | loader: 'babel-loader', 36 | options: { 37 | presets: ['env'], 38 | } 39 | } 40 | 41 | } 42 | ] 43 | }, 44 | plugins: [ 45 | new HtmlWebpackPlugin({ 46 | title: 'exdemo' 47 | }), 48 | new CopyWebpackPlugin([ 49 | {from: 'src/wxs', to: 'wxs', force: true} 50 | ]) 51 | ] 52 | }; 53 | -------------------------------------------------------------------------------- /webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | // const CopyWebpackPlugin = require('copy-webpack-plugin'); 2 | const webpack = require('webpack'); 3 | const path = require('path'); 4 | const root = pathRelativeToRoot => path.resolve(__dirname, '..', pathRelativeToRoot); 5 | module.exports = { 6 | entry: { 7 | master: __dirname + '/src/master/index.js', 8 | slaves: __dirname + '/src/slave/index.js' 9 | }, 10 | output: { 11 | path: __dirname + '/dist/box/', 12 | filename: '[name]/index.js', 13 | libraryTarget: "umd" 14 | }, 15 | devtool: 'source-map', 16 | plugins: [ 17 | new webpack.LoaderOptionsPlugin({ 18 | minimize: false, 19 | debug: true 20 | }), 21 | // new webpack.optimize.UglifyJsPlugin({ 22 | // // sourceMap: true, 23 | // compress: { 24 | // warnings: false, 25 | // /* eslint-disable fecs-camelcase */ 26 | // drop_console: false 27 | // /* eslint-disable fecs-camelcase */ 28 | // }, 29 | // // sourceMap: true, 30 | // comments: false 31 | // }), 32 | 33 | // new CopyWebpackPlugin([{ 34 | // from: __dirname + '/src/templates/**/*', 35 | // to: __dirname + '/dist/box/[1]/[name].[ext]', 36 | // test: /([^/]+)\/([^/]+)\.[^.]+$/ 37 | // }]) 38 | ] 39 | }; 40 | -------------------------------------------------------------------------------- /webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const baseWebpackConfig = require('./webpack.base.conf.js'); 3 | 4 | module.exports = merge( 5 | baseWebpackConfig, 6 | { 7 | resolve: { 8 | alias: { 9 | 'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1 10 | } 11 | }, 12 | module: { 13 | loaders: [{ 14 | test: /\.js$/, 15 | exclude: /node_modules/, 16 | loader: 'babel-loader', 17 | query: { 18 | presets: ['env'], 19 | plugins: [ 20 | 'transform-class-properties', ['transform-object-rest-spread', {'useBuiltIns': true}], 21 | 'transform-decorators-legacy', 22 | 'transform-object-assign', 23 | ['istanbul', { 24 | 'exclude': [ 25 | 'src/utils/**/*.js', 26 | 'test/spec/*.js', 27 | 'src/master/custom-component/index.js' 28 | ] 29 | }] 30 | ] 31 | } 32 | } 33 | ] 34 | } 35 | } 36 | ); 37 | --------------------------------------------------------------------------------