Skip to content

Support the "sections" alternate source map structure #16

Activity

  1. DavidMikeSimon commented on Feb 27, 2013

    @DavidMikeSimon

    Seconding this, this feature would be very handy for the grunt-coffee-redux tool.

  2. fitzgen commented on Jul 3, 2013

    @fitzgen
    ContributorAuthor

    Proposed API

    Goals:

    • Not depend on any specific web, node, or Firefox APIs for fetching the sub source maps
    • Not force developers using the existing API to change their code. Aside: It's ok if we throw an error when they haven't loaded a sub-source map we need because they wouldn't have been able to consume an indexed source map before and it would have failed anyways.

    Notes:

    • I'm using Constructor#method instead of Constructor.prototype.method because the latter is too long and is messing up github's formatting.

    SourceMapConsumer#fetchSubSourceMaps(fetcher, callback)

    • fetcher(subSourceMapURL, onSuccess, onFailure): Performs HTTP GET requests to fetch sub source map URLs and passes the information on via onSuccess(httpResponseBody) or onFailure(error) callbacks
    • callback(sourceMapConsumer, errors): A function called once all of the sub source maps have been loaded. The errors argument should be null in the case of no errors, or a list of error objects if there were errors fetching any of the sub source maps.

    If this is not an indexed source map, or every section of this indexed source map is an inline map (not a pointer to a URL) we should call callback immediately.

    Note that each sub source map could also be an indexed source map, so we will need to make sure to load each sub source map as well before calling callback.

    Changes to the SourceMapConsumer constructor

    Add two optional arguments, fetcher and callback (the same as above) so that the function's signature is SourceMapConsumer(sourceMap[, fetcher, callback]).

    Changes to SourceMapConsumer#originalPositionFor(generatedPosition)

    If we are not consuming an indexed source map, proceed normally.

    If we are in an indexed source map:

    Look up which sub source map the location falls into

    If the sub source map is still fetching, throw a "not yet loaded" error

    If fetching the sub source map failed, throw a "fetching sub source map for section failed" error

    If fetching the sub source map completed successfully:

    Determine the relative location into that sub source map by substracting the section's offsets

    Query the sub source map for the original position of the relative location and return that.

    Changes to SourceMapConsumer#generatedPositionFor(originalPosition)

    Note: Because we can't be sure which sub source map contains the mapping with the original position we are being given until we parse each of them, we have to throw an error if any section is not available.

    If any of the sub source maps are still loading, throw a "not yet loaded" error

    If any of the sub source maps failed to load, throw a "fetching sub source map for section(s) [, , ...]" error

    If all the sub source maps have been loaded successfully:

    Iterate over each sub source map and ask for the generated position we are looking for

    Return the first non-{ line: null, column: null } position we find

    If we find none, return { line: null, column: null }

  3. sokra commented on Jul 4, 2013

    @sokra
    Contributor
    • A callback parameter in a constructor seems weird to me
    • I would prefer node.js callback style for the fetcher function(err, rawSourceMap), but it's ok.
    • The behaivior for the getters seem weird: They "randomly" throw errors if the map is not fully loaded. There is no chance to get notified if this mapping is ready (only the full map).
    • Extending SourceMapConsumer with this stuff make the code bigger for everyone.
    • Staying environement independend is good.

    I would like to propose an alternative API for discussion:

    It

    • doesn't extend the current SourceMapConsumer
    • introduce a AsyncSourceMapConsumer for nice on demand loading. The SourceMapConsumer API, but async.
    var map = new AsyncSourceMapConsumer(rawSourceMap, fetcher); // doesn't fetch anything
    map.originalPositionFor(genPos, function(err, orgPos) {}); // fetch parts on demand
    map.generatedPositionFor(orgPos, function(err, genPos) {}); // need to fetch everything
    map.toSourceMapConsumer(function(sourceMapConsumer, errors) {
      // gives a sourceMapConsumer in every case. failed mappings are missing.
      // the errors array summarize all fetch errors.
      var genPos = sourceMapConsumer.generatedPositionFor(orgPos); // may throw if fetch failed
      // sourceMapConsumer is a instance of `SourceMapConsumer` or `IndexSourceMapConsumer`
    });

    The class IndexSourceMapConsumer has the same API as SourceMapConsumer but work with index maps. It is used by AsyncSourceMapConsumer#toSourceMapConsumer.

    var indexMap = new IndexSourceMapConsumer(rawSourceMap);
    var subMaps = indexMap.getSubMapUrls(); // String[]
    indexMap.setSubMap(url, sourceMapConsumer);
    indexMap.setSubMapError(url, error); // accessing this sub map will throw that error
    // same API as SourceMapConsumer

    For API users:

    • The existing SourceMapConsumer doesn't change and cannot consume index maps.
    • A simple change of the existing code can consume index maps (AsyncSourceMapConsumer#toSourceMapConsumer)
    • A complex change of the existing code can consume index maps with on demand loading of source maps (AsyncSourceMapConsumer#originalPositionFor etc.)

    Alternative: Merge the IndexSourceMapConsumer with the SourceMapConsumer...

    • Existing code can comsume index maps, if they don't reference other SourceMaps by url.
    • But it would increase code size for SourceMapConsumer.

    Alernative 2: Rename SourceMapConsumer to NormalSourceMapConsumer. Make a new SourceMapConsumer constructor, which automatically decide between NormalSourceMapConsumer and IndexSourceMapConsumer. They are subclasses of the new SourceMapConsumer interface.

    • Existing code can comsume index maps, if they don't reference other SourceMaps by url.
    • Existing code which subclasses SourceMapConsumer will break.
  4. tivac commented on Jul 5, 2013

    @tivac
    • Definitely agree on preferring node-style callbacks.
    • I'd prefer to not introduce another object type for index maps if possible, they're part of the spec & I feel like the SourceMapConsumer object should support the entirety of the spec.
    • I'm not convinced that it will be such a massive amount of code that adding support to the existing SourceMapConsumer is a problem. That being said, I don't use this code on a client so as long as it's well-tested & well-written code size isn't a pressing concern for me.
  5. tivac commented on Jul 5, 2013

    @tivac

    Hmm, starting to look into this but the testing code doesn't seem to support async tests. I don't really want to rewrite the test framework, any suggestions @fitzgen? I could wrap the test calls into something like caolan/async but I dunno how kosher that is for @mozilla code.

  6. fitzgen commented on Jul 5, 2013

    @fitzgen
    ContributorAuthor

    (Aside: @tivac, @firass just told me that you are also a veteran of ResTek, nice 👍)


    • Regarding testing and async: it should be easy to mock a fetcher that is not async and just returns hard coded maps depending on the url you give it
    • Ok, let's do node style callbacks
    • I don't see any reason to expose getting a list of the sections or setting a sub source map for a given section in the public API
    • Strongly prefer to fetch sections greedily (or at minimum have a switch for this): developers using a debugger don't want to wait around for a subsection of a source map to load because they stepped into a new section of code
    • Regarding @tivac's second bullet point: I agree that the public API of a SourceMapConsumer should support the entirety of the spec, however we should be able to implement that support however we want and if the user gets back an IndexSourceMapConsumer instance that is still valid. What we shouldn't do is force users to check what kind of source map they have and choose the right consumer, or alternatively check which kind of consumer they get in the end. The user should have just one branch of logic / code path regardless what kind of source map they get.
      • The existing code path (new SourceMapConsumer(map)) obviously doesn't support async-ness that we will need when we support fetching sub source maps. I'm ok with maintaining this code path and saying it will only work when nothing is remote.
      • But we need a new one that supports async fetching and gives us the One Code Path to Rule Them All (works for source maps that don't need remote fetching and source maps that do)
    • We haven't really discussed generating index maps yet. I think we could just create an IndexSourceMapGenerator or even just have a SourceMapGenerator.createIndexMap class method. The single code path arguments don't apply here because they are two different use cases.
    • I like @sokra's alternative 2.
      • I think that the new SourceMapConsumer should provide and interface of methods that NormalSourceMapConsumer (aside/nit: I really don't like "Normal", any better name ideas?) and IndexSourceMapConsumer can implement and they should inherit from the interface so that way let smc = new SourceMapConsumer(map); smc instanceof SourceMapConsumer // true;
      • How do we pass in fetcher to the IndexSourceMapConsumer? As I mention above, I want to avoid having to explicitly check whether I get an IndexSourceMapConsumer or a NormalSourceMapConsumer from the SourceMapConsumer constructor; I should only need one branch of logic.
      • Regarding sub classing breaking, there are a few options:
        • Break sub classing and increment the version to 0.2.0 to indicate a backwards incompatible change. Composition still works fine either way.
        • Instead of making SourceMapConsumer an interface which NormalSourceMapConsumer and IndexSourceMapConsumer implement, we could just modify the SourceMapConsumer constructor to return an IndexSourceMapConsumer instance when needed. This is moving back towards my original proposal.
        • SourceMapConsumer's constructor can look at which kind of source map it has, and create the proper "SourceMapConsumerImplementation" object for the right type of source map it has and then in each of its methods just pass the arguments to this._implementation.originalPositionFor or whatever. This should maintain subclassing, but at the cost of some misdirection and a little bit of code bloat.
  7. tivac commented on Jul 5, 2013

    @tivac
    Async Tests

    I was hoping to test it being actually async, to ensure we were properly handling cases where that is diffcult (out-of-order responses, mostly). If you're cool with it a sync fetcher is ok-but-not-great by me.

    Naming

    I really prefer something like SimpleSourceMapConsumer over NormalSourceMapConsumer, "Normal" implies that index maps are "abnormal". Maybe they are, maybe they aren't. Seems unfair to brand them as such though.

    SourceMapGenerator support

    I'd like to get consuming Index source maps working with an API we're happy with first, generating is also important but at least for me it's definitely secondary. Feeling like we have enough discussion about just consuming that we should focus there first!

    Inheritance/API

    So long as you're ok with API calls randomly breaking in the async case if they are used before the callback has happened I still see no real strong case for splitting the functionality. If you broke them out SimpleSourceMapConsumer would still contain about 80% of the code & I don't know that using inheritance to build an IndexSourceMapConsumer for the last 20% is really worth it.

    SourceMapConsumer() could throw an error if it's passed a raw source map that has section elements referencing URLs but not given a fetcher/callback. Is that too obtuse?

    RE: ResTek: If Answerline was still using YUI2.x when you were there that was totally my fault, sorry! 😳

  8. fitzgen commented on Jul 5, 2013

    @fitzgen
    ContributorAuthor

    I was hoping to test it being actually async, to ensure we were properly handling cases where that is diffcult (out-of-order responses, mostly). If you're cool with it a sync fetcher is ok-but-not-great by me.

    I agree that it would be best, but I think we can push this back to a follow up issue, or at least start implementing support for index maps now and then merge the async testing in before merging the full feature.

    SimpleSourceMapConsumer

    👍

    SourceMapConsumer() could throw an error if it's passed a raw source map that has section elements referencing URLs but not given a fetcher/callback. Is that too obtuse?

    👍

    Inheritance

    I think they should be separate. Having if (this.sections) as the first thing in every method of SourceMapConsumer would not be nice.

  9. tivac commented on Jul 5, 2013

    @tivac

    I think they should be separate. Having if (this.sections) as the first thing in every method of SourceMapConsumer would not be nice.

    Oof, ok. I can still try to work on it but I don't think I'll have enough time for a refactor that large. We'll see what I can accomplish.

  10. sokra commented on Jul 6, 2013

    @sokra
    Contributor

    Just another idea: We could give methods like originalPositionFor an optional callback. In the sync version they throw not-yet-loaded Error. With callback they return, when the required part is loaded. This would keep the API backward compatible, but give new code the opportunity to react to the fetching process.

    New methods include: fetchSubMaps([fetcher], [callback]) (greedily) and setSubMapFetcher(fetcher) (on demand) with fetcher be a function(url, callback(err, rawSourceMapObject))

  11. fitzgen commented on Jul 6, 2013

    @fitzgen
    ContributorAuthor

    I really like that idea, but it doesn't work with SourceMapConsumer.prototype.sources because it's a getter.

    _sigh_

    What would be the prefect API if we didn't worry about backwards compatibility? Maybe its time...

  12. sokra commented on Jul 7, 2013

    @sokra
    Contributor

    That's a good question... Maybe something like this:

    interface SourceMap { // Personally I would rename it^^
      // callback is "function(err, result: X)" for "X abc([callback])"
      {source,line,column,name} orginalPositionFor(line, column, [callback])
      {line,column} generatedPositionFor(source, line, column, [callback])
      String[] allSources([callback])
      {source: sourceContent} allSourceContents([callback])
      // Mapping is {original:{source,line,column,name},generated:{line,column}}
      Mapping[] allMappings([order], [callback])
      Mapping[] mappingsFor(source, [order], [callback])
      Mapping[] mappingsFor(startLine, startColumn, endLine, endColumn, [order], [callback])
    }
    class SimpleSourceMap : SourceMap {
      new SimpleSourceMap(sourceMap: Object)
    }
    class IndexSourceMap : SourceMap {
      new IndexSourceMap(sourceMap: Object, [fetcher])
      fetch([callback])
    }
    class FutureSourceMap : SourceMap {
      new FutureSourceMap(url: String, fetcher)
      fetch([callback])
    }
    module "source-map" {
      // callback is "function(error, sourceMap: SourceMap)"
      static SourceMap create(sourceMap: Object, [fetcher, [callback]])
      static SourceMap create(sourceMap: Object, [fetcher, fetchLazily: true])
      static FutureSourceMap create(url: String, fetcher, [callback])
      static FutureSourceMap create(url: String, fetcher, fetchLazily: true)
    }
  13. fitzgen commented on Jul 8, 2013

    @fitzgen
    ContributorAuthor

    Just another idea: We could give methods like originalPositionFor an optional callback. In the sync version they throw not-yet-loaded Error. With callback they return, when the required part is loaded. This would keep the API backward compatible, but give new code the opportunity to react to the fetching process.

    New methods include: fetchSubMaps([fetcher], [callback]) (greedily) and setSubMapFetcher(fetcher) (on demand) with fetcher be a function(url, callback(err, rawSourceMapObject))

    Let's combine this proposal and the this._implementation.originalPositionFor so that inheritance doesn't break.

    We can change sources to be a method with an optional callback and bump the version number.

  14. jlfwong commented on Jul 1, 2014

    @jlfwong

    What's the current status of this?

    It looks like #69 is in limbo at the moment.

    A lot of this discussion seems to revolve around asynchronicity, but having a subset of this functionality synchronously would be really useful to me.

    For my current project, I need to be able to consume indexed source maps, but each of the sections has a map instead of a url in it (i.e. once the map is loaded, no more fetching is required).

    For example:

                       {"version": 3,
                        "file": "foo.out",
                        "sections": [
                            {'offset': {'line': 0, 'column': 0},
                             'map': {"version": 3,
                                     "file": "i1",
                                     "sourceRoot": "/",
                                     "sources": ["i1"],
                                     "names": [],
                                     "mappings": "AAAA"},
                             },
                            {'offset': {'line': 1, 'column': 0},
                             'map': {"version": 3,
                                     "file": "i2",
                                     "sourceRoot": "/",
                                     "sources": ["i2"],
                                     "names": [],
                                     "mappings": "AAAA"},
                            }
                            ]}
    
    1. Would you be open to accepting a patch that provides that functionality without adding asynchronicity?
    2. After all the discussion about potential APIs, I'm unsure what the final proposal is.
  15. fitzgen commented on Jul 2, 2014

    @fitzgen
    ContributorAuthor

    Would you be open to accepting a patch that provides that functionality without adding asynchronicity?

    Yes.

    After all the discussion about potential APIs, I'm unsure what the final proposal is.

    Add a method for setting the source map fetcher. It should take (urlString, callback) and call the callback with (error, null) on a fetch failure, or (null, data) on fetch success.

    Add a method to greedily fetch all sub source maps that takes a callback which gets passed (error) on failure or (null) on success.

    All methods that access source mapped data should take an optional callback. If no callback is provided and the data is available, return it. If no callback is provided and the data is not available, throw an error. If a callback is provided and the data is available, call the callback with the arguments (null, data). If a callback is provided and the data is not available, attempt to load it. If it fails to load, call the callback with (error, null). If loading succeeds, call the callback with (null, data).

    Make sources a function (taking an optional callback, as above), not a getter.

  16. DavidSouther commented on Sep 7, 2014

    @DavidSouther

    Pushing two years on this... what's that hard part about recursively iterating a sourcemap?

  17. fitzgen commented on Sep 9, 2014

    @fitzgen
    ContributorAuthor

    Pushing two years on this... what's that hard part about recursively iterating a sourcemap?

    Sounds like you're volunteering your time! Great! When should we expect your contributions?

  18. DavidSouther commented on Sep 11, 2014

    @DavidSouther

    After I've had time to fully parse and understand the commentary in this thread, as well as source-map-consumer.js. So... indefinite.

  19. jlfwong commented on Jan 26, 2015

    @jlfwong

    A subset of this now works since #127 has been merged :)

  20. amasad commented on Apr 28, 2015

    @amasad

    @jlfwong what does #127 leave unimplemented from this?

  21. zanona commented on Oct 15, 2017

    @zanona

    Wow, it would be really awesome to have this so we could have source maps for HTML files with inline scripts on different parts of a document, especially web components. Can I please just confirm if the spec proposes this but it has not been used or implemented yet?
    Thanks guys

  22. added a commit that references this issue on Oct 15, 2017
  23. tromey commented on Oct 17, 2017

    @tromey
    Contributor

    Wow, it would be really awesome to have this so we could have source maps for HTML files with inline scripts on different parts of a document, especially web components. Can I please just confirm if the spec proposes this but it has not been used or implemented yet?

    Inline <script> can set the source map URL (and the sourceURL if desired). This can work fine without needing the sections approach. However, there are still some bugs in this area in Firefox at least. I'm not totally sure this answers your question...?

  24. zanona commented on Oct 17, 2017

    @zanona

    Thanks, @tromey!
    I actually meant when you have multiple <script> tags in a single HTML file since only one sourceMappingURL declaration is accepted per file. I actually did some tests yesterday and could get it working well in Chrome with sections defined, which was great 😄
    Perhaps that's already good enough because when I first saw this issue opened I wondered if the section feature was actually implemented or just a proposal.

  25. tromey commented on Oct 18, 2017

    @tromey
    Contributor

    I actually meant when you have multiple <script> tags in a single HTML file since only one sourceMappingURL declaration is accepted per file.

    I think the intent is that each separate <script> can have its own sourceMappingURL; but like I mentioned, there are some bugs in the Firefox devtools in this area. I don't think source-mapping the entire HTML document is supported (if it is, that must be some browser's own extension).

    I wondered if the section feature was actually implemented or just a proposal.

    I think it is implemented; however, in this library, there's no support for a sectioned source map where the sections are not inlined. Nobody ever implemented the proposed fetching API.

  26. added a commit that references this issue on Oct 19, 2017
  27. hybrist commented on Apr 13, 2021

    @hybrist
    Collaborator

    The basic feature (support for indexed source maps) has been implemented in its most commonly used form (embedded map, not external url reference). I created #437 to track support for external URLs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions