Web
Embedding a built Vue app in a Hugo post
I have a toy Vue.js project built using node.js, Vue CLI, and Webpack. The Vue CLI build script outputs a folder (called dist/
by default) that contains an index.html
file as well as js/
and css/
subdirectories that hold all the files index.html
links to.
I tested two primary ways of embedding this type of Vue app into a Hugo blog post:
- Copying the guts of the app’s
index.html
and pasting them into the post’s.md
file., and - Using an HTML
<object>
or<iframe>
element to embed the Vue app’s entireindex.html
in the page.
Preparation
The folder containing the app can be kept either in the Hugo site’s static/
directory (or a subdirectory of it), or in the page bundle (i.e. the directory) containing the post.
The optional vue.config.js
file can be used to set two handy options before building the Vue project:
publicPath
sets the path of all the links within the generatedindex.html
, telling the browser where to find them.outputDir
names the folder that holds the built app files (includingindex.html
). IfoutputDir
isn’t set, the folder will be calleddist/
.
These can also be changed by hand after the app is built.
Here is an example of a vue.config.js
file used for one of the above scenarios:
// vue.config.js
module.exports = {
publicPath: '/vue/chooser-horiz-scroll/',
outputDir: 'chooser-horiz-scroll'
}
General workflow
- Edit
vue.config.js
to setpublicPath
andoutputDir
. - Build the app:
npm run build
. - Copy the generated directory to the desired location in the Hugo project’s directory structure.
- Copy or embed (the important parts of) the app’s
index.html
file into the Hugo post.
1. Pasting the links and <app>
element into the blog post
Using this method, the Hugo page becomes the document
as far as the app’s code goes. This could cause unanticipated behaviour related to, e.g., CSS and event listeners, set in the Vue app.
Using absolute path | Using relative path | |
---|---|---|
Location of chooser-horiz-scroll/ |
/static/vue/ |
Same directory as post’s .md file |
publicPath option (set in vue.config.js ) |
/vue/chooser-horiz-scroll/ |
./chooser-horiz-scroll/ |
Notes | When the code is within the blog post, any relative links need to be with respect to the location of the post’s Markdown file. |
Paste the needed content of index.html
into the post in order to bring in the other files from the app. We don’t need the <head>
, <body>
, or <meta>
tags, nor do we need the favicon link, but the other <script>
, <link>
, and <noscript>
tags stay.
In my case the result looked like:
<link href=/vue/chooser-horiz-scroll/css/app.048bd1fd.css rel=preload as=style><link href=/vue/chooser-horiz-scroll/js/app.5614f1b8.js rel=preload as=script><link href=/vue/chooser-horiz-scroll/js/chunk-vendors.95b82d44.js rel=preload as=script><link href=/vue/chooser-horiz-scroll/css/app.048bd1fd.css rel=stylesheet><noscript><strong>We're sorry but chooser-horiz-scroll doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/vue/chooser-horiz-scroll/js/chunk-vendors.95b82d44.js></script><script src=/vue/chooser-horiz-scroll/js/app.5614f1b8.js></script>
Because the generated files have different names after every build, updating the post after a change to the app requires (1) copying chooser-horiz-scroll/
into the Hugo project, and (2) replacing the HTML within the Markdown file for the post. Perhaps one could write an elaborate shortcode using readFile
to read index.html
, strip it of the unwanted bits and incorporate it into the post.
2. Embedding index.html
itself using <object>
or <iframe>
Embedding a Vue.js app using <object>
or <iframe>
isolates the app in its own document
.
I wrote Hugo shortcodes to use the HTML <object>
element to embed the app.
For apps located in the /static/vue/
directory:
{{/* vueobject.html */}}
{{- $appDirName := .Get 0 -}}
{{- $fullPath := (printf "%s%s" "/vue/" $appDirName) -}}
<object data="{{$fullPath}}" class="objectdata-localvue">Warning: file {{$fullPath}} could not be included.</object>
In use, it looks like (assuming the Vue CLI output directory is chooser-horiz-scroll
):
{{< vueobject "chooser-horiz-scroll" >}}
For apps located in a subdirectory of the post’s branch bundle:
{{/* relvue.html */}}
{{- $appDirName := .Get 0 -}}
{{- $fullPath := (printf "%s%s%s" "/" .Page.File.Dir $appDirName) -}}
<object data="{{$fullPath}}" class="objectdata-localvue">Warning: file {{$fullPath}} could not be included.</object>
Use it like this:
{{< relvue "chooser-horiz-scroll" >}}
Using absolute path | Using relative path | |
---|---|---|
Location of chooser-horiz-scroll/ |
/static/vue/ |
Same directory as post’s .md file |
publicPath option (set in vue.config.js ) |
/vue/chooser-horiz-scroll/ |
./ |
Shortcode | vueobject |
relvue |
Notes | Requires calling the post file _index.md , making the post directory a Hugo branch bundle. Not ideal because this makes the post look an empty section in my menus. Further understanding of page resources in a leaf bundle might reveal a way to avoid this. |
Updating after rebuilding the app means simply copying the folder into the Hugo project, with no changes to the post.
Using <iframe>
instead just requires changing <object data="{{$fullPath}}"
to <iframe src="{{$fullPath}}"
. I had both working, and it’s not yet clear to me which, if either, is better practice. Many Hugo shortcodes use <iframe>
to embed external content (such as videos).