Skip to content

Commit a1c54fc

Browse files
dummdidummSomaticITSimon Holthausen
authored
feat: better Typescript transpilation (#392)
* feat: pass source markup to transformers (if provided) * feat: allow typescript to know used variables in template - inject vars from markup to typescript - do not use importTransform when markup is available - inject component script to context=module script - refactor modules/markup to reuse common functions - test injection behavior * fix: handle sourcemaps in typescript transformer - introduce two deps: magic-string, sorcery - produce a source map on each step of the process - store all source maps in a chain - refactor transformer to increase readability * bump svelte for passing tests, handle empty code/map case * move new transpilation into a flag for backwards compatibility * lint, make build work * Adjust failing tests Inline source maps are stripped in recents Svelte preprocess versions, therefore we need to test the source map generation differently * docs * rename option, more docs * Remove dynamic imports of source-map and sorcery, make them "real" deps * make handling mixed imports the default * adjust docs * Ignore possible errors in $$vars$$ * undo ts-ignore as it's not needed only syntax errors are reported, not semantic errors * fix: handle store subscription in template * handle store subscription in script * handle imports in instance script that are only used in module script * remove unused code * fix comment regression + add test for it * lint * filter out reserved keywords to prevent syntax errors * cleanup Co-authored-by: Maxime LUCE <maxime@touchify.co> Co-authored-by: Simon Holthausen <simon.holthausen@accso.de>
1 parent e2a9e87 commit a1c54fc

19 files changed

+790
-104
lines changed

docs/preprocessing.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -368,20 +368,19 @@ Note: `svelte-preprocess` automatically configures inclusion paths for your root
368368

369369
### TypeScript
370370

371-
| Option | Default | Description |
372-
| ------------------- | ----------- | ----------------------------------------------------------------------------------------------------------- |
373-
| `tsconfigDirectory` | `undefined` | optional `string` that specifies from where to load the tsconfig from.<br><br>i.e `'./configs'` |
374-
| `tsconfigFile` | `undefined` | optional `string` pointing torwards a `tsconfig` file.<br><br>i.e `'./tsconfig.app.json'` |
375-
| `compilerOptions` | `undefined` | optional compiler options configuration. These will be merged with options from the tsconfig file if found. |
371+
| Option | Default | Description |
372+
| -------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
373+
| `tsconfigDirectory` | `undefined` | optional `string` that specifies from where to load the tsconfig from.<br><br>i.e `'./configs'` |
374+
| `tsconfigFile` | `undefined` | optional `string` pointing torwards a `tsconfig` file.<br><br>i.e `'./tsconfig.app.json'` |
375+
| `compilerOptions` | `undefined` | optional compiler options configuration. These will be merged with options from the tsconfig file if found. |
376+
| `handleMixedImports` | inferred | optional `boolean` that defines the transpilation strategy. If set to `true`, you don't need to strictly separate types and values in imports. You need at least Svelte version 3.39 if you want to use this. `true` by default if you meet the minimum version requirement, else `false`. |
376377

377378
You can check the [`compilerOptions` reference](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) for specific TypeScript options.
378379

379380
#### Typescript - Limitations
380381

381382
- Since `v4`, `svelte-preprocess` doesn't type-check your component, its only concern is to transpile it into valid JavaScript for the compiler. If you want to have your components type-checked, you can use [svelte-check](https://github.com/sveltejs/language-tools/blob/master/packages/svelte-check/README.md).
382383

383-
- As we're only transpiling, it's not possible to import types or interfaces into your svelte component without using the new TS 3.8 `type` import modifier: `import type { SomeInterface } from './MyModule'` otherwise bundlers will complain that the name is not exported by `MyModule`.
384-
385384
- Using TypeScript inside a component's markup is currently **not** supported. See [#318](https://github.com/sveltejs/svelte-preprocess/issues/318) for development updates to this.
386385

387386
### `globalStyle`

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,16 @@
8585
"sass": "^1.26.8",
8686
"stylus": "^0.54.7",
8787
"sugarss": "^2.0.0",
88-
"svelte": "^3.23.0",
88+
"svelte": "^3.42.0",
8989
"ts-jest": "^25.1.0",
9090
"typescript": "^3.9.5"
9191
},
9292
"dependencies": {
9393
"@types/pug": "^2.0.4",
9494
"@types/sass": "^1.16.0",
9595
"detect-indent": "^6.0.0",
96+
"magic-string": "^0.25.7",
97+
"sorcery": "^0.10.0",
9698
"strip-indent": "^3.0.0"
9799
},
98100
"peerDependencies": {

src/autoProcess.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { transformMarkup } from './modules/markup';
2424
export const transform = async (
2525
name: string,
2626
options: TransformerOptions,
27-
{ content, map, filename, attributes }: TransformerArgs<any>,
27+
{ content, markup, map, filename, attributes }: TransformerArgs<any>,
2828
): Promise<Processed> => {
2929
if (options === false) {
3030
return { code: content };
@@ -39,6 +39,7 @@ export const transform = async (
3939

4040
return transformer({
4141
content,
42+
markup,
4243
filename,
4344
map,
4445
attributes,
@@ -122,6 +123,7 @@ export function sveltePreprocess(
122123
): Preprocessor => async (svelteFile) => {
123124
let {
124125
content,
126+
markup,
125127
filename,
126128
lang,
127129
alias,
@@ -158,6 +160,7 @@ export function sveltePreprocess(
158160

159161
const transformed = await transform(lang, transformerOptions, {
160162
content,
163+
markup,
161164
filename,
162165
attributes,
163166
});
@@ -176,6 +179,7 @@ export function sveltePreprocess(
176179
if (transformers.replace) {
177180
const transformed = await transform('replace', transformers.replace, {
178181
content,
182+
markup: content,
179183
filename,
180184
});
181185

@@ -192,11 +196,13 @@ export function sveltePreprocess(
192196
const script: PreprocessorGroup['script'] = async ({
193197
content,
194198
attributes,
199+
markup: fullMarkup,
195200
filename,
196201
}) => {
197202
const transformResult: Processed = await scriptTransformer({
198203
content,
199204
attributes,
205+
markup: fullMarkup,
200206
filename,
201207
});
202208

@@ -206,7 +212,7 @@ export function sveltePreprocess(
206212
const transformed = await transform(
207213
'babel',
208214
getTransformerOptions('babel'),
209-
{ content: code, map, filename, attributes },
215+
{ content: code, markup: fullMarkup, map, filename, attributes },
210216
);
211217

212218
code = transformed.code;
@@ -221,11 +227,13 @@ export function sveltePreprocess(
221227
const style: PreprocessorGroup['style'] = async ({
222228
content,
223229
attributes,
230+
markup: fullMarkup,
224231
filename,
225232
}) => {
226233
const transformResult = await cssTransformer({
227234
content,
228235
attributes,
236+
markup: fullMarkup,
229237
filename,
230238
});
231239

@@ -246,6 +254,7 @@ export function sveltePreprocess(
246254

247255
const transformed = await transform('postcss', postcssOptions, {
248256
content: code,
257+
markup: fullMarkup,
249258
map,
250259
filename,
251260
attributes,
@@ -259,7 +268,7 @@ export function sveltePreprocess(
259268
const transformed = await transform(
260269
'globalStyle',
261270
getTransformerOptions('globalStyle'),
262-
{ content: code, map, filename, attributes },
271+
{ content: code, markup: fullMarkup, map, filename, attributes },
263272
);
264273

265274
code = transformed.code;

src/modules/markup.ts

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
import type { Transformer, Preprocessor } from '../types';
22

3+
/** Create a tag matching regexp. */
4+
export function createTagRegex(tagName: string, flags?: string): RegExp {
5+
return new RegExp(
6+
`/<!--[^]*?-->|<${tagName}(\\s[^]*?)?(?:>([^]*?)<\\/${tagName}>|\\/>)`,
7+
flags,
8+
);
9+
}
10+
11+
/** Strip script and style tags from markup. */
12+
export function stripTags(markup: string): string {
13+
return markup
14+
.replace(createTagRegex('style', 'gi'), '')
15+
.replace(createTagRegex('script', 'gi'), '');
16+
}
17+
18+
/** Transform an attribute string into a key-value object */
19+
export function parseAttributes(attributesStr: string): Record<string, any> {
20+
return attributesStr
21+
.split(/\s+/)
22+
.filter(Boolean)
23+
.reduce((acc: Record<string, string | boolean>, attr) => {
24+
const [name, value] = attr.split('=');
25+
26+
// istanbul ignore next
27+
acc[name] = value ? value.replace(/['"]/g, '') : true;
28+
29+
return acc;
30+
}, {});
31+
}
32+
333
export async function transformMarkup(
434
{ content, filename }: { content: string; filename: string },
535
transformer: Preprocessor | Transformer<unknown>,
@@ -9,35 +39,29 @@ export async function transformMarkup(
939

1040
markupTagName = markupTagName.toLocaleLowerCase();
1141

12-
const markupPattern = new RegExp(
13-
`/<!--[^]*?-->|<${markupTagName}(\\s[^]*?)?(?:>([^]*?)<\\/${markupTagName}>|\\/>)`,
14-
);
42+
const markupPattern = createTagRegex(markupTagName);
1543

1644
const templateMatch = content.match(markupPattern);
1745

1846
/** If no <template> was found, run the transformer over the whole thing */
1947
if (!templateMatch) {
20-
return transformer({ content, attributes: {}, filename, options });
48+
return transformer({
49+
content,
50+
markup: content,
51+
attributes: {},
52+
filename,
53+
options,
54+
});
2155
}
2256

2357
const [fullMatch, attributesStr = '', templateCode] = templateMatch;
2458

25-
/** Transform an attribute string into a key-value object */
26-
const attributes = attributesStr
27-
.split(/\s+/)
28-
.filter(Boolean)
29-
.reduce((acc: Record<string, string | boolean>, attr) => {
30-
const [name, value] = attr.split('=');
31-
32-
// istanbul ignore next
33-
acc[name] = value ? value.replace(/['"]/g, '') : true;
34-
35-
return acc;
36-
}, {});
59+
const attributes = parseAttributes(attributesStr);
3760

3861
/** Transform the found template code */
3962
let { code, map, dependencies } = await transformer({
4063
content: templateCode,
64+
markup: templateCode,
4165
attributes,
4266
filename,
4367
options,

src/modules/tagInfo.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const getTagInfo = async ({
2727
attributes,
2828
filename,
2929
content,
30+
markup,
3031
}: PreprocessorArgs) => {
3132
const dependencies = [];
3233
// catches empty content and self-closing tags
@@ -62,5 +63,6 @@ export const getTagInfo = async ({
6263
lang,
6364
alias,
6465
dependencies,
66+
markup,
6567
};
6668
};

src/modules/utils.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,54 @@ export function setProp(obj, keyList, val) {
107107

108108
obj[keyList[i]] = val;
109109
}
110+
111+
export const javascriptReservedKeywords = new Set([
112+
'arguments',
113+
'await',
114+
'break',
115+
'case',
116+
'catch',
117+
'class',
118+
'const',
119+
'continue',
120+
'debugger',
121+
'default',
122+
'delete',
123+
'do',
124+
'else',
125+
'enum',
126+
'eval',
127+
'export',
128+
'extends',
129+
'false',
130+
'finally',
131+
'for',
132+
'function',
133+
'if',
134+
'implements',
135+
'import',
136+
'in',
137+
'instanceof',
138+
'interface',
139+
'let',
140+
'new',
141+
'null',
142+
'package',
143+
'private',
144+
'protected',
145+
'public',
146+
'return',
147+
'static',
148+
'super',
149+
'switch',
150+
'this',
151+
'throw',
152+
'true',
153+
'try',
154+
'typeof',
155+
'var',
156+
'void',
157+
'while',
158+
'with',
159+
'yield',
160+
]);

src/processors/typescript.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default (options?: Options.Typescript): PreprocessorGroup => ({
88
const { transformer } = await import('../transformers/typescript');
99
let {
1010
content,
11+
markup,
1112
filename,
1213
attributes,
1314
lang,
@@ -22,6 +23,7 @@ export default (options?: Options.Typescript): PreprocessorGroup => ({
2223

2324
const transformed = await transformer({
2425
content,
26+
markup,
2527
filename,
2628
attributes,
2729
options,

0 commit comments

Comments
 (0)