mirror of https://github.com/sveltejs/svelte
commit
fd843a1d77
@ -0,0 +1,88 @@
|
||||
import { decode as decode_mappings } from 'sourcemap-codec';
|
||||
import { Processed } from './types';
|
||||
|
||||
/**
|
||||
* Import decoded sourcemap from mozilla/source-map/SourceMapGenerator
|
||||
* Forked from source-map/lib/source-map-generator.js
|
||||
* from methods _serializeMappings and toJSON.
|
||||
* We cannot use source-map.d.ts types, because we access hidden properties.
|
||||
*/
|
||||
function decoded_sourcemap_from_generator(generator: any) {
|
||||
let previous_generated_line = 1;
|
||||
const converted_mappings = [[]];
|
||||
let result_line;
|
||||
let result_segment;
|
||||
let mapping;
|
||||
|
||||
const source_idx = generator._sources.toArray()
|
||||
.reduce((acc, val, idx) => (acc[val] = idx, acc), {});
|
||||
|
||||
const name_idx = generator._names.toArray()
|
||||
.reduce((acc, val, idx) => (acc[val] = idx, acc), {});
|
||||
|
||||
const mappings = generator._mappings.toArray();
|
||||
result_line = converted_mappings[0];
|
||||
|
||||
for (let i = 0, len = mappings.length; i < len; i++) {
|
||||
mapping = mappings[i];
|
||||
|
||||
if (mapping.generatedLine > previous_generated_line) {
|
||||
while (mapping.generatedLine > previous_generated_line) {
|
||||
converted_mappings.push([]);
|
||||
previous_generated_line++;
|
||||
}
|
||||
result_line = converted_mappings[mapping.generatedLine - 1]; // line is one-based
|
||||
} else if (i > 0) {
|
||||
const previous_mapping = mappings[i - 1];
|
||||
if (
|
||||
// sorted by selectivity
|
||||
mapping.generatedColumn === previous_mapping.generatedColumn &&
|
||||
mapping.originalColumn === previous_mapping.originalColumn &&
|
||||
mapping.name === previous_mapping.name &&
|
||||
mapping.generatedLine === previous_mapping.generatedLine &&
|
||||
mapping.originalLine === previous_mapping.originalLine &&
|
||||
mapping.source === previous_mapping.source
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result_line.push([mapping.generatedColumn]);
|
||||
result_segment = result_line[result_line.length - 1];
|
||||
|
||||
if (mapping.source != null) {
|
||||
result_segment.push(...[
|
||||
source_idx[mapping.source],
|
||||
mapping.originalLine - 1, // line is one-based
|
||||
mapping.originalColumn
|
||||
]);
|
||||
if (mapping.name != null) {
|
||||
result_segment.push(name_idx[mapping.name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const map = {
|
||||
version: generator._version,
|
||||
sources: generator._sources.toArray(),
|
||||
names: generator._names.toArray(),
|
||||
mappings: converted_mappings
|
||||
};
|
||||
if (generator._file != null) {
|
||||
(map as any).file = generator._file;
|
||||
}
|
||||
// not needed: map.sourcesContent and map.sourceRoot
|
||||
return map;
|
||||
}
|
||||
|
||||
export function decode_map(processed: Processed) {
|
||||
let decoded_map = typeof processed.map === 'string' ? JSON.parse(processed.map) : processed.map;
|
||||
if (typeof(decoded_map.mappings) === 'string') {
|
||||
decoded_map.mappings = decode_mappings(decoded_map.mappings);
|
||||
}
|
||||
if ((decoded_map as any)._mappings && decoded_map.constructor.name === 'SourceMapGenerator') {
|
||||
// import decoded sourcemap from mozilla/source-map/SourceMapGenerator
|
||||
decoded_map = decoded_sourcemap_from_generator(decoded_map);
|
||||
}
|
||||
|
||||
return decoded_map;
|
||||
}
|
||||
@ -1,327 +1,230 @@
|
||||
import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
|
||||
import { decode as decode_mappings } from 'sourcemap-codec';
|
||||
import { getLocator } from 'locate-character';
|
||||
import {
|
||||
StringWithSourcemap,
|
||||
sourcemap_add_offset,
|
||||
combine_sourcemaps,
|
||||
parse_attached_sourcemap
|
||||
} from '../utils/string_with_sourcemap';
|
||||
|
||||
export interface Processed {
|
||||
code: string;
|
||||
map?: string | object; // we are opaque with the type here to avoid dependency on the remapping module for our public types.
|
||||
import { MappedCode, SourceLocation, parse_attached_sourcemap, sourcemap_add_offset, combine_sourcemaps } from '../utils/mapped_code';
|
||||
import { decode_map } from './decode_sourcemap';
|
||||
import { replace_in_code, slice_source } from './replace_in_code';
|
||||
import { MarkupPreprocessor, Source, Preprocessor, PreprocessorGroup, Processed } from './types';
|
||||
|
||||
interface SourceUpdate {
|
||||
string?: string;
|
||||
map?: DecodedSourceMap;
|
||||
dependencies?: string[];
|
||||
}
|
||||
|
||||
export interface PreprocessorGroup {
|
||||
markup?: (options: {
|
||||
content: string;
|
||||
filename: string;
|
||||
}) => Processed | Promise<Processed>;
|
||||
style?: Preprocessor;
|
||||
script?: Preprocessor;
|
||||
}
|
||||
|
||||
export type Preprocessor = (options: {
|
||||
content: string;
|
||||
attributes: Record<string, string | boolean>;
|
||||
filename?: string;
|
||||
}) => Processed | Promise<Processed>;
|
||||
|
||||
function parse_attributes(str: string) {
|
||||
const attrs = {};
|
||||
str.split(/\s+/).filter(Boolean).forEach(attr => {
|
||||
const p = attr.indexOf('=');
|
||||
if (p === -1) {
|
||||
attrs[attr] = true;
|
||||
} else {
|
||||
attrs[attr.slice(0, p)] = '\'"'.includes(attr[p + 1]) ?
|
||||
attr.slice(p + 2, -1) :
|
||||
attr.slice(p + 1);
|
||||
}
|
||||
});
|
||||
return attrs;
|
||||
}
|
||||
|
||||
function get_file_basename(filename: string) {
|
||||
return filename.split(/[/\\]/).pop();
|
||||
}
|
||||
|
||||
interface Replacement {
|
||||
offset: number;
|
||||
length: number;
|
||||
replacement: StringWithSourcemap;
|
||||
}
|
||||
/**
|
||||
* Represents intermediate states of the preprocessing.
|
||||
*/
|
||||
class PreprocessResult implements Source {
|
||||
// sourcemap_list is sorted in reverse order from last map (index 0) to first map (index -1)
|
||||
// so we use sourcemap_list.unshift() to add new maps
|
||||
// https://github.com/ampproject/remapping#multiple-transformations-of-a-file
|
||||
sourcemap_list: Array<DecodedSourceMap | RawSourceMap> = [];
|
||||
dependencies: string[] = [];
|
||||
file_basename: string;
|
||||
|
||||
async function replace_async(
|
||||
file_basename: string,
|
||||
source: string,
|
||||
get_location: ReturnType<typeof getLocator>,
|
||||
re: RegExp,
|
||||
func: (...any) => Promise<StringWithSourcemap>
|
||||
): Promise<StringWithSourcemap> {
|
||||
const replacements: Array<Promise<Replacement>> = [];
|
||||
source.replace(re, (...args) => {
|
||||
replacements.push(
|
||||
func(...args).then(
|
||||
res =>
|
||||
({
|
||||
offset: args[args.length - 2],
|
||||
length: args[0].length,
|
||||
replacement: res
|
||||
}) as Replacement
|
||||
)
|
||||
);
|
||||
return '';
|
||||
});
|
||||
const out = new StringWithSourcemap();
|
||||
let last_end = 0;
|
||||
for (const { offset, length, replacement } of await Promise.all(
|
||||
replacements
|
||||
)) {
|
||||
// content = unchanged source characters before the replaced segment
|
||||
const content = StringWithSourcemap.from_source(
|
||||
file_basename, source.slice(last_end, offset), get_location(last_end));
|
||||
out.concat(content).concat(replacement);
|
||||
last_end = offset + length;
|
||||
get_location: ReturnType<typeof getLocator>;
|
||||
|
||||
constructor(public source: string, public filename: string) {
|
||||
this.update_source({ string: source });
|
||||
|
||||
// preprocess source must be relative to itself or equal null
|
||||
this.file_basename = filename == null ? null : get_file_basename(filename);
|
||||
}
|
||||
// final_content = unchanged source characters after last replaced segment
|
||||
const final_content = StringWithSourcemap.from_source(
|
||||
file_basename, source.slice(last_end), get_location(last_end));
|
||||
return out.concat(final_content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import decoded sourcemap from mozilla/source-map/SourceMapGenerator
|
||||
* Forked from source-map/lib/source-map-generator.js
|
||||
* from methods _serializeMappings and toJSON.
|
||||
* We cannot use source-map.d.ts types, because we access hidden properties.
|
||||
*/
|
||||
function decoded_sourcemap_from_generator(generator: any) {
|
||||
let previous_generated_line = 1;
|
||||
const converted_mappings = [[]];
|
||||
let result_line;
|
||||
let result_segment;
|
||||
let mapping;
|
||||
|
||||
const source_idx = generator._sources.toArray()
|
||||
.reduce((acc, val, idx) => (acc[val] = idx, acc), {});
|
||||
|
||||
const name_idx = generator._names.toArray()
|
||||
.reduce((acc, val, idx) => (acc[val] = idx, acc), {});
|
||||
|
||||
const mappings = generator._mappings.toArray();
|
||||
result_line = converted_mappings[0];
|
||||
|
||||
for (let i = 0, len = mappings.length; i < len; i++) {
|
||||
mapping = mappings[i];
|
||||
|
||||
if (mapping.generatedLine > previous_generated_line) {
|
||||
while (mapping.generatedLine > previous_generated_line) {
|
||||
converted_mappings.push([]);
|
||||
previous_generated_line++;
|
||||
}
|
||||
result_line = converted_mappings[mapping.generatedLine - 1]; // line is one-based
|
||||
} else if (i > 0) {
|
||||
const previous_mapping = mappings[i - 1];
|
||||
if (
|
||||
// sorted by selectivity
|
||||
mapping.generatedColumn === previous_mapping.generatedColumn &&
|
||||
mapping.originalColumn === previous_mapping.originalColumn &&
|
||||
mapping.name === previous_mapping.name &&
|
||||
mapping.generatedLine === previous_mapping.generatedLine &&
|
||||
mapping.originalLine === previous_mapping.originalLine &&
|
||||
mapping.source === previous_mapping.source
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
update_source({ string: source, map, dependencies }: SourceUpdate) {
|
||||
if (source != null) {
|
||||
this.source = source;
|
||||
this.get_location = getLocator(source);
|
||||
}
|
||||
|
||||
if (map) {
|
||||
this.sourcemap_list.unshift(map);
|
||||
}
|
||||
result_line.push([mapping.generatedColumn]);
|
||||
result_segment = result_line[result_line.length - 1];
|
||||
|
||||
if (mapping.source != null) {
|
||||
result_segment.push(...[
|
||||
source_idx[mapping.source],
|
||||
mapping.originalLine - 1, // line is one-based
|
||||
mapping.originalColumn
|
||||
]);
|
||||
if (mapping.name != null) {
|
||||
result_segment.push(name_idx[mapping.name]);
|
||||
}
|
||||
|
||||
if (dependencies) {
|
||||
this.dependencies.push(...dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
const map = {
|
||||
version: generator._version,
|
||||
sources: generator._sources.toArray(),
|
||||
names: generator._names.toArray(),
|
||||
mappings: converted_mappings
|
||||
};
|
||||
if (generator._file != null) {
|
||||
(map as any).file = generator._file;
|
||||
to_processed(): Processed {
|
||||
// Combine all the source maps for each preprocessor function into one
|
||||
const map: RawSourceMap = combine_sourcemaps(this.file_basename, this.sourcemap_list);
|
||||
|
||||
return {
|
||||
// TODO return separated output, in future version where svelte.compile supports it:
|
||||
// style: { code: styleCode, map: styleMap },
|
||||
// script { code: scriptCode, map: scriptMap },
|
||||
// markup { code: markupCode, map: markupMap },
|
||||
|
||||
code: this.source,
|
||||
dependencies: [...new Set(this.dependencies)],
|
||||
map: map as object,
|
||||
toString: () => this.source
|
||||
};
|
||||
}
|
||||
// not needed: map.sourcesContent and map.sourceRoot
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a preprocessor output and its leading prefix and trailing suffix into StringWithSourceMap
|
||||
* Convert preprocessor output for the tag content into MappedCode
|
||||
*/
|
||||
function get_replacement(
|
||||
file_basename: string,
|
||||
offset: number,
|
||||
get_location: ReturnType<typeof getLocator>,
|
||||
original: string,
|
||||
processed: Processed,
|
||||
prefix: string,
|
||||
suffix: string,
|
||||
tag_name: 'script' | 'style'
|
||||
): StringWithSourcemap {
|
||||
|
||||
// Convert the unchanged prefix and suffix to StringWithSourcemap
|
||||
const prefix_with_map = StringWithSourcemap.from_source(
|
||||
file_basename, prefix, get_location(offset));
|
||||
const suffix_with_map = StringWithSourcemap.from_source(
|
||||
file_basename, suffix, get_location(offset + prefix.length + original.length));
|
||||
|
||||
parse_attached_sourcemap(processed, tag_name);
|
||||
|
||||
// Convert the preprocessed code and its sourcemap to a StringWithSourcemap
|
||||
function processed_content_to_code(processed: Processed, location: SourceLocation, file_basename: string): MappedCode {
|
||||
// Convert the preprocessed code and its sourcemap to a MappedCode
|
||||
let decoded_map: DecodedSourceMap;
|
||||
if (processed.map) {
|
||||
decoded_map = typeof processed.map === 'string' ? JSON.parse(processed.map) : processed.map;
|
||||
if (typeof(decoded_map.mappings) === 'string') {
|
||||
decoded_map.mappings = decode_mappings(decoded_map.mappings);
|
||||
}
|
||||
if ((decoded_map as any)._mappings && decoded_map.constructor.name === 'SourceMapGenerator') {
|
||||
// import decoded sourcemap from mozilla/source-map/SourceMapGenerator
|
||||
decoded_map = decoded_sourcemap_from_generator(decoded_map);
|
||||
}
|
||||
decoded_map = decode_map(processed);
|
||||
|
||||
// offset only segments pointing at original component source
|
||||
const source_index = decoded_map.sources.indexOf(file_basename);
|
||||
if (source_index !== -1) {
|
||||
sourcemap_add_offset(decoded_map, get_location(offset + prefix.length), source_index);
|
||||
sourcemap_add_offset(decoded_map, location, source_index);
|
||||
}
|
||||
}
|
||||
const processed_with_map = StringWithSourcemap.from_processed(processed.code, decoded_map);
|
||||
|
||||
// Surround the processed code with the prefix and suffix, retaining valid sourcemappings
|
||||
return prefix_with_map.concat(processed_with_map).concat(suffix_with_map);
|
||||
return MappedCode.from_processed(processed.code, decoded_map);
|
||||
}
|
||||
|
||||
export default async function preprocess(
|
||||
source: string,
|
||||
preprocessor: PreprocessorGroup | PreprocessorGroup[],
|
||||
options?: { filename?: string }
|
||||
) {
|
||||
// @ts-ignore todo: doublecheck
|
||||
const filename = (options && options.filename) || preprocessor.filename; // legacy
|
||||
const dependencies = [];
|
||||
/**
|
||||
* Given the whole tag including content, return a `MappedCode`
|
||||
* representing the tag content replaced with `processed`.
|
||||
*/
|
||||
function processed_tag_to_code(
|
||||
processed: Processed,
|
||||
tag_name: 'style' | 'script',
|
||||
attributes: string,
|
||||
source: Source
|
||||
): MappedCode {
|
||||
const { file_basename, get_location } = source;
|
||||
|
||||
// preprocess source must be relative to itself or equal null
|
||||
const file_basename = filename == null ? null : get_file_basename(filename);
|
||||
const build_mapped_code = (code: string, offset: number) =>
|
||||
MappedCode.from_source(slice_source(code, offset, source));
|
||||
|
||||
const preprocessors = preprocessor
|
||||
? Array.isArray(preprocessor) ? preprocessor : [preprocessor]
|
||||
: [];
|
||||
const tag_open = `<${tag_name}${attributes || ''}>`;
|
||||
const tag_close = `</${tag_name}>`;
|
||||
|
||||
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 tag_open_code = build_mapped_code(tag_open, 0);
|
||||
const tag_close_code = build_mapped_code(tag_close, tag_open.length + source.source.length);
|
||||
|
||||
// sourcemap_list is sorted in reverse order from last map (index 0) to first map (index -1)
|
||||
// so we use sourcemap_list.unshift() to add new maps
|
||||
// https://github.com/ampproject/remapping#multiple-transformations-of-a-file
|
||||
const sourcemap_list: Array<DecodedSourceMap | RawSourceMap> = [];
|
||||
parse_attached_sourcemap(processed, tag_name);
|
||||
|
||||
// TODO keep track: what preprocessor generated what sourcemap? to make debugging easier = detect low-resolution sourcemaps in fn combine_mappings
|
||||
const content_code = processed_content_to_code(processed, get_location(tag_open.length), file_basename);
|
||||
|
||||
for (const fn of markup) {
|
||||
return tag_open_code.concat(content_code).concat(tag_close_code);
|
||||
}
|
||||
|
||||
// run markup preprocessor
|
||||
const processed = await fn({
|
||||
content: source,
|
||||
function parse_tag_attributes(str: string) {
|
||||
// note: won't work with attribute values containing spaces.
|
||||
return str
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.reduce((attrs, attr) => {
|
||||
const i = attr.indexOf('=');
|
||||
const [key, value] = i > 0 ? [attr.slice(0, i), attr.slice(i+1)] : [attr];
|
||||
const [, unquoted] = (value && value.match(/^['"](.*)['"]$/)) || [];
|
||||
|
||||
return { ...attrs, [key]: unquoted ?? value ?? true };
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the updates required to process all instances of the specified tag.
|
||||
*/
|
||||
async function process_tag(
|
||||
tag_name: 'style' | 'script',
|
||||
preprocessor: Preprocessor,
|
||||
source: Source
|
||||
): Promise<SourceUpdate> {
|
||||
const { filename } = source;
|
||||
const tag_regex =
|
||||
tag_name === 'style'
|
||||
? /<!--[^]*?-->|<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi
|
||||
: /<!--[^]*?-->|<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi;
|
||||
|
||||
const dependencies: string[] = [];
|
||||
|
||||
async function process_single_tag(
|
||||
tag_with_content: string,
|
||||
attributes = '',
|
||||
content = '',
|
||||
tag_offset: number
|
||||
): Promise<MappedCode> {
|
||||
const no_change = () => MappedCode.from_source(slice_source(tag_with_content, tag_offset, source));
|
||||
|
||||
if (!attributes && !content) return no_change();
|
||||
|
||||
const processed = await preprocessor({
|
||||
content: content || '',
|
||||
attributes: parse_tag_attributes(attributes || ''),
|
||||
filename
|
||||
});
|
||||
|
||||
if (!processed) continue;
|
||||
|
||||
if (!processed) return no_change();
|
||||
if (processed.dependencies) dependencies.push(...processed.dependencies);
|
||||
source = processed.code;
|
||||
if (processed.map) {
|
||||
sourcemap_list.unshift(
|
||||
typeof(processed.map) === 'string'
|
||||
if (!processed.map && processed.code === content) return no_change();
|
||||
|
||||
return processed_tag_to_code(processed, tag_name, attributes, slice_source(content, tag_offset, source));
|
||||
}
|
||||
|
||||
const { string, map } = await replace_in_code(tag_regex, process_single_tag, source);
|
||||
|
||||
return { string, map, dependencies };
|
||||
}
|
||||
|
||||
async function process_markup(filename: string, process: MarkupPreprocessor, source: Source) {
|
||||
const processed = await process({
|
||||
content: source.source,
|
||||
filename
|
||||
});
|
||||
|
||||
if (processed) {
|
||||
return {
|
||||
string: processed.code,
|
||||
map: processed.map
|
||||
? // TODO: can we use decode_sourcemap?
|
||||
typeof processed.map === 'string'
|
||||
? JSON.parse(processed.map)
|
||||
: processed.map
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
dependencies: processed.dependencies
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function preprocess_tag_content(tag_name: 'style' | 'script', preprocessor: Preprocessor) {
|
||||
const get_location = getLocator(source);
|
||||
const tag_regex = tag_name === 'style'
|
||||
? /<!--[^]*?-->|<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi
|
||||
: /<!--[^]*?-->|<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi;
|
||||
export default async function preprocess(
|
||||
source: string,
|
||||
preprocessor: PreprocessorGroup | PreprocessorGroup[],
|
||||
options?: { filename?: string }
|
||||
): Promise<Processed> {
|
||||
// @ts-ignore todo: doublecheck
|
||||
const filename = (options && options.filename) || preprocessor.filename; // legacy
|
||||
|
||||
const preprocessors = preprocessor ? (Array.isArray(preprocessor) ? preprocessor : [preprocessor]) : [];
|
||||
|
||||
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 result = new PreprocessResult(source, filename);
|
||||
|
||||
const res = await replace_async(
|
||||
file_basename,
|
||||
source,
|
||||
get_location,
|
||||
tag_regex,
|
||||
async (match, attributes = '', content = '', offset) => {
|
||||
const no_change = () => StringWithSourcemap.from_source(
|
||||
file_basename, match, get_location(offset));
|
||||
if (!attributes && !content) {
|
||||
return no_change();
|
||||
}
|
||||
attributes = attributes || '';
|
||||
content = content || '';
|
||||
|
||||
// run script preprocessor
|
||||
const processed = await preprocessor({
|
||||
content,
|
||||
attributes: parse_attributes(attributes),
|
||||
filename
|
||||
});
|
||||
if (processed && processed.dependencies) {
|
||||
dependencies.push(...processed.dependencies);
|
||||
}
|
||||
if (!processed || !processed.map && processed.code === content) {
|
||||
return no_change();
|
||||
}
|
||||
return get_replacement(file_basename, offset, get_location, content, processed, `<${tag_name}${attributes}>`, `</${tag_name}>`, tag_name);
|
||||
}
|
||||
);
|
||||
source = res.string;
|
||||
sourcemap_list.unshift(res.map);
|
||||
// TODO keep track: what preprocessor generated what sourcemap?
|
||||
// to make debugging easier = detect low-resolution sourcemaps in fn combine_mappings
|
||||
|
||||
for (const process of markup) {
|
||||
result.update_source(await process_markup(filename, process, result));
|
||||
}
|
||||
|
||||
for (const fn of script) {
|
||||
await preprocess_tag_content('script', fn);
|
||||
for (const process of script) {
|
||||
result.update_source(await process_tag('script', process, result));
|
||||
}
|
||||
|
||||
for (const fn of style) {
|
||||
await preprocess_tag_content('style', fn);
|
||||
for (const preprocess of style) {
|
||||
result.update_source(await process_tag('style', preprocess, result));
|
||||
}
|
||||
|
||||
// Combine all the source maps for each preprocessor function into one
|
||||
const map: RawSourceMap = combine_sourcemaps(
|
||||
file_basename,
|
||||
sourcemap_list
|
||||
);
|
||||
|
||||
return {
|
||||
// TODO return separated output, in future version where svelte.compile supports it:
|
||||
// style: { code: styleCode, map: styleMap },
|
||||
// script { code: scriptCode, map: scriptMap },
|
||||
// markup { code: markupCode, map: markupMap },
|
||||
|
||||
code: source,
|
||||
dependencies: [...new Set(dependencies)],
|
||||
map: (map as object),
|
||||
toString() {
|
||||
return source;
|
||||
}
|
||||
};
|
||||
return result.to_processed();
|
||||
}
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
import { MappedCode } from '../utils/mapped_code';
|
||||
import { Source } from './types';
|
||||
|
||||
interface Replacement {
|
||||
offset: number;
|
||||
length: number;
|
||||
replacement: MappedCode;
|
||||
}
|
||||
|
||||
export function slice_source(
|
||||
code_slice: string,
|
||||
offset: number,
|
||||
{ file_basename, filename, get_location }: Source
|
||||
): Source {
|
||||
return {
|
||||
source: code_slice,
|
||||
get_location: (index: number) => get_location(index + offset),
|
||||
file_basename,
|
||||
filename
|
||||
};
|
||||
}
|
||||
|
||||
function calculate_replacements(
|
||||
re: RegExp,
|
||||
get_replacement: (...match: any[]) => Promise<MappedCode>,
|
||||
source: string
|
||||
) {
|
||||
const replacements: Array<Promise<Replacement>> = [];
|
||||
|
||||
source.replace(re, (...match) => {
|
||||
replacements.push(
|
||||
get_replacement(...match).then(
|
||||
replacement => {
|
||||
const matched_string = match[0];
|
||||
const offset = match[match.length-2];
|
||||
|
||||
return ({ offset, length: matched_string.length, replacement });
|
||||
}
|
||||
)
|
||||
);
|
||||
return '';
|
||||
});
|
||||
|
||||
return Promise.all(replacements);
|
||||
}
|
||||
|
||||
function perform_replacements(
|
||||
replacements: Replacement[],
|
||||
source: Source
|
||||
): MappedCode {
|
||||
const out = new MappedCode();
|
||||
let last_end = 0;
|
||||
|
||||
for (const { offset, length, replacement } of replacements) {
|
||||
const unchanged_prefix = MappedCode.from_source(
|
||||
slice_source(source.source.slice(last_end, offset), last_end, source)
|
||||
);
|
||||
out.concat(unchanged_prefix).concat(replacement);
|
||||
last_end = offset + length;
|
||||
}
|
||||
|
||||
const unchanged_suffix = MappedCode.from_source(slice_source(source.source.slice(last_end), last_end, source));
|
||||
|
||||
return out.concat(unchanged_suffix);
|
||||
}
|
||||
|
||||
export async function replace_in_code(
|
||||
regex: RegExp,
|
||||
get_replacement: (...match: any[]) => Promise<MappedCode>,
|
||||
location: Source
|
||||
): Promise<MappedCode> {
|
||||
const replacements = await calculate_replacements(regex, get_replacement, location.source);
|
||||
|
||||
return perform_replacements(replacements, location);
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
import { Location } from 'locate-character';
|
||||
|
||||
export interface Source {
|
||||
source: string;
|
||||
get_location: (search: number) => Location;
|
||||
file_basename: string;
|
||||
filename: string;
|
||||
}
|
||||
|
||||
export interface Processed {
|
||||
code: string;
|
||||
map?: string | object; // we are opaque with the type here to avoid dependency on the remapping module for our public types.
|
||||
dependencies?: string[];
|
||||
toString?: () => string;
|
||||
}
|
||||
|
||||
export type MarkupPreprocessor = (options: {
|
||||
content: string;
|
||||
filename: string;
|
||||
}) => Processed | Promise<Processed>;
|
||||
|
||||
export type Preprocessor = (options: {
|
||||
content: string;
|
||||
attributes: Record<string, string | boolean>;
|
||||
filename?: string;
|
||||
}) => Processed | Promise<Processed>;
|
||||
|
||||
export interface PreprocessorGroup {
|
||||
markup?: MarkupPreprocessor;
|
||||
style?: Preprocessor;
|
||||
script?: Preprocessor;
|
||||
}
|
||||
@ -0,0 +1,162 @@
|
||||
/* generated by Svelte vX.Y.Z */
|
||||
import {
|
||||
SvelteComponent,
|
||||
component_subscribe,
|
||||
detach,
|
||||
element,
|
||||
init,
|
||||
insert,
|
||||
noop,
|
||||
safe_not_equal,
|
||||
space,
|
||||
subscribe,
|
||||
toggle_class
|
||||
} from "svelte/internal";
|
||||
|
||||
import { reactiveStoreVal, unreactiveExport } from "./store";
|
||||
|
||||
function create_fragment(ctx) {
|
||||
let div0;
|
||||
let t0;
|
||||
let div1;
|
||||
let t1;
|
||||
let div2;
|
||||
let t2;
|
||||
let div3;
|
||||
let t3;
|
||||
let div4;
|
||||
let t4;
|
||||
let div5;
|
||||
let t5;
|
||||
let div6;
|
||||
let t6;
|
||||
let div7;
|
||||
let t7;
|
||||
let div8;
|
||||
|
||||
return {
|
||||
c() {
|
||||
div0 = element("div");
|
||||
t0 = space();
|
||||
div1 = element("div");
|
||||
t1 = space();
|
||||
div2 = element("div");
|
||||
t2 = space();
|
||||
div3 = element("div");
|
||||
t3 = space();
|
||||
div4 = element("div");
|
||||
t4 = space();
|
||||
div5 = element("div");
|
||||
t5 = space();
|
||||
div6 = element("div");
|
||||
t6 = space();
|
||||
div7 = element("div");
|
||||
t7 = space();
|
||||
div8 = element("div");
|
||||
toggle_class(div0, "update1", reactiveModuleVar);
|
||||
toggle_class(div1, "update2", /*reactiveConst*/ ctx[0].x);
|
||||
toggle_class(div2, "update3", nonReactiveGlobal && /*reactiveConst*/ ctx[0].x);
|
||||
toggle_class(div3, "update4", /*$reactiveStoreVal*/ ctx[2]);
|
||||
toggle_class(div4, "update5", /*$reactiveDeclaration*/ ctx[3]);
|
||||
toggle_class(div5, "static1", nonReactiveModuleVar);
|
||||
toggle_class(div6, "static2", nonReactiveGlobal);
|
||||
toggle_class(div7, "static3", nonReactiveModuleVar && nonReactiveGlobal);
|
||||
toggle_class(div8, "static4", unreactiveExport);
|
||||
},
|
||||
m(target, anchor) {
|
||||
insert(target, div0, anchor);
|
||||
insert(target, t0, anchor);
|
||||
insert(target, div1, anchor);
|
||||
insert(target, t1, anchor);
|
||||
insert(target, div2, anchor);
|
||||
insert(target, t2, anchor);
|
||||
insert(target, div3, anchor);
|
||||
insert(target, t3, anchor);
|
||||
insert(target, div4, anchor);
|
||||
insert(target, t4, anchor);
|
||||
insert(target, div5, anchor);
|
||||
insert(target, t5, anchor);
|
||||
insert(target, div6, anchor);
|
||||
insert(target, t6, anchor);
|
||||
insert(target, div7, anchor);
|
||||
insert(target, t7, anchor);
|
||||
insert(target, div8, anchor);
|
||||
},
|
||||
p(ctx, [dirty]) {
|
||||
if (dirty & /*reactiveModuleVar*/ 0) {
|
||||
toggle_class(div0, "update1", reactiveModuleVar);
|
||||
}
|
||||
|
||||
if (dirty & /*reactiveConst*/ 1) {
|
||||
toggle_class(div1, "update2", /*reactiveConst*/ ctx[0].x);
|
||||
}
|
||||
|
||||
if (dirty & /*nonReactiveGlobal, reactiveConst*/ 1) {
|
||||
toggle_class(div2, "update3", nonReactiveGlobal && /*reactiveConst*/ ctx[0].x);
|
||||
}
|
||||
|
||||
if (dirty & /*$reactiveStoreVal*/ 4) {
|
||||
toggle_class(div3, "update4", /*$reactiveStoreVal*/ ctx[2]);
|
||||
}
|
||||
|
||||
if (dirty & /*$reactiveDeclaration*/ 8) {
|
||||
toggle_class(div4, "update5", /*$reactiveDeclaration*/ ctx[3]);
|
||||
}
|
||||
},
|
||||
i: noop,
|
||||
o: noop,
|
||||
d(detaching) {
|
||||
if (detaching) detach(div0);
|
||||
if (detaching) detach(t0);
|
||||
if (detaching) detach(div1);
|
||||
if (detaching) detach(t1);
|
||||
if (detaching) detach(div2);
|
||||
if (detaching) detach(t2);
|
||||
if (detaching) detach(div3);
|
||||
if (detaching) detach(t3);
|
||||
if (detaching) detach(div4);
|
||||
if (detaching) detach(t4);
|
||||
if (detaching) detach(div5);
|
||||
if (detaching) detach(t5);
|
||||
if (detaching) detach(div6);
|
||||
if (detaching) detach(t6);
|
||||
if (detaching) detach(div7);
|
||||
if (detaching) detach(t7);
|
||||
if (detaching) detach(div8);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let nonReactiveModuleVar = Math.random();
|
||||
let reactiveModuleVar = Math.random();
|
||||
|
||||
function instance($$self, $$props, $$invalidate) {
|
||||
let reactiveDeclaration;
|
||||
let $reactiveStoreVal;
|
||||
|
||||
let $reactiveDeclaration,
|
||||
$$unsubscribe_reactiveDeclaration = noop,
|
||||
$$subscribe_reactiveDeclaration = () => ($$unsubscribe_reactiveDeclaration(), $$unsubscribe_reactiveDeclaration = subscribe(reactiveDeclaration, $$value => $$invalidate(3, $reactiveDeclaration = $$value)), reactiveDeclaration);
|
||||
|
||||
component_subscribe($$self, reactiveStoreVal, $$value => $$invalidate(2, $reactiveStoreVal = $$value));
|
||||
$$self.$$.on_destroy.push(() => $$unsubscribe_reactiveDeclaration());
|
||||
nonReactiveGlobal = Math.random();
|
||||
const reactiveConst = { x: Math.random() };
|
||||
reactiveModuleVar += 1;
|
||||
|
||||
if (Math.random()) {
|
||||
reactiveConst.x += 1;
|
||||
}
|
||||
|
||||
$: $$subscribe_reactiveDeclaration($$invalidate(1, reactiveDeclaration = reactiveModuleVar * 2));
|
||||
return [reactiveConst, reactiveDeclaration, $reactiveStoreVal, $reactiveDeclaration];
|
||||
}
|
||||
|
||||
class Component extends SvelteComponent {
|
||||
constructor(options) {
|
||||
super();
|
||||
init(this, options, instance, create_fragment, safe_not_equal, {});
|
||||
}
|
||||
}
|
||||
|
||||
export default Component;
|
||||
@ -0,0 +1,31 @@
|
||||
<script context="module">
|
||||
let nonReactiveModuleVar = Math.random();
|
||||
let reactiveModuleVar = Math.random();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { reactiveStoreVal, unreactiveExport } from './store';
|
||||
|
||||
nonReactiveGlobal = Math.random();
|
||||
const reactiveConst = {x: Math.random()};
|
||||
|
||||
$: reactiveDeclaration = reactiveModuleVar * 2;
|
||||
|
||||
reactiveModuleVar += 1;
|
||||
if (Math.random()) {
|
||||
reactiveConst.x += 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--These should all get updaters because they have at least one reactive dependency-->
|
||||
<div class:update1={reactiveModuleVar}></div>
|
||||
<div class:update2={reactiveConst.x}></div>
|
||||
<div class:update3={nonReactiveGlobal && reactiveConst.x}></div>
|
||||
<div class:update4={$reactiveStoreVal}></div>
|
||||
<div class:update5={$reactiveDeclaration}></div>
|
||||
|
||||
<!--These shouldn't get updates because they're purely non-reactive-->
|
||||
<div class:static1={nonReactiveModuleVar}></div>
|
||||
<div class:static2={nonReactiveGlobal}></div>
|
||||
<div class:static3={nonReactiveModuleVar && nonReactiveGlobal}></div>
|
||||
<div class:static4={unreactiveExport}></div>
|
||||
@ -0,0 +1,4 @@
|
||||
import { writable } from '../../../../store';
|
||||
|
||||
export const reactiveStoreVal = writable(0);
|
||||
export const unreactiveExport = true;
|
||||
@ -0,0 +1 @@
|
||||
<input use:autofocus use:autofocus>
|
||||
@ -0,0 +1,34 @@
|
||||
{
|
||||
"html": {
|
||||
"start": 0,
|
||||
"end": 35,
|
||||
"type": "Fragment",
|
||||
"children": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 35,
|
||||
"type": "Element",
|
||||
"name": "input",
|
||||
"attributes": [
|
||||
{
|
||||
"start": 7,
|
||||
"end": 20,
|
||||
"type": "Action",
|
||||
"name": "autofocus",
|
||||
"modifiers": [],
|
||||
"expression": null
|
||||
},
|
||||
{
|
||||
"start": 21,
|
||||
"end": 34,
|
||||
"type": "Action",
|
||||
"name": "autofocus",
|
||||
"modifiers": [],
|
||||
"expression": null
|
||||
}
|
||||
],
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
{
|
||||
"code": "invalid-class-directive",
|
||||
"message": "Class binding name cannot be empty",
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 10,
|
||||
"character": 10
|
||||
},
|
||||
"pos": 10
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
<h1 class:={true}>Hello</h1>
|
||||
@ -0,0 +1,20 @@
|
||||
// Test support for the `foreign` namespace preserving attribute case.
|
||||
|
||||
export default {
|
||||
html: `
|
||||
<page horizontalAlignment="center">
|
||||
<button textWrap="true" text="button">
|
||||
</page>
|
||||
`,
|
||||
options: {
|
||||
hydrate: false // Hydration test will fail as case sensitivity is only handled for svg elements.
|
||||
},
|
||||
compileOptions: {
|
||||
namespace: 'foreign'
|
||||
},
|
||||
test({ assert, target }) {
|
||||
const attr = sel => target.querySelector(sel).attributes[0].name;
|
||||
assert.equal(attr('page'), 'horizontalAlignment');
|
||||
assert.equal(attr('button'), 'textWrap');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
<page horizontalAlignment="center">
|
||||
<button textWrap="true" text="button">
|
||||
</page>
|
||||
@ -0,0 +1,18 @@
|
||||
// Test support for the `foreign` namespace preserving attribute case.
|
||||
|
||||
export default {
|
||||
html: `
|
||||
<page horizontalAlignment="center">
|
||||
<button textWrap="true" text="button">
|
||||
</page>
|
||||
`,
|
||||
options: {
|
||||
hydrate: false // Hydration test will fail as case sensitivity is only handled for svg elements.
|
||||
},
|
||||
|
||||
test({ assert, target }) {
|
||||
const attr = sel => target.querySelector(sel).attributes[0].name;
|
||||
assert.equal(attr('page'), 'horizontalAlignment');
|
||||
assert.equal(attr('button'), 'textWrap');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,4 @@
|
||||
<svelte:options namespace="foreign" />
|
||||
<page horizontalAlignment="center">
|
||||
<button textWrap="true" text="button">
|
||||
</page>
|
||||
@ -0,0 +1,20 @@
|
||||
import { store } from './store.js';
|
||||
|
||||
export default {
|
||||
html: '<h1>0</h1>',
|
||||
before_test() {
|
||||
store.reset();
|
||||
},
|
||||
async test({ assert, target, component }) {
|
||||
store.set(42);
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
assert.htmlEqual(target.innerHTML, '<h1>42</h1>');
|
||||
|
||||
assert.equal(store.numberOfTimesSubscribeCalled(), 1);
|
||||
},
|
||||
test_ssr({ assert }) {
|
||||
assert.equal(store.numberOfTimesSubscribeCalled(), 1);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import { store } from './store';
|
||||
</script>
|
||||
|
||||
<h1>{$store}</h1>
|
||||
@ -0,0 +1,18 @@
|
||||
import { writable } from '../../../../store';
|
||||
const _store = writable(0);
|
||||
let count = 0;
|
||||
|
||||
export const store = {
|
||||
..._store,
|
||||
subscribe(fn) {
|
||||
count++;
|
||||
return _store.subscribe(fn);
|
||||
},
|
||||
reset() {
|
||||
count = 0;
|
||||
_store.set(0);
|
||||
},
|
||||
numberOfTimesSubscribeCalled() {
|
||||
return count;
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
export default {
|
||||
html: `
|
||||
a: moduleA
|
||||
b: moduleB
|
||||
moduleA: moduleA
|
||||
moduleB: moduleB
|
||||
`,
|
||||
async test({ assert, target, component }) {
|
||||
await component.updateModuleA();
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
a: moduleA
|
||||
b: moduleB
|
||||
moduleA: moduleA
|
||||
moduleB: moduleB
|
||||
`);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
<script context="module">
|
||||
let moduleA = 'moduleA';
|
||||
let moduleB = 'moduleB';
|
||||
</script>
|
||||
<script>
|
||||
export function updateModuleA() {
|
||||
moduleA = 'something else';
|
||||
}
|
||||
$: a = moduleA;
|
||||
$: b = moduleB;
|
||||
</script>
|
||||
a: {a}
|
||||
b: {b}
|
||||
moduleA: {moduleA}
|
||||
moduleB: {moduleB}
|
||||
@ -0,0 +1,7 @@
|
||||
<svelte:options namespace="foreign" />
|
||||
<page>
|
||||
<a>not actually a link</a>
|
||||
<label>This isn't a html label</label>
|
||||
<figure>This is maybe a QT figure</figure>
|
||||
</page>
|
||||
|
||||
@ -0,0 +1 @@
|
||||
[]
|
||||
@ -0,0 +1,15 @@
|
||||
[{
|
||||
"code": "invalid-binding",
|
||||
"message": "'value' is not a valid binding. Foreign elements only support bind:this",
|
||||
"pos": 81,
|
||||
"start": {
|
||||
"line": 6,
|
||||
"column": 7,
|
||||
"character": 81
|
||||
},
|
||||
"end": {
|
||||
"line": 6,
|
||||
"column": 28,
|
||||
"character": 102
|
||||
}
|
||||
}]
|
||||
@ -0,0 +1,6 @@
|
||||
<svelte:options namespace="foreign" />
|
||||
<script>
|
||||
let whatever;
|
||||
</script>
|
||||
|
||||
<input bind:value={whatever} />
|
||||
@ -0,0 +1,6 @@
|
||||
<script context="module">
|
||||
let foo;
|
||||
</script>
|
||||
<script>
|
||||
$: bar = foo;
|
||||
</script>
|
||||
@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"code": "module-script-reactive-declaration",
|
||||
"message": "\"foo\" is declared in a module script and will not be reactive",
|
||||
"pos": 65,
|
||||
"start": {
|
||||
"character": 65,
|
||||
"column": 10,
|
||||
"line": 5
|
||||
},
|
||||
"end": {
|
||||
"character": 68,
|
||||
"column": 13,
|
||||
"line": 5
|
||||
}
|
||||
}
|
||||
]
|
||||
Loading…
Reference in new issue