Google App Script三类Scriptlet的使用差异与选型指南
Google Apps Script 的HTML模板引擎提供的三类Scriptlet没有复杂的抽象概念,核心差异完全围绕「输出行为」和「转义规则」两个维度,结合实际开发场景的选型逻辑可以直接对照落地,没有模糊的适用边界。
1. 标准Scriptlet <? ... ?>
核心特性
无任何HTML输出:执行块内的服务端JavaScript代码后,不会把任何执行结果、代码内容注入到最终渲染的HTML页面中。因为不输出内容,自然不存在内容转义的逻辑。
适用场景
所有不需要直接渲染到页面上的服务端逻辑都应该用这个语法,是所有数据处理、逻辑判断环节的唯一选项。
代码示例
<? // 直接从工作表读取源数据,不需要把查询代码输出到页面 const sheetData = SpreadsheetApp.getActive() .getSheetByName("用户列表") .getDataRange() .getValues(); // 做数据清洗和格式预处理,纯逻辑计算,无输出 const validUserList = sheetData .filter(row => row[1] === "已激活") // 过滤未激活用户 .map(row => ({ userId: row[0], userName: row[2].trim() })); // 根据当前用户权限判断是否显示管理员入口 const isAdmin = Session.getActiveUser().getEmail() === "admin@example.com"; ?>
避坑提示
不要把需要展示的内容写在这类Scriptlet块里,否则内容会只在服务端执行阶段存在,最终渲染的页面上完全看不到。
2. 打印Scriptlet <?= ... ?>
核心特性
执行块内的代码后,会把计算结果自动注入到HTML的对应位置,同时自动启用上下文感知的内容转义——引擎会根据这段代码在HTML中所处的位置(是在文本节点、标签属性、<script>脚本块、还是<style>样式块),自动匹配对应的转义规则,把可能导致代码注入的特殊字符转成对应的HTML实体,从根源上防范XSS安全问题。
适用场景
这是所有动态内容渲染的默认首选,只要是需要把动态变量、计算结果展示到页面上,且内容来源是不可信的(比如用户输入、数据库存储的文本、第三方接口返回的内容),都应该用这个语法,不需要手动做转义处理。
代码示例
<!-- 把前面预处理好的用户列表渲染成页面内容 --> <div class="user-panel"> <? if (isAdmin) { ?> <h3>管理员视图:激活用户列表</h3> <? } else { ?> <h3>普通用户视图:</h3> <? } ?> <ul class="user-list"> <? for (let i = 0; i < validUserList.length; i++) { ?> <li> <!-- 自动转义:如果userName里包含 <img src=x onerror=alert(1)> 这类恶意代码,会被转成纯文本展示,不会触发脚本执行 --> 用户ID:<?= validUserList[i].userId ?>,用户名:<?= validUserList[i].userName ?> </li> <? } ?> </ul> </div> <script> // 给前端JS传用户列表数据,引擎会自动把字符串里的单引号、反斜杠做转义,避免JS语法错误 const pageUserData = <?= validUserList ?>; </script>
避坑提示
不要用这个语法输出已经构建好的完整HTML片段,否则所有<p>、<strong>这类HTML标签都会被转成纯文本字符,页面会显示成满屏的标签代码,而不是正常渲染的内容。
3. 强制打印Scriptlet <?!= ... ?>
核心特性
执行块内的代码后,会把计算结果原封不动注入到HTML对应位置,完全不做任何内容转义,所有特殊字符、HTML标签、脚本内容都会被原样保留。
适用场景
仅在明确确认渲染内容完全可信、且内容本身就是合法HTML结构的时候使用——典型场景包括:服务端生成的、经过全量XSS清洗的富文本内容、已经通过白名单校验的HTML组件片段、固定结构的静态HTML代码。
代码示例
<? // 对用户提交的富文本做全量安全清洗:移除所有script标签、事件属性,仅保留白名单内的合法标签 const cleanedRichContent = doSanitizeRichHtml(userSubmittedContent); ?> <div class="rich-content-container"> <!-- 不做转义,直接输出已经清洗完成的合法HTML,保留<p>、<strong>这类合法标签的结构 --> 用户发布的富文本:<?!= cleanedRichContent ?> </div>
避坑提示
严禁用这个语法直接输出未做安全处理的用户输入、第三方未校验的接口内容,否则恶意脚本会被直接注入到页面中,产生严重的XSS安全漏洞。
三类Scriptlet选型对照速查
- 标准Scriptlet
<? ... ?>:只跑逻辑,不输出内容,所有数据查询、变量定义、循环判断环节用这个 - 打印Scriptlet
<?= ... ?>:默认渲染所有动态内容,自动转义防注入,除非明确不需要转义否则优先用这个 - 强制打印Scriptlet
<?!= ... ?>:仅用来输出经过安全校验的合法HTML片段,不做任何转义
补充说明:很多开发者调试时会在标准Scriptlet块里写
console.log(xxx),这类代码会正常在Apps Script的执行日志中输出,也不会污染最终渲染的HTML,属于标准Scriptlet的合理使用范围。
内容的提问来源于stack exchange,提问作者aerioeus

