christophercliff / metalsmith-fingerprint

A fingerprint plugin for Metalsmith
MIT License
28 stars 7 forks source link

link to sourcemap is lost after fingerprinting #10

Open oupala opened 8 years ago

oupala commented 8 years ago

According to Source Map Revision 3 Proposal:

Source Map Naming Optionally, a source map will have the same name as the generated file but with a “.map” extension. For example, for “page.js” a source map named “page.js.map” would be generated.

If a source file (css or js) is fingerprinted (which implies renamed), the link with the sourcemap (which is still old-named plus the .map extension) will be lost.

Is there a way to relink the fingerprinted file and its sourcemap?

I can't see any obvious way to do this. Feel free to suggest a smart way to do this...

oupala commented 8 years ago

@ksmithut said:

Fingerprinting should definitely support sourcemaps, even if they don't. Most plugins that do support sourcemaps have some kind of input sourcemap option, which takes the input source map and generates a new sourcemap.

oupala commented 8 years ago

It appears that the way sourcemap is implemented by less and sass does not absolutely require that css file and its sourcemap have the same basename.

Looking at this page show that the sourcemap name is embedded in the css file:

This will create a .css.map source map file for each CSS file, and add a comment to the end of your CSS file with the location of the sourcemap: /# sourceMappingURL=index.css.map /. The devtools will use this source map to map locations in the CSS style sheet to locations in the original source.

As propagating the basename in the soucemap seems to be not mandatory, I let experts decide if this issue should be fixed or not.

rstacruz commented 8 years ago

:+1: — it'd be nice to have this.

if foo.css is fingerprinted to foo-1a2b3c4d.css, then the corresponding foo.css.map should be fingerprinted to `foo-1a2b3c4d.css.map as well. same with JavaScript.

the alternative is to just use embedded sourcemaps instead.

gunnx commented 8 years ago

I updated locally to do this and optionally remove the originals

var crypto = require('crypto');
var multimatch = require('multimatch');
var path = require('path');
var sourceMapUrl = require('source-map-url');

var KEY = 'metalsmith';

module.exports = plugin;

function plugin(options) {
  options = options || {};

  return function (files, metalsmith, done) {
    var metadata = metalsmith.metadata();
    var remove = (options.remove || false);

    Object.keys(files)
      .filter(function (file) {
        return multimatch(file, options.pattern).length > 0
      })
      .forEach(function (file) {
        var hash = crypto.createHmac('md5', KEY).update(files[file].contents).digest('hex');
        var ext = path.extname(file);
        var fingerprint =  [file.substring(0, file.lastIndexOf(ext)), '-', hash, ext].join('').replace(/\\/g, '/');

        // update sourcemap
        files[file].contents = updateSourceMapComment(file, files, fingerprint, remove);

        // update filename
        files[fingerprint] = files[file];

        // delete original
        if (remove) {
          delete files[file];
        }

        // add metadata
        metadata.fingerprint = (metadata.fingerprint || {});
        metadata.fingerprint[file] = fingerprint;
      });
    return process.nextTick(done);
  }
}

function updateSourceMapComment(filename, files, newFilename, remove) {
  var newMapFilename = newFilename + '.map';
  var contents = files[filename].contents.toString();
  var currentMapFilename = filename + '.map';

  // if there is a sourcemap in the contents then replace with fingerprint version and delete original
  if (sourceMapUrl.existsIn(contents)) {
    files[newMapFilename] = files[currentMapFilename];

    if (remove) {
      delete files[currentMapFilename];
    }

    return contents.replace(sourceMapUrl.regex, '/*# sourceMappingURL=' + newMapFilename + ' */');
  }

  return contents;
}