javascriptgoogle-sheetsgoogle-apps-script

Apps Script: eval(include("FilterUICode")) fails with SyntaxError: expected expression, got '<' when injecting JS from separate file


I'm building a Google Sheets-bound Apps Script add-on with a custom sidebar (FilterUI.html). I'm trying to factor out the JavaScript into a separate file (FilterUICode.html) and include it like this:

<script>
  const raw = <?= JSON.stringify(include("FilterUICode")) ?>;
  document.getElementById("dump").textContent = raw;
  eval(raw);
</script>


i also tried escaping with '!'>

const raw = <?!= JSON.stringify(include("FilterUICode")) ?>;

In FilterUICode.html I have something very simple:

console.log("✅ Script loaded");
document.addEventListener("DOMContentLoaded", () => {
  const dbg = document.getElementById("debug");
  if (dbg) {
    dbg.style.display = "block";
    dbg.textContent += "✅ JS ran\n";
  }
});

when i run my script, i consistently get:

Uncaught SyntaxError: expected expression, got '<'

There are no or HTML tags in the external FilterUICode.html, just plain JavaScript.

I've already tried:

Forcing clasp push --force from a clean local copy

Verifying include() uses HtmlService.createHtmlOutputFromFile(...)

Confirming that raw does not contain any leading <

Ctrl+F5 to bypass browser cache

Reloading the script editor manually

This setup is minimal and should work in principle. The bug only occurs when using include() + eval(...). If I inline the JS in FilterUI.html, everything works fine.

Has anyone encountered this or found a workaround?

-------------------- complete src for mini project----

----Code.js

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('🔧 Tools')
    .addItem('Open Filter UI', 'showSidebar')
    .addToUi();
}

function showSidebar() {
  const html = HtmlService.createHtmlOutputFromFile('FilterUI')
    .setTitle('Filter by Distance')
    .setWidth(400);
  SpreadsheetApp.getUi().showSidebar(html);
}

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

---FilterUI.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
      body {
        font-family: sans-serif;
        padding: 1em;
        max-width: 400px;
        box-sizing: border-box;
      }
      pre#debug {
        background: #f0f0f0;
        padding: 10px;
        font-size: 12px;
        white-space: pre-wrap;
        margin-top: 20px;
        border-radius: 4px;
      }
    </style>
  </head>
  <body>
    <h2>Minimal Factored JS Test</h2>
    <pre id="debug">[INIT] HTML loaded\n</pre>

    <script>
      const raw = <?= JSON.stringify(include("FilterUICode")) ?>;
      document.getElementById("debug").textContent += "\n[DEBUG] Injected script length: " + raw.length;
      try {
        eval(raw);
      } catch (err) {
        console.error("❌ Eval failed:", err);
        alert("❌ Error evaluating script: " + err.message);
      }
    </script>
  </body>
</html>

---- FilterUICode.html

function log(msg) {
  const dbg = document.getElementById("debug");
  if (dbg) dbg.textContent += `\n[LOG ${new Date().toISOString()}] ${msg}`;
  console.log(msg);
}

log("FilterUICode: TOP LEVEL start");

document.addEventListener("DOMContentLoaded", () => {
  log("✅ DOMContentLoaded fired");
});

window.addEventListener("load", () => {
  log("✅ window load fired");
});

log("FilterUICode: BOTTOM LEVEL end");


Solution

  • If my understanding of your expected result is correct, it is required to modify createHtmlOutputFromFile to createTemplateFromFile. So, please modify your showing script as follows and test it again.

    Code.js:

    From:

    function showSidebar() {
      const html = HtmlService.createHtmlOutputFromFile('FilterUI')
        .setTitle('Filter by Distance')
        .setWidth(400);
      SpreadsheetApp.getUi().showSidebar(html);
    }
    

    To:

    function showSidebar() {
      const html = HtmlService.createTemplateFromFile('FilterUI').evaluate()
        .setTitle('Filter by Distance')
        .setWidth(400);
      SpreadsheetApp.getUi().showSidebar(html);
    }
    

    FilterUI.html:

    From:

    const raw = <?= JSON.stringify(include("FilterUICode")) ?>;
    

    To:

    const raw = <?!= JSON.stringify(include("FilterUICode")) ?>;
    

    Reference: