httpVueLoader.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. (function umd(root,factory){
  2. if(typeof module==='object' && typeof exports === 'object' )
  3. module.exports=factory()
  4. else if(typeof define==='function' && define.amd)
  5. define([],factory)
  6. else
  7. root.httpVueLoader=factory()
  8. })(this,function factory() {
  9. 'use strict';
  10. var scopeIndex = 0;
  11. StyleContext.prototype = {
  12. withBase: function(callback) {
  13. var tmpBaseElt;
  14. if ( this.component.baseURI ) {
  15. // firefox and chrome need the <base> to be set while inserting or modifying <style> in a document.
  16. tmpBaseElt = document.createElement('base');
  17. tmpBaseElt.href = this.component.baseURI;
  18. var headElt = this.component.getHead();
  19. headElt.insertBefore(tmpBaseElt, headElt.firstChild);
  20. }
  21. callback.call(this);
  22. if ( tmpBaseElt )
  23. this.component.getHead().removeChild(tmpBaseElt);
  24. },
  25. scopeStyles: function(styleElt, scopeName) {
  26. function process() {
  27. var sheet = styleElt.sheet;
  28. var rules = sheet.cssRules;
  29. for ( var i = 0; i < rules.length; ++i ) {
  30. var rule = rules[i];
  31. if ( rule.type !== 1 )
  32. continue;
  33. var scopedSelectors = [];
  34. rule.selectorText.split(/\s*,\s*/).forEach(function(sel) {
  35. scopedSelectors.push(scopeName+' '+sel);
  36. var segments = sel.match(/([^ :]+)(.+)?/);
  37. scopedSelectors.push(segments[1] + scopeName + (segments[2]||''));
  38. });
  39. var scopedRule = scopedSelectors.join(',') + rule.cssText.substr(rule.selectorText.length);
  40. sheet.deleteRule(i);
  41. sheet.insertRule(scopedRule, i);
  42. }
  43. }
  44. try {
  45. // firefox may fail sheet.cssRules with InvalidAccessError
  46. process();
  47. } catch (ex) {
  48. if ( ex instanceof DOMException && ex.code === DOMException.INVALID_ACCESS_ERR ) {
  49. styleElt.sheet.disabled = true;
  50. styleElt.addEventListener('load', function onStyleLoaded() {
  51. styleElt.removeEventListener('load', onStyleLoaded);
  52. // firefox need this timeout otherwise we have to use document.importNode(style, true)
  53. setTimeout(function() {
  54. process();
  55. styleElt.sheet.disabled = false;
  56. });
  57. });
  58. return;
  59. }
  60. throw ex;
  61. }
  62. },
  63. compile: function() {
  64. var hasTemplate = this.template !== null;
  65. var scoped = this.elt.hasAttribute('scoped');
  66. if ( scoped ) {
  67. // no template, no scopable style needed
  68. if ( !hasTemplate )
  69. return;
  70. // firefox does not tolerate this attribute
  71. this.elt.removeAttribute('scoped');
  72. }
  73. this.withBase(function() {
  74. this.component.getHead().appendChild(this.elt);
  75. });
  76. if ( scoped )
  77. this.scopeStyles(this.elt, '['+this.component.getScopeId()+']');
  78. return Promise.resolve();
  79. },
  80. getContent: function() {
  81. return this.elt.textContent;
  82. },
  83. setContent: function(content) {
  84. this.withBase(function() {
  85. this.elt.textContent = content;
  86. });
  87. }
  88. };
  89. function StyleContext(component, elt) {
  90. this.component = component;
  91. this.elt = elt;
  92. }
  93. ScriptContext.prototype = {
  94. getContent: function() {
  95. return this.elt.textContent;
  96. },
  97. setContent: function(content) {
  98. this.elt.textContent = content;
  99. },
  100. compile: function(module) {
  101. var childModuleRequire = function(childURL) {
  102. return httpVueLoader.require(resolveURL(this.component.baseURI, childURL));
  103. }.bind(this);
  104. var childLoader = function(childURL, childName) {
  105. return httpVueLoader(resolveURL(this.component.baseURI, childURL), childName);
  106. }.bind(this);
  107. try {
  108. Function('exports', 'require', 'httpVueLoader', 'module', this.getContent()).call(this.module.exports, this.module.exports, childModuleRequire, childLoader, this.module);
  109. } catch(ex) {
  110. if ( !('lineNumber' in ex) ) {
  111. return Promise.reject(ex);
  112. }
  113. var vueFileData = responseText.replace(/\r?\n/g, '\n');
  114. var lineNumber = vueFileData.substr(0, vueFileData.indexOf(script)).split('\n').length + ex.lineNumber - 1;
  115. throw new (ex.constructor)(ex.message, url, lineNumber);
  116. }
  117. return Promise.resolve(this.module.exports)
  118. .then(httpVueLoader.scriptExportsHandler.bind(this))
  119. .then(function(exports) {
  120. this.module.exports = exports;
  121. }.bind(this));
  122. }
  123. };
  124. function ScriptContext(component, elt) {
  125. this.component = component;
  126. this.elt = elt;
  127. this.module = { exports:{} };
  128. }
  129. TemplateContext.prototype = {
  130. getContent: function() {
  131. return this.elt.innerHTML;
  132. },
  133. setContent: function(content) {
  134. this.elt.innerHTML = content;
  135. },
  136. getRootElt: function() {
  137. var tplElt = this.elt.content || this.elt;
  138. if ( 'firstElementChild' in tplElt )
  139. return tplElt.firstElementChild;
  140. for ( tplElt = tplElt.firstChild; tplElt !== null; tplElt = tplElt.nextSibling )
  141. if ( tplElt.nodeType === Node.ELEMENT_NODE )
  142. return tplElt;
  143. return null;
  144. },
  145. compile: function() {
  146. return Promise.resolve();
  147. }
  148. };
  149. function TemplateContext(component, elt) {
  150. this.component = component;
  151. this.elt = elt;
  152. }
  153. Component.prototype = {
  154. getHead: function() {
  155. return document.head || document.getElementsByTagName('head')[0];
  156. },
  157. getScopeId: function() {
  158. if ( this._scopeId === '' ) {
  159. this._scopeId = 'data-s-' + (scopeIndex++).toString(36);
  160. this.template.getRootElt().setAttribute(this._scopeId, '');
  161. }
  162. return this._scopeId;
  163. },
  164. load: function(componentURL) {
  165. return httpVueLoader.httpRequest(componentURL)
  166. .then(function(responseText) {
  167. this.baseURI = componentURL.substr(0, componentURL.lastIndexOf('/')+1);
  168. var doc = document.implementation.createHTMLDocument('');
  169. // IE requires the <base> to come with <style>
  170. doc.body.innerHTML = (this.baseURI ? '<base href="'+this.baseURI+'">' : '') + responseText;
  171. for ( var it = doc.body.firstChild; it; it = it.nextSibling ) {
  172. switch ( it.nodeName ) {
  173. case 'TEMPLATE':
  174. this.template = new TemplateContext(this, it);
  175. break;
  176. case 'SCRIPT':
  177. this.script = new ScriptContext(this, it);
  178. break;
  179. case 'STYLE':
  180. this.styles.push(new StyleContext(this, it));
  181. break;
  182. }
  183. }
  184. return this;
  185. }.bind(this));
  186. },
  187. _normalizeSection: function(eltCx) {
  188. var p;
  189. if ( eltCx === null || !eltCx.elt.hasAttribute('src') ) {
  190. p = Promise.resolve(null);
  191. } else {
  192. p = httpVueLoader.httpRequest(eltCx.elt.getAttribute('src'))
  193. .then(function(content) {
  194. eltCx.elt.removeAttribute('src');
  195. return content;
  196. });
  197. }
  198. return p
  199. .then(function(content) {
  200. if ( eltCx !== null && eltCx.elt.hasAttribute('lang') ) {
  201. var lang = eltCx.elt.getAttribute('lang');
  202. eltCx.elt.removeAttribute('lang');
  203. return httpVueLoader.langProcessor[lang.toLowerCase()].call(this, content === null ? eltCx.getContent() : content);
  204. }
  205. return content;
  206. }.bind(this))
  207. .then(function(content) {
  208. if ( content !== null )
  209. eltCx.setContent(content);
  210. });
  211. },
  212. normalize: function() {
  213. return Promise.all(Array.prototype.concat(
  214. this._normalizeSection(this.template),
  215. this._normalizeSection(this.script),
  216. this.styles.map(this._normalizeSection)
  217. ))
  218. .then(function() {
  219. return this;
  220. }.bind(this));
  221. },
  222. compile: function() {
  223. return Promise.all(Array.prototype.concat(
  224. this.template && this.template.compile(),
  225. this.script && this.script.compile(),
  226. this.styles.map(function(style) { return style.compile(); })
  227. ))
  228. .then(function() {
  229. return this;
  230. }.bind(this));
  231. }
  232. };
  233. function Component(name) {
  234. this.name = name;
  235. this.template = null;
  236. this.script = null;
  237. this.styles = [];
  238. this._scopeId = '';
  239. }
  240. function identity(value) {
  241. return value;
  242. }
  243. function parseComponentURL(url) {
  244. var comp = url.match(/(.*?)([^/]+?)\/?(\.vue)?(\?.*|#.*|$)/);
  245. return {
  246. name: comp[2],
  247. url: comp[1] + comp[2] + (comp[3] === undefined ? '/index.vue' : comp[3]) + comp[4]
  248. };
  249. }
  250. function resolveURL(baseURL, url) {
  251. if (url.substr(0, 2) === './' || url.substr(0, 3) === '../') {
  252. return baseURL + url;
  253. }
  254. return url;
  255. }
  256. httpVueLoader.load = function(url, name) {
  257. return function() {
  258. return new Component(name).load(url)
  259. .then(function(component) {
  260. return component.normalize();
  261. })
  262. .then(function(component) {
  263. return component.compile();
  264. })
  265. .then(function(component) {
  266. var exports = component.script !== null ? component.script.module.exports : {};
  267. if ( component.template !== null )
  268. exports.template = component.template.getContent();
  269. if ( exports.name === undefined )
  270. if ( component.name !== undefined )
  271. exports.name = component.name;
  272. exports._baseURI = component.baseURI;
  273. return exports;
  274. });
  275. };
  276. };
  277. httpVueLoader.register = function(Vue, url) {
  278. var comp = parseComponentURL(url);
  279. Vue.component(comp.name, httpVueLoader.load(comp.url));
  280. };
  281. httpVueLoader.install = function(Vue) {
  282. Vue.mixin({
  283. beforeCreate: function () {
  284. var components = this.$options.components;
  285. for ( var componentName in components ) {
  286. if ( typeof(components[componentName]) === 'string' && components[componentName].substr(0, 4) === 'url:' ) {
  287. var comp = parseComponentURL(components[componentName].substr(4));
  288. var componentURL = ('_baseURI' in this.$options) ? resolveURL(this.$options._baseURI, comp.url) : comp.url;
  289. if ( isNaN(componentName) )
  290. components[componentName] = httpVueLoader.load(componentURL, componentName);
  291. else
  292. components[componentName] = Vue.component(comp.name, httpVueLoader.load(componentURL, comp.name));
  293. }
  294. }
  295. }
  296. });
  297. };
  298. httpVueLoader.require = function(moduleName) {
  299. return window[moduleName];
  300. };
  301. httpVueLoader.httpRequest = function(url) {
  302. return new Promise(function(resolve, reject) {
  303. var xhr = new XMLHttpRequest();
  304. xhr.open('GET', url);
  305. xhr.responseType = 'text';
  306. xhr.onreadystatechange = function() {
  307. if ( xhr.readyState === 4 ) {
  308. if ( xhr.status >= 200 && xhr.status < 300 )
  309. resolve(xhr.responseText);
  310. else
  311. reject(xhr.status);
  312. }
  313. };
  314. xhr.send(null);
  315. });
  316. };
  317. httpVueLoader.langProcessor = {
  318. html: identity,
  319. js: identity,
  320. css: identity
  321. };
  322. httpVueLoader.scriptExportsHandler = identity;
  323. function httpVueLoader(url, name) {
  324. var comp = parseComponentURL(url);
  325. return httpVueLoader.load(comp.url, name);
  326. }
  327. return httpVueLoader;
  328. });