Skip to content

Commit

Permalink
feat(ios): move to JS based require implementation
Browse files Browse the repository at this point in the history
- retains native require but overrides during app startup to match Node.js/Android
- require logic is almost entirely in JS now
- expose some extra native methods to implement similarly to Android
- fix js core "binding" hack to work on ios in same way as android
  • Loading branch information
sgtcoolguy committed Jan 13, 2021
1 parent 12e8d24 commit 1e66008
Show file tree
Hide file tree
Showing 6 changed files with 1,607 additions and 728 deletions.
39 changes: 31 additions & 8 deletions common/Resources/ti.internal/extensions/binding.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@
* implementation. We then intercept require calls to handle requests for these modules
* and lazily load the file.
*/
import Module from './node/module';
const isAndroid = Ti.Platform.osname === 'android';
const isIOS = !isAndroid && (Ti.Platform.osname === 'iphone' || Ti.Platform.osname === 'ipad');

if (!isAndroid) {
// Hack in our own Module impl for iOS/Windows!
global.Module = Module;

if (!Module.main) {
const main = new Module('.', null, {});
main.filename = '/ti.main.js';
main.path = '/';
main.paths = main.nodeModulesPaths(main.path);
Module.cache[main.filename] = main;
main.loaded = true;
Module.main = main;
}

global.require = function (moduleId) {
return Module.main.require(moduleId);
};
}

/**
* Used by @function bindObjectToCoreModuleId
Expand Down Expand Up @@ -34,7 +56,7 @@ function isHijackableModuleId(path) {

// Hack require to point to this as a core module "binding"
const originalRequire = global.require;
// This works for iOS as-is, and also intercepts the call on Android for ti.main.js (the first file executed)
// This works for Windows as-is, and also intercepts the call on Android/iOS for ti.main.js (the first file executed)
global.require = function (moduleId) {

if (bindings.has(moduleId)) {
Expand All @@ -47,8 +69,8 @@ global.require = function (moduleId) {
return originalRequire(moduleId);
};

if (Ti.Platform.name === 'android') {
// ... but we still need to hack it when requiring from other files for Android
if (isAndroid || isIOS) {
// ... but we still need to hack it when requiring from other files for Android/iOS (due to module.js impl)
const originalModuleRequire = global.Module.prototype.require;
global.Module.prototype.require = function (path, context) {

Expand Down Expand Up @@ -108,8 +130,9 @@ export function redirect(moduleId, filepath) {
redirects.set(moduleId, filepath);
}

const binding = {
register,
redirect
};
global.binding = binding;
// FIXME: There's a collision here with global.binding declared in KrollBridge.m on iOS
if (!global.binding) {
global.binding = {};
}
global.binding.register = register;
global.binding.redirect = redirect;
113 changes: 113 additions & 0 deletions common/Resources/ti.internal/extensions/node/invoker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2011-Present by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/

/**
* Generates a wrapped invoker function for a specific API
* This lets us pass in context-specific data to a function
* defined in an API namespace (i.e. on a module)
*
* We use this for create methods, and other APIs that take
* a KrollInvocation object as their first argument in Java
*
* For example, an invoker for a "create" method might look
* something like this:
*
* function createView(sourceUrl, options) {
* var view = new View(options);
* view.sourceUrl = sourceUrl;
* return view;
* }
*
* And the corresponding invoker for app.js would look like:
*
* UI.createView = function() {
* return createView("app://app.js", arguments[0]);
* }
*
* wrapperAPI: The scope specific API (module) wrapper
* realAPI: The actual module implementation
* apiName: The top level API name of the root module
* invocationAPI: The actual API to generate an invoker for
* scopeVars: A map that is passed into each invoker
*/

function genInvoker(wrapperAPI, realAPI, apiName, invocationAPI, scopeVars) {
var namespace = invocationAPI.namespace;
var names = namespace.split('.');
var length = names.length;
if (namespace === apiName) {
length = 0;
}

var apiNamespace = wrapperAPI;

for (var j = 0, namesLen = length; j < namesLen; ++j) {
var name = names[j];
var api;

// Create a module wrapper only if it hasn't been wrapped already.
if (Object.prototype.hasOwnProperty.call(apiNamespace, name)) {
api = apiNamespace[name];

} else {
function SandboxAPI() {
// FIXME: Use non-deprecated way to get prototype!
var proto = this.__proto__; // eslint-disable-line no-proto
Object.defineProperty(this, '_events', {
get: function () {
return proto._events;
},
set: function (value) {
proto._events = value;
}
});
}
SandboxAPI.prototype = apiNamespace[name];

api = new SandboxAPI();
apiNamespace[name] = api;
}

apiNamespace = api;
realAPI = realAPI[name];
}

var delegate = realAPI[invocationAPI.api];

// These invokers form a call hierarchy so we need to
// provide a way back to the actual root Titanium / actual impl.
while (delegate.__delegate__) {
delegate = delegate.__delegate__;
}

apiNamespace[invocationAPI.api] = createInvoker(realAPI, delegate, scopeVars);
}
exports.genInvoker = genInvoker;

/**
* Creates and returns a single invoker function that wraps
* a delegate function, thisObj, and scopeVars
* @param {object} thisObj The `this` object to use when invoking the `delegate` function
* @param {function} delegate The function to wrap/delegate to under the hood
* @param {object} scopeVars The scope variables to splice into the arguments when calling the delegate
* @return {function}
*/
function createInvoker(thisObj, delegate, scopeVars) {
var urlInvoker = function invoker() { // eslint-disable-line func-style
var args = Array.prototype.slice.call(arguments);
args.splice(0, 0, invoker.__scopeVars__);

return delegate.apply(invoker.__thisObj__, args);
};

urlInvoker.__delegate__ = delegate;
urlInvoker.__thisObj__ = thisObj;
urlInvoker.__scopeVars__ = scopeVars;

return urlInvoker;
}
exports.createInvoker = createInvoker;
Loading

0 comments on commit 1e66008

Please sign in to comment.