0

I try to import all the components of a folder, and show one of them depending of a prop passed.

I use webpack with vue-loader to import all my components. Every component is a *.vue file.

The problem is that by importing some of my components stored in a subfolder, I got this error at runtime :

[Vue warn]: Failed to mount component: template or render function not defined.

found in

---> <Test2>
       <VoneDocs> at src\components\VoneDocs.vue
         <App> at src\App.vue
           <Root>

After reserach and the help of @craig_h I found that the problem come from the way I import my files :

<template>
  <transition name="fade">
  <div class="vone-docs" v-if="docName !== undefined">
    <component :is="docName"/>
  </div>
  </transition>
</template>

<script>
import Test from '../assets/docs/Test';

// import all docs (*.vue files) in '../assets/docs'
let docsContext = require.context('../assets/docs', false, /\.vue$/);
let docsData = {}; // docsData is {...<filenames>: <components data>}
let docsNames = {};
let docsComponents = {};
docsContext.keys().forEach(function (key) {
  docsData[key] = docsContext(key); // contains [{<filename>: <component data>}]
  docsNames[key] = key.replace(/^\.\/(.+)\.vue$/, '$1'); // contains [{<filename>: <component name>}]
  docsComponents[docsNames[key]] = docsData[key]; // contains [{<component name>: <component data>}]
});

export default {
  name: 'vone-docs',

  props: ['page'],

  components: {
    ...docsComponents,
    Test
  },

  computed: {
    docName () {
      return this.page;
    },

    docFileName () {
      return './' + this.docName + '.vue';
    },

    docData () {
      return docsData[this.docFileName];
    }
  },

  beforeRouteUpdate (to, from, next) {
    if (to.path === from.path) {
      location.hash = to.hash;
    } else next();
  },

  mounted () {
    console.log(docsComponents);
  }
};
</script>

While my Test component is successfully displayed when docName is 'test' (because it's directly imported), every another Vue single-file-component imported with require.context() leads to the error : Failed to mount component: template or render function not defined.

Is there anything I wrote wrong with my require.context() ?

Here is my webpack configuration (except the use of raw-loader and html-loader, it is the same as Vue webpack-template's one).

// webpack.base.conf.js
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    app: './src/main.js'
  },
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  module: {
    rules: [
      ...(config.dev.useEslint? [{
        test: /\.(js|vue)$/,
        loader: 'eslint-loader',
        enforce: 'pre',
        include: [resolve('src'), resolve('test')],
        options: {
          formatter: require('eslint-friendly-formatter'),
          emitWarning: !config.dev.showEslintErrorsInOverlay
        }
      }] : []),
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test')]
      },
      {
        test: /\.(png|jpe?g|gif)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      // Art SVG are loaded as strings. Must be placed in the html with `v-html` directive.
      {
        test: /\.raw\.svg$/,
        loader: 'raw-loader'
      },
      // Icon SVG are loaded as files like regular images.
      {
        test: /\.icon\.svg$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(html)$/,
        use: {
          loader: 'html-loader',
          options: {
            attrs: [':data-src', 'img:src']
          }
        }
      }
    ]
  }
}

Thanks for your help !

2
  • What does your main vue instance look like? Have you defined a render function? Commented Nov 25, 2017 at 10:42
  • I edited the post ! ;) I use only *.vue file which don't require render functions (as far i know), and a string-template in my main Vue instance. Commented Nov 25, 2017 at 13:38

2 Answers 2

0

Ah OK, if you're using the build without the template compiler, you cannot use the template property. What you need to do instead is mount your base component (the one with router-view in it) to your main view instance using a render function:

import App from './components/App.vue'

new Vue({
  el: '#app',
  router,
  render: h => h(App) // This mounts the base component (App.vue) on to `#app`
})

Remember that your base component should also be a .vue file.

I wrote a fairly detailed answer on setting up a Vue SPA the other day which might help you out: vue-router how to persist navbar?

Sign up to request clarification or add additional context in comments.

4 Comments

I'm confused, fix it like you say doesn't weem to change anything :/ By the way, in my Webpack configuration (which is Vue's webpack-template one), Vue as loaded as 'vue/dist/vue.esm.js', the full version (compiler+runtime)
Those templates work out of the box, so something else is wrong. It's going to be tough to know exactly where, but I would start by removing the id="app" from App.vue because that id will also be used in index.html which may be causing a conflicts.
Yes you're right ! I fixed this but it's not really what that brokes the docsComponents
I've tested things and I'm pretty sure the problem came from my require.context() block of code. I explained it in the main post.
0

Okay I finally solved the problem.

In https://forum.vuejs.org/t/vue-loader-with-webpack-not-supporting-commonjs-require/15014/4, Linus Borg says that with vue-loader doesn't normalise exports.

let docsData = {};

function importAllDocs (r) {
  r.keys().forEach(function (key) {
    docsData[key.replace(/^\.\/(.+)\.vue$/, '$1')] = r(key).default;
  });
}

importAllDocs(require.context('../assets/docs', true, /\.vue$/));

Access r(key).default instead of r(key) solved the problem.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.