Skip to main content

vue-collector

A pure-Python tool for compiling Vue single-file components (.vue files) into browser-ready assets — no Node.js, no npm, no build toolchain required.

Purpose

vue-collector is a stepping stone before Vite. It lets you write Vue components using standard .vue files and compile them from Python, without setting up a JavaScript build pipeline. When your project outgrows vue-collector, moving to Vite requires no rewriting — the same .vue files work in both environments.

What it is:

  • A quick way to add Vue components to a Python backend (Flask, FastAPI, Django, etc.)
  • Suitable for internal tools, dashboards, and prototypes where plain JS is enough
  • Write real .vue files from day one, migrate to Vite later with zero friction

What it is not:

  • A replacement for Vite, Webpack, or Rollup
  • Production-ready: no tree-shaking, no code splitting, no hot-reload, no module resolution

When to use it:

  • You want a few interactive Vue pieces in a Python app without touching npm
  • Simplicity and zero JS tooling beats optimal bundle size for your current stage
  • You expect to migrate to a proper Vite project as the frontend grows

Vite migration path

vue-collector is designed so that every .vue file you write is a strict subset of a standard Vite SFC. The same file builds in both vue-collector and Vite without edits. The invariant: if vue-collector builds it, Vite builds it and behaves the same.

  • Options API export default { ... } is fully supported in Vue 3 / Vite
  • import statements are required by Vite to resolve child components — vue-collector simply ignores (strips) them, since it registers every component globally. You don't have to write them by hand: vue-collector format inserts them for you.
  • components: { ... } (local component registration) — same: needed by Vite, stripped by vue-collector, auto-inserted by format
  • name property is stripped — vue-collector derives component names from the file path (see Component naming)
  • <style lang="less"> is required — this ensures Vite knows to use the LESS preprocessor (install less as a dev dependency in your Vite project)

Because vue-collector ignores node_modules/ and dist/, you can even keep a full Vite project (package.json, vite.config.js, index.html, main.js, node_modules/) in the same directory as your .vue files — vue-collector scans only the .vue files and skips everything else.

When you're ready to switch to Vite:

  1. Set up a Vite project (npm create vite@latest)
  2. Copy your .vue files — they work as-is
  3. Run vue-collector format (if you haven't already) so the import/components glue Vite needs is present — vue-collector ignored it, Vite now uses it
  4. Run npm install less --save-dev if your styles use LESS

Component format

Components use a simplified subset of the Vue SFC format:

  • export default { ... } is the only supported component definition — no defineComponent(), no <script setup>
  • <style lang="less"> is required when using styles — ensures Vite compatibility. <style scoped lang="less"> for scoped styles
  • No @import in <style> — inline styles only
  • One <template>, <script>, and <style> section per file
  • No TypeScript<script lang="ts"> raises an error; plain JavaScript only
  • Vue directives (v-for, v-if, @click, :bind) pass through unchanged
  • Named slots via nested <template #slot> are supported
  • Multiple root elements (Vue 3 fragments) are supported

Vite glue — needed by Vite, ignored (stripped) by vue-collector, and auto-inserted by vue-collector format:

  • import statements in <script>
  • components: { ... } in export default
  • name property in export default (vue-collector derives the name from the path instead)
<!-- components/Counter.vue -->
<template>
  <div class="counter">
    <span>{{ count }}</span>
    <button @click="increment">+</button>
  </div>
</template>

<style scoped lang="less">
.counter { display: flex; gap: 8px; }
button { cursor: pointer; }
</style>

<script>
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() { this.count++ }
  }
}
</script>

Component naming

A component's name is derived from its path relative to the source directory: each path segment is kebab-cased and joined with -. This single name is used everywhere — global registration, the components key, and the tag you write in templates — so it resolves identically in vue-collector and Vite.

File Component name (tag)
App.vue app
Component/ItemList.vue component-item-list
Handler/Main.vue handler-main
<!-- in a template, reference children by that name -->
<component-item-list />
<handler-main />

If two files map to the same name (e.g. Component/ItemList.vue and ComponentItemList.vue), the build fails fast with an aggregated error before anything is written. (Across multiple source directories the same name is treated as an intentional override — see Multiple source directories.)


Build mode 1 — HTML file

All components are injected into a single index.html produced from your template.html.

template.html:

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <style><|styles|></style>
</head>
<body>
  <div id="app">
    <counter />
  </div>

  <|templates|>

  <script>
    const app = Vue.createApp({});
    <|scripts|>
    app.mount('#app');
  </script>
</body>
</html>

Templates are stored as <template id="..."> tags and referenced by selector from app.component(). <|scripts|> expands to one self-contained IIFE per component, each registering itself and holding its own module-level code (so components stay isolated).

The three placeholders are <|styles|>, <|templates|>, and <|scripts|>.

Python:

from vue_collector import prepare_compiled

with open('template.html') as f:
    html = prepare_compiled(f.read(), vue_dir='vue/')

with open('index.html', 'w') as f:
    f.write(html)

Project layout for this mode:

project/
├── template.html       # your HTML skeleton with <|placeholders|>
├── index.html          # generated output
└── vue/
    ├── Counter.vue
    └── Card.vue

Build mode 2 — Standalone JS + CSS assets

Components are compiled into two files named by a content hash, so filenames change automatically whenever any .vue file changes (safe for long-term browser caching).

Templates are inlined as backtick strings inside app.component() — no <template id> tags needed. The JS file exports a single initComponents(app) function.

Python:

from vue_collector import write_assets

js_file, css_file = write_assets(
    vue_dir='vue/',      # directory containing .vue files
    output_dir='static', # writes files here
    extra_js='',         # optional JS prepended verbatim (e.g. app init code)
)
# js_file  → 'components.a3f9c1d2e4b5f678.js'
# css_file → 'components.a3f9c1d2e4b5f678.css'

Or in-memory:

from vue_collector import prepare_assets

js_content, css_content = prepare_assets(vue_dir='vue/')

Generated JS structure:

// extra_js content goes here (if provided)

function initComponents(app) {
  (function() {
    // module-level code from this .vue's <script> (outside export default) lives
    // here, isolated to this component's IIFE — matching Vite's per-module scope
    app.component('counter', {name: 'counter', template: `
  <div class="counter">
    <span>{{ count }}</span>
    <button @click="increment">+</button>
  </div>
`,
    data() { return { count: 0 } },
    methods: { increment() { this.count++ } }});
  })();
}

Each component is wrapped in its own IIFE, so module-level constants in different .vue files never collide — the same isolation Vite gives each module.

HTML page using generated assets:

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <link rel="stylesheet" href="/static/components.a3f9c1d2e4b5f678.css">
</head>
<body>
  <div id="app">
    <counter />
  </div>
  <script src="/static/components.a3f9c1d2e4b5f678.js"></script>
  <script>
    const app = Vue.createApp({});
    initComponents(app);
    app.mount('#app');
  </script>
</body>
</html>

Project layout for this mode:

project/
├── static/
│   ├── components.a3f9c1d2e4b5f678.js    # generated
│   └── components.a3f9c1d2e4b5f678.css   # generated
└── vue/
    ├── Counter.vue
    └── Card.vue

Flask integration example

from flask import Flask, render_template_string
from vue_collector import write_assets, VueSectionError

app = Flask(__name__)

try:
    js_file, css_file = write_assets(vue_dir='vue/', output_dir='static')
except VueSectionError as e:
    print(f'Component error: {e}')
    raise

PAGE = """
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <link rel="stylesheet" href="/static/{{ css }}">
</head>
<body>
  <div id="app"><my-counter /></div>
  <script src="/static/{{ js }}"></script>
  <script>
    const app = Vue.createApp({});
    initComponents(app);
    app.mount('#app');
  </script>
</body>
</html>
"""

@app.route('/')
def index():
    return render_template_string(PAGE, js=js_file, css=css_file)

Auto-reload with watchdog

find_vue_files is useful for watching .vue files with a file watcher:

from vue_collector import find_vue_files, write_assets

# Get all .vue paths to pass to your watchdog observer
paths_to_watch = find_vue_files('vue/')

Multiple source directories

All functions that accept vue_dir also accept a list of directories. This enables shared component libraries across builds:

project/
├── shared/           # components used by all builds
│   ├── Button.vue
│   └── Card.vue
├── admin/            # admin-only components
│   └── UserTable.vue
└── static/
from vue_collector import write_assets

# Public build — shared components only
js, css = write_assets(vue_dir='shared/', output_dir='static')

# Admin build — shared + admin components
js, css = write_assets(vue_dir=['shared/', 'admin/'], output_dir='static')

Name conflicts — last directory wins. If two directories contain a component with the same name, the last directory in the list takes priority:

shared/Button.vue   ← base version
admin/Button.vue    ← overrides shared version in admin build
# admin/Button.vue is used; shared/Button.vue is ignored for this build
js, css = write_assets(vue_dir=['shared/', 'admin/'], output_dir='static')

Low-level API

from vue_collector import VueComponent, collect_vue, VueSectionError

# Parse a single component
with open('vue/Counter.vue') as f:
    vc = VueComponent('Counter.vue', f.read())

