Home / Blog / How to fix: “script setup cannot contain es module exports” error
How to fix: “script setup cannot contain es module exports” error

How to fix: “script setup cannot contain es module exports” error

Mostafa Said
Mostafa Said
Updated: October 14th 2024

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.

Why Does This Error Happen?

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:

  • Props: Passing data down to a child component.
  • Emits: Emitting events to send data up to a parent component.
  • Provide/Inject: Sharing data deeply between ancestor and descendant 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.

script setup cannot contain es module exports error

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.

The Proper Way to Expose 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.

How defineExpose Works

defineExpose 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.

ScreenFlow.gif

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.

defineExpose and useTemplateRef

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().

Common Use Cases for 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:

  • Exposing component state for testing purposes: Sometimes, you need to access the internal state of a component for testing or debugging.
  • Creating reusable components: If you’re building a component library, you might want certain methods or properties to be accessible for advanced use cases.
  • Interacting with third-party libraries: You might need to expose some internal component data to work with external JavaScript libraries that rely on component instance access.

Summary

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.

  • Encapsulation is key: Components in Vue 3 are designed to be self-contained.
  • Use defineExpose: When you need to share internal state with the outside world, defineExpose provides a clear, declarative way to do so.
  • Stay modular: Only expose what’s necessary and keep your components’ logic clean and manageable.

By understanding this pattern, you’ll avoid common pitfalls and write more maintainable, scalable Vue components.

Start learning Vue.js for free

Mostafa Said
Mostafa Said
With over 7 years of e-learning expertise honed at major companies like Vodafone Intelligent Solutions (_VOIS), Mostafa is full-time instructor at Vue School and a full-stack developer in the wild. He built and maintained dozens of apps using Vue, Nuxt, Laravel, Tailwind, and more. He merges modern teaching methods with coding education to empower aspiring developers of all experience levels. When he's not glued to the code editor, hackathons fuel his competitive spirit, with Deepgram and Appwrite grand prizes under his belt.

Comments

Latest Vue School Articles

Developing a Full Stack Nuxt App with Bolt.new &#8211; An AI Experiment

Developing a Full Stack Nuxt App with Bolt.new – An AI Experiment

Use bolt.new to quickly iterate on a new Nuxt project. Learn how in this article, where we bootstrap a custom blog in no time!
Daniel Kelly
Daniel Kelly
Upgrading Eslint from v8 to v9 in Vue.js

Upgrading Eslint from v8 to v9 in Vue.js

Learn how to upgrade ESLint to v9 in Vue.js, taking advantage of the new flat configuration system and other significant changes in ESLint 9.0.
Mostafa Said
Mostafa Said

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!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.