Repository navigation
test: cover modules with an admin and a blog module - #176
protibimbok wants to merge 4 commits into
Conversation
Output of `artisan modular:init`: WithModules(Modules) in bootstrap/app.go, an empty bootstrap/modules.go, config/modular.go, modules/.gitkeep and the go:generate modular:publish line in main.go. Adds facades.Modular() to app/facades.
modules/admin is a Vue admin that other modules fill, modelled on laramod-admin: - contracts.ProvidesDashboardScripts: a module lists Vite entries the admin page loads before its own main.ts; each must also be in ViteEntries(). - dashboard.Entries collects them in module order and fails on a script that is not a Vite entry. The page renders them with one Vite Tags call. - /admin and /admin/*page serve the page; a small client router, a module registry (registerModule with menu, routes, fills) and a slot/fill mechanism (createSlot: Provider, Outlet, Fill via Teleport) with two slots, DashboardWidgets and HeaderActions. modules/blog uses most capabilities (routes, API routes, config, migrations, views, translations, Vite entries) and implements ProvidesDashboardScripts: its dashboard.ts registers the Posts and New post pages, fills HeaderActions with a "New post" button and DashboardWidgets with a post count card. tsconfig.json type-checks module scripts and maps @admin/*.
ModularTestSuite: registry order and paths, capabilities, module config and translations, modular:list, the modules field of vite:inputs, the blog API and page against the database. AdminDashboardTestSuite: dashboard.Entries (order, skipped modules, a script missing from ViteEntries, no admin) and the rendered admin page from a fixture manifest.
hwbrzzl
left a comment
There was a problem hiding this comment.
Thanks. Could you simplify the examples? There are a lot of unnecessary files for an example.
| "goravel/bootstrap" | ||
| ) | ||
|
|
||
| //go:generate go run . artisan modular:publish |
There was a problem hiding this comment.
This feature exists for third party modules. If the module source does not live in the project dir then vite cannot work properly (building, figuring out the path). Similar logic applies for language files, views etc. We need an way to bring them into project dir.
go provides a generate command.
The go generate command is a built-in Go tool that automates running code-generation programs directly from instructions embedded in your Go source files. It is designed for package authors to reduce repetitive boilerplate code, and it must be run manually during development rather than automatically during go build
We are using it to make the integration easier.
People will be able to build individual parts like auth, blog, chat - publish and re-use them without requiring to change anything.
| "github.com/goravel/framework/contracts/modular" | ||
| ) | ||
|
|
||
| func Modular() modular.Registry { |
There was a problem hiding this comment.
Do you mean Module.Registry() ?
| } | ||
|
|
||
| // Order -1 makes the admin the last module, so its routes come after the others'. | ||
| func (m *Module) Order() int { |
There was a problem hiding this comment.
Why not respect the module slice order in the bootstrap/modules.go file? Then this function can be removed entirely.
There was a problem hiding this comment.
make:module inserts in the end, and some app may require this ordering i.e. put themselves in the end. users don't need to be aware of that order unless necessary. this is also optional, so no need to implement if not necessary
| // admin's own router decides which page is shown, so a refresh on | ||
| // /admin/blog/posts works. | ||
| func Web(router route.Router) { | ||
| base := "/" + strings.Trim(facades.Config().GetString("modules.admin.path", "admin"), "/") |
There was a problem hiding this comment.
How about
| base := "/" + strings.Trim(facades.Config().GetString("modules.admin.path", "admin"), "/") | |
| base := "/" + strings.Trim(facades.Config().Module(admin).GetString("path", "admin"), "/") |
There was a problem hiding this comment.
do you mean .Module("admin")?
If no, where does admin come from
| base := "/" + strings.Trim(facades.Config().GetString("modules.admin.path", "admin"), "/") | ||
| controller := controllers.NewDashboardController() | ||
|
|
||
| router.Get(base, controller.Show) |
There was a problem hiding this comment.
Can facades.Route be used here directly?
There was a problem hiding this comment.
yes, but not recommended as it may bypass some grouping behavior.
| routes.Web(router) | ||
| } | ||
|
|
||
| func (m *Module) ApiRoutes(router route.Router) { |
There was a problem hiding this comment.
This function can be merged with Routes.
There was a problem hiding this comment.
auth module may handle authentication differently in api & web routes.
different middlewares may be applied based on api & web.
how do we handle that?
Summary
The framework's modular feature (goravel/framework#1613, goravel/vite-plugin#4) gets a working example here: two modules in
modules/, wired the way an application would wire them, and feature tests that pin the behaviour.modules/adminis a small Vue admin that other modules extend. It shows how a module can define a capability of its own, which other modules implement, on top of the framework's.modules/bloguses most framework capabilities (routes, API routes, config, migrations, views, translations, Vite entries) and plugs into the admin.The admin is modelled on laramod-admin.
What's in it
Enabling modules
The unedited output of
artisan modular:init:WithModules(Modules)inbootstrap/app.go,bootstrap/modules.go,config/modular.goand thego:generate ... modular:publishline inmain.go. Plusfacades.Modular()inapp/facades.The admin module
The
DashboardScriptscapability. A module takes part in the admin by implementing:dashboard.Entries(registry)collects every module's scripts in module order, then the admin's ownresources/js/main.ts, so every module has registered before the admin mounts.ViteEntries(), because that is what gets it built. A missing one fails withdashboard script [x] of module [y] is not in its ViteEntries()./adminand/admin/*pagerender one page. Every entry goes through a singlefacades.Vite().Tags(...)call, so shared chunks are preloaded once. The path comes frommodules.admin.path.Order() -1, so its catch-all route comes after every other module's routes.Frontend (
modules/admin/resources/js, Vue):registerModule({ id, order, menu, routes, fills })createSlot(name)→Provider/Outlet/FillFillteleports its children into theOutletwhile both are mounted, in mount order.DashboardWidgets,HeaderActionsAdminLink,navigateThe blog module
GET /blog: a module view (blog/index.tmpl) with a module translation (blog/messages.title).GET|POST /api/blog/posts: lists the latest posts with a total, and creates posts with validation (422 with errors).blog_postsmigration is generated withmake:migration --module=blogand registered on the module struct.DashboardScripts()returnsresources/js/dashboard.ts, which registers:HeaderActions, shown only on the Posts page,DashboardWidgets.Tests
ModularTestSuite:modules.*config and module translations,modular:list,modulesfield ofvite:inputs,AdminDashboardTestSuite:dashboard.Entries, table-driven: module order, modules without scripts skipped, a script missing fromViteEntries(), no admin registered,/admin,/admin/blogand/admin/blog/newpages from a fixture manifest,VitePluginTestSuite:@,@blog,@admin) pinned,@blog/dashboard.tsresolves through the dev server.Dependencies / before merging
go.modstill pins a framework version without modules, so it needs a new pin once that branch is merged.goravel-vite-plugin0.1.x ignores module entries.npm cifails in CI until a release is pinned instead.modular:listdoesn't showDashboardScripts. It's the admin's own capability, not one the framework knows about.How to try it