Files
pretix_original/vite.config.ts
rash f04df7a6ee Migrate vue2 control components and widget to vue3 and vite (#5989)
* setup vite and integrate fully with django

- vite starts with `python manage.py runserver`
- add templatetags to simply load vite hmr and entry points
- add eslint (recheck rules)
- enable non-strict ts

* better syntax for cors header setting

* migrate checkin rules editor to vue3

- move constants to a module
- move reading from and writing to non-vue html to django interop module
- switch to composition api and script setup sfc with pug
- use optional chaining operators a lot to simplify code

* migrate webcheckin plugin to vite+vue3

- migrate vue sfcs to script setup and pug
- move fetch calls into a api.ts module
- move common formatting and i18n strings into module

* fix migration error

* first draft migrating widget to vue3/vite

* first couple widget e2e tests

courtesy of claude
most of the tests don't work yet

* test file is not actually used

* drop widget_ prefix from e2e test fixtures

* add test for complete widget journey for simple event

* switch timezone in e2e tests to Europe/Berlin

* make dates in e2e tests relative

* migrate widget bugfix #5886

* start testing event series widget

* working vite widget setup for prod (untested), local dev (with or without dev server) and pytests, with flags for running the old version or the vite version

* simplify e2e test iframe check

* less flaky e2e tests

* top level await in iife build mode is not supported, so let's do import.meta.glob instead (we just need the build step not to see await, the code doesn't actually ever get loaded because it's DEV only)

* fix inconsistencies from automatic migration

* Allow gradual rollout of new vite-based widget by adding urls to an allowlist that gets checked against the "Origin" http header of request fetching the widget js

* add e2e tests for widget button, testing empty cart, adding specific items, and subevents

* remove janky claude testts again

* resolve migration TODOs: properly refocus parent on navigations

* use `npm run dev:control` for the vite dev server for admin components

* upgrade npm dependencies

* fix js linter errors

* fix python linter errors

* build all control vue components

* add new js config files to check-manifest ignore

* working prod build

acutal serving of built assets not tested yet

* fix templatetag paths to match what's in the vite mantifest

* add missing quotes around 'unsafe-eval' cors value

* remove now unused old vue2 tooling

* try fixing e2e test ci

* fix flake8 error

* check if vite build artefacts are in the wheel

* add license headers

* remove dom manipilation code necessary for `div.pretix-widget-compat` to work. No longer needed for vue3

* remove superfluous `createElement` calls

They might have been there because of IE, which is no longer relevant

* make widget dev mode parametizable through query params and document the usage and those params

* fix rst syntax

* remove migration todos file

Co-authored-by: luelista <mira@teamwiki.de>

* rearrange dockerfile commands for smaller image, thanks @luelista

* Update .gitignore, adding .vite

Co-authored-by: luelista <mira@teamwiki.de>

* add eslint CI

* make vue dev work in plugins

* fix docker build

* rebuild vite setup to support static prod plugins and dynamic hmr plugin development

* use toml for vite plugin config instead of standalone json file

* Add widget changes from #6047, #6149

* Allow buttons to reuse cart (Z#23226853)

* Always keep cart of buttons with items set

* widget: handle cart if not same-site (#6149)

---------

Co-authored-by: luelista <mira@teamwiki.de>
Co-authored-by: Kara Engelhardt <engelhardt@pretix.eu>
2026-05-11 15:05:06 +02:00

151 lines
5.0 KiB
TypeScript

// vite build config for control UI
// widget has its own config, see src/pretix/static/pretixpresale/widget/vite.config.ts
import { defineConfig, type Plugin } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import { execSync } from 'child_process'
import { readFileSync } from 'fs'
import { parse as parseToml } from 'smol-toml'
// Shared dependencies exposed to plugins via import map.
// Adding a dep here auto-generates a _vendor/{name} entry and
// makes it available in the import map.
const SHARED_DEPS = ['vue']
const { entries: pretixPluginEntries } = discoverPretixPlugins()
const pluginDirs = [...new Set(Object.values(pretixPluginEntries).map(p => path.dirname(p)))]
export default defineConfig({
plugins: [
vue(),
sharedDepsPlugin(),
pretixPluginDevEntries(),
],
resolve: {
// Pin shared deps to pretix's node_modules to prevent duplicate instances
// across plugins whose node_modules live in sibling directories
dedupe: [...SHARED_DEPS, '@vue/runtime-core', '@vue/reactivity', '@vue/shared'],
},
server: {
fs: {
// Allow serving source files from sibling plugin directories
allow: ['src', ...pluginDirs],
},
},
build: {
manifest: true,
outDir: path.resolve(__dirname, 'src/pretix/static.dist/vite/control'),
rollupOptions: {
preserveEntrySignatures: 'exports-only',
input: {
'webcheckin/main': path.resolve(__dirname, 'src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.ts'),
'checkinrules/main': path.resolve(__dirname, 'src/pretix/static/pretixcontrol/js/ui/checkinrules/index.ts'),
...Object.fromEntries(SHARED_DEPS.map(dep => [`_vendor/${dep}`, `virtual:vendor/${dep}`])),
...pretixPluginEntries,
},
}
},
optimizeDeps: {
exclude: ['moment', 'jquery']
}
})
// Virtual module plugin: generates re-export entries for each shared dep
// In dev mode, serves /__pretix_importmap so the python template tag can build the import map without hardcoding dep names.
function sharedDepsPlugin (): Plugin {
const PREFIX = 'virtual:vendor/'
const RESOLVED = '\0virtual:vendor/'
return {
name: 'pretix-shared-deps',
resolveId (id) {
if (id.startsWith(PREFIX))
return RESOLVED + id.slice(PREFIX.length)
},
load (id) {
if (id.startsWith(RESOLVED)) {
const pkg = id.slice(RESOLVED.length)
return `export * from '${pkg}'`
}
},
// Serve the import map data so the Python template tag can fetch it in dev mode
configureServer (server) {
server.middlewares.use((req, res, next) => {
if (req.url === '/__pretix_importmap') {
const imports = Object.fromEntries(
SHARED_DEPS.map(dep => [
dep,
`/node_modules/.vite/deps/${dep.replace('/', '_')}.js`,
])
)
res.setHeader('Content-Type', 'application/json')
res.setHeader('Access-Control-Allow-Origin', '*')
res.end(JSON.stringify(imports))
return
}
next()
})
},
}
}
// TODO move to separate file?
function discoverPretixPlugins (): { entries: Record<string, string> } {
let manifestFiles: string[] = []
try {
const raw = execSync(`python -c "
import importlib_metadata as metadata, json, pathlib, tomllib
result = []
for ep in metadata.entry_points(group='pretix.plugin'):
dist = ep.dist
if not dist: continue
try:
url_info = json.loads(dist.read_text('direct_url.json') or '{}')
if not url_info.get('dir_info', {}).get('editable'):
continue # non-editable plugins build their own assets
p = pathlib.Path(url_info['url'].replace('file://', '')) / 'pretixplugin.toml'
if not p.exists():
continue
with p.open('rb') as f:
if 'vite' in tomllib.load(f):
result.append(str(p))
except Exception:
pass
print(json.dumps(result))
"`, { stdio: ['pipe', 'pipe', 'inherit'] }).toString().trim()
manifestFiles = JSON.parse(raw)
} catch (error) {
console.error('Failed to discover pretix plugins, skipping plugin entries:', error)
}
const entries: Record<string, string> = {}
for (const manifestFile of manifestFiles) {
const packageRoot = manifestFile.replace(/[/\\]pretixplugin\.toml$/, '')
const parsed = parseToml(readFileSync(manifestFile, 'utf8')) as {
vite: { entries: Record<string, string> }
}
for (const [name, rel] of Object.entries(parsed.vite.entries)) {
entries[name] = path.join(packageRoot, rel)
}
}
return { entries }
}
// In dev mode, the browser requests /{entryName} from the Vite dev server.
// Vite can't find these files since they live outside the project root.
// This plugin rewrites those URLs to /@fs{absPath} so Vite serves them directly.
function pretixPluginDevEntries (): Plugin {
return {
name: 'pretix-plugin-dev-entries',
configureServer (server) {
server.middlewares.use((req, _res, next) => {
const urlPath = req.url?.split('?')[0]
if (urlPath) {
const name = urlPath.slice(1) // strip leading /
if (name in pretixPluginEntries)
req.url = `/@fs${pretixPluginEntries[name]}`
}
next()
})
},
}
}