Skip to content

[CI] Ensure JS packages (either from vendor/ or npm versions) can be installed/used inside an Encore App #2613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 1, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[TestApps] Introduce an EncoreApp with all UX PHP and JS packages req…
…uired
  • Loading branch information
Kocal committed Mar 1, 2025
commit c7debbf62f07c96c5dd0389b5093bd9c225ba8fe
47 changes: 47 additions & 0 deletions test_apps/encore-app/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration

###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=
###< symfony/framework-bundle ###

###> symfony/ux-google-map ###
# Options available at https://github.com/symfony/ux/blob/2.x/src/Map/src/Bridge/Google/README.md
#
GOOGLE_MAPS_API_KEY="# Get your API key at https://developers.google.com/maps/documentation/javascript/get-api-key"
UX_MAP_DSN=google://%env(GOOGLE_MAPS_API_KEY)%@default
###< symfony/ux-google-map ###

###> symfony/ux-leaflet-map ###
# Options available at https://github.com/symfony/ux/blob/2.x/src/Map/src/Bridge/Leaflet/README.md
#
UX_MAP_DSN=leaflet://default
###< symfony/ux-leaflet-map ###

###> symfony/mercure-notifier ###
# MERCURE_DSN=mercure://default
###< symfony/mercure-notifier ###

###> symfony/mercure-bundle ###
# See https://symfony.com/doc/current/mercure.html#configuration
# The URL of the Mercure hub, used by the app to publish updates (can be a local URL)
MERCURE_URL=https://example.com/.well-known/mercure
# The public URL of the Mercure hub, used by the browser to connect
MERCURE_PUBLIC_URL=https://example.com/.well-known/mercure
# The secret used to sign the JWTs
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
###< symfony/mercure-bundle ###
4 changes: 4 additions & 0 deletions test_apps/encore-app/.env.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

###> symfony/framework-bundle ###
APP_SECRET=ccb30b91aeb20e033fe10056ae0614e9
###< symfony/framework-bundle ###
28 changes: 28 additions & 0 deletions test_apps/encore-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
composer.lock

.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
/node_modules
yarn-error.log

###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###

###> symfony/webpack-encore-bundle ###
/node_modules/
/public/build/
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###
22 changes: 22 additions & 0 deletions test_apps/encore-app/assets/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Welcome to your app's main JavaScript file!
*
* We recommend including the built version of this JavaScript file
* (and its CSS file) in your base layout (base.html.twig).
*/
import { registerVueControllerComponents } from '@symfony/ux-vue';
import { registerSvelteControllerComponents } from '@symfony/ux-svelte';
import { registerReactControllerComponents } from '@symfony/ux-react';
import './bootstrap.js';

// any CSS you import will output into a single css file (app.css in this case)
import './styles/app.css';
import { THIS_FIELD_IS_MISSING, trans } from './translator';

registerReactControllerComponents(require.context('./react/controllers', true, /\.(j|t)sx?$/));
registerSvelteControllerComponents(require.context('./svelte/controllers', true, /\.svelte$/));
registerVueControllerComponents(require.context('./vue/controllers', true, /\.vue$/));

document.addEventListener('DOMContentLoaded', () => {
console.log(trans(THIS_FIELD_IS_MISSING));
})
10 changes: 10 additions & 0 deletions test_apps/encore-app/assets/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { startStimulusApp } from '@symfony/stimulus-bridge';

