Svelte - Carbon component import breaks debugging

546 views Asked by At

I've setup a Svelte and Electron that uses Carbon Components. I have it working for the most part, but I'm stuck at debugging.

Here's a sample component I'm using for testing:

<script lang="ts">
    import { Tile } from 'carbon-components-svelte'
    let name = 'John Doe'

    function test() {
        console.log('You clicked me!')
    }
</script>

<div>
    <Tile>
        <h2>{name}</h2>
        <button on:click={test}>Click me</button>
    </Tile>
</div>

When I load the component into a dev environment, I can't set breakpoints in Chrome's dev tools:

enter image description here

However, if I open my svelte.config.js, which initially look:

import sveltePreprocess from 'svelte-preprocess'
import { optimizeImports } from "carbon-preprocess-svelte";

export default {
  preprocess: [ sveltePreprocess(), optimizeImports() ]
}

And remove the optimizeImports() call, then breakpoints work correctly.

Does anyone have any clue why this is happening? Clearly it's something to do with the carbon-preprocess-svelte library. I created a basic project that reproduces the issue: https://github.com/troncoso/carbon-svelte-bug

2

There are 2 answers

1
hackape On BEST ANSWER

TL;DR: The inline source map is kinda broken, it's a bug in carbon-preprocess-svelte.


When you use: import { Tile } from 'carbon-components-svelte', the default module resolution will import the named export Tile from a big umbrella file at carbon-components-svelte/lib/index.js, which is a big minified code bundle. Clearly this is not optimal.

optimizeImports from carbon-preprocess-svelte is a preprocessor that tries to optimize it by reading your import code then rewriting it into sth like import Tile from "carbon-components-svelte/src/Tile/Tile.svelte".

Obviously there's some kind of bug in this preprocessor, that produces a broken source map. To be precise, it produces a source map that misses out some mapping positions. Chrome devtool relies on these mapping positions to set a breakpoint. When it's missing, it cannot set one.

When you manually rewrite the import, you skip this preprocessor, thus you avoid the wrong behavior. This is consistent to your current finding.

We might rest our case here and conclude that you should avoid using optimizeImports for now, and just report the bug to carbon team. Below will dive into more details.


This is the inline source map of the bad case:

//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7T0FDQSxJQUFBLE1BQUEsK0NBQUE7Ozs7Ozs7Ozs7Ozs7O2FBUThCLFVBQ2xCO3VCQUFDLEdBQUs7Ozs7O0dBRGhCLFVBRVE7Ozs7O3dEQUZVLEdBQVM7Ozs7O3VEQUNoQixHQUFLOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztLQVJsQixLQUFBLEdBQUEsQ0FBQTs7T0FDQSxTQUFBO2tCQUNBLEtBQUEsSUFBQSxDQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7IiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIkNvdW50ZXIuc3ZlbHRlIl0sInNvdXJjZXNDb250ZW50IjpbIjxzY3JpcHQgbGFuZz1cInRzXCI+XG4gIGltcG9ydCBUaWxlIGZyb20gJ2NhcmJvbi1jb21wb25lbnRzLXN2ZWx0ZS9zcmMvVGlsZS9UaWxlLnN2ZWx0ZSdcbiAgbGV0IGNvdW50OiBudW1iZXIgPSAwXG4gIGNvbnN0IGluY3JlbWVudCA9ICgpID0+IHtcbiAgICBjb3VudCArPSAxXG4gIH1cbjwvc2NyaXB0PlxuXG48VGlsZT5cbiAgPGJ1dHRvbiBvbjpjbGljaz17aW5jcmVtZW50fT5cbiAgICBDbGlja3M6IHtjb3VudH1cbiAgPC9idXR0b24+XG48L1RpbGU+XG5cbjxzdHlsZT5cbiAgYnV0dG9uIHtcbiAgICBmb250LWZhbWlseTogaW5oZXJpdDtcbiAgICBmb250LXNpemU6IGluaGVyaXQ7XG4gICAgcGFkZGluZzogMWVtIDJlbTtcbiAgICBjb2xvcjogI2ZmM2UwMDtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDI1NSwgNjIsIDAsIDAuMSk7XG4gICAgYm9yZGVyLXJhZGl1czogMmVtO1xuICAgIGJvcmRlcjogMnB4IHNvbGlkIHJnYmEoMjU1LCA2MiwgMCwgMCk7XG4gICAgb3V0bGluZTogbm9uZTtcbiAgICB3aWR0aDogMjAwcHg7XG4gICAgZm9udC12YXJpYW50LW51bWVyaWM6IHRhYnVsYXItbnVtcztcbiAgICBjdXJzb3I6IHBvaW50ZXI7XG4gIH1cblxuICBidXR0b246Zm9jdXMge1xuICAgIGJvcmRlcjogMnB4IHNvbGlkICNmZjNlMDA7XG4gIH1cblxuICBidXR0b246YWN0aXZlIHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDI1NSwgNjIsIDAsIDAuMik7XG4gIH1cbjwvc3R5bGU+XG4iXX0=

