跳转至

CSP(内容安全策略)构建版本

为了让 Alpine 能够从 HTML 属性(如 x-on:click="console.log()")中执行 JavaScript 表达式,它需要使用一些实用工具,这些工具违反了一些应用程序出于安全目的而执行的"unsafe-eval"内容安全策略

在底层,Alpine 实际上并不使用 eval(),因为它既慢又问题多。相反,它使用 Function 声明,这种方式要好得多,但仍然违反了"unsafe-eval"。

Alpine 提供了一种不违反"unsafe-eval"的替代构建版本,并支持 Alpine 大部分的内联表达式语法。

安装

你可以通过 <script> 标签引入或通过 NPM 安装来使用此构建版本:

通过 CDN

你可以像使用标准 Alpine 构建版本一样,将此构建版本的 CDN 作为 <script> 标签引入:

<!-- Alpine 的 CSP 友好核心 -->
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/csp@3.x.x/dist/cdn.min.js"></script>

通过 NPM

你也可以从 NPM 安装此构建版本,以便在你的打包工具中使用:

npm install @alpinejs/csp

然后从你的打包工具中初始化它:

import Alpine from '@alpinejs/csp'

window.Alpine = Alpine

Alpine.start()

基本示例

以下是使用 Alpine CSP 构建版本的计数器组件示例。请注意,大多数表达式的使用方式与常规 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.x.x/dist/cdn.min.js"></script>
    </head>
    <body>
        <div x-data="{ count: 0, message: 'Hello' }">
            <button x-on:click="count++">Increment</button>
            <button x-on:click="count = 0">Reset</button>

            <span x-text="count"></span>
            <span x-text="message + ' World'"></span>
            <span x-show="count > 5">Count is greater than 5!</span>
        </div>
    </body>
</html>

支持的功能

CSP 构建版本支持你在 Alpine 中可能用到的大多数 JavaScript 表达式:

对象和数组字面量

<!-- ✅ 这些可以工作 -->
<div x-data="{ user: { name: 'John', 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 ? 'Yes' : 'No'"></span>
    <span x-text="'Hello ' + name"></span>
    <div x-show="!loading && count > 0"></div>
</div>

赋值和更新

<!-- ✅ 这些可以工作 -->
<div x-data="{ count: 0, user: { name: '' } }">
    <button x-on:click="count++">Increment</button>
    <button x-on:click="count = 0">Reset</button>
    <input x-model="user.name">
</div>

方法调用

<!-- ✅ 这些可以工作 -->
<div x-data="{ items: ['a', 'b'] }">
    <button x-on:click="items.push('c')">Add Item</button>
</div>

不支持的功能

一些高级且可能有危险的 JavaScript 功能不受支持:

复杂表达式

<!-- ❌ 这些不工作 -->
<div x-data="{ user: { name: '' } }">
    <!-- 属性赋值 -->
    <button x-on:click="user.name = 'John'">Bad</button>

    <!-- 箭头函数 -->
    <button x-on:click="() => console.log('hi')">Bad</button>

    <!-- 解构 -->
    <div x-text="{ name } = user">Bad</div>

    <!-- 模板字面量 -->
    <div x-text="`Hello ${name}`">Bad</div>

    <!-- 展开运算符 -->
    <div x-data="{ ...defaults }">Bad</div>
</div>

全局变量和函数

<!-- ❌ 这些不工作 -->
<div x-data>
    <button x-on:click="console.log('hi')"></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>

HTML 注入

<!-- ❌ 这些不工作 -->
<div x-data="{ message: 'Hello <span>World</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 CSP 构建版本配合使用的 CSP 头部示例:

Content-Security-Policy: default-src 'self'; script-src 'nonce-[random]' 'strict-dynamic';

关键在于从你的 script-src 指令中移除 'unsafe-eval',同时仍然允许基于 nonce 的脚本运行。