Skip to content

Commit

Permalink
Merge pull request #351 from jquick-axway/TIMOB-28461
Browse files Browse the repository at this point in the history
feat(ios): support LiveView
  • Loading branch information
jquick-axway authored Jun 17, 2021
2 parents f98983d + 9ed66b7 commit 577e63f
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 250 deletions.
2 changes: 2 additions & 0 deletions iphone/Resources/hyperloop/hyperloop.bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// This file will be overwritten by hyperloop build hook if at least 1 native class was detected in app.
// Will provide require/import bindings between native type name to hyperloop generated JS file.
35 changes: 35 additions & 0 deletions iphone/hooks/generate/code-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class CodeGenerator {
this.generateStructs(outputPath);
this.generateModules(outputPath);
this.generateCustoms(outputPath);
this.generateBootstrap(outputPath);
}

/**
Expand Down Expand Up @@ -233,6 +234,40 @@ class CodeGenerator {
util.generateFile(outputPath, 'custom', {framework:'Hyperloop', name:'Custom'}, code, '.m');
}

/**
* Generate a hyperloop bootstrap script to be loaded on app startup, but before the "app.js" gets loaded.
* Provides JS require/import alias names matching native class names to their equivalent JS files.
* @param {String} outputPath Path of directory to write bootstrap file to.
*/
generateBootstrap(outputPath) {
const fileLines = [];
const fetchBindingsFrom = (sourceTypes) => {
if (!sourceTypes) {
return;
}
const isModule = (sourceTypes == this.sourceSet.modules);
for (const typeName in sourceTypes) {
const frameworkName = sourceTypes[typeName].framework;
if (!frameworkName) {
continue;
}
const requireName = `/hyperloop/${frameworkName.toLowerCase()}/${typeName.toLowerCase()}`;
if (frameworkName !== typeName) {
fileLines.push(`binding.redirect('${frameworkName}/${typeName}', '${requireName}');`);
}
if (isModule) {
fileLines.push(`binding.redirect('${frameworkName}', '${requireName}');`);
}
}
};
fetchBindingsFrom(this.sourceSet.classes);
fetchBindingsFrom(this.sourceSet.structs);
fetchBindingsFrom(this.sourceSet.modules);

const filePath = path.join(outputPath, 'hyperloop.bootstrap.js');
fs.writeFileSync(filePath, fileLines.join('\n') + '\n');
}

/**
* Checks if a module needs a native wrapper file generated.
*
Expand Down
84 changes: 0 additions & 84 deletions iphone/hooks/generate/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
* Copyright (c) 2015-2018 by Appcelerator, Inc.
*/
'use strict';
const fs = require('fs');
const path = require('path');
const util = require('util');
const utillib = require('./util');
const classgen = require('./class');
const babelParser = require('@babel/parser');
Expand Down Expand Up @@ -479,84 +476,6 @@ function addSymbolReference (state, node, key) {
}
}

/**
* Checks for methods that require refactoring and adds them to a list we
* use later on to show migration instructions.
*
* This can be removed with Hyperloop 3.0 or propably even earlier.
*
* @param {Object} state Parser state object
* @param {Object} node Node in the AST to inspect
*/
function addMigrationHelpIfNeeded(state, node) {
state.needMigration = state.needMigration || [];
var migrationTable = utillib.getMethodTableForMigration();

if (['CallExpression', 'MemberExpression'].indexOf(node.type) === -1) {
return;
}

var migratableMethod = traverseUpAndFindMigratableMethod(node, migrationTable);
if (migratableMethod !== null) {
var entryExists = state.needMigration.some(function (m) {
return m.label === migratableMethod.label && m.line === migratableMethod.line;
});
if (!entryExists) {
state.needMigration.push(migratableMethod);
}
}
}

