Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: thlorenz/convert-source-map
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.9.0
Choose a base ref
...
head repository: thlorenz/convert-source-map
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.0.0
Choose a head ref
  • 5 commits
  • 7 files changed
  • 3 contributors

Commits on Oct 10, 2022

  1. Copy the full SHA
    204bdc8 View commit details
  2. Copy the full SHA
    2572a2f View commit details

Commits on Oct 15, 2022

  1. feat!: Support URI encoded source maps (#75)

    fix!: No longer allow colon between charset and encoding
    feat!: Add capture groups to the commentRegex
    
    Co-authored-by: Blaine Bublitz <blaine.bublitz@gmail.com>
    prantlf and phated authored Oct 15, 2022
    Copy the full SHA
    e6b18c4 View commit details

Commits on Oct 17, 2022

  1. feat(BREAKING): Replace mapFileDir argument with a function for readi…

    …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 <blaine.bublitz@gmail.com>
    prantlf and phated authored Oct 17, 2022
    Copy the full SHA
    2320633 View commit details
  2. 2.0.0

    phated committed Oct 17, 2022
    Copy the full SHA
    f1ed815 View commit details
Showing with 470 additions and 57 deletions.
  1. +1 −1 .github/workflows/ci.yml
  2. +93 −10 README.md
  3. +79 −25 index.js
  4. +3 −6 package.json
  5. +144 −11 test/comment-regex.js
  6. +18 −1 test/convert-source-map.js
  7. +132 −3 test/map-file-comment.js
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node: ["0.10", "0.12", "4", "6", "8", "10", "12", "14", "16", "18"]
node: ["4", "6", "8", "10", "12", "14", "16", "18"]
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
103 changes: 93 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
@@ -33,32 +50,90 @@ Returns source map converter from given object.

Returns source map converter from given json string.

### fromURI(uri)

Returns source map converter from given uri encoded json string.

### fromBase64(base64)

Returns source map converter from given base64 encoded json string.

### fromComment(comment)

Returns source map converter from given base64 encoded json string prefixed with `//# sourceMappingURL=...`.
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.
Finds last sourcemap comment in file and returns source map converter or returns `null` if no source map comment was found.

### fromMapFileSource(source, readMap)

### fromMapFileSource(source, mapFileDir)
Finds last sourcemap comment in file and returns source map converter or returns `null` if no source map comment was found.

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).

The sourcemap will be read from the map file found by parsing `# sourceMappingURL=file` comment. For more info see
fromMapFileComment.
If `readMap` doesn't return a `Promise`, `fromMapFileSource` will return a source map converter synchronously.

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()

@@ -70,6 +145,10 @@ Converts source map to json string. If `space` is given (optional), this will be
[JSON.stringify](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/stringify) when the
JSON string is generated.

### toURI()

Converts source map to uri encoded json string.

### toBase64()

Converts source map to base64 encoded json string.
@@ -81,6 +160,8 @@ Converts source map to an inline comment that can be appended to the source-file
By default, the comment is formatted like: `//# sourceMappingURL=...`, which you would
normally see in a JS source file.

When `options.encoding == 'uri'`, the data will be uri encoded, otherwise they will be base64 encoded.

When `options.multiline == true`, the comment is formatted like: `/*# sourceMappingURL=... */`, which you would find in a CSS source file.

### addProperty(key, value)
@@ -107,6 +188,8 @@ Returns `src` with all source map comments pointing to map files removed.

Provides __a fresh__ RegExp each time it is accessed. Can be used to find source map comments.

Breaks down a source map comment into groups: Groups: 1: media type, 2: MIME type, 3: charset, 4: encoding, 5: data.

### mapFileCommentRegex

Provides __a fresh__ RegExp each time it is accessed. Can be used to find source map comments pointing to map files.
104 changes: 79 additions & 25 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
'use strict';
var fs = require('fs');
var path = require('path');

Object.defineProperty(exports, 'commentRegex', {
get: function getCommentRegex () {
return /^\s*\/(?:\/|\*)[@#]\s+sourceMappingURL=data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(?:.*)$/mg;
// Groups: 1: media type, 2: MIME type, 3: charset, 4: encoding, 5: data.
return /^\s*?\/[\/\*][@#]\s+?sourceMappingURL=data:(((?:application|text)\/json)(?:;charset=([^;,]+?)?)?)?(?:;(base64))?,(.*?)$/mg;
}
});


Object.defineProperty(exports, 'mapFileCommentRegex', {
get: function getMapFileCommentRegex () {
// Matches sourceMappingURL in either // or /* comment styles.
return /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"`]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/){1}[ \t]*$)/mg;
return /(?:\/\/[@#][ \t]+?sourceMappingURL=([^\s'"`]+?)[ \t]*?$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^*]+?)[ \t]*?(?:\*\/){1}[ \t]*?$)/mg;
}
});

@@ -45,29 +45,43 @@ 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);
if (opts.isEncoded) sm = decodeBase64(sm);
if (opts.isJSON || opts.isEncoded) sm = JSON.parse(sm);
if (opts.hasComment) {
sm = stripComment(sm);
}

if (opts.encoding === 'base64') {
sm = decodeBase64(sm);
} else if (opts.encoding === 'uri') {
sm = decodeURIComponent(sm);
}

if (opts.isJSON || opts.encoding) {
sm = JSON.parse(sm);
}

this.sourcemap = sm;
}
@@ -104,10 +118,22 @@ function encodeBase64WithBtoa() {
return btoa(unescape(encodeURIComponent(json)));
}

Converter.prototype.toURI = function () {
var json = this.toJSON();
return encodeURIComponent(json);
};

Converter.prototype.toComment = function (options) {
var base64 = this.toBase64();
var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64;
return options && options.multiline ? '/*# ' + data + ' */' : '//# ' + data;
var encoding, content, data;
if (options != null && options.encoding === 'uri') {
encoding = '';
content = this.toURI();
} else {
encoding = ';base64';
content = this.toBase64();
}
data = 'sourceMappingURL=data:application/json;charset=utf-8' + encoding + ',' + content;
return options != null && options.multiline ? '/*# ' + data + ' */' : '//# ' + data;
};

// returns copy instead of original
@@ -137,20 +163,42 @@ exports.fromJSON = function (json) {
return new Converter(json, { isJSON: true });
};

exports.fromURI = function (uri) {
return new Converter(uri, { encoding: 'uri' });
};

exports.fromBase64 = function (base64) {
return new Converter(base64, { isEncoded: true });
return new Converter(base64, { encoding: 'base64' });
};

exports.fromComment = function (comment) {
var m, encoding;
comment = comment
.replace(/^\/\*/g, '//')
.replace(/\*\/$/g, '');

return new Converter(comment, { isEncoded: true, hasComment: true });
m = exports.commentRegex.exec(comment);
encoding = m && m[4] || 'uri';
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
@@ -160,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) {
9 changes: 3 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "convert-source-map",
"version": "1.9.0",
"version": "2.0.0",
"description": "Converts a source-map from/to different formats and allows adding/changing properties.",
"main": "index.js",
"scripts": {
@@ -30,12 +30,9 @@
},
"license": "MIT",
"engine": {
"node": ">=0.6"
"node": ">=4"
},
"files": [
"index.js"
],
"browser": {
"fs": false
}
]
}
Loading