@csedl/svelte-on-rails 0.0.5 → 0.0.7
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.
- package/README.md +9 -14
- package/index.js +2 -121
- package/package.json +1 -1
- package/src/cleanupSvelteComponents.js +38 -0
- package/src/debugUtils.js +25 -0
- package/src/initializeSvelteComponents.js +84 -0
package/README.md
CHANGED
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Works together with the [svelte-on-rails](https://gitlab.com/sedl/svelte-on-rails) gem.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Please follow the [gem](https://gitlab.com/sedl/svelte-on-rails) instructions.
|
|
8
|
+
- Svelte >= 5
|
|
9
|
+
- tested with ruby 3.2.2 and rails 7.1
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
## Installation
|
|
8
12
|
|
|
9
13
|
```
|
|
10
14
|
npm i @csedl/svelte-on-rails
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
A file like `initializeSvelte.js` that is imported into your entrypoint file
|
|
17
|
+
Create a file like `initializeSvelte.js` and import it into your entrypoint file, like `application.js`
|
|
16
18
|
|
|
17
19
|
```javascript
|
|
18
20
|
import { initializeSvelteComponents, cleanupSvelteComponents } from 'svelte-on-rails';
|
|
@@ -34,7 +36,8 @@ document.addEventListener('turbo:before-cache', () => {
|
|
|
34
36
|
});
|
|
35
37
|
```
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
- The last boolean argument is for debugging logs to the console
|
|
40
|
+
- I have inserted very detailed error logs, so please check your browser console for logs.
|
|
38
41
|
|
|
39
42
|
## How it works
|
|
40
43
|
|
|
@@ -52,16 +55,8 @@ to the wrapping element. Then, this lib
|
|
|
52
55
|
- adds the attribute `data-svelte-initialized='true'`
|
|
53
56
|
- removes the class `svelte-on-rails-not-initialized-component`
|
|
54
57
|
|
|
55
|
-
## Requirements
|
|
56
|
-
|
|
57
|
-
- Svelte >= 5
|
|
58
|
-
- tested with ruby 3.2.2 and rails 7.1
|
|
59
|
-
|
|
60
58
|
## Testing
|
|
61
59
|
|
|
62
|
-
I developed this all on Rails-7, together with `vite_rails`.
|
|
63
|
-
|
|
64
|
-
|
|
65
60
|
There is a [rails project](https://gitlab.com/sedl/svelte-on-rails-tests) with testings (based on playwright) where the gem and this package are tested together.
|
|
66
61
|
|
|
67
62
|
The gem includes its own tests.
|
package/index.js
CHANGED
|
@@ -1,123 +1,4 @@
|
|
|
1
|
-
import {mount, hydrate, unmount} from 'svelte';
|
|
2
1
|
|
|
3
|
-
|
|
4
|
-
const svelteInstances = new WeakMap();
|
|
2
|
+
export {initializeSvelteComponents} from "./src/initializeSvelteComponents";
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
* Initializes Svelte components on elements with the specified class.
|
|
8
|
-
* @param {string} componentsRoot - Root path for component files
|
|
9
|
-
* @param {Object} components - Object containing imported Svelte components
|
|
10
|
-
* @param {boolean} debug - Enable debug logging
|
|
11
|
-
*/
|
|
12
|
-
export function initializeSvelteComponents(componentsRoot, components, debug = false) {
|
|
13
|
-
const virginClass = 'svelte-on-rails-not-initialized-component';
|
|
14
|
-
const virginElements = document.getElementsByClassName(virginClass);
|
|
15
|
-
debugLog(`Found ${virginElements.length} elements with class "${virginClass}"`, debug);
|
|
16
|
-
if (virginElements.length === 0) return;
|
|
17
|
-
|
|
18
|
-
// Convert live HTMLCollection to a static array
|
|
19
|
-
const elementsArray = Array.from(virginElements);
|
|
20
|
-
|
|
21
|
-
// Iterate over the static array
|
|
22
|
-
for (const element of elementsArray) {
|
|
23
|
-
const action = element.innerHTML ? 'hydrate' : 'mount';
|
|
24
|
-
const componentName = element.getAttribute('data-svelte-component');
|
|
25
|
-
|
|
26
|
-
// Check component
|
|
27
|
-
const componentPath = `${componentsRoot}/${componentName}.svelte`.replace(/\/+/g, '/');
|
|
28
|
-
if (!components[componentPath]) {
|
|
29
|
-
console.error(`[initializeSvelteComponents] Component not found for path: ${componentPath}`);
|
|
30
|
-
continue; // Proceed to the next element
|
|
31
|
-
}
|
|
32
|
-
const component = components[componentPath].default;
|
|
33
|
-
|
|
34
|
-
// Parse props
|
|
35
|
-
let props = {};
|
|
36
|
-
const attrKey = 'data-props';
|
|
37
|
-
const propsString = element.getAttribute(attrKey);
|
|
38
|
-
try {
|
|
39
|
-
if (propsString) props = JSON.parse(propsString);
|
|
40
|
-
} catch (e) {
|
|
41
|
-
console.error(`[initializeSvelteComponents] Error parsing ${attrKey} ("${propsString}") for ${componentName}:`, e);
|
|
42
|
-
continue; // Proceed to the next element
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
let instance;
|
|
47
|
-
if (action === 'mount') {
|
|
48
|
-
element.innerHTML = '';
|
|
49
|
-
instance = mount(component, {
|
|
50
|
-
target: element,
|
|
51
|
-
props,
|
|
52
|
-
hydrate: false,
|
|
53
|
-
});
|
|
54
|
-
debugLog2(`Mounted successfully ${componentName}, with parsed props:`, props, debug);
|
|
55
|
-
} else if (action === 'hydrate') {
|
|
56
|
-
instance = hydrate(component, {
|
|
57
|
-
target: element,
|
|
58
|
-
hydrate: true,
|
|
59
|
-
props: props,
|
|
60
|
-
});
|
|
61
|
-
debugLog2(`Hydrated successfully ${componentName}, with parsed props:`, props, debug);
|
|
62
|
-
} else {
|
|
63
|
-
console.error(`[initializeSvelteComponents] Unknown action: "${action}" for component ${componentName}`);
|
|
64
|
-
continue; // Proceed to the next element
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Store instance and set attributes only after successful mount/hydrate
|
|
68
|
-
svelteInstances.set(element, instance);
|
|
69
|
-
element.classList.remove(virginClass);
|
|
70
|
-
element.setAttribute('data-svelte-initialized', 'true');
|
|
71
|
-
} catch (e) {
|
|
72
|
-
console.error(`[initializeSvelteComponents] Error ${action} ${componentName}:`, e);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Cleans up all initialized Svelte component instances.
|
|
79
|
-
*/
|
|
80
|
-
/**
|
|
81
|
-
* Cleans up all initialized Svelte component instances.
|
|
82
|
-
*/
|
|
83
|
-
export function cleanupSvelteComponents(debug = false) {
|
|
84
|
-
const initializedClass = 'svelte-initialized';
|
|
85
|
-
const initializedElements = document.querySelectorAll(`[data-${initializedClass}]`);
|
|
86
|
-
debugLog(`Cleaning up ${initializedElements.length} initialized Svelte components`, debug);
|
|
87
|
-
|
|
88
|
-
for (const element of initializedElements) {
|
|
89
|
-
try {
|
|
90
|
-
// Check if an instance exists for the element
|
|
91
|
-
const instance = svelteInstances.get(element);
|
|
92
|
-
if (instance) {
|
|
93
|
-
// In Svelte 5, use unmount to destroy the instance
|
|
94
|
-
unmount(instance);
|
|
95
|
-
debugLog2(`Unmounted Svelte component for element:`, element, debug);
|
|
96
|
-
} else {
|
|
97
|
-
debugLog(`No instance found for element, skipping unmount:`, element, debug);
|
|
98
|
-
}
|
|
99
|
-
} catch (e) {
|
|
100
|
-
if (debug) {
|
|
101
|
-
console.error(`[cleanupSvelteComponents] Error unmounting component for element:`, e);
|
|
102
|
-
}
|
|
103
|
-
// Fallback: Clear the element's content to ensure cleanup
|
|
104
|
-
element.innerHTML = '';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Remove the instance from storage and clean up attributes
|
|
108
|
-
svelteInstances.delete(element);
|
|
109
|
-
element.removeAttribute('data-svelte-initialized');
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function debugLog(msg, debug) {
|
|
114
|
-
if (debug) {
|
|
115
|
-
console.log(`[initializeSvelteComponents] ${msg}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function debugLog2(msg, object, debug) {
|
|
120
|
-
if (debug) {
|
|
121
|
-
console.log(`[initializeSvelteComponents] ${msg}`, object);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
4
|
+
export {cleanupSvelteComponents} from "./src/cleanupSvelteComponents";
|
package/package.json
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { unmount } from 'svelte';
|
|
2
|
+
import { debugLog, debugLog2 } from './debugUtils.js';
|
|
3
|
+
|
|
4
|
+
// Store for tracking initialized Svelte component instances
|
|
5
|
+
const svelteInstances = new WeakMap();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Cleans up all initialized Svelte component instances.
|
|
9
|
+
*/
|
|
10
|
+
export function cleanupSvelteComponents(debug = false) {
|
|
11
|
+
const initializedClass = 'svelte-initialized';
|
|
12
|
+
const initializedElements = document.querySelectorAll(`[data-${initializedClass}]`);
|
|
13
|
+
debugLog(`Cleaning up ${initializedElements.length} initialized Svelte components`, debug);
|
|
14
|
+
|
|
15
|
+
for (const element of initializedElements) {
|
|
16
|
+
try {
|
|
17
|
+
// Check if an instance exists for the element
|
|
18
|
+
const instance = svelteInstances.get(element);
|
|
19
|
+
if (instance) {
|
|
20
|
+
// In Svelte 5, use unmount to destroy the instance
|
|
21
|
+
unmount(instance);
|
|
22
|
+
debugLog2(`Unmounted Svelte component for element:`, element, debug);
|
|
23
|
+
} else {
|
|
24
|
+
debugLog(`No instance found for element, skipping unmount:`, element, debug);
|
|
25
|
+
}
|
|
26
|
+
} catch (e) {
|
|
27
|
+
if (debug) {
|
|
28
|
+
console.error(`[cleanupSvelteComponents] Error unmounting component for element:`, e);
|
|
29
|
+
}
|
|
30
|
+
// Fallback: Clear the element's content to ensure cleanup
|
|
31
|
+
element.innerHTML = '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Remove the instance from storage and clean up attributes
|
|
35
|
+
svelteInstances.delete(element);
|
|
36
|
+
element.removeAttribute('data-svelte-initialized');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function debugLog(msg, debug) {
|
|
2
|
+
if (debug) {
|
|
3
|
+
console.log(`[initializeSvelteComponents] ${msg}`);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function debugLog2(msg, object, debug) {
|
|
8
|
+
if (debug) {
|
|
9
|
+
console.log(`[initializeSvelteComponents] ${msg}`, object);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function checkComponentsRoot(componentsRoot, firstImport) {
|
|
14
|
+
const reg = new RegExp(`^${componentsRoot}`);
|
|
15
|
+
|
|
16
|
+
if (!reg.test(firstImport)) {
|
|
17
|
+
console.error(
|
|
18
|
+
`[initializeSvelteComponents] ERROR\nThe componentsRoot («${componentsRoot}») does not match.\n` +
|
|
19
|
+
`The first found import is: «${firstImport}».\n` +
|
|
20
|
+
`components root must equal the beginning of the import path, example:\n` +
|
|
21
|
+
`componentsRoot: «/javascript», and ` +
|
|
22
|
+
`import: «/javascript/components/MyComponent.svelte» => would work`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { mount, hydrate } from 'svelte';
|
|
2
|
+
import { debugLog, debugLog2, checkComponentsRoot } from './debugUtils.js';
|
|
3
|
+
|
|
4
|
+
// Store for tracking initialized Svelte component instances
|
|
5
|
+
const svelteInstances = new WeakMap();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Initializes Svelte components on elements with the specified class.
|
|
9
|
+
* @param {string} componentsRoot - Root path for component files
|
|
10
|
+
* @param {Object} components - Object containing imported Svelte components
|
|
11
|
+
* @param {boolean} debug - Enable debug logging
|
|
12
|
+
*/
|
|
13
|
+
export function initializeSvelteComponents(componentsRoot, components, debug = false) {
|
|
14
|
+
const virginClass = 'svelte-on-rails-not-initialized-component';
|
|
15
|
+
const virginElements = document.getElementsByClassName(virginClass);
|
|
16
|
+
debugLog(`Found ${virginElements.length} elements with class "${virginClass}"`, debug);
|
|
17
|
+
if (virginElements.length === 0) return;
|
|
18
|
+
|
|
19
|
+
// Convert live HTMLCollection to a static array
|
|
20
|
+
const elementsArray = Array.from(virginElements);
|
|
21
|
+
|
|
22
|
+
// Check if componentsRoot is setup correctly
|
|
23
|
+
checkComponentsRoot(componentsRoot, Object.keys(components)[0]);
|
|
24
|
+
|
|
25
|
+
// Iterate over the static array
|
|
26
|
+
for (const element of elementsArray) {
|
|
27
|
+
const action = element.innerHTML ? 'hydrate' : 'mount';
|
|
28
|
+
const componentName = element.getAttribute('data-svelte-component');
|
|
29
|
+
|
|
30
|
+
// Check component
|
|
31
|
+
const componentPath = `${componentsRoot}/${componentName}.svelte`.replace(/\/+/g, '/');
|
|
32
|
+
if (!components[componentPath]) {
|
|
33
|
+
console.error(
|
|
34
|
+
`[initializeSvelteComponents] ERROR\n` +
|
|
35
|
+
`Component not found for path: «${componentPath}»\n` +
|
|
36
|
+
`Imported paths are:\n` +
|
|
37
|
+
`«${Object.keys(components).join('»\n«')}»`
|
|
38
|
+
);
|
|
39
|
+
continue; // Proceed to the next element
|
|
40
|
+
}
|
|
41
|
+
const component = components[componentPath].default;
|
|
42
|
+
|
|
43
|
+
// Parse props
|
|
44
|
+
let props = {};
|
|
45
|
+
const attrKey = 'data-props';
|
|
46
|
+
const propsString = element.getAttribute(attrKey);
|
|
47
|
+
try {
|
|
48
|
+
if (propsString) props = JSON.parse(propsString);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error(`[initializeSvelteComponents] Error parsing ${attrKey} ("${propsString}") for ${componentName}:`, e);
|
|
51
|
+
continue; // Proceed to the next element
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
let instance;
|
|
56
|
+
if (action === 'mount') {
|
|
57
|
+
element.innerHTML = '';
|
|
58
|
+
instance = mount(component, {
|
|
59
|
+
target: element,
|
|
60
|
+
props,
|
|
61
|
+
hydrate: false,
|
|
62
|
+
});
|
|
63
|
+
debugLog2(`Mounted successfully ${componentName}, with parsed props:`, props, debug);
|
|
64
|
+
} else if (action === 'hydrate') {
|
|
65
|
+
instance = hydrate(component, {
|
|
66
|
+
target: element,
|
|
67
|
+
hydrate: true,
|
|
68
|
+
props: props,
|
|
69
|
+
});
|
|
70
|
+
debugLog2(`Hydrated successfully ${componentName}, with parsed props:`, props, debug);
|
|
71
|
+
} else {
|
|
72
|
+
console.error(`[initializeSvelteComponents] Unknown action: "${action}" for component ${componentName}`);
|
|
73
|
+
continue; // Proceed to the next element
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Store instance and set attributes only after successful mount/hydrate
|
|
77
|
+
svelteInstances.set(element, instance);
|
|
78
|
+
element.classList.remove(virginClass);
|
|
79
|
+
element.setAttribute('data-svelte-initialized', 'true');
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.error(`[initializeSvelteComponents] Error ${action} ${componentName}:`, e);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|