You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
5.7 KiB
243 lines
5.7 KiB
'use strict'; |
|
|
|
var path = require('path'); |
|
|
|
var fs = require('graceful-fs'); |
|
var nal = require('now-and-later'); |
|
var File = require('vinyl'); |
|
var convert = require('convert-source-map'); |
|
var removeBOM = require('remove-bom-buffer'); |
|
var appendBuffer = require('append-buffer'); |
|
var normalizePath = require('normalize-path'); |
|
|
|
var urlRegex = /^(https?|webpack(-[^:]+)?):\/\//; |
|
|
|
function isRemoteSource(source) { |
|
return source.match(urlRegex); |
|
} |
|
|
|
function parse(data) { |
|
try { |
|
return JSON.parse(removeBOM(data)); |
|
} catch (err) { |
|
// TODO: should this log a debug? |
|
} |
|
} |
|
|
|
function loadSourceMap(file, state, callback) { |
|
// Try to read inline source map |
|
state.map = convert.fromSource(state.content); |
|
|
|
if (state.map) { |
|
state.map = state.map.toObject(); |
|
// Sources in map are relative to the source file |
|
state.path = file.dirname; |
|
state.content = convert.removeComments(state.content); |
|
// Remove source map comment from source |
|
file.contents = new Buffer(state.content, 'utf8'); |
|
return callback(); |
|
} |
|
|
|
// Look for source map comment referencing a source map file |
|
var mapComment = convert.mapFileCommentRegex.exec(state.content); |
|
|
|
var mapFile; |
|
if (mapComment) { |
|
mapFile = path.resolve(file.dirname, mapComment[1] || mapComment[2]); |
|
state.content = convert.removeMapFileComments(state.content); |
|
// Remove source map comment from source |
|
file.contents = new Buffer(state.content, 'utf8'); |
|
} else { |
|
// If no comment try map file with same name as source file |
|
mapFile = file.path + '.map'; |
|
} |
|
|
|
// Sources in external map are relative to map file |
|
state.path = path.dirname(mapFile); |
|
|
|
fs.readFile(mapFile, onRead); |
|
|
|
function onRead(err, data) { |
|
if (err) { |
|
return callback(); |
|
} |
|
state.map = parse(data); |
|
callback(); |
|
} |
|
} |
|
|
|
// Fix source paths and sourceContent for imported source map |
|
function fixImportedSourceMap(file, state, callback) { |
|
if (!state.map) { |
|
return callback(); |
|
} |
|
|
|
state.map.sourcesContent = state.map.sourcesContent || []; |
|
|
|
nal.map(state.map.sources, normalizeSourcesAndContent, callback); |
|
|
|
function assignSourcesContent(sourceContent, idx) { |
|
state.map.sourcesContent[idx] = sourceContent; |
|
} |
|
|
|
function normalizeSourcesAndContent(sourcePath, idx, cb) { |
|
var sourceRoot = state.map.sourceRoot || ''; |
|
var sourceContent = state.map.sourcesContent[idx] || null; |
|
|
|
if (isRemoteSource(sourcePath)) { |
|
assignSourcesContent(sourceContent, idx); |
|
return cb(); |
|
} |
|
|
|
if (state.map.sourcesContent[idx]) { |
|
return cb(); |
|
} |
|
|
|
if (sourceRoot && isRemoteSource(sourceRoot)) { |
|
assignSourcesContent(sourceContent, idx); |
|
return cb(); |
|
} |
|
|
|
var basePath = path.resolve(file.base, sourceRoot); |
|
var absPath = path.resolve(state.path, sourceRoot, sourcePath); |
|
var relPath = path.relative(basePath, absPath); |
|
var unixRelPath = normalizePath(relPath); |
|
|
|
state.map.sources[idx] = unixRelPath; |
|
|
|
if (absPath !== file.path) { |
|
// Load content from file async |
|
return fs.readFile(absPath, onRead); |
|
} |
|
|
|
// If current file: use content |
|
assignSourcesContent(state.content, idx); |
|
cb(); |
|
|
|
function onRead(err, data) { |
|
if (err) { |
|
assignSourcesContent(null, idx); |
|
return cb(); |
|
} |
|
assignSourcesContent(removeBOM(data).toString('utf8'), idx); |
|
cb(); |
|
} |
|
} |
|
} |
|
|
|
function mapsLoaded(file, state, callback) { |
|
|
|
if (!state.map) { |
|
state.map = { |
|
version: 3, |
|
names: [], |
|
mappings: '', |
|
sources: [normalizePath(file.relative)], |
|
sourcesContent: [state.content], |
|
}; |
|
} |
|
|
|
state.map.file = normalizePath(file.relative); |
|
file.sourceMap = state.map; |
|
|
|
callback(); |
|
} |
|
|
|
function addSourceMaps(file, state, callback) { |
|
var tasks = [ |
|
loadSourceMap, |
|
fixImportedSourceMap, |
|
mapsLoaded, |
|
]; |
|
|
|
function apply(fn, key, cb) { |
|
fn(file, state, cb); |
|
} |
|
|
|
nal.mapSeries(tasks, apply, done); |
|
|
|
function done() { |
|
callback(null, file); |
|
} |
|
} |
|
|
|
/* Write Helpers */ |
|
function createSourceMapFile(opts) { |
|
return new File({ |
|
cwd: opts.cwd, |
|
base: opts.base, |
|
path: opts.path, |
|
contents: new Buffer(JSON.stringify(opts.content)), |
|
stat: { |
|
isFile: function() { |
|
return true; |
|
}, |
|
isDirectory: function() { |
|
return false; |
|
}, |
|
isBlockDevice: function() { |
|
return false; |
|
}, |
|
isCharacterDevice: function() { |
|
return false; |
|
}, |
|
isSymbolicLink: function() { |
|
return false; |
|
}, |
|
isFIFO: function() { |
|
return false; |
|
}, |
|
isSocket: function() { |
|
return false; |
|
}, |
|
}, |
|
}); |
|
} |
|
|
|
var needsMultiline = ['.css']; |
|
|
|
function getCommentOptions(extname) { |
|
var opts = { |
|
multiline: (needsMultiline.indexOf(extname) !== -1), |
|
}; |
|
|
|
return opts; |
|
} |
|
|
|
function writeSourceMaps(file, destPath, callback) { |
|
var sourceMapFile; |
|
var commentOpts = getCommentOptions(file.extname); |
|
|
|
var comment; |
|
if (destPath == null) { |
|
// Encode source map into comment |
|
comment = convert.fromObject(file.sourceMap).toComment(commentOpts); |
|
} else { |
|
var mapFile = path.join(destPath, file.relative) + '.map'; |
|
var sourceMapPath = path.join(file.base, mapFile); |
|
|
|
// Create new sourcemap File |
|
sourceMapFile = createSourceMapFile({ |
|
cwd: file.cwd, |
|
base: file.base, |
|
path: sourceMapPath, |
|
content: file.sourceMap, |
|
}); |
|
|
|
var sourcemapLocation = path.relative(file.dirname, sourceMapPath); |
|
|
|
sourcemapLocation = normalizePath(sourcemapLocation); |
|
|
|
comment = convert.generateMapFileComment(sourcemapLocation, commentOpts); |
|
} |
|
|
|
// Append source map comment |
|
file.contents = appendBuffer(file.contents, comment); |
|
|
|
callback(null, file, sourceMapFile); |
|
} |
|
|
|
module.exports = { |
|
addSourceMaps: addSourceMaps, |
|
writeSourceMaps: writeSourceMaps, |
|
};
|
|
|