parser instead of regex for preprocessor

pull/6611/head
Tan Li Hau 5 years ago
parent b554e343e8
commit 24b8d45f67

@ -204,12 +204,18 @@ result: {
dependencies?: Array<string>
}>,
script?: (input: { content: string, markup: string, attributes: Record<string, string>, filename: string }) => Promise<{
code: string,
dependencies?: Array<string>,
attributes?: Record<string, string | boolean>
}>,
expression?: (input: { content: string, markup: string, filename: string }) => Promise<{
code: string,
dependencies?: Array<string>
}>,
style?: (input: { content: string, markup: string, attributes: Record<string, string>, filename: string }) => Promise<{
code: string,
dependencies?: Array<string>
dependencies?: Array<string>,
attributes?: Record<string, string | boolean>
}>
}>,
options?: {
@ -224,7 +230,7 @@ The `preprocess` function provides convenient hooks for arbitrarily transforming
The first argument is the component source code. The second is an array of *preprocessors* (or a single preprocessor, if you only have one), where a preprocessor is an object with `markup`, `script` and `style` functions, each of which is optional.
Each `markup`, `script` or `style` function must return an object (or a Promise that resolves to an object) with a `code` property, representing the transformed source code, and an optional array of `dependencies`.
Each `markup`, `script`, `expression` or `style` function must return an object (or a Promise that resolves to an object) with a `code` property, representing the transformed source code, an optional array of `dependencies`, and an optional object of `attributes`.
The `markup` function receives the entire component source text, along with the component's `filename` if it was specified in the third argument.
@ -254,10 +260,16 @@ const { code } = await svelte.preprocess(source, {
---
The `script` and `style` functions receive the contents of `<script>` and `<style>` elements respectively (`content`) as well as the entire component source text (`markup`). In addition to `filename`, they get an object of the element's attributes.
The `script` and `style` functions receive the contents of `<script>` and `<style>` elements respectively (`content`), while the `expression` function receives the contents within the `{...}` expression (`content`).
The `script`, `style`, and `expression` functions receive the entire component source text (`markup`) as well as the name of the file (`filename`).
The `script` and `style` functions also get an object of the element's attributes (`attributes`).
If a `dependencies` array is returned, it will be included in the result object. This is used by packages like [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) to watch additional files for changes, in the case where your `<style>` tag has an `@import` (for example).
If an `attributes` object is returned, it will replace the attributes on the `<script>` or `<style >` tag.
```js
const svelte = require('svelte/compiler');
const sass = require('node-sass');

@ -2,6 +2,7 @@ import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types
import { getLocator } from 'locate-character';
import { MappedCode, SourceLocation, parse_attached_sourcemap, sourcemap_add_offset, combine_sourcemaps } from '../utils/mapped_code';
import { decode_map } from './decode_sourcemap';
import { Position } from './quick_parser';
import { replace_in_code, slice_source } from './replace_in_code';
import { MarkupPreprocessor, Source, Preprocessor, PreprocessorGroup, Processed } from './types';
@ -89,6 +90,20 @@ function processed_content_to_code(processed: Processed, location: SourceLocatio
return MappedCode.from_processed(processed.code, decoded_map);
}
function stringify_attributes(attributes: Record<string, string | boolean>) {
return Object.keys(attributes).map(key => {
const value = attributes[key];
if (typeof value === 'boolean') {
if (value) {
return key;
}
} else {
const value_string = value.indexOf('"') > -1 ? `'${value}'` : `"${value}"`;
return key + '=' + value_string;
}
}).filter(Boolean).join(' ');
}
/**
* Given the whole tag including content, return a `MappedCode`
* representing the tag content replaced with `processed`.
@ -96,7 +111,8 @@ function processed_content_to_code(processed: Processed, location: SourceLocatio
function processed_tag_to_code(
processed: Processed,
tag_name: 'style' | 'script',
attributes: string,
original_attributes: string,
updated_attributes: string,
source: Source
): MappedCode {
const { file_basename, get_location } = source;
@ -104,74 +120,55 @@ function processed_tag_to_code(
const build_mapped_code = (code: string, offset: number) =>
MappedCode.from_source(slice_source(code, offset, source));
const tag_open = `<${tag_name}${attributes || ''}>`;
const original_tag_open = `<${tag_name}${original_attributes || ''}>`;
const updated_tag_open = updated_attributes ? `<${tag_name} ${updated_attributes}>` : original_tag_open;
const tag_close = `</${tag_name}>`;
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);
parse_attached_sourcemap(processed, tag_name);
const content_code = processed_content_to_code(processed, get_location(tag_open.length), file_basename);
const tag_open_code = build_mapped_code(updated_tag_open, 0);
const tag_close_code = build_mapped_code(tag_close, original_tag_open.length + source.source.length);
const content_code = processed_content_to_code(processed, get_location(original_tag_open.length), file_basename);
return tag_open_code.concat(content_code).concat(tag_close_code);
}
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',
tag_name: 'style' | 'script' | 'expression',
preprocessor: Preprocessor,
source: Source
): Promise<SourceUpdate> {
const { filename, source: markup } = 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
{ source: content, attributes, raw_attributes, offset, length }: Position
): Promise<MappedCode> {
const no_change = () => MappedCode.from_source(slice_source(tag_with_content, tag_offset, source));
const no_change = () => MappedCode.from_source(slice_source(source.source.slice(offset, offset + length), offset, source));
if (!attributes && !content) return no_change();
const processed = await preprocessor({
content: content || '',
attributes: parse_tag_attributes(attributes || ''),
content,
attributes,
markup,
filename
});
if (!processed) return no_change();
if (processed.dependencies) dependencies.push(...processed.dependencies);
if (!processed.map && processed.code === content) return no_change();
return processed_tag_to_code(processed, tag_name, attributes, slice_source(content, tag_offset, source));
if (!processed.map && processed.code === content && !('attributes' in processed)) return no_change();
parse_attached_sourcemap(processed, tag_name);
if (tag_name === 'expression') {
return processed_content_to_code(processed, source.get_location(offset), source.file_basename);
} else {
const updated_attributes = ('attributes' in processed) ? stringify_attributes(processed.attributes) : null;
return processed_tag_to_code(processed, tag_name, raw_attributes, updated_attributes, slice_source(content, offset, source));
}
}
const { string, map } = await replace_in_code(tag_regex, process_single_tag, source);
const { string, map } = await replace_in_code(tag_name, process_single_tag, source);
return { string, map, dependencies };
}
@ -210,6 +207,7 @@ export default async function preprocess(
const markup = preprocessors.map(p => p.markup).filter(Boolean);
const script = preprocessors.map(p => p.script).filter(Boolean);
const expression = preprocessors.map(p => p.expression).filter(Boolean);
const style = preprocessors.map(p => p.style).filter(Boolean);
const result = new PreprocessResult(source, filename);
@ -225,6 +223,10 @@ export default async function preprocess(
result.update_source(await process_tag('script', process, result));
}
for (const process of expression) {
result.update_source(await process_tag('expression', process, result));
}
for (const preprocess of style) {
result.update_source(await process_tag('style', preprocess, result));
}

@ -0,0 +1,321 @@
import { whitespace } from '../utils/patterns';
export interface Position {
offset: number;
length: number;
source: string;
attributes: Record<string, string|boolean> | null;
raw_attributes: string;
}
const matching_bracket = {
']': '[',
'}': '{',
')': '('
};
// simplified svelte parser
export default function parse(template: string) {
const scripts: Position[] = [];
const styles: Position[] = [];
const expressions: Position[] = [];
let index = 0;
while (index < template.length) {
if (match('<')) {
parse_tag();
} else if (match('{')) {
parse_mustache();
} else {
parse_text();
}
}
function add_script_expression(get_expression: () => string) {
const offset = index;
const source = get_expression();
expressions.push({ offset, length: source.length, source, attributes: null, raw_attributes: null });
}
function parse_tag() {
const tag_start = index;
index++;
if (eat('!--')) {
read_until_regex(/-->/);
eat('-->');
} else if (eat('/')) {
// closing tag
read_until('>');
index++;
} else {
const tag_name = read_until_regex(/(\s|\/|>)/);
if (tag_name === 'script') {
const raw_attributes = read_until_regex(/\/?>/);
const attributes = parse_tag_attributes(raw_attributes);
let source: string;
if (eat('/>')) {
source = '';
} else {
eat('>');
source = read_until_regex(/<\/script\s*>/);
eat_regex(/<\/script\s*>/);
}
const length = index - tag_start;
scripts.push({ offset: tag_start, length, source, attributes, raw_attributes });
} else if (tag_name === 'style') {
const raw_attributes = read_until_regex(/\/?>/);
const attributes = parse_tag_attributes(raw_attributes);
let source: string;
if (eat('/>')) {
source = '';
} else {
eat('>');
source = read_until_regex(/<\/style\s*>/);
eat_regex(/<\/style\s*>/);
}
const length = index - tag_start;
styles.push({ offset: tag_start, length, source, attributes, raw_attributes });
} else {
while (index < template.length) {
allow_whitespace();
if (eat('{')) {
// handle spread
eat_regex(/\s+\.\.\./);
add_script_expression(() => read_expression_until('}'));
index++;
continue;
}
// attribute name
if (!read_until_regex(/[\s=/>"']/)) {
break;
}
allow_whitespace();
if (eat('=')) {
// attribute value
allow_whitespace();
const quote_mark = eat("'") ? "'" : eat('"') ? '"' : null;
if (quote_mark && eat(quote_mark)) {
continue;
}
const ending_regex =
quote_mark === "'"
? /'/
: quote_mark === '"' ? /"/ : /(\/>|[\s"'=<>`])/;
while (index < template.length) {
if (match_regex(ending_regex)) {
break;
} else if (eat('{')) {
add_script_expression(() => read_expression_until('}'));
index++;
} else {
index++;
}
}
if (quote_mark) index++;
}
}
}
}
}
function parse_tag_attributes(str: string) {
const attributes = {};
let i = 0;
while (i < str.length) {
const char = str[i++];
if (/\s/.test(char)) continue;
let name = char;
while (i < str.length && !/[\s=/>"']/.test(str[i])) {
name += str[i++];
}
if (str[i] === '=') {
i++;
const quote_mark = str[i];
const regex = (
quote_mark === "'" ? /'/ :
quote_mark === '"' ? /"/ :
/(\/>|[\s"'=<>`])/
);
if (quote_mark === "'" || quote_mark === '"') {
i++;
}
const match = str.slice(i).match(regex);
let value;
if (match) {
value = str.slice(i, i + match.index);
i += match.index + 1;
} else {
value = str.slice(i + 1);
i = str.length;
}
attributes[name] = value;
} else {
attributes[name] = true;
}
}
return attributes;
}
function parse_mustache() {
index++;
// {/if}, {/each}, {/await} or {/key}
if (eat('/')) {
read_until('}');
index++;
} else if (eat(':else')) {
allow_whitespace();
if (eat('if')) {
allow_whitespace();
add_script_expression(() => read_expression_until('}'));
}
allow_whitespace();
eat('}');
} else if (match(':then') || match(':catch')) {
eat(':then') || eat('catch');
if (!eat('}')) {
allow_whitespace();
read_expression_until('}');
eat('}');
}
} else if (eat('#')) {
if (eat('if') || eat('key')) {
allow_whitespace();
add_script_expression(() => read_expression_until('}'));
index++;
} else if (eat('each')) {
allow_whitespace();
add_script_expression(() => read_expression_until(/\sas/));
eat_regex(/\sas/);
allow_whitespace();
read_expression_until('}');
index++;
} else if (eat('await')) {
allow_whitespace();
add_script_expression(() => read_expression_until(/\sthen|\scatch|\}/));
if (eat_regex(/\sthen/) || eat_regex(/\scatch/)) {
read_expression_until('}');
}
index++;
} else {
read_until('}');
index++;
}
} else if (eat('@html')) {
allow_whitespace();
add_script_expression(() => read_expression_until('}'));
index++;
} else if (eat('@debug')) {
allow_whitespace();
add_script_expression(() => read_expression_until('}'));
index++;
} else {
add_script_expression(() => read_expression_until('}'));
index++;
}
}
function parse_text() {
while (index < template.length && !match('<') && !match('{')) {
index++;
}
}
function match(str: string) {
return template.slice(index, index + str.length) === str;
}
function match_regex(pattern: RegExp) {
const match = pattern.exec(template.slice(index));
if (!match || match.index !== 0) return null;
return match[0];
}
function eat(str: string) {
if (match(str)) {
index += str.length;
return true;
}
return false;
}
function eat_regex(pattern: RegExp) {
const result = match_regex(pattern);
if (result) index += result.length;
return result;
}
function allow_whitespace() {
while (index < template.length && whitespace.test(template[index])) {
index++;
}
}
function read_until(str: string) {
const start = index;
const next_index = template.slice(start).indexOf(str);
if (next_index > -1) {
index += next_index;
return template.slice(start, index);
}
index = template.length;
return template.slice(start);
}
function read_until_regex(pattern: RegExp) {
const start = index;
const match = pattern.exec(template.slice(index));
if (match) {
index += match.index;
return template.slice(start, index);
}
index = template.length;
return template.slice(start);
}
function read_expression_until(str: string | RegExp) {
const bracket_stack = [];
let quote = null;
const start = index;
const is_ending =
typeof str === 'string' ? () => match(str) : () => match_regex(str);
while (index < template.length) {
if (bracket_stack.length === 0 && quote === null && is_ending()) {
break;
}
const char = template[index];
switch (char) {
case "'":
case '"':
case '`': {
if (quote === null) {
quote = char;
} else if (quote === char) {
quote = null;
}
break;
}
case '[':
case '{':
case '(':
if (quote === null) {
bracket_stack.push(char);
}
break;
case ']':
case '}':
case ')':
if (quote === null) {
const find = matching_bracket[char];
// assume unclosed bracket
while (bracket_stack.length) {
if (bracket_stack.pop() === find) {
break;
}
}
}
}
index++;
}
return template.slice(start, index);
}
return { scripts, styles, expressions };
}

@ -1,4 +1,5 @@
import { MappedCode } from '../utils/mapped_code';
import parse, { Position } from './quick_parser';
import { Source } from './types';
interface Replacement {
@ -21,24 +22,24 @@ export function slice_source(
}
function calculate_replacements(
re: RegExp,
get_replacement: (...match: any[]) => Promise<MappedCode>,
tag_name: 'script' | 'style' | 'expression',
get_replacement: (position: Position) => Promise<MappedCode>,
source: string
) {
const replacements: Array<Promise<Replacement>> = [];
const positions = parse(source);
source.replace(re, (...match) => {
(tag_name === 'script' ? positions.scripts : tag_name === 'expression' ? positions.expressions : positions.styles).forEach(position => {
replacements.push(
get_replacement(...match).then(
get_replacement(position).then(
replacement => {
const matched_string = match[0];
const offset = match[match.length - 2];
const length = position.length;
const offset = position.offset;
return ({ offset, length: matched_string.length, replacement });
return ({ offset, length, replacement });
}
)
);
return '';
});
return Promise.all(replacements);
@ -65,11 +66,11 @@ function perform_replacements(
}
export async function replace_in_code(
regex: RegExp,
get_replacement: (...match: any[]) => Promise<MappedCode>,
tag_name: 'script' | 'style' | 'expression',
get_replacement: (position: Position) => Promise<MappedCode>,
location: Source
): Promise<MappedCode> {
const replacements = await calculate_replacements(regex, get_replacement, location.source);
const replacements = await calculate_replacements(tag_name, get_replacement, location.source);
return perform_replacements(replacements, location);
}

@ -11,6 +11,7 @@ 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[];
attributes?: Record<string, string | boolean>;
toString?: () => string;
}
@ -35,5 +36,6 @@ export type Preprocessor = (options: {
export interface PreprocessorGroup {
markup?: MarkupPreprocessor;
style?: Preprocessor;
expression?: Preprocessor;
script?: Preprocessor;
}

@ -297,9 +297,9 @@ export function apply_preprocessor_sourcemap(filename: string, svelte_map: Sourc
}
// parse attached sourcemap in processed.code
export function parse_attached_sourcemap(processed: Processed, tag_name: 'script' | 'style'): void {
export function parse_attached_sourcemap(processed: Processed, tag_name: 'script' | 'style' | 'expression'): void {
const r_in = '[#@]\\s*sourceMappingURL\\s*=\\s*(\\S*)';
const regex = (tag_name == 'script')
const regex = (tag_name == 'script' || tag_name == 'expression')
? new RegExp('(?://' + r_in + ')|(?:/\\*' + r_in + '\\s*\\*/)$')
: new RegExp('/\\*' + r_in + '\\s*\\*/$');
function log_warning(message) {
@ -308,7 +308,7 @@ export function parse_attached_sourcemap(processed: Processed, tag_name: 'script
console.warn(`warning: ${message}. processed.code = ${JSON.stringify(code_start)}`);
}
processed.code = processed.code.replace(regex, (_, match1, match2) => {
const map_url = (tag_name == 'script') ? (match1 || match2) : match1;
const map_url = (tag_name == 'script' || tag_name == 'expression') ? (match1 || match2) : match1;
const map_data = (map_url.match(/data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(\S*)/) || [])[1];
if (map_data) {
// sourceMappingURL is data URL

@ -0,0 +1,14 @@
export default {
preprocess: {
script: () => {
return {
code: 'tag'
};
},
expression: () => {
return {
code: 'replaced'
};
}
}
};

@ -0,0 +1,46 @@
<script>
let a = 1;
</script>
<div on:click={() => { return a?.b?.c; }}>
this is {a#c}
</div>
<div
use:action={{ foo: 1 }}
use:action="{ foo: 1 }"
{ ...asdf[123]["qwe(((("]() }
class:foo="{123}"
transition:fade
in:fly={{ y: 10 }}
/>
{#if a}
{#each array['a`[x']["]]]]]]"]['[[[['] as item}
{:else}
{#await getPromise().[foo()]['"a"'] then { a }}
{:catch error}
{/await}
{/each}
{:else if b}
{#each array[123](f, y, z) as item}
{ [([(a['asdf](((}}}[][!@#asdf}}}}'])])] + foo }
{/each}
{:else}
{/if}
{#each foo
as {
as
}}
{as}
{/each}
<Component
a={foo > 32 + { a: 1}}
b="3[3+4)4"
class="a {b} fds {c[123]} df {{ a: 3 }[`a
`]} qwe"
>
<div on:click on:bind> {a} {@debug b} {@html '@debug qwe'} {a < 32 }</div>
</Component>

@ -0,0 +1,43 @@
<script>tag</script>
<div on:click={replaced}>
this is {replaced}
</div>
<div
use:action={replaced}
use:action="{replaced}"
{ ...replaced}
class:foo="{replaced}"
transition:fade
in:fly={replaced}
/>
{#if replaced}
{#each replaced as item}
{:else}
{#await replaced then { a }}
{:catch error}
{/await}
{/each}
{:else if replaced}
{#each replaced as item}
{replaced}
{/each}
{:else}
{/if}
{#each replaced
as {
as
}}
{replaced}
{/each}
<Component
a={replaced}
b="3[3+4)4"
class="a {replaced} fds {replaced} df {replaced} qwe"
>
<div on:click on:bind> {replaced} {@debug replaced} {@html replaced} {replaced}</div>
</Component>

@ -0,0 +1,9 @@
export default {
preprocess: {
script: ({ is_expression, content }) => {
return {
code: is_expression ? content : 'let z = 42;'
};
}
}
};

@ -0,0 +1,23 @@
<script>
let a = 1;
</script>
<script context="module">
let b = 2;
</script>
{@html '<script>let c = 3;</script>'}
<script context="worker">
let d = 4;
</script>
{@html `
<script>
let e = 5;
</script>
`}
<script>
let f = 6;
</script>

@ -0,0 +1,15 @@
<script>let z = 42;</script>
<script context="module">let z = 42;</script>
{@html '<script>let c = 3;</script>'}
<script context="worker">let z = 42;</script>
{@html `
<script>
let e = 5;
</script>
`}
<script>let z = 42;</script>

@ -0,0 +1,9 @@
export default {
preprocess: {
style: () => {
return {
code: 'h1 { color: white; }'
};
}
}
};

@ -0,0 +1,45 @@
<script context="module">
let a = '<style>div { color: blue; }</style>';
let b = `
<style>
div { color: green; }
</style>
`;
</script>
<style>
div {
color: purple;
}
</style>
<script>
let c = '<style>div { color: blue; }</style>';
let d = `
<style>
div { color: green; }
</style>
`;
</script>
{a} {b} {c} {d}
{@html '<style>div { color: yellow; }</style>'}
<style>
div {
color: purple;
}
</style>
{@html `
<style>
div { color: pink; }
</style>
`}
<style>
div {
color: purple;
}
</style>

@ -0,0 +1,33 @@
<script context="module">
let a = '<style>div { color: blue; }</style>';
let b = `
<style>
div { color: green; }
</style>
`;
</script>
<style>h1 { color: white; }</style>
<script>
let c = '<style>div { color: blue; }</style>';
let d = `
<style>
div { color: green; }
</style>
`;
</script>
{a} {b} {c} {d}
{@html '<style>div { color: yellow; }</style>'}
<style>h1 { color: white; }</style>
{@html `
<style>
div { color: pink; }
</style>
`}
<style>h1 { color: white; }</style>

@ -0,0 +1,16 @@
export default {
preprocess: {
script: ({ content, attributes }) => {
attributes = { ...attributes, c: false, insert: 'foobar' };
delete attributes['lang'];
if (attributes.context) {
attributes.context = 'module';
}
return {
code: content,
attributes
};
}
}
};

@ -0,0 +1,14 @@
<script lang="ts" a="b" />
<script context="worker" foo='a'>
let a = 1;
</script>
<script a="a=3" b='qwe' c d={a} e f class="a {asdf} qwe">
let a = 1;
</script>
<script a="'a' b `c`" b='"a cd `""'>
</script>
{a}

@ -0,0 +1,14 @@
<script a="b" insert="foobar"></script>
<script context="module" foo="a" insert="foobar">
let a = 1;
</script>
<script a="a=3" b="qwe" d="{a}" e f class="a {asdf} qwe" insert="foobar">
let a = 1;
</script>
<script a="'a' b `c`" b='"a cd `""' insert="foobar">
</script>
{a}

@ -37,6 +37,9 @@ export default {
get_processor('script', 'replace_me_script', 'done_replace_script_1'),
get_processor('script', 'done_replace_script_1', 'done_replace_script_2'),
get_processor('expression', 'replace_me_script', 'done_replace_script_1'),
get_processor('expression', 'done_replace_script_1', 'done_replace_script_2'),
get_processor('style', '.replace_me_style', '.done_replace_style_1'),
get_processor('style', '.done_replace_style_1', '.done_replace_style_2')

@ -8,4 +8,4 @@
replace_me_script = 'hello'
;
</script>
<h1 class="done_replace_style_2">{done_replace_script_2}</h1>
<h1 class="done_replace_style_2">{replace_me_script}</h1>

@ -6,4 +6,4 @@
<script>
export let foo = { baritone: { baz: 5 } }
</script>
<h1>{foo.bar.baz}</h1>
<h1>{foo.baritone.baz}</h1>

@ -1,5 +1,11 @@
import * as ts from 'typescript';
const tsCompilerOptions = {
target: ts.ScriptTarget.ES2015,
module: ts.ModuleKind.ES2015,
sourceMap: true
};
export default {
js_map_sources: [
'input.svelte'
@ -9,17 +15,24 @@ export default {
script: ({ content, filename }) => {
const { outputText, sourceMapText } = ts.transpileModule(content, {
fileName: filename,
compilerOptions: {
target: ts.ScriptTarget.ES2015,
module: ts.ModuleKind.ES2015,
sourceMap: true
}
compilerOptions: tsCompilerOptions
});
return {
code: outputText,
map: sourceMapText
};
},
expression: ({ content, filename }) => {
const { outputText, sourceMapText } = ts.transpileModule(content, {
fileName: filename,
compilerOptions: tsCompilerOptions
});
return {
code: outputText.replace(/;([\s]+\/\/# sourceMappingURL=[\S]+)$/, '$1'),
map: sourceMapText
};
}
}
]

@ -15,4 +15,4 @@
</script>
<h1>Hello world!</h1>
<div>Counter value: {count}</div>
<div>Counter value: {count as number}</div>

Loading…
Cancel
Save