Skip to content

Commit 94bc665

Browse files
ChqThomasweaverryan
authored andcommitted
[Svelte] Introduce Svelte UX component
1 parent 8eb4e08 commit 94bc665

30 files changed

+1065
-0
lines changed

.github/workflows/test.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ jobs:
120120
run: php vendor/bin/simple-phpunit
121121
working-directory: src/React
122122

123+
- name: Svelte Dependencies
124+
uses: ramsey/composer-install@v2
125+
with:
126+
working-directory: src/Svelte
127+
dependency-versions: lowest
128+
- name: Svelte Tests
129+
run: php vendor/bin/simple-phpunit
130+
working-directory: src/Svelte
131+
123132
tests-php8-low-deps:
124133
runs-on: ubuntu-latest
125134
steps:
@@ -228,6 +237,14 @@ jobs:
228237
working-directory: src/Autocomplete
229238
run: php vendor/bin/simple-phpunit
230239

240+
- name: Svelte Dependencies
241+
uses: ramsey/composer-install@v2
242+
with:
243+
working-directory: src/Svelte
244+
- name: Svelte Tests
245+
working-directory: src/Svelte
246+
run: php vendor/bin/simple-phpunit
247+
231248
tests-php81-high-deps:
232249
runs-on: ubuntu-latest
233250
steps:

