encoredigitalgroup/wirescript
Composer 安装命令:
composer require encoredigitalgroup/wirescript
包简介
Generate TypeScript $wire interface files from PHP classes annotated with #[WireExposed].
README 文档
README
Generate TypeScript $wire interface files from PHP classes annotated with #[WireExposed].
WireScript generates the entire mechanical frontend surface a Livewire/Alpine module needs, so a developer hand-writes
only component behavior. PHP is the single source of truth: annotate the public properties and methods the frontend
calls, run the generator, and commit the emitted files. A --check mode fails CI when any committed file drifts from
what the generator would produce.
From each registered module, wirescript:generate emits — all carrying a @generated header and all byte-stable:
| Artifact | Purpose |
|---|---|
types/wire/<Name>.ts | One export interface per annotated class (the $wire public surface). |
types/wire/index.ts | The WireRegistry, the canonical Livewire WireBase, and Wire<T>. |
register.ts | alpine.data() wiring for every factory under components/. |
index.ts | The alpine:init entry point that calls the generated register function. |
lib/persisted.ts | Typed Alpine.$persist wrapper with the module's storage-key prefix. |
lib/keymap.ts | Generic typed keyboard-shortcut dispatcher. |
Component factories themselves are never generated or overwritten — only scaffolded on demand (see Scaffolding components). The only manual step that remains is registering the module's interface group from its service provider.
- Package:
encoredigitalgroup/wirescript - Namespace:
EncoreDigitalGroup\WireScript - Requires: PHP 8.4+, Laravel 12/13
Installation
composer require encoredigitalgroup/wirescript
The service provider (EncoreDigitalGroup\WireScript\Providers\WireScriptServiceProvider) is auto-discovered. It binds
the WireTypeRegistry singleton and registers the wirescript:generate and wirescript:scaffold console commands.
WireScript derives every output path from one registered directory. A module is expected to follow the conventional
layout (resources/js/types/wire, resources/js/components, resources/js/lib); the module JS root is the directory
two levels above the wire-types directory.
Usage
1. Annotate the PHP class
Mark each public property or method the frontend reads or calls with #[WireExposed]:
use EncoreDigitalGroup\WireScript\WireExposed;
class BacklogList extends Component
{
#[WireExposed(type: 'number[]')]
public array $selectedTicketIds = [];
#[WireExposed]
public function clearFilters(): void { /* ... */ }
#[WireExposed(params: ['ticketIds' => 'unknown[]'])]
public function bulkAssign(array $ticketIds): void { /* ... */ }
}
Override arguments handle shapes reflection cannot infer:
| Argument | Target | Effect |
|---|---|---|
type | property | Explicit TS type, overrides reflection. |
returns | method | Explicit TS return type, without the Promise<> wrapper. |
params | method | Per-parameter TS overrides, keyed by parameter name. |
Reflection defaults: int/float → number, bool → boolean, string → string, array → unknown[],
?T → T | null, Collection → unknown[], Carbon → string, models/DTOs → Record<string, unknown>,
void return → Promise<void>.
2. Register the interfaces
Each consuming package registers a group from its own service provider's boot(). This co-locates the registry with
the package that owns the classes:
use EncoreDigitalGroup\WireScript\WireTypeRegistry;
public function boot(): void
{
app(WireTypeRegistry::class)->register(
outputDir: __DIR__ . '/../../resources/js/types/wire',
interfaces: [
'BacklogList' => BacklogList::class,
'SprintPanel' => SprintPanel::class,
],
);
}
WireTypeRegistry is a singleton, so register() calls accumulate across every booting provider. The command iterates
every registered group, so a single wirescript:generate serves the whole application.
The optional moduleKey argument sets the module identity used for the storage-key prefix, the register-function name,
and the doc-header labels. When omitted it is derived from the path — the directory segment above resources (e.g.
Projects → prefix projects., function registerProjectsComponents). Pass it explicitly when the directory name does
not match the desired identity:
app(WireTypeRegistry::class)->register(
outputDir: __DIR__ . '/../../resources/js/types/wire',
interfaces: ['BacklogList' => BacklogList::class],
moduleKey: 'Projects',
);
3. Generate and verify
php artisan wirescript:generate # write every generated artifact
php artisan wirescript:generate --check # exit 1 on drift, write nothing (use in CI)
Every artifact in the table above is written with a @generated header. Commit the output. --check regenerates each
file in memory and compares it byte-for-byte against what is on disk — so adding a #[WireExposed] member, or
adding/removing a component file, makes --check fail until you regenerate. Component scaffolds hold developer behavior
and are deliberately excluded from the gate. Wire a test or CI step to the --check mode to catch drift:
expect(Artisan::call('wirescript:generate', ['--check' => true]))->toBe(0);
Scaffolding components
wirescript:scaffold writes a typed Alpine component factory under the module's components/ directory. It is
create-if-absent: it never overwrites an existing file and is not part of the --check gate. The generated half
(imports, $wire typing, the factory shell) keeps the boilerplate correct; you fill only the behavior body.
php artisan wirescript:scaffold backlogList --wire=BacklogList # typed this.$wire: Wire<'BacklogList'>
php artisan wirescript:scaffold backlogFilterPopover # no $wire (pure UI state)
Pass --group=<Module> only when more than one module is registered. After scaffolding, run wirescript:generate to
wire the new factory into register.ts.
Vite integration
The package ships a Vite plugin at resources/vite/index.js. Its default export discovers, for every module under
app_modules/ (override with {modulesDir}), the module frontend entry (resources/js/index.ts) and adds it as a
build input. Import it by its Composer vendor path:
import wirescript from './vendor/encoredigitalgroup/wirescript/resources/vite';
export default defineConfig({
plugins: [
// ...
...wirescript(),
],
});
Because discovery is convention-driven, a module ships frontend code just by adding resources/js/index.ts — it needs
no per-module Vite plugin. The generated $wire types (resources/js/types/wire/*.ts) are not registered as build
inputs: they are type-only export interface files, so the module's index.ts pulls them in via import type instead.
wirescriptInputs(moduleDir) is exported for building the input list for a single module explicitly.
Loading the bundles on the page
The Vite plugin only registers each module's index.ts as a build input — the compiled bundle lands in the manifest,
but a build step cannot place a <script> tag on a server-rendered page. WireScript closes that gap at runtime: it reads
every module registered with WireTypeRegistry, resolves each entry, and emits the tags through Laravel's Vite service
(the hot-file URL under npm run dev, the manifest URL in production). Two delivery surfaces, both wired by the service
provider:
- Filament — a
panels::scripts.beforerender hook is auto-registered when Filament is installed, so every panel page loads its modules with no host configuration. The hook fires before Filament boots Alpine, so each module's generatedalpine:initlistener attaches in time. Plain Livewire / Blade — add the
@wirescriptsdirective to your layout, before@livewireScripts:<head> {{-- ... --}} @wirescripts </head>
Both render the same tags. A module that registers $wire types but ships no resources/js/index.ts is skipped, and
nothing is emitted until the assets are built (a dev server is running, or public/build/manifest.json exists).
Public surface
| Symbol | Role |
|---|---|
EncoreDigitalGroup\WireScript\WireExposed | Attribute marking a property/method for TS interface generation. |
EncoreDigitalGroup\WireScript\WireTypeRegistry | Singleton collecting per-package interface groups. |
php artisan wirescript:generate | Generates (or, with --check, verifies) the full wiring surface. |
php artisan wirescript:scaffold | Creates a typed component factory stub (never overwrites). |
wirescript() / wirescriptInputs() (resources/vite) | Vite input discovery for module frontend entries. |
WireScriptManifest / @wirescripts directive | Runtime <script> injection of each module's built entry. |
The canonical templates for every generated artifact live in resources/stubs/ — editing the built-in Livewire
WireBase surface, or any boilerplate, is a single change there.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: BSD-3-Clause
- 更新时间: 2026-06-21