/**
* Traverse up in the AST to find all possible method calls that may require
* a migration note.
*
* Only handles nested Call- and MemberExpressions so we can detect stuff
* like this:
*
* var path1 = UIBundle.mainBundle().bundlePath
* var path2 = UIBundle.mainBundle().pathForImageResource()
*
* @param {Object} node Node in the AST to inspect
* @param {Object} migrationTable Object with mapping of class name and methods that need migration
* @return {Object|null} Object with info about matching call expression or null if none found
*/
function traverseUpAndFindMigratableMethod(node, migrationTable) {
if (!node) {
return null;
}

if (['CallExpression', 'MemberExpression'].indexOf(node.type) === -1) {
return null;
}

if (node.type === 'MemberExpression') {
return traverseUpAndFindMigratableMethod(node.object, migrationTable);
}

var callee = node.callee;
if (callee.type !== 'MemberExpression') {
return null;
}

if (callee.object.type !== 'Identifier') {
return traverseUpAndFindMigratableMethod(callee.object, migrationTable);
}

var objectName = callee.object.name;
var methods = migrationTable.hasOwnProperty(objectName) ? migrationTable[objectName] : [];
var methodName = callee.property.name;
if (methods.indexOf(methodName) === -1) {
return null;
}

return {
objectName: objectName,
methodName: methodName,
line: node.loc.start.line
};
}

