Skip to content

Commit 2320633

Browse files
prantlfphated
andauthored
feat(BREAKING): Replace mapFileDir argument with a function for reading 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]>
1 parent e6b18c4 commit 2320633

File tree

4 files changed

+250
-32
lines changed

4 files changed

+250
-32
lines changed

README.md

+79-8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ console.log(modified);
2323
{"version":3,"file":"build/foo.min.js","sources":["SRC/FOO.JS"],"names":[],"mappings":"AAAA","sourceRoot":"/"}
2424
```
2525

26+
## Upgrading
27+
28+
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.
29+
30+
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.
31+
32+
If you are using `convert-source-map` in nodejs and want the previous behavior, you'll use a function like such:
33+
34+
```diff
35+
+ var fs = require('fs'); // Import the fs module to read a file
36+
+ var path = require('path'); // Import the path module to resolve a path against your directory
37+
- var conv = convert.fromMapFileSource(css, '../my-dir');
38+
+ var conv = convert.fromMapFileSource(css, function (filename) {
39+
+ return fs.readFileSync(path.resolve('../my-dir', filename), 'utf-8');
40+
+ });
41+
```
42+
2643
## API
2744

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

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

48-
### fromMapFileComment(comment, mapFileDir)
65+
### fromMapFileComment(comment, readMap)
4966

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

52-
`filename` must point to a file that is found inside the `mapFileDir`. Most tools store this file right next to the
53-
generated file, i.e. the one containing the source map.
69+
`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).
70+
71+
If `readMap` doesn't return a `Promise`, `fromMapFileComment` will return a source map converter synchronously.
72+
73+
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.
74+
75+
#### Examples
76+
77+
**Synchronous read in Node.js:**
78+
79+
```js
80+
var convert = require('convert-source-map');
81+
var fs = require('fs');
82+
83+
function readMap(filename) {
84+
return fs.readFileSync(filename, 'utf8');
85+
}
86+
87+
var json = convert
88+
.fromMapFileComment('//# sourceMappingURL=map-file-comment.css.map', readMap)
89+
.toJSON();
90+
console.log(json);
91+
```
92+
93+
94+
**Asynchronous read in Node.js:**
95+
96+
```js
97+
var convert = require('convert-source-map');
98+
var { promises: fs } = require('fs'); // Notice the `promises` import
99+
100+
function readMap(filename) {
101+
return fs.readFile(filename, 'utf8');
102+
}
103+
104+
var converter = await convert.fromMapFileComment('//# sourceMappingURL=map-file-comment.css.map', readMap)
105+
var json = converter.toJSON();
106+
console.log(json);
107+
```
108+
109+
**Asynchronous read in the browser:**
110+
111+
```js
112+
var convert = require('convert-source-map');
113+
114+
async function readMap(url) {
115+
const res = await fetch(url);
116+
return res.text();
117+
}
118+
119+
const converter = await convert.fromMapFileComment('//# sourceMappingURL=map-file-comment.css.map', readMap)
120+
var json = converter.toJSON();
121+
console.log(json);
122+
```
54123

55124
### fromSource(source)
56125

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

59-
### fromMapFileSource(source, mapFileDir)
128+
### fromMapFileSource(source, readMap)
129+
130+
Finds last sourcemap comment in file and returns source map converter or returns `null` if no source map comment was found.
131+
132+
`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).
60133

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

64-
The sourcemap will be read from the map file found by parsing `# sourceMappingURL=file` comment. For more info see
65-
fromMapFileComment.
136+
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.
66137

67138
### toObject()
68139

index.js

+38-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
'use strict';
2-
var fs = require('fs');
3-
var path = require('path');
42

53
Object.defineProperty(exports, 'commentRegex', {
64
get: function getCommentRegex () {
@@ -47,29 +45,30 @@ function stripComment(sm) {
4745
return sm.split(',').pop();
4846
}
4947

50-
function readFromFileMap(sm, dir) {
51-
// NOTE: this will only work on the server since it attempts to read the map file
52-
48+
function readFromFileMap(sm, read) {
5349
var r = exports.mapFileCommentRegex.exec(sm);
54-
5550
// for some odd reason //# .. captures in 1 and /* .. */ in 2
5651
var filename = r[1] || r[2];
57-
var filepath = path.resolve(dir, filename);
5852

5953
try {
60-
return fs.readFileSync(filepath, 'utf8');
54+
var sm = read(filename);
55+
if (sm != null && typeof sm.catch === 'function') {
56+
return sm.catch(throwError);
57+
} else {
58+
return sm;
59+
}
6160
} catch (e) {
62-
throw new Error('An error occurred while trying to read the map file at ' + filepath + '\n' + e);
61+
throwError(e);
62+
}
63+
64+
function throwError(e) {
65+
throw new Error('An error occurred while trying to read the map file at ' + filename + '\n' + e.stack);
6366
}
6467
}
6568

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

69-
if (opts.isFileComment) {
70-
sm = readFromFileMap(sm, opts.commentFileDir);
71-
}
72-
7372
if (opts.hasComment) {
7473
sm = stripComment(sm);
7574
}
@@ -182,8 +181,24 @@ exports.fromComment = function (comment) {
182181
return new Converter(comment, { encoding: encoding, hasComment: true });
183182
};
184183

185-
exports.fromMapFileComment = function (comment, dir) {
186-
return new Converter(comment, { commentFileDir: dir, isFileComment: true, isJSON: true });
184+
function makeConverter(sm) {
185+
return new Converter(sm, { isJSON: true });
186+
}
187+
188+
exports.fromMapFileComment = function (comment, read) {
189+
if (typeof read === 'string') {
190+
throw new Error(
191+
'String directory paths are no longer supported with `fromMapFileComment`\n' +
192+
'Please review the Upgrading documentation at https://github.com/thlorenz/convert-source-map#upgrading'
193+
)
194+
}
195+
196+
var sm = readFromFileMap(comment, read);
197+
if (sm != null && typeof sm.then === 'function') {
198+
return sm.then(makeConverter);
199+
} else {
200+
return makeConverter(sm);
201+
}
187202
};
188203

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

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

201222
exports.removeComments = function (src) {

package.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,5 @@
3434
},
3535
"files": [
3636
"index.js"
37-
],
38-
"browser": {
39-
"fs": false
40-
}
37+
]
4138
}

test/map-file-comment.js

+132-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,88 @@
44
var test = require('tap').test
55
, rx = require('..')
66
, fs = require('fs')
7+
, path = require('path')
78
, convert = require('..')
89

10+
function readMapSyncString(filename) {
11+
var filepath = path.join(__dirname, 'fixtures', filename)
12+
return fs.readFileSync(filepath, 'utf-8')
13+
}
14+
function readMapSyncBuffer(filename) {
15+
var filepath = path.join(__dirname, 'fixtures', filename)
16+
return fs.readFileSync(filepath)
17+
}
18+
19+
function readMapAsyncString(filename) {
20+
return new Promise(function (resolve, reject) {
21+
var filepath = path.join(__dirname, 'fixtures', filename)
22+
fs.readFile(filepath, 'utf8', function (err, content) {
23+
if (err) {
24+
reject(err)
25+
} else {
26+
resolve(content)
27+
}
28+
})
29+
})
30+
}
31+
function readMapAsyncBuffer(filename) {
32+
return new Promise(function (resolve, reject) {
33+
var filepath = path.join(__dirname, 'fixtures', filename)
34+
fs.readFile(filepath, function (err, content) {
35+
if (err) {
36+
reject(err)
37+
} else {
38+
resolve(content)
39+
}
40+
})
41+
})
42+
}
43+
44+
test('\nresolving a "/*# sourceMappingURL=map-file-comment.css.map*/" style comment inside a given css content', function (t) {
45+
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment.css', 'utf8')
46+
var conv = convert.fromMapFileSource(css, readMapSyncString);
47+
var sm = conv.toObject();
48+
49+
t.deepEqual(
50+
sm.sources
51+
, [ './client/sass/core.scss',
52+
'./client/sass/main.scss' ]
53+
, 'resolves paths of original sources'
54+
)
55+
56+
t.equal(sm.file, 'map-file-comment.css', 'includes filename of generated file')
57+
t.equal(
58+
sm.mappings
59+
, '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'
60+
, 'includes mappings'
61+
)
62+
t.end()
63+
})
64+
65+
test('\nresolving a "//# sourceMappingURL=map-file-comment.css.map" style comment inside a given css content', function (t) {
66+
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment-double-slash.css', 'utf8')
67+
var conv = convert.fromMapFileSource(css, readMapSyncString);
68+
var sm = conv.toObject();
69+
70+
t.deepEqual(
71+
sm.sources
72+
, [ './client/sass/core.scss',
73+
'./client/sass/main.scss' ]
74+
, 'resolves paths of original sources'
75+
)
76+
77+
t.equal(sm.file, 'map-file-comment.css', 'includes filename of generated file')
78+
t.equal(
79+
sm.mappings
80+
, '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'
81+
, 'includes mappings'
82+
)
83+
t.end()
84+
})
85+
986
test('\nresolving a "/*# sourceMappingURL=map-file-comment.css.map*/" style comment inside a given css content', function (t) {
1087
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment.css', 'utf8')
11-
var conv = convert.fromMapFileSource(css, __dirname + '/fixtures');
88+
var conv = convert.fromMapFileSource(css, readMapSyncBuffer);
1289
var sm = conv.toObject();
1390

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

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

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

128+
test('\nresolving a "//# sourceMappingURL=map-file-comment.css.map" style comment asynchronously', function (t) {
129+
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment-double-slash.css', 'utf8')
130+
var promise = convert.fromMapFileSource(css, readMapAsyncString);
131+
promise.then(function (conv) {
132+
var sm = conv.toObject();
133+
134+
t.deepEqual(
135+
sm.sources
136+
, [ './client/sass/core.scss',
137+
'./client/sass/main.scss' ]
138+
, 'resolves paths of original sources'
139+
)
140+
141+
t.equal(sm.file, 'map-file-comment.css', 'includes filename of generated file')
142+
t.equal(
143+
sm.mappings
144+
, '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'
145+
, 'includes mappings'
146+
)
147+
t.end()
148+
}, function (err) {
149+
t.error(err, 'read map');
150+
t.end()
151+
});
152+
})
153+
154+
test('\nresolving a "//# sourceMappingURL=map-file-comment.css.map" style comment asynchronously', function (t) {
155+
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment-double-slash.css', 'utf8')
156+
var promise = convert.fromMapFileSource(css, readMapAsyncBuffer);
157+
promise.then(function (conv) {
158+
var sm = conv.toObject();
159+
160+
t.deepEqual(
161+
sm.sources
162+
, [ './client/sass/core.scss',
163+
'./client/sass/main.scss' ]
164+
, 'resolves paths of original sources'
165+
)
166+
167+
t.equal(sm.file, 'map-file-comment.css', 'includes filename of generated file')
168+
t.equal(
169+
sm.mappings
170+
, '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'
171+
, 'includes mappings'
172+
)
173+
t.end()
174+
}, function (err) {
175+
t.error(err, 'read map');
176+
t.end()
177+
});
178+
})
179+
51180
test('\nresolving a /*# sourceMappingURL=data:application/json;base64,... */ style comment inside a given css content', function(t) {
52181
var css = fs.readFileSync(__dirname + '/fixtures/map-file-comment-inline.css', 'utf8')
53-
var conv = convert.fromSource(css, __dirname + '/fixtures')
182+
var conv = convert.fromSource(css)
54183
var sm = conv.toObject()
55184

56185
t.deepEqual(

0 commit comments

Comments
 (0)