-
-
Notifications
You must be signed in to change notification settings - Fork 209
/
Copy pathindex.ts
241 lines (223 loc) · 8.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
import MagicString from 'magic-string';
import { convertHtmlxToJsx, TemplateProcessResult } from '../htmlxtojsx_v2';
import { parseHtmlx } from '../utils/htmlxparser';
import { addComponentExport } from './addComponentExport';
import { createRenderFunction } from './createRenderFunction';
import { ExportedNames } from './nodes/ExportedNames';
import { Generics } from './nodes/Generics';
import { ImplicitStoreValues } from './nodes/ImplicitStoreValues';
import { processInstanceScriptContent } from './processInstanceScriptContent';
import { createModuleAst, ModuleAst, processModuleScriptTag } from './processModuleScriptTag';
import path from 'path';
import { parse, VERSION } from 'svelte/compiler';
function processSvelteTemplate(
str: MagicString,
parse: typeof import('svelte/compiler').parse,
options: {
emitOnTemplateError?: boolean;
namespace?: string;
accessors?: boolean;
mode?: 'ts' | 'dts';
typingsNamespace?: string;
svelte5Plus: boolean;
}
): TemplateProcessResult {
const { htmlxAst, tags } = parseHtmlx(str.original, parse, options);
return convertHtmlxToJsx(str, htmlxAst, tags, options);
}
export function svelte2tsx(
svelte: string,
options: {
parse?: typeof import('svelte/compiler').parse;
version?: string;
filename?: string;
isTsFile?: boolean;
emitOnTemplateError?: boolean;
namespace?: string;
mode?: 'ts' | 'dts';
accessors?: boolean;
typingsNamespace?: string;
noSvelteComponentTyped?: boolean;
} = { parse }
) {
options.mode = options.mode || 'ts';
options.version = options.version || VERSION;
const str = new MagicString(svelte);
const basename = path.basename(options.filename || '');
const svelte5Plus = Number(options.version![0]) > 4;
const isTsFile = options?.isTsFile;
// process the htmlx as a svelte template
let {
htmlAst,
moduleScriptTag,
scriptTag,
rootSnippets,
slots,
uses$$props,
uses$$slots,
uses$$restProps,
events,
componentDocumentation,
resolvedStores,
usesAccessors,
isRunes
} = processSvelteTemplate(str, options.parse || parse, {
...options,
svelte5Plus
});
/* Rearrange the script tags so that module is first, and instance second followed finally by the template
* This is a bit convoluted due to some trouble I had with magic string. A simple str.move(start,end,0) for each script wasn't enough
* since if the module script was already at 0, it wouldn't move (which is fine) but would mean the order would be swapped when the script tag tried to move to 0
* In this case we instead have to move it to moduleScriptTag.end. We track the location for the script move in the MoveInstanceScriptTarget var
*/
let instanceScriptTarget = 0;
let moduleAst: ModuleAst | undefined;
if (moduleScriptTag) {
moduleAst = createModuleAst(str, moduleScriptTag);
if (moduleScriptTag.start != 0) {
//move our module tag to the top
str.move(moduleScriptTag.start, moduleScriptTag.end, 0);
} else {
//since our module script was already at position 0, we need to move our instance script tag to the end of it.
instanceScriptTarget = moduleScriptTag.end;
}
}
const renderFunctionStart = scriptTag
? str.original.lastIndexOf('>', scriptTag.content.start) + 1
: instanceScriptTarget;
const implicitStoreValues = new ImplicitStoreValues(resolvedStores, renderFunctionStart);
//move the instance script and process the content
let exportedNames = new ExportedNames(str, 0, basename, isTsFile, svelte5Plus, isRunes);
let generics = new Generics(str, 0, { attributes: [] } as any);
let uses$$SlotsInterface = false;
if (scriptTag) {
//ensure it is between the module script and the rest of the template (the variables need to be declared before the jsx template)
if (scriptTag.start != instanceScriptTarget) {
str.move(scriptTag.start, scriptTag.end, instanceScriptTarget);
}
const res = processInstanceScriptContent(
str,
scriptTag,
events,
implicitStoreValues,
options.mode,
moduleAst,
isTsFile,
basename,
svelte5Plus,
isRunes
);
uses$$props = uses$$props || res.uses$$props;
uses$$restProps = uses$$restProps || res.uses$$restProps;
uses$$slots = uses$$slots || res.uses$$slots;
({ exportedNames, events, generics, uses$$SlotsInterface } = res);
}
exportedNames.usesAccessors = usesAccessors;
if (svelte5Plus) {
exportedNames.checkGlobalsForRunes(implicitStoreValues.getGlobals());
}
//wrap the script tag and template content in a function returning the slot and exports
createRenderFunction({
str,
scriptTag,
scriptDestination: instanceScriptTarget,
slots,
events,
exportedNames,
uses$$props,
uses$$restProps,
uses$$slots,
uses$$SlotsInterface,
generics,
svelte5Plus,
isTsFile,
mode: options.mode
});
// we need to process the module script after the instance script has moved otherwise we get warnings about moving edited items
if (moduleScriptTag) {
processModuleScriptTag(
str,
moduleScriptTag,
new ImplicitStoreValues(
implicitStoreValues.getAccessedStores(),
renderFunctionStart,
scriptTag || options.mode === 'ts' ? undefined : (input) => `</>;${input}<>`
),
moduleAst
);
if (!scriptTag) {
moduleAst.tsAst.forEachChild((node) =>
exportedNames.hoistableInterfaces.analyzeModuleScriptNode(node)
);
}
}
if (moduleScriptTag || scriptTag) {
for (const [start, end, globals] of rootSnippets) {
const hoist_to_module =
moduleScriptTag &&
(globals.size === 0 ||
[...globals.keys()].every((id) =>
exportedNames.hoistableInterfaces.isAllowedReference(id)
));
if (hoist_to_module) {
str.move(
start,
end,
scriptTag
? scriptTag.start + 1 // +1 because imports are also moved at that position, and we want to move interfaces after imports
: moduleScriptTag.end
);
} else if (scriptTag) {
str.move(start, end, renderFunctionStart);
}
}
}
addComponentExport({
str,
canHaveAnyProp: !exportedNames.uses$$Props && (uses$$props || uses$$restProps),
events,
isTsFile,
exportedNames,
usesAccessors,
usesSlots: slots.size > 0,
fileName: options?.filename,
componentDocumentation,
mode: options.mode,
generics,
isSvelte5: svelte5Plus,
noSvelteComponentTyped: options.noSvelteComponentTyped
});
if (options.mode === 'dts') {
// Prepend the import which is used for TS files
// The other shims need to be provided by the user ambient-style,
// for example through filenames.push(require.resolve('svelte2tsx/svelte-shims.d.ts'))
// TODO replace with SvelteComponent for Svelte 5, keep old for backwards compatibility with Svelte 3
if (options.noSvelteComponentTyped) {
str.prepend('import { SvelteComponent } from "svelte"\n' + '\n');
} else {
str.prepend('import { SvelteComponentTyped } from "svelte"\n' + '\n');
}
let code = str.toString();
// Remove all tsx occurences and the template part from the output
code = code
// prepended before each script block
.replace('<></>;', '')
.replace('<></>;', '')
// tsx in render function
.replace(/<>.*<\/>/s, '')
.replace('\n() => ();', '');
return {
code
};
} else {
str.prepend('///<reference types="svelte" />\n');
return {
code: str.toString(),
map: str.generateMap({ hires: true, source: options?.filename }),
exportedNames: exportedNames.getExportsMap(),
events: events.createAPI(),
// not part of the public API so people don't start using it
htmlAst
};
}
}