Open-source project
vuejs/repl avatar
vuejs/repl

@vue/repl: a Vue single file component playground you can drop into a page

Vue SFC REPL as a Vue 3 component

1,098 stars222 forksTypeScriptMIT

At a glance

What is it?
The Vue REPL is published as a component rather than a website, so you can embed a working SFC editor with a preview pane into your own application, choose between a light CodeMirror editor and a full Monaco one, and control where the sandbox fetches the Vue runtime from.
Who is it for?
Adopt @vue/repl if you want a working Vue single file component editor inside your own documentation, playground or tutorial site, and you can live with the sandbox loading the Vue runtime and the compiler from a CDN. Do not adopt it for an offline or air-gapped environment without reconfiguring the resource links, and do not use it as a build-time tool, because it is a runtime component rather than a compiler you invoke.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A playground is a component, not a service

The whole idea of @vue/repl is that the thing that was a website is now a package. The description is one line: Vue SFC REPL as a Vue 3 component, and the package name is scoped to Vue, the version is 4.7.2, and the licence is MIT.

The basic usage is two imports and a template. With the CodeMirror editor:

vue
<script setup>
import { Repl } from '@vue/repl'
import CodeMirror from '@vue/repl/codemirror-editor'
// import '@vue/repl/style.css'
// ^ no longer needed after 3.0
</script>

<template>
  <Repl :editor="CodeMirror" />
</template>

With Monaco, the same shape with a different import:

vue
<script setup>
import { Repl } from '@vue/repl'
import Monaco from '@vue/repl/monaco-editor'
// import '@vue/repl/style.css'
// ^ no longer needed after 3.0
</script>

<template>
  <Repl :editor="Monaco" />
</template>

The editor is a prop, and it has to be passed in explicitly. The README notes that @vue/repl version 2 and above support Monaco but require you to pass in the editor to be used for tree-shaking, which is why both the code samples above import the editor separately rather than letting the package pull one in.

The hosted instance is at repl-vuejs.vercel.app, which is the reference implementation of the same component, and the repository is under the vuejs organisation with Evan You listed as the author in the package metadata.

Two editors with a real trade-off, and one Vite caveat

The editor choice is the first decision a user makes, and the README describes each in one line.

CodeMirror gives a basic editing experience with no intellisense. It is described as lighter weight, making fewer network requests, and better for embedding use cases. Monaco, on the other hand, comes with Volar support, autocomplete, type inference and semantic highlighting. It has a heavier bundle and loads dts files from a CDN, and the README calls it better for standalone use cases.

That framing is the whole trade-off in two sentences. If the REPL is one panel on a page that has other content, the network requests Monaco makes for type definitions are a tax on every visitor for a feature most of them will never use. If the REPL is the page, those requests buy autocomplete and type inference, which is the difference between a code editor and a text field.

The Vite caveat comes first in the README and is easy to miss. The package has to be excluded from dependency optimisation:

ts
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
  optimizeDeps: {
    exclude: ['@vue/repl'],
  },
  // ...
})

The reason is the same one as the tree-shaking note. The package resolves its editor and its runtime assets at runtime rather than as static imports, so a pre-bundler that tries to pre-bundle it will either fail or bundle the wrong thing. The exclusion is not an optimisation, it is a correctness requirement for the way the package loads its pieces.

One more small thing: the stylesheet import is commented out in both samples, with a note that it is no longer needed after version 3.0.

The runtime comes from a CDN, and the import map decides how

A REPL has to execute Vue somewhere, and the answer here is a CDN rather than a bundled copy. The `useVueImportMap` hook is what configures that, and the README shows what it returns.

vue
const {
  importMap: builtinImportMap,
  vueVersion,
  productionMode,
} = useVueImportMap({
  // specify the default URL to import Vue runtime from in the sandbox
  // default is the CDN link from jsdelivr.com with version matching Vue's version
  // from peerDependency
  runtimeDev: 'cdn link to vue.runtime.esm-browser.js',
  runtimeProd: 'cdn link to vue.runtime.esm-browser.prod.js',
  serverRenderer: 'cdn link to server-renderer.esm-browser.js',
})