// Registers Stimulus controllers from controllers.json and in the controllers/ directory
export const app = startStimulusApp(require.context(
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
true,
/\.[jt]sx?$/
));
// register any custom, 3rd party controllers here
// app.register('some_controller_name', SomeImportedController);
123 changes: 123 additions & 0 deletions test_apps/encore-app/assets/controllers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"controllers": {
"@symfony/ux-autocomplete": {
"autocomplete": {
"enabled": true,
"fetch": "eager",
"autoimport": {
"tom-select/dist/css/tom-select.default.css": true,
"tom-select/dist/css/tom-select.bootstrap4.css": false,
"tom-select/dist/css/tom-select.bootstrap5.css": false
}
}
},
"@symfony/ux-chartjs": {
"chart": {
"enabled": true,
"fetch": "eager"
}
},
"@symfony/ux-cropperjs": {
"cropper": {
"enabled": true,
"fetch": "eager",
"autoimport": {
"cropperjs/dist/cropper.min.css": true,
"@symfony/ux-cropperjs/dist/style.min.css": true
}
}
},
"@symfony/ux-dropzone": {
"dropzone": {
"enabled": true,
"fetch": "eager",
"autoimport": {
"@symfony/ux-dropzone/dist/style.min.css": true
}
}
},
"@symfony/ux-google-map": {
"map": {
"enabled": true,
"fetch": "lazy"
}
},
"@symfony/ux-lazy-image": {
"lazy-image": {
"enabled": true,
"fetch": "eager"
}
},
"@symfony/ux-leaflet-map": {
"map": {
"enabled": true,
"fetch": "lazy"
}
},
"@symfony/ux-live-component": {
"live": {
"enabled": true,
"fetch": "eager",
"autoimport": {
"@symfony/ux-live-component/dist/live.min.css": true
}
}
},
"@symfony/ux-notify": {
"notify": {
"enabled": true,
"fetch": "eager"
}
},
"@symfony/ux-react": {
"react": {
"enabled": true,
"fetch": "eager"
}
},
"@symfony/ux-svelte": {
"svelte": {
"enabled": true,
"fetch": "eager"
}
},
"@symfony/ux-swup": {
"swup": {
"enabled": true,
"fetch": "eager"
}
},
"@symfony/ux-toggle-password": {
"toggle-password": {
"enabled": true,
"fetch": "eager",
"autoimport": {
"@symfony/ux-toggle-password/dist/style.min.css": true
}
}
},
"@symfony/ux-turbo": {
"turbo-core": {
"enabled": true,
"fetch": "eager"
},
"mercure-turbo-stream": {
"enabled": false,
"fetch": "eager"
}
},
"@symfony/ux-typed": {
"typed": {
"enabled": true,
"fetch": "eager"
}
},
"@symfony/ux-vue": {
"vue": {
"enabled": true,
"fetch": "eager"
}
}
},
"entrypoints": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
const tokenCheck = /^[-_\/+a-zA-Z0-9]{24,}$/;

// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
document.addEventListener('submit', function (event) {
generateCsrfToken(event.target);
}, true);

// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
document.addEventListener('turbo:submit-start', function (event) {
const h = generateCsrfHeaders(event.detail.formSubmission.formElement);
Object.keys(h).map(function (k) {
event.detail.formSubmission.fetchRequest.headers[k] = h[k];
});
});

// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
document.addEventListener('turbo:submit-end', function (event) {
removeCsrfToken(event.detail.formSubmission.formElement);
});

export function generateCsrfToken (formElement) {
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');

if (!csrfField) {
return;
}

let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
let csrfToken = csrfField.value;

if (!csrfCookie && nameCheck.test(csrfToken)) {
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
}

if (csrfCookie && tokenCheck.test(csrfToken)) {
const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}

export function generateCsrfHeaders (formElement) {
const headers = {};
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');

if (!csrfField) {
return headers;
}

const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');

if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
headers[csrfCookie] = csrfField.value;
}

return headers;
}

export function removeCsrfToken (formElement) {
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');

if (!csrfField) {
return;
}

const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');

if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';

document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}

/* stimulusFetch: 'lazy' */
export default 'csrf-protection-controller';
16 changes: 16 additions & 0 deletions test_apps/encore-app/assets/controllers/hello_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Controller } from '@hotwired/stimulus';

/*
* This is an example Stimulus controller!
*
* Any element with a data-controller="hello" attribute will cause
* this controller to be executed. The name "hello" comes from the filename:
* hello_controller.js -> "hello"
*
* Delete this file or adapt it for your use!
*/
export default class extends Controller {
connect() {
this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
}
}
1 change: 1 addition & 0 deletions test_apps/encore-app/assets/icons/symfony.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions test_apps/encore-app/assets/react/controllers/Hello.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export default function (props) {
return <div>Hello {props.fullName}</div>;
}
3 changes: 3 additions & 0 deletions test_apps/encore-app/assets/styles/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background-color: lightgray;
}
5 changes: 5 additions & 0 deletions test_apps/encore-app/assets/svelte/controllers/Hello.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
export let name = "Svelte";
</script>

<div>Hello {name}</div>
16 changes: 16 additions & 0 deletions test_apps/encore-app/assets/translator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { localeFallbacks } from '../var/translations/configuration';
import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-translator';
/*
* This file is part of the Symfony UX Translator package.
*
* If folder "../var/translations" does not exist, or some translations are missing,
* you must warmup your Symfony cache to refresh JavaScript translations.
*
* If you use TypeScript, you can rename this file to "translator.ts" to take advantage of types checking.
*/

setLocaleFallbacks(localeFallbacks);

export { trans };

export * from '../var/translations';
9 changes: 9 additions & 0 deletions test_apps/encore-app/assets/vue/controllers/Hello.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<template>
<div>Hello {{ name }}!</div>
</template>

<script setup>
defineProps({
name: String
});
</script>
Loading