
Technology stack
- Vite 8 – Build tool with HMR
- Vue.js 3.5 – Interactive components (TodoList, GallerySwiper, SwiperSlider)
- Bootstrap 5.3 – UI framework
- Sass 1.99 – CSS preprocessor (Modern Compiler API)
- PostCSS – preset-env, pxtorem
- ESLint 10 / Stylelint 17 – Code quality
- Swiper 12 – Touch sliders (integrated via Vue components)
- Jarallax 3 – Parallax scrolling
Quick Start
cd Build
npm ci
npm run watch # Auto-rebuild on file changes
In an mpc monorepo running DDEV, you can also start the build from the website’s root directory: ddev mp-core-build (equivalent to npm run build within libs/mp-core/Build/).
The output is generated in Resources/Public/ (JavaScripts, stylesheets, fonts, icons, images, favicons, backend layouts).
| Script | Description |
|---|---|
build | Lint + production build (minified, optimised) + bundle size check |
build:analyze | Production build with rollup-plugin-visualizer (writes reports/bundle-stats.html) |
dev | Lint + development build with source maps |
watch | Development build with file watcher |
check-size | Run the bundle size budget gate for Resources/Public/ |
lint | Run ESLint + Stylelint |
eslint / eslint.fix | JavaScript linting |
stylelint / stylelint.fix | CSS/SCSS linting |
Clean build: rm -rf node_modules Resources/Public && npm ci && npm run build
Project structure
Build directory
Build/
├── Assets/
│ ├── Fonts/ # Web fonts (WOFF2)
│ ├── Images/ # Source images, Icons/
│ ├── Scripts/ # JavaScript/Vue
│ │ ├── code/ # Feature modules
│ │ │ ├── Utils/ # Shared utilities (domUtils.js, …)
│ │ │ ├── Vue/ # vue-initialisation.js (component registry)
│ │ │ └── Navigation/ # Primary / Secondary / Tertiary
│ │ └── components/ # Vue SFCs
│ ├── Scss/ # SCSS (ITCSS layers)
│ │ ├── Base/ # Variables, fonts
│ │ ├── Elements/ # Base elements
│ │ ├── Mixins/ # SCSS mixins
│ │ ├── Modules/ # UI components
│ │ ├── Templates/ # Layout helpers
│ │ └── Extensions/ # TYPO3 extension overrides
│ └── Static/ # Copied as-is (BackendLayouts, Favicons)
├── vite.config.js
├── eslint.config.js
├── stylelint.config.js
└── postcss.config.js
Resources directory
Resources/
├── Private/ # Fluid templates (not web-accessible)
│ ├── Backend/, Language/, Layouts/, Partials/, Templates/
├── Extensions/ # Extension template overrides
│ ├── fluid_styled_content/, form/, indexed_search/, news/
└── Public/ # Compiled assets (web-accessible)
├── Fonts/, Icons/, Images/, JavaScripts/, StyleSheets/, Favicons/
Bundle budgets
Each npm run build ends with a call to scripts/check-bundle-size.js. The
script reads each compiled JS/CSS file from Resources/Public/, calculates the
sizes using gzip (level 9) and Brotli (quality 11), compares these with
scripts/bundle-budgets.jsonand terminates execution with a non-zero value if a file-specific or
combined total budget is exceeded. A WARN message (orange warning) is issued if a
bundle is within 10 per cent of its budget – this is the signal to carry out a refactoring before
the next function pushes us over the limit.
If a bundle has grown for valid reasons (new component, intended upgrade of a dependency),
update scripts/bundle-budgets.json in the same commit as the size change
. Never increase a budget simply to bypass the gate.
Baseline (3 June 2026)
Captured with Vite 8, Bootstrap 5.3.8, Vue 3.5, Swiper 12.
| Bundle | Raw | Gzip | Brotli |
|---|---|---|---|
bootstrap.js | 65.9 KiB | 20.1 KiB | 17.9 KiB |
screen.js | 33.0 KiB | 9.6 KiB | 8.6 KiB |
vue.js | 239.8 KiB | 72.5 KiB | 63.8 KiB |
navigationPrimary.js | 1.7 KiB | 662 B | 560 B |
navigationSecondary.js | 5.6 KiB | 1.3 KiB | 1.2 KiB |
navigationTertiary.js | 4.4 KiB | 1.3 KiB | 1.1 KiB |
paginationTruncate.js | 1.7 KiB | 733 B | 579 B |
theme-init.js | 222 B | 183 B | 129 B |
bootstrap.css | 174.1 KiB | 24.2 KiB | 17.5 KiB |
screen.css | 67.4 KiB | 10.6 KiB | 9.1 KiB |
vue.css | 24.2 KiB | 3.9 KiB | 3.4 KiB |
navigationPrimary.css | 7.8 KiB | 1.8 KiB | 1.6 KiB |
navigationSecondary.css | 25.2 KiB | 3.6 KiB | 3.2 KiB |
navigationTertiary.css | 17.3 KiB | 3.2 KiB | 2.8 KiB |
ckeditor.css | 18.3 KiB | 2.7 KiB | 2.3 KiB |
print.css | 1.3 KiB | 559 B | 432 B |
| Total | -- | 158.7 KiB | 135.8 KiB |
Following the refactoring of ‘manual-chunk’ and ‘dynamic-import’ (see ‘Vendor separation’
below), it is expected that vue.js to shrink considerably, as the Vue runtime environment,
Swiper and the three Vue SFCs will be moved to long-lived vendor-* / per-SFC
chunks. Run the process npm run check-size once after the next build and reduce
the affected budgets in scripts/bundle-budgets.json.
‘Vendor Splitting’
vite.config.js defines a manualChunks map that groups these
node_modules paths into named vendor chunks:
| Block | Content |
|---|---|
vendor-vue | vue, @vue/* |
vendor-swiper | swiper |
vendor-bootstrap | bootstrap |
vendor-popper | @popperjs/core |
vendor-jarallax | jarallax |
Provider blocks only change if the specified dependency changes; therefore, they remain in the HTTP cache across deployments, whilst our own application code changes.
Code splitting for Vue components
code/Vue/vue-initialisation.js uses dynamic import() per component, so that
each .vue SFC is compiled into its own chunk. A page that only
SwiperSlider is never downloaded TodoList or GallerySwiper. Add new
components by using the componentLoaders map in
vue-initialisation.js.
On-demand provider: Jarallax
code/jarallax.js loads the Jarallax provider bundle on a delayed basis via dynamic
import('jarallax'), subject to a document.querySelectorAll('.grid-parallax')
presence check. The .grid-parallax Wrapper is only rendered
fluid_styled_content/Layouts/Container.html if an editor has the
parallax tick box (grid_parallax = 1) for a ce_container-style
content element.
Impact: Pages without a parallax container request the
vendor-jarallax-*.js chunk (~26 KiB in raw format / ~7 KiB gzip / ~6 KiB brotli).
Thanks to the manualChunks
map; only the network request changes from ‘eager’ to ‘deferred’.
Visualisation of the bundle
npm run build:analyze
Writes Build/reports/bundle-stats.html (Treemap, gzip- and Brotli-compatible,
git-ignored). Open directly in the browser; no server required.
Vite entry points
Defined in Build/vite.config.js:
| Bundle | Purpose |
|---|---|
bootstrap.js | Initialisation of the Bootstrap framework |
screen.js | Main front-end (sticky header, theme, etc.) |
vue.js | Vue.js 3 components (including Swiper integration) |
navigationPrimary/Secondary/Tertiary.js | Navigation levels |
print.js | Print-specific styles |
backend.js | TYPO3 backend styles |
ckeditor.js | CKEditor RTE styles |
Note: Swiper is integrated into the
vue.jsbundle – there is no separateswiper.jsentry point.
Add a new entry
- Create a JS file in
Build/Assets/Scripts/ - Register under
vite.config.js - Execute
npm run watch - Integrate into Fluid:
<f:asset.script identifier="myfeature" src="EXT:mp_core/Resources/Public/JavaScripts/myfeature.js" />
<f:asset.css identifier="myfeature" href="EXT:mp_core/Resources/Public/StyleSheets/myfeature.css" />
JavaScript architecture
Function modules (Build/Assets/Scripts/code/)
Core: main.js, i18n.js, i18nLinkHelper.js
UI: jarallax.js, modalGallery.js, openAccordionAndTabs.js, pagination.js, sticky.js, totop.js
Navigation: nav-toggle.js, Navigation/Primary/navigation.js, Navigation/Secondary/navigation.js, Navigation/Tertiary/navigation.js
Layout: moveHeaderDate.js, moveMeta.js, theme.js
Search: searchAutosuggest.js — Auto-complete for indexed_search (header and /suche form)
Shared utility functions (code/Utils/domUtils.js)
debounce(func, wait)-- Performance-optimised handling of resizing and scrollingtoggleNavState(...)-- open/closed state of the navigationhandleDropdownVisibility(element, showCb, hideCb)-- Bootstrap dropdown eventstoggleAriaLabelAndTitle(element, openLabel, closeLabel)-- Accessible toggling of labels
Vue.js components
Located in Build/Assets/Scripts/components/:
| Component | Description |
|---|---|
TodoList.vue | Interactive to-do list using localStorage, registered as a CType mpcore_todolist |
GallerySwiper.vue | Swiper-based gallery carousel for the gallery content element |
SwiperSlider.vue | Generic Swiper slider for container slider elements |
The components are registered in code/Vue/vue-initialisation.js.
Vue mounts on elements with data-container="vue" and data-component="ComponentName" (see VueComponents.typoscript and templates for content elements). Optional data-* attributes are passed via props (e.g. data-card-title in ‘TodoList’).
Creating a new component
- Create
.vuefile inBuild/Assets/Scripts/components/ - Register in
code/Vue/vue-initialisation.js - Create and via
<f:asset.script>in Fluid (thevue.jsentry point automatically integrates registered components).
SCSS architecture (ITCSS)
Levels ranging from low to high specificity:
- Settings (
Base/) – variables, fonts, colour palettes - Tools (
Mixins/) – functions, mixins (no CSS output) - General – Reset, Normalise (from Bootstrap)
- Elements (
Elements/) – Basic HTML elements - Objects -- Layout patterns
- Components (
Modules/) – Styled UI components - Utility functions -- Utility classes
Bootstrap customisation
- Light theme:
Build/Assets/Scss/Base/Bootstrap/_custom-variables.scss - Dark theme:
Build/Assets/Scss/Base/Bootstrap/_custom-variables-dark.scss
Asset Management
| Asset type | Templates |
|---|---|
| Images in SCSS | url('../../Images/Icons/icon.png') (relative path) |
| Fonts | @include font-face('Name', '../../Fonts/file', 400, normal, woff2) |
| Inline SVG | svg-load('../Images/Icons/arrow.svg') |
| Static files | Build/Assets/Static/ -> copied to Resources/Public/ |
Template integration
Fluid
<f:asset.css identifier="screen" href="EXT:mp_core/Resources/Public/StyleSheets/screen.css" />
<f:asset.script identifier="screen" src="EXT:mp_core/Resources/Public/JavaScripts/screen.js" />
TypoScript
page {
includeCSS.screen = EXT:mp_core/Resources/Public/StyleSheets/screen.css
includeJSFooter.screen = EXT:mp_core/Resources/Public/JavaScripts/screen.js
}
Priority of template paths: Higher numbers take precedence over lower ones (0 = core, 10 = extension, 20+ = project).
Overrides caused by extensions
| Extension | Path | Notes |
|---|---|---|
| fluid_styled_content | Resources/Extensions/fluid_styled_content/Private/ | In Bootstrap 5 style |
| Form | Resources/Extensions/form/ | Bootstrap forms + YAML configuration |
| Posts | Resources/Extensions/news/ | List, detail and category views |
| Indexed search | Resources/Extensions/indexed_search/ | Bootstrap search results + drop-down menu with autocomplete |
Search (indexed_search)
mp-core replaces the default ‘indexed_search’ templates and adds an accessible combo box suggestion list for the search field in the header as well as for the dedicated search page (/suche).
Website settings
Configure under ‘Website management’ → ‘Websites’ → ‘Settings’ → ‘Search’ (or config/sites/<id>/settings.yaml):
| Setting | Default | Effect |
|---|---|---|
search.headerSearch | true | Show search box in the header (all navigation variants) |
search.autosuggest | true | Suggestions whilst typing for header and /suche forms |
If the automatic suggestion function is deactivated, a simple search field is displayed in both forms.
Behaviour
- Suggestions are retrieved as JSON from
SearchSuggestMiddleware/SearchSuggestService— indexed base words and matching page titles, limited to the current website, language and front-end user access (no Solr required). - The drop-down list follows the WAI-ARIA listbox pattern with keyboard navigation and polite status messages.
- The top results link to the same detail URLs as the full search results (including media library entries, which are resolved via indexer route arguments).
- The header search is sent via POST to the “indexed_search”
search(cHash-safe). For desktop navigation types 1/2/3, the field appears either in the meta bar flyout or inline in the mobile hamburger menu, depending on the breakpoint.
Front-end module: Build/Assets/Scripts/code/searchAutosuggest.js (included in screen.js).
Best practices
JavaScript: Modular code in code/, common patterns in Utils/, debounce on resize/scroll, event delegation, ARIA labels, lint before committing.
SCSS: Observe ITCSS levels, CSS variables for theming, logical properties for RTL, maximum of 3 nesting levels, mobile-first min-width queries.
Vue.js: Single-file components, scope-specific styles, prop validation, Composition API for complex logic.
Troubleshooting
| Problem | Solution |
|---|---|
| 404 error with fonts | Check ../ Segments from SCSS for Fonts/ |
| Missing images in CSS | Path in the Assets/Images/ |
| bundle is too large | Import only the required Bootstrap components |
| Changes are not displayed | Clear the browser and TYPO3 cache |
Important: Never Resources/Public/ directly. Always edit in Build/Assets/ and run npm run build.
Further information
- Favicons – Favicon assets and Fluid Partial (not with
output.html) - configuration – Site Sets, TypoScript, TCA
- Vite | Vue.js | Bootstrap 5 | ITCSS