The defaults are the jsdelivr CDN links, with the version matching Vue's version from the peer dependency. The three URLs are the development runtime, the production runtime and the server renderer, and they are separate because the production runtime and the server renderer are different builds with different characteristics.

Two things come out of this hook that the caller then controls. `vueVersion` is a ref you can set, and the README's example sets it to a specific version, `3.2.8`, so the sandbox runs a particular Vue rather than the newest. `productionMode` is a ref that turns the production build on.

`importMap` is the third return value, and it is the pre-set import map for the sandbox. That matters for a reason that is easy to overlook: the sandbox is a separate module graph from your application, and the import map is what tells it where to resolve bare specifiers like `vue` and `@vue/repl` itself from.

The store carries the state, and the URL carries the state

The advanced usage is one component that wires a store together and puts the state in the address bar. The README describes it as customizing the behaviour of the REPL by manually initializing the store, and points at the v4 migration guide.

The store is created with `useStore`, and it takes an options object and a second argument, the serialized state to restore from:

vue
const store = useStore(
  {
    // pre-set import map
    builtinImportMap,
    vueVersion,
    // starts on the output pane (mobile only) if the URL has a showOutput query
    showOutput: ref(query.has('showOutput')),
    // starts on a different tab on the output pane if the URL has a outputMode query
    // and default to the "preview" tab
    outputMode: ref(query.get('outputMode') || 'preview'),
  },
  // initialize repl with previously serialized state
  location.hash,
)

The URL is the persistence layer. The query string carries the configuration, in this case whether to open on the output pane and which output tab to use, and the hash carries the serialized REPL state, so a link can reproduce exactly what someone was looking at.

Writing it back is a single effect:

vue
watchEffect(() => history.replaceState({}, '', store.serialize()))

`history.replaceState` rather than `pushState`, so the address bar updates as the user types without filling the browser history with every keystroke. And the store is passed into the component rather than created by it:

vue
<template>
  <Repl :store="store" :editor="Monaco" :showCompileOutput="true" />
</template>

There is a second entry point for the case where you only want the output. The `Sandbox` component renders the preview without an editor, and it takes the same store:

vue
<template>
  <Sandbox :store="store" />
</template>

That split is useful for a documentation site where the code is authored elsewhere and the page shows the result.

Resource links are how you leave the default CDN behind

Every URL the sandbox fetches is a resource link, and the package exposes a typed configuration object for them. The type lists an optional URL for the ES Module Shims, functions that generate the URL of the Vue compiler and of the TypeScript library based on the version, and three Monaco-specific functions for fetching the latest version of a package, browsing a package directory and fetching the content of a file from a package.

The point of the type is that these are functions of the version rather than fixed strings, so switching Vue versions in the REPL also switches the compiler and the type definitions to match.

The README then gives two complete examples of replacing the defaults. With unpkg:

ts
const store = useStore({
  resourceLinks: ref({
    esModuleShims:
      'https://unpkg.com/[email protected]/dist/es-module-shims.wasm.js',
    vueCompilerUrl: (version) =>
      `https://unpkg.com/@vue/compiler-sfc@${version}/dist/compiler-sfc.esm-browser.js`,
    typescriptLib: (version) =>
      `https://unpkg.com/typescript@${version}/lib/typescript.js`,
  }),
})

And with npmmirror, the same set of URLs pointed at registry.npmmirror.com, whose path layout differs, with the version as a directory segment and a `/files/` path component.

For anyone deploying this in a network where jsdelivr is slow or blocked, or in a region where the npmmirror is faster, this is the mechanism. The type is the contract, and both examples are complete enough to copy.

The ES Module Shims entry deserves a note, because its name explains the requirement it solves. The sandbox has to load bare specifiers in the browser, and the shims are what make that possible without a bundler.

The export map decides what lands in your bundle

The `package.json` export map is the clearest statement of the package's design, and it explains several details in the README.

The root export provides types, an ESM entry and a `require` entry that resolves to `dist/ssr-stub.js`. That is the same path as the `main` field, so requiring this package in a CommonJS or server-rendered context loads a stub rather than the real component. The `ssr-stub.js` file at the repository root is the source of that stub.

