mirror of
https://github.com/pretix/pretix.git
synced 2026-05-09 15:54:03 +00:00
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
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,5 +24,6 @@ local/
|
|||||||
.project
|
.project
|
||||||
.pydevproject
|
.pydevproject
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/*
|
||||||
87
eslint.config.mjs
Normal file
87
eslint.config.mjs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
import globals from 'globals'
|
||||||
|
import js from '@eslint/js'
|
||||||
|
import ts from 'typescript-eslint'
|
||||||
|
import stylistic from '@stylistic/eslint-plugin'
|
||||||
|
import vue from 'eslint-plugin-vue'
|
||||||
|
import vuePug from 'eslint-plugin-vue-pug'
|
||||||
|
|
||||||
|
const ignores = globalIgnores([
|
||||||
|
'**/node_modules',
|
||||||
|
'**/dist'
|
||||||
|
])
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
ignores,
|
||||||
|
...ts.config(
|
||||||
|
js.configs.recommended,
|
||||||
|
ts.configs.recommended
|
||||||
|
),
|
||||||
|
stylistic.configs.customize({
|
||||||
|
indent: 'tab',
|
||||||
|
braceStyle: '1tbs',
|
||||||
|
quoteProps: 'as-needed'
|
||||||
|
}),
|
||||||
|
...vue.configs['flat/recommended'],
|
||||||
|
...vuePug.configs['flat/recommended'],
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
localStorage: false,
|
||||||
|
$: 'readonly',
|
||||||
|
$$: 'readonly',
|
||||||
|
$ref: 'readonly',
|
||||||
|
$computed: 'readonly',
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@typescript-eslint/parser'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'no-debugger': 'off',
|
||||||
|
curly: 0,
|
||||||
|
'no-return-assign': 0,
|
||||||
|
'no-console': 'off',
|
||||||
|
'vue/require-default-prop': 0,
|
||||||
|
'vue/require-v-for-key': 0,
|
||||||
|
'vue/valid-v-for': 'warn',
|
||||||
|
'vue/no-reserved-keys': 0,
|
||||||
|
'vue/no-setup-props-destructure': 0,
|
||||||
|
'vue/multi-word-component-names': 0,
|
||||||
|
'vue/max-attributes-per-line': 0,
|
||||||
|
'vue/attribute-hyphenation': ['warn', 'never'],
|
||||||
|
'vue/v-on-event-hyphenation': ['warn', 'never'],
|
||||||
|
'import/first': 0,
|
||||||
|
'@typescript-eslint/ban-ts-comment': 0,
|
||||||
|
'@typescript-eslint/no-explicit-any': 0,
|
||||||
|
'no-use-before-define': 'off',
|
||||||
|
'no-var': 'error',
|
||||||
|
|
||||||
|
'@typescript-eslint/no-use-before-define': ['error', {
|
||||||
|
typedefs: false,
|
||||||
|
functions: false,
|
||||||
|
}],
|
||||||
|
|
||||||
|
'@typescript-eslint/no-unused-vars': ['error', {
|
||||||
|
args: 'all',
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
caughtErrors: 'all',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
destructuredArrayIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
ignoreRestSiblings: true
|
||||||
|
}],
|
||||||
|
|
||||||
|
'@stylistic/comma-dangle': 0,
|
||||||
|
'@stylistic/space-before-function-paren': ['error', 'always'],
|
||||||
|
'@stylistic/max-statements-per-line': ['error', { max: 1, ignoredNodes: ['BreakStatement'] }],
|
||||||
|
'@stylistic/member-delimiter-style': 0,
|
||||||
|
'@stylistic/arrow-parens': 0,
|
||||||
|
'@stylistic/generator-star-spacing': 0,
|
||||||
|
'@stylistic/yield-star-spacing': ['error', 'after'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
])
|
||||||
4172
package-lock.json
generated
Normal file
4172
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
package.json
Normal file
42
package.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "pretix",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"homepage": "https://github.com/pretix/pretix#readme",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/pretix/pretix/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/pretix/pretix.git"
|
||||||
|
},
|
||||||
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
|
"author": "",
|
||||||
|
"type": "module",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"doc": "doc"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint:eslint": "eslint . --ext .js,.ts,.vue",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.5.27"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.2",
|
||||||
|
"@stylistic/eslint-plugin": "^5.7.1",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.4",
|
||||||
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"eslint-plugin-vue": "^10.7.0",
|
||||||
|
"eslint-plugin-vue-pug": "^1.0.0-alpha.5",
|
||||||
|
"pug": "^3.0.3",
|
||||||
|
"stylus": "^0.64.0",
|
||||||
|
"typescript-eslint": "^8.54.0",
|
||||||
|
"vite": "^7.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,9 +37,11 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'django.contrib.humanize',
|
'django.contrib.humanize',
|
||||||
|
# pretix needs to go before staticfiles
|
||||||
|
# so we can override the runserver command
|
||||||
'pretix.base',
|
'pretix.base',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
'pretix.control',
|
'pretix.control',
|
||||||
'pretix.presale',
|
'pretix.presale',
|
||||||
'pretix.multidomain',
|
'pretix.multidomain',
|
||||||
|
|||||||
38
src/pretix/base/management/commands/runserver.py
Normal file
38
src/pretix/base/management/commands/runserver.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2023-present Tobias Kunze
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only WITH LicenseRef-Pretalx-AGPL-3.0-Terms
|
||||||
|
|
||||||
|
"""This command supersedes the Django-inbuilt runserver command.
|
||||||
|
|
||||||
|
It runs the local frontend server, if node is installed and the setting
|
||||||
|
is set.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import atexit
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.staticfiles.management.commands.runserver import Command as Parent
|
||||||
|
from django.utils.autoreload import DJANGO_AUTORELOAD_ENV
|
||||||
|
|
||||||
|
|
||||||
|
class Command(Parent):
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
# Only start Vite in the non-main process of the autoreloader
|
||||||
|
if settings.VITE_DEV_MODE and os.environ.get(DJANGO_AUTORELOAD_ENV) != "true":
|
||||||
|
# Start the vite server in the background
|
||||||
|
vite_server = subprocess.Popen(
|
||||||
|
["npm", "run", "dev"],
|
||||||
|
cwd=Path(__file__).parent.parent.parent.parent.parent
|
||||||
|
)
|
||||||
|
|
||||||
|
def cleanup():
|
||||||
|
vite_server.terminate()
|
||||||
|
try:
|
||||||
|
vite_server.wait(timeout=5)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
vite_server.kill()
|
||||||
|
|
||||||
|
atexit.register(cleanup)
|
||||||
|
|
||||||
|
super().handle(*args, **options)
|
||||||
@@ -280,11 +280,11 @@ class SecurityMiddleware(MiddlewareMixin):
|
|||||||
|
|
||||||
h = {
|
h = {
|
||||||
'default-src': ["{static}"],
|
'default-src': ["{static}"],
|
||||||
'script-src': ['{static}'],
|
'script-src': ["{static}", "http://localhost:5173", "ws://localhost:5173"] if settings.VITE_DEV_MODE else ["{static}"],
|
||||||
'object-src': ["'none'"],
|
'object-src': ["'none'"],
|
||||||
'frame-src': ['{static}'],
|
'frame-src': ['{static}'],
|
||||||
'style-src': ["{static}", "{media}"],
|
'style-src': ["{static}", "{media}"],
|
||||||
'connect-src': ["{dynamic}", "{media}"],
|
'connect-src': ["{dynamic}", "{media}", "ws://localhost:5173" if settings.VITE_DEV_MODE else ""],
|
||||||
'img-src': ["{static}", "{media}", "data:"] + img_src,
|
'img-src': ["{static}", "{media}", "data:"] + img_src,
|
||||||
'font-src': ["{static}"] + list(font_src),
|
'font-src': ["{static}"] + list(font_src),
|
||||||
'media-src': ["{static}", "data:"],
|
'media-src': ["{static}", "data:"],
|
||||||
|
|||||||
92
src/pretix/base/templatetags/vite.py
Normal file
92
src/pretix/base/templatetags/vite.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022-present Tobias Kunze
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only WITH LicenseRef-Pretalx-AGPL-3.0-Terms
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
_MANIFEST = {}
|
||||||
|
MANIFEST_PATH = settings.STATIC_ROOT + "pretalx-manifest.json"
|
||||||
|
|
||||||
|
# We're building the manifest if we don't have a dev server running AND if we're
|
||||||
|
# not currently running `rebuild` (which creates the manifest in the first place).
|
||||||
|
if not settings.VITE_DEV_MODE and not settings.VITE_IGNORE:
|
||||||
|
try:
|
||||||
|
with open(MANIFEST_PATH) as fp:
|
||||||
|
_MANIFEST = json.load(fp)
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.warning(f"Error reading vite manifest at {MANIFEST_PATH}: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_script_tag(path, attrs):
|
||||||
|
all_attrs = " ".join(f'{key}="{value}"' for key, value in attrs.items())
|
||||||
|
if settings.VITE_DEV_MODE:
|
||||||
|
src = urljoin(settings.VITE_DEV_SERVER, path)
|
||||||
|
else:
|
||||||
|
src = urljoin(settings.STATIC_URL, path)
|
||||||
|
return f'<script {all_attrs} src="{src}"></script>'
|
||||||
|
|
||||||
|
|
||||||
|
def generate_css_tags(asset, already_processed=None):
|
||||||
|
"""Recursively builds all CSS tags used in a given asset.
|
||||||
|
|
||||||
|
Ignore the side effects."""
|
||||||
|
tags = []
|
||||||
|
manifest_entry = _MANIFEST[asset]
|
||||||
|
if already_processed is None:
|
||||||
|
already_processed = []
|
||||||
|
|
||||||
|
# Put our own CSS file first for specificity
|
||||||
|
if "css" in manifest_entry:
|
||||||
|
for css_path in manifest_entry["css"]:
|
||||||
|
if css_path not in already_processed:
|
||||||
|
full_path = urljoin(settings.STATIC_URL, css_path)
|
||||||
|
tags.append(f'<link rel="stylesheet" href="{full_path}" />')
|
||||||
|
already_processed.append(css_path)
|
||||||
|
|
||||||
|
# Import each file only one by way of side effects in already_processed
|
||||||
|
if "imports" in manifest_entry:
|
||||||
|
for import_path in manifest_entry["imports"]:
|
||||||
|
tags += generate_css_tags(import_path, already_processed)
|
||||||
|
|
||||||
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
@mark_safe
|
||||||
|
def vite_asset(path):
|
||||||
|
"""
|
||||||
|
Generates one <script> tag and <link> tags for each of the CSS dependencies.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not path:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if settings.VITE_DEV_MODE:
|
||||||
|
return generate_script_tag(path, {"type": "module"})
|
||||||
|
|
||||||
|
manifest_entry = _MANIFEST.get(path)
|
||||||
|
if not manifest_entry:
|
||||||
|
raise RuntimeError(f"Cannot find {path} in Vite manifest at {MANIFEST_PATH}")
|
||||||
|
|
||||||
|
tags = generate_css_tags(path)
|
||||||
|
tags.append(
|
||||||
|
generate_script_tag(
|
||||||
|
manifest_entry["file"], {"type": "module", "crossorigin": ""}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return "".join(tags)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
@mark_safe
|
||||||
|
def vite_hmr():
|
||||||
|
if not settings.VITE_DEV_MODE:
|
||||||
|
return ""
|
||||||
|
return generate_script_tag("@vite/client", {"type": "module"})
|
||||||
@@ -863,3 +863,9 @@ FILE_UPLOAD_MAX_SIZE_EMAIL_AUTO_ATTACHMENT = 1024 * 1024 * config.getint("pretix
|
|||||||
FILE_UPLOAD_MAX_SIZE_OTHER = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_other", fallback=10)
|
FILE_UPLOAD_MAX_SIZE_OTHER = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_other", fallback=10)
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
|
||||||
|
VITE_DEV_SERVER_PORT = 5173
|
||||||
|
VITE_DEV_SERVER = f"http://localhost:{VITE_DEV_SERVER_PORT}"
|
||||||
|
VITE_DEV_MODE = DEBUG
|
||||||
|
VITE_IGNORE = False # Used to ignore `collectstatic`/`rebuild`
|
||||||
|
|||||||
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"include": ["src/pretix/static/**/*", "src/pretix/static/**/*.vue"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
},
|
||||||
|
"strict": false,
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"noErrorTruncation": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"types": ["node", "events"]
|
||||||
|
}
|
||||||
|
}
|
||||||
22
vite.config.js
Normal file
22
vite.config.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue()
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
manifest: true,
|
||||||
|
outDir: path.resolve(__dirname, '../../static.dist/vite'),
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
// 'webcheckin/main': path.resolve(__dirname, '../plugins/webcheckin/static/pretixplugins/webcheckin/main.js'),
|
||||||
|
'checkinrules/main': path.resolve(__dirname, '../pretixcontrol/js/ui/checkinrules/index.ts')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['moment', 'jquery']
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user