And here's the one of the good case:

//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7T0FDQSxJQUFBLE1BQUEsK0NBQUE7Ozs7Ozs7Ozs7Ozs7O2FBUThCLFVBQ2xCO3VCQUFDLEdBQUs7Ozs7O0dBRGhCLFVBRVE7Ozs7O3dEQUZVLEdBQVM7Ozs7O3VEQUNoQixHQUFLOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztLQVJsQixLQUFBLEdBQUEsQ0FBQTs7T0FDQSxTQUFBO2tCQUNBLEtBQUEsSUFBQSxDQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7IiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIkNvdW50ZXIuc3ZlbHRlIl0sInNvdXJjZXNDb250ZW50IjpbIjxzY3JpcHQgbGFuZz1cInRzXCI+XG4gIGltcG9ydCBUaWxlIGZyb20gJ2NhcmJvbi1jb21wb25lbnRzLXN2ZWx0ZS9zcmMvVGlsZS9UaWxlLnN2ZWx0ZSdcbiAgbGV0IGNvdW50OiBudW1iZXIgPSAwXG4gIGNvbnN0IGluY3JlbWVudCA9ICgpID0+IHtcbiAgICBjb3VudCArPSAxXG4gIH1cbjwvc2NyaXB0PlxuXG48VGlsZT5cbiAgPGJ1dHRvbiBvbjpjbGljaz17aW5jcmVtZW50fT5cbiAgICBDbGlja3M6IHtjb3VudH1cbiAgPC9idXR0b24+XG48L1RpbGU+XG5cbjxzdHlsZT5cbiAgYnV0dG9uIHtcbiAgICBmb250LWZhbWlseTogaW5oZXJpdDtcbiAgICBmb250LXNpemU6IGluaGVyaXQ7XG4gICAgcGFkZGluZzogMWVtIDJlbTtcbiAgICBjb2xvcjogI2ZmM2UwMDtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDI1NSwgNjIsIDAsIDAuMSk7XG4gICAgYm9yZGVyLXJhZGl1czogMmVtO1xuICAgIGJvcmRlcjogMnB4IHNvbGlkIHJnYmEoMjU1LCA2MiwgMCwgMCk7XG4gICAgb3V0bGluZTogbm9uZTtcbiAgICB3aWR0aDogMjAwcHg7XG4gICAgZm9udC12YXJpYW50LW51bWVyaWM6IHRhYnVsYXItbnVtcztcbiAgICBjdXJzb3I6IHBvaW50ZXI7XG4gIH1cblxuICBidXR0b246Zm9jdXMge1xuICAgIGJvcmRlcjogMnB4IHNvbGlkICNmZjNlMDA7XG4gIH1cblxuICBidXR0b246YWN0aXZlIHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDI1NSwgNjIsIDAsIDAuMik7XG4gIH1cbjwvc3R5bGU+XG4iXX0=

They're base64 encoded json, if we decoded them, we get:

bad case:

{
  "version": 3,
  "mappings": ";;;;;;;;;;;;;;;;;;;;;;;OAAiB,IAAA,MAAA,+CAAA;;;;;;;;;;;;;;aASa,UAClB;uBAAC,GAAK;;;;;GADhB,UAEQ;;;;;wDAFU,GAAS;;;;;uDAChB,GAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
  "names": [],
  "sources": ["Counter.svelte"],
  "sourcesContent": [
    "<script lang=\"ts\">\n  import { Tile } from 'carbon-components-svelte'\n  let count: number = 0\n  const increment = () => {\n    count += 1\n  }\n</script>\n\n<Tile>\n  <button on:click={increment}>\n    Clicks: {count}\n  </button>\n</Tile>\n\n<style>\n  button {\n    font-family: inherit;\n    font-size: inherit;\n    padding: 1em 2em;\n    color: #ff3e00;\n    background-color: rgba(255, 62, 0, 0.1);\n    border-radius: 2em;\n    border: 2px solid rgba(255, 62, 0, 0);\n    outline: none;\n    width: 200px;\n    font-variant-numeric: tabular-nums;\n    cursor: pointer;\n  }\n\n  button:focus {\n    border: 2px solid #ff3e00;\n  }\n\n  button:active {\n    background-color: rgba(255, 62, 0, 0.2);\n  }\n</style>\n"
  ]
}

good case:

