Search code examples
javascripthtmlgoogle-apps-scriptweb-applicationssource-maps

Mapping client code to source code is not working


I am creating a simple web app using a Google Apps Script, I want to add the client code to the source code so that I can use it for debugging if I have errors. etc

UPDATE: STRUCTURE OF MY CODE in editor

enter image description here

Here is my codes.

code.gs

function doGet(){
  return HtmlService.createTemplateFromFile('index')
  .evaluate()
  .setTitle('APP')
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <?!= include('importsJs'); ?>
  <?!= include('other/employee/scripts'); ?>
  <?!= include('other/jobs/scripts'); ?>
  <title>Document</title>
</head>
<body>
  
</body>
</html>

I have learned on the Internet about mapping client code to source code in dev tools, I just need to add //# sourceURL = filename.js

employees html file (other/employee/scripts) looks like this

<script>
var employees = [{ name: 'Charlie', position: 'Sales' }];

console.log(employees)

//# sourceURL=employee.js
</script>

and jobs html file (other/jobs/scripts) looks like this

<script>
var employees = [{ name: 'Charlie', position: 'Sales' }];

console.log(employees)

//# sourceURL=employee.js
</script>

Here is the source code in the editor demo

The expected result is when you click usercodepanel it should show me the line of code which give the console log results, for now when you click it's empty :( very bad for debugging

enter image description here

Live demo

NOTE: I am loading JavaScript files dynamically using the doT.js library.

UPDATE: on my local machine, the source code looks like this, am using clasp to deploy my project

source codes

The solution above does not give me what I want, what do I need to do to get the expected results?


Solution

  • The issue you are encountering is that, upon deployment, App Script removes all static comments in your HTML, CSS, and JavaScript in order to optimize your site. But truthfully, it does this too aggressively and naively, removing any appearance of "// ..." in the code, even if it's located inside a template string, where it's actually valid.

    As a result, there is no simple toggle to just make it work, but there is a very hacky workaround. Do not include "//" statically in your code, instead, dynamically insert it with your scripts into the HTML as the page loads. To do so, we can write a helper function in App Script called importScript() that outputs a script that itself injects a script with the sourceURL and your code into the HTML.

    Here is the implementation of importScript():

    // in Code.gs
    function includeFile(filename) {
      return HtmlService.createHtmlOutputFromFile(filename)
          .getContent();
    }
    
    function stripScriptTags(script) {
      const OPEN_TAG = "<script>",
            CLOSE_TAG = "</script>",
            start = script.indexOf(OPEN_TAG),
            end = script.lastIndexOf(CLOSE_TAG);
      if (start === -1 || end === -1) throw new Error("Encountered malformed script");
      return script.slice(start + OPEN_TAG.length, end).trim();
    }
    
    function includeScript(filename, sourceURL) {
      const scriptContent = stripScriptTags(includeFile(filename));
      const escapedScriptStr = scriptContent.replace(/[`$]/g, '\\$&');
    
      return `
        <script>
          var newScript = document.createElement("script");
          newScript.text = "!!# sourceURL=${sourceURL}".replaceAll("!","/"); 
          newScript.text += "\\n" + \`${escapedScriptStr}\`;
          document.currentScript.insertAdjacentElement("afterend", newScript);
        </script>
      `;
    }
    

    To use it, simply update your Index.html to call includeScript() with the your scripts' filenames and the source URLs you want to use:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <?!= includeScript('other/jobs/scripts', 'jobs.js'); ?>
        <?!= includeScript('other/employee/scripts', 'employee.js'); ?>
        <title>Document</title>
      </head>
      <body>
      </body>
    </html>
    

    Then just deploy your App Script, and now you should see the following in the console: enter image description here

    Here is my own App Script that demos it working with sourceURL.

    NOTE: The method above works if the imported scripts only contain a single script tag. Otherwise, all other HTML in the file gets truncated.

    However, here is a more complicated version of importScripts() which works with arbitrary HTML (multiple tags) in the files, and a demo:

    function includeScript(filename, sourceURL) {
      scriptContent = includeFile(filename)
        .replace(/<script>/g, '<script data-to-clone>')
        .replace(/<\/script>/g, '<\\/script>')
        .replace(/[`$]/g, '\\$&').trim();
    
      return `
        <script>
          document.currentScript.insertAdjacentHTML("afterend", \`${scriptContent}\`);
        </script>
        <script>
          var _temp = Array.from(document.querySelectorAll("script[data-to-clone]"));
          _temp.reverse();
          _temp.forEach((scriptClone) => {
            scriptClone.removeAttribute("data-to-clone");
    
            const newScript = document.createElement("script");
            newScript.text = "/" + "/# sourceURL=${sourceURL}";
            newScript.text += "\\n" + scriptClone.text.trim();
            document.currentScript.insertAdjacentElement("afterend", newScript);
    
            scriptClone.setAttribute("data-is-cloned", "");
          });
        </script>
      `;
    }