UNPKG

micro-app

Version:

(<5kb) [📱iOS] Create Progressive Web App Dynamically.

542 lines (483 loc) 17.1 kB
/*! * @ProjectName micro-app * @Version 1.0.2 * @Author lixinliang(https://github.com/lixinliang) * @Update 2016-12-07 9:11:44 am */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["microApp"] = factory(); else root["microApp"] = factory(); })(this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; /** * @webpack * @library microApp * @libraryTarget umd */ var doc = document; var head = doc.head; var define = Object.defineProperty.bind(Object); var userAgent = navigator.userAgent; /** * The platform is ios or not * @type {Boolean} ios */ var ios = /\(i[^;]+;( U;)? CPU.+Mac OS X/i.test(userAgent); /** * The device is mobile(iPhone and iPod) or not * @type {Boolean} mobile */ var mobile = !/iPad/i.test(userAgent); /** * The browser is safari or not * @type {Boolean} safari */ var safari = /\bversion\/([0-9.]+(?: beta)?)(?: mobile(?:\/[a-z0-9]+)?)? safari\//i.test(userAgent); /** * The major version of os * @type {Number} os */ var os = parseInt((userAgent.match(/\bcpu(?: iphone)? os /i.test(userAgent) ? /\bcpu(?: iphone)? os ([0-9._]+)/i : /\biph os ([0-9_]+)/i) || [, 0])[1]); /** * Append the elemment * @param {Element} elem target element * @return {Element} elem target element */ var append = function append(elem) { head.appendChild(elem); return elem; }; /** * Define filter * @param {String} name filter name * @param {Function} method filter method * @return {Object} this this */ function filter(name, method) { if (typeof name != 'string' || typeof method != 'function') { throw new TypeError('[micro-app] microApp.filter( name : String, method : Function );'); } // let character = name.match(/[\||\(|\)]/); var character = name.match(/\|/); if (character) { throw new TypeError('[micro-app] "' + character[0] + '" is not allowed.'); } filter[name] = method; return this; } /** * Remove the elemment * @param {Element} elem target element */ var remove = function remove(elem) { head.removeChild(elem); }; var icon = [ // pad [ // os <= 6 [ // low dpi '72x72', // high dpi '144x144'], // os >= 7 [ // low dpi '76x76', // high dpi '152x152']], // phone or pod [ // os <= 6 [ // low dpi '57x57', // high dpi '114x114'], // os >= 7 [ // low dpi '60x60', // high dpi '120x120']]]; var splash = {}; var width = 'device-width'; var height = 'device-height'; if (mobile) { splash[width] = 320; splash[height] = 480; [{ width: 320, height: 568 }, { width: 375, height: 667 }, { width: 414, height: 736 }].forEach(function (type) { if (matchMedia('(' + width + ':' + type.width + 'px)and(' + height + ':' + type.height + 'px)').matches) { splash[width] = type.width; splash[height] = type.height; } }); splash[width] += 'px'; splash[height] += 'px'; } else { splash[width] = '768px'; splash[height] = '1024px'; } splash['-webkit-device-pixel-ratio'] = devicePixelRatio; /** * Get the autosize config * @param {String} type icon/splash * @return {Object} config config */ var autosize = function autosize(type) { if (type == 'icon') { var sizes = icon[+mobile][+(os > 6)][+(devicePixelRatio > 1)]; return { sizes: sizes }; } if (type == 'splash') { var result = []; for (var rule in splash) { result.push('(' + rule + ':' + splash[rule] + ')'); } if (!mobile || devicePixelRatio == 3) { var _rule = '(orientation:landscape)'; if (matchMedia(_rule).matches) { result.push(_rule); } else { result.push('(orientation:portrait)'); } } var media = result.join('and'); return { media: media }; } }; var container = doc.createElement('div'); /** * Create an element * @param {String} html html code * @return {Element} elem result element */ var createElement = function createElement(html) { container.innerHTML = html; return container.firstElementChild; }; var microApp = doc.querySelector('script[micro-app]') || append(createElement('<script micro-app>')); /** * Define static property * @param {Object} target target object * @param {String} name property name * @param {AnyType} value property value */ var defineProperty = function defineProperty(target, name, value) { return define(target, name, { value: value, writable: false, enumerable: false, configurable: false }); }; var supportConfigurable = Object.getOwnPropertyDescriptor(function () {}, 'name').configurable; /** * Override a function on microApp * @param {String} name function name * @param {Function} handler function body */ var override = function override(name, handler) { var method = microApp[name]; // Reset the method name defineProperty(microApp, name, defineProperty(function () { var anonymous = function anonymous() { // `bubbles` as a flag var bubbles = true; var result = handler.call(this, { stopPropagation: function stopPropagation() { bubbles = false; } }, arguments); // Interrupt the function chain by `event.stopPropagation` and give the `result` as return value return bubbles ? method.apply(this, arguments) : result; }; if (supportConfigurable) { return defineProperty(anonymous, 'name', name); } else { return anonymous; } }(), 'toString', function toString() { return 'function ' + name + '() { [native code] }'; })); }; var _Element$prototype = Element.prototype; var setAttribute = _Element$prototype.setAttribute; var removeAttribute = _Element$prototype.removeAttribute; /** * Set or remove an attribute * @param {Element} elem target element * @param {String} attribute attribute name * @param {AnyType} value attribute value */ var setAttribute$1 = function setAttribute$1(elem, attribute, value) { if (value === null) { removeAttribute.call(elem, attribute); } else { setAttribute.call(elem, attribute, value); } }; /** * Define property and listen change of value on microApp * @param {String_Array} name String or Array like [property name, attribute name] * @param {Function} onChange callback */ function defineAttribute(name, onChange) { // propertyName should be camel case, attributeName should be hyphen case var propertyName = void 0; var attributeName = void 0; if (typeof name == 'string') { propertyName = attributeName = name; } else { propertyName = name[0]; attributeName = name[1]; } // Get defaults from attribute var value = microApp.getAttribute(attributeName); if (value !== null) { onChange(attributeName, value, null); } // Getter, setter var propertyConfig = { get: function get() { return value; }, set: function set(newValue) { if (newValue !== value) { // the value is diffrent from old value onChange(attributeName, newValue, value); } return value = newValue; }, enumerable: false }; define(microApp, propertyName, propertyConfig); // Mark down the property name defineAttribute[propertyName] = true; // Both define camel and hyphen if (propertyName != attributeName) { define(microApp, attributeName, propertyConfig); // Mark down the property name defineAttribute[attributeName] = true; } } var defaultArrayValue = {}.toString.call([]); var defaultBase64Value = '[object Base64]'; /** * Create multiple elements * @param {String} code html code * @param {String} attributeName an default attribute name * @return {Function} func onChange of `defineAttribute` */ var createMultiElement = function createMultiElement(code, attributeName) { // Previous items var previousItems = []; // onChange return function (name, value, previous) { // Format to `Array` var currentItems = value instanceof Array ? value.slice(0) : value === null ? [] : [value]; // Clear old elements previousItems.forEach(function (element) { remove(element); }); previousItems = []; // Create new elements var length = currentItems.length; for (var i = 0; i < length; i++) { var _value = currentItems[i]; if (_value !== null) { (function () { // Create element var element = createElement(code); if (_value instanceof Object) { // `attributeName` should be `undefined` defaults setAttribute$1(element, attributeName, void 0); for (var key in _value) { setAttribute$1(element, key, _value[key]); } } else { setAttribute$1(element, attributeName, _value); } // Safari can not parse the img url includes '#' var attributeValue = element.getAttribute(attributeName); if (attributeValue.indexOf('#') > -1) { (function () { var temp = attributeValue.split('#'); setAttribute$1(element, attributeName, temp[0]); setAttribute$1(element, 'filter', temp[1]); var interrupt = false; temp[1].split('|').forEach(function (filterName) { if (interrupt) { return; } var filterMethod = filter[filterName]; if (filterMethod) { interrupt = filterMethod.call(element) === false; } }); })(); } // Save the element previousItems.push(append(element)); })(); } } // Set value as attribute setAttribute$1(microApp, name, value instanceof Array ? defaultArrayValue : /^data:image/.test(value) ? defaultBase64Value : value); }; }; // Version defineProperty(microApp, 'version', ("1.0.2")); // Define a filter by `microApp.filter` defineProperty(microApp, 'filter', filter); if (ios && safari) { (function () { // A filter of `precomposed` microApp.filter('precomposed', function () { this.rel = 'apple-touch-icon-precomposed'; }) // A filter of `autosize` .filter('autosize', function () { var type = this.getAttribute('rel') == 'apple-touch-startup-image' ? 'splash' : 'icon'; var attributes = autosize(type); if (attributes) { for (var attributeName in attributes) { this.setAttribute(attributeName, attributes[attributeName]); } } }); // Capable, `null` equates disable, others will change to enable var capable = createElement('<meta name="apple-mobile-web-app-capable" content="yes">'); defineAttribute('capable', function (name, value, previous) { setAttribute$1(microApp, name, value); if (value === null) { remove(capable); } if (previous === null) { append(capable); } }); // StatusBarStyle, normally, the value is one of `black-translucent`,`black`,`white` var statusBarStyle = createElement('<meta name="apple-mobile-web-app-status-bar-style">'); defineAttribute(['statusBarStyle', 'status-bar-style'], function (name, value, previous) { setAttribute$1(microApp, name, value); setAttribute$1(statusBarStyle, 'content', value); if (value === null) { remove(statusBarStyle); } if (previous === null) { append(statusBarStyle); } }); // Title, the app's name var title = createElement('<meta name="apple-mobile-web-app-title">'); defineAttribute('title', function (name, value, previous) { setAttribute$1(microApp, name, value); setAttribute$1(title, 'content', value); if (value === null) { remove(title); } if (previous === null) { append(title); } }); // Icon, the cover of app defineAttribute('icon', createMultiElement('<link rel="apple-touch-icon">', 'href')); // Splash, the start up image defineAttribute('splash', createMultiElement('<link rel="apple-touch-startup-image">', 'href')); // Override the method `getAttribute` override('getAttribute', function (event, args) { var name = args[0]; if (microApp === this && name in defineAttribute) { // This attribute is defined event.stopPropagation(); return microApp[name]; } }); // Override the method `setAttribute` override('setAttribute', function (event, args) { var name = args[0]; var value = args[1]; if (microApp === this && name in defineAttribute) { // This attribute is defined event.stopPropagation(); return microApp[name] = value; } }); // Override the method `removeAttribute` override('removeAttribute', function (event, args) { var name = args[0]; if (microApp === this && name in defineAttribute) { // This attribute is defined event.stopPropagation(); return microApp[name] = null; } }); })(); } module.exports = window.microApp = microApp; // 尚存问题 // 1. 点击分享按钮后 Safari会检索页面对应meta标签并且写入变量存储 无论点击分享按钮多少次以及后续操作包括添加到桌面 都不会重新修改变量 导致icon无法动态修改 // 2. 添加bookmark或者favorite后 点击分享按钮 应用icon会根据 添加到bookmark时为准 而不会去检索页面的meta标签 // 体验优化 // 1. 用户安装时触发的事件 // 2. 增加删除描述文案 // 3. 提供原生交互 // 3.1 应用的3Dtouch快捷方式 // 3.2 消息推送 // 3.3 应用进入后台事件(Home键) // 3.4 应用返回前台事件 /***/ } /******/ ]) }); ;