Created
September 13, 2017 23:47
-
-
Save mikermcneil/2d3ae647038e0b8c38aa43a04b07ae14 to your computer and use it in GitHub Desktop.
A little wrapper to make it easier for you to work with Vue.js files in a hybrid web application. (Designed for use with Sails.js.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* globalizer.js | |
* v0.2.1 | |
* | |
* Copyright 2017, Mike McNeil (@mikermcneil) | |
* MIT License | |
*/ | |
(function(global, factory){ | |
var Vue; | |
var _; | |
var VueRouter; | |
var $; | |
//˙°˚°·. | |
//‡CJS ˚°˚°·˛ | |
if (typeof exports === 'object' && typeof module !== 'undefined') { | |
var _require = require;// eslint-disable-line no-undef | |
var _module = module;// eslint-disable-line no-undef | |
// required deps: | |
Vue = _require('vue'); | |
_ = _require('lodash'); | |
// optional deps: | |
try { VueRouter = _require('vue-router'); } catch (e) { if (e.code === 'MODULE_NOT_FOUND') {/* ok */} else { throw e; } } | |
try { $ = _require('jquery'); } catch (e) { if (e.code === 'MODULE_NOT_FOUND') {/* ok */} else { throw e; } } | |
// export: | |
_module.exports = factory(Vue, _, VueRouter, $); | |
} | |
//˙°˚°· | |
//‡AMD ˚¸ | |
else if(typeof define === 'function' && define.amd) {// eslint-disable-line no-undef | |
// Register as an anonymous module. | |
define([], function () {// eslint-disable-line no-undef | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
// FUTURE: maybe use optional dep. loading here instead? | |
// e.g. `function('vue', 'lodash', 'vue-router', 'jquery')` | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
// required deps: | |
if (!global.Vue) { throw new Error('`Vue` global does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the Vue.js library is getting brought in before `globalizer`.)'); } | |
Vue = global.Vue; | |
if (!global._) { throw new Error('`_` global does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the Lodash library is getting brought in before `globalizer`.)'); } | |
_ = global._; | |
// optional deps: | |
VueRouter = global.VueRouter || undefined; | |
$ = global.$ || global.jQuery || undefined; | |
// So... there's not really a huge point to supporting AMD here-- | |
// except that if you're using it in your project, it makes this | |
// module fit nicely with the others you're using. And if you | |
// really hate globals, I guess there's that. | |
// ¯\_(ツ)_/¯ | |
return factory(Vue, _, VueRouter, $); | |
});//ƒ | |
} | |
//˙°˚˙°· | |
//‡NUDE ˚°·˛ | |
else { | |
// required deps: | |
if (!global.Vue) { throw new Error('`Vue` global does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the Vue.js library is getting brought in before `globalizer`.)'); } | |
Vue = global.Vue; | |
if (!global._) { throw new Error('`_` global does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the Lodash library is getting brought in before `globalizer`.)'); } | |
_ = global._; | |
// optional deps: | |
VueRouter = global.VueRouter || undefined; | |
$ = global.$ || global.jQuery || undefined; | |
// export: | |
if (global.globalizer) { throw new Error('Conflicting global (`globalizer`) already exists!'); } | |
global.globalizer = factory(Vue, _, VueRouter, $); | |
} | |
})(this, function (Vue, _, VueRouter, $){ | |
/** | |
* Module state | |
*/ | |
// Keep track of whether or not a page script has already been loaded in the DOM. | |
var didAlreadyLoadPageScript; | |
// The variable we'll be exporting. | |
var globalizer; | |
/** | |
* Module utilities (private) | |
*/ | |
function _ensureGlobalCache(){ | |
globalizer._cache = globalizer._cache || {}; | |
}; | |
function _exportOnGlobalCache(moduleName, moduleDefinition){ | |
_ensureGlobalCache(); | |
if (globalizer._cache[moduleName]) { throw new Error('Something else (e.g. a helper or constant) has already been registered under that name (`'+moduleName+'`'); } | |
globalizer._cache[moduleName] = moduleDefinition; | |
}; | |
function _exposeJQueryPoweredMethods(def){ | |
if (def.methods && def.methods.$get) { throw new Error('Page script definition contains `methods` with a `$get` key, but you\'re not allowed to override that'); } | |
if (def.methods && def.methods.$find) { throw new Error('Page script definition contains `methods` with a `$find` key, but you\'re not allowed to override that'); } | |
def.methods = def.methods || {}; | |
if ($) { | |
def.methods.$get = function (){ return $(this.$el); }; | |
def.methods.$find = function (subSelector){ return $(this.$el).find(subSelector); }; | |
} | |
else { | |
def.methods.$get = function (){ throw new Error('Cannot use .$get() method because, at the time when this page script was registered, jQuery (`$`) did not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure jQuery is getting brought in before `globalizer`.)'); }; | |
def.methods.$find = function (){ throw new Error('Cannot use .$find() method because, at the time when this page script was registered, jQuery (`$`) did not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure jQuery is getting brought in before `globalizer`.)'); }; | |
} | |
}; | |
/** | |
* Module exports | |
*/ | |
globalizer = {}; | |
/** | |
* registerHelper() | |
* | |
* Build a callable helper function from a helper script definition, | |
* then attach it to the global namespace so that it can be accessed later | |
* via `.require()`. | |
* | |
* @param {String} helperName | |
* @param {Function} def | |
*/ | |
globalizer.registerHelper = function(helperName, def){ | |
// Usage | |
if (!helperName) { throw new Error('1st argument (helper name) is required'); } | |
if (!def) { throw new Error('2nd argument (helper definition) is required'); } | |
if (!_.isFunction(def)) { throw new Error('2nd argument (helper definition) should be a function'); } | |
// Build callable helper | |
// > FUTURE: support machine defs | |
var callableHelper = def; | |
callableHelper.name = helperName; | |
// Attach to global cache | |
_exportOnGlobalCache(helperName, callableHelper); | |
}; | |
/** | |
* registerConstant() | |
* | |
* Attach a constant to the global namespace so that it can be accessed | |
* later via `.require()`. | |
* | |
* @param {String} constantName | |
* @param {Ref} value | |
*/ | |
globalizer.registerConstant = function(constantName, value){ | |
// Usage | |
if (!constantName) { throw new Error('1st argument (constant name) is required'); } | |
if (_.isUndefined(value)) { throw new Error('2nd argument (the constant value) is required'); } | |
// > FUTURE: freeze constant, if supported | |
// Attach to global cache | |
_exportOnGlobalCache(constantName, value); | |
}; | |
/** | |
* registerPage() | |
* | |
* Define a page script, if applicable for the current contents of the DOM. | |
* | |
* @param {String} pageName | |
* @param {Dictionary} def | |
* | |
* @returns {Ref} [new vue app thing for this page] | |
*/ | |
globalizer.registerPage = function(pageName, def){ | |
// Usage | |
if (!pageName) { throw new Error('1st argument (page name) is required'); } | |
if (!def) { throw new Error('2nd argument (page script definition) is required'); } | |
// Only actually build+load this page script if it is relevant for the current contents of the DOM. | |
if (!document.getElementById(pageName)) { return; } | |
// Spinlock | |
if (didAlreadyLoadPageScript) { throw new Error('Cannot load page script (`'+pageName+') because a page script has already been loaded on this page.'); } | |
didAlreadyLoadPageScript = true; | |
// Automatically set `el` | |
if (def.el) { throw new Error('Page script definition contains `el`, but you\'re not allowed to override that'); } | |
def.el = '#'+pageName; | |
// Expose extra methods, if jQuery is available. | |
_exposeJQueryPoweredMethods(def); | |
// Automatically attach `pageName` to `data`, for convenience. | |
if (def.data && def.data.pageName) { throw new Error('Page script definition contains `data` with a `pageName` key, but you\'re not allowed to override that'); } | |
def.data = _.extend({ | |
pageName: pageName | |
}, def.data||{}); | |
// Attach `goto` method, for convenience. | |
if (def.methods && def.methods.goto) { throw new Error('Page script definition contains `methods` with a `goto` key-- but you\'re not allowed to override that'); } | |
def.methods = def.methods || {}; | |
if (VueRouter) { | |
def.methods.goto = function (rootRelativeUrlOrOpts){ | |
return this.$router.push(rootRelativeUrlOrOpts); | |
}; | |
} | |
else { | |
def.methods.goto = function (){ throw new Error('Cannot use .goto() method because, at the time when this page script was registered, VueRouter did not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure VueRouter is getting brought in before `globalizer`.)'); }; | |
} | |
// If virtualPages was specified, check usage and then... | |
if (def.virtualPages && def.router) { throw new Error('Cannot specify both `virtualPages` AND an actual Vue `router`! Use one or the other.'); } | |
if (def.router && !VueRouter) { throw new Error('Cannot use `router`, because that depends on the Vue Router. But `VueRouter` does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the VueRouter plugin is getting brought in before `globalizer`.)'); } | |
if (!def.virtualPages && def.html5HistoryMode !== undefined) { throw new Error('Cannot specify `html5HistoryMode` without also specifying `virtualPages`!'); } | |
if (!def.virtualPages && def.beforeEach !== undefined) { throw new Error('Cannot specify `beforeEach` without also specifying `virtualPages`!'); } | |
if (def.virtualPages) { | |
if (!VueRouter) { throw new Error('Cannot use `virtualPages`, because it depends on the Vue Router. But `VueRouter` does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the VueRouter plugin is getting brought in before `globalizer`.)'); } | |
// Change `virtualPages` in our def so it's the thing that VueRouter expects. | |
def = _.extend( | |
{ | |
// Pass in `router` | |
router: (function(){ | |
var newRouter = new VueRouter({ | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
// FUTURE: Consider binding popstate handler in order to intercept | |
// back/fwd button navigation / typing in the URL bar that would send | |
// the user to another URL under the same domain. This would provide | |
// a slightly better user experience for certain cases. | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
mode: def.html5HistoryMode || 'hash', | |
routes: _.reduce(def.virtualPages, function(memo, vueComponentDef, urlPattern) { | |
// Expose extra methods on virtual page script, if jQuery is available. | |
_exposeJQueryPoweredMethods(vueComponentDef); | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
// FUTURE: If urlPattern contains a url pattern variable (e.g. `:id`) | |
// or wildcard "splat" (e.g. `*`), then log a warning reminding whoever | |
// did it to be careful because of this: | |
// https://router.vuejs.org/en/essentials/dynamic-matching.html#reacting-to-params-changes | |
// | |
// In other words, going between `/foo/3` and `/foo/4` doesn't work as expected. | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
memo.push({ | |
path: urlPattern, | |
component: vueComponentDef | |
}); | |
return memo; | |
}, []) | |
}); | |
if (def.beforeEach) { | |
newRouter.beforeEach(def.beforeEach); | |
}//fi | |
return newRouter; | |
})(), | |
}, | |
_.omit(def, ['virtualPages']) | |
); | |
}//fi | |
// Construct Vue instance for this page script. | |
var vm = new Vue(def); | |
return vm; | |
};//ƒ | |
/** | |
* registerComponent() | |
* | |
* Define a Vue component. | |
* | |
* @param {String} componentName | |
* @param {Dictionary} def | |
* | |
* @returns {Ref} [new vue component for this page] | |
*/ | |
globalizer.registerComponent = function(componentName, def){ | |
// Expose extra methods on component def, if jQuery is available. | |
_exposeJQueryPoweredMethods(def); | |
Vue.component(componentName, def); | |
}; | |
/** | |
* require() | |
* | |
* Require a helper function, constant, virtual page def, or component def from the global namespace. | |
* | |
* @param {String} moduleName | |
* @returns {Ref} [e.g. the callable helper function, the value of the constant, or the definition] | |
* @throws {Error} if no such module has been registered | |
*/ | |
globalizer.require = function(moduleName) { | |
// Usage | |
if (!moduleName) { throw new Error('1st argument (module name -- i.e. the name of a helper or constant) is required'); } | |
// Fetch from global cache | |
_ensureGlobalCache(); | |
if (_.isUndefined(globalizer._cache[moduleName])) { | |
var err = new Error('No module (helper, constant, virtual page def, or component def) is registered under that name (`'+moduleName+'`'); | |
err.name = 'RequireError'; | |
err.code = 'MODULE_NOT_FOUND'; | |
throw err; | |
} | |
return globalizer._cache[moduleName]; | |
}; | |
return globalizer; | |
});//…) |
Mike, what is the different between data object
(parasails) and data function
(vue)?
p.s. I hope I asked it in the right forum.
Thank you.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage
First, put globalizer in
assets/dependencies/globalizer.js
, and make sure that stuff is getting loaded first in your pipeline.js file.Next, make sure you also have Vue.js, lodash (and probably also VueRouter and jQuery, if you want the bonus stuff).
views/pages/login.ejs
assets/styles/pages/login.less
make sure you're importing this in importer.less!
assets/js/pages/login.page.js