vuejs / vue-loader

📦 Webpack loader for Vue.js components
MIT License
4.98k stars 913 forks source link

Can't build SFC containing Custom Block with SFC as `src` #1344

Open sheijne opened 6 years ago

sheijne commented 6 years ago

Version

15.2.4

Reproduction link

https://gitlab.com/ovenwand/VueLoaderBug

Steps to reproduce

  1. Run npm install to install all the build dependencies
  2. Run npm start to run webpack-dev-server
  3. See the error in your console.

What is expected?

The SFC should be passed to the custom loader which should do all kinds of beautiful magic (what the loader does is unrelated).

What is actually happening?

The SFC is always passed to vue-loader first, and as soon as the SFC is passed to vue-loader an error is thrown:

Module build failed (from ./node_modules/vue-loader/lib/index.js):
TypeError: Cannot read property 'content' of undefined

Which happens because it tries to load the src as a Custom Block even though it is really just a plain old SFC without any additives.


Use case (not included with the repo): I am trying to parse some example blocks, which use the src property to load an SFC. Inside the loader I register the component and pass the source to another component. I do this in a similar way as VueMaterial does (https://github.com/vuematerial/vue-material/blob/dev/build/loaders/component-example-loader.js).

I've tried messing with my webpack configuration, none of my attempts have succeeded to achieve my goal (I can elaborate if needed, long story...).

libertyswift commented 5 years ago

I have the same mistake. There are ways to solve it?

sheijne commented 5 years ago

I haven't been able to find a fix to this problem. However I ended up using the following (alternative) code, which works mostly fine:

example-loader.js

const fs = require('fs');
const path = require('path');
const { transform } = require('@babel/core');
const { parseQuery } = require('loader-utils');
const babelrc = require('../../package').babel;

const transpile = (code) => transform(code, babelrc).code;

module.exports = function(source, map) {
    this.cacheable && this.cacheable();

    const query = parseQuery(this.resourceQuery);
    const fileDir = this.resourcePath.replace(path.basename(this.resourcePath), '');
    const filePath = path.resolve(fileDir, query['example-src']);
    const fileName = path.basename(filePath).replace('.vue', '');
    source = fs.readFileSync(filePath, 'utf8');

    const code = `
        import Vue from 'vue';
        import ExampleLoading from 'docs/components/ExampleLoading';

        export default function (Component) {
            const asyncComponent = () => ({
                component: import('${filePath}'),
                loading: ExampleLoading,
                delay: 0,
            });

            Vue.component('${fileName}', asyncComponent);

            Component.options.examples = Component.options.examples || {};
            Component.options.examples['${fileName}'] = {
                name: '${fileName}',
                source: ${JSON.stringify(source)},
            };
        }
    `;

    this.callback(null, transpile(code), map);
};

webpack.config.js

module: {
    rules: [
        {
            resourceQuery: /blockType=example/,
            loader: require.resolve('./loaders/example-loader'),
        },
    ],
},

component.js

<!-- using 'example-src' instead of 'src' -->
<example example-src="./examples/ComponentExample.vue/>

<template>
    <!-- example components available in $options.examples -->
    <component :is="$options.examples.ComponentExample"/>
</template>

I also made a mixin that maps the examples to a computed prop.

Should've posted this here before, hope it helps you

hastom commented 4 years ago

Is this ever gonna be fixed?