print(vc.component_name)  # 'counter'  (path-derived; used for registration + tag)
print(vc.name)            # 'Counter'  (PascalCase identifier, for generated imports)
print(vc.style)           # '.counter{display:flex;gap:8px}'
print(vc.raw_template)    # '<div class="counter">...</div>'  (for JS inline use)
print(vc.template)        # '<template id="template-counter">...</template>'  (for HTML mode)

# Iterate all components in a directory — yields VueComponent objects
for component in collect_vue('vue/'):
    print(component.name, component.style)

# Errors always come as VueSectionError
try:
    VueComponent('Bad.vue', '<template><div></template>')
except VueSectionError as e:
    print(e.file_name)  # 'Bad.vue'
    print(e.section)    # None (structural error), or 'script' / 'style'
    print(e.message)    # human-readable description

Installation

pip install vue-collector

CLI

Four commands: check, format, build assets, and build html. Each is available three ways:

vue-collector <command> ...
uv run vue-collector <command> ...
python -m vue_collector <command> ...

All writing/building commands run validation first; if it fails, nothing is written.

vue-collector check <dir>...

Validate .vue files against the Vite-compatibility contract — without writing anything. Reports:

  • duplicate names — two files mapping to the same path-derived component name (aggregated)
  • structural errors — malformed/duplicate sections, non-LESS styles, TypeScript, @import
  • contract issues (advisory) — a component used in a template but never registered/imported; <script setup>

Exits non-zero if any problem is found. Accepts one or more directories.

vue-collector check src/components/
vue-collector check shared/ admin/

vue-collector format <dir>

Two things in one pass, in-place:

  1. Format — sections reordered to <template><style><script>, basic indentation, no trailing whitespace.
  2. Add Vite glue — for each child component used in a template, insert its import and a components: { 'tag': Identifier } entry, with correct relative import paths. This is what keeps a file valid for both vue-collector and Vite.
vue-collector format src/components/

Idempotent — already-formatted files with up-to-date glue are left untouched.

Note: The formatting itself is intentionally simple — a small fixed set of rules, no configuration, no understanding of JS/CSS semantics beyond what's needed. It is not a substitute for Prettier or ESLint. For a project that has graduated to a Node-based toolchain, use Prettier with the Vue plugin instead.

vue-collector build assets <dir>... -o <out_dir> [--extra-js <file>]

Build mode 2 — write components.{hash}.js and components.{hash}.css into out_dir. --extra-js prepends a file's contents to the JS output verbatim.

vue-collector build assets src/components/ -o static/
vue-collector build assets shared/ admin/ -o static/ --extra-js bootstrap.js

vue-collector build html <dir>... --template <file> -o <file>

Build mode 1 — fill an HTML template's <|...|> placeholders with the compiled components and write a single self-contained file.

vue-collector build html src/components/ --template template.html -o dist/index.html

Limitations

Feature Status
LESS compilation Supported — <style lang="less"> required
<style scoped> Supported
Vue directives (v-for, v-if, @click, :bind) Pass-through (not validated)
Named slots (<template #slot>) Supported
Multiple root elements (Vue 3 fragments) Supported
export default {} as component definition Supported (only supported form)
Component naming Path-based kebab-case (Component/ItemList.vuecomponent-item-list)
import in <script> Vite glue — stripped; format auto-inserts
components: {} in export default Vite glue — stripped; format auto-inserts
name in export default Stripped — name derived from path
defineComponent() / <script setup> Not supported
@import in <style> Not supported
TypeScript Not supported
SCSS / Sass / Stylus Not supported
CSS Modules Not supported

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vue_collector-0.4.0.tar.gz (59.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vue_collector-0.4.0-py3-none-any.whl (31.3 kB view details)

Uploaded Python 3

File details

Details for the file vue_collector-0.4.0.tar.gz.

File metadata

  • Download URL: vue_collector-0.4.0.tar.gz
  • Upload date:
  • Size: 59.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Linux Mint","version":"22.2","id":"zara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for vue_collector-0.4.0.tar.gz
Algorithm Hash digest
SHA256 74a9344d9eeeaf3a475009ee2162cee08ebd193d85002bff9d33b918c05fe592
MD5 66305dda3c622d8408711075c6ac6a40
BLAKE2b-256 a7b881436a1edcb81083f22e70d3902dc3373b17007459c162e090f1325ffcea

See more details on using hashes here.

File details

Details for the file vue_collector-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: vue_collector-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 31.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Linux Mint","version":"22.2","id":"zara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for vue_collector-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bf803b7318c5820fb5b4894a108d3d820f6d9c23929aa6134594239b6097f0f1
MD5 e34ed8a2ce0b4713dbaf9ba4eca4ee28
BLAKE2b-256 6c8c9d7ea3c9ce6a818d64f1bf09e9a515926e4b395f3501da9dad2654ef2c6b

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 files

0.3.1

2 files

0.3.0

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page