http-vue-loader
Advanced tools
Comparing version 1.0.0 to 1.1.0
{ | ||
"name": "http-vue-loader", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Load .vue files directly from your html/js. No node.js environment, no build step.", | ||
@@ -5,0 +5,0 @@ "main": "./src/httpVueLoader.js", |
@@ -81,3 +81,2 @@ # http-vue-loader | ||
... | ||
``` | ||
@@ -94,6 +93,5 @@ | ||
## Dependances | ||
* [Vue.js 2](https://vuejs.org/) | ||
* [axios](https://github.com/mzabriskie/axios) (Can easily be replaced by another lib) | ||
* [es6-promise](https://github.com/stefanpenner/es6-promise) (optional, except for IE) | ||
## Requirements | ||
* [Vue.js 2](https://vuejs.org/) ([compiler and runtime](https://vuejs.org/v2/guide/installation.html#Explanation-of-Different-Builds)) | ||
* [es6-promise](https://github.com/stefanpenner/es6-promise) (optional, except for IE, Chrome < 33, FireFox < 29, [see other](http://caniuse.com/#search=promise) ) | ||
@@ -114,2 +112,23 @@ | ||
##### httpVueLoader.httpRequest(`url`) | ||
This is the default httpLoader. | ||
Use axios instead of the default http loader: | ||
``` | ||
httpVueLoader.httpRequest = function(url) { | ||
return axios.get(url) | ||
.then(function(res) { | ||
return res.data; | ||
}) | ||
.catch(function(err) { | ||
return Promise.reject(err.status); | ||
}); | ||
} | ||
``` | ||
## How it works | ||
@@ -120,3 +139,4 @@ | ||
1. process each section (template, script and style) | ||
1. return a promise to Vue (async components) | ||
1. return a promise to Vue.js (async components) | ||
1. then Vue.js compiles and cache the component | ||
@@ -123,0 +143,0 @@ |
'use strict'; | ||
httpVueLoader.componentNameFromURL = function(url) { | ||
return url.match(/([^/]+)\.vue|$/)[1]; | ||
httpVueLoader.parseComponentURL = function(url) { | ||
var comp = url.match(/(.*?)([^/]+?)\/?(\.vue)?(?:\?|#|$)/); | ||
return { | ||
name: comp[2], | ||
url: comp[1] + comp[2] + (comp[3] === undefined ? '/index.vue' : comp[3]) | ||
} | ||
} | ||
httpVueLoader.scopeStyles = function(styleElt, scopeName) { | ||
@@ -14,13 +20,13 @@ | ||
var rules = sheet.cssRules; | ||
for ( var i = 0; i < rules.length ; ++i ) { | ||
var rule = rules[i]; | ||
if ( rule.type !== 1 ) | ||
continue; | ||
var scopedSelectors = []; | ||
rule.selectorText.split(/\s*,\s*/).forEach(function(sel) { | ||
scopedSelectors.push(scopeName+' '+sel); | ||
@@ -30,4 +36,4 @@ var segments = sel.match(/([^ :]+)(.+)?/); | ||
}); | ||
scopedRule = scopedSelectors.join(',') + rule.cssText.substr(rule.selectorText.length); | ||
var scopedRule = scopedSelectors.join(',') + rule.cssText.substr(rule.selectorText.length); | ||
sheet.deleteRule(i); | ||
@@ -38,7 +44,7 @@ sheet.insertRule(scopedRule, i); | ||
try { | ||
// firefox may fail sheet.cssRules with InvalidAccessError | ||
process(); | ||
} catch (ex) { | ||
styleElt.sheet.disabled = true; | ||
@@ -48,4 +54,9 @@ styleElt.addEventListener('load', function onStyleLoaded() { | ||
styleElt.removeEventListener('load', onStyleLoaded); | ||
process(); | ||
styleElt.sheet.disabled = false; | ||
// firefox need this timeout otherwise we have to use document.importNode(style, true) | ||
setTimeout(function() { | ||
process(); | ||
styleElt.sheet.disabled = false; | ||
}) | ||
}); | ||
@@ -55,27 +66,35 @@ } | ||
httpVueLoader.scopeIndex = 0; | ||
function httpVueLoader(url, name) { | ||
httpVueLoader.load = function(url, name) { | ||
function getRelativeBase(url) { | ||
var pos = url.lastIndexOf('/'); | ||
return url.substr(0, pos+1); | ||
} | ||
return function(resolve, reject) { | ||
httpVueLoader.httpRequest(url) | ||
.then(function(responseText) { | ||
axios.get(url) | ||
.then(function(res) { | ||
function require(moduleName) { | ||
return window[moduleName]; | ||
} | ||
var module = { exports:{} }; | ||
var templateElt = null; | ||
var scriptElt = null; | ||
var styleElts = []; | ||
var baseURI = getRelativeBase(url); | ||
var doc = document.implementation.createHTMLDocument(''); | ||
doc.body.innerHTML = res.data; | ||
// IE requires the <base> to come with <style> | ||
doc.body.innerHTML = (baseURI ? '<base href="'+baseURI+'">' : '') + responseText; | ||
for ( var it = doc.body.firstChild; it; it = it.nextSibling ) { | ||
switch ( it.nodeName ) { | ||
@@ -94,3 +113,2 @@ case 'TEMPLATE': | ||
if ( scriptElt !== null ) { | ||
@@ -101,55 +119,83 @@ | ||
} catch(ex) { | ||
if ( !('lineNumber' in ex) ) { | ||
reject(ex); | ||
return | ||
} | ||
var vueFileData = res.data.replace(/\r?\n/g, '\n'); | ||
var vueFileData = responseText.replace(/\r?\n/g, '\n'); | ||
var lineNumber = vueFileData.substr(0, vueFileData.indexOf(scriptElt.textContent)).split('\n').length + ex.lineNumber - 1; | ||
throw new (ex.constructor)(ex.message, res.request.responseURL, lineNumber); | ||
throw new (ex.constructor)(ex.message, url, lineNumber); | ||
} | ||
} | ||
return Promise.resolve(module.exports) | ||
.then(function(exports) { | ||
var headElt = document.head || document.getElementsByTagName('head')[0]; | ||
if ( baseURI ) { | ||
// firefox and chrome need the <base> to be set while inserting the <style> in the document | ||
var tmpBaseElt = document.createElement('base'); | ||
tmpBaseElt.href = baseURI; | ||
headElt.insertBefore(tmpBaseElt, headElt.firstChild); | ||
} | ||
var hasScoped = false; | ||
for ( var i = 0; i < styleElts.length; ++i ) { | ||
if ( styleElts[i].hasAttribute('scoped') ) { | ||
hasScoped = true; | ||
break; | ||
var scopeId = ''; | ||
function getScopeId(templateRootElement) { | ||
if ( scopeId === '' ) { | ||
scopeId = 'data-s-' + (httpVueLoader.scopeIndex++).toString(36); | ||
(templateElt.content || templateElt).firstElementChild.setAttribute(scopeId, ''); | ||
} | ||
return scopeId; | ||
} | ||
} | ||
for ( var i = 0; i < styleElts.length; ++i ) { | ||
var scopeId = ''; | ||
if ( templateElt !== null ) { | ||
var style = styleElts[i]; | ||
var scoped = style.hasAttribute('scoped'); | ||
if ( hasScoped ) { | ||
scopeId = 'data-s-' + (httpVueLoader.scopeIndex++).toString(36); | ||
(templateElt.content || templateElt).firstElementChild.setAttribute(scopeId, ''); | ||
if ( scoped ) { | ||
// no template, no scopable style | ||
if ( templateElt === null ) | ||
continue; | ||
// firefox does not tolerate this attribute | ||
style.removeAttribute('scoped'); | ||
} | ||
headElt.appendChild(style); | ||
if ( scoped ) | ||
httpVueLoader.scopeStyles(style, '['+getScopeId()+']'); | ||
} | ||
module.exports.template = templateElt.innerHTML; | ||
} | ||
if ( baseURI ) | ||
headElt.removeChild(tmpBaseElt); | ||
if ( templateElt !== null ) | ||
exports.template = templateElt.innerHTML; | ||
if ( exports.name === undefined ) | ||
if ( name !== undefined ) | ||
exports.name = name; | ||
return exports; | ||
}); | ||
for ( var i = 0; i < styleElts.length; ++i ) { | ||
}) | ||
.then(resolve, reject); | ||
} | ||
} | ||
var style = document.createElement('style'); | ||
style.textContent = styleElts[i].textContent; // style.styleSheet.cssText = styleElts[i].textContent; | ||
document.getElementsByTagName('head')[0].appendChild(style); | ||
if ( hasScoped ) | ||
httpVueLoader.scopeStyles(style, '['+scopeId+']'); | ||
} | ||
function httpVueLoader(url, name) { | ||
if ( module.exports.name === undefined ) | ||
if ( name !== undefined ) | ||
module.exports.name = name; | ||
resolve(module.exports); | ||
}, reject); | ||
} | ||
var comp = httpVueLoader.parseComponentURL(url); | ||
return httpVueLoader.load(comp.url, name); | ||
} | ||
@@ -159,4 +205,5 @@ | ||
function httpVueLoaderRegister(Vue, url) { | ||
Vue.component(httpVueLoader.componentNameFromURL(url), httpVueLoader(url)); | ||
var comp = httpVueLoader.parseComponentURL(url); | ||
Vue.component(comp.name, httpVueLoader.load(comp.url)); | ||
} | ||
@@ -166,22 +213,19 @@ | ||
httpVueLoader.install = function(Vue) { | ||
Vue.mixin({ | ||
beforeCreate: function () { | ||
var components = this.$options.components; | ||
for ( var componentName in components ) { | ||
if ( typeof(components[componentName]) === 'string' && components[componentName].substr(0, 4) === 'url:' ) { | ||
var url = components[componentName].substr(4); | ||
if ( isNaN(componentName) ) { | ||
components[componentName] = httpVueLoader(url, componentName); | ||
} else { | ||
var name = httpVueLoader.componentNameFromURL(url); | ||
components[componentName] = Vue.component(name, httpVueLoader(url, name)); | ||
} | ||
var comp = httpVueLoader.parseComponentURL(components[componentName].substr(4)); | ||
if ( isNaN(componentName) ) | ||
components[componentName] = httpVueLoader.load(comp.url, componentName); | ||
else | ||
components[componentName] = Vue.component(comp.name, httpVueLoader.load(comp.url, comp.name)); | ||
} | ||
@@ -192,1 +236,15 @@ } | ||
} | ||
httpVueLoader.httpRequest = function(url) { | ||
return new Promise(function(resolve, reject) { | ||
var xhr = new XMLHttpRequest(); | ||
xhr.open('GET', url, false); | ||
xhr.send(null); | ||
if ( xhr.status === 200 ) | ||
resolve(xhr.responseText); | ||
else | ||
reject(xhr.status, xhr.statusText); | ||
}); | ||
} |
11550
5
167
158