turbo-mount 0.3.3 → 0.4.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/README.md +7 -2
- data/app/assets/javascripts/turbo-mount/svelte.js +3 -2
- data/app/assets/javascripts/turbo-mount/svelte.min.js +1 -1
- data/app/assets/javascripts/turbo-mount/svelte.min.js.map +1 -1
- data/app/assets/javascripts/turbo-mount/svelte4.js +15 -0
- data/app/assets/javascripts/turbo-mount/svelte4.min.js +2 -0
- data/app/assets/javascripts/turbo-mount/svelte4.min.js.map +1 -0
- data/app/assets/javascripts/turbo-mount.js +3 -5
- data/app/assets/javascripts/turbo-mount.min.js +1 -1
- data/app/assets/javascripts/turbo-mount.min.js.map +1 -1
- data/lib/generators/turbo_mount/frameworks.yml +23 -0
- data/lib/generators/turbo_mount/helpers.rb +61 -0
- data/lib/generators/turbo_mount/install/turbo-mount.js.tt +1 -1
- data/lib/generators/turbo_mount/install_generator.rb +33 -70
- data/lib/generators/turbo_mount/js_package_manager.rb +57 -0
- data/lib/turbo/mount/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1e390ef4fc7a3d329693a294437fa8f5d27c9157654b3a9b21621b26dd6c7c5
|
4
|
+
data.tar.gz: 591ea18f8b02da05da9a9d6dba5249eccdf01ec2b4d00477341df76166c0bd7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd306af35603685bebc4c2a7ef0fe31909fa56b521f0808562aeee3f8c90f2d5098aa846e6633356b716ced5aa51b8c5e6131252b97c5b91ccf14242aa90baff
|
7
|
+
data.tar.gz: 8a7221dad2c96c5c8c873e841a4007f1b45bcf6adada668af008692b035589590a230c411756f1cac86918d74521d0f6cec47d8c2125feddb1b5bfca91187ec3
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning].
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.4.0] - 2024-11-03
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- [BREAKING] Support Svelte 5. ([@skryukov])
|
15
|
+
`turbo-mount/svelte` is now the Svelte 5 plugin. The Svelte 4 plugin is now `turbo-mount/svelte4`.
|
16
|
+
|
10
17
|
## [0.3.3] - 2024-09-24
|
11
18
|
|
12
19
|
### Added
|
@@ -75,7 +82,8 @@ and this project adheres to [Semantic Versioning].
|
|
75
82
|
[@jkogara]: https://github.com/jkogara
|
76
83
|
[@skryukov]: https://github.com/skryukov
|
77
84
|
|
78
|
-
[Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.
|
85
|
+
[Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.4.0...HEAD
|
86
|
+
[0.4.0]: https://github.com/skryukov/turbo-mount/compare/v0.3.3...v0.4.0
|
79
87
|
[0.3.3]: https://github.com/skryukov/turbo-mount/compare/v0.3.2...v0.3.3
|
80
88
|
[0.3.2]: https://github.com/skryukov/turbo-mount/compare/v0.3.1...v0.3.2
|
81
89
|
[0.3.1]: https://github.com/skryukov/turbo-mount/compare/v0.3.0...v0.3.1
|
data/README.md
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
<p align="center">
|
2
|
+
<img src="./assets/logo.svg" title="Turbo Mount logo" width="220" height="45">
|
3
|
+
</p>
|
4
|
+
|
5
|
+
<h1 align="center">Turbo Mount</h1>
|
2
6
|
|
3
7
|
[](https://rubygems.org/gems/turbo-mount)
|
4
8
|
|
@@ -143,7 +147,8 @@ This will generate the following HTML:
|
|
143
147
|
|
144
148
|
- React: `"turbo-mount/react"`
|
145
149
|
- Vue: `"turbo-mount/vue"`
|
146
|
-
- Svelte: `"turbo-mount/
|
150
|
+
- Svelte 4: `"turbo-mount/svelte4"`
|
151
|
+
- Svelte 5: `"turbo-mount/svelte"`
|
147
152
|
|
148
153
|
To add support for other frameworks, create a custom plugin. See included plugins for examples.
|
149
154
|
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import { buildRegisterFunction } from 'turbo-mount';
|
2
2
|
export { TurboMount } from 'turbo-mount';
|
3
|
+
import { mount, unmount } from 'svelte';
|
3
4
|
|
4
5
|
const plugin = {
|
5
6
|
mountComponent: (mountProps) => {
|
6
7
|
const { el, Component, props } = mountProps;
|
7
|
-
const component =
|
8
|
+
const component = mount(Component, { target: el, props });
|
8
9
|
return () => {
|
9
|
-
component
|
10
|
+
unmount(component);
|
10
11
|
};
|
11
12
|
},
|
12
13
|
};
|
@@ -1,2 +1,2 @@
|
|
1
|
-
import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";const
|
1
|
+
import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";import{mount as t,unmount as r}from"svelte";const n={mountComponent:o=>{const{el:n,Component:e,props:m}=o,p=t(e,{target:n,props:m});return()=>{r(p)}}},e=o(n);export{n as default,e as registerComponent};
|
2
2
|
//# sourceMappingURL=svelte.min.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"svelte.min.js","sources":["../../src/plugins/svelte/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport {
|
1
|
+
{"version":3,"file":"svelte.min.js","sources":["../../src/plugins/svelte/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { Component, mount, unmount } from \"svelte\";\n\nconst plugin: Plugin<Component> = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n const component = mount(Component, { target: el, props });\n return () => {\n unmount(component);\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","component","mount","target","unmount","registerComponent","buildRegisterFunction"],"mappings":"oIAGA,MAAMA,EAA4B,CAChCC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAC3BI,EAAYC,EAAMH,EAAW,CAAEI,OAAQL,EAAIE,UACjD,MAAO,KACLI,EAAQH,EAAU,CACnB,GAICI,EAAoBC,EAAsBX"}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { buildRegisterFunction } from 'turbo-mount';
|
2
|
+
export { TurboMount } from 'turbo-mount';
|
3
|
+
|
4
|
+
const plugin = {
|
5
|
+
mountComponent: (mountProps) => {
|
6
|
+
const { el, Component, props } = mountProps;
|
7
|
+
const component = new Component({ target: el, props });
|
8
|
+
return () => {
|
9
|
+
component.$destroy();
|
10
|
+
};
|
11
|
+
},
|
12
|
+
};
|
13
|
+
const registerComponent = buildRegisterFunction(plugin);
|
14
|
+
|
15
|
+
export { plugin as default, registerComponent };
|
@@ -0,0 +1,2 @@
|
|
1
|
+
import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";const t={mountComponent:o=>{const{el:t,Component:r,props:n}=o,e=new r({target:t,props:n});return()=>{e.$destroy()}}},r=o(t);export{t as default,r as registerComponent};
|
2
|
+
//# sourceMappingURL=svelte4.min.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"svelte4.min.js","sources":["../../src/plugins/svelte4/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { ComponentType } from \"svelte\";\n\nconst plugin: Plugin<ComponentType> = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n\n const component = new Component({ target: el, props });\n\n return () => {\n component.$destroy();\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","component","target","$destroy","registerComponent","buildRegisterFunction"],"mappings":"wFAGA,MAAMA,EAAgC,CACpCC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAE3BI,EAAY,IAAIF,EAAU,CAAEG,OAAQJ,EAAIE,UAE9C,MAAO,KACLC,EAAUE,UAAU,CACrB,GAICC,EAAoBC,EAAsBV"}
|
@@ -64,10 +64,9 @@ class TurboMount {
|
|
64
64
|
this.application.turboMount = this;
|
65
65
|
this.application.register("turbo-mount", TurboMountController);
|
66
66
|
document.addEventListener("turbo:before-morph-element", (event) => {
|
67
|
-
var _a;
|
68
67
|
const turboMorphEvent = event;
|
69
68
|
const { target, detail } = turboMorphEvent;
|
70
|
-
if (
|
69
|
+
if (target.getAttribute("data-controller")?.includes("turbo-mount")) {
|
71
70
|
target.setAttribute("data-turbo-mount-props-value", detail.newElement.getAttribute("data-turbo-mount-props-value") ||
|
72
71
|
"{}");
|
73
72
|
event.preventDefault();
|
@@ -112,15 +111,14 @@ const identifierNames = (name) => {
|
|
112
111
|
return [`turbo-mount--${controllerName}`, `turbo-mount-${controllerName}`];
|
113
112
|
};
|
114
113
|
const registerComponentsBase = ({ plugin, turboMount, components, controllers, }) => {
|
115
|
-
|
116
|
-
const controllerModules = controllers !== null && controllers !== void 0 ? controllers : [];
|
114
|
+
const controllerModules = controllers ?? [];
|
117
115
|
for (const { module, filename } of components) {
|
118
116
|
const name = filename
|
119
117
|
.replace(/\.\w*$/, "")
|
120
118
|
.replace(/^[./]*components\//, "");
|
121
119
|
const identifiers = identifierNames(name);
|
122
120
|
const controller = controllerModules.find(({ identifier }) => identifiers.includes(identifier));
|
123
|
-
const component =
|
121
|
+
const component = module.default ?? module;
|
124
122
|
if (controller) {
|
125
123
|
turboMount.register(plugin, name, component, controller.controllerConstructor);
|
126
124
|
}
|
@@ -1,2 +1,2 @@
|
|
1
|
-
import{Controller as t,Application as o}from"@hotwired/stimulus";class n extends t{constructor(){super(...arguments),this.skipPropsChangeCallback=!1}connect(){this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}disconnect(){this.umountComponent()}propsValueChanged(){this.skipPropsChangeCallback?this.skipPropsChangeCallback=!1:(this.umountComponent(),this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps)))}get componentProps(){return this.propsValue}get mountElement(){return this.hasMountTarget?this.mountTarget:this.element}get resolvedComponent(){return this.resolveMounted(this.componentValue).component}get resolvedPlugin(){return this.resolveMounted(this.componentValue).plugin}umountComponent(){this._umountComponentCallback&&this._umountComponentCallback(),this._umountComponentCallback=void 0}mountComponent(t,o,n){return this.resolvedPlugin.mountComponent({el:t,Component:o,props:n})}resolveMounted(t){return this.application.turboMount.resolve(t)}setComponentProps(t){this.skipPropsChangeCallback=!0,this.propsValue=t}}n.values={props:Object,component:String},n.targets=["mount"];const e=t=>t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();class r{constructor(t={}){this.components=new Map,this.application=this.findOrStartApplication(t.application),this.application.turboMount=this,this.application.register("turbo-mount",n),document.addEventListener("turbo:before-morph-element",(t=>{
|
1
|
+
import{Controller as t,Application as o}from"@hotwired/stimulus";class n extends t{constructor(){super(...arguments),this.skipPropsChangeCallback=!1}connect(){this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}disconnect(){this.umountComponent()}propsValueChanged(){this.skipPropsChangeCallback?this.skipPropsChangeCallback=!1:(this.umountComponent(),this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps)))}get componentProps(){return this.propsValue}get mountElement(){return this.hasMountTarget?this.mountTarget:this.element}get resolvedComponent(){return this.resolveMounted(this.componentValue).component}get resolvedPlugin(){return this.resolveMounted(this.componentValue).plugin}umountComponent(){this._umountComponentCallback&&this._umountComponentCallback(),this._umountComponentCallback=void 0}mountComponent(t,o,n){return this.resolvedPlugin.mountComponent({el:t,Component:o,props:n})}resolveMounted(t){return this.application.turboMount.resolve(t)}setComponentProps(t){this.skipPropsChangeCallback=!0,this.propsValue=t}}n.values={props:Object,component:String},n.targets=["mount"];const e=t=>t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();class r{constructor(t={}){this.components=new Map,this.application=this.findOrStartApplication(t.application),this.application.turboMount=this,this.application.register("turbo-mount",n),document.addEventListener("turbo:before-morph-element",(t=>{const o=t,{target:n,detail:e}=o;n.getAttribute("data-controller")?.includes("turbo-mount")&&(n.setAttribute("data-turbo-mount-props-value",e.newElement.getAttribute("data-turbo-mount-props-value")||"{}"),t.preventDefault())}))}register(t,o,r,s){if(s||(s=n),this.components.has(o))throw new Error(`Component '${o}' is already registered.`);if(this.components.set(o,{component:r,plugin:t}),s){const t=`turbo-mount-${e(o).replace("/","--")}`;this.application.register(t,s)}}resolve(t){const o=this.components.get(t);if(!o)throw new Error(`Unknown component: ${t}`);return o}findOrStartApplication(t){let n=t||window.Stimulus;return n||(n=o.start(),window.Stimulus=n),n}}function s(t){return(o,n,e,r)=>{o.register(t,n,e,r)}}const i=t=>{const o=e(t).replace("/","--");return[`turbo-mount--${o}`,`turbo-mount-${o}`]},u=({plugin:t,turboMount:o,components:n,controllers:e})=>{const r=e??[];for(const{module:e,filename:s}of n){const n=s.replace(/\.\w*$/,"").replace(/^[./]*components\//,""),u=i(n),p=r.find((({identifier:t})=>u.includes(t))),l=e.default??e;p?o.register(t,n,l,p.controllerConstructor):o.register(t,n,l)}};export{r as TurboMount,n as TurboMountController,s as buildRegisterFunction,u as registerComponentsBase};
|
2
2
|
//# sourceMappingURL=turbo-mount.min.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"turbo-mount.min.js","sources":["../src/turbo-mount-controller.ts","../src/helpers.ts","../src/turbo-mount.ts","../src/registerComponentsBase.ts"],"sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\nimport { ApplicationWithTurboMount } from \"./turbo-mount\";\n\nexport class TurboMountController extends Controller {\n static values = {\n props: Object,\n component: String,\n };\n static targets = [\"mount\"];\n\n private skipPropsChangeCallback = false;\n\n declare propsValue: object;\n declare componentValue: string;\n declare readonly hasMountTarget: boolean;\n declare readonly mountTarget: Element;\n\n _umountComponentCallback?: () => void;\n\n connect() {\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n disconnect() {\n this.umountComponent();\n }\n\n propsValueChanged() {\n // Prevent re-mounting the component if the props are being set by the component itself\n if (this.skipPropsChangeCallback) {\n this.skipPropsChangeCallback = false;\n return;\n }\n\n this.umountComponent();\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n get componentProps() {\n return this.propsValue;\n }\n\n get mountElement() {\n return this.hasMountTarget ? this.mountTarget : this.element;\n }\n\n get resolvedComponent() {\n return this.resolveMounted(this.componentValue).component;\n }\n\n get resolvedPlugin() {\n return this.resolveMounted(this.componentValue).plugin;\n }\n\n umountComponent() {\n this._umountComponentCallback && this._umountComponentCallback();\n this._umountComponentCallback = undefined;\n }\n\n mountComponent(el: Element, Component: unknown, props: object) {\n return this.resolvedPlugin.mountComponent({ el, Component, props });\n }\n\n resolveMounted(component: string) {\n const app = this.application as ApplicationWithTurboMount;\n return app.turboMount.resolve(component);\n }\n\n setComponentProps(props: object) {\n this.skipPropsChangeCallback = true;\n this.propsValue = props;\n }\n}\n","export const camelToKebabCase = (str: string) => {\n return str.replace(/([a-z])([A-Z])/g, \"$1-$2\").toLowerCase();\n};\n","import { Application, ControllerConstructor } from \"@hotwired/stimulus\";\n\nimport { camelToKebabCase } from \"./helpers\";\nimport { TurboMountController } from \"./turbo-mount-controller\";\n\ndeclare global {\n interface Window {\n Stimulus?: Application;\n }\n}\n\nexport interface ApplicationWithTurboMount extends Application {\n turboMount: TurboMount;\n}\n\nexport type MountComponentProps<T> = {\n el: Element;\n Component: T;\n props: object;\n};\n\nexport type Plugin<T> = {\n mountComponent: (props: MountComponentProps<T>) => () => void;\n};\n\nexport type TurboMountProps = {\n application?: Application;\n};\n\ntype TurboMountComponents<T> = Map<string, { component: T; plugin: Plugin<T> }>;\n\ninterface TurboMorphEvent extends CustomEvent {\n target: Element;\n detail: {\n newElement: Element;\n };\n}\n\nexport class TurboMount {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components: TurboMountComponents<any>;\n application: ApplicationWithTurboMount;\n\n constructor(props: TurboMountProps = {}) {\n this.components = new Map();\n this.application = this.findOrStartApplication(props.application);\n this.application.turboMount = this;\n this.application.register(\"turbo-mount\", TurboMountController);\n\n document.addEventListener(\"turbo:before-morph-element\", (event) => {\n const turboMorphEvent = event as unknown as TurboMorphEvent;\n const { target, detail } = turboMorphEvent;\n\n if (target.getAttribute(\"data-controller\")?.includes(\"turbo-mount\")) {\n target.setAttribute(\n \"data-turbo-mount-props-value\",\n detail.newElement.getAttribute(\"data-turbo-mount-props-value\") ||\n \"{}\",\n );\n event.preventDefault();\n }\n });\n }\n\n register<T>(\n plugin: Plugin<T>,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) {\n controller ||= TurboMountController;\n if (this.components.has(name)) {\n throw new Error(`Component '${name}' is already registered.`);\n }\n this.components.set(name, { component, plugin });\n\n if (controller) {\n const controllerName = `turbo-mount-${camelToKebabCase(name).replace(\"/\", \"--\")}`;\n this.application.register(controllerName, controller);\n }\n }\n\n resolve(name: string) {\n const component = this.components.get(name);\n if (!component) {\n throw new Error(`Unknown component: ${name}`);\n }\n return component;\n }\n\n private findOrStartApplication(hydratedApp?: Application) {\n let application = hydratedApp || window.Stimulus;\n\n if (!application) {\n application = Application.start();\n window.Stimulus = application;\n }\n return application as ApplicationWithTurboMount;\n }\n}\n\nexport function buildRegisterFunction<T>(plugin: Plugin<T>) {\n return (\n turboMount: TurboMount,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) => {\n turboMount.register(plugin, name, component, controller);\n };\n}\n","import { Definition } from \"@hotwired/stimulus\";\n\nimport { TurboMount, Plugin } from \"./turbo-mount\";\nimport { camelToKebabCase } from \"./helpers\";\n\nexport type ComponentModule = { default: never } | never;\n\nexport type ComponentDefinition = {\n filename: string;\n module: ComponentModule;\n};\n\ntype RegisterComponentsProps<T> = {\n plugin: Plugin<T>;\n turboMount: TurboMount;\n components: ComponentDefinition[];\n controllers?: Definition[];\n};\n\nconst identifierNames = (name: string) => {\n const controllerName = camelToKebabCase(name).replace(\"/\", \"--\");\n\n return [`turbo-mount--${controllerName}`, `turbo-mount-${controllerName}`];\n};\n\nexport const registerComponentsBase = <T>({\n plugin,\n turboMount,\n components,\n controllers,\n}: RegisterComponentsProps<T>) => {\n const controllerModules = controllers ?? [];\n\n for (const { module, filename } of components) {\n const name = filename\n .replace(/\\.\\w*$/, \"\")\n .replace(/^[./]*components\\//, \"\");\n\n const identifiers = identifierNames(name);\n\n const controller = controllerModules.find(({ identifier }) =>\n identifiers.includes(identifier),\n );\n const component = module.default ?? module;\n\n if (controller) {\n turboMount.register(\n plugin,\n name,\n component,\n controller.controllerConstructor,\n );\n } else {\n turboMount.register(plugin, name, component);\n }\n }\n};\n"],"names":["TurboMountController","Controller","constructor","this","skipPropsChangeCallback","connect","_umountComponentCallback","mountComponent","mountElement","resolvedComponent","componentProps","disconnect","umountComponent","propsValueChanged","propsValue","hasMountTarget","mountTarget","element","resolveMounted","componentValue","component","resolvedPlugin","plugin","undefined","el","Component","props","application","turboMount","resolve","setComponentProps","values","Object","String","targets","camelToKebabCase","str","replace","toLowerCase","TurboMount","components","Map","findOrStartApplication","register","document","addEventListener","event","turboMorphEvent","target","detail","_a","getAttribute","includes","setAttribute","newElement","preventDefault","name","controller","has","Error","set","controllerName","get","hydratedApp","window","Stimulus","Application","start","buildRegisterFunction","identifierNames","registerComponentsBase","controllers","controllerModules","module","filename","identifiers","find","identifier","default","controllerConstructor"],"mappings":"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"}
|
1
|
+
{"version":3,"file":"turbo-mount.min.js","sources":["../src/turbo-mount-controller.ts","../src/helpers.ts","../src/turbo-mount.ts","../src/registerComponentsBase.ts"],"sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\nimport { ApplicationWithTurboMount } from \"./turbo-mount\";\n\nexport class TurboMountController extends Controller {\n static values = {\n props: Object,\n component: String,\n };\n static targets = [\"mount\"];\n\n private skipPropsChangeCallback = false;\n\n declare propsValue: object;\n declare componentValue: string;\n declare readonly hasMountTarget: boolean;\n declare readonly mountTarget: Element;\n\n _umountComponentCallback?: () => void;\n\n connect() {\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n disconnect() {\n this.umountComponent();\n }\n\n propsValueChanged() {\n // Prevent re-mounting the component if the props are being set by the component itself\n if (this.skipPropsChangeCallback) {\n this.skipPropsChangeCallback = false;\n return;\n }\n\n this.umountComponent();\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n get componentProps() {\n return this.propsValue;\n }\n\n get mountElement() {\n return this.hasMountTarget ? this.mountTarget : this.element;\n }\n\n get resolvedComponent() {\n return this.resolveMounted(this.componentValue).component;\n }\n\n get resolvedPlugin() {\n return this.resolveMounted(this.componentValue).plugin;\n }\n\n umountComponent() {\n this._umountComponentCallback && this._umountComponentCallback();\n this._umountComponentCallback = undefined;\n }\n\n mountComponent(el: Element, Component: unknown, props: object) {\n return this.resolvedPlugin.mountComponent({ el, Component, props });\n }\n\n resolveMounted(component: string) {\n const app = this.application as ApplicationWithTurboMount;\n return app.turboMount.resolve(component);\n }\n\n setComponentProps(props: object) {\n this.skipPropsChangeCallback = true;\n this.propsValue = props;\n }\n}\n","export const camelToKebabCase = (str: string) => {\n return str.replace(/([a-z])([A-Z])/g, \"$1-$2\").toLowerCase();\n};\n","import { Application, ControllerConstructor } from \"@hotwired/stimulus\";\n\nimport { camelToKebabCase } from \"./helpers\";\nimport { TurboMountController } from \"./turbo-mount-controller\";\n\ndeclare global {\n interface Window {\n Stimulus?: Application;\n }\n}\n\nexport interface ApplicationWithTurboMount extends Application {\n turboMount: TurboMount;\n}\n\nexport type MountComponentProps<T> = {\n el: Element;\n Component: T;\n props: object;\n};\n\nexport type Plugin<T> = {\n mountComponent: (props: MountComponentProps<T>) => () => void;\n};\n\nexport type TurboMountProps = {\n application?: Application;\n};\n\ntype TurboMountComponents<T> = Map<string, { component: T; plugin: Plugin<T> }>;\n\ninterface TurboMorphEvent extends CustomEvent {\n target: Element;\n detail: {\n newElement: Element;\n };\n}\n\nexport class TurboMount {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components: TurboMountComponents<any>;\n application: ApplicationWithTurboMount;\n\n constructor(props: TurboMountProps = {}) {\n this.components = new Map();\n this.application = this.findOrStartApplication(props.application);\n this.application.turboMount = this;\n this.application.register(\"turbo-mount\", TurboMountController);\n\n document.addEventListener(\"turbo:before-morph-element\", (event) => {\n const turboMorphEvent = event as unknown as TurboMorphEvent;\n const { target, detail } = turboMorphEvent;\n\n if (target.getAttribute(\"data-controller\")?.includes(\"turbo-mount\")) {\n target.setAttribute(\n \"data-turbo-mount-props-value\",\n detail.newElement.getAttribute(\"data-turbo-mount-props-value\") ||\n \"{}\",\n );\n event.preventDefault();\n }\n });\n }\n\n register<T>(\n plugin: Plugin<T>,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) {\n controller ||= TurboMountController;\n if (this.components.has(name)) {\n throw new Error(`Component '${name}' is already registered.`);\n }\n this.components.set(name, { component, plugin });\n\n if (controller) {\n const controllerName = `turbo-mount-${camelToKebabCase(name).replace(\"/\", \"--\")}`;\n this.application.register(controllerName, controller);\n }\n }\n\n resolve(name: string) {\n const component = this.components.get(name);\n if (!component) {\n throw new Error(`Unknown component: ${name}`);\n }\n return component;\n }\n\n private findOrStartApplication(hydratedApp?: Application) {\n let application = hydratedApp || window.Stimulus;\n\n if (!application) {\n application = Application.start();\n window.Stimulus = application;\n }\n return application as ApplicationWithTurboMount;\n }\n}\n\nexport function buildRegisterFunction<T>(plugin: Plugin<T>) {\n return (\n turboMount: TurboMount,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) => {\n turboMount.register(plugin, name, component, controller);\n };\n}\n","import { Definition } from \"@hotwired/stimulus\";\n\nimport { TurboMount, Plugin } from \"./turbo-mount\";\nimport { camelToKebabCase } from \"./helpers\";\n\nexport type ComponentModule = { default: never } | never;\n\nexport type ComponentDefinition = {\n filename: string;\n module: ComponentModule;\n};\n\ntype RegisterComponentsProps<T> = {\n plugin: Plugin<T>;\n turboMount: TurboMount;\n components: ComponentDefinition[];\n controllers?: Definition[];\n};\n\nconst identifierNames = (name: string) => {\n const controllerName = camelToKebabCase(name).replace(\"/\", \"--\");\n\n return [`turbo-mount--${controllerName}`, `turbo-mount-${controllerName}`];\n};\n\nexport const registerComponentsBase = <T>({\n plugin,\n turboMount,\n components,\n controllers,\n}: RegisterComponentsProps<T>) => {\n const controllerModules = controllers ?? [];\n\n for (const { module, filename } of components) {\n const name = filename\n .replace(/\\.\\w*$/, \"\")\n .replace(/^[./]*components\\//, \"\");\n\n const identifiers = identifierNames(name);\n\n const controller = controllerModules.find(({ identifier }) =>\n identifiers.includes(identifier),\n );\n const component = module.default ?? module;\n\n if (controller) {\n turboMount.register(\n plugin,\n name,\n component,\n controller.controllerConstructor,\n );\n } else {\n turboMount.register(plugin, name, component);\n }\n }\n};\n"],"names":["TurboMountController","Controller","constructor","this","skipPropsChangeCallback","connect","_umountComponentCallback","mountComponent","mountElement","resolvedComponent","componentProps","disconnect","umountComponent","propsValueChanged","propsValue","hasMountTarget","mountTarget","element","resolveMounted","componentValue","component","resolvedPlugin","plugin","undefined","el","Component","props","application","turboMount","resolve","setComponentProps","values","Object","String","targets","camelToKebabCase","str","replace","toLowerCase","TurboMount","components","Map","findOrStartApplication","register","document","addEventListener","event","turboMorphEvent","target","detail","getAttribute","includes","setAttribute","newElement","preventDefault","name","controller","has","Error","set","controllerName","get","hydratedApp","window","Stimulus","Application","start","buildRegisterFunction","identifierNames","registerComponentsBase","controllers","controllerModules","module","filename","identifiers","find","identifier","default","controllerConstructor"],"mappings":"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"}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
react:
|
2
|
+
pins: "react react-dom react-dom/client"
|
3
|
+
npm_packages: "react react-dom"
|
4
|
+
vite_plugin: "@vitejs/plugin-react"
|
5
|
+
extension: "jsx"
|
6
|
+
|
7
|
+
vue:
|
8
|
+
pins: "vue"
|
9
|
+
npm_packages: "vue"
|
10
|
+
vite_plugin: "@vitejs/plugin-vue"
|
11
|
+
extension: "vue"
|
12
|
+
|
13
|
+
svelte4:
|
14
|
+
pins: "svelte"
|
15
|
+
npm_packages: "svelte@4"
|
16
|
+
vite_plugin: "@sveltejs/vite-plugin-svelte"
|
17
|
+
extension: "svelte"
|
18
|
+
|
19
|
+
svelte:
|
20
|
+
pins: "svelte"
|
21
|
+
npm_packages: "svelte@5"
|
22
|
+
vite_plugin: "@sveltejs/vite-plugin-svelte"
|
23
|
+
extension: "svelte"
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module TurboMount
|
2
|
+
module Generators
|
3
|
+
module Helpers
|
4
|
+
### FS Helpers
|
5
|
+
def js_destination_path
|
6
|
+
return ViteRuby.config.source_code_dir if defined?(ViteRuby)
|
7
|
+
|
8
|
+
if file?("config/vite.json")
|
9
|
+
source_code_dir = JSON.parse(File.read(file_path("config/vite.json"))).dig("all", "sourceCodeDir")
|
10
|
+
return source_code_dir || "app/frontend"
|
11
|
+
end
|
12
|
+
|
13
|
+
"app/javascript"
|
14
|
+
end
|
15
|
+
|
16
|
+
def js_destination_root
|
17
|
+
file_path(js_destination_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def js_entrypoint
|
21
|
+
if vite?
|
22
|
+
js_file_path "entrypoints/application.js"
|
23
|
+
else
|
24
|
+
js_file_path "application.js"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def js_file_path(*relative_path)
|
29
|
+
File.join(js_destination_root, *relative_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def file?(*relative_path)
|
33
|
+
File.file?(file_path(*relative_path))
|
34
|
+
end
|
35
|
+
|
36
|
+
def file_path(*relative_path)
|
37
|
+
File.join(destination_root, *relative_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def vite?
|
41
|
+
file?("config/vite.json") && Dir.glob(file_path("vite.config.*")).any?
|
42
|
+
end
|
43
|
+
|
44
|
+
# Interactivity Helpers
|
45
|
+
def ask(*)
|
46
|
+
unless options[:interactive]
|
47
|
+
say_error "Specify all options when running the generator non-interactively.", :red
|
48
|
+
exit(1)
|
49
|
+
end
|
50
|
+
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def yes?(*)
|
55
|
+
return false unless options[:interactive]
|
56
|
+
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -13,6 +13,6 @@ const turboMount = new TurboMount();
|
|
13
13
|
// If you want to automatically register components use:
|
14
14
|
// import { registerComponents } from "turbo-mount/registerComponents/<%= framework %>";
|
15
15
|
// const controllers = import.meta.glob("/controllers/**/*_controller.js", { eager: true });
|
16
|
-
// const components = import.meta.glob("/components
|
16
|
+
// const components = import.meta.glob("/components/**/*.<%= extension %>", { eager: true });
|
17
17
|
// registerComponents({ turboMount, components, controllers });
|
18
18
|
<%- end -%>
|
@@ -1,37 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "yaml"
|
4
|
+
require "rails/generators"
|
5
|
+
require "rails/generators/base"
|
6
|
+
|
7
|
+
require_relative "helpers"
|
8
|
+
require_relative "js_package_manager"
|
9
|
+
|
3
10
|
module TurboMount
|
4
11
|
module Generators
|
5
12
|
class InstallGenerator < Rails::Generators::Base
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
npm_packages: "react react-dom",
|
10
|
-
vite_plugin: "@vitejs/plugin-react"
|
11
|
-
},
|
12
|
-
"vue" => {
|
13
|
-
pins: "vue",
|
14
|
-
npm_packages: "vue",
|
15
|
-
vite_plugin: "@vitejs/plugin-vue"
|
16
|
-
},
|
17
|
-
"svelte" => {
|
18
|
-
pins: "svelte",
|
19
|
-
npm_packages: "svelte",
|
20
|
-
vite_plugin: "@sveltejs/vite-plugin-svelte"
|
21
|
-
}
|
22
|
-
}.freeze
|
13
|
+
include Helpers
|
14
|
+
|
15
|
+
FRAMEWORKS = YAML.load_file(File.expand_path("./frameworks.yml", __dir__))
|
23
16
|
|
24
17
|
source_root File.expand_path("install", __dir__)
|
25
18
|
|
19
|
+
class_option :framework, type: :string,
|
20
|
+
desc: "The framework you want to use with Turbo Mount",
|
21
|
+
enum: FRAMEWORKS.keys,
|
22
|
+
default: nil
|
23
|
+
|
24
|
+
class_option :package_manager, type: :string, default: nil,
|
25
|
+
enum: JSPackageManager.package_managers,
|
26
|
+
desc: "The package manager you want to use to install Turbo Mount"
|
27
|
+
|
28
|
+
class_option :verbose, type: :boolean, default: false,
|
29
|
+
desc: "Run the generator in verbose mode"
|
30
|
+
|
26
31
|
def install
|
27
32
|
say "Installing Turbo Mount"
|
28
33
|
|
29
|
-
|
30
|
-
say "Could not find a package.json or config/importmap.rb file to add the turbo-mount dependency to, please add it manually.", :red
|
31
|
-
exit!
|
32
|
-
end
|
34
|
+
package_manager.validate!
|
33
35
|
|
34
|
-
if importmap?
|
36
|
+
if package_manager.importmap?
|
35
37
|
install_importmap
|
36
38
|
else
|
37
39
|
install_nodejs
|
@@ -43,19 +45,10 @@ module TurboMount
|
|
43
45
|
private
|
44
46
|
|
45
47
|
def install_nodejs
|
46
|
-
|
47
|
-
when "npm"
|
48
|
-
run "npm install turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
|
49
|
-
when "pnpm"
|
50
|
-
run "pnpm install turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
|
51
|
-
when "yarn"
|
52
|
-
run "yarn add turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
|
53
|
-
when "bun"
|
54
|
-
run "bun add turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
|
55
|
-
end
|
48
|
+
package_manager.add_dependencies("turbo-mount", FRAMEWORKS[framework][:npm_packages])
|
56
49
|
|
57
50
|
say "Creating Turbo Mount initializer"
|
58
|
-
template "turbo-mount.js",
|
51
|
+
template "turbo-mount.js", js_file_path("turbo-mount.js")
|
59
52
|
begin
|
60
53
|
append_to_file js_entrypoint, %(import "./turbo-mount"\n)
|
61
54
|
rescue
|
@@ -66,7 +59,7 @@ module TurboMount
|
|
66
59
|
|
67
60
|
def install_importmap
|
68
61
|
say "Creating Turbo Mount initializer"
|
69
|
-
template "turbo-mount.js",
|
62
|
+
template "turbo-mount.js", js_file_path("turbo-mount-initializer.js")
|
70
63
|
append_to_file "app/javascript/application.js", %(import "turbo-mount-initializer"\n)
|
71
64
|
|
72
65
|
say "Pinning Turbo Mount to the importmap"
|
@@ -75,53 +68,23 @@ module TurboMount
|
|
75
68
|
append_to_file "config/importmap.rb", %(pin "turbo-mount-initializer"\n)
|
76
69
|
|
77
70
|
say "Pinning framework dependencies to the importmap"
|
78
|
-
|
79
|
-
end
|
80
|
-
|
81
|
-
def js_entrypoint
|
82
|
-
if vite?
|
83
|
-
"app/javascript/entrypoints/application.js"
|
84
|
-
else
|
85
|
-
"app/javascript/application.js"
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def vite?
|
90
|
-
Dir.glob(Rails.root.join("vite.config.*")).any?
|
91
|
-
end
|
92
|
-
|
93
|
-
def importmap?
|
94
|
-
build_tool == "importmap"
|
71
|
+
package_manager.add_dependencies(FRAMEWORKS[framework][:pins])
|
95
72
|
end
|
96
73
|
|
97
74
|
def warn_about_vite_plugin
|
98
75
|
say "Make sure to install and add #{FRAMEWORKS[framework][:vite_plugin]} to your Vite config", :yellow
|
99
76
|
end
|
100
77
|
|
101
|
-
def
|
102
|
-
|
103
|
-
|
104
|
-
@build_tool = detect_build_tool
|
78
|
+
def package_manager
|
79
|
+
@package_manager ||= JSPackageManager.new(self)
|
105
80
|
end
|
106
81
|
|
107
|
-
def
|
108
|
-
|
109
|
-
if Rails.root.join("package-lock.json").exist?
|
110
|
-
"npm"
|
111
|
-
elsif Rails.root.join("pnpm-lock.yaml").exist?
|
112
|
-
"pnpm"
|
113
|
-
elsif Rails.root.join("bun.config.js").exist?
|
114
|
-
"bun"
|
115
|
-
else
|
116
|
-
"yarn"
|
117
|
-
end
|
118
|
-
elsif Rails.root.join("config/importmap.rb").exist?
|
119
|
-
"importmap"
|
120
|
-
end
|
82
|
+
def extension
|
83
|
+
FRAMEWORKS[framework][:extension]
|
121
84
|
end
|
122
85
|
|
123
86
|
def framework
|
124
|
-
@framework ||= ask("What framework do you want to use with Turbo Mount?", limited_to: FRAMEWORKS.keys, default: "react")
|
87
|
+
@framework ||= options[:framework] || ask("What framework do you want to use with Turbo Mount?", :green, limited_to: FRAMEWORKS.keys, default: "react")
|
125
88
|
end
|
126
89
|
end
|
127
90
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module TurboMount
|
2
|
+
module Generators
|
3
|
+
class JSPackageManager
|
4
|
+
def self.package_managers
|
5
|
+
%w[npm yarn bun pnpm importmap]
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(generator)
|
9
|
+
@generator = generator
|
10
|
+
@package_manager = generator.options[:package_manager] || detect_package_manager
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate!
|
14
|
+
return if @package_manager.present?
|
15
|
+
|
16
|
+
@generator.say "Could not find a package.json or config/importmap.rb file to add the turbo-mount dependency to, please add it manually.", :red
|
17
|
+
exit(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
def importmap?
|
21
|
+
@package_manager == "importmap"
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_dependencies(*dependencies)
|
25
|
+
cmd =
|
26
|
+
if importmap?
|
27
|
+
"bin/importmap pin #{dependencies.join(" ")}"
|
28
|
+
else
|
29
|
+
"#{@package_manager} add #{dependencies.join(" ")}#{@generator.options[:verbose] ? "" : " --silent"}"
|
30
|
+
end
|
31
|
+
@generator.in_root { @generator.run cmd }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def detect_package_manager
|
37
|
+
if file?("package.json")
|
38
|
+
if file?("package-lock.json")
|
39
|
+
"npm"
|
40
|
+
elsif file?("pnpm-lock.yaml")
|
41
|
+
"pnpm"
|
42
|
+
elsif file?("bun.lockb")
|
43
|
+
"bun"
|
44
|
+
else
|
45
|
+
"yarn"
|
46
|
+
end
|
47
|
+
elsif file?("config/importmap.rb")
|
48
|
+
"importmap"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def file?(*relative_path)
|
53
|
+
@generator.file?(*relative_path)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/turbo/mount/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbo-mount
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Svyatoslav Kryukov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -44,11 +44,17 @@ files:
|
|
44
44
|
- app/assets/javascripts/turbo-mount/svelte.js
|
45
45
|
- app/assets/javascripts/turbo-mount/svelte.min.js
|
46
46
|
- app/assets/javascripts/turbo-mount/svelte.min.js.map
|
47
|
+
- app/assets/javascripts/turbo-mount/svelte4.js
|
48
|
+
- app/assets/javascripts/turbo-mount/svelte4.min.js
|
49
|
+
- app/assets/javascripts/turbo-mount/svelte4.min.js.map
|
47
50
|
- app/assets/javascripts/turbo-mount/vue.js
|
48
51
|
- app/assets/javascripts/turbo-mount/vue.min.js
|
49
52
|
- app/assets/javascripts/turbo-mount/vue.min.js.map
|
53
|
+
- lib/generators/turbo_mount/frameworks.yml
|
54
|
+
- lib/generators/turbo_mount/helpers.rb
|
50
55
|
- lib/generators/turbo_mount/install/turbo-mount.js.tt
|
51
56
|
- lib/generators/turbo_mount/install_generator.rb
|
57
|
+
- lib/generators/turbo_mount/js_package_manager.rb
|
52
58
|
- lib/turbo/mount.rb
|
53
59
|
- lib/turbo/mount/engine.rb
|
54
60
|
- lib/turbo/mount/helpers.rb
|