Политика безопасности содержимого (CSP)
Чтобы Alpine мог выполнять выражения JavaScript из HTML-атрибутов, таких как x-on:click="console.log()", ему необходимо использовать утилиты, которые нарушают политику безопасности содержимого unsafe-eval Content Security Policy, применяемую некоторыми приложениями в целях безопасности.
Alpine предлагает альтернативную сборку, которая не нарушает unsafe-eval и поддерживает большую часть синтаксиса встроенных выражений Alpine.
Вы можете использовать эту сборку, включив её в тег <script> или установив с помощью менеджера пакетов.
Вы можете включить CDN этой сборки в виде тега <script>, как это делается в стандартной сборке Alpine:
<!-- Ядро Alpine, дружественное к CSP --><script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/csp@3/dist/cdn.min.js"></script>Вы можете установить эту сборку для использования внутри вашего пакета следующим образом:
npm add @alpinejs/csppnpm add @alpinejs/cspyarn add @alpinejs/cspЗатем инициализируйте её из вашего пакета:
import Alpine from '@alpinejs/csp';
window.Alpine = Alpine;window.Alpine.start();Вот рабочий компонент счётчика с использованием CSP-сборки Alpine. Обратите внимание, что большинство выражений работают точно так же, как в обычном Alpine:
<html> <head> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-a23gbfz9e'" /> <script defer nonce="a23gbfz9e" src="https://cdn.jsdelivr.net/npm/@alpinejs/csp@3/dist/cdn.min.js" ></script> </head> <body> <div x-data="{ count: 0, message: 'Привет' }"> <button x-on:click="count++"><span x-text="count"></span> Увеличить</button> <button x-on:click="count = 0">Сбросить</button> <div> <span x-text="message + ', мир!'"></span> <span x-show="count > 5">Число больше 5!</span> </div> </div> </body></html>CSP-сборка поддерживает большинство выражений JavaScript, которые вы захотите использовать в Alpine:
<!-- ✅ Работают --><div x-data="{ user: { name: 'Вася', age: 30 }, items: [1, 2, 3] }"> <span x-text="user.name"></span> <span x-text="items[0]"></span></div><!-- ✅ Работают --><div x-data="{ count: 5, name: 'Alpine' }"> <span x-text="count + 10"></span> <span x-text="count > 3"></span> <span x-text="count === 5 ? 'Да' : 'Нет'"></span> <span x-text="'Привет, ' + name"></span> <div x-show="!loading && count > 0"></div></div><!-- ✅ Работают --><div x-data="{ count: 0, user: { name: '' } }"> <button x-on:click="count++">Увеличить</button> <button x-on:click="count = 0">Сбросить</button> <input x-model="user.name"></div><!-- ✅ Работают --><div x-data="{ items: ['a', 'b'] }"> <button x-on:click="items.push('c')">Добавить пункт</button></div>Некоторые расширенные и потенциально опасные возможности JavaScript не поддерживаются:
<!-- ❌ Не работают --><div x-data="{ user: { name: '' } }"> <!-- Назначение свойств --> <button x-on:click="user.name = 'Жека'">Плохо</button>
<!-- Стрелочные функции --> <button x-on:click="() => console.log('hi')">Плохо</button>
<!-- Деструктуризация --> <div x-text="{ name } = user">Плохо</div>
<!-- Шаблонные строки --> <div x-text="`Привет, ${name}`">Плохо</div>
<!-- Оператор spread --> <div x-data="{ ...defaults }">Плохо</div></div><!-- ✅ Работают --><div x-data> <button x-on:click="console.log('привет')"></button> <span x-text="document.title"></span> <span x-text="window.innerWidth"></span> <span x-text="Math.max(count, 100)"></span> <span x-text="parseInt('123') + count"></span> <span x-text="JSON.stringify({ value: count })"></span></div><!-- ❌ Не работают --><div x-data="{ message: 'Привет, <span>мир</span>' }"> <span x-html="message"></span> <span x-init="$el.insertAdjacentHTML('beforeend', message)"></span></div>Хотя CSP-сборка поддерживает простые встроенные выражения, сложную логику лучше выносить в отдельные функции или компоненты через Alpine.data() для лучшей организации:
<!-- Вместо этого --><div x-data="{ users: [] }" x-show="users.filter(u => u.active && u.role === 'admin').length > 0"><!-- Делайте так --><div x-data="userManager" x-show="hasActiveAdmins">
<script nonce="..."> Alpine.data('userManager', () => ({ users: [],
get hasActiveAdmins() { return this.users.filter(u => u.active && u.role === 'admin').length > 0 } }))</script>Такой подход делает ваш код более читаемым, тестируемым и сопровождаемым, особенно когда речь идет о сложных приложениях.
Вот пример заголовка CSP, который совместим со сборкой Alpine.js для CSP:
Content-Security-Policy: default-src 'self'; script-src 'nonce-[random]' 'strict-dynamic';Ключевой момент — это удаление директивы unsafe-eval из вашей директивы script-src, при этом сохраняя возможность выполнения ваших скриптов, основанных на одноразовых значениях (nonce).