{
  "version": 3,
  "mappings": ";;;;;;;;;;;;;;;;;;;;;;;OACA,IAAA,MAAA,+CAAA;;;;;;;;;;;;;;aAQ8B,UAClB;uBAAC,GAAK;;;;;GADhB,UAEQ;;;;;wDAFU,GAAS;;;;;uDAChB,GAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KARlB,KAAA,GAAA,CAAA;;OACA,SAAA;kBACA,KAAA,IAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
  "names": [],
  "sources": ["Counter.svelte"],
  "sourcesContent": [
    "<script lang=\"ts\">\n  import Tile from 'carbon-components-svelte/src/Tile/Tile.svelte'\n  let count: number = 0\n  const increment = () => {\n    count += 1\n  }\n</script>\n\n<Tile>\n  <button on:click={increment}>\n    Clicks: {count}\n  </button>\n</Tile>\n\n<style>\n  button {\n    font-family: inherit;\n    font-size: inherit;\n    padding: 1em 2em;\n    color: #ff3e00;\n    background-color: rgba(255, 62, 0, 0.1);\n    border-radius: 2em;\n    border: 2px solid rgba(255, 62, 0, 0);\n    outline: none;\n    width: 200px;\n    font-variant-numeric: tabular-nums;\n    cursor: pointer;\n  }\n\n  button:focus {\n    border: 2px solid #ff3e00;\n  }\n\n  button:active {\n    background-color: rgba(255, 62, 0, 0.2);\n  }\n</style>\n"
  ]
}

Problem lies in the mappings field. You can make sense of both files with the source map decoder then compare by clicking the dump button. You'll see that some mapping information are simply missing in the bad case file.

This article Anatomy of source maps, if it interests you, will guide you through the technical details of the js source map format (to understand the mysterious ;;OAAiB,IAAA,MAAA,+CAAA;; part).

But for now let's simply read the human-friendly dump:

Counter.svelte,137,27,5,0,null

translates to:

position 137:27 from compiled file `Counter.js`
maps to 
position 5:0 from original source file `Counter.svelte`

And here's the dump for both files.

bad case mapping dump:

Counter.svelte,24,7,1,17,null
Counter.svelte,24,11,1,17,null
Counter.svelte,24,17,1,17,null
Counter.svelte,24,64,1,17,null
Counter.svelte,38,13,10,30,null
Counter.svelte,38,23,11,12,null
Counter.svelte,39,23,11,13,null
Counter.svelte,39,26,11,18,null
Counter.svelte,44,3,10,2,null
Counter.svelte,44,13,12,10,null
Counter.svelte,49,56,10,20,null
Counter.svelte,49,59,10,29,null
Counter.svelte,54,55,11,13,null
Counter.svelte,54,58,11,18,null

good case mapping dump:

Counter.svelte,24,7,2,0,null
Counter.svelte,24,11,2,0,null
Counter.svelte,24,17,2,0,null
Counter.svelte,24,64,2,0,null
Counter.svelte,38,13,10,30,null
Counter.svelte,38,23,11,12,null
Counter.svelte,39,23,11,13,null
Counter.svelte,39,26,11,18,null
Counter.svelte,44,3,10,2,null
Counter.svelte,44,13,12,10,null
Counter.svelte,49,56,10,20,null
Counter.svelte,49,59,10,29,null
Counter.svelte,54,55,11,13,null
Counter.svelte,54,58,11,18,null
Counter.svelte,134,5,3,0,null
Counter.svelte,134,10,3,0,null
Counter.svelte,134,13,3,0,null
Counter.svelte,134,14,3,0,null
Counter.svelte,136,7,4,0,null
Counter.svelte,136,16,4,0,null
Counter.svelte,137,18,5,0,null
Counter.svelte,137,23,5,0,null
Counter.svelte,137,27,5,0,null
Counter.svelte,137,28,5,0,null
0
Troncoso On

Read @hackape's answer for details. But ultimately it seems like a bug with carbon-preprocess-svelte, specifically the optimizeImports() plugin. I created an issue on that project: https://github.com/carbon-design-system/carbon-preprocess-svelte/issues/18

In the meantime, I came up with a solution that made sure the source maps work and I still get tree shaking. I created a carbon.ts file:

import { Button } from 'carbon-components-svelte/src/Button'
import { Tile, ClickableTile } from 'carbon-components-svelte/src/Tile'
import { StructuredList, StructuredListHead, StructuredListBody, StructuredListRow,
    StructuredListCell } from 'carbon-components-svelte/src/StructuredList'
import { Header, HeaderGlobalAction, HeaderUtilities, Content }
    from 'carbon-components-svelte/src/UIShell'
import { Theme } from 'carbon-components-svelte/src/Theme'
import { TooltipDefinition } from 'carbon-components-svelte/src/TooltipDefinition'

export  {
    Button,
    Tile,
    ClickableTile,
    StructuredList,
    StructuredListBody,
    StructuredListCell,
    StructuredListHead,
    StructuredListRow,
    Header,
    HeaderGlobalAction,
    HeaderUtilities,
    Theme,
    TooltipDefinition,
    Content
}

When I need a component, I just add it to this file. Then I import the component like this:

<script lang="ts">
    import { Button } from '@/components/carbon'
</script>

I know I could have just done the imports in the svelte files themselves, but this way when the bug is fixed I can much more easily find and replace all the imports. Just search for "@/components/carbon" and replace it with "carbon-components-svelte".