Skip to content

Commit

Permalink
feat(BREAKING): Replace mapFileDir argument with a function for readi…
Browse files Browse the repository at this point in the history
…ng the source map (#76)

fix(BREAKING): Remove the nodejs fs and path imports
feat(BREAKING): Require a function for reading the source map content to be passed as a parameter
feat: Support reading source maps sync or async
chore(BREAKING): Throw if a string directory path is passed to fromMapFileComment or fromMapFileSource
chore: Add Upgrading section to the README

Co-authored-by: Blaine Bublitz <[email protected]>
  • Loading branch information
prantlf and phated authored Oct 17, 2022
1 parent e6b18c4 commit 2320633
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 32 deletions.
87 changes: 79 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ console.log(modified);
{"version":3,"file":"build/foo.min.js","sources":["SRC/FOO.JS"],"names":[],"mappings":"AAAA","sourceRoot":"/"}
```

## Upgrading

Prior to v2.0.0, the `fromMapFileComment` and `fromMapFileSource` functions took a String directory path and used that to resolve & read the source map file from the filesystem. However, this made the library limited to nodejs environments and broke on sources with querystrings.

In v2.0.0, you now need to pass a function that does the file reading. It will receive the source filename as a String that you can resolve to a filesystem path, URL, or anything else.

If you are using `convert-source-map` in nodejs and want the previous behavior, you'll use a function like such:

```diff
+ var fs = require('fs'); // Import the fs module to read a file
+ var path = require('path'); // Import the path module to resolve a path against your directory
- var conv = convert.fromMapFileSource(css, '../my-dir');
+ var conv = convert.fromMapFileSource(css, function (filename) {
+ return fs.readFileSync(path.resolve('../my-dir', filename), 'utf-8');
+ });
```

## API

### fromObject(obj)
Expand All @@ -45,24 +62,78 @@ Returns source map converter from given base64 encoded json string.

Returns source map converter from given base64 or uri encoded json string prefixed with `//# sourceMappingURL=...`.

### fromMapFileComment(comment, mapFileDir)
### fromMapFileComment(comment, readMap)

Returns source map converter from given `filename` by parsing `//# sourceMappingURL=filename`.

`filename` must point to a file that is found inside the `mapFileDir`. Most tools store this file right next to the
generated file, i.e. the one containing the source map.
`readMap` must be a function which receives the source map filename and returns either a String or Buffer of the source map (if read synchronously), or a `Promise` containing a String or Buffer of the source map (if read asynchronously).

If `readMap` doesn't return a `Promise`, `fromMapFileComment` will return a source map converter synchronously.

If `readMap` returns a `Promise`, `fromMapFileComment` will also return `Promise`. The `Promise` will be either resolved with the source map converter or rejected with an error.

#### Examples

**Synchronous read in Node.js:**

```js
var convert = require('convert-source-map');
var fs = require('fs');

function readMap(filename) {
return fs.readFileSync(filename, 'utf8');
}

var json = convert
.fromMapFileComment('//# sourceMappingURL=map-file-comment.css.map', readMap)
.toJSON();
console.log(json);
```


**Asynchronous read in Node.js:**

```js
var convert = require('convert-source-map');
var { promises: fs } = require('fs'); // Notice the `promises` import

function readMap(filename) {
return fs.readFile(filename, 'utf8');
}

var converter = await convert.fromMapFileComment('//# sourceMappingURL=map-file-comment.css.map', readMap)
var json = converter.toJSON();
console.log(json);
```

**Asynchronous read in the browser:**

```js
var convert = require('convert-source-map');

async function readMap(url) {
const res = await fetch(url);
return res.text();
}

const converter = await convert.fromMapFileComment('//# sourceMappingURL=map-file-comment.css.map', readMap)
var json = converter.toJSON();
console.log(json);
```

### fromSource(source)

Finds last sourcemap comment in file and returns source map converter or returns `null` if no source map comment was found.

### fromMapFileSource(source, mapFileDir)
### fromMapFileSource(source, readMap)

Finds last sourcemap comment in file and returns source map converter or returns `null` if no source map comment was found.

`readMap` must be a function which receives the source map filename and returns either a String or Buffer of the source map (if read synchronously), or a `Promise` containing a String or Buffer of the source map (if read asynchronously).

Finds last sourcemap comment in file and returns source map converter or returns `null` if no source map comment was
found.
If `readMap` doesn't return a `Promise`, `fromMapFileSource` will return a source map converter synchronously.

The sourcemap will be read from the map file found by parsing `# sourceMappingURL=file` comment. For more info see
fromMapFileComment.
If `readMap` returns a `Promise`, `fromMapFileSource` will also return `Promise`. The `Promise` will be either resolved with the source map converter or rejected with an error.

### toObject()

Expand Down
55 changes: 38 additions & 17 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
'use strict';
var fs = require('fs');
var path = require('path');

Object.defineProperty(exports, 'commentRegex', {
get: function getCommentRegex () {
Expand Down Expand Up @@ -47,29 +45,30 @@ function stripComment(sm) {
return sm.split(',').pop();
}

function readFromFileMap(sm, dir) {
// NOTE: this will only work on the server since it attempts to read the map file

function readFromFileMap(sm, read) {
var r = exports.mapFileCommentRegex.exec(sm);

// for some odd reason //# .. captures in 1 and /* .. */ in 2
var filename = r[1] || r[2];
var filepath = path.resolve(dir, filename);

try {
return fs.readFileSync(filepath, 'utf8');
var sm = read(filename);
if (sm != null && typeof sm.catch === 'function') {
return sm.catch(throwError);
} else {
return sm;
}
} catch (e) {
throw new Error('An error occurred while trying to read the map file at ' + filepath + '\n' + e);
throwError(e);
}

function throwError(e) {
throw new Error('An error occurred while trying to read the map file at ' + filename + '\n' + e.stack);
}
}

function Converter (sm, opts) {
opts = opts || {};

if (opts.isFileComment) {
sm = readFromFileMap(sm, opts.commentFileDir);
}

if (opts.hasComment) {
sm = stripComment(sm);
}
Expand Down Expand Up @@ -182,8 +181,24 @@ exports.fromComment = function (comment) {
return new Converter(comment, { encoding: encoding, hasComment: true });
};

exports.fromMapFileComment = function (comment, dir) {
return new Converter(comment, { commentFileDir: dir, isFileComment: true, isJSON: true });
function makeConverter(sm) {
return new Converter(sm, { isJSON: true });
}

exports.fromMapFileComment = function (comment, read) {
if (typeof read === 'string') {
throw new Error(
'String directory paths are no longer supported with `fromMapFileComment`\n' +
'Please review the Upgrading documentation at https://github.com/thlorenz/convert-source-map#upgrading'
)
}

var sm = readFromFileMap(comment, read);
if (sm != null && typeof sm.then === 'function') {
return sm.then(makeConverter);
} else {
return makeConverter(sm);
}
};

// Finds last sourcemap comment in file or returns null if none was found
Expand All @@ -193,9 +208,15 @@ exports.fromSource = function (content) {
};

// Finds last sourcemap comment in file or returns null if none was found
exports.fromMapFileSource = function (content, dir) {
exports.fromMapFileSource = function (content, read) {
if (typeof read === 'string') {
throw new Error(
'String directory paths are no longer supported with `fromMapFileSource`\n' +
'Please review the Upgrading documentation at https://github.com/thlorenz/convert-source-map#upgrading'
)
}
var m = content.match(exports.mapFileCommentRegex);
return m ? exports.fromMapFileComment(m.pop(), dir) : null;
return m ? exports.fromMapFileComment(m.pop(), read) : null;
};

exports.removeComments = function (src) {
Expand Down
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,5 @@
},
"files": [
"index.js"
],
"browser": {
"fs": false
}
]
}
135 changes: 132 additions & 3 deletions test/map-file-comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,88 @@
var test = require('tap').test
, rx = require('..')
, fs = require('fs')
, path = require('path')
, convert = require('..')

function readMapSyncString(filename) {
var filepath = path.join(__dirname, 'fixtures', filename)
return fs.readFileSync(filepath, 'utf-8')
}
function readMapSyncBuffer(filename) {
var filepath = path.join(__dirname, 'fixtures', filename)
return fs.readFileSync(filepath)
}

function readMapAsyncString(filename) {
return new Promise(function (resolve, reject) {
var filepath = path.join(__dirname, 'fixtures', filename)
fs.readFile(filepath, 'utf8', function (err, content) {
if (err) {
reject(err)
} else {
resolve(content)
}
})
})
}
function readMapAsyncBuffer(filename) {
return new Promise(function (resolve, reject) {
var filepath = path.join(__dirname, 'fixtures', filename)
fs.readFile(filepath, function (err, content) {
if (err) {
reject(err)
} else {
resolve(content)
}
})
})
}

test('\nresolving a "/*# sourceMappingURL=map-file-comment.css.map*/" style comment inside a given css content', function (t) {
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment.css', 'utf8')
var conv = convert.fromMapFileSource(css, readMapSyncString);
var sm = conv.toObject();

t.deepEqual(
sm.sources
, [ './client/sass/core.scss',
'./client/sass/main.scss' ]
, 'resolves paths of original sources'
)

t.equal(sm.file, 'map-file-comment.css', 'includes filename of generated file')
t.equal(
sm.mappings
, 'AAAA,wBAAyB;EACvB,UAAU,EAAE,IAAI;EAChB,MAAM,EAAE,KAAK;EACb,OAAO,EAAE,IAAI;EACb,aAAa,EAAE,iBAAiB;EAChC,KAAK,EAAE,OAAkB;;AAG3B,wBAAyB;EACvB,OAAO,EAAE,IAAI;;ACTf,gBAAiB;EACf,UAAU,EAAE,IAAI;EAChB,KAAK,EAAE,MAAM;;AAGf,kBAAmB;EACjB,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,IAAI;EACb,UAAU,EAAE,KAAK;EACjB,aAAa,EAAE,GAAG;EAClB,KAAK,EAAE,KAAK;;AAEd,kBAAmB;EACjB,KAAK,EAAE,KAAK;;AAGd,mBAAoB;EAClB,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI'
, 'includes mappings'
)
t.end()
})

test('\nresolving a "//# sourceMappingURL=map-file-comment.css.map" style comment inside a given css content', function (t) {
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment-double-slash.css', 'utf8')
var conv = convert.fromMapFileSource(css, readMapSyncString);
var sm = conv.toObject();

t.deepEqual(
sm.sources
, [ './client/sass/core.scss',
'./client/sass/main.scss' ]
, 'resolves paths of original sources'
)

t.equal(sm.file, 'map-file-comment.css', 'includes filename of generated file')
t.equal(
sm.mappings
, 'AAAA,wBAAyB;EACvB,UAAU,EAAE,IAAI;EAChB,MAAM,EAAE,KAAK;EACb,OAAO,EAAE,IAAI;EACb,aAAa,EAAE,iBAAiB;EAChC,KAAK,EAAE,OAAkB;;AAG3B,wBAAyB;EACvB,OAAO,EAAE,IAAI;;ACTf,gBAAiB;EACf,UAAU,EAAE,IAAI;EAChB,KAAK,EAAE,MAAM;;AAGf,kBAAmB;EACjB,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,IAAI;EACb,UAAU,EAAE,KAAK;EACjB,aAAa,EAAE,GAAG;EAClB,KAAK,EAAE,KAAK;;AAEd,kBAAmB;EACjB,KAAK,EAAE,KAAK;;AAGd,mBAAoB;EAClB,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI'
, 'includes mappings'
)
t.end()
})

test('\nresolving a "/*# sourceMappingURL=map-file-comment.css.map*/" style comment inside a given css content', function (t) {
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment.css', 'utf8')
var conv = convert.fromMapFileSource(css, __dirname + '/fixtures');
var conv = convert.fromMapFileSource(css, readMapSyncBuffer);
var sm = conv.toObject();

t.deepEqual(
Expand All @@ -29,7 +106,7 @@ test('\nresolving a "/*# sourceMappingURL=map-file-comment.css.map*/" style comm

test('\nresolving a "//# sourceMappingURL=map-file-comment.css.map" style comment inside a given css content', function (t) {
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment-double-slash.css', 'utf8')
var conv = convert.fromMapFileSource(css, __dirname + '/fixtures');
var conv = convert.fromMapFileSource(css, readMapSyncBuffer);
var sm = conv.toObject();

t.deepEqual(
Expand All @@ -48,9 +125,61 @@ test('\nresolving a "//# sourceMappingURL=map-file-comment.css.map" style commen
t.end()
})

test('\nresolving a "//# sourceMappingURL=map-file-comment.css.map" style comment asynchronously', function (t) {
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment-double-slash.css', 'utf8')
var promise = convert.fromMapFileSource(css, readMapAsyncString);
promise.then(function (conv) {
var sm = conv.toObject();

t.deepEqual(
sm.sources
, [ './client/sass/core.scss',
'./client/sass/main.scss' ]
, 'resolves paths of original sources'
)

t.equal(sm.file, 'map-file-comment.css', 'includes filename of generated file')
t.equal(
sm.mappings
, 'AAAA,wBAAyB;EACvB,UAAU,EAAE,IAAI;EAChB,MAAM,EAAE,KAAK;EACb,OAAO,EAAE,IAAI;EACb,aAAa,EAAE,iBAAiB;EAChC,KAAK,EAAE,OAAkB;;AAG3B,wBAAyB;EACvB,OAAO,EAAE,IAAI;;ACTf,gBAAiB;EACf,UAAU,EAAE,IAAI;EAChB,KAAK,EAAE,MAAM;;AAGf,kBAAmB;EACjB,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,IAAI;EACb,UAAU,EAAE,KAAK;EACjB,aAAa,EAAE,GAAG;EAClB,KAAK,EAAE,KAAK;;AAEd,kBAAmB;EACjB,KAAK,EAAE,KAAK;;AAGd,mBAAoB;EAClB,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI'
, 'includes mappings'
)
t.end()
}, function (err) {
t.error(err, 'read map');
t.end()
});
})

test('\nresolving a "//# sourceMappingURL=map-file-comment.css.map" style comment asynchronously', function (t) {
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment-double-slash.css', 'utf8')
var promise = convert.fromMapFileSource(css, readMapAsyncBuffer);
promise.then(function (conv) {
var sm = conv.toObject();

t.deepEqual(
sm.sources
, [ './client/sass/core.scss',
'./client/sass/main.scss' ]
, 'resolves paths of original sources'
)

t.equal(sm.file, 'map-file-comment.css', 'includes filename of generated file')
t.equal(
sm.mappings
, 'AAAA,wBAAyB;EACvB,UAAU,EAAE,IAAI;EAChB,MAAM,EAAE,KAAK;EACb,OAAO,EAAE,IAAI;EACb,aAAa,EAAE,iBAAiB;EAChC,KAAK,EAAE,OAAkB;;AAG3B,wBAAyB;EACvB,OAAO,EAAE,IAAI;;ACTf,gBAAiB;EACf,UAAU,EAAE,IAAI;EAChB,KAAK,EAAE,MAAM;;AAGf,kBAAmB;EACjB,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,IAAI;EACb,UAAU,EAAE,KAAK;EACjB,aAAa,EAAE,GAAG;EAClB,KAAK,EAAE,KAAK;;AAEd,kBAAmB;EACjB,KAAK,EAAE,KAAK;;AAGd,mBAAoB;EAClB,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI'
, 'includes mappings'
)
t.end()
}, function (err) {
t.error(err, 'read map');
t.end()
});
})

test('\nresolving a /*# sourceMappingURL=data:application/json;base64,... */ style comment inside a given css content', function(t) {
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment-inline.css', 'utf8')
var conv = convert.fromSource(css, __dirname + '/fixtures')
var conv = convert.fromSource(css)
var sm = conv.toObject()

t.deepEqual(
Expand Down

0 comments on commit 2320633

Please sign in to comment.