Johannes Stricker
Johannes Stricker

Reputation: 1781

Prepending custom loader to vue-loader breaks when template includes custom tags

I have a vue2 component that looks like this:

<template>
  <p>Hello world</p>
</template>

<script>
export default { name: 'Example' };
</script>

<docs>
Some documentation...
</docs>

I also want to use an additional loader for my vue files, so my webpack config looks like this:

module.exports = {
  module: {
    rules: [{
      test: /\.vue$/,
      use: [{
        loader: 'vue-loader',
      }, {
        loader: path.resolve(__dirname, 'CustomLoader.js'),
      }],
    }],
  },
};

with

// CustomLoader.js
module.exports = function(source) {
  return source;
}

Running webpack bundle throws an error that it's missing a loader for the <docs> block, even though the CustomLoader returns the source code unchanged. Something along the lines of:

Error in path/to/component.vue?vue&type=custom&index=0&blockType=docs
Module parse failed: Unexpected token
File was processed with these loaders:
* ./node_modules/vue-loader/lib.index.js
* ./CustomLoader.js
You may need an additional loader to handle the result of these loaders.

Removing either of

Can anyone tell me what the problem is?

Upvotes: 2

Views: 613

Answers (1)

Johannes Stricker
Johannes Stricker

Reputation: 1781

I've found the answer to this myself, after digging through the source code of vue-loader. Broadly speaking, vue-loader checks if another loader for vue files exists. If there is no other loader, then it throws the content of the custom block away. Otherwise, it will return the content, expecting that the other loader knows what to do with it. That's why adding another loader, even one that does nothing, causes the build to fail.

If you want to better understand what's happening check out the vue-loader source, specifically:

To understand how to get around this, you must first understand how vue-loader parses the template. It replaces each block with an import statement and an inline loader chain. So this

<template>
  <!-- ... -->
</template>

<script>
// ...
</script>

<docs>
// ...
</docs>

becomes this

import render from 'custom-loader!vue-loader!source.vue?vue&type=template'
import script from 'custom-loader!vue-loader!source.vue?vue&type=script'
import 'custom-loader!vue-loader!source.vue?vue&type=custom&blockType=docs'

So essentially, the custom loader will get called multiple times for each .vue file. Once on the first pass, then once again for each block that was expanded.

My workaround was to check the resourceQuery in my loader. If it points to a custom block I just return an empty template. Something like this:

// CustomLoader.js
module.exports = function(source) {
  if (this.resourceQuery.includes('type=custom')) {
    return '<custom></custom>';
  }
  return source;
}

What's important is that you return something that vue-loader (which uses the vue template compiler) is able to parse. Therefore the <custom> tag instead of an empty string.

Upvotes: 1

Related Questions