-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add source map support for preprocessors #5015
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ import add_to_set from './utils/add_to_set'; | |
import check_graph_for_cycles from './utils/check_graph_for_cycles'; | ||
import { print, x, b } from 'code-red'; | ||
import { is_reserved_keyword } from './utils/reserved_keywords'; | ||
import remapping from '@ampproject/remapping'; | ||
|
||
interface ComponentOptions { | ||
namespace?: string; | ||
|
@@ -324,6 +325,35 @@ export default class Component { | |
js.map.sourcesContent = [ | ||
this.source | ||
]; | ||
|
||
if (compile_options.sourceMap) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would probably call this option |
||
if (js.map) { | ||
const pre_remap_sources = js.map.sources; | ||
js.map = remapping([js.map, compile_options.sourceMap], () => null); | ||
// remapper can remove our source if it isn't used (no segments map back to it). It is still handy to have a source | ||
// so we add it back | ||
if (js.map.sources && js.map.sources.length == 0) { | ||
js.map.sources = pre_remap_sources; | ||
} | ||
Object.defineProperties(js.map, { | ||
toString: { | ||
enumerable: false, | ||
value: function toString() { | ||
return JSON.stringify(this); | ||
} | ||
}, | ||
toUrl: { | ||
enumerable: false, | ||
value: function toUrl() { | ||
return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString()); | ||
} | ||
} | ||
}); | ||
} | ||
if (css.map) { | ||
css.map = remapping([css.map, compile_options.sourceMap], () => null); | ||
} | ||
} | ||
} | ||
|
||
return { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
import remapper from '@ampproject/remapping'; | ||
import { decode } from 'sourcemap-codec'; | ||
import { getLocator } from 'locate-character'; | ||
import { GeneratedStringWithMap, offset_source_location } from '../utils/string_with_map'; | ||
|
||
|
||
export interface Processed { | ||
code: string; | ||
map?: object | string; | ||
|
@@ -37,34 +43,7 @@ function parse_attributes(str: string) { | |
interface Replacement { | ||
offset: number; | ||
length: number; | ||
replacement: string; | ||
} | ||
|
||
async function replace_async(str: string, re: RegExp, func: (...any) => Promise<string>) { | ||
const replacements: Array<Promise<Replacement>> = []; | ||
str.replace(re, (...args) => { | ||
replacements.push( | ||
func(...args).then( | ||
res => | ||
({ | ||
offset: args[args.length - 2], | ||
length: args[0].length, | ||
replacement: res, | ||
}) as Replacement | ||
) | ||
); | ||
return ''; | ||
}); | ||
let out = ''; | ||
let last_end = 0; | ||
for (const { offset, length, replacement } of await Promise.all( | ||
replacements | ||
)) { | ||
out += str.slice(last_end, offset) + replacement; | ||
last_end = offset + length; | ||
} | ||
out += str.slice(last_end); | ||
return out; | ||
replacement: GeneratedStringWithMap; | ||
} | ||
|
||
export default async function preprocess( | ||
|
@@ -81,55 +60,124 @@ export default async function preprocess( | |
const markup = preprocessors.map(p => p.markup).filter(Boolean); | ||
const script = preprocessors.map(p => p.script).filter(Boolean); | ||
const style = preprocessors.map(p => p.style).filter(Boolean); | ||
const source_maps: Array<Processed['map']> = []; | ||
|
||
let source_locator: ReturnType<typeof getLocator>; | ||
|
||
function get_replacement(offset: number, original: string, processed: Processed, prefix: string, suffix: string): GeneratedStringWithMap { | ||
const generated_prefix = GeneratedStringWithMap.from_source(filename, prefix, source_locator(offset)); | ||
const generated_suffix = GeneratedStringWithMap.from_source(filename, suffix, source_locator(offset + prefix.length + original.length)); | ||
|
||
let generated; | ||
if (processed.map) { | ||
const full_map = typeof processed.map === "string" ? JSON.parse(processed.map) : processed.map; | ||
const decoded_map = { ...full_map, mappings: decode(full_map.mappings) }; | ||
const processed_offset = source_locator(offset + prefix.length); | ||
generated = GeneratedStringWithMap.from_generated(processed.code, offset_source_location(processed_offset, decoded_map)); | ||
} else { | ||
generated = GeneratedStringWithMap.from_generated(processed.code); | ||
} | ||
const map = generated_prefix.concat(generated).concat(generated_suffix); | ||
return map; | ||
} | ||
|
||
async function replace_async(str: string, re: RegExp, func: (...any) => Promise<GeneratedStringWithMap>): Promise<GeneratedStringWithMap> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this fn There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
const replacement_promises: Array<Promise<Replacement>> = []; | ||
str.replace(re, (...args) => { | ||
replacement_promises.push( | ||
func(...args).then( | ||
(replacement) => | ||
({ | ||
offset: args[args.length - 2], | ||
length: args[0].length, | ||
replacement | ||
}) as Replacement | ||
) | ||
); | ||
return ''; | ||
}); | ||
const replacements = await Promise.all(replacement_promises); | ||
|
||
let out: GeneratedStringWithMap; | ||
let last_end = 0; | ||
for (const { offset, length, replacement } of replacements) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should probably be on the previous line for consistency with the existing code |
||
const content = GeneratedStringWithMap.from_source(filename, str.slice(last_end, offset), source_locator(last_end)); | ||
out = out ? out.concat(content) : content; | ||
out = out.concat(replacement); | ||
last_end = offset + length; | ||
} | ||
const final_content = GeneratedStringWithMap.from_source(filename, str.slice(last_end), source_locator(last_end)); | ||
out = out.concat(final_content); | ||
return out; | ||
} | ||
|
||
for (const fn of markup) { | ||
const processed = await fn({ | ||
content: source, | ||
filename | ||
}); | ||
if (processed && processed.dependencies) dependencies.push(...processed.dependencies); | ||
source = processed ? processed.code : source; | ||
if (processed && processed.map) source_maps.unshift(processed.map); | ||
} | ||
|
||
for (const fn of script) { | ||
source = await replace_async( | ||
source_locator = getLocator(source); | ||
const res = await replace_async( | ||
source, | ||
/<!--[^]*?-->|<script(\s[^]*?)?>([^]*?)<\/script>/gi, | ||
async (match, attributes = '', content) => { | ||
async (match, attributes = '', content, offset) => { | ||
const no_change = () => GeneratedStringWithMap.from_source(filename, match, source_locator(offset)); | ||
|
||
if (!attributes && !content) { | ||
return match; | ||
return no_change(); | ||
} | ||
|
||
attributes = attributes || ''; | ||
const processed = await fn({ | ||
content, | ||
attributes: parse_attributes(attributes), | ||
filename | ||
}); | ||
if (processed && processed.dependencies) dependencies.push(...processed.dependencies); | ||
return processed ? `<script${attributes}>${processed.code}</script>` : match; | ||
|
||
if (!processed) return no_change(); | ||
if (processed.dependencies) dependencies.push(...processed.dependencies); | ||
return get_replacement(offset, content, processed, `<script${attributes}>`, `</script>`); | ||
} | ||
); | ||
source = res.generated; | ||
source_maps.unshift(res.as_sourcemap()); | ||
} | ||
|
||
for (const fn of style) { | ||
source = await replace_async( | ||
source_locator = getLocator(source); | ||
const res = await replace_async( | ||
source, | ||
/<!--[^]*?-->|<style(\s[^]*?)?>([^]*?)<\/style>/gi, | ||
async (match, attributes = '', content) => { | ||
async (match, attributes = '', content, offset) => { | ||
const no_change = () => GeneratedStringWithMap.from_source(filename, match, source_locator(offset)); | ||
if (!attributes && !content) { | ||
return match; | ||
return no_change(); | ||
} | ||
|
||
const processed: Processed = await fn({ | ||
content, | ||
attributes: parse_attributes(attributes), | ||
filename | ||
}); | ||
if (processed && processed.dependencies) dependencies.push(...processed.dependencies); | ||
return processed ? `<style${attributes}>${processed.code}</style>` : match; | ||
|
||
if (!processed) return no_change(); | ||
if (processed.dependencies) dependencies.push(...processed.dependencies); | ||
return get_replacement(offset, content, processed, `<style${attributes}>`, `</style>`); | ||
} | ||
); | ||
|
||
source = res.generated; | ||
source_maps.unshift(res.as_sourcemap()); | ||
} | ||
|
||
const map: ReturnType<typeof remapper> = source_maps.length == 0 ? null : remapper(source_maps as any, () => null); | ||
return { | ||
// TODO return separated output, in future version where svelte.compile supports it: | ||
// style: { code: styleCode, map: styleMap }, | ||
|
@@ -138,7 +186,7 @@ export default async function preprocess( | |
|
||
code: source, | ||
dependencies: [...new Set(dependencies)], | ||
|
||
map, | ||
toString() { | ||
return source; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you move this up a few lines so that it's in alphabetical order?