src/Svelte/.gitattributes

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/.gitattributes export-ignore
2+
/.gitignore export-ignore
3+
/.symfony.bundle.yaml export-ignore
4+
/assets/.gitignore export-ignore
5+
/assets/jest.config.js export-ignore
6+
/assets/test export-ignore
7+
/tests export-ignore
8+
/assets/src/*.ts export-ignore

src/Svelte/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/composer.lock
2+
/phpunit.xml
3+
/vendor/
4+
/.phpunit.result.cache

src/Svelte/.symfony.bundle.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
branches: ["2.x"]
2+
maintained_branches: ["2.x"]
3+
doc_dir: "doc"

src/Svelte/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Fabien Potencier
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is furnished
10+
to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

src/Svelte/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Symfony UX Svelte
2+
3+
Symfony UX Svelte integrates [Svelte](https://svelte.dev/) into Symfony applications.
4+
It provides tools to render Svelte 3 components from Twig.
5+
6+
**This repository is a READ-ONLY sub-tree split**. See
7+
https://github.com/symfony/ux to create issues or submit pull requests.
8+
9+
## Resources
10+
11+
- [Documentation](https://symfony.com/bundles/ux-svelte/current/index.html)
12+
- [Report issues](https://github.com/symfony/ux/issues) and
13+
[send Pull Requests](https://github.com/symfony/ux/pulls)
14+
in the [main Symfony UX repository](https://github.com/symfony/ux)

src/Svelte/assets/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/node_modules/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// <reference types="webpack-env" />
2+
import type { SvelteComponent } from 'svelte';
3+
declare global {
4+
function resolveSvelteComponent(name: string): typeof SvelteComponent;
5+
interface Window {
6+
resolveSvelteComponent(name: string): typeof SvelteComponent;
7+
}
8+
}
9+
export declare function registerSvelteControllerComponents(context: __WebpackModuleApi.RequireContext): void;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
function registerSvelteControllerComponents(context) {
2+
const svelteControllers = {};
3+
const importAllSvelteComponents = (r) => {
4+
r.keys().forEach((key) => (svelteControllers[key] = r(key).default));
5+
};
6+
importAllSvelteComponents(context);
7+
window.resolveSvelteComponent = (name) => {
8+
const component = svelteControllers[`./${name}.svelte`];
9+
if (typeof component === 'undefined') {
10+
throw new Error(`Svelte controller "${name}" does not exist`);
11+
}
12+
return component;
13+
};
14+
}
15+
16+
export { registerSvelteControllerComponents };
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
import { SvelteComponent } from 'svelte';
3+
export default class extends Controller<Element & {
4+
root?: SvelteComponent;
5+
}> {
6+
private app;
7+
readonly componentValue: string;
8+
private props;
9+
private intro;
10+
readonly propsValue: Record<string, unknown> | null | undefined;
11+
readonly introValue: boolean | undefined;
12+
static values: {
13+
component: StringConstructor;
14+
props: ObjectConstructor;
15+
intro: BooleanConstructor;
16+
};
17+
connect(): void;
18+
disconnect(): void;
19+
_destroyIfExists(): void;
20+
private dispatchEvent;
21+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
3+
class default_1 extends Controller {
4+
connect() {
5+
var _a, _b;
6+
this.element.innerHTML = '';
7+
this.props = (_a = this.propsValue) !== null && _a !== void 0 ? _a : undefined;
8+
this.intro = (_b = this.introValue) !== null && _b !== void 0 ? _b : undefined;
9+
this.dispatchEvent('connect');
10+
const Component = window.resolveSvelteComponent(this.componentValue);
11+
this._destroyIfExists();
12+
this.app = new Component({
13+
target: this.element,
14+
props: this.props,
15+
intro: this.intro,
16+
});
17+
this.element.root = this.app;
18+
this.dispatchEvent('mount', {
19+
component: Component,
20+
});
21+
}
22+
disconnect() {
23+
this._destroyIfExists();
24+
this.dispatchEvent('unmount');
25+
}
26+
_destroyIfExists() {
27+
if (this.element.root !== undefined) {
28+
this.element.root.$destroy();
29+
delete this.element.root;
30+
}
31+
}
32+
dispatchEvent(name, payload = {}) {
33+
const detail = Object.assign({ componentName: this.componentValue, props: this.props, intro: this.intro }, payload);
34+
this.dispatch(name, { detail, prefix: 'svelte' });
35+
}
36+
}
37+
default_1.values = {
38+
component: String,
39+
props: Object,
40+
intro: Boolean,
41+
};
42+
43+
export { default_1 as default };

src/Svelte/assets/jest.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { defaults } = require('jest-config');
2+
const jestConfig = require('../../../jest.config.js');
3+
4+
jestConfig.moduleFileExtensions = [...defaults.moduleFileExtensions, 'svelte'];
5+
jestConfig.transform['^.+\\.svelte$'] = ['svelte-jester'];
6+
7+
module.exports = jestConfig;

src/Svelte/assets/package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "@symfony/ux-svelte",
3+
"description": "Integration of Svelte in Symfony",
4+
"main": "dist/register_controller.js",
5+
"module": "dist/register_controller.js",
6+
"version": "1.0.0",
7+
"license": "MIT",
8+
"symfony": {
9+
"controllers": {
10+
"svelte": {
11+
"main": "dist/render_controller.js",
12+
"fetch": "eager",
13+
"enabled": true
14+
}
15+
}
16+
},
17+
"peerDependencies": {
18+
"@hotwired/stimulus": "^3.0.0",
19+
"svelte": "^3.0"
20+
},
21+
"devDependencies": {
22+
"@hotwired/stimulus": "^3.0.0",
23+
"@types/webpack-env": "^1.16",
24+
"svelte": "^3.0",
25+
"svelte-jester": "^2.3"
26+
}
27+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* This file is part of the Symfony package.
3+
*
4+
* (c) Fabien Potencier <fabien@symfony.com>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
'use strict';
11+
12+
import type { SvelteComponent } from 'svelte';
13+
14+
declare global {
15+
function resolveSvelteComponent(name: string): typeof SvelteComponent;
16+
17+
interface Window {
18+
resolveSvelteComponent(name: string): typeof SvelteComponent;
19+
}
20+
}
21+
22+
export function registerSvelteControllerComponents(context: __WebpackModuleApi.RequireContext) {
23+
const svelteControllers: { [key: string]: object } = {};
24+
25+
const importAllSvelteComponents = (r: __WebpackModuleApi.RequireContext) => {
26+
r.keys().forEach((key) => (svelteControllers[key] = r(key).default));
27+
};
28+
29+
importAllSvelteComponents(context);
30+
31+
// Expose a global Svelte loader to allow rendering from the Stimulus controller
32+
(window as any).resolveSvelteComponent = (name: string): object => {
33+
const component = svelteControllers[`./${name}.svelte`];
34+
if (typeof component === 'undefined') {
35+
throw new Error(`Svelte controller "${name}" does not exist`);
36+
}
37+
38+
return component;
39+
};
40+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict';
2+
3+
import { Controller } from '@hotwired/stimulus';
4+
import { SvelteComponent } from 'svelte';
5+
6+
export default class extends Controller<Element & { root?: SvelteComponent }> {
7+
private app: SvelteComponent;
8+
declare readonly componentValue: string;
9+
10+
private props: Record<string, any> | undefined;
11+
private intro: boolean | undefined;
12+
13+
declare readonly propsValue: Record<string, unknown> | null | undefined;
14+
declare readonly introValue: boolean | undefined;
15+
16+
static values = {
17+
component: String,
18+
props: Object,
19+
intro: Boolean,
20+
};
21+
22+
connect() {
23+
this.element.innerHTML = '';
24+
25+
this.props = this.propsValue ?? undefined;
26+
this.intro = this.introValue ?? undefined;
27+
28+
this.dispatchEvent('connect');
29+
30+
const Component = window.resolveSvelteComponent(this.componentValue);
31+
32+
this._destroyIfExists();
33+
34+
// @see https://svelte.dev/docs#run-time-client-side-component-api-creating-a-component
35+
this.app = new Component({
36+
target: this.element,
37+
props: this.props,
38+
intro: this.intro,
39+
});
40+
41+
this.element.root = this.app;
42+
43+
this.dispatchEvent('mount', {
44+
component: Component,
45+
});
46+
}
47+
48+
disconnect() {
49+
this._destroyIfExists();
50+
this.dispatchEvent('unmount');
51+
}
52+
53+
_destroyIfExists() {
54+
if (this.element.root !== undefined) {
55+
this.element.root.$destroy();
56+
delete this.element.root;
57+
}
58+
}
59+
60+
private dispatchEvent(name: string, payload: object = {}) {
61+
const detail = {
62+
componentName: this.componentValue,
63+
props: this.props,
64+
intro: this.intro,
65+
...payload,
66+
};
67+
this.dispatch(name, { detail, prefix: 'svelte' });
68+
}
69+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
import { fade } from "svelte/transition"
3+
export let name = "without props";
4+
</script>
5+
6+
<div transition:fade={{ duration: 100 }}>
7+
<div>Hello {name}</div>
8+
</div>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* This file is part of the Symfony package.
3+
*
4+
* (c) Fabien Potencier <fabien@symfony.com>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
'use strict';
11+
12+
import {registerSvelteControllerComponents} from '../src/register_controller';
13+
import MyComponent from './fixtures/MyComponent.svelte';
14+
import {createRequireContextPolyfill} from './util/require_context_poylfill';
15+
16+
require.context = createRequireContextPolyfill(__dirname);
17+
18+
describe('registerSvelteControllerComponents', () => {
19+
it('registers controllers from require context', () => {
20+
registerSvelteControllerComponents(require.context('./fixtures', true, /\.svelte$/));
21+
const resolveComponent = (window as any).resolveSvelteComponent;
22+
23+
expect(resolveComponent).not.toBeUndefined();
24+
expect(resolveComponent('MyComponent')).toBe(MyComponent);
25+
expect(resolveComponent('MyComponent')).not.toBeUndefined();
26+
});
27+
});

0 commit comments

Comments
 (0)