---
title: "VueUse 13: ESM only, and what that means for you · vuejs.school"
description: "VueUse 13 has one breaking change, the CommonJS build is gone. For a Vite app that changes nothing. For Jest and old Node scripts it needs a fix."
canonical_url: "https://vuejs.school/community/blog/vueuse-13-esm-only"
meta:
  "og:description": "VueUse 13 has one breaking change, the CommonJS build is gone. For a Vite app that changes nothing. For Jest and old Node scripts it needs a fix."
  "og:image": "https://vuejs.school/_og/d/a_~VnVlVXNlIDEzOiBFU00gb25seSwgYW5kIHdoYXQgdGhhdCBtZWFucyBmb3IgeW91IMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~VnVlVXNlIDEzOiBFU00gb25seSwgYW5kIHdoYXQgdGhhdCBtZWFucyBmb3IgeW91%2Cdescription_~VnVlVXNlIDEzIGhhcyBvbmUgYnJlYWtpbmcgY2hhbmdlLCB0aGUgQ29tbW9uSlMgYnVpbGQgaXMgZ29uZS4gRm9yIGEgVml0ZSBhcHAgdGhhdCBjaGFuZ2VzIG5vdGhpbmcuIEZvciBKZXN0IGFuZCBvbGQgTm9kZSBzY3JpcHRzIGl0IG5lZWRzIGEgZml4Lg%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA1IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWV1c2UtMTMtZXNtLW9ubHki%2Cs_u3Vx7iCrrOqad6Fj.png"
  "og:image:alt": "VueUse 13: ESM only, and what that means for you · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "VueUse 13: ESM only, and what that means for you · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~VnVlVXNlIDEzOiBFU00gb25seSwgYW5kIHdoYXQgdGhhdCBtZWFucyBmb3IgeW91IMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~VnVlVXNlIDEzOiBFU00gb25seSwgYW5kIHdoYXQgdGhhdCBtZWFucyBmb3IgeW91%2Cdescription_~VnVlVXNlIDEzIGhhcyBvbmUgYnJlYWtpbmcgY2hhbmdlLCB0aGUgQ29tbW9uSlMgYnVpbGQgaXMgZ29uZS4gRm9yIGEgVml0ZSBhcHAgdGhhdCBjaGFuZ2VzIG5vdGhpbmcuIEZvciBKZXN0IGFuZCBvbGQgTm9kZSBzY3JpcHRzIGl0IG5lZWRzIGEgZml4Lg%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA1IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWV1c2UtMTMtZXNtLW9ubHki%2Cs_u3Vx7iCrrOqad6Fj.png"
  "twitter:image:alt": "VueUse 13: ESM only, and what that means for you · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## One breaking change: the CommonJS build is gone

The [release notes](https://github.com/vueuse/vueuse/releases/tag/v13.0.0) list exactly one change, under "Breaking Changes": the CommonJS build has been dropped, and the package is now ESM only. There is no list of new functions, no changed options and no removed composables. The major version changed because a new module format can break somebody's import, not because the API moved.

The `package.json` of `@vueuse/core` shows the change. In 12.8.2, the `exports` map sent `import` to `index.mjs` and `require` to `index.cjs`, and the `main` field pointed at `index.cjs`. In 13.0.0, the map points `.` (the package's main entry) straight at `./index.mjs`, and the `.cjs` file no longer exists. `vue` also became a peer dependency (`^3.5.0`) instead of a regular one. Everything else is 12.8, shipped in one format instead of two.

## ESM and CommonJS in plain words

JavaScript has two module systems. CommonJS is the older one from Node: a file reads another with `require('x')` and publishes values with `module.exports`. ES modules, ESM for short, are the standard the language itself defines: `import { useStorage } from '@vueuse/core'` and `export function`. Browsers only understand ESM. Node understands both. Bundlers understand both and translate.

For years libraries shipped two builds, so that a `require()` from an older tool and an `import` from a newer one both worked. Two builds cost maintenance and space (`@vueuse/core` is 1.5 MB unpacked in 12.8.2, 0.8 MB in 13.0.0), and an app can load both copies of one library at once, which breaks code that expects a single shared instance. VueUse's creator made the case for ESM only in February, in [Move on to ESM-only](https://antfu.me/posts/move-on-to-esm-only).

## Vite apps: nothing to do

If your project was created with `npm create vue@latest` or `npx nuxi init`, you are on Vite, and Vite has always consumed ESM. It pre-bundles dependencies with esbuild in development and bundles with Rollup for production, and both read `index.mjs` happily. Vitest runs on Vite and loads packages with `import`, so VueUse 13 works in your Vitest tests too. Your own code already uses `import`, so a composable like this one is the same on 12 and 13:

*composables/useTheme.ts*

```
import { useStorage } from '@vueuse/core'

export function useTheme() {
  return useStorage<'light' | 'dark'>('theme', 'light')
}
```

The Nuxt module, `@vueuse/nuxt`, is unaffected for the same reason: Nuxt is an ESM project end to end.

## Jest: let Babel transform VueUse, or move to Vitest

Jest is the case the release breaks. Out of the box Jest runs tests as CommonJS and does not transform anything inside `node_modules`. When a test imports a component that imports `@vueuse/core`, Jest tries to `require()` an ESM file and stops with:

```
SyntaxError: Cannot use import statement outside a module
```

The quick fix is to let Babel transform VueUse's files. Two settings in `jest.config.js` do it:

*jest.config.js**Works***

```
module.exports = {
  testEnvironment: 'jsdom',
  transform: {
    '^.+\\.vue$': '@vue/vue3-jest',
    '^.+\\.m?[jt]s$': 'babel-jest',
  },
  transformIgnorePatterns: ['node_modules/(?!.pnpm|@vueuse)'],
}
```

- `transformIgnorePatterns` lists what Jest leaves alone, by default all of `node_modules`. The negative lookahead `(?!…)` turns it into "ignore everything in `node_modules` except folders starting with `@vueuse`". The `.pnpm` part is for pnpm, which keeps the real files under `node_modules/.pnpm/…/node_modules/`: it makes the pattern skip the first `node_modules/` and test the second, as the [Jest docs](https://jestjs.io/docs/29.7/configuration#transformignorepatterns-arraystring) explain.
- The `m?` in the `babel-jest` pattern makes it match `.mjs` too. VueUse 13's entry files end in `.mjs`, so without it they are never transformed.

Babel then converts VueUse's files to CommonJS on the fly. That needs a project-wide config file such as `babel.config.js`, because a `.babelrc` file does not apply to files in `node_modules`. It works, but every ESM-only dependency you add needs another entry in that pattern.

The better fix is to stop fighting the module system. Vitest reads ESM natively and has a Jest-compatible API, so most test files need only small changes: `vi.fn()` in place of `jest.fn()`, and the `globals` option if your tests use `describe` and `it` without importing them. The [migration guide](https://v3.vitest.dev/guide/migration.html#jest) lists the rest. If VueUse 13 is the first ESM-only package to break your Jest setup, it will not be the last, and this is a good week to migrate.

## Node scripts that still use `require()`

The other place a `require()` hides is a Node script: a build step, a script that seeds data, or a hand-written server-side rendering server written in CommonJS. Node 18, Node 20 before 20.19.0 and Node 22 before 22.12 cannot `require()` an ES module by default, so on those versions this line throws `Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported`:

*scripts/report.js**Breaks***

```
const { useDateFormat } = require('@vueuse/core')
```

Two fixes. Either turn that file into an ES module, by renaming it to `.mjs` or by adding `"type": "module"` to its `package.json`, and use `import`:

*scripts/report.mjs**Works***

```
import { useDateFormat } from '@vueuse/core'
```

Or keep the file as CommonJS and load VueUse with a dynamic `import()`, which returns a promise and works in both module systems:

*scripts/report.js**Works***

```
async function main() {
  const { useDateFormat } = await import('@vueuse/core')
}

main()
```

Node 22.12 and later can `require()` an ES module directly, and so can Node 20.19.0, released on 13 March: on those versions the original line keeps working. Do not rely on that unless you control which Node version runs the script, for example in CI and on the server.

## Move `MaybeRef` imports from VueUse to Vue

Because 13 is 12.8 in a new format, the deprecations from 12.8.0 are the ones to act on now. The types `MaybeRef` and `MaybeRefOrGetter` that VueUse exported for years are deprecated in favour of the identical types Vue itself has shipped since 3.3. Change the import, and nothing else:

*composables/useGreeting.ts*

```
import { computed, toValue, type MaybeRefOrGetter } from 'vue'

export function useGreeting(name: MaybeRefOrGetter<string>) {
  return computed(() => `Hello, ${toValue(name)}`)
}
```

`toValue()` unwraps a ref, calls a getter, or returns a plain value as is, which is what makes a `MaybeRefOrGetter` argument convenient for callers. Both the type and the helper come from `vue`, so a composable written this way has no VueUse import at all.

## Upgrade and check that it worked

Upgrade every `@vueuse/*` package you use to the same major, because `@vueuse/components`, `@vueuse/integrations`, `@vueuse/router` and `@vueuse/nuxt` share `@vueuse/core`'s version number. Leave out `@vueuse/nuxt` if you are not on Nuxt:

```
pnpm add @vueuse/core@13 @vueuse/nuxt@13
```

Then run a three-line check from the project's root folder. The first line proves the package loads as ESM from Node, the second proves your bundler is happy, the third proves your tests are:

```
node -e "import('@vueuse/core').then(m => console.log(typeof m.useStorage))"
pnpm build
pnpm test
```

The first command should print `function`. If the build and the tests pass too, there is nothing else in this release to look at. If `pnpm test` is the one that fails, go back to the Jest section.

**Check it stuck**3 questions from the Meet VueUse quiz.

[**Practice now**](https://vuejs.school/learn/vueuse/meet-vueuse?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Deep dive · 6 min read**VueUse 15's new composables: useWebMCP, useLiveAnnouncer and useTemporalNow**](https://vuejs.school/community/blog/vueuse-15s-new-composables-usewebmcp-useliveannouncer-usetemporalnow) [Release notes · 6 min read**VueUse 15: the breaking changes in plain words**](https://vuejs.school/community/blog/vueuse-15-the-breaking-changes-in-plain-words)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/vueuse-13-esm-only#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-03-13","description":"VueUse 13 has one breaking change, the CommonJS build is gone. For a Vite app that changes nothing. For Jest and old Node scripts it needs a fix.","name":"VueUse 13: ESM only, and what that means for you","url":"https://vuejs.school/community/blog/vueuse-13-esm-only","breadcrumb":{"@id":"https://vuejs.school/community/blog/vueuse-13-esm-only#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/vueuse-13-esm-only"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/vueuse-13-esm-only#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-03-13","description":"VueUse 13 has one breaking change, the CommonJS build is gone. For a Vite app that changes nothing. For Jest and old Node scripts it needs a fix.","headline":"VueUse 13: ESM only, and what that means for you","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~VnVlVXNlIDEzOiBFU00gb25seSwgYW5kIHdoYXQgdGhhdCBtZWFucyBmb3IgeW91IMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~VnVlVXNlIDEzOiBFU00gb25seSwgYW5kIHdoYXQgdGhhdCBtZWFucyBmb3IgeW91%2Cdescription_~VnVlVXNlIDEzIGhhcyBvbmUgYnJlYWtpbmcgY2hhbmdlLCB0aGUgQ29tbW9uSlMgYnVpbGQgaXMgZ29uZS4gRm9yIGEgVml0ZSBhcHAgdGhhdCBjaGFuZ2VzIG5vdGhpbmcuIEZvciBKZXN0IGFuZCBvbGQgTm9kZSBzY3JpcHRzIGl0IG5lZWRzIGEgZml4Lg%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA1IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWV1c2UtMTMtZXNtLW9ubHki%2Cs_u3Vx7iCrrOqad6Fj.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/vueuse-13-esm-only#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/vueuse-13-esm-only#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/vueuse-13-esm-only#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"VueUse 13: ESM only, and what that means for you","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~VnVlVXNlIDEzOiBFU00gb25seSwgYW5kIHdoYXQgdGhhdCBtZWFucyBmb3IgeW91IMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~VnVlVXNlIDEzOiBFU00gb25seSwgYW5kIHdoYXQgdGhhdCBtZWFucyBmb3IgeW91%2Cdescription_~VnVlVXNlIDEzIGhhcyBvbmUgYnJlYWtpbmcgY2hhbmdlLCB0aGUgQ29tbW9uSlMgYnVpbGQgaXMgZ29uZS4gRm9yIGEgVml0ZSBhcHAgdGhhdCBjaGFuZ2VzIG5vdGhpbmcuIEZvciBKZXN0IGFuZCBvbGQgTm9kZSBzY3JpcHRzIGl0IG5lZWRzIGEgZml4Lg%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA1IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWV1c2UtMTMtZXNtLW9ubHki%2Cs_u3Vx7iCrrOqad6Fj.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~VnVlVXNlIDEzOiBFU00gb25seSwgYW5kIHdoYXQgdGhhdCBtZWFucyBmb3IgeW91IMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~VnVlVXNlIDEzOiBFU00gb25seSwgYW5kIHdoYXQgdGhhdCBtZWFucyBmb3IgeW91%2Cdescription_~VnVlVXNlIDEzIGhhcyBvbmUgYnJlYWtpbmcgY2hhbmdlLCB0aGUgQ29tbW9uSlMgYnVpbGQgaXMgZ29uZS4gRm9yIGEgVml0ZSBhcHAgdGhhdCBjaGFuZ2VzIG5vdGhpbmcuIEZvciBKZXN0IGFuZCBvbGQgTm9kZSBzY3JpcHRzIGl0IG5lZWRzIGEgZml4Lg%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA1IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWV1c2UtMTMtZXNtLW9ubHki%2Cs_u3Vx7iCrrOqad6Fj.png"}]}
```