Runtime Error integrating a component lib that uses @vue/composition-api: 'You must use this function within the “setup()” method'

Hi all,

any help with the following problem would be greatly appreciated! :slight_smile: :pray:

Situation:

My project contains two packages:

  • child-component-lib
    • contains a single view About.vue written in composition-API-style (with vue2 helper libraries @vue/composition-api and vuex-composition-helpers)
    • exports a single RouteConfig (path: "/" --> component: "About.vue")
    • build as a lib (with vue-cli-service build --target lib…)
  • parent-app
    • imports the route from child-component-lib into its router
    • contains a simple view that displays one line of text and a <router-view />

Expected behavior: It works without problems.

Actual behavior: I see an error output in the console. [Vue warn]: Error in data(): "Error: You must use this function within the "setup()" method, or insert the store as first argument." The error message is misleading, because the error is actually thrown inside setup() method. It can be traced back to getCurrentInstance() returning undefined (inside @vue/composition-api).

Investigation:

  • It turns out that the error disappears when I include the same About.vue in the parent-app itself (just switch the route, to try it out), i.e., it works when we avoid the import from the built library.
  • So it looks like it’s a problem with the build setup
    (one of vue.config.js, webpack, babel, typescript, …)

Reproduction Example:

mkdir temp
cd temp
git clone git@github.com:tholst/vue-composition-api-comp-lib.git
cd vue-composition-api-comp-lib/child-component-lib
npm install
npm run build
cd ../parent-app/
npm install
npm run serve

Error Screenshot

I don’t think this is a Vue problem. I think it’s a dependencies problem. Which is a shame, because I can fix Vue problems but I have no idea how to fix dependency problems like this.

The problem is that you have two copies of @vue/composition-api. One is coming from child-component-lib and the other from parent-app. That won’t work, they need to share the same copy of that package. Otherwise getCurrentInstance can’t work. One of them has the instance set and the other doesn’t. If you take a look inside app.js and chunk-vendors.js and search for function getCurrentInstance you should find one in each.

That’s all I’ve got. I don’t know how to fix it.

1 Like

Thanks for your comments @skirtle!

Yes, I was suspecting the same (among many other things). I tried to to use webpack externals but it didn’t change anything.

configureWebpack: {
    externals: {
        "@vue/composition-api": "@vue/composition-api",
        "vuex-composition-helpers": "vuex-composition-helpers"
    } 
...

It must be something in the dependencies or build setup. :confused:

I tried something similar. I managed to get the external libraries out of the .js files in the dist folder. That made one of the getCurrentInstance functions jump from app.js to chunk-vendors.js, so both were in the same file. It felt a bit like progress but made no difference to the final outcome.

1 Like

I finally understood what the problems were. @skirtle, turns out we were on the right track!

First, there was the actual problem. Second, there was a problem (in the local development setup) that made the solution look like it was not working, when it actually was working.

The Actual Problem + Solution

The child-component-lib was bundling their own versions of the npm packages @vue/composition-api and vuex-composition-helpers. This had the following effect: When I was running the parent-app there were actually two instances of those libraries and the vue component from the child-component-lib was accessing the wrong object that had not been properly initialized.

The solution was to prevent the bundling of those libraries in the child-component-lib, by

  1. making them devDependencies and peerDependencies.

  2. instructing webpack not to bundle them on npm run build.

    package.json

    "dependencies": {
        ...
    },
    "devDependencies": {
        "@vue/composition-api": "^1.0.0-beta.19",
        "vuex-composition-helpers": "^1.0.21",
        ...
    },
    "peerDependencies": {
        "@vue/composition-api": "^1.0.0-beta.19",
        "vuex-composition-helpers": "^1.0.21"
    },
    

    vue.config.js

    configureWebpack: {
        externals: {
            "@vue/composition-api": "@vue/composition-api",
            "vuex-composition-helpers": "vuex-composition-helpers"
        },
        ...
    }
    

The Tricky Problem that Made Things Difficult

I was trying to fix this problem locally, without actually publishing the package. And it seemed to work, because I was seeing the same problem locally that I also saw in the published packages.

I did local development by directly linking the parent-app and child-component-libs. I tried both

  1. a direct folder dependency

    package.json

    "dependencies": {
        "@tholst/child-component-lib": "file:../child-component-lib",
    },
    
  2. npm link

    cd child-component-lib
    npm link
    cd ../parent-app
    npm link @tholst/child-component-lib
    

Both approaches have the effect that they actually import (=symlink to) the child-component-lib’s folder with all files and folders (instead of only the files that would be published in the npm package).

And that meant the following: Even though I had excluded the two composition-API libs from the bundle and made them dev/peer dependencies (see solution to actual problem), they were still installed and present in the child-component-lib’s node_modules. And that node_modules folder was symlinked into the parent-app package. And in this way the child-component-lib still had access to their own copy of the libraries that we wanted to exclude from the build (see actual problem). And I was still seeing the error as before.

And this way my local development approach obscured the fact that the solution to the actual problem was actually working.

The first part seems like what I had but I’m still not really clear about how to solve the second part. How do you import the local dependency for development without publishing it somewhere?

I also don’t really follow why it was pulling in the dev/peer dependencies when building the parent-app. I get that they’re in the node_modules folder but why does that cause it to get pulled into the build when there’s already another copy?

I mean, if you’re happy to give up on local development then you can avoid using webpack for the library altogether and use Rollup instead. Generally, Rollup is better suited to library development, which is one reason why it’s so difficult to find good examples of how to use webpack for this kind of thing.

Yeah, I am also wondering about both of your questions.

  1. At least I could use the local dependency after deleting the node_modules folder in its folder (not the parent’s).
  2. Maybe I was missing some webpack configuration in the parent-app that would prevent resolving imports to local node_modules (of the child component) and use the parent’s node_modules instead. I was also quite suprised to see this. I found it out while debugging in chrome and checking the files’ paths.

I may give rollup a try. Thanks for the advice.

Thanks! :slight_smile: