惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

雷峰网
雷峰网
GbyAI
GbyAI
Stack Overflow Blog
Stack Overflow Blog
Apple Machine Learning Research
Apple Machine Learning Research
The Cloudflare Blog
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Azure Blog
Microsoft Azure Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 聂微东
L
LangChain Blog
云风的 BLOG
云风的 BLOG
Jina AI
Jina AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
I
InfoQ
大猫的无限游戏
大猫的无限游戏
MyScale Blog
MyScale Blog
人人都是产品经理
人人都是产品经理
小众软件
小众软件
量子位
The GitHub Blog
The GitHub Blog
博客园 - 【当耐特】

皓子的小站

网站字体应用之坑——font-family 篇 糟糕的 meta name="theme-color" How to Automatically Track Newly Supported ESLint Rules in Oxlint How to Gradually Migrate from ESLint to Oxlint (Without Breaking Everything) 静态资源预压缩:零运行时开销,极致节省带宽 CVE-2025-70886 Proof of Concept (PoC) | A Script to Crash Halo CMS Comment Backend 自定义网页鼠标指针——一段曲折的旅程 CVE-2025-70886 漏洞复现/PoC | 一个脚本让 Halo CMS 评论后台瘫痪 2025 年终总结 & 博客两周年 老用户专享!已有 Halo 专业版授权可免费升级商业版 自动追踪 Oxlint 对 ESLint 规则的新增支持 Halo 贡献者证书与实体周边盲盒开箱 博客评论系统指南 CDN 回源跟随配置导致登录异常问题排查 SCDN 免费赞助计划:助力高质量博客&博客圈 锐捷校园网:网络共享与带宽叠加方案(哈理工案例) ESLint 到 Oxlint 渐进式迁移快速上手指南 解决 Vite 破坏 Thymeleaf 模板内联 JS & CSS 的方法 我的博客,为什么是月更? 博客俱乐部一周年纪念品开箱 网站字体加载之坑——format 篇 经历 1000000000 次 DDoS 请求攻击后,我总结了三条经验 题解分享:[AtCoder Beginner Contest 414 E] Count A%B=C 题解分享:[蓝桥杯 2025 国 Python A] 杨辉三角 P12876 FiF口语训练破解刷分教程(适用于 Windows) 不要与蠢人辩论 小心网络地雷·续篇 当心网络组织“开往”·续篇 当心网络组织“开往” 小心网络地雷
Fixing Vite Breaking Inline JS & CSS in Thymeleaf Tem...
HowieHz · 2025-09-07 · via 皓子的小站

Introduction

Thymeleaf has a concept called natural templates. As mentioned in the official docs, this includes JavaScript natural templates and CSS natural templates.

Here’s an example from the documentation:

<script th:inline="javascript">
    var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";
</script>

<style th:inline="css">
    .main\ elems {
      text-align: /*[[${align}]]*/ left;
    }
</style>

As you can see, natural templates are implemented using comments. But when you use Vite and treat the template HTML as an entry point (by specifying it in vite.config.ts via build.rollupOptions.input)—so you can apply things like Tailwind CSS class name mangling, tree-shaking, minification, and CSS splitting—you’ll run into the problem of Vite breaking Thymeleaf’s natural templates.

To deal with this, here are some practical solutions.


The Solutions

We’ll group the inline blocks we want to skip into three categories:

  1. Inline <script> without type="module".
  2. Inline <script> with type="module".
  3. Inline <style>.

Inline <script> Without type="module"

According to the Vite docs, <script src> isn’t processed by Vite, so you don’t need to do anything special here:

<script th:inline="javascript">
    var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";
</script>

Inline <script> With type="module"

From the Vite docs, <script type="module" src> is processed. But there’s a built-in way to skip it: just add the vite-ignore attribute.

Example:

<script vite-ignore type="module" th:inline="javascript">
    var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";
</script>

Inline <style>

Vite doesn’t provide a way to skip inline <style> blocks. I experimented with writing a plugin that runs as early as possible, but the contents still ended up minified.

Luckily, Thymeleaf has something called prototype-only comment blocks:

<span>hello!</span>
<!--/*/
  <div th:text="${...}">
    ...
  </div>
/*/-->
<span>goodbye!</span>

These blocks are treated as comments in static mode, but when Thymeleaf processes the template, the markers <!--/*/ and /*/--> are stripped and the contents are preserved as real markup.

That means we can wrap our inline <style> with these markers, and Vite will leave it alone (as long as you’re not running an HTML minifier):

<!--/*/
<style th:inline="css">
    .main\ elems {
      text-align: /*[[${align}]]*/ left;
    }
</style>
/*/-->

If you are using an HTML minifier, you might need to write a custom Vite plugin to prevent it from messing with these special comments. Here’s a plugin example (not guaranteed, but works as a reference):

Vite Plugin to Handle HTML Minify

Place the plugin in ./plugins/vite-plugin-html-ignore-block.ts, then register it in your vite.config.ts:

import vitePluginHtmlIgnoreBlock from './plugins/vite-plugin-html-ignore-block';

export default defineConfig({
  // ...other config
  plugins: [
    vitePluginHtmlIgnoreBlock(),
    // ...other plugins
  ],
});

Plugin code:

import type { Plugin } from 'vite';

export default function vitePluginHtmlIgnoreBlock(): Plugin[] {
  const rawMap = new Map<string, string>();
  let idx = 0;

  // Match <!--/*/ ... /*/-->, supports multiline
  const blockReg = /<!--\/\*\/([\s\S]*?)\/\*\/-->/g;

  // Pre stage: replace with <ignore-N></ignore-N>
  const pre: Plugin = {
    name: 'vite-plugin-html-ignore-block-pre',
    enforce: 'pre',
    transformIndexHtml: {
      order: 'pre',
      handler(html) {
        return html.replace(blockReg, (match) => {
          const tag = `ignore-${idx}`;
          rawMap.set(tag, match);
          idx++;
          return `<${tag}></${tag}>`;
        });
      }
    }
  };

  // Post stage: restore original content
  const post: Plugin = {
    name: 'vite-plugin-html-ignore-block-post',
    enforce: 'post',
    transformIndexHtml: {
      order: 'post',
      handler(html) {
        let result = html;
        for (const [tag, raw] of rawMap.entries()) {
          const reg = new RegExp(`<${tag}></${tag}>`, 'g');
          result = result.replace(reg, raw);
        }
        return result;
      }
    }
  };

  return [pre, post];
}

Final Notes

That’s it! Hopefully these approaches save you some frustration when combining Vite with Thymeleaf natural templates.

Feel free to drop a comment if you’ve got improvements or run into edge cases. And of course—happy coding!