This content originally appeared on DEV Community and was authored by The Jared Wilcurt
“The web is so complex now”
“Ugh, I wish I didn’t need all these different tools and build steps just to make a website”
“But I don’t want to give up all the convenience I’m used to”
Sure, that’s enough strawmen to pretend to justify this sillyness.
Vue invented this phenomenal idea of a “Single file component”. Where you break up your app into UI component chunks (separation of features), and within each component, there are 3 dedicated places for the 3 languages of the web. Each with their own focus (separation of concerns).
Technically you can use Vue via a CDN and it works, but out of the box, they don’t have a way to support these wonderful .vue
files in the browser. So we need to pull in a library that can dynamically download .vue
files on the fly, and also process them to regular JS render functions that the Vue runtime can handle. Fortunately this library does exist, however there are some caveats to working with this approach.
Below I’ve outlined a basic “Hello World” setup, with comments pointing out things that work differently when you run everything directly in the browser without a build step.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Hello, World</title>
<link rel="stylesheet" href="/styles/main.css" type="text/css" />
<!-- FROM: cdn.jsdelivr.net/npm/vue@3.5.17/dist/vue.global.prod.js -->
<script type="text/javascript" src="/vendors/vue.global.prod.js"></script>
</head>
<body>
<div v-cloak id="app" class="container">
<!--
All HTML tags must be lowercase.
Component names must be two words and hyphenated so the browser can
differentiate them from real HTML tags, which are always one word.
All components must have ending tags.
BAD: <Logo />
BAD: <SiteLogo />
BAD: <site-logo />
BAD: <logo></logo>
GOOD: <site-logo></site-logo>
-->
<site-logo></site-logo>
<h2>Hello, World</h2>
</div>
<!-- FROM: cdn.jsdelivr.net/npm/axios@1.10.0/dist/axios.min.js -->
<script type="text/javascript" src="/vendors/axios.min.js"></script>
<!-- FROM: cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js -->
<script type="text/javascript" src="/vendors/vue3-sfc-loader.js"></script>
<script type="text/javascript" src="/scripts/helpers.js"></script>
<script type="text/javascript" src="/scripts/app.js"></script>
<link rel="stylesheet" href="/styles/fonts.css" />
</body>
</html>
/* /scripts/helpers.js */
// Abstract away the component import process to simplify it
window.httpVueLoader = function (componentPath) {
const sfcLoaderOptions = {
moduleCache: {
vue: Vue
},
getFile: async function (url) {
const response = await fetch(url);
if (!response.ok) {
throw Object.assign(new Error(response.statusText + ' ' + url), { response });
}
return await response.text();
},
addStyle: function (textContent) {
const style = Object.assign(document.createElement('style'), { textContent });
const reference = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, reference);
}
};
return Vue.defineAsyncComponent(function () {
return window['vue3-sfc-loader'].loadModule(componentPath, sfcLoaderOptions);
});
};
/* /scripts/app.js */
const app = Vue.createApp({
components: {
// Importing child components is a little different
'site-logo': httpVueLoader('/components/site-logo.vue')
},
// But the rest of the component is normal
data: function () {
return {};
},
methods: {},
computed: {},
created: function () {}
});
<!-- /components/site-logo.vue -->
<template>
<div>
<a href="/index.html">
<img
src="/imgs/logo.png"
alt="Hello World logo"
class="wordmark"
/>
</a>
</div>
</template>
<!--
All the code you write in your components needs to be browser-safe.
Meaning you must use only HTML, CSS, and JavaScript (no meta-languages).
Also you can only use language features supported in the browser, there
are no build tools to polyfill, vendor-prefix, or transpile the syntactic sugar for you.
-->
<script>
export default {
name: 'site-logo'
};
</script>
<style scoped>
.wordmark {
height: 200px;
}
</style>
The above example doesn’t show Vue-Router or Pinia, but they both have a CDN approach officially documented and can be pulled in normally.
Also, in the same spirit, you could pull in a library like marked.js
to convert markdown files on the fly to HTML in the browser.
This means your entire project has no build step, but you can still break up your code into modular chunks and dynamically load them in, processing them at runtime to generate the page content on the fly.
The upsides:
- You can statically host these files and they just work
- You can edit the files on the server and they are live instantly, no build step or processing required.
- Though if you are hosting your static site on GitHub Pages, it lets you create a GitHub Action to do the build step on every commit anyways, which would result in a much better user and dev experience. But we aren’t here for those practical solutions.
- No CLI tooling or dependency management
- No build config files to deal with
- Working with the real languages right in the browser, like the good ol’ days
- When it comes time to update dependencies, you just change the version number in the URL (if you are pointing to the CDN directly. My examples show them as downloaded to a vendors folder which is better for performance, but lol, nothing else about this is, so who cares)
- Converting this over to a system with build tools is pretty straight-forward. Really it’s just changing the imports in each file, and the basic setup. Component logic doesn’t change at all.
The downsides to this is are:
- You send much more data than if you used a build step.
- The page has to “build” at runtime
- Content loads dynamically in chunks rather than in optimized bundles
- SEO is much harder
- If you make a mistake you have no tools to catch you.
- If you do use tools, like a linter, you’ll need to adjust linter settings to work within the limitations of this approach.
- The projects this works best on are Websites (not webapps), that are mostly static anyways. And could probably just be written in plain HTML and CSS without any JS at all.
- and surrrrrre we could use a Static Site Generator (SSG) like VitePress, but that involves a build step! what are you crazy? this convoluted, inefficient approach is so much more con-veeeeen-yent.
lol don’t actually do this, but also do it, I don’t care, I’m not your dad
This content originally appeared on DEV Community and was authored by The Jared Wilcurt