httpVueLoader.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  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) {
  227. return style.compile();
  228. })
  229. ))
  230. .then(function () {
  231. return this;
  232. }.bind(this));
  233. }
  234. };
  235. function Component(name) {
  236. this.name = name;
  237. this.template = null;
  238. this.script = null;
  239. this.styles = [];
  240. this._scopeId = '';
  241. }
  242. function identity(value) {
  243. return value;
  244. }
  245. function parseComponentURL(url) {
  246. var comp = url.match(/(.*?)([^/]+?)\/?(\.vue)?(\?.*|#.*|$)/);
  247. return {
  248. name: comp[2],
  249. url: comp[1] + comp[2] + (comp[3] === undefined ? '/index.vue' : comp[3]) + comp[4]
  250. };
  251. }
  252. function resolveURL(baseURL, url) {
  253. if (url.substr(0, 2) === './' || url.substr(0, 3) === '../') {
  254. return baseURL + url;
  255. }
  256. return url;
  257. }
  258. httpVueLoader.load = function (url, name) {
  259. return function () {
  260. return new Component(name).load(url)
  261. .then(function (component) {
  262. return component.normalize();
  263. })
  264. .then(function (component) {
  265. return component.compile();
  266. })
  267. .then(function (component) {
  268. var exports = component.script !== null ? component.script.module.exports : {};
  269. if (component.template !== null)
  270. exports.template = component.template.getContent();
  271. if (exports.name === undefined)
  272. if (component.name !== undefined)
  273. exports.name = component.name;
  274. exports._baseURI = component.baseURI;
  275. return exports;
  276. });
  277. };
  278. };
  279. httpVueLoader.register = function (Vue, url) {
  280. var comp = parseComponentURL(url);
  281. Vue.component(comp.name, httpVueLoader.load(comp.url));
  282. };
  283. httpVueLoader.install = function (Vue) {
  284. Vue.mixin({
  285. beforeCreate: function () {
  286. var components = this.$options.components;
  287. for (var componentName in components) {
  288. if (typeof (components[componentName]) === 'string' && components[componentName].substr(0, 4) === 'url:') {
  289. var comp = parseComponentURL(components[componentName].substr(4));
  290. var componentURL = ('_baseURI' in this.$options) ? resolveURL(this.$options._baseURI, comp.url) : comp.url;
  291. if (isNaN(componentName))
  292. components[componentName] = httpVueLoader.load(componentURL, componentName);
  293. else
  294. components[componentName] = Vue.component(comp.name, httpVueLoader.load(componentURL, comp.name));
  295. }
  296. }
  297. }
  298. });
  299. };
  300. httpVueLoader.require = function (moduleName) {
  301. return window[moduleName];
  302. };
  303. httpVueLoader.httpRequest = function (url) {
  304. return new Promise(function (resolve, reject) {
  305. var xhr = new XMLHttpRequest();
  306. xhr.open('GET', url);
  307. xhr.responseType = 'text';
  308. xhr.onreadystatechange = function () {
  309. if (xhr.readyState === 4) {
  310. if (xhr.status >= 200 && xhr.status < 300)
  311. resolve(xhr.responseText);
  312. else
  313. reject(xhr.status);
  314. }
  315. };
  316. xhr.send(null);
  317. });
  318. };
  319. httpVueLoader.langProcessor = {
  320. html: identity,
  321. js: identity,
  322. css: identity
  323. };
  324. httpVueLoader.scriptExportsHandler = identity;
  325. function httpVueLoader(url, name) {
  326. var comp = parseComponentURL(url);
  327. return httpVueLoader.load(comp.url, name);
  328. }
  329. return httpVueLoader;
  330. });