diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md index d2dc4a1..b441007 100644 --- a/CONTRIBUTE.md +++ b/CONTRIBUTE.md @@ -31,6 +31,56 @@ This is shorthand for `docker compose -f docker-compose.dev.yml -f docker-compos Skipping this step is the most common cause of "my change isn't showing up." +## JS/CSS: no separate npm package + +This package's JS (Stimulus controllers) and CSS ship as plain source files under `resources/js/` and `resources/css/`, read directly by a consumer app's own Vite build — there is no separate `@boboko/core` npm package, and no `npm install`/`file:` dependency step of any kind. + +The reason: Composer already gives every environment one single, unconditional path — `vendor/boboko/core` — whether that resolves to a real symlink into `../boboko-core` (local path repo) or a real installed copy (tagged VCS release). A consumer's `vite.config.js` and JS entry point just read straight from that path, so there is nothing to toggle on the JS side — whatever Composer resolved is exactly what Vite sees, automatically, in both dev and prod. + +**Stable entry point.** A consumer imports from [resources/js/index.js](resources/js/index.js) only — never from a path reaching into a specific module's internals (e.g. `resources/js/checkout/index.js` directly). That barrel file re-exports whatever a consumer needs (currently just `registerCheckout`), so this package's internal file layout can change without breaking every consumer's own entry point: + +```js +// consumer app's resources/js/app.js +import { registerCheckout } from "../../vendor/boboko/core/resources/js/index.js"; +registerCheckout(application); +``` + +```php +{{-- consumer app's layout --}} +@vite(['vendor/boboko/core/resources/css/checkout.css', 'resources/css/app.css', 'resources/js/app.js']) +``` + +**What a consumer's `vite.config.js` needs**, because `vendor/boboko/core` is a symlink in local path-repo dev (not a real directory): + +```js +export default defineConfig({ + server: { + watch: { + // vendor/boboko/core is a symlink into ../boboko-core in local + // path-repo dev. Vite/chokidar don't follow symlinks for watched + // files by default, so edits to core's source wouldn't otherwise + // trigger HMR. No-op against a real installed copy (tagged VCS + // release) in production — there's no symlink to follow, and + // production only ever runs a one-shot `npm run build`, which + // doesn't watch anything regardless. + followSymlinks: true, + }, + }, +}); +``` + +Bare imports inside this package's own JS (`leaflet`, `@hotwired/stimulus`) resolve against the *consumer's* `node_modules` — Node's normal upward `node_modules` resolution walks from `vendor/boboko/core/resources/js/...` up through `vendor/boboko/`, `vendor/`, to the consumer app's root, where `node_modules` lives. This works with zero extra config as long as `vendor/boboko/core` sits inside the consumer's own directory tree (true for both the symlink and the real-copy case) — a consuming app's `vite`-equivalent Docker service just needs the same bind mount PHP containers already get, landing at the same path: + +```yaml +# consumer app's docker-compose.core-dev.yml +services: + vite: + volumes: + - ../boboko-core:/app/vendor/boboko/core +``` + +(Match whatever the consumer's Vite container's working directory actually is — `/app` above, `/var/www/html` for the PHP containers in `boboko-test`'s convention.) + ## Verifying changes against a real database There is no automated test suite for this package — too much of Lunar's behavior (table prefixing, nested sets, translatable attributes, Filament panel filters) only breaks in combination, against real Postgres, in a way that's impractical to fake in isolation. Instead, verify changes directly against a consumer app's live database. The practical workflow used throughout this package's `MigrateImport` feature: diff --git a/docs/modules.md b/docs/modules.md index 7d05580..8a44d8d 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -122,6 +122,63 @@ Docker Compose merges `volumes:` lists additively across `-f` files, so the over --- +## Frontend Assets (JS/CSS) + +A module's JS (Stimulus controllers) and CSS ship as plain source files under `resources/js/` and `resources/css/` — **there is no separate npm package per module.** A module is never `npm install`ed; its frontend assets are read directly by the consuming app's own Vite build, straight out of `vendor/boboko/`. + +This mirrors the PHP story above exactly: Composer already gives every environment one single, unconditional path — `vendor/boboko/` — whether that resolves to a symlink into a sibling checkout (local path repo) or a real installed copy (tagged VCS release). A consumer's `vite.config.js` and JS entry point read from that same path, so there is nothing to toggle on the JS side — whatever Composer resolved is exactly what Vite sees, in both dev and prod, automatically. + +**Each module exposes one stable JS entry point** — `resources/js/index.js` — that re-exports whatever a consumer needs, e.g. `boboko-core`'s: + +```js +// boboko-core/resources/js/index.js +export { registerCheckout } from './checkout/index.js' +``` + +A consuming app imports from that one file only, never from a path reaching into a module's internal folder structure directly: + +```js +// consumer app's resources/js/app.js +import { registerCheckout } from "../../vendor/boboko/core/resources/js/index.js"; +registerCheckout(application); +``` + +```php +{{-- consumer app's layout --}} +@vite(['vendor/boboko/core/resources/css/checkout.css', 'resources/css/app.css', 'resources/js/app.js']) +``` + +This keeps a module's internal file layout free to change without breaking every consumer's entry point — the same reasoning as PSR-4 namespaces for PHP, just for JS imports. + +**A consuming app's `vite.config.js` needs one addition**, because `vendor/boboko/` is a symlink in local path-repo dev (not a real directory Vite would otherwise watch through): + +```js +export default defineConfig({ + server: { + watch: { + // vendor/boboko/ is a symlink into ../boboko- in + // local path-repo dev. Vite/chokidar don't follow symlinks for + // watched files by default, so edits to a module's source + // wouldn't otherwise trigger HMR. No-op against a real installed + // copy (tagged VCS release) in production. + followSymlinks: true, + }, + }, +}); +``` + +Bare imports inside a module's own JS (e.g. `leaflet`, `@hotwired/stimulus`) resolve against the **consumer's** `node_modules` via Node's normal upward resolution walk from `vendor/boboko//resources/js/...` — no extra config needed, as long as `vendor/boboko/` sits inside the consumer's own directory tree (true for both the symlink and real-copy case). The consumer's Vite Docker service (if any) needs the same bind mount the PHP containers already get, landing at the equivalent path relative to its own working directory: + +```yaml +# consumer app's docker-compose.core-dev.yml +services: + vite: + volumes: + - ../boboko-core:/app/vendor/boboko/core # match /app to the vite service's actual workdir +``` + +--- + ## Creating a New Module **1. Create the repository and `composer.json`:** diff --git a/package.json b/package.json new file mode 100644 index 0000000..d351574 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "@boboko/core", + "version": "0.23.0", + "private": true, + "type": "module", + "description": "Portable Stimulus controllers and styles for boboko-core's cart + checkout module. Installed as a real npm dependency (file:../boboko-core in dev, a tagged git install in prod) so a consuming app's `npm install` resolves this package's own dependencies (leaflet, @hotwired/stimulus) transitively, the same way `composer update boboko/*` does for PHP. See CONTRIBUTE.md's \"JS/CSS: a real npm package\" section.", + "exports": { + ".": "./resources/js/index.js", + "./checkout": "./resources/js/checkout/index.js", + "./checkout/*": "./resources/js/checkout/*", + "./css/*": "./resources/css/*", + "./vite-plugin": "./vite-plugin.js" + }, + "dependencies": { + "@hotwired/stimulus": "^3.2.2", + "leaflet": "^1.9.4" + }, + "peerDependencies": { + "vite": "^8.0.0" + } +} diff --git a/resources/js/checkout/index.js b/resources/js/checkout/index.js index 77a9525..ac335c0 100644 --- a/resources/js/checkout/index.js +++ b/resources/js/checkout/index.js @@ -13,6 +13,7 @@ import BbkPaymentController from './bbk-payment-controller' // When this module moves to boboko-core this file ships with it unchanged; // only that one import line in the host entry point differs per project. export function registerCheckout(application) { + console.log('[@boboko/core] checkout module loaded from', import.meta.url, '- test 2') application.register('bbk-add-to-cart', BbkAddToCartController) application.register('bbk-box-now-locker', BbkBoxNowLockerController) application.register('bbk-cart', BbkCartController) diff --git a/resources/js/index.js b/resources/js/index.js new file mode 100644 index 0000000..90e03b3 --- /dev/null +++ b/resources/js/index.js @@ -0,0 +1,9 @@ +// Single stable JS entry point for this package. A consuming app imports +// from here (vendor/boboko/core/resources/js/index.js), never from a path +// reaching into a specific module's internals — so this file's exports can +// grow or its modules' internal layout can change without breaking every +// consumer's own entry point. +// +// stoic_embed.js is not re-exported here: per its own docblock, it's a +// standalone vendored script meant to be included directly, not imported. +export { registerCheckout } from './checkout/index.js' diff --git a/src/Providers/CheckoutModuleServiceProvider.php b/src/Providers/CheckoutModuleServiceProvider.php index 8a0a539..df3a288 100644 --- a/src/Providers/CheckoutModuleServiceProvider.php +++ b/src/Providers/CheckoutModuleServiceProvider.php @@ -18,13 +18,16 @@ use Modules\Core\Cart\Services\CartService; * CheckoutTranslationsSeeder rather than shipped as lang/ files. * * A consuming app wires this module in with: - * 1. `php artisan vendor:publish --tag=core-checkout-assets` — copies - * resources/js/checkout/** and resources/css/checkout.css into the - * host's own resources/ tree. Vite only ever bundles from a host's - * own resources/ directory, so these are published (an explicit, - * host-owned, re-publishable copy) rather than imported cross-package. - * 2. `import { registerCheckout } from './checkout'` in the host's own - * JS entry point, and a @vite entry for the published checkout.css. + * 1. `"@boboko/core": "file:../boboko-core"` as an npm dependency (see this + * package's own package.json `exports`), with a bind-mount of the core + * checkout into the host's Vite container so the `file:` symlink + * resolves in dev (see 3dealer's docker-compose.core-dev.yml) and + * `resolve.preserveSymlinks: true` in the host's vite.config.js so bare + * imports (stimulus, leaflet) still resolve against the host's own + * node_modules through that symlink. + * 2. `import { registerCheckout } from '@boboko/core/checkout'` in the + * host's own JS entry point, and a @vite entry for + * `node_modules/@boboko/core/resources/css/checkout.css`. * 3. `@include('checkout::drawer')` in the host's own layout. * See config/checkout.php for the handful of per-site settings (login * route, single-country mode, ...) a host is expected to publish and diff --git a/vite-plugin.js b/vite-plugin.js new file mode 100644 index 0000000..41d7288 --- /dev/null +++ b/vite-plugin.js @@ -0,0 +1,59 @@ +// Vite integration for @boboko/core, mirroring how CoreServiceProvider owns +// and ships its own PHP wiring instead of making every consumer hand-copy +// it. A consuming app's vite.config.js just does: +// +// import { boboko } from '@boboko/core/vite-plugin' +// export default defineConfig({ plugins: [..., boboko()] }) +// +// All of the settings below exist only because @boboko/core is typically +// installed as a local `file:../boboko-core` path dependency in dev +// (symlinked into node_modules by npm) rather than a real installed copy — +// see this package's own CONTRIBUTE.md. +export function boboko() { + return { + name: 'boboko-core', + config() { + return { + optimizeDeps: { + // @boboko/core is a live local dependency in dev, not a + // stable third-party lib. Vite's dependency pre-bundler + // otherwise caches it once under node_modules/.vite/deps + // and never re-scans it on a plain source edit, silently + // serving a stale bundle. Excluding it makes Vite treat + // it like first-party source: always transformed live. + exclude: ['@boboko/core'], + // Excluding @boboko/core above means its own dependencies + // (leaflet, @hotwired/stimulus) are no longer discovered + // by Vite's dependency scanner, since that scanner only + // crawls from already-optimized entry points. Without + // this, leaflet is served straight from its raw UMD + // source instead of the pre-bundled ESM shim, and + // `import L from 'leaflet'` fails with "does not provide + // an export named 'default'". Forces pre-bundling + // regardless of how they're reached in the import graph. + include: ['leaflet', '@hotwired/stimulus'], + }, + resolve: { + // The local `file:../boboko-core` form installs as a + // symlink, same as npm always does for a local `file:` + // target. Without this, Vite resolves the symlink's bare + // imports relative to its real path outside the + // consumer's own root, where there's no node_modules, + // instead of from the symlink's location in the + // consumer's own node_modules. Harmless no-op against a + // real installed copy (tagged VCS release). + preserveSymlinks: true, + }, + server: { + watch: { + // Same symlink as above: Vite/chokidar don't follow + // symlinks for watched files by default, so edits to + // core's source wouldn't otherwise trigger HMR. + // No-op against a real installed copy. + followSymlinks: true, + }, + }, + } + }, + } +}