If you’ve dabbled with Vue.js 3’s <script setup>
, you might have run into the error: "script setup cannot contain es module exports". This error pops up when you try to export something from a <script setup>
tag, which is not allowed due to how the Vue’ script setup
works under the hood. Let’s explore why this happens, how <script setup>
operates, and how to properly expose data from your components using defineExpose
.
In Vue.js 3, Script Setup is a compile-time, simplified way to use the Composition API inside Single-File Components (SFCs). It’s designed to make your Vue components cleaner and more efficient, removing the boilerplate code you’d typically write in a standard <script>
block.
By default, components using <script setup>
are “closed”. This means that none of the data you define inside the component is accessible from the outside unless you deliberately expose it. Vue.js enforces this encapsulation to keep your component logic self-contained and modular. Here are the main mechanisms Vue.js provides to communicate between components:
If you tried to export data, functions, or anything using the export
keyword, Vue.js will throw the error because you’re breaking the encapsulation that it enforces.
Here’s an example that would trigger the “script setup cannot contain es module exports” error:
<script setup>
import { ref } from 'vue';
const count = ref(0);
export { count }; // ❌ This will cause the error
</script>
This also will fail:
<script setup>
import { ref } from 'vue'
export default { // ❌ This will cause the error
setup() {
const count = ref(0);
return { count }
}
}
</script>
In both of the above cases, you’ll notice that eslint is complaining and showing the error “<script setup>
cannot contain ES module exports”.
The reason these are failing is that <script setup>
is automatically scoped to the component’s instance, and it doesn’t allow exports as it’s not a traditional ES module. This is why the error “script setup cannot contain es module exports” will show up if we tried to export data.
To expose data from a component that uses <script setup>
, Vue 3 provides the defineExpose macro. This macro allows you to explicitly define what parts of your component’s internal state or methods should be accessible via useTemplateRef() or $parent chains.
Let’s start by looking at how to fix the above error using defineExpose
:
<script setup>
import { ref } from 'vue'
const count = ref(1)
defineExpose({ count })
</script>
In this example, count
is now exposed to the parent component through template refs useTemplateRef()
, allowing it to be accessed outside the component instance in a controlled manner. Notice how defineExpose
acts as a middleman, explicitly defining what can be shared, unlike the export
statement which tried to force data outside the component.
defineExpose
WorksdefineExpose
allows you to selectively expose properties, methods, or any data declared inside the <script setup>
block. This gives you full control over what can be accessed from the outside.
Here’s a more detailed example, where we expose multiple values, including a reactive ref
and a computed property:
<script setup>
import { ref, computed } from 'vue';
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
defineExpose({
count,
doubleCount,
increment
});
</script>
Now, a parent component can access count
, doubleCount
, and increment()
through a template ref:
<template>
<ChildComponent ref="child" />
<button @click="child.increment">Increment</button>
<pre>{{ child }}</pre>
</template>
<script setup>
import { useTemplateRef } from 'vue'
import ChildComponent from './components/ChildComponent.vue'
const child = useTemplateRef('child')
</script>
With defineExpose
, you have fine-grained control over what data or function is accessible outside the component, ensuring you maintain proper encapsulation while still providing flexibility.
Note that useTemplateRef is only available in versions (^3.5.*)+. If you’re using an older version, make sure to use the old syntax for template refs in Vue.js.
One important aspect to understand is how defineExpose()
works in relation to component access. defineExpose()
exposes properties on the component's template ref, not on the component definition imported from the .vue
file.
To clarify, when you use defineExpose()
, the exposed properties are not added to the component definition itself (i.e., the object you import when you import a component in JavaScript). Instead, these properties are exposed on the instance of the component when accessed via a useTemplateRef()
.
defineExpose
defineExpose
is particularly useful when you need to expose internal methods or state to parent components for complex interactions or custom component libraries. For example:
Vue 3’s <script setup>
is powerful but comes with a unique set of rules. The error "script setup cannot contain es module exports" happens because <script setup>
does not support traditional export
statements, as it is designed to keep the component's scope encapsulated. To expose internal data or methods from the component, the right way is to use defineExpose
.
defineExpose
: When you need to share internal state with the outside world, defineExpose
provides a clear, declarative way to do so.By understanding this pattern, you’ll avoid common pitfalls and write more maintainable, scalable Vue components.
Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.