Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions .github/workflows/studio-frontend-build.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
name: "Studio Frontend Build"
name: "Studio Frontend Build Packaged"

on:
push:
paths:
- "assets/studio/**"
- "assets/**"
- "build-dist/**"
pull_request:
paths:
- "assets/studio/**"
- "assets/**"
workflow_dispatch:


concurrency:
group: studio-frontend-build-packaged-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

permissions:
contents: write

jobs:
studio-frontend-build:
uses: pimcore/workflows-collection-public/.github/workflows/reusable-studio-frontend-build.yaml@main
uses: pimcore/workflows-collection-public/.github/workflows/reusable-studio-frontend-build-packaged.yaml@main
with:
working-directory: assets
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ nbproject
/.env
/.htaccess
/auth.json
/config.json
/config.json

# Expanded frontend build is generated from build-dist/build-<id>.zip, not committed
/public/build/*
!/public/build/.gitkeep
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,28 @@ This bundle provides the basic structure of a Pimcore Studio example extension b
The Pimcore Studio Example Bundle was created to help developers quickly get started with creating custom extensions for Pimcore Studio. It includes a set of examples for extending the UI and integrating new features.

For further information, visit the [Pimcore Studio UI Plugin Development Examples documentation](https://github.com/pimcore/studio-ui-bundle/blob/2026.x/doc/04_Extending/02_Plugin_Development_Examples/README.md).

## Frontend build

The compiled frontend is shipped as a single committed archive, `build-dist/build-<id>.zip`,
instead of a committed `public/build/` directory. On the target installation the Studio UI
Bundle extracts it into `public/build/` during `bin/console cache:warmup`, so a deployment
needs no Node.js and no per-bundle build step. See
[Shipping the Frontend Build as an Archive](https://github.com/pimcore/studio-ui-bundle/blob/2026.x/doc/04_Extending/04_Shipping_the_Frontend_Build_as_an_Archive.md)
for the full mechanism.

To rebuild it after changing anything under `assets/`:

```bash
cd assets
npm ci
npm run build && npm run package-build
```

The build id is derived from the contents of `assets/`, so an unchanged source produces the
same archive and nothing to commit. `public/build/` is gitignored — a build you produced
locally is never overwritten by the extractor; delete the directory and warm the cache to go
back to the committed archive.

CI runs both scripts on every push touching `assets/` and commits `build-dist/` back to the
branch, so committing the archive by hand is optional.
24 changes: 12 additions & 12 deletions assets/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dev": "NODE_ENV=development rsbuild build",
"dev-server": "NODE_ENV=dev-server rsbuild dev",
"build": "rsbuild build",
"package-build": "studio-package-build --build-dir ../public/build --out-dir ../build-dist",
"lint": "eslint --ext .js,.jsx,.ts,.tsx ./js",
"lint-fix": "eslint --ext .js,.jsx,.ts,.tsx ./js --fix",
"lint-fix-watch": "nodemon --watch './js/**/*' --exec \"eslint --ext .js,.jsx,.ts,.tsx ./js --fix --cache\" -e js,jsx,ts,tsx",
Expand Down Expand Up @@ -56,11 +57,10 @@
"whatwg-fetch": "^3.6.20"
},
"dependencies": {
"@pimcore/studio-ui-bundle": "2026.2.0",
"@pimcore/studio-ui-bundle": "^2026.2.11",
"nodemon": "^3.1.10",
"react": "18.3.x",
"react-dom": "18.3.x",
"uuid": "^10.0.0"
"react-dom": "18.3.x"
},
"overrides": {
"axios": "1.12.0"
Expand Down
27 changes: 19 additions & 8 deletions assets/rsbuild.config.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import { defineConfig } from '@rsbuild/core'
import { pluginReact } from '@rsbuild/plugin-react'
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { pluginGenerateEntrypoints } from '@pimcore/studio-ui-bundle/rsbuild/plugins';
import { pluginGenerateEntrypoints, pluginWriteBuildId } from '@pimcore/studio-ui-bundle/rsbuild/plugins';
import { createDynamicRemote } from '@pimcore/studio-ui-bundle/rsbuild/utils';
import { getBuildGroupId } from '@pimcore/studio-ui-bundle/bundler/build-id';
import path from 'path'
import fs from 'fs';
import { v4 } from 'uuid';
import packages from './package.json'

const buildId = v4();
const buildPath = path.resolve(__dirname, '..', 'public', 'build', buildId);
// Content-derived id for this assets tree (node_modules and dist excluded), so an unchanged
// source yields the same id - and therefore the same build-dist/build-<id>.zip - instead of a
// fresh archive on every build. The build output lives outside this directory, otherwise each
// build would change the id.
const buildId = getBuildGroupId(__dirname);
const buildRoot = path.resolve(__dirname, '..', 'public', 'build');
const buildPath = path.resolve(buildRoot, buildId);

if (fs.existsSync( path.resolve(__dirname, '..', 'public', 'build'))) {
fs.readdirSync(path.resolve(__dirname, '..', 'public', 'build')).forEach((file) => {
if (file !== 'studio-npm-package.tgz') {
fs.rmSync(path.resolve(__dirname, '..', 'public', 'build', file), { recursive: true });
// Drop stale build dirs: studio-package-build picks the build id deterministically from the
// .build-id files it finds on disk, not "the newest", so a leftover dir from an earlier source
// state could otherwise end up in the archive instead of this build.
// Only directories are swept: plain files at this level must survive, in particular the
// tracked .gitkeep that keeps public/build/ in git once the expanded build is gitignored.
if (fs.existsSync(buildRoot)) {
fs.readdirSync(buildRoot, { withFileTypes: true }).forEach((entry) => {
if (entry.isDirectory() && entry.name !== buildId) {
fs.rmSync(path.resolve(buildRoot, entry.name), { recursive: true, force: true });
}
})
}
Expand Down Expand Up @@ -65,6 +75,7 @@ export default defineConfig({
},
},
plugins: [
pluginWriteBuildId({ buildId }),
pluginGenerateEntrypoints(),
pluginReact(),
pluginModuleFederation({
Expand Down
Binary file added build-dist/build-fc755267691a.zip
Binary file not shown.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"prefer-stable": true,
"minimum-stability": "dev",
"require": {
"pimcore/studio-ui-bundle": "^2025.4 || ^2026.1",
"pimcore/studio-ui-bundle": "^2025.4.9 || ^2026.2.1",
"pimcore/studio-backend-bundle": "^2025.4 || ^2026.1",
"pimcore/studio-dashboards-bundle": "^2025.4 || ^2026.1"
},
Expand Down
Empty file added public/build/.gitkeep
Empty file.

This file was deleted.

This file was deleted.

25 changes: 0 additions & 25 deletions public/build/0cee0751-e939-48ab-8371-8a5241ad02c2/entrypoints.json

This file was deleted.

13 changes: 0 additions & 13 deletions public/build/0cee0751-e939-48ab-8371-8a5241ad02c2/exposeRemote.js

This file was deleted.

This file was deleted.

38 changes: 0 additions & 38 deletions public/build/0cee0751-e939-48ab-8371-8a5241ad02c2/manifest.json

This file was deleted.

Loading
Loading