Importing iframe with Webpack and Vue.js
I've spent hours on a webpack + Vue.js + iframe issue yesterday. As I don't want all those hours to be completly wasted, I'm going to document my issue and the final solution.
The problem
I'm working on a Vue.js application, using Webpack for building all the assets.
One of the pages need to include an iframe, loading a stand-alone
keen-explorer.html file.
My problem was that the keen-explorer.html was not included in the final build
and resulted in a 404.
Context: Why do I need to do that?
I need to instanciate a Keen.io explorer dashboard from inside my app. The
keen-explorer JavaScript module cannot be imported from a script, as far as
I can tell. It needs to be loaded inside the global window object, along with
its dependencies (Bootstrap and jQuery).
I tried different ways to include it in my final Webpack build, but the iframe was the best solution I could find as it will isolate all the external dependencies from the rest of my app. Anyway, back to the problem at hand.
Naive approach and first 404
I'm using vue-loader to parse the content of my .vue files. Here is my
template (using pug):
iframe(src="html/keen-explorer.html")
If I run webpack, if finishes without any error, but when loading the
application, I have a 404 as html/keen-explorer.html cannot be found. Webpack
did not include it in the final build and considered the src as any other
attribute with no particular meaning.
Making vue-loader require iframe sources
Turns out that vue-loader does not automatically import the iframe[src]
values by default. You have to update your webpack config to define what it
should parse:
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
transformToRequire: {
iframe: 'src',
},
},
},
],
}
Now, if I rerun webpack, I have an error because webpack can't find
html/keen-explorer.html. We're making progress, at least it tries to import
it.
Fixing the filepath issues
vue-loader seems to treat all assets in the template as relative to the final
output, while here I'm trying to reference a file present in my src/html
directory.
First thing to do was to define a resolve alias in webpack to tell it where to
look for files that starts with html/ using the following config:
resolve: {
alias: {
html: path.resolve(__dirname, 'src', 'html'),
},
},
Then, you should not forget to prepend your src value with a ~ in the
template, to tell vue-loader that this should be resolved as an import.
iframe(src="~html/keen-explorer.html")
Rerunning webpack results in no more filepath errors. The file is found and included in the final build.
Problem is... the filepath has been replaced by the content of the file, not
its path in the final build. That means that my iframe is now trying to load
<doctype>[...], which does not work at all.
Replacing the file with its built filepath
We're approaching the end and the solution is near. The last thing missing at that point is a way to replace the imported file by its built filepath in the final output.
Thankfully, that's what the file-loader loader is for:
module: {
rules: [
{
test: /\.html$/,
use: [
{
loader: 'file-loader',
options: {
name: '[hash].[ext]',
},
}
],
},
],
},
Doing so I can have an iframe with a src set to the relative filepath to the
built html file.
Conclusion
It took me about 25mn to write this article, but more than 6h to hunt and debug this issue from start to finish (not counting all the wrong leads trying to embed the iframe in base64).
To recap, if you want to load a standalone iframe from your Vue.js application suing webpack you need to:
- Configure
vue-loaderso if followsiframe[src]attributes - Prepend
~to yoursrcvalues - Configure the resolve alias so filepath can be resolved by webpack
- Add a
file-loaderto process.htmlpages