mirror of https://github.com/sveltejs/svelte
parent
b554e343e8
commit
24b8d45f67
@ -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 };
|
||||||
|
}
|
||||||
@ -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}
|
||||||
Loading…
Reference in new issue