Creating Scripts and Components¶
In MageObsidian Components, themes carry their own JavaScript and Vue components, under the theme directory:
Files under a module use view/frontend/web/ instead. Everything below works the same either way — the only difference is the namespace you refer to them by: Theme:: for the theme's own files, Vendor_Module:: for a module's.
Workflow¶
1. Scripts in web/js¶
A script is a module, not a bundle entry. It is loaded once, does its work and exports whatever the rest needs.
Load it from a template with the script() helper, which emits a <script type="module"> pointing at the built file:
Import it from other JS with the framework's specifier — never a relative path:
The specifier is what makes theme inheritance work: a child theme that ships web/js/greeting.ts transparently replaces the parent's for everyone who imports it. A relative path resolves to one specific file on disk and skips that entirely. A typo fails the build with suggestions rather than shipping broken.
2. Vue components in web/components¶
Components are single-file components using <script setup>. TypeScript is the default (lang="ts").
Mount it from a template as an island — a marker the runtime hydrates, not a page-wide app:
render_vue(name, props, eager, serverHtml, hydrate):
| Argument | Meaning |
|---|---|
name |
Theme::components/X or Vendor_Module::components/X |
props |
Passed to the component as its props |
eager |
true mounts immediately; the default waits until the island scrolls into view |
serverHtml |
Markup for the component's initial state, so the island has something to show before Vue arrives |
hydrate |
With serverHtml, adopt that markup instead of replacing it — no layout shift |
Generate serverHtml with the mage-obsidian:island-ssr bin (see Vue islands). An eager island that replaces its container shifts the page on mount; bin/magento mage-obsidian:frontend:doctor reports every one that does.
Templates: Twig or phtml¶
With the optional Twig module installed, templates are .twig and the helpers above are Twig functions. They are declared is_safe => html, so they never need |raw.
Without it, the same capabilities are ViewModel calls from .phtml:
New themes are expected to be written in Twig; .phtml remains supported for compatibility and for the rare block whose PHP the theme has to keep.
Guidelines¶
- Location. Scripts in
web/js, components inweb/components. Under a module, the same two directories insideview/frontend/web/. - Naming. kebab-case for
web/js, PascalCase for components. - Imports. Always
Theme::/Vendor_Module::. Relative paths across a module or theme boundary break inheritance. - Composition API only.
<script setup>; the Options API is not used anywhere in the shipped storefront. - Dynamic imports work with the same specifiers, and are how an island's code stays out of the critical path:
- Vite processes both directories — bundled, tree-shaken, and fingerprinted into
web/generated.
Adding to an existing page¶
A component that has to appear inside a block someone else owns does not need that block edited. Declare a child block in layout XML and render it where the parent template calls child_html:
If the parent template has no hook for it, an interceptor can wrap the JS instead, without editing the module that owns it.