Title needs work, idk what to call this.
Writing a component library with typescript requires importing Vue so you can use Vue.extend(...) to get typings. This causes problems when webpack decides to load a different instance of vue.
See https://github.com/vuetifyjs/vuetify/issues/4068
Local registration to accept a function that synchronously returns a component, calling it with the parent vue instance.
The library can then do:
export default function MyComponent (Vue: VueConstructor) {
return Vue.extend({ ... })
}
And be used like:
import MyComponent from 'some-library'
export default {
components: { MyComponent }
}
Of course that would then cause other problems, particularly where we use methods directly from other components. Maybe something that adds types like Vue.extend() but doesn't have any runtime behaviour would be better instead?
// When used, this will behave the same as a bare options object, instead of being an entire vue instance
export default Vue.component({
...
})
You may want to use https://github.com/ktsn/babel-plugin-remove-vue-extend
馃憣
How does that handle stuff like mixins (usage) or directly calling methods?
EDIT: Didn't rtfm, this looks perfect. I'll have to try it out to see how well it actually works though. I might have to massage the types a bit to get #2 working.
@ktsn can we close this or is there anything we could do in Vue core to improve the situation?
The plugin doesn't work on everything, it seems to assume that all exports are Vue.extend()

EDIT: Or not, I still get the same error if I wrap that component.
I'm fine with having to use Vue.extend, it's just not ideal that it creates a standalone instance. It would be nice if vue still resolved component references and everything else the same way as with bare options objects. It isn't really a problem anyway unless one of our users messes up and somehow ends up with two different vue imports.
Alternatively we may try to detect duplicate Vue imports.
Yeah that would be helpful, the current errors don't really indicate what's actually going on very well. Is our current setup likely to be a problem with vue-test-utils and createLocalVue though? vuejs/vue-test-utils#532 seems similar to this one.
A few months ago I have asked for help with an issue related to this one on stackoverflow.
I've been living with my temporary solution since then (passing the Vue instance to the library initializer, and not importing Vue in the library), but I'm not very happy about it and now I'm stuck because Vuetify also started to use typescript and creates a separate Vue instance and it's not clear how to tell to webpack (or to Vue) that it's always the same instance that has to be used.
The solution provided at the top of this thread does not help me much, since I'm not using babel.
Are you aware of an existing solution to have a package and dependencies, all written in typescript and compiled with webpack, to share the same Vue instance?
The solution should also allow to use .vue files in packages, which, with the original proposal at the top of this thread would probably not be possible.
After giving a hard look at the problem, the only solution that I found at the moment (with webpack) is to add vue as an external import wherever you import it.
In webpack.config.js
externals: {
vue: 'Vue'
},
and then import vue.js with a script tag (or an equivalent solution) in your html file.
This avoids that vue is instantiated more than once. Depending on your package and subpackage structure, you probably need to import your subpackages and or vuetify also as external modules.
That means that you have to pack them as standalone libraries, with, in webpack.config.js
````
output: {
path: 'path/to/your/output',
filename: 'build.js',
library: 'LibraryName',
},
and in tsconfig.json
"compilerOptions": {
...
"module": "es2015"
...
}
````
As a side effect with this solution you can also work with vue files in your libraries. The proposed solution at the top of this issue would in any case not allow that, because you cannot inject the vue constructor in a vue file (as far as I understand).
If you are working with tests suites like karma and phantomjs you need to inject vue and the other external libraries there as well.
This is now a workaround and a better solution would be if vue would have at initialization an option to detect if there is another VueConstructor outside the actual bundle and use it (as an option, because that may or may not be desired).
I use this to make sure the same file is always loaded, you can omit the path.resolve if there's no symlinks or other weird directory setup being used.
resolve: {
alias: {
'vue$': path.resolve(__dirname, '../node_modules/vue/dist/vue.runtime.esm.js')
// 'vue$': 'vue/dist/vue.runtime.esm.js'
}
}
I had the "duplicate import" problem in a monorepo project using Lerna, and it seems to be related to the same problem when using npm link or yarn link. My project has a shared component package that multiple apps import, including an Electron app and a browser extension. It seems that lerna bootstrap uses symlinks, ultimately leading to some bundle confusion, in which Vue is separately imported for the main repo and the dependency, thereby causing the '$listeners and $attrs are readonly' errors.
From what I've found, the common generalized solution seems to be to mark your dependency as an 'external' in whatever build configuration you're using.
I'm using Electron with electron-builder and @vue/cli. I solved this problem by adding this to my vue.config.js:
module.exports = {
pluginOptions: {
electronBuilder: {
// List native deps here if they don't work
externals: ['my-shared-components'],
}
}
}
Most helpful comment
Alternatively we may try to detect duplicate Vue imports.