turbo-mount 0.3.3 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6385a766c9e0173e886eb33c27946e222dba5ee810744af4c6d317a93d80b315
4
- data.tar.gz: 0c4402e72e9a5b6a98b8219e0ad8749ee3b2716f99412ebe3fcd35783798be7a
3
+ metadata.gz: 0b29826ddb780be9b604f24aa2a2a4a8635b1dc95106315baeef4323ace52172
4
+ data.tar.gz: 8f70abb8e1a8a44d26c294f30173b366502e4fe7f6a6e55c4dfe87778f59d883
5
5
  SHA512:
6
- metadata.gz: bf22bb88a3042c20b96774167c25b500146f1ba002f2224609e4444afd968e68527c664970ca823c1b73f09e74c685cd7d51f7354fd838bceb8d1b00a5f13d14
7
- data.tar.gz: e4c5cb4ee14f437e35d49d4d36ec5e423649f8e5685319dec43c1c6f35f1fca8f7231eb1bf016ce9b0fb274eab184145049446d6ccfa579657fce151bffc3fcf
6
+ metadata.gz: aaf7c5d7d5813d7a34137103cee1701ba05400bb3b60e1a5c72da1a261cf4930fa2566c7cba4184740cb4dc0ae91ff28c7ce724d8aca3015b46b807dbe480afa
7
+ data.tar.gz: 72dd49789b076a28aa63df75a607438998748dd69793dbd2db82d300c4022007e567b88342755aa6212d5402b4be86579c9d50549f2941e436bbdf76600441d1
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.1] - 2024-11-26
11
+
12
+ ### Added
13
+
14
+ - Support Shakapacker in the installation script. ([@skryukov])
15
+
16
+ ### Fixed
17
+
18
+ - Fix installation script interactive mode. ([@skryukov])
19
+ - Fix installation script dependencies installation. ([@skryukov])
20
+
21
+ ## [0.4.0] - 2024-11-03
22
+
23
+ ### Added
24
+
25
+ - [BREAKING] Support Svelte 5. ([@skryukov])
26
+ `turbo-mount/svelte` is now the Svelte 5 plugin. The Svelte 4 plugin is now `turbo-mount/svelte4`.
27
+
10
28
  ## [0.3.3] - 2024-09-24
11
29
 
12
30
  ### Added
@@ -75,7 +93,9 @@ and this project adheres to [Semantic Versioning].
75
93
  [@jkogara]: https://github.com/jkogara
76
94
  [@skryukov]: https://github.com/skryukov
77
95
 
