Skip to content

Commit 55c5443

Browse files
committed
feature: example plugin
0 parents  commit 55c5443

19 files changed

Lines changed: 4634 additions & 0 deletions

.dockerignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
dist
3+
.git
4+
.github

.github/workflows/build.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches:
6+
- "main"
7+
workflow_dispatch:
8+
9+
concurrency:
10+
group: build
11+
cancel-in-progress: true
12+
13+
permissions:
14+
contents: read
15+
packages: write
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-24.04
20+
steps:
21+
- name: Check out the repo
22+
uses: actions/checkout@v4
23+
- name: Set up QEMU
24+
uses: docker/setup-qemu-action@v3
25+
- name: Set up Docker Buildx
26+
uses: docker/setup-buildx-action@v3
27+
- name: Log in to GitHub Docker Registry
28+
uses: docker/login-action@v3
29+
with:
30+
registry: ghcr.io
31+
username: ${{ github.actor }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
33+
- name: Build and push
34+
uses: docker/build-push-action@v6
35+
with:
36+
context: .
37+
file: ./Dockerfile
38+
push: true
39+
platforms: linux/amd64,linux/arm64
40+
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/5stack-example-plugin:buildcache
41+
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/5stack-example-plugin:buildcache,mode=max
42+
tags: |
43+
ghcr.io/${{ github.repository_owner }}/5stack-example-plugin:latest
44+
ghcr.io/${{ github.repository_owner }}/5stack-example-plugin:${{ github.sha }}
45+
- name: Delete old package versions
46+
uses: actions/delete-package-versions@v5
47+
with:
48+
package-name: 5stack-example-plugin
49+
package-type: container
50+
min-versions-to-keep: 9
51+
ignore-versions: '^buildcache$'

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Builds the Module Federation remote (remoteEntry.js + assets) and serves it as
2+
# static files. There is no backend — a plugin this small is just a web server
3+
# handing the panel one JS file.
4+
FROM node:22-alpine AS build
5+
WORKDIR /app
6+
COPY package.json package-lock.json ./
7+
RUN npm ci
8+
COPY . .
9+
RUN npm run build
10+
11+
FROM nginx:alpine
12+
COPY nginx.conf /etc/nginx/conf.d/default.conf
13+
COPY --from=build /app/dist /usr/share/nginx/html
14+
EXPOSE 80

README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# 5stack Plugin — Hello World
2+
3+
The smallest complete [5stack plugin](https://docs.5stack.gg/plugins): a Vue
4+
Module Federation remote that renders **natively inside the 5stack panel**
5+
same sidebar, header, theme, and login. Copy this repo as the starting point for
6+
your own.
7+
8+
```sh
9+
npm install
10+
npm run dev # standalone preview at :5173 with a fake dev user
11+
npm run build # -> dist/
12+
npm run preview # serve dist/ at :4173 with CORS, for testing in a real panel
13+
```
14+
15+
## What makes it a plugin
16+
17+
| File | Role |
18+
| --- | --- |
19+
| `src/App.vue` | Your UI. Receives the logged-in `user` as a prop. |
20+
| `vite.config.ts` | Exposes `./App` as a Federation remote; declares shared singletons. |
21+
| `public/5stack-plugin.json` | The manifest the panel auto-detects. |
22+
| `tailwind.config.js` | Pulls in the `@5stack/ui` preset so you inherit 5stack theming. |
23+
| `src/main.ts` | Standalone dev entry — **not** used when embedded in the panel. |
24+
25+
## Try it against a running panel
26+
27+
```sh
28+
npm run build && npm run preview
29+
```
30+
31+
Then in the panel: **Settings → Application → Custom Pages → Add**, paste
32+
`http://localhost:4173`, hit **Detect**, toggle **Enabled**, and save. Make sure
33+
the **Custom Pages** master switch is on. Your page appears in the sidebar at
34+
`/apps/hello`.
35+
36+
`npm run preview` already sends the CORS and `no-store` headers the panel needs.
37+
When you deploy for real, your own web server must do the same — see
38+
[Deploying](https://docs.5stack.gg/plugins/deploying).
39+
40+
## Renaming it
41+
42+
Federation scopes share one flat global namespace across every plugin an
43+
operator installs, so `hello` will collide — pick something specific:
44+
45+
| Rename | In |
46+
| --- | --- |
47+
| `name: "hello"` | `vite.config.ts` |
48+
| `"scope": "hello"` | `public/5stack-plugin.json` (must equal the above) |
49+
| `"slug": "hello"` | `public/5stack-plugin.json` (your URL: `/apps/<slug>`) |
50+
| `[data-hello-plugin]` | `tailwind.config.js` and `src/App.vue` (must match) |
51+
52+
## Two things that are easy to get wrong
53+
54+
**Version lockstep.** Every package in `shared` is `requiredVersion: false`, so a
55+
version that disagrees with the panel's does not error — it silently loads a
56+
second copy and breaks reactivity. Pin the same versions the panel uses.
57+
58+
**Style scoping.** This plugin's CSS is injected at runtime *after* the panel's,
59+
so unscoped utilities would override host chrome. `important:
60+
"[data-hello-plugin]"` in `tailwind.config.js` scopes every utility under the
61+
wrapper in `App.vue`. Keep both in sync, and prefer named theme tokens over
62+
arbitrary values like `min-h-[60vh]`.
63+
64+
## Docs
65+
66+
Full guide: **https://docs.5stack.gg/plugins**
67+
68+
- [Getting Started](https://docs.5stack.gg/plugins/getting-started)
69+
- [The Manifest](https://docs.5stack.gg/plugins/manifest)
70+
- [Module Federation](https://docs.5stack.gg/plugins/module-federation)
71+
- [Styling](https://docs.5stack.gg/plugins/styling)
72+
- [Components](https://docs.5stack.gg/plugins/components)
73+
- [Backend & Auth](https://docs.5stack.gg/plugins/backend)
74+
- [Deploying](https://docs.5stack.gg/plugins/deploying)
75+
76+
For a fuller example with a backend and a database, see the
77+
[inventory plugin](https://github.com/lukepolo/5stack-inventory-plugin).

index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en" class="dark">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>5stack Hello World (dev)</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="/src/main.ts"></script>
11+
</body>
12+
</html>

nginx.conf

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
server {
2+
listen 80;
3+
server_name _;
4+
root /usr/share/nginx/html;
5+
6+
# The panel imports remoteEntry.js cross-origin; allow any origin (public JS,
7+
# no credentials). Handled here in the container — not in the ingress.
8+
location / {
9+
add_header Access-Control-Allow-Origin "*" always;
10+
try_files $uri $uri/ =404;
11+
}
12+
13+
# remoteEntry.js keeps a STABLE filename but its content changes every build,
14+
# so it must never be cached by a CDN (Cloudflare) — otherwise a stale copy
15+
# (or a cached 404) is served. Hashed /assets/* chunks stay cacheable.
16+
location = /assets/remoteEntry.js {
17+
add_header Access-Control-Allow-Origin "*" always;
18+
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
19+
try_files $uri =404;
20+
}
21+
22+
# Same reasoning: the panel re-reads the manifest to detect the plugin.
23+
location = /5stack-plugin.json {
24+
add_header Access-Control-Allow-Origin "*" always;
25+
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
26+
try_files $uri =404;
27+
}
28+
}

0 commit comments

Comments
 (0)