Error: Transformation map 0 must have exactly one source file
milahu opened this issue · 2 comments
please explain this error
Error: Transformation map 0 must have exactly one source file.
Did you specify these with the most recent transformation maps first?
this is required for all but the last/oldest sourcemap (index -1)
i mean, why must maps 0 to -2 have only one source file?
this looks more like 'not implemented' than 'error'
put differently:
why does remapper not support convergent transform chains?
= multiple input files + single output file, arranged in a tree structure
also, if i recall right, only the first entry of maps[-1].sources
is propagated to result.sources. doesnt that break sourcesIdx values?
reproduce
const remapper = require('@ampproject/remapping');
// segment =
// [ columnIdx_gen ]
// | [ columnIdx_gen, sourcesIdx, lineIdx, columnIdx ]
// | [ columnIdx_gen, sourcesIdx, lineIdx, columnIdx, namesIdx ]
const trigger_error = 1;
const sourcemapList = [
// map 0
{
version: 3,
sources: trigger_error
? [
'file-3', // src 0
'file-4', // src 1
]
: [
'file-3',
],
names: [],
mappings: [
trigger_error
? [
[0, 0, 0, 0], // col 0 = src 0
[1, 1, 0, 0], // col 1 = src 1
]
: [
[0, 0, 0, 0],
]
]
},
// map 1
{
version: 3,
sources: [
'file-1', // src 0
'file-2', // src 1
],
names: [],
mappings: [
[
[0, 0, 0, 0], // col 0 = src 0
[1, 1, 0, 0], // col 1 = src 1
]
]
}
];
const sourcemap = remapper(
sourcemapList,
function loader(file) {
return null;
}
);
console.dir(sourcemap);
actual result with trigger_error = 0
:
sources: [
'file-1', // src 0 of map 1
],
expected result:
sources: [
'file-1',
'file-2',
'file-3',
'file-4',
],
Ok, so there are a few parts here.
i mean, why must maps 0 to -2 have only one source file?
This is only a requirement when using the array form. The array form is explicitly for multiple transforms of a single file. Eg, think about running a minifier on a file, or a babel transform. These don't combine multiple source files into one, only a single file into a single file.
The array form provides this as a convenience. You can think of it a bit like minify(transform(file))
mapping to [minifiedMap, transformedMap, fileMap]
. Here, minifiedMap
has no other inputs, and the same with transformedMap
. And because the next item in the array is a single map, we can assume that minifiedMap
's input is transformedMap
, and transformedMap
's input is fileMap
.
You'll need to use multiple calls to remapping
to support multiple input files with multiple input files.
also, if i recall right, only the first entry of maps[-1].sources is propagated to result.sources. doesn't that break sourcesIdx values?
I think this is because your first sourcemap (in the trigger_error = 0
case) only has a single sourcemap segment. Since you only reference line 0 of of the next sourcemap, you only take the mapping from file-1
. If you were to include a second segment for [1, 0, 0, 1]
, you'd see both file-1
and file-2
.
You'll need to use multiple calls to
remapping
to support multiple input files with multiple input files.
but then i still have the same limitation:
all except the last map must have only one source file
now solved this with the loader interface
const remapping = require('@ampproject/remapping');
const MagicString = require('magic-string');
const SourcemapCodec = require('sourcemap-codec');
function _dir(obj) {
console.log(require('util').inspect(obj, false, null, true));
}
function push(bundle, filename, source) {
bundle.addSource({
filename,
content: new MagicString(source),
separator: '\n',
//separator: '', // ERROR. probably a bug in magic-string
});
// mutate global
code[filename] = source;
}
function done(bundle, filename, extraOptions = {}) {
// mutate globals
code[filename] = bundle.toString();
map[filename] = bundle.generateDecodedMap({
file: filename,
includeContent: true,
hires: false,
...extraOptions,
});
}
const code = {};
const map = {};
let b, s;
b = new MagicString.Bundle();
push(b, 'src-8', ' interface');
push(b, 'src-7', ' loader');
push(b, 'src-6', ' the');
done(b, 'bundle-3');
b = new MagicString.Bundle();
push(b, 'bundle-3', code['bundle-3']);
push(b, 'src-5', ' with');
push(b, 'src-4', ' works');
push(b, 'src-3', ' this');
done(b, 'bundle-2');
b = new MagicString.Bundle();
push(b, 'bundle-2', code['bundle-2']);
push(b, 'src-2', ' world');
push(b, 'src-1', ' hello');
done(b, 'bundle-1');
//console.log('code:');
//_dir(code);
//console.log('sources:');
//_dir(Object.keys(map).map(f => [f, map[f].sources]));
/*
map-1 map-2 map-3
^ ^ ^
bundle-1 < bundle-2 < bundle-3 <
src-1 src-3 src-6
src-2 src-4 src-7
src-5 src-8
*/
// use remapping with loader interface
const map_combined_1 = remapping(
map['bundle-1'],
function loader(filename) {
if (map[filename])
console.log('loading map for '+filename);
else
console.log('found original source '+filename);
return map[filename] || null;
}
);
map_combined_1.mappings = SourcemapCodec.decode(map_combined_1.mappings);
console.log('combined sources:');
_dir(map_combined_1.sources);
console.log('combined mappings:');
_dir(map_combined_1.mappings);
// loop mappings
console.log('code = '+JSON.stringify(code['bundle-1']));
const line_list = code['bundle-1'].split('\n');
map_combined_1.mappings.forEach((segment_list, output_line) => {
segment_list.forEach(([output_column, source, line, column, name], segment) => {
const last_output_column = segment_list[segment + 1]
? segment_list[segment + 1][0]
: undefined;
if (line_list[output_line])
console.log(
'token '+JSON.stringify(line_list[output_line].slice(output_column, last_output_column))
+' <- '+map_combined_1.sources[source]
//+' @ line '+line+' column '+column
+' = '+JSON.stringify(code[map_combined_1.sources[source]])
);
else
console.log('error: output_line '+output_line+' not in code');
})
});