Skip to content

Instantly share code, notes, and snippets.

@thedamon
Last active November 2, 2018 01:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thedamon/94aefe219f9f386350b50f88aba72e82 to your computer and use it in GitHub Desktop.
Save thedamon/94aefe219f9f386350b50f88aba72e82 to your computer and use it in GitHub Desktop.
Vue components on demand; anywhere in your weird/old/headful CMS
/*
In a large CMS site (or sites), where content authors can add any component to any page at any time, it's sometimes tricky to figure out how to integrate Vue apps and components in a way that plays well together, especially when we don't want to need to bundle all our javascript on pageload and load stuff that's not necessary on one page or another.
Whatever the backend, DomInitor will take care of initializing Vue (and/or JS coomponents) based on data attributes, but only load the javascript it needs for a given page AND work in IE11.
It works by scanning the HTML of your page for data attributes and using the values of them + webpack to asynchronously load all the js chunks.
There is a lot of code specific to my use case here, but it gives a good example of how to achieve the dream of being able to have Vue components built as SFCs and bundled by webpack easily used within a massive 'legacy'/'oldschool' website while only loading the components used on any given page. (Caveat: because this is run once on pageload, if the DOM changes, you may be out of luck. But if any DOM change that would require a new component only happens inside a dedicated Vue app.. then you're fine!)
// in component:
<accordion- :title="${titleFromServer}" data-vue-component="path/to/Accordion">Hello I am slot <base-button data-vue-component="path/to/baseButton"></base-button></accordion->
// or for an App (rly so we can target a specific other directory and only build out appname.app.js)
<my-cool-spa-like-app data-vue-app="/path/to/app"></my-cool-spa-like-app>
The paths are relative to a components directory and an apps directory. You could also create an app/component manifest with path aliases to load into webpack to avoid having to use the literal paths.
*/
import Vue from 'vue';
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';
import VueI18n from 'vue-i18n';
import { camelCase } from 'change-case';
import { getLang } from '@/utils/i18n';
export default function init(config) {
initVueComponents(config);
document.querySelectorAll('[data-js-component]').forEach(getJsComponent);
legacyInits(config);
}
function initVueComponents() {
let vues = {};
document
.querySelectorAll('[data-vue-app],[data-vue-component]')
.forEach(el => {
let type = el.dataset.vueApp ? 'app' : 'component';
let path = el.dataset.vueApp || el.dataset.vueComponent;
let name =
el.dataset.componentName ||
(el.dataset.vueComponent && el.dataset.vueComponent.split('/').pop()) ||
el.dataset.vueApp.split('/').pop() ||
path;
// setting type+path as key removes duplicates.
vues[type + path] = {
type,
path,
name
};
});
loadVues(Object.values(vues))
.then(modules => {
// then init Vue!
Vue.use(VueI18n);
Vue.use(Vuex);
Vue.config.devtools = true;
// create the components array
let components = {};
modules.forEach(module => {
let name = camelCase(module.default.name);
components[name] = module.default || module;
});
new Vue({
el: '#site',
i18n: i18nConfig(),
store: initialStore(),
components
});
})
.catch(err => {
console.error(err);
});
}
function loadVues(vues) {
return new Promise((resolve, reject) => {
let imports = [];
vues.forEach(vue => {
let importPromise;
if (vue.type === 'component') {
importPromise = import(/* webpackChunkName: "vue-chunk" */
/* webpackExclude: /\.test\.js$/ */
`@/scripts/vue-components/${vue.path}.vue`);
}
if (vue.type === 'app') {
importPromise = import(/* webpackChunkName: "vue-apps" */
/* webpackExclude: /\.test\.js$/ */
`@/apps/${vue.path}.app.vue`);
}
imports.push(importPromise);
});
Promise.all(imports)
.then(modules => {
resolve(modules);
})
.catch(err => {
reject(err);
});
});
}
function initialStore() {
return new Vuex.Store({
state: {
userData: {}
},
plugins: [createPersistedState()]
});
}
function i18nConfig() {
// preload generic translations for multiple components (could be from another file/server generated). Other i18n should be done in <i18n> blocks on each component/app.
const messages = {
en: {},
fr: {}
};
return new VueI18n({ locale: getLang(), messages });
}
function getJsComponent(el) {
// any patterns for targetting non-vue scripts.
}
function legacyInits(config) {
// any other components that need to be included/intialized. ideally through import()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment