UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

819 lines (754 loc) 19.2 kB
const queryParser = require('querystring') const URL = require('url') const path = require('path') const fse = require('fs-extra') const util = require('./util') const bundleWrapper = (code, sourceUrl) => { const injectedGlobals = [ // ES 'Promise', // W3C 'window', 'weex', 'service', 'Rax', 'services', 'global', 'screen', 'document', 'navigator', 'location', 'fetch', 'Headers', 'Response', 'Request', 'URL', 'URLSearchParams', 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'requestAnimationFrame', 'cancelAnimationFrame', 'alert', // ModuleJS 'define', 'require', // Weex 'bootstrap', 'register', 'render', '__d', '__r', '__DEV__', '__weex_define__', '__weex_require__', '__weex_viewmodel__', '__weex_document__', '__weex_bootstrap__', '__weex_options__', '__weex_data__', '__weex_downgrade__', '__weex_require_module__', 'Vue', // fixed by xxxxxx uni-app '__uniConfig', '__uniRoutes', '__definePage', '__registerConfig', '__registerApp', '__registerPage', 'plus', 'uni', 'wx', 'getApp', 'getCurrentPages', 'UniServiceJSBridge' ] const bundlewrapper = 'function __weex_bundle_entry__(' + injectedGlobals.join(',') + '){' const rearRegexp = /\/\/#\s*sourceMappingURL(?!.*?\s+.)|$/ const match = /^\/\/\s?{\s?"framework"\s?:\s?"(\w+)"\s?}/.exec(code) let anno = '' if (match) { anno = '$$frameworkFlag["' + (sourceUrl || '@') + '"]="' + match[1] + '";' } const globalVars = [ 'WeexPlus', 'getUni', 'getApp', 'getCurrentPages', 'getUniEmitter' ] const globalVarsStr = globalVars.map(name => { return `var ${name} = typeof ${name} === 'undefined' ? services.${name} : ${name};` }).join('') const exportGlobalVars = [ 'plus', 'uni', 'wx', 'getApp', 'getCurrentPages' ] const exportGlobalVarsStr = exportGlobalVars.map(name => { return `this.${name} = typeof ${name} !== 'undefined' ? ${name} : {};` }).join('') return anno + bundlewrapper + globalVarsStr + exportGlobalVarsStr + code.replace(rearRegexp, '}\n$&') } const transformUrlToLocalUrl = sourceURl => { const rHttpHeader = /^(https?|taobao|qap):\/\/(?!.*your_current_ip)/i let bundleUrl if (rHttpHeader.test(sourceURl)) { const query = queryParser.parse(URL.parse(sourceURl).query) if (query['_wx_tpl']) { bundleUrl = util.normalize(query['_wx_tpl']).replace(rHttpHeader, '') } else { bundleUrl = util.normalize(sourceURl).replace(rHttpHeader, '') } } else { bundleUrl = sourceURl.replace( /^(https?|taobao|qap):\/\/(.*your_current_ip):(\d+)\//i, 'file://' ) } if (bundleUrl.charAt(bundleUrl.length - 1) === '?') { bundleUrl = bundleUrl.substring(0, bundleUrl.length - 1) } if (bundleUrl.charAt(bundleUrl.length - 1) === '?') { bundleUrl = bundleUrl.substring(0, bundleUrl.length - 1) } return '/source/' + bundleUrl } const eventConstructor = `// event constructor function __EventEmitter__() { this._handlers = {}; } __EventEmitter__.prototype = { constructor: __EventEmitter__, off: function (method, handler) { if (handler) { for (var i = 0; i < this._handlers[method].length; i++) { if (this._handlers[method][i] === handler) { this._handlers[method].splice(i, 1); i--; } } } else { this._handlers[method] = []; } }, once: function (method, handler) { var self = this; var fired = false; function g() { self.off(method, g); if (!fired) { fired = true; handler.apply(self, Array.prototype.slice.call(arguments)); } } this.on(method, g); }, on: function (method, handler) { if (this._handlers[method]) { this._handlers[method].push(handler); } else { this._handlers[method] = [handler]; } }, _emit: function (method, args, context) { var handlers = this._handlers[method]; if (handlers && handlers.length > 0) { handlers.forEach(function (handler) { handler.apply(context, args) }); return true; } else { return false; } }, emit: function (method) { var context = {}; var args = Array.prototype.slice.call(arguments, 1); if (!this._emit(method, args, context)) { this._emit('*', args, context) } this._emit('$finally', args, context); return context; } };` const mockContextApi = `// Redefine the JSFramework API var __syncRequest__ = function(data, channelId) { var request = new XMLHttpRequest(); request.open("POST", \`/syncCallNative/$\{channelId}\`, false); // "false" makes the request synchronous request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); request.send(JSON.stringify(data)); if (request.status === 200) { return JSON.parse(request.responseText); } else { return { error: request.responseText }; } }; self.callNativeModule = function () { var message = { method: 'WxDebug.syncCall', params: { method: 'callNativeModule', args: __protectedAragument__(arguments) } } var result = __syncRequest__(message, __channelId__); if (result && result.error) { self.console.error(result.error); // throw new Error(result.error); } else { return result && result.ret }; } self.callNativeComponent = function () { var args = Array.prototype.slice.call(arguments); for (var i = 0; i < args.length; i++) { if (!args[i]) { args[i] = '' } } var message = { method: 'WxDebug.syncCall', params: { method: 'callNativeComponent', args: args } } var result = __syncRequest__(message, __channelId__); if (result.error) { self.console.error(result.error); // throw new Error(result.error); } else { return result.ret; }; }; self.callNative = function (instance, tasks, callback) { for (var i = 0; i < tasks.length; i++) { var task = tasks[i]; if (task.method == 'addElement') { for (var key in task.args[1].style) { if (Number.isNaN(task.args[1].style[key])) { self.console.error('invalid value [NaN] for style [' + key + ']', task); } } } } var payload = { method: 'WxDebug.callNative', params: { instance: instance, tasks: tasks, callback: callback } }; __postData__(payload); }; self.callAddElement = function (instance, ref, dom, index, callback) { var payload = { method: 'WxDebug.callAddElement', params: { instance: instance, ref: ref, dom: dom, index: index, callback: callback } }; __postData__(payload); }; self.__updateComponentData = function (instance, componentId, data) { var payload = { method: 'WxDebug.callUpdateComponentData', params: { instance: instance, componentId: componentId + '', data: data } }; __postData__(payload); }; self.nativeLog = function (args) { self.console.log(args) };` const generateSandboxWorkerEntry = env => { const mockBrowserApi = `// Redefine navigator Object.defineProperty(navigator, 'appCodeName', { get: function() { return '${env.device.name}'; } }); Object.defineProperty(navigator, 'appName', { get: function() { return '${env.environment.WXEnvironment.appName}'; } }); Object.defineProperty(navigator, 'appVersion', { get: function() { return '${env.environment.WXEnvironment.appVersion}'; } }); Object.defineProperty(navigator, 'product', { get: function() { return '${env.device.name}'; } }); Object.defineProperty(navigator, 'platform', { get: function() { return '${env.device.platform}'; } }); Object.defineProperty(navigator, 'userAgent', { get: function() { return '${env.device.platform === 'android' ? 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36' : 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25'}'; } }); // Redefine console var __origConsole__ = this.console; var __rewriteLog__ = function () { var LEVELS = ['error', 'warn', 'info', 'log', 'debug']; var backupConsole = { error: __origConsole__.error, warn: __origConsole__.warn, info: __origConsole__.info, log: __origConsole__.log, debug: __origConsole__.debug }; function resetConsole() { self.console.error = backupConsole.error; self.console.warn = backupConsole.warn; self.console.info = backupConsole.info; self.console.log = backupConsole.log; self.console.debug = backupConsole.debug; self.console.time = __origConsole__.time; self.console.timeEnd = __origConsole__.timeEnd; } function noop() {} return function (logLevel) { resetConsole(); LEVELS.slice(LEVELS.indexOf(logLevel) + 1).forEach(function (level) { self.console[level] = noop; }) } }(); // Redefine timer var __cachedSetTimeout__ = this.setTimeout; Object.defineProperty(this, 'setTimeout', { get: function () { return __cachedSetTimeout__; }, set: function () {} }); var __cachedSetInterval__ = this.setInterval; Object.defineProperty(this, 'setInterval', { get: function () { return __cachedSetInterval__; }, set: function () {} }); var __cachedClearTimeout__ = this.clearTimeout; Object.defineProperty(this, 'clearTimeout', { get: function () { return __cachedClearTimeout__; }, set: function () {} }); var __cachedClearInterval__ = this.clearInterval; Object.defineProperty(this, 'clearInterval', { get: function () { return __cachedClearInterval__; }, set: function () {} }); // Redefine onmessage var __eventEmitter__ = new __EventEmitter__(); var __postmessage__ = self.postMessage self.addEventListener('message', function(message) { __eventEmitter__.emit(message.data && message.data.method, message.data); }, false); ` const worker = fse.readFileSync( path.join(__dirname, '../worker/sandbox_worker.js') ) const mockAndroidApi = env.isLayoutAndSandbox ? `self.callCreateBody = function (instance, domStr) { if (!domStr) return; var payload = { method: 'WxDebug.callCreateBody', params: { instance: instance, domStr: domStr } }; __postData__(payload); }; self.callUpdateFinish = function (instance, tasks, callback) { var payload = { method: 'WxDebug.callUpdateFinish', params: { instance: instance, tasks: tasks, callback: callback } }; __postData__(payload); }; self.callCreateFinish = function (instance) { var payload = { method: 'WxDebug.callCreateFinish', params: { instance: instance } }; __postData__(payload); } self.callRefreshFinish = function (instance, tasks, callback) { var payload = { method: 'WxDebug.callRefreshFinish', params: { instance: instance, tasks: tasks, callback: callback } }; __postData__(payload); } self.callUpdateAttrs = function (instance, ref, data) { var payload = { method: 'WxDebug.callUpdateAttrs', params: { instance: instance, ref: ref, data: data } }; __postData__(payload); } self.callUpdateStyle = function (instance, ref, data) { var payload = { method: 'WxDebug.callUpdateStyle', params: { instance: instance, ref: ref, data: data } }; __postData__(payload); } self.callRemoveElement = function (instance, ref) { var payload = { method: 'WxDebug.callRemoveElement', params: { instance: instance, ref: ref } }; __postData__(payload); } self.callMoveElement = function (instance, ref, parentRef, index_str) { var payload = { method: 'WxDebug.callMoveElement', params: { instance: instance, ref: ref, parentRef: parentRef, index_str: index_str } }; __postData__(payload);; } self.callAddEvent = function (instance, ref, event) { var payload = { method: 'WxDebug.callAddEvent', params: { instance: instance, ref: ref, event: event } }; __postData__(payload); } self.callRemoveEvent = function (instance, ref, event) { var payload = { method: 'WxDebug.callRemoveEvent', params: { instance: instance, ref: ref, event: event } }; __postData__(payload); } ` : '' let environment = `${eventConstructor} ${mockBrowserApi} ${mockContextApi} ${mockAndroidApi} ` if (env.jsframework) { environment += `importScripts('${env.jsframework}');\n` // environment += `importScripts('/lib/runtime/js-framework.js');\n` } return `${environment} ${worker} ` } const generateWorkerEntry = env => { const mockBrowserApi = `// Redefine navigator Object.defineProperty(navigator, 'appCodeName', { get: function() { return '${env.device.name}'; } }); Object.defineProperty(navigator, 'appName', { get: function() { return '${env.environment.WXEnvironment.appName}'; } }); Object.defineProperty(navigator, 'appVersion', { get: function() { return '${env.environment.WXEnvironment.appVersion}'; } }); Object.defineProperty(navigator, 'product', { get: function() { return '${env.device.name}'; } }); Object.defineProperty(navigator, 'platform', { get: function() { return '${env.device.platform}'; } }); Object.defineProperty(navigator, 'userAgent', { get: function() { return '${env.device.platform === 'android' ? 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36' : 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25'}'; } }); // Redefine console var __origConsole__ = this.console; var __rewriteLog__ = function () { var LEVELS = ['error', 'warn', 'info', 'log', 'debug']; var backupConsole = { error: __origConsole__.error, warn: __origConsole__.warn, info: __origConsole__.info, log: __origConsole__.log, debug: __origConsole__.debug }; function resetConsole() { self.console.error = backupConsole.error; self.console.warn = backupConsole.warn; self.console.info = backupConsole.info; self.console.log = backupConsole.log; self.console.debug = backupConsole.debug; self.console.time = __origConsole__.time; self.console.timeEnd = __origConsole__.timeEnd; } function noop() {} return function (logLevel) { resetConsole(); LEVELS.slice(LEVELS.indexOf(logLevel) + 1).forEach(function (level) { self.console[level] = noop; }) } }(); // Redefine timer var __cachedSetTimeout__ = this.setTimeout; Object.defineProperty(this, 'setTimeout', { get: function () { return __cachedSetTimeout__; }, set: function () {} }); var __cachedSetInterval__ = this.setInterval; Object.defineProperty(this, 'setInterval', { get: function () { return __cachedSetInterval__; }, set: function () {} }); var __cachedClearTimeout__ = this.clearTimeout; Object.defineProperty(this, 'clearTimeout', { get: function () { return __cachedClearTimeout__; }, set: function () {} }); var __cachedClearInterval__ = this.clearInterval; Object.defineProperty(this, 'clearInterval', { get: function () { return __cachedClearInterval__; }, set: function () {} }); // Redefine onmessage var __eventEmitter__ = new __EventEmitter__(); var __postmessage__ = self.postMessage self.addEventListener('message', function(message) { __eventEmitter__.emit(message.data && message.data.method, message.data); }, false); ` const worker = fse.readFileSync(path.join(__dirname, '../worker/worker.js')) const androidMockApi = env.isLayoutAndSandbox ? `self.callCreateBody = function (instance, domStr) { if (!domStr) return; var payload = { method: 'WxDebug.callCreateBody', params: { instance: instance, domStr: domStr } }; __postData__(payload); }; self.callUpdateFinish = function (instance, tasks, callback) { var payload = { method: 'WxDebug.callUpdateFinish', params: { instance: instance, tasks: tasks, callback: callback } }; __postData__(payload); }; self.callCreateFinish = function (instance) { var payload = { method: 'WxDebug.callCreateFinish', params: { instance: instance } }; __postData__(payload); } self.callRefreshFinish = function (instance, tasks, callback) { var payload = { method: 'WxDebug.callRefreshFinish', params: { instance: instance, tasks: tasks, callback: callback } }; __postData__(payload); } self.callUpdateAttrs = function (instance, ref, data) { var payload = { method: 'WxDebug.callUpdateAttrs', params: { instance: instance, ref: ref, data: data } }; __postData__(payload); } self.callUpdateStyle = function (instance, ref, data) { var payload = { method: 'WxDebug.callUpdateStyle', params: { instance: instance, ref: ref, data: data } }; __postData__(payload); } self.callRemoveElement = function (instance, ref) { var payload = { method: 'WxDebug.callRemoveElement', params: { instance: instance, ref: ref } }; __postData__(payload); } self.callMoveElement = function (instance, ref, parentRef, index_str) { var payload = { method: 'WxDebug.callMoveElement', params: { instance: instance, ref: ref, parentRef: parentRef, index_str: index_str } }; __postData__(payload);; } self.callAddEvent = function (instance, ref, event) { var payload = { method: 'WxDebug.callAddEvent', params: { instance: instance, ref: ref, event: event } }; __postData__(payload); } self.callRemoveEvent = function (instance, ref, event) { var payload = { method: 'WxDebug.callRemoveEvent', params: { instance: instance, ref: ref, event: event } }; __postData__(payload); }` : '' let environment = `${eventConstructor} ${mockBrowserApi} ${mockContextApi} ${androidMockApi} self.$$frameworkFlag = {}; ` if (env.jsframework) { environment += `importScripts('${env.jsframework}');\n` // environment += `importScripts('/lib/runtime/js-framework.js');\n` } if (env.importScripts && env.importScripts.length > 0) { env.importScripts.forEach(script => { environment += `importScripts('${script}');\n` }) } if (env.sourceUrl) { environment += `importScripts('${env.sourceUrl}');\n` } return ` ${environment} ${worker} ` } const pickDomain = str => { if (/file:\/\//.test(str)) { return str.replace('file://', '') } if (/http(s)?/.test(str)) { return URL.parse(str).hostname } } module.exports = { bundleWrapper, transformUrlToLocalUrl, generateSandboxWorkerEntry, generateWorkerEntry, pickDomain }