forked from sveltejs/svelte
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
140 lines (124 loc) · 3.62 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import { SourceMap } from 'magic-string';
export interface PreprocessorGroup {
markup?: (options: {
content: string,
filename: string
}) => { code: string, map?: SourceMap | string, dependencies?: string[] };
style?: Preprocessor;
script?: Preprocessor;
}
export type Preprocessor = (options: {
content: string,
attributes: Record<string, string | boolean>,
filename?: string
}) => { code: string, map?: SourceMap | string, dependencies?: string[] };
interface Processed {
code: string;
map?: SourceMap | string;
dependencies?: string[];
}
function parse_attribute_value(value: string) {
return /^['"]/.test(value) ?
value.slice(1, -1) :
value;
}
function parse_attributes(str: string) {
const attrs = {};
str.split(/\s+/).filter(Boolean).forEach(attr => {
const [name, value] = attr.split('=');
attrs[name] = value ? parse_attribute_value(value) : true;
});
return attrs;
}
interface Replacement {
offset: number;
length: number;
replacement: string;
}
async function replace_async(str: string, re: RegExp, func: (...any) => Promise<string>) {
const replacements: Promise<Replacement>[] = [];
str.replace(re, (...args) => {
replacements.push(
func(...args).then(
res =>
<Replacement>({
offset: args[args.length - 2],
length: args[0].length,
replacement: res,
})
)
);
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;
}
export default async function preprocess(
source: string,
preprocessor: PreprocessorGroup | PreprocessorGroup[],
options?: { filename?: string }
) {
const filename = (options && options.filename) || preprocessor.filename; // legacy
const dependencies = [];
const preprocessors = 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);
for (const fn of markup) {
const processed: Processed = await fn({
content: source,
filename
});
if (processed && processed.dependencies) dependencies.push(...processed.dependencies);
source = processed ? processed.code : source;
}
for (const fn of script) {
source = await replace_async(
source,
/<script(\s[^]*?)?>([^]*?)<\/script>/gi,
async (match, attributes = '', content) => {
const processed: 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;
}
);
}
for (const fn of style) {
source = await replace_async(
source,
/<style(\s[^]*?)?>([^]*?)<\/style>/gi,
async (match, attributes = '', content) => {
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;
}
);
}
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)],
toString() {
return source;
}
};
}