/**
* parse a buf of JS into a state object
*/
Expand All @@ -577,7 +496,6 @@ Parser.prototype.parse = function (buf, fn, state) {
// reset these per module
state.classesByVariable = {};
state.referencedClasses = {};
state.needMigration = [];

// these are symbol references found in our source code.
// this is a little brute force and sloppy but gets
Expand Down Expand Up @@ -616,7 +534,6 @@ Parser.prototype.parse = function (buf, fn, state) {
const prop = p.node.callee.name;
isValidSymbol(prop) && (state.References.functions[prop] = (state.References.functions[prop] || 0) + 1);
}
addMigrationHelpIfNeeded(state, p.node);

if (isHyperloopMethodCall(p.node, 'defineClass')) {
if (p.parent.type !== 'VariableDeclaration' && p.parent.type !== 'VariableDeclarator') {
Expand Down Expand Up @@ -646,7 +563,6 @@ Parser.prototype.parse = function (buf, fn, state) {
MemberExpression: function(p) {
if (!/^(AssignmentExpression|CallExpression|ExpressionStatement|VariableDeclaration)$/.test(p.parent.type)) {
addSymbolReference(state, p.node, 'getter');
addMigrationHelpIfNeeded(state, p.node);
}
},
AssignmentExpression: function(p) {
Expand Down
4 changes: 2 additions & 2 deletions iphone/hooks/generate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ function generateFromJSON(name, json, state, callback, includes) {

processProtocolInheritance(json.protocols);

var modules = {};
var sourceSet = {
classes: {},
structs: {},
Expand Down Expand Up @@ -242,6 +243,7 @@ function generateFromJSON(name, json, state, callback, includes) {
});
}
sourceSet.classes[k] = genclass.generate(json, cls, state);
makeModule(modules, cls, state);
});

// structs
Expand All @@ -254,8 +256,6 @@ function generateFromJSON(name, json, state, callback, includes) {
sourceSet.structs[k] = genstruct.generate(json, struct);
});

// modules
var modules = {};
// define module based functions
json.functions && Object.keys(json.functions).forEach(function (k) {
var func = json.functions[k];
Expand Down
19 changes: 16 additions & 3 deletions iphone/hooks/generate/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
*/
'use strict';
const util = require('./util');
const path = require('path');
const fs = require('fs');

function makeModule(json, module, state) {
var entry = {
Expand All @@ -15,7 +13,8 @@ function makeModule(json, module, state) {
class_methods: [],
obj_class_method: [],
static_variables: {},
blocks: module.blocks
blocks: module.blocks,
nested_types: {}
},
framework: module.framework,
filename: module.filename,
Expand Down Expand Up @@ -58,6 +57,20 @@ function makeModule(json, module, state) {
}
});

// Make framework's classes and structs available via the JS module's properties.
const copyNestedTypes = (sourceTypes) => {
if (sourceTypes) {
for (const typeName in sourceTypes) {
const typeInfo = sourceTypes[typeName];
if ((typeInfo.framework === module.framework) && (typeName !== module.name)) {
entry.class.nested_types[typeName] = typeInfo;
}
}
}
};
copyNestedTypes(json.classes);
copyNestedTypes(json.structs);

entry.renderedImports = util.makeImports(json, entry.imports);
return entry;
}
Expand Down
6 changes: 6 additions & 0 deletions iphone/hooks/generate/templates/class.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,10 @@ Object.setPrototypeOf = Object.setPrototypeOf || function(obj, proto) {
return obj;
}
Object.defineProperty(<%= data.class.name %>, '<%= data.class.name %>', {
value: <%= data.class.name %>,
enumerable: false,
writable: false
});
module.exports = <%= data.class.name %>;
21 changes: 21 additions & 0 deletions iphone/hooks/generate/templates/module.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,27 @@ keys.forEach(function (k, index) { %>
});
<% } -%>

<% if (data.class.nested_types && Object.keys(data.class.nested_types).length) { -%>
// framework classes and structs
Object.defineProperties(<%= data.class.name %>, {
<% var keys = Object.keys(data.class.nested_types);
keys.forEach(function (nestedTypeName, index) { %>
<%=nestedTypeName%>: {
get: function() {
return require('/hyperloop/<%= data.framework.toLowerCase() %>/<%= nestedTypeName.toLowerCase() %>');
},
enumerable: true
}<%=index + 1 < keys.length ? ',':''%>
<% }) %>
});
<% } -%>

<% if (!data.excludeHeader) { -%>
Object.defineProperty(<%= data.class.name %>, '<%= data.class.name %>', {
value: <%= data.class.name %>,
enumerable: false,
writable: false
});
module.exports = <%= data.class.name %>;
<% } -%>
6 changes: 6 additions & 0 deletions iphone/hooks/generate/templates/struct.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,10 @@ function $initialize () {
$init = true;
}
Object.defineProperty(<%= data.class.name %>, '<%= data.class.name %>', {
value: <%= data.class.name %>,
enumerable: false,
writable: false
});

module.exports = <%= data.class.name %>;
36 changes: 0 additions & 36 deletions iphone/hooks/generate/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -1267,41 +1267,6 @@ function resolveArg (metabase, imports, arg) {
}
}

/**
* Gets a mapping of all classes and their properties that are affected by
* the UIKIT_DEFINE_AS_PROPERTIES or FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST
* macros.
*
* UIKIT_DEFINE_AS_PROPERTIES and FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST introduce
* new readonly properties in favor of methods with the same name. This changes
* how one would access them in Hyperloop.
*
* For example:
*
* // < Hyperloop 2.0.0, as method
* var color = UIColor.redColor();
* // >= Hyperloop 2.0.0, as property (note the missing parenthesis)
* var color = UIColor.redColor;
*
* @return {Object} Contains a mapping of class names and their affected properties
*/
function getMethodTableForMigration() {
var migrationFilename = 'migration-20161014143619.json';

if (getMethodTableForMigration.cachedTable) {
return getMethodTableForMigration.cachedTable;
}

var migrationPathAndFilename = path.resolve(__dirname, path.join('../../data', migrationFilename));
if (fs.existsSync(migrationPathAndFilename)) {
getMethodTableForMigration.cachedTable = JSON.parse(fs.readFileSync(migrationPathAndFilename).toString());
} else {
getMethodTableForMigration.cachedTable = {};
}

return getMethodTableForMigration.cachedTable;
}

exports.repeat = repeat;
exports.generateTemplate = generateTemplate;
exports.makeImports = makeImports;
Expand All @@ -1327,7 +1292,6 @@ exports.camelCase = camelCase;
exports.resolveArg = resolveArg;
exports.toValueDefault = toValueDefault;
exports.isPrimitive = isPrimitive;
exports.getMethodTableForMigration = getMethodTableForMigration;

Object.defineProperty(exports, 'logger', {
get: function () {
Expand Down
Loading

0 comments on commit 577e63f

Please sign in to comment.