78
- [Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.3.3...HEAD
96
+ [Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.4.1...HEAD
97
+ [0.4.1]: https://github.com/skryukov/turbo-mount/compare/v0.4.0...v0.4.1
98
+ [0.4.0]: https://github.com/skryukov/turbo-mount/compare/v0.3.3...v0.4.0
79
99
  [0.3.3]: https://github.com/skryukov/turbo-mount/compare/v0.3.2...v0.3.3
80
100
  [0.3.2]: https://github.com/skryukov/turbo-mount/compare/v0.3.1...v0.3.2
81
101
  [0.3.1]: https://github.com/skryukov/turbo-mount/compare/v0.3.0...v0.3.1
data/README.md CHANGED
@@ -1,4 +1,11 @@
1
- # Turbo Mount
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.svg">
4
+ <img src="assets/logo.svg" title="Turbo Mount logo" width="220" height="45">
5
+ </picture>
6
+ </p>
7
+
8
+ <h1 align="center">Turbo Mount</h1>
2
9
 
3
10
  [![Gem Version](https://badge.fury.io/rb/turbo-mount.svg)](https://rubygems.org/gems/turbo-mount)
4
11
 
@@ -143,7 +150,8 @@ This will generate the following HTML:
143
150
 
144
151
  - React: `"turbo-mount/react"`
145
152
  - Vue: `"turbo-mount/vue"`
146
- - Svelte: `"turbo-mount/svelte"`
153
+ - Svelte 4: `"turbo-mount/svelte4"`
154
+ - Svelte 5: `"turbo-mount/svelte"`
147
155
 
148
156
  To add support for other frameworks, create a custom plugin. See included plugins for examples.
149
157
 
@@ -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 = new Component({ target: el, props });
8
+ const component = mount(Component, { target: el, props });
8
9
  return () => {
9
- component.$destroy();
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 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};
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 { ComponentType } from \"svelte\";\n\nconst plugin: Plugin<ComponentType> = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\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,EAC3BI,EAAY,IAAIF,EAAU,CAAEG,OAAQJ,EAAIE,UAE9C,MAAO,KACLC,EAAUE,UAAU,CACrB,GAICC,EAAoBC,EAAsBV"}
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 ((_a = target.getAttribute("data-controller")) === null || _a === void 0 ? void 0 : _a.includes("turbo-mount")) {
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
- var _a;
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 = (_a = module.default) !== null && _a !== void 0 ? _a : module;
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=>{var o;const n=t,{target:e,detail:r}=n;(null===(o=e.getAttribute("data-controller"))||void 0===o?void 0:o.includes("turbo-mount"))&&(e.setAttribute("data-turbo-mount-props-value",r.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})=>{var r;const s=null!=e?e:[];for(const{module:e,filename:u}of n){const n=u.replace(/\.\w*$/,"").replace(/^[./]*components\//,""),l=i(n),p=s.find((({identifier:t})=>l.includes(t))),a=null!==(r=e.default)&&void 0!==r?r:e;p?o.register(t,n,a,p.controllerConstructor):o.register(t,n,a)}};export{r as TurboMount,n as TurboMountController,s as buildRegisterFunction,u as registerComponentsBase};
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,67 @@
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
+ elsif shakapacker?
24
+ js_file_path "packs/application.js"
25
+ else
26
+ js_file_path "application.js"
27
+ end
28
+ end
29
+
30
+ def js_file_path(*relative_path)
31
+ File.join(js_destination_root, *relative_path)
32
+ end
33
+
34
+ def file?(*relative_path)
35
+ File.file?(file_path(*relative_path))
36
+ end
37
+
38
+ def file_path(*relative_path)
39
+ File.join(destination_root, *relative_path)
40
+ end
41
+
42
+ def vite?
43
+ file?("config/vite.json") && Dir.glob(file_path("vite.config.*")).any?
44
+ end
45
+
46
+ def shakapacker?
47
+ file?("config/shakapacker.yml") || file?("config/webpacker.yml")
48
+ end
49
+
50
+ # Interactivity Helpers
51
+ def ask(*)
52
+ unless options[:interactive]
53
+ say_error "Specify all options when running the generator non-interactively.", :red
54
+ exit(1)
55
+ end
56
+
57
+ super
58
+ end
59
+
60
+ def yes?(*)
61
+ return false unless options[:interactive]
62
+
63
+ super
64
+ end
65
+ end
66
+ end
67
+ 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/**/*.jsx", { eager: true });
16
+ // const components = import.meta.glob("/components/**/*.<%= extension %>", { eager: true });
17
17
  // registerComponents({ turboMount, components, controllers });
18
18
  <%- end -%>
@@ -1,37 +1,43 @@
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
- FRAMEWORKS = {
7
- "react" => {
8
- pins: "react react-dom react-dom/client",
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 :interactive, type: :boolean, default: true,
29
+ desc: "Whether to prompt for optional installations"
30
+
31
+ class_option :verbose, type: :boolean, default: false,
32
+ desc: "Run the generator in verbose mode"
33
+
26
34
  def install
27
35
  say "Installing Turbo Mount"
28
36
 
29
- if build_tool.nil?
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
37
+ package_manager.validate!
33
38
 
34
- if importmap?
39
+ create_initializer
40
+ if package_manager.importmap?
35
41
  install_importmap
36
42
  else
37
43
  install_nodejs
@@ -42,86 +48,51 @@ module TurboMount
42
48
 
43
49
  private
44
50
 
45
- def install_nodejs
46
- case build_tool
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
56
-
51
+ def create_initializer
57
52
  say "Creating Turbo Mount initializer"
58
- template "turbo-mount.js", File.join("app/javascript/turbo-mount.js")
53
+
54
+ needs_alias = shakapacker? || package_manager.importmap?
55
+ initializer_path = needs_alias ? "turbo-mount-initializer.js" : "turbo-mount.js"
56
+ initializer_import = needs_alias ? %(import "turbo-mount-initializer"\n) : %(import "./turbo-mount"\n)
57
+
58
+ template "turbo-mount.js", js_file_path(initializer_path)
59
59
  begin
60
- append_to_file js_entrypoint, %(import "./turbo-mount"\n)
60
+ append_to_file js_entrypoint, initializer_import
61
61
  rescue
62
- say 'Could not find the application entrypoint, please add `import "./turbo-mount"` manually.', :yellow
62
+ say "Could not find the application entrypoint, please add `#{initializer_import.strip}` manually.", :yellow
63
63
  end
64
+ end
65
+
66
+ def install_nodejs
67
+ package_manager.add_dependencies("turbo-mount", FRAMEWORKS[framework]["npm_packages"])
68
+
64
69
  warn_about_vite_plugin if vite?
65
70
  end
66
71
 
67
72
  def install_importmap
68
- say "Creating Turbo Mount initializer"
69
- template "turbo-mount.js", File.join("app/javascript/turbo-mount-initializer.js")
70
- append_to_file "app/javascript/application.js", %(import "turbo-mount-initializer"\n)
71
-
72
73
  say "Pinning Turbo Mount to the importmap"
73
74
  append_to_file "config/importmap.rb", %(pin "turbo-mount", to: "turbo-mount.min.js"\n)
74
75
  append_to_file "config/importmap.rb", %(pin "turbo-mount/#{framework}", to: "turbo-mount/#{framework}.min.js"\n)
75
76
  append_to_file "config/importmap.rb", %(pin "turbo-mount-initializer"\n)
76
77
 
77
78
  say "Pinning framework dependencies to the importmap"
78
- run "bin/importmap pin #{FRAMEWORKS[framework][:pins]}"
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"
79
+ package_manager.add_dependencies(FRAMEWORKS[framework]["pins"])
95
80
  end
96
81
 
97
82
  def warn_about_vite_plugin
98
- say "Make sure to install and add #{FRAMEWORKS[framework][:vite_plugin]} to your Vite config", :yellow
83
+ say "Make sure to install and add #{FRAMEWORKS[framework]["vite_plugin"]} to your Vite config", :yellow
99
84
  end
100
85
 
101
- def build_tool
102
- return @build_tool if defined?(@build_tool)
103
-
104
- @build_tool = detect_build_tool
86
+ def package_manager
87
+ @package_manager ||= JSPackageManager.new(self)
105
88
  end
106
89
 
107
- def detect_build_tool
108
- if Rails.root.join("package.json").exist?
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
90
+ def extension
91
+ FRAMEWORKS[framework]["extension"]
121
92
  end
122
93
 
123
94
  def framework
124
- @framework ||= ask("What framework do you want to use with Turbo Mount?", limited_to: FRAMEWORKS.keys, default: "react")
95
+ @framework ||= options[:framework] || ask("What framework do you want to use with Turbo Mount?", :green, limited_to: FRAMEWORKS.keys, default: "react")
125
96
  end
126
97
  end
127
98
  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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Turbo
4
4
  module Mount
5
- VERSION = "0.3.3"
5
+ VERSION = "0.4.1"
6
6
  end
7
7
  end
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.3.3
4
+ version: 0.4.1
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-09-24 00:00:00.000000000 Z
11
+ date: 2024-11-26 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
@@ -78,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
84
  - !ruby/object:Gem::Version
79
85
  version: '0'
80
86
  requirements: []
81
- rubygems_version: 3.5.17
87
+ rubygems_version: 3.5.23
82
88
  signing_key:
83
89
  specification_version: 4
84
90
  summary: Use React, Vue, Svelte, and other components with Hotwire.