create-ampless 1.0.0-alpha.78 → 1.0.0-alpha.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +411 -32
- package/dist/templates/_shared/docs/plugin-author-guide.ja.md +258 -12
- package/dist/templates/_shared/docs/plugin-author-guide.md +337 -20
- package/dist/templates/_shared/package.json +12 -12
- package/dist/templates/_shared/plugins/README.ja.md +30 -6
- package/dist/templates/_shared/plugins/README.md +32 -6
- package/dist/templates/plugin-local/README.md +34 -0
- package/dist/templates/plugin-local/index.ts +39 -0
- package/dist/templates/plugin-standalone/CHANGELOG.md +1 -0
- package/dist/templates/plugin-standalone/README.ja.md +43 -0
- package/dist/templates/plugin-standalone/README.md +43 -0
- package/dist/templates/plugin-standalone/package.json +47 -0
- package/dist/templates/plugin-standalone/src/index.test.ts +16 -0
- package/dist/templates/plugin-standalone/src/index.ts +29 -0
- package/dist/templates/plugin-standalone/tsconfig.json +16 -0
- package/dist/templates/plugin-standalone/tsup.config.ts +8 -0
- package/package.json +1 -1
|
@@ -21,8 +21,62 @@ this page is the hands-on companion.
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
+
## 0. Theme vs Plugin Boundary
|
|
25
|
+
|
|
26
|
+
ampless ships both themes and plugins. Picking the right tool keeps
|
|
27
|
+
your code where future-you (and other site authors) will look for it.
|
|
28
|
+
|
|
29
|
+
| Want to change... | Use a theme | Use a plugin |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| Layout, typography, colour, per-route UI | ✓ | |
|
|
32
|
+
| Custom components on home / post / tag pages | ✓ | |
|
|
33
|
+
| Admin-editable settings exposed to non-developers | | ✓ (`adminSettings`) |
|
|
34
|
+
| Background work after content events (RSS, search index, webhooks) | | ✓ (`eventHooks`) |
|
|
35
|
+
| Trusted side effects (S3 writes, external API push) | | ✓ (`writePublicAsset` + `trusted`) |
|
|
36
|
+
| Theme-independent `<head>` / `<body>` injection (analytics, consent) | | ✓ (`publicHead` / `publicBodyEnd`) |
|
|
37
|
+
| Per-post machine-readable metadata (JSON-LD, etc.) | | ✓ (`schema` via `publicBodyForPost`) |
|
|
38
|
+
| Code you want to share across multiple ampless sites | | ✓ (publish as npm package) |
|
|
39
|
+
|
|
40
|
+
Rule of thumb:
|
|
41
|
+
|
|
42
|
+
- Theme = **what the page looks like**. Read-only at render time.
|
|
43
|
+
- Plugin = **what happens beyond rendering**: admin-editable config,
|
|
44
|
+
background processing, theme-agnostic injection, machine-readable
|
|
45
|
+
metadata, cross-site reusability.
|
|
46
|
+
|
|
47
|
+
The two boundaries that frequently catch new authors:
|
|
48
|
+
|
|
49
|
+
- **Storage / DynamoDB / external API writes belong in a plugin**, never
|
|
50
|
+
a theme. Themes only read.
|
|
51
|
+
- **A feature you want admins to toggle from `/admin/plugins` belongs
|
|
52
|
+
in a plugin**, even if its visible effect is purely cosmetic. Themes
|
|
53
|
+
carry their own settings, but those are theme-display settings, not
|
|
54
|
+
site-operational ones.
|
|
55
|
+
|
|
56
|
+
Some features genuinely sit at the boundary — see [`docs/architecture/08-plugin-architecture.md`](https://github.com/heavymoons/ampless/blob/main/docs/architecture/08-plugin-architecture.md)
|
|
57
|
+
for the longer discussion.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
24
61
|
## 1. What a plugin can do
|
|
25
62
|
|
|
63
|
+
ampless plugins are written in one of three places — pick the one
|
|
64
|
+
that matches how widely you want the code shared:
|
|
65
|
+
|
|
66
|
+
| Where | Use when | Lives in |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| **First-party** | Building on the ampless core for everyone | `packages/plugin-*/` in the ampless monorepo |
|
|
69
|
+
| **Site-local** | Site-specific customisation, no separate publish | `plugins/<name>/` inside your site repo |
|
|
70
|
+
| **External npm package** | Sharing with other sites, ready to `npm publish` | A standalone repo (`@scope/ampless-plugin-foo`) |
|
|
71
|
+
|
|
72
|
+
All three forms call the same `definePlugin({...})` factory and use the
|
|
73
|
+
same surfaces. The differences are packaging, distribution, and the
|
|
74
|
+
opt-in install-time validation that the static `package.json#amplessPlugin`
|
|
75
|
+
manifest enables (see §3 below).
|
|
76
|
+
|
|
77
|
+
§14 below has a one-command scaffold (`npx create-ampless plugin <name>`)
|
|
78
|
+
that produces ready-to-use boilerplate for either of the latter two.
|
|
79
|
+
|
|
26
80
|
An ampless plugin is a TypeScript module that returns an
|
|
27
81
|
`AmplessPlugin` object. It plugs into one or more of the following
|
|
28
82
|
surfaces:
|
|
@@ -38,36 +92,72 @@ surfaces:
|
|
|
38
92
|
| `hooks` | trust_level-matched processor Lambda | async, on SQS event | Existing |
|
|
39
93
|
| `settings.public` | `/admin/plugins` form | declarative manifest | 2 |
|
|
40
94
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
95
|
+
A few surfaces don't exist yet — they're reserved for later phases
|
|
96
|
+
and aren't shaped by `definePlugin` today:
|
|
97
|
+
|
|
98
|
+
- **Arbitrary `ReactNode` injection into pages.** The sync render
|
|
99
|
+
surfaces (`publicHead`, `publicBodyEnd`, `publicBodyForPost`) only
|
|
100
|
+
return descriptor variants. The descriptor validator is the safety
|
|
101
|
+
boundary for what the runtime renders into the page; it isn't a JS
|
|
102
|
+
sandbox around the plugin itself. (Plugins run as ordinary
|
|
103
|
+
TypeScript in the same Node process as the rest of the site.)
|
|
104
|
+
- **Network inside the sync render surfaces.** The sync surfaces are
|
|
105
|
+
shaped for declarative output — they don't take a Promise and there's
|
|
106
|
+
no async result path, so doing `await fetch(...)` inside `publicHead`
|
|
107
|
+
blocks SSR with no way to surface a deadline. Trusted Lambdas
|
|
108
|
+
(`hooks`) are where the runtime expects outbound HTTP. Background
|
|
109
|
+
work belongs there.
|
|
110
|
+
- **Admin routes / server routes / content fields.** Reserved for
|
|
111
|
+
Phase 6b.
|
|
112
|
+
- **Secrets.** The `secretSettings` capability is reserved for
|
|
113
|
+
Phase 6a; nothing in `settings.public` should be a credential.
|
|
51
114
|
|
|
52
115
|
---
|
|
53
116
|
|
|
54
117
|
## 2. Minimum file layout
|
|
55
118
|
|
|
56
|
-
|
|
57
|
-
|
|
119
|
+
The fastest way to create a plugin is to scaffold one:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Site-local (writes plugins/<name>/index.ts in the current ampless site)
|
|
123
|
+
npx create-ampless@latest plugin my-thing
|
|
124
|
+
|
|
125
|
+
# Standalone npm package (writes ./<name>/ ready for `npm publish`)
|
|
126
|
+
npx create-ampless@latest plugin @myscope/ampless-plugin-my-thing --standalone
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
§14 covers the full flow. The rest of this section explains what the
|
|
130
|
+
generated files mean, so you can author one by hand if you prefer.
|
|
131
|
+
|
|
132
|
+
### Site-local
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
plugins/
|
|
136
|
+
my-thing/
|
|
137
|
+
index.ts # the factory; that's the whole plugin
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The site's `package.json` / `tsconfig.json` already cover compilation —
|
|
141
|
+
nothing else needs to ship. Register it from `cms.config.ts` with a
|
|
142
|
+
relative import.
|
|
143
|
+
|
|
144
|
+
### Standalone npm package
|
|
58
145
|
|
|
59
146
|
```
|
|
60
|
-
my-
|
|
147
|
+
ampless-plugin-my-thing/
|
|
61
148
|
package.json
|
|
62
149
|
tsconfig.json
|
|
63
150
|
tsup.config.ts
|
|
151
|
+
README.md
|
|
152
|
+
CHANGELOG.md
|
|
64
153
|
src/
|
|
65
154
|
index.ts
|
|
66
155
|
index.test.ts
|
|
67
156
|
```
|
|
68
157
|
|
|
69
158
|
See `packages/plugin-rss/` and `packages/plugin-analytics-ga4/` in
|
|
70
|
-
this repo for working examples
|
|
159
|
+
this repo for working first-party examples — the standalone scaffold
|
|
160
|
+
mirrors their layout.
|
|
71
161
|
|
|
72
162
|
The bare-minimum `src/index.ts`:
|
|
73
163
|
|
|
@@ -108,6 +198,7 @@ That's it. Restart `npm run dev`, view source on any page, and the
|
|
|
108
198
|
```ts
|
|
109
199
|
interface AmplessPlugin {
|
|
110
200
|
name: string // package-like identifier, e.g. 'analytics-ga4'
|
|
201
|
+
packageName?: string // npm package name for install-time cross-check
|
|
111
202
|
apiVersion: 1 // bump only when the contract changes
|
|
112
203
|
trust_level: 'untrusted' | 'trusted' | 'privileged'
|
|
113
204
|
instanceId?: string // namespace for multi-instance installs
|
|
@@ -159,6 +250,87 @@ What `/admin/plugins` shows as the panel heading. A plain string is
|
|
|
159
250
|
fine for single-locale plugins; the per-locale map form
|
|
160
251
|
(`{ en: 'GA4', ja: 'GA4' }`) reads from the admin's active locale.
|
|
161
252
|
|
|
253
|
+
### `packageName`
|
|
254
|
+
|
|
255
|
+
Optional. When set, the runtime resolves
|
|
256
|
+
`<packageName>/package.json` at startup and cross-checks the static
|
|
257
|
+
`amplessPlugin` block there against the factory return value. This
|
|
258
|
+
catches install-time mistakes a plugin author would otherwise only see
|
|
259
|
+
at runtime (or never — capability mismatches don't crash, they just
|
|
260
|
+
quietly skip the surface).
|
|
261
|
+
|
|
262
|
+
For standalone plugins, set this to the npm package name your
|
|
263
|
+
`package.json#name` declares:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
return definePlugin({
|
|
267
|
+
name: 'site-verification',
|
|
268
|
+
packageName: '@ishinao/ampless-plugin-site-verification',
|
|
269
|
+
apiVersion: 1,
|
|
270
|
+
// ...
|
|
271
|
+
})
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Site-local plugins don't need it — leave it unset and the cross-check
|
|
275
|
+
is skipped (backward compatible, identical to plugins predating Phase
|
|
276
|
+
5).
|
|
277
|
+
|
|
278
|
+
### Static manifest in `package.json` (standalone plugins only)
|
|
279
|
+
|
|
280
|
+
For the cross-check to find the static manifest, two things must be
|
|
281
|
+
true about your published package:
|
|
282
|
+
|
|
283
|
+
1. `package.json#amplessPlugin` declares the same fields the factory
|
|
284
|
+
returns:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
"amplessPlugin": {
|
|
288
|
+
"apiVersion": 1,
|
|
289
|
+
"name": "site-verification",
|
|
290
|
+
"trustLevel": "untrusted",
|
|
291
|
+
"capabilities": ["publicHead", "adminSettings"],
|
|
292
|
+
"displayName": { "en": "Site verification", "ja": "サイト所有権確認" }
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
2. `package.json#exports` explicitly exposes `./package.json`:
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
"exports": {
|
|
300
|
+
".": {
|
|
301
|
+
"import": "./dist/index.js",
|
|
302
|
+
"types": "./dist/index.d.ts"
|
|
303
|
+
},
|
|
304
|
+
"./package.json": "./package.json"
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Without this, Node's package-exports gating rejects
|
|
309
|
+
`import.meta.resolve('<pkg>/package.json')` with
|
|
310
|
+
`ERR_PACKAGE_PATH_NOT_EXPORTED` and the runtime silently skips the
|
|
311
|
+
cross-check (your plugin still runs; you just don't get the
|
|
312
|
+
install-time guard).
|
|
313
|
+
|
|
314
|
+
The `create-ampless plugin --standalone` scaffold ships both correctly.
|
|
315
|
+
Add a `"ampless-plugin"` entry to your `package.json#keywords` while
|
|
316
|
+
you're there — it's the convention used by npm searches surfacing
|
|
317
|
+
ampless plugins.
|
|
318
|
+
|
|
319
|
+
What the runtime checks:
|
|
320
|
+
|
|
321
|
+
| Field | Mismatch behaviour |
|
|
322
|
+
|---|---|
|
|
323
|
+
| `apiVersion` (factory vs manifest) | **Throws** at startup |
|
|
324
|
+
| `apiVersion` (newer than runtime supports) | **Throws** at startup |
|
|
325
|
+
| `name` | Warns in dev |
|
|
326
|
+
| `trustLevel` | Warns in dev |
|
|
327
|
+
| `capabilities` (set comparison) | Warns in dev |
|
|
328
|
+
|
|
329
|
+
The two `apiVersion` cases are the only failure that aborts startup —
|
|
330
|
+
they protect against loading a plugin built for an ampless API the
|
|
331
|
+
runtime can't speak. Everything else is a developer-visible warning,
|
|
332
|
+
not a runtime block.
|
|
333
|
+
|
|
162
334
|
---
|
|
163
335
|
|
|
164
336
|
## 4. Picking a `trust_level`
|
|
@@ -194,8 +366,12 @@ redeploying.
|
|
|
194
366
|
## 5. Sync surfaces
|
|
195
367
|
|
|
196
368
|
These run inside the **public Next.js process** (the site visitor's
|
|
197
|
-
request thread). They
|
|
198
|
-
network
|
|
369
|
+
request thread) and execute synchronously. They aren't designed to
|
|
370
|
+
do network I/O: there's no async result path, so an `await fetch(...)`
|
|
371
|
+
inside `publicHead` would block SSR with no deadline. Side effects
|
|
372
|
+
that need network calls go in `hooks` (trusted Lambdas, async).
|
|
373
|
+
Public-process plugin code runs with the public-page IAM role — no
|
|
374
|
+
elevated AWS access.
|
|
199
375
|
|
|
200
376
|
| Surface | Returns | Use case |
|
|
201
377
|
|---|---|---|
|
|
@@ -222,9 +398,14 @@ The `ctx` object carries:
|
|
|
222
398
|
## 6. Descriptor reference
|
|
223
399
|
|
|
224
400
|
`publicHead` and `publicBodyEnd` return **descriptor objects**, not
|
|
225
|
-
`ReactNode`. The runtime validates them
|
|
226
|
-
|
|
227
|
-
|
|
401
|
+
`ReactNode`. The runtime validates them (URL scheme denylist, attrs
|
|
402
|
+
allowlist, dedup by id) and then builds the React elements itself.
|
|
403
|
+
This is the safety boundary for the **HTML output** a plugin can
|
|
404
|
+
contribute — it bounds what the runtime emits, not what the plugin's
|
|
405
|
+
own code does. Plugins run as ordinary TypeScript in the same Node
|
|
406
|
+
process as the rest of the site; the descriptor pipeline keeps the
|
|
407
|
+
public-page surface narrow and auditable without depending on a JS
|
|
408
|
+
sandbox.
|
|
228
409
|
|
|
229
410
|
### Common variants
|
|
230
411
|
|
|
@@ -393,6 +574,49 @@ and renders the returned descriptors. The runtime inserts a
|
|
|
393
574
|
`<script type="application/ld+json">` element with the auto-escaped
|
|
394
575
|
body into the page.
|
|
395
576
|
|
|
577
|
+
### Client-side DOM mutation: don't
|
|
578
|
+
|
|
579
|
+
Inline scripts you return from `publicHead` or `publicBodyEnd` execute
|
|
580
|
+
during HTML parsing, before React hydrates the page. **They must not
|
|
581
|
+
mutate visible DOM inside a React-managed subtree** — when hydration
|
|
582
|
+
runs, React sees a tree that doesn't match its virtual DOM, throws a
|
|
583
|
+
`Hydration failed because the server rendered HTML didn't match the
|
|
584
|
+
client` error, and regenerates the subtree from scratch. Your inserted
|
|
585
|
+
nodes get wiped.
|
|
586
|
+
|
|
587
|
+
React 19 additionally refuses to execute `<script>` tags it
|
|
588
|
+
encounters while rendering a client component, so a script that did
|
|
589
|
+
something like `document.body.append(myNewElement)` may not even fire.
|
|
590
|
+
|
|
591
|
+
**Safe patterns**:
|
|
592
|
+
|
|
593
|
+
- **Global state / non-DOM side effects**: push to `window.dataLayer`,
|
|
594
|
+
set a config object, instantiate an analytics SDK. This is what
|
|
595
|
+
`@ampless/plugin-analytics-ga4`, `@ampless/plugin-gtm`, and
|
|
596
|
+
`@ampless/plugin-plausible` do.
|
|
597
|
+
- **External widget loaders**: load a third-party script that manages
|
|
598
|
+
its own isolated container (Crisp, Intercom, Drift). The widget's
|
|
599
|
+
shadow DOM / fixed-position overlay lives outside React's tree and
|
|
600
|
+
doesn't conflict with hydration.
|
|
601
|
+
- **SSR-only descriptors**: return `meta` / `link` / `noscript` (and,
|
|
602
|
+
on `publicBodyEnd`, `iframe`) — the runtime renders these
|
|
603
|
+
server-side and they're part of React's virtual DOM from the start.
|
|
604
|
+
|
|
605
|
+
**Patterns to avoid**:
|
|
606
|
+
|
|
607
|
+
- `document.createElement('div')` + `document.body.append(...)`
|
|
608
|
+
- Modifying classes / attributes / text content of elements rendered
|
|
609
|
+
by the theme
|
|
610
|
+
- Inserting per-post HTML by reading something like `#post-body`
|
|
611
|
+
client-side — there's no `publicHead`-for-post analogue today, and
|
|
612
|
+
any client-side rewrite of a server-rendered subtree races against
|
|
613
|
+
hydration
|
|
614
|
+
|
|
615
|
+
For visible per-post output, return JSON-LD via `publicBodyForPost`
|
|
616
|
+
(see §6's `PublicPostBodyDescriptor`) or stick with the theme's own
|
|
617
|
+
templates. A future ampless capability for server-side per-post HTML
|
|
618
|
+
injection is on the roadmap.
|
|
619
|
+
|
|
396
620
|
---
|
|
397
621
|
|
|
398
622
|
## 7. Async event hooks
|
|
@@ -757,7 +981,100 @@ Worked examples to crib from:
|
|
|
757
981
|
|
|
758
982
|
---
|
|
759
983
|
|
|
760
|
-
## 14.
|
|
984
|
+
## 14. Quickstart: scaffolding with `create-ampless`
|
|
985
|
+
|
|
986
|
+
For a fast path from idea to working plugin, the `create-ampless`
|
|
987
|
+
CLI ships a `plugin <name>` subcommand:
|
|
988
|
+
|
|
989
|
+
```bash
|
|
990
|
+
# Site-local: scaffolds plugins/<name>/index.ts inside the current
|
|
991
|
+
# ampless site. Run from the site repo root.
|
|
992
|
+
npx create-ampless@latest plugin my-thing \
|
|
993
|
+
--trust-level untrusted \
|
|
994
|
+
--capabilities publicHead,adminSettings
|
|
995
|
+
|
|
996
|
+
# Standalone npm package: scaffolds ./<dir>/ with package.json,
|
|
997
|
+
# tsconfig.json, tsup.config.ts, README + .ja, CHANGELOG, .gitignore,
|
|
998
|
+
# and src/index.ts + src/index.test.ts. Run from wherever you want
|
|
999
|
+
# the new package directory to land.
|
|
1000
|
+
npx create-ampless@latest plugin @myscope/ampless-plugin-thing \
|
|
1001
|
+
--standalone \
|
|
1002
|
+
--trust-level untrusted \
|
|
1003
|
+
--capabilities publicHead,adminSettings \
|
|
1004
|
+
--description "What this plugin does"
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
Standalone scaffolds include everything Phase 5's cross-check needs:
|
|
1008
|
+
`package.json#amplessPlugin`, the `./package.json` subpath export,
|
|
1009
|
+
the `packageName` factory field, the `ampless-plugin` discovery
|
|
1010
|
+
keyword, and a minimal vitest sample so `pnpm install && pnpm test &&
|
|
1011
|
+
pnpm build` runs clean on the freshly generated directory.
|
|
1012
|
+
|
|
1013
|
+
Both modes also accept a positional flag-less invocation (`npx
|
|
1014
|
+
create-ampless@latest plugin`) that walks you through the same
|
|
1015
|
+
questions interactively via the @clack prompt UI.
|
|
1016
|
+
|
|
1017
|
+
### Publishing a standalone plugin
|
|
1018
|
+
|
|
1019
|
+
```bash
|
|
1020
|
+
cd ampless-plugin-thing
|
|
1021
|
+
pnpm install
|
|
1022
|
+
pnpm test
|
|
1023
|
+
pnpm build
|
|
1024
|
+
pnpm publish --access public --tag alpha
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
`--access public` is mandatory for scoped names (`@scope/...`).
|
|
1028
|
+
`--tag alpha` matches the current ampless pre-release cadence — drop
|
|
1029
|
+
it once the package reaches a stable major.
|
|
1030
|
+
|
|
1031
|
+
There's a publish-to-install lag of "seconds to minutes" before
|
|
1032
|
+
`npm install <pkg>@alpha` picks up a fresh publish (CDN + registry
|
|
1033
|
+
replica propagation). If `npm install` 404s right after `npm publish`
|
|
1034
|
+
returns, wait 1-2 minutes and retry — `npm view <pkg>@alpha version`
|
|
1035
|
+
visible in the registry is necessary but sometimes not sufficient.
|
|
1036
|
+
|
|
1037
|
+
### Naming the package
|
|
1038
|
+
|
|
1039
|
+
By npm convention, scope and the conventional `ampless-plugin-`
|
|
1040
|
+
prefix collapse to a short identifier for `AmplessPlugin.name`:
|
|
1041
|
+
|
|
1042
|
+
| npm package | `AmplessPlugin.name` |
|
|
1043
|
+
|---|---|
|
|
1044
|
+
| `@ampless/plugin-gtm` | `gtm` |
|
|
1045
|
+
| `@scope/ampless-plugin-clarity` | `clarity` |
|
|
1046
|
+
| `ampless-plugin-readme-toc` | `readme-toc` |
|
|
1047
|
+
| `weird-name-no-prefix` | `weird-name-no-prefix` |
|
|
1048
|
+
|
|
1049
|
+
The scaffold does this stripping automatically. Hand-author the same
|
|
1050
|
+
mapping if you skip the scaffold; the install-time cross-check warns
|
|
1051
|
+
on a mismatch between the package's static manifest and the factory.
|
|
1052
|
+
|
|
1053
|
+
### Site-local follow-up
|
|
1054
|
+
|
|
1055
|
+
After a site-local scaffold:
|
|
1056
|
+
|
|
1057
|
+
```ts
|
|
1058
|
+
// cms.config.ts
|
|
1059
|
+
import myThingPlugin from './plugins/my-thing'
|
|
1060
|
+
|
|
1061
|
+
export default defineConfig({
|
|
1062
|
+
// ...
|
|
1063
|
+
plugins: [
|
|
1064
|
+
myThingPlugin(),
|
|
1065
|
+
],
|
|
1066
|
+
})
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
The scaffold prints this snippet at the end of its run — copy it into
|
|
1070
|
+
`cms.config.ts` to activate the plugin.
|
|
1071
|
+
|
|
1072
|
+
`update-ampless` never touches the `plugins/` directory (it's
|
|
1073
|
+
PROTECTED), so the scaffolded code is safe across ampless upgrades.
|
|
1074
|
+
|
|
1075
|
+
---
|
|
1076
|
+
|
|
1077
|
+
## 15. Where to ask
|
|
761
1078
|
|
|
762
1079
|
- Architecture / design questions → [`docs/architecture/08-plugin-architecture.md`](https://github.com/heavymoons/ampless/blob/main/docs/architecture/08-plugin-architecture.md)
|
|
763
1080
|
- Bugs in a first-party plugin → file an issue against
|
|
@@ -25,19 +25,19 @@
|
|
|
25
25
|
"@tiptap/pm": "^3.23.6",
|
|
26
26
|
"@tiptap/react": "^3.23.6",
|
|
27
27
|
"@tiptap/starter-kit": "^3.23.6",
|
|
28
|
-
"@ampless/plugin-analytics-ga4": "^0.2.0-alpha.
|
|
29
|
-
"@ampless/plugin-gtm": "^0.1.1-alpha.
|
|
30
|
-
"@ampless/plugin-og-image": "^0.2.0-alpha.
|
|
31
|
-
"@ampless/plugin-plausible": "^0.1.1-alpha.
|
|
32
|
-
"@ampless/plugin-rss": "^0.2.0-alpha.
|
|
33
|
-
"@ampless/plugin-schema-jsonld": "^0.1.1-alpha.
|
|
34
|
-
"@ampless/plugin-seo": "^0.2.0-alpha.
|
|
35
|
-
"@ampless/plugin-webhook": "^0.2.0-alpha.
|
|
36
|
-
"@ampless/admin": "^1.0.0-alpha.
|
|
37
|
-
"@ampless/backend": "^1.0.0-alpha.
|
|
38
|
-
"@ampless/runtime": "^1.0.0-alpha.
|
|
28
|
+
"@ampless/plugin-analytics-ga4": "^0.2.0-alpha.6",
|
|
29
|
+
"@ampless/plugin-gtm": "^0.1.1-alpha.5",
|
|
30
|
+
"@ampless/plugin-og-image": "^0.2.0-alpha.23",
|
|
31
|
+
"@ampless/plugin-plausible": "^0.1.1-alpha.5",
|
|
32
|
+
"@ampless/plugin-rss": "^0.2.0-alpha.23",
|
|
33
|
+
"@ampless/plugin-schema-jsonld": "^0.1.1-alpha.2",
|
|
34
|
+
"@ampless/plugin-seo": "^0.2.0-alpha.23",
|
|
35
|
+
"@ampless/plugin-webhook": "^0.2.0-alpha.23",
|
|
36
|
+
"@ampless/admin": "^1.0.0-alpha.50",
|
|
37
|
+
"@ampless/backend": "^1.0.0-alpha.40",
|
|
38
|
+
"@ampless/runtime": "^1.0.0-alpha.29",
|
|
39
39
|
"@digital-go-jp/tailwind-theme-plugin": "^0.3.4",
|
|
40
|
-
"ampless": "^1.0.0-alpha.
|
|
40
|
+
"ampless": "^1.0.0-alpha.23",
|
|
41
41
|
"aws-amplify": "^6.17.0",
|
|
42
42
|
"class-variance-authority": "^0.7.1",
|
|
43
43
|
"clsx": "^2.1.1",
|
|
@@ -10,14 +10,38 @@
|
|
|
10
10
|
|
|
11
11
|
## いつ使うか
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
詳しい判断軸は [プラグイン作者ガイド](../docs/plugin-author-guide.ja.md)
|
|
14
|
+
§0「テーマとプラグインの境界線」に書いてある。プラグインが初めて、または
|
|
15
|
+
「これはテーマ側に書くべきか、プラグインか」で迷ったら先にそちらを読む。
|
|
16
|
+
ざっくり言うと:
|
|
17
|
+
|
|
18
|
+
- **テーマ** = ページの見た目 (レイアウト / コンポーネント / CSS)
|
|
19
|
+
- **プラグイン** = admin で編集する設定、バックグラウンド処理、
|
|
20
|
+
テーマを横断する注入、機械可読メタデータ、複数サイトで共有したい機能
|
|
21
|
+
|
|
22
|
+
その中でも *ローカル* プラグイン (このディレクトリ) を使うのは、サイト
|
|
23
|
+
固有の機能で:
|
|
24
|
+
|
|
25
|
+
- プラグイン surface (`publicHead` / `publicBodyEnd` / `metadata` /
|
|
26
|
+
`eventHooks` 等) が必要
|
|
27
|
+
- このサイト限定の用途 (一度きりのフッタークレジット、サイト特有の
|
|
28
|
+
JSON-LD 拡張、まだ汎用化したくない analytics スニペット等)
|
|
17
29
|
- 別パッケージとして version 上げて publish するのは大袈裟
|
|
18
30
|
|
|
19
|
-
複数サイトで使いたくなったら、独立 npm
|
|
20
|
-
`npx create-ampless plugin --standalone`
|
|
31
|
+
複数サイトで使いたくなったら、独立 npm パッケージに切り出す:
|
|
32
|
+
`npx create-ampless@latest plugin <name> --standalone` で publish 用の
|
|
33
|
+
ディレクトリ一式が scaffold される。
|
|
34
|
+
|
|
35
|
+
## クライアントサイドスクリプトに関する注意
|
|
36
|
+
|
|
37
|
+
`publicHead` / `publicBodyEnd` で `inlineScript` を返す場合、その body の
|
|
38
|
+
中で **可視 DOM 要素を挿入してはいけない**。React の hydration は DOM が
|
|
39
|
+
virtual DOM と一致していることを前提にしていて、外部からの DOM 操作は
|
|
40
|
+
reconciliation で消されてコンソールに hydration error が出る。
|
|
41
|
+
安全なパターンは `window.dataLayer` 等のグローバル状態操作、自分で隔離
|
|
42
|
+
コンテナを持つ外部ウィジェットローダ、そして SSR 専用 descriptor (`meta` /
|
|
43
|
+
`link` / `noscript` / `iframe`)。詳細は [作者ガイド §6](../docs/plugin-author-guide.ja.md)
|
|
44
|
+
「クライアント側 DOM 変更は禁止」セクションを参照。
|
|
21
45
|
|
|
22
46
|
## 最小例
|
|
23
47
|
|
|
@@ -10,15 +10,41 @@ add here is yours to keep.
|
|
|
10
10
|
|
|
11
11
|
## When to use a local plugin
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
The longer answer lives in §0 ("Theme vs Plugin Boundary") of the
|
|
14
|
+
[plugin author guide](../docs/plugin-author-guide.md) — read that
|
|
15
|
+
first if you're new to plugins, or if you're unsure whether a feature
|
|
16
|
+
belongs in a plugin or a theme. Short version:
|
|
17
|
+
|
|
18
|
+
- **Theme** = what the page looks like (layout, components, CSS).
|
|
19
|
+
- **Plugin** = admin-editable config, background processing,
|
|
20
|
+
theme-agnostic injection, machine-readable metadata, or anything
|
|
21
|
+
you want to share between sites.
|
|
22
|
+
|
|
23
|
+
Reach for a *local* plugin (this directory) when you want a feature
|
|
24
|
+
that:
|
|
25
|
+
|
|
26
|
+
- needs a plugin surface (`publicHead` / `publicBodyEnd` /
|
|
27
|
+
`metadata` / `eventHooks` / etc.)
|
|
28
|
+
- is specific to this site (a one-off footer credit, a custom JSON-LD
|
|
29
|
+
enrichment, an analytics snippet you're not yet ready to ship as a
|
|
30
|
+
reusable package)
|
|
17
31
|
- you'd rather not version-bump or republish to iterate on
|
|
18
32
|
|
|
19
33
|
When the plugin grows useful for more than one site, lift it into its
|
|
20
|
-
own npm package
|
|
21
|
-
|
|
34
|
+
own npm package — `npx create-ampless@latest plugin <name> --standalone`
|
|
35
|
+
scaffolds the directory ready for `npm publish`.
|
|
36
|
+
|
|
37
|
+
## A note on client-side scripts
|
|
38
|
+
|
|
39
|
+
If your `publicHead` / `publicBodyEnd` returns an `inlineScript`, the
|
|
40
|
+
script body **must not insert visible DOM elements** into the page.
|
|
41
|
+
React's hydration assumes the DOM matches its virtual DOM, and any
|
|
42
|
+
foreign nodes get wiped during reconciliation (plus a console error).
|
|
43
|
+
Safe patterns are `window.dataLayer`-style global state, external
|
|
44
|
+
widget loaders that manage their own isolated container, and
|
|
45
|
+
SSR-only descriptors (`meta` / `link` / `noscript` / `iframe`).
|
|
46
|
+
See the [author guide §6](../docs/plugin-author-guide.md) "Client-side
|
|
47
|
+
DOM mutation: don't" section for the full story.
|
|
22
48
|
|
|
23
49
|
## Minimal example
|
|
24
50
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# {{nameKebab}}
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
Site-local plugin in `plugins/{{nameKebab}}/`. This directory is
|
|
6
|
+
user-owned: `update-ampless` never touches it.
|
|
7
|
+
|
|
8
|
+
## Register
|
|
9
|
+
|
|
10
|
+
Add to `cms.config.ts`:
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import {{nameCamelCase}}Plugin from './plugins/{{nameKebab}}'
|
|
14
|
+
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
// ...
|
|
17
|
+
plugins: [
|
|
18
|
+
{{nameCamelCase}}Plugin(),
|
|
19
|
+
],
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Restart `next dev` and visit `/admin/plugins` to configure.
|
|
24
|
+
|
|
25
|
+
## Settings
|
|
26
|
+
|
|
27
|
+
(none yet — declare via `settings.public` in `index.ts`)
|
|
28
|
+
|
|
29
|
+
## Notes
|
|
30
|
+
|
|
31
|
+
- Trust level: `{{trustLevel}}`
|
|
32
|
+
- Capabilities: `{{capabilitiesList}}`
|
|
33
|
+
- When this plugin grows useful for more than one site, lift it into
|
|
34
|
+
its own npm package via `npx create-ampless plugin <name> --standalone`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { definePlugin, type AmplessPlugin } from 'ampless'
|
|
2
|
+
|
|
3
|
+
export interface {{NameCamelCase}}Options {
|
|
4
|
+
/** Optional namespace when registering multiple instances. */
|
|
5
|
+
instanceId?: string
|
|
6
|
+
// TODO: add the constructor options your plugin needs.
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* {{description}}
|
|
11
|
+
*
|
|
12
|
+
* Site-local plugin. Add to your `cms.config.ts`:
|
|
13
|
+
*
|
|
14
|
+
* import {{nameCamelCase}}Plugin from './plugins/{{nameKebab}}'
|
|
15
|
+
*
|
|
16
|
+
* export default defineConfig({
|
|
17
|
+
* // ...
|
|
18
|
+
* plugins: [
|
|
19
|
+
* {{nameCamelCase}}Plugin(),
|
|
20
|
+
* ],
|
|
21
|
+
* })
|
|
22
|
+
*/
|
|
23
|
+
export default function {{nameCamelCase}}Plugin(
|
|
24
|
+
options: {{NameCamelCase}}Options = {}
|
|
25
|
+
): AmplessPlugin {
|
|
26
|
+
const instanceId = options.instanceId ?? '{{nameKebab}}'
|
|
27
|
+
return definePlugin({
|
|
28
|
+
name: '{{nameKebab}}',
|
|
29
|
+
instanceId,
|
|
30
|
+
apiVersion: 1,
|
|
31
|
+
trust_level: '{{trustLevel}}',
|
|
32
|
+
displayName: { en: '{{DisplayName}}', ja: '{{displayNameJa}}' },
|
|
33
|
+
capabilities: [{{capabilitiesList}}],
|
|
34
|
+
// TODO: implement the surfaces declared in `capabilities`. The
|
|
35
|
+
// most common starting point is `publicHead(ctx)` returning a
|
|
36
|
+
// descriptor array. See the plugin author guide:
|
|
37
|
+
// https://github.com/heavymoons/ampless/blob/main/packages/ampless/docs/plugin-author-guide.md
|
|
38
|
+
})
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# {{packageName}}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
> English: [README.md](./README.md)
|
|
2
|
+
|
|
3
|
+
# {{packageName}}
|
|
4
|
+
|
|
5
|
+
> [Pre-release / alpha] {{description}}
|
|
6
|
+
|
|
7
|
+
## インストール
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install {{packageName}}@alpha
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 設定
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// cms.config.ts
|
|
17
|
+
import { defineConfig } from 'ampless'
|
|
18
|
+
import {{nameCamelCase}}Plugin from '{{packageName}}'
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
// ...
|
|
22
|
+
plugins: [
|
|
23
|
+
{{nameCamelCase}}Plugin(),
|
|
24
|
+
],
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`/admin/plugins` で設定。
|
|
29
|
+
|
|
30
|
+
## Trust level
|
|
31
|
+
|
|
32
|
+
`{{trustLevel}}`。Capabilities: `{{capabilitiesList}}`。
|
|
33
|
+
|
|
34
|
+
## Publish
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm install
|
|
38
|
+
pnpm test
|
|
39
|
+
pnpm build
|
|
40
|
+
pnpm publish --access public --tag alpha
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
API 詳細は [ampless plugin author guide](https://github.com/heavymoons/ampless/blob/main/packages/ampless/docs/plugin-author-guide.md) を参照。
|