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
.vuefiles 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 importstatements 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 formatinserts them for you.components: { ... }(local component registration) — same: needed by Vite, stripped by vue-collector, auto-inserted byformatnameproperty 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 (installlessas 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:
- Set up a Vite project (
npm create vite@latest) - Copy your
.vuefiles — they work as-is - Run
vue-collector format(if you haven't already) so theimport/componentsglue Vite needs is present — vue-collector ignored it, Vite now uses it - Run
npm install less --save-devif your styles use LESS
Component format
Components use a simplified subset of the Vue SFC format:
export default { ... }is the only supported component definition — nodefineComponent(), no<script setup><style lang="less">is required when using styles — ensures Vite compatibility.<style scoped lang="less">for scoped styles- No
@importin<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:
importstatements in<script>components: { ... }inexport defaultnameproperty inexport 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:
- Format — sections reordered to
<template>→<style>→<script>, basic indentation, no trailing whitespace. - Add Vite glue — for each child component used in a template, insert its
importand acomponents: { '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.vue → component-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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74a9344d9eeeaf3a475009ee2162cee08ebd193d85002bff9d33b918c05fe592
|
|
| MD5 |
66305dda3c622d8408711075c6ac6a40
|
|
| BLAKE2b-256 |
a7b881436a1edcb81083f22e70d3902dc3373b17007459c162e090f1325ffcea
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf803b7318c5820fb5b4894a108d3d820f6d9c23929aa6134594239b6097f0f1
|
|
| MD5 |
e34ed8a2ce0b4713dbaf9ba4eca4ee28
|
|
| BLAKE2b-256 |
6c8c9d7ea3c9ce6a818d64f1bf09e9a515926e4b395f3501da9dad2654ef2c6b
|