The editor entries are different. `./monaco-editor` and `./codemirror-editor` both have types and an import entry, and both set `require` to `null`, so requiring either editor from CommonJS fails on purpose rather than silently doing something odd. There is also a `./core` export with the same shape, which is the part of the package that does not depend on an editor at all.

The stylesheet is exported twice, as `./style.css` and `./dist/style.css`, both pointing at `dist/vue-repl.css`, and the `./package.json` export is present for tooling that reads it.

The build side is Vite, with two configurations, `vite.config.ts` and `vite.preview.config.ts`, and the scripts are `vite` for development, `vite build` for a build and `vite build -c vite.preview.config.ts` for the preview build. Type checking is `vue-tsc --noEmit`, formatting is prettier, linting is eslint, and the release process uses `bumpp --all` with a conventional changelog on the Angular preset. The package manager is pnpm at 10.14.0, the package is ESM, and the repository has a `.node-version` file, a `test/` directory and a `CHANGELOG.md`.

The releases show a steady cadence: v4.7.2 on 2026-04-13, v4.7.1 on 2025-12-04 and v4.7.0 on 2025-09-04, with the last push on 2026-10-13.

Where @vue/repl is the wrong tool

The first wrong case is an offline or restricted environment. By default the sandbox loads the Vue runtime, the compiler, the TypeScript library and the ES Module Shims from a CDN, and Monaco additionally fetches dts files. On a network where jsdelivr is unreachable, the REPL is a blank panel until you replace the resource links.

The second is a build-time tool. @vue/repl is a runtime component that runs a compiler in the browser, so it is the wrong thing to reach for if you want to compile a single file component in a Node script or in CI. That is a different tool with a different contract.

The third is an application that needs the editor and the preview to look exactly like the Vue documentation site. This is a component with its own layout and its own styles, and the README does not describe a theming API beyond the fact that the stylesheet is a separate file you can override.

The fourth is a project that wants the lightest possible embedding and has already chosen CodeMirror for other reasons. In that case the package's value reduces to the compiler and the preview pane, and the export map's `./core` entry is the part you would actually be using.

The fifth is a team that expects a hosted, multi-user playground. This is a component. Whoever deploys it owns the CDN dependencies, the browser support matrix and the fact that a user's session state lives in a URL hash that can be shared, with everything that implies about link rot when the compiler or the runtime version changes.

Editorial conclusion

Adopt @vue/repl if you want a working Vue single file component editor inside your own documentation, playground or tutorial site, and you can live with the sandbox loading the Vue runtime and the compiler from a CDN. Do not adopt it for an offline or air-gapped environment without reconfiguring the resource links, and do not use it as a build-time tool, because it is a runtime component rather than a compiler you invoke. Before you ship it, exclude the package from Vite dependency optimisation as the README shows, pick your editor deliberately, since Monaco and CodeMirror differ in bundle size and in what they can do, and set the resource links if your users cannot reach jsdelivr.

Frequently asked questions

How do I use the Vue REPL as a component?

Import the `Repl` component from `@vue/repl` and pass an editor in, for example `import CodeMirror from '@vue/repl/codemirror-editor'` and then `<Repl :editor="CodeMirror" />`. The style.css import is no longer needed after version 3.0.

Should I use the CodeMirror or the Monaco editor with @vue/repl?

CodeMirror gives a basic editing experience with no intellisense, is lighter and makes fewer network requests, which suits embedding. Monaco has Volar support with autocomplete, type inference and semantic highlighting, but a heavier bundle and loads dts files from a CDN, which suits standalone use.

Why do I have to exclude @vue/repl from Vite dependency optimisation?

The README shows `optimizeDeps: { exclude: ['@vue/repl'] }` in the Vite config, and notes that from version 2 the editor has to be passed in explicitly for tree-shaking, because the package resolves its pieces at runtime rather than through static imports.

Can I change where the Vue REPL loads Vue and the compiler from?

Yes. The `useVueImportMap` hook accepts runtimeDev, runtimeProd and serverRenderer URLs, and the store's `resourceLinks` option replaces the ES Module Shims, the Vue compiler, the TypeScript library and the Monaco package browsing URLs. The README gives complete examples for unpkg and npmmirror.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. vuejs/repl on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/vuejs-repl.svg)](https://hysenlabs.com/projects/vuejs-repl)

Was this useful?