I had to work on a MVP where there were specific instructions to use Vue directly from a CDN. The Vue build available in this way is also called UMD (Universal Module Definition) build since you can use Vue from anywhere and the project does not need specific setup to build and package your code. Here’s a demo of how a simple Vue setup from CDN can be used for quick demo projects.
But, why use Vue from CDN? There may be a few cases where such an arrangement can help-
demo simple functionality (and do not want to spend time on setup)
create quick MVPs that can be demonstrated using simple HTML and JS files
make everything portable; enable others to easily change stuff without the full Vue setup
Rather than building a simple “hello world”, we will incorporate two components and router, and axios to call external services - and get all this working within a single HTML page and a couple of JS files.
Setup
There is nothing to setup when using Vue from CDN. Create a new index.html page and include Vue (and Vue router).
<scriptsrc="https://unpkg.com/axios/dist/axios.min.js"></script><script>Vue.component("Navigation",Navigation);constroutes=[{path:"/",component:Access,},{path:"/content",component:Content,},];constrouter=newVueRouter({routes:routes,mode:"history",base:"/",});varapp=newVue({el:"#app",router:router,data(){return{authstatus:false,apiurls:{authget:"http://localhost:9000/auth",authpost:"http://localhost:9000/auth",postget:"http://localhost:9000/posts",},};},methods:{setAuthStatus(val){console.log("Setting authstatus through event to: ",val);this.authstatus=val;console.log("New value: ",app.authstatus);},},});</script>
As you can see, we created a couple of variables and a method. These will be used by more than one components that we will create next.
Also note that we used mode: "history". This will necessitate using a server to serve the index.html file. If you don’t want that, use mode: "hash".
Create Components
We will not quite use .vue files - although it can be done by using vue-http-loader package when using Vue UMD build. Instead, we will create “pseudo-components” by just defining the entire Vue template structure in an object.
Let us create three components -
Get access details and authenticate
Show content
Navigation: this can be included in the main HTML page or in both of the above components.
Navigation
Create a new file and call it Navigation.vue.js.
Create the Vue template as an object (use Bootstrap for styling)
Show access and content links
Display Content links only if user has “logged in”
data (and computed, methods etc.) will be objects as well, and used where relevant (almost represents real Vue single file components)
The value to check whether user has logged in comes from the root.
1
this.$root.authstatus;
Although I am not a fan of accessing variables like this, it serves its purpose in a simple setup. Use Vuex or pass values through props/events if it becomes more complex.
constAccess={template:`
<div>
<Navigation/>
<h1>Access</h1>
<div class="error" v-if="validationErrors.length > 0" style = "color:red; margin-top: 2em; margin-bottom: 1em;">
{{ validationErrors }}
</div>
<div v-else style="margin-top: 2em; margin-bottom: 1em;"> </div>
<form @submit.prevent="validateAndSubmit" id="formLogin">
<div class="field section" style="margin-top:3em">
<label for="email" >Email:</label>
<input type="text" name="email" v-model="email" @input="validate" :disabled="authstatus"/>
<button style="margin-left:1em" type="submit" :disabled="authstatus">Access</button>
</div>
<div class="section">
<i v-if="authstatus">You have access! </i>
<i v-else>Verify email to gain access.</i>
</div>
</form>
</div>`,data(){return{email:"",validationErrors:[],};},computed:{authstatus(){// this way of sharing values is ok for small projects.
// large projects must use Vuex
returnthis.$root.authstatus;},urls(){// fetch URLs from root
returnthis.$root.apiurls;},},mounted(){this.fetchAuthStatus();// check auth on mount
},methods:{asyncfetchAuthStatus(){// triggered at load. Relevant only when you have a session.
// for JWT and similar: check against token, and pass refresh token if expired
console.log("Fetching auth status..");constres=awaitaxios.get(this.urls.authget,{crossdomain:true,});constauthData=res.data;console.log(`GET auth done!`,authData);if(authData["auth-status"]){console.log("Auth approved!");}else{// this is silent error since user will ..
// .. most likely not have access the first time the screen loads
console.log("Auth denied..! Re-login.");}this.$emit("auth-verified",authData["auth-status"]);},asynclogin(){try{// main login flow
constres=awaitaxios.post(this.urls.authpost,{email:this.email});constauthData=res.data;console.log(`POST auth done!`,authData);if(!authData["auth-status"]){this.validationErrors.push("Could not verify email. Validate and resubmit.");}this.$emit("auth-verified",authData["auth-status"]);}catch(e){this.validationErrors.push[e.message];console.error(e);}},validateAndSubmit(){// .. validate before sending the request off to server.
this.validationErrors=[];if(this.validate()){this.login();}},validate(){consterrors=[];constpattern=/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;if(!pattern.test(this.email))errors.push("Invalid email.");if(errors)this.validationErrors=errors;elsethis.validationErrors=[];return!this.validationErrors.length;},},};
Content
Create a new file content.vue.js.
Include -
template, data and methods
logic to call an external service to get posts
Like earlier, we will fetch variables from the root by using this.$root.
constContent={template:`
<div>
<Navigation/>
<h1>Content</h1>
<div class="error" v-if="validationErrors.length > 0" style = "color:red; margin-top: 2em; margin-bottom: 1em;">
{{ validationErrors }}
</div>
<div v-else style="margin-top: 2em; margin-bottom: 1em;"> </div>
<div class="field section" style="margin-top:3em">
<label for="keywords" >Keywords:</label>
<input type="text" name="keywords" v-model="keywords"/>
<button style="margin-left:1em" @click="">Search</button>
</div>
<div class="field section">
<label for="posts"><b>Posts</b></label>
</div>
<div class="posts" style="margin-left:30%; text-align: left!important">
<div v-for="(post,index) in posts" :key="index" >
{{post.id}}. {{post.description}}
</div>
<i v-if="!posts || posts.length == 0">No content found.</i>
</div>
<div class="section" style="margin-top:1em">
</div>
</div>`,data(){return{keywords:"",posts:[],validationErrors:[],currentPage:0,totalPages:0,};},computed:{authstatus(){// large projects must used Vuex. This will work for small components and data sets.
returnthis.$root.authstatus;},urls(){// a simple way to standardize URLs from root
returnthis.$root.apiurls;},},mounted(){this.fetchPosts();},methods:{asyncfetchPosts(){if(!this.authstatus)// authstatus check here is not quite required, but is just a fall-back
this.validationErrors.push("Not logged in. You have to login to fetch posts.");else{console.log("Fetching posts..");// keywords passed as query - as-is.
// verify whether server requires any specific format
constres=awaitaxios.get(`${this.urls.postget}?query=${this.keywords}&page=${this.currentPage+1}`,{crossdomain:true,});constpostData=res.data;console.log(`GET posts done!`,postData);if(!postData["auth-status"]){// auth-status is returned by post requests too!
console.log("Auth denied..! Re-login.");this.validationErrors.push("Access is denied.");}this.posts=postData["posts"]?postData["posts"]:[];this.currentPage=postData["currentpage"]?postData["currentpage"]:0;this.totalPages=postData["totalpages"]?postData["totalpages"]:0;// pagination logic yet to be implemented to navigate to subsequent pages
console.log(".. posts fetched!");}},},validateAndSubmit(){// there are no validations at this time.
this.validationErrors=[];this.fetchPosts();},};
<scriptsrc="./navigation.vue.js"></script><scriptsrc="./access.vue.js"></script><scriptsrc="./content.vue.js"></script><scriptsrc="https://unpkg.com/axios/dist/axios.min.js"></script><script>Vue.component("Navigation",Navigation);Vue.component("Content",Content);Vue.component("Access",Access);constroutes=[{path:"/",component:Access,},{path:"/content",component:Content,},];constrouter=newVueRouter({routes:routes,mode:"history",base:"/",});varapp=newVue({el:"#app",router:router,data(){return{authstatus:false,apiurls:{authget:"http://localhost:9000/auth",authpost:"http://localhost:9000/auth",postget:"http://localhost:9000/posts",},};},methods:{setAuthStatus(val){console.log("Setting authstatus through event to: ",val);this.authstatus=val;console.log("New value: ",app.authstatus);},},});</script>
And ta da.. your application is ready. Just serve the directory that has index.html. For e.g. we can use a quick http server using http-server:
npm i -g http-server
http-server
If you use mode: "hash", you can simply open index.html to see the magic.
How it works?
Parent HTML page instantiates Vue and mounts in on a div.
Three JS files that define Vue templates, methods, et al. as objects.
Component objects defined in the external JS files are imported and used in parent HTML page.
Using Vue from CDN can certainly play a role in super simple projects when there are people in the ecosystem who are not quite onboard with Vue, and want to understand how things work by seeing a simple project.
But, I find it cleaner to just use Vue CLI and its single file components. While Vue CLI does bring in complexity in build processes, the overall structure is easier to understand and demonstrate to a technical audience.