普通html网站引用Vue单文件组件
本篇中,Insus.NET创建一个vue单文件组件,然后在普通的html网站中去引用这个组件。
把下面代码,存储为一个名为httpVueLoader.js。
(function umd(root, factory) { if (typeof module === 'object' && typeof exports === 'object') module.exports = factory() else if (typeof define === 'function' && define.amd) define([], factory) else root.httpVueLoader = factory() })(this, function factory() { 'use strict'; var scopeIndex = 0; StyleContext.prototype = { withBase: function (callback) { var tmpBaseElt; if (this.component.baseURI) { // firefox and chrome need the <base> to be set while inserting or modifying <style> in a document. tmpBaseElt = document.createElement('base'); tmpBaseElt.href = this.component.baseURI; var headElt = this.component.getHead(); headElt.insertBefore(tmpBaseElt, headElt.firstChild); } callback.call(this); if (tmpBaseElt) this.component.getHead().removeChild(tmpBaseElt); }, scopeStyles: function (styleElt, scopeName) { function process() { var sheet = styleElt.sheet; 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); var segments = sel.match(/([^ :]+)(.+)?/); scopedSelectors.push(segments[1] + scopeName + (segments[2] || '')); }); var scopedRule = scopedSelectors.join(',') + rule.cssText.substr(rule.selectorText.length); sheet.deleteRule(i); sheet.insertRule(scopedRule, i); } } try { // firefox may fail sheet.cssRules with InvalidAccessError process(); } catch (ex) { if (ex instanceof DOMException && ex.code === DOMException.INVALID_ACCESS_ERR) { styleElt.sheet.disabled = true; styleElt.addEventListener('load', function onStyleLoaded() { styleElt.removeEventListener('load', onStyleLoaded); // firefox need this timeout otherwise we have to use document.importNode(style, true) setTimeout(function () { process(); styleElt.sheet.disabled = false; }); }); return; } throw ex; } }, compile: function () { var hasTemplate = this.template !== null; var scoped = this.elt.hasAttribute('scoped'); if (scoped) { // no template, no scopable style needed if (!hasTemplate) return; // firefox does not tolerate this attribute this.elt.removeAttribute('scoped'); } this.withBase(function () { this.component.getHead().appendChild(this.elt); }); if (scoped) this.scopeStyles(this.elt, '[' + this.component.getScopeId() + ']'); return Promise.resolve(); }, getContent: function () { return this.elt.textContent; }, setContent: function (content) { this.withBase(function () { this.elt.textContent = content; }); } }; function StyleContext(component, elt) { this.component = component; this.elt = elt; } ScriptContext.prototype = { getContent: function () { return this.elt.textContent; }, setContent: function (content) { this.elt.textContent = content; }, compile: function (module) { var childModuleRequire = function (childURL) { return httpVueLoader.require(resolveURL(this.component.baseURI, childURL)); }.bind(this); var childLoader = function (childURL, childName) { return httpVueLoader(resolveURL(this.component.baseURI, childURL), childName); }.bind(this); try { Function('exports', 'require', 'httpVueLoader', 'module', this.getContent()).call(this.module.exports, this.module.exports, childModuleRequire, childLoader, this.module); } catch (ex) { if (!('lineNumber' in ex)) { return Promise.reject(ex); } var vueFileData = responseText.replace(/\r?\n/g, '\n'); var lineNumber = vueFileData.substr(0, vueFileData.indexOf(script)).split('\n').length + ex.lineNumber - 1; throw new (ex.constructor)(ex.message, url, lineNumber); } return Promise.resolve(this.module.exports) .then(httpVueLoader.scriptExportsHandler.bind(this)) .then(function (exports) { this.module.exports = exports; }.bind(this)); } }; function ScriptContext(component, elt) { this.component = component; this.elt = elt; this.module = { exports: {} }; } TemplateContext.prototype = { getContent: function () { return this.elt.innerHTML; }, setContent: function (content) { this.elt.innerHTML = content; }, getRootElt: function () { var tplElt = this.elt.content || this.elt; if ('firstElementChild' in tplElt) return tplElt.firstElementChild; for (tplElt = tplElt.firstChild; tplElt !== null; tplElt = tplElt.nextSibling) if (tplElt.nodeType === Node.ELEMENT_NODE) return tplElt; return null; }, compile: function () { return Promise.resolve(); } }; function TemplateContext(component, elt) { this.component = component; this.elt = elt; } Component.prototype = { getHead: function () { return document.head || document.getElementsByTagName('head')[0]; }, getScopeId: function () { if (this._scopeId === '') { this._scopeId = 'data-s-' + (scopeIndex++).toString(36); this.template.getRootElt().setAttribute(this._scopeId, ''); } return this._scopeId; }, load: function (componentURL) { return httpVueLoader.httpRequest(componentURL) .then(function (responseText) { this.baseURI = componentURL.substr(0, componentURL.lastIndexOf('/') + 1); var doc = document.implementation.createHTMLDocument(''); // IE requires the <base> to come with <style> doc.body.innerHTML = (this.baseURI ? '<base href="' + this.baseURI + '">' : '') + responseText; for (var it = doc.body.firstChild; it; it = it.nextSibling) { switch (it.nodeName) { case 'TEMPLATE': this.template = new TemplateContext(this, it); break; case 'SCRIPT': this.script = new ScriptContext(this, it); break; case 'STYLE': this.styles.push(new StyleContext(this, it)); break; } } return this; }.bind(this)); }, _normalizeSection: function (eltCx) { var p; if (eltCx === null || !eltCx.elt.hasAttribute('src')) { p = Promise.resolve(null); } else { p = httpVueLoader.httpRequest(eltCx.elt.getAttribute('src')) .then(function (content) { eltCx.elt.removeAttribute('src'); return content; }); } return p .then(function (content) { if (eltCx !== null && eltCx.elt.hasAttribute('lang')) { var lang = eltCx.elt.getAttribute('lang'); eltCx.elt.removeAttribute('lang'); return httpVueLoader.langProcessor[lang.toLowerCase()].call(this, content === null ? eltCx.getContent() : content); } return content; }.bind(this)) .then(function (content) { if (content !== null) eltCx.setContent(content); }); }, normalize: function () { return Promise.all(Array.prototype.concat( this._normalizeSection(this.template), this._normalizeSection(this.script), this.styles.map(this._normalizeSection) )) .then(function () { return this; }.bind(this)); }, compile: function () { return Promise.all(Array.prototype.concat( this.template && this.template.compile(), this.script && this.script.compile(), this.styles.map(function (style) { return style.compile(); }) )) .then(function () { return this; }.bind(this)); } }; function Component(name) { this.name = name; this.template = null; this.script = null; this.styles = []; this._scopeId = ''; } function identity(value) { return value; } function parseComponentURL(url) { var comp = url.match(/(.*?)([^/]+?)\/?(\.vue)?(\?.*|#.*|$)/); return { name: comp[2], url: comp[1] + comp[2] + (comp[3] === undefined ? '/index.vue' : comp[3]) + comp[4] }; } function resolveURL(baseURL, url) { if (url.substr(0, 2) === './' || url.substr(0, 3) === '../') { return baseURL + url; } return url; } httpVueLoader.load = function (url, name) { return function () { return new Component(name).load(url) .then(function (component) { return component.normalize(); }) .then(function (component) { return component.compile(); }) .then(function (component) { var exports = component.script !== null ? component.script.module.exports : {}; if (component.template !== null) exports.template = component.template.getContent(); if (exports.name === undefined) if (component.name !== undefined) exports.name = component.name; exports._baseURI = component.baseURI; return exports; }); }; }; httpVueLoader.register = function (Vue, url) { var comp = parseComponentURL(url); Vue.component(comp.name, httpVueLoader.load(comp.url)); }; 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 comp = parseComponentURL(components[componentName].substr(4)); var componentURL = ('_baseURI' in this.$options) ? resolveURL(this.$options._baseURI, comp.url) : comp.url; if (isNaN(componentName)) components[componentName] = httpVueLoader.load(componentURL, componentName); else components[componentName] = Vue.component(comp.name, httpVueLoader.load(componentURL, comp.name)); } } } }); }; httpVueLoader.require = function (moduleName) { return window[moduleName]; }; httpVueLoader.httpRequest = function (url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.responseType = 'text'; xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) resolve(xhr.responseText); else reject(xhr.status); } }; xhr.send(null); }); }; httpVueLoader.langProcessor = { html: identity, js: identity, css: identity }; httpVueLoader.scriptExportsHandler = identity; function httpVueLoader(url, name) { var comp = parseComponentURL(url); return httpVueLoader.load(comp.url, name); } return httpVueLoader; });
创建一个 vue组件:
<template> <div> <div> <fieldset> <legend>User</legend> <div> <label>Name:</label>{{prop_object.name}} </div> <div> <label>Age:</label>{{prop_object.age}} </div> <div> <label>Email:</label>{{prop_object.email}} </div> </fieldset> </div> <hr /> <div> <fieldset> <legend>Data</legend> <ul> <li v-for="pa in prop_array"> <span>{{pa.data}}</span> </li> </ul> </fieldset> </div> </div> </template>
javascript代码:
module.exports = { props: { prop_object: { type: Object, default: function () { return {} } }, prop_array: { type: Array, default: function () { return [] } } } };
样式:
<style> </style>
以上组件准备完毕,下面是html网页,将引用以上vue组件:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>communicate</title> </head> <body> <div id="comm"> <insus-component :prop_object="obj" :prop_array="arr"></insus-component> </div> </body> </html>
Vue.config.productionTip = false; Vue.config.devtools = false; Vue.use(httpVueLoader); var vue_app = new Vue({ el: '#comm', data() { return { obj: null, arr: null }; }, mounted: function () { this.init(); }, methods: { init: function () { this.obj = { name: 'Insus.NET', age: '100', email: 'insus@163.com' } this.arr = [{ data: 'a' }, { data: 'b' }, { data: 'c' }, { data: 'd' }, { data: 'e' }, { data: 'f' }] }, }, components: { 'insus-component': 'url:/VueComponents/zc.vue' } })
运行结果: