From fa264d937477cb431da89a55bb045751b027afbc Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Wed, 2 Dec 2015 18:47:05 +0200 Subject: [PATCH 1/3] Make polymer-expressions/path-parser a simple CommonJS module. The weird module.exports code broke the webpack parser. --- js-libs/polymer-expressions/path-parser.js | 696 ++++++++++----------- 1 file changed, 340 insertions(+), 356 deletions(-) diff --git a/js-libs/polymer-expressions/path-parser.js b/js-libs/polymer-expressions/path-parser.js index a17d2b87a..dbcd32ba1 100644 --- a/js-libs/polymer-expressions/path-parser.js +++ b/js-libs/polymer-expressions/path-parser.js @@ -7,403 +7,387 @@ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ -(function (global) { - 'use strict'; +'use strict'; - function detectEval() { - // Don't test for eval if we're running in a Chrome App environment. - // We check for APIs set that only exist in a Chrome App context. - if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { - return false; - } - - // Firefox OS Apps do not allow eval. This feature detection is very hacky - // but even if some other platform adds support for this function this code - // will continue to work. - if (typeof navigator != 'undefined' && navigator.getDeviceStorage) { - return false; - } - - try { - var f = new Function('', 'return true;'); - return f(); - } catch (ex) { - return false; - } +function detectEval() { + // Don't test for eval if we're running in a Chrome App environment. + // We check for APIs set that only exist in a Chrome App context. + if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { + return false; } - var hasEval = detectEval(); - - function isIndex(s) { - return +s === s >>> 0 && s !== ''; + // Firefox OS Apps do not allow eval. This feature detection is very hacky + // but even if some other platform adds support for this function this code + // will continue to work. + if (typeof navigator != 'undefined' && navigator.getDeviceStorage) { + return false; } - function toNumber(s) { - return +s; + try { + var f = new Function('', 'return true;'); + return f(); + } catch (ex) { + return false; } +} - function isObject(obj) { - return obj === Object(obj); - } +var hasEval = detectEval(); - var numberIsNaN = global.Number.isNaN || function (value) { - return typeof value === 'number' && global.isNaN(value); - } +function isIndex(s) { + return +s === s >>> 0 && s !== ''; +} - function areSameValue(left, right) { - if (left === right) - return left !== 0 || 1 / left === 1 / right; - if (numberIsNaN(left) && numberIsNaN(right)) - return true; +function toNumber(s) { + return +s; +} - return left !== left && right !== right; - } +function isObject(obj) { + return obj === Object(obj); +} - var createObject = ('__proto__' in {}) ? - function (obj) { return obj; } : - function (obj) { - var proto = obj.__proto__; - if (!proto) - return obj; - var newObject = Object.create(proto); - Object.getOwnPropertyNames(obj).forEach(function (name) { - Object.defineProperty(newObject, name, - Object.getOwnPropertyDescriptor(obj, name)); - }); - return newObject; - }; +var numberIsNaN = Number.isNaN || function (value) { + return typeof value === 'number' && isNaN(value); +} - var identStart = '[\$_a-zA-Z]'; - var identPart = '[\$_a-zA-Z0-9]'; - var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$'); +function areSameValue(left, right) { + if (left === right) + return left !== 0 || 1 / left === 1 / right; + if (numberIsNaN(left) && numberIsNaN(right)) + return true; - function getPathCharType(char) { - if (char === undefined) - return 'eof'; + return left !== left && right !== right; +} - var code = char.charCodeAt(0); +var createObject = ('__proto__' in {}) ? + function (obj) { return obj; } : + function (obj) { + var proto = obj.__proto__; + if (!proto) + return obj; + var newObject = Object.create(proto); + Object.getOwnPropertyNames(obj).forEach(function (name) { + Object.defineProperty(newObject, name, + Object.getOwnPropertyDescriptor(obj, name)); + }); + return newObject; + }; - switch (code) { - case 0x5B: // [ - case 0x5D: // ] - case 0x2E: // . - case 0x22: // " - case 0x27: // ' - case 0x30: // 0 - return char; +var identStart = '[\$_a-zA-Z]'; +var identPart = '[\$_a-zA-Z0-9]'; +var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$'); - case 0x5F: // _ - case 0x24: // $ - return 'ident'; +function getPathCharType(char) { + if (char === undefined) + return 'eof'; - case 0x20: // Space - case 0x09: // Tab - case 0x0A: // Newline - case 0x0D: // Return - case 0xA0: // No-break space - case 0xFEFF: // Byte Order Mark - case 0x2028: // Line Separator - case 0x2029: // Paragraph Separator - return 'ws'; - } + var code = char.charCodeAt(0); - // a-z, A-Z - if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) + switch (code) { + case 0x5B: // [ + case 0x5D: // ] + case 0x2E: // . + case 0x22: // " + case 0x27: // ' + case 0x30: // 0 + return char; + + case 0x5F: // _ + case 0x24: // $ return 'ident'; - // 1-9 - if (0x31 <= code && code <= 0x39) - return 'number'; - - return 'else'; + case 0x20: // Space + case 0x09: // Tab + case 0x0A: // Newline + case 0x0D: // Return + case 0xA0: // No-break space + case 0xFEFF: // Byte Order Mark + case 0x2028: // Line Separator + case 0x2029: // Paragraph Separator + return 'ws'; } - var pathStateMachine = { - 'beforePath': { - 'ws': ['beforePath'], - 'ident': ['inIdent', 'append'], - '[': ['beforeElement'], - 'eof': ['afterPath'] - }, + // a-z, A-Z + if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) + return 'ident'; - 'inPath': { - 'ws': ['inPath'], - '.': ['beforeIdent'], - '[': ['beforeElement'], - 'eof': ['afterPath'] - }, + // 1-9 + if (0x31 <= code && code <= 0x39) + return 'number'; - 'beforeIdent': { - 'ws': ['beforeIdent'], - 'ident': ['inIdent', 'append'] - }, + return 'else'; +} - 'inIdent': { - 'ident': ['inIdent', 'append'], - '0': ['inIdent', 'append'], - 'number': ['inIdent', 'append'], - 'ws': ['inPath', 'push'], - '.': ['beforeIdent', 'push'], - '[': ['beforeElement', 'push'], - 'eof': ['afterPath', 'push'] - }, +var pathStateMachine = { + 'beforePath': { + 'ws': ['beforePath'], + 'ident': ['inIdent', 'append'], + '[': ['beforeElement'], + 'eof': ['afterPath'] + }, - 'beforeElement': { - 'ws': ['beforeElement'], - '0': ['afterZero', 'append'], - 'number': ['inIndex', 'append'], - "'": ['inSingleQuote', 'append', ''], - '"': ['inDoubleQuote', 'append', ''] - }, + 'inPath': { + 'ws': ['inPath'], + '.': ['beforeIdent'], + '[': ['beforeElement'], + 'eof': ['afterPath'] + }, - 'afterZero': { - 'ws': ['afterElement', 'push'], - ']': ['inPath', 'push'] - }, + 'beforeIdent': { + 'ws': ['beforeIdent'], + 'ident': ['inIdent', 'append'] + }, - 'inIndex': { - '0': ['inIndex', 'append'], - 'number': ['inIndex', 'append'], - 'ws': ['afterElement'], - ']': ['inPath', 'push'] - }, + 'inIdent': { + 'ident': ['inIdent', 'append'], + '0': ['inIdent', 'append'], + 'number': ['inIdent', 'append'], + 'ws': ['inPath', 'push'], + '.': ['beforeIdent', 'push'], + '[': ['beforeElement', 'push'], + 'eof': ['afterPath', 'push'] + }, - 'inSingleQuote': { - "'": ['afterElement'], - 'eof': ['error'], - 'else': ['inSingleQuote', 'append'] - }, + 'beforeElement': { + 'ws': ['beforeElement'], + '0': ['afterZero', 'append'], + 'number': ['inIndex', 'append'], + "'": ['inSingleQuote', 'append', ''], + '"': ['inDoubleQuote', 'append', ''] + }, - 'inDoubleQuote': { - '"': ['afterElement'], - 'eof': ['error'], - 'else': ['inDoubleQuote', 'append'] - }, + 'afterZero': { + 'ws': ['afterElement', 'push'], + ']': ['inPath', 'push'] + }, - 'afterElement': { - 'ws': ['afterElement'], - ']': ['inPath', 'push'] - } + 'inIndex': { + '0': ['inIndex', 'append'], + 'number': ['inIndex', 'append'], + 'ws': ['afterElement'], + ']': ['inPath', 'push'] + }, + + 'inSingleQuote': { + "'": ['afterElement'], + 'eof': ['error'], + 'else': ['inSingleQuote', 'append'] + }, + + 'inDoubleQuote': { + '"': ['afterElement'], + 'eof': ['error'], + 'else': ['inDoubleQuote', 'append'] + }, + + 'afterElement': { + 'ws': ['afterElement'], + ']': ['inPath', 'push'] } +} - function noop() { } +function noop() { } - function parsePath(path) { - var keys = []; - var index = -1; - var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath'; +function parsePath(path) { + var keys = []; + var index = -1; + var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath'; - var actions = { - push: function () { - if (key === undefined) - return; - - keys.push(key); - key = undefined; - }, - - append: function () { - if (key === undefined) - key = newChar - else - key += newChar; - } - }; - - function maybeUnescapeQuote() { - if (index >= path.length) + var actions = { + push: function () { + if (key === undefined) return; - var nextChar = path[index + 1]; - if ((mode == 'inSingleQuote' && nextChar == "'") || - (mode == 'inDoubleQuote' && nextChar == '"')) { - index++; - newChar = nextChar; - actions.append(); - return true; - } - } + keys.push(key); + key = undefined; + }, - while (mode) { + append: function () { + if (key === undefined) + key = newChar + else + key += newChar; + } + }; + + function maybeUnescapeQuote() { + if (index >= path.length) + return; + + var nextChar = path[index + 1]; + if ((mode == 'inSingleQuote' && nextChar == "'") || + (mode == 'inDoubleQuote' && nextChar == '"')) { index++; - c = path[index]; - - if (c == '\\' && maybeUnescapeQuote(mode)) - continue; - - type = getPathCharType(c); - typeMap = pathStateMachine[mode]; - transition = typeMap[type] || typeMap['else'] || 'error'; - - if (transition == 'error') - return; // parse error; - - mode = transition[0]; - action = actions[transition[1]] || noop; - newChar = transition[2] === undefined ? c : transition[2]; - action(); - - if (mode === 'afterPath') { - return keys; - } - } - - return; // parse error - } - - function isIdent(s) { - return identRegExp.test(s); - } - - var constructorIsPrivate = {}; - - function Path(parts, privateToken) { - if (privateToken !== constructorIsPrivate) - throw Error('Use Path.get to retrieve path objects'); - - for (var i = 0; i < parts.length; i++) { - this.push(String(parts[i])); - } - - if (hasEval && this.length) { - this.getValueFrom = this.compiledGetValueFromFn(); - } - } - - // TODO(rafaelw): Make simple LRU cache - var pathCache = {}; - - function getPath(pathString) { - if (pathString instanceof Path) - return pathString; - - if (pathString == null || pathString.length == 0) - pathString = ''; - - if (typeof pathString != 'string') { - if (isIndex(pathString.length)) { - // Constructed with array-like (pre-parsed) keys - return new Path(pathString, constructorIsPrivate); - } - - pathString = String(pathString); - } - - var path = pathCache[pathString]; - if (path) - return path; - - var parts = parsePath(pathString); - if (!parts) - return invalidPath; - - var path = new Path(parts, constructorIsPrivate); - pathCache[pathString] = path; - return path; - } - - Path.get = getPath; - - function formatAccessor(key) { - if (isIndex(key)) { - return '[' + key + ']'; - } else { - return '["' + key.replace(/"/g, '\\"') + '"]'; - } - } - - Path.prototype = createObject({ - __proto__: [], - valid: true, - - toString: function () { - var pathString = ''; - for (var i = 0; i < this.length; i++) { - var key = this[i]; - if (isIdent(key)) { - pathString += i ? '.' + key : key; - } else { - pathString += formatAccessor(key); - } - } - - return pathString; - }, - - getValueFrom: function (obj, directObserver) { - for (var i = 0; i < this.length; i++) { - if (obj == null) - return; - obj = obj[this[i]]; - } - return obj; - }, - - iterateObjects: function (obj, observe) { - for (var i = 0; i < this.length; i++) { - if (i) - obj = obj[this[i - 1]]; - if (!isObject(obj)) - return; - observe(obj, this[i]); - } - }, - - compiledGetValueFromFn: function () { - var str = ''; - var pathString = 'obj'; - str += 'if (obj != null'; - var i = 0; - var key; - for (; i < (this.length - 1) ; i++) { - key = this[i]; - pathString += isIdent(key) ? '.' + key : formatAccessor(key); - str += ' &&\n ' + pathString + ' != null'; - } - str += ')\n'; - - var key = this[i]; - pathString += isIdent(key) ? '.' + key : formatAccessor(key); - - str += ' return ' + pathString + ';\nelse\n return undefined;'; - return new Function('obj', str); - }, - - setValueFrom: function (obj, value) { - if (!this.length) - return false; - - for (var i = 0; i < this.length - 1; i++) { - if (!isObject(obj)) - return false; - obj = obj[this[i]]; - } - - if (!isObject(obj)) - return false; - - obj[this[i]] = value; + newChar = nextChar; + actions.append(); return true; } - }); - - var invalidPath = new Path('', constructorIsPrivate); - invalidPath.valid = false; - invalidPath.getValueFrom = invalidPath.setValueFrom = function () { }; - - // Export the observe-js object for **Node.js**, with - // backwards-compatibility for the old `require()` API. If we're in - // the browser, export as a global object. - - var expose = global; - - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - expose = exports = module.exports; - } - expose = exports; } - expose.Path = Path; + while (mode) { + index++; + c = path[index]; -})(typeof global !== 'undefined' && global && typeof module !== 'undefined' && module ? global : this || window); \ No newline at end of file + if (c == '\\' && maybeUnescapeQuote(mode)) + continue; + + type = getPathCharType(c); + typeMap = pathStateMachine[mode]; + transition = typeMap[type] || typeMap['else'] || 'error'; + + if (transition == 'error') + return; // parse error; + + mode = transition[0]; + action = actions[transition[1]] || noop; + newChar = transition[2] === undefined ? c : transition[2]; + action(); + + if (mode === 'afterPath') { + return keys; + } + } + + return; // parse error +} + +function isIdent(s) { + return identRegExp.test(s); +} + +var constructorIsPrivate = {}; + +function Path(parts, privateToken) { + if (privateToken !== constructorIsPrivate) + throw Error('Use Path.get to retrieve path objects'); + + for (var i = 0; i < parts.length; i++) { + this.push(String(parts[i])); + } + + if (hasEval && this.length) { + this.getValueFrom = this.compiledGetValueFromFn(); + } +} + +// TODO(rafaelw): Make simple LRU cache +var pathCache = {}; + +function getPath(pathString) { + if (pathString instanceof Path) + return pathString; + + if (pathString == null || pathString.length == 0) + pathString = ''; + + if (typeof pathString != 'string') { + if (isIndex(pathString.length)) { + // Constructed with array-like (pre-parsed) keys + return new Path(pathString, constructorIsPrivate); + } + + pathString = String(pathString); + } + + var path = pathCache[pathString]; + if (path) + return path; + + var parts = parsePath(pathString); + if (!parts) + return invalidPath; + + var path = new Path(parts, constructorIsPrivate); + pathCache[pathString] = path; + return path; +} + +Path.get = getPath; + +function formatAccessor(key) { + if (isIndex(key)) { + return '[' + key + ']'; + } else { + return '["' + key.replace(/"/g, '\\"') + '"]'; + } +} + +Path.prototype = createObject({ + __proto__: [], + valid: true, + + toString: function () { + var pathString = ''; + for (var i = 0; i < this.length; i++) { + var key = this[i]; + if (isIdent(key)) { + pathString += i ? '.' + key : key; + } else { + pathString += formatAccessor(key); + } + } + + return pathString; + }, + + getValueFrom: function (obj, directObserver) { + for (var i = 0; i < this.length; i++) { + if (obj == null) + return; + obj = obj[this[i]]; + } + return obj; + }, + + iterateObjects: function (obj, observe) { + for (var i = 0; i < this.length; i++) { + if (i) + obj = obj[this[i - 1]]; + if (!isObject(obj)) + return; + observe(obj, this[i]); + } + }, + + compiledGetValueFromFn: function () { + var str = ''; + var pathString = 'obj'; + str += 'if (obj != null'; + var i = 0; + var key; + for (; i < (this.length - 1) ; i++) { + key = this[i]; + pathString += isIdent(key) ? '.' + key : formatAccessor(key); + str += ' &&\n ' + pathString + ' != null'; + } + str += ')\n'; + + var key = this[i]; + pathString += isIdent(key) ? '.' + key : formatAccessor(key); + + str += ' return ' + pathString + ';\nelse\n return undefined;'; + return new Function('obj', str); + }, + + setValueFrom: function (obj, value) { + if (!this.length) + return false; + + for (var i = 0; i < this.length - 1; i++) { + if (!isObject(obj)) + return false; + obj = obj[this[i]]; + } + + if (!isObject(obj)) + return false; + + obj[this[i]] = value; + return true; + } +}); + +var invalidPath = new Path('', constructorIsPrivate); +invalidPath.valid = false; +invalidPath.getValueFrom = invalidPath.setValueFrom = function () { }; + +exports.Path = Path; From 6a37218ace6758b39949c3c44a5e3bcb90777cef Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Mon, 14 Dec 2015 12:19:17 +0200 Subject: [PATCH 2/3] Make knownFolders.currentApp default to __dirname. In cases where we can't find a `tns_modules` subfolder (webpack, etc), assume the current dir is the app dir. --- file-system/file-system.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/file-system/file-system.ts b/file-system/file-system.ts index f9ebd41b6..f1f27fb60 100644 --- a/file-system/file-system.ts +++ b/file-system/file-system.ts @@ -442,10 +442,17 @@ export module knownFolders { export var currentApp = function (): Folder { if (!_app) { - var currentDir = __dirname; - var path = currentDir.substring(0, currentDir.indexOf("/tns_modules")); + const currentDir = __dirname; + const tnsModulesIndex = currentDir.indexOf("/tns_modules"); + + // Module not hosted in ~/tns_modules when bundled. Use current dir. + let appPath = currentDir; + if (tnsModulesIndex !== -1) { + // Strip part after tns_modules to obtain app root + appPath = currentDir.substring(0, tnsModulesIndex); + } _app = new Folder(); - _app[pathProperty] = path; + _app[pathProperty] = appPath; _app[isKnownProperty] = true; } @@ -465,4 +472,4 @@ export module path { } export var separator = getFileAccess().getPathSeparator(); -} \ No newline at end of file +} From 648e4d216d9bb3a0ca1fd20c84fe48ed9dafc7d3 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Mon, 14 Dec 2015 14:41:16 +0200 Subject: [PATCH 3/3] Add utils/module-loader - a mechanism to resolve modules at runtime. Exposes a module registration API, that webpack users can use to bundle "dynamic" modules that are required through some variable. Falls back to the require API if no module is registered. Reworked dynamic `require` code to use module-loader: - navigation: ui/frame. - XML UI build ui/builder/builder and ui/builder/component-builder. - module on-demand loads in global functions: globals. --- globals/globals.ts | 27 ++- ui/builder/builder.ts | 281 ++++++++++++++++---------------- ui/builder/component-builder.ts | 74 ++++----- ui/frame/frame-common.ts | 91 ++++++----- 4 files changed, 250 insertions(+), 223 deletions(-) diff --git a/globals/globals.ts b/globals/globals.ts index c6006f3e6..0af8f290a 100644 --- a/globals/globals.ts +++ b/globals/globals.ts @@ -6,11 +6,36 @@ global.moduleMerge = function (sourceExports: any, destExports: any) { } } +type ModuleLoader = () => any; +const modules: Map = new Map(); + +global.registerModule = function(name: string, loader: ModuleLoader): void { + modules.set(name, loader); +} + +global.moduleExists = function(name: string): boolean { + return modules.has(name); +} + +global.loadModule = function(name: string): any { + const loader = modules.get(name); + if (loader) { + return loader(); + } else { + return require(name); + } +} + +global.registerModule("timer", () => require("timer")); +global.registerModule("ui/dialogs", () => require("ui/dialogs")); +global.registerModule("../xhr/xhr", () => require("../xhr/xhr")); +global.registerModule("fetch", () => require("fetch")); + function registerOnGlobalContext(name, module) { Object.defineProperty(global, name, { get: function () { // We do not need to cache require() call since it is already cached in the runtime. - let m = require(module); + let m = global.loadModule(module); global.moduleMerge(m, global); // Redefine the property to make sure the above code is executed only once. diff --git a/ui/builder/builder.ts b/ui/builder/builder.ts index 9c0dec3be..573ec2d20 100644 --- a/ui/builder/builder.ts +++ b/ui/builder/builder.ts @@ -1,43 +1,41 @@ -import view = require("ui/core/view"); -import fs = require("file-system"); -import xml = require("xml"); -import types = require("utils/types"); -import componentBuilder = require("ui/builder/component-builder"); -import platform = require("platform"); -import definition = require("ui/builder"); -import page = require("ui/page"); -import fileResolverModule = require("file-system/file-name-resolver"); -import trace = require("trace"); -import debug = require("utils/debug"); -import builder = require("ui/builder"); +import * as trace from "trace"; +import {debug, ScopeError, SourceError, Source} from "utils/debug"; +import * as xml from "xml"; +import {View, Template} from "ui/core/view"; +import {File, path, knownFolders} from "file-system"; +import {isString, isFunction, isDefined} from "utils/types"; +import {ComponentModule, setPropertyValue, getComponentModule} from "ui/builder/component-builder"; +import {platformNames, device} from "platform"; +import {LoadOptions} from "ui/builder"; +import {Page} from "ui/page"; +import {resolveFileName} from "file-system/file-name-resolver"; -export function parse(value: string | view.Template, context: any): view.View { - if (types.isString(value)) { - var viewToReturn: view.View; - - if (context instanceof view.View) { +export function parse(value: string | Template, context: any): View { + if (isString(value)) { + var viewToReturn: View; + + if (context instanceof View) { context = getExports(context); } - + var componentModule = parseInternal(value, context); - + if (componentModule) { viewToReturn = componentModule.component; } - + return viewToReturn; - } else if (types.isFunction(value)) { - return (value)(); + } else if (isFunction(value)) { + return (