Skip to content

Commit

Permalink
feat: add experimental support for Svelte 5
Browse files Browse the repository at this point in the history
  • Loading branch information
dummdidumm committed Nov 10, 2023
1 parent a48cc4e commit 923f437
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 60 deletions.
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
@@ -0,0 +1,13 @@
# Contributing

PRs for issues are welcome. Before pushing the code, make sure that linting and tests pass.

Commit pattern: conventional commit (`docs/feat/fix/chore: ..`) (https://www.conventionalcommits.org/en/v1.0.0/)
Changelog pattern: conventional-changelog tool to generate the changelog (https://github.com/conventional-changelog/conventional-changelog)

The publishing process is manual right now, so the current workflow is:

- Merge to main
- Checkout to main
- run npm run patch | minor | major - this will update the changelog, bump the package.json and create the release tag
- run npm publish (maybe 2fa will be asked here)
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -104,7 +104,7 @@
"sass": "^1.26.8",
"stylus": "^0.55.0",
"sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
"svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0",
"svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0",
"typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
Expand Down
9 changes: 6 additions & 3 deletions src/transformers/typescript.ts
Expand Up @@ -150,12 +150,13 @@ function injectVarsToCode({
}): string {
if (!markup) return content;

// @ts-ignore different in Svelte 5
const { vars } = compile(stripTags(markup), {
generate: false,
varsReport: 'full',
errorMode: 'warn',
filename,
});
}) as { vars: any[] };

const sep = `\n${injectedCodeSeparator}\n`;
const varnames = vars.map((v) =>
Expand Down Expand Up @@ -552,11 +553,13 @@ const transformer: Transformer<Options.Typescript> = async ({
const compilerOptions = getCompilerOptions({ filename, options, basePath });
const versionParts = pkg.version.split('.');
const canUseMixedImportsTranspiler =
+versionParts[0] > 3 || (+versionParts[0] === 3 && +versionParts[1] >= 39);
+versionParts[0] === 4 ||
(+versionParts[0] === 3 && +versionParts[1] >= 39);

if (!canUseMixedImportsTranspiler && options.handleMixedImports) {
throw new Error(
'You need at least Svelte 3.39 to use the handleMixedImports option',
'You need at least Svelte 3.39 and at most Svelte 4.x to use the handleMixedImports option. ' +
'The option is no longer available for Svelte 5 and beyond. Use the verbatimModuleSyntax TypeScript option instead.',
);
}

Expand Down
114 changes: 59 additions & 55 deletions test/transformers/typescript.test.ts
@@ -1,6 +1,6 @@
import { resolve } from 'path';

import { compile } from 'svelte/compiler';
import { compile, VERSION } from 'svelte/compiler';

import sveltePreprocess from '../../src';
import { loadTsconfig } from '../../src/transformers/typescript';
Expand All @@ -16,6 +16,8 @@ import type { Diagnostic } from 'typescript';

spyConsole({ silent: true });

const IS_SVELTE_5_PLUS = Number(VERSION.split('.')[0]) >= 5;

const EXPECTED_SCRIPT = getFixtureContent('script.js');

const autoProcessTS = (content: string, compilerOptions?: any) => {
Expand Down Expand Up @@ -113,34 +115,6 @@ describe('transformer - typescript', () => {
expect(code).toContain(getFixtureContent('script.js'));
});

it('should strip unused and type imports', async () => {
const tpl = getFixtureContent('TypeScriptImports.svelte');

const opts = sveltePreprocess({
typescript: { tsconfigFile: false },
});

const { code } = await preprocess(tpl, opts);

// Test that imports are properly preserved
expect(code).toContain(`import { fly } from "svelte/transition"`);
expect(code).toContain(`import { flip } from "svelte/animate"`);
expect(code).toContain(`import Nested from "./Nested.svelte"`);
expect(code).toContain(`import { hello } from "./script"`);
expect(code).toContain(`import { AValue } from "./types"`);
expect(code).toContain(
`import { storeTemplateOnly, storeScriptOnly, store0 } from "./store"`,
);
expect(code).toContain(
`import { onlyUsedInModuleScript } from "./modulescript";`,
);
expect(code).toContain(
`import { storeModuleTemplateOnly, storeModuleScriptOnly } from "./store";`,
);
// Test that comments are properly preserved
expect(code).toContain('<!-- Some comment -->');
});

it('should keep all value imports with verbatimModuleSyntax', async () => {
const tpl = getFixtureContent('PreserveValueImports.svelte');

Expand Down Expand Up @@ -192,18 +166,6 @@ describe('transformer - typescript', () => {
expect(code).toBe(`<script lang="ts" context="module"></script>`);
});

it('should strip unused and type imports in context="module" tags', async () => {
const tpl = getFixtureContent('TypeScriptImportsModule.svelte');

const opts = sveltePreprocess({
typescript: { tsconfigFile: false },
});

const { code } = await preprocess(tpl, opts);

expect(code).toContain(`import { AValue } from "./types";`);
});

it('should produce sourcemap', async () => {
const tpl = getFixtureContent('TypeScriptImportsModule.svelte');

Expand All @@ -217,20 +179,6 @@ describe('transformer - typescript', () => {
expect(map).toHaveProperty('sources', ['App.svelte']);
});

it('should work when tsconfig contains outDir', async () => {
const opts = sveltePreprocess({
typescript: {
tsconfigFile: './test/fixtures/tsconfig.outdir.json',
handleMixedImports: true,
},
sourceMap: true,
});

const preprocessed = await preprocess(template, opts);

expect(preprocessed.toString?.()).toContain(EXPECTED_SCRIPT);
});

it('supports extends field', () => {
const { options } = loadTsconfig({}, getTestAppFilename(), {
tsconfigFile: './test/fixtures/tsconfig.extends1.json',
Expand Down Expand Up @@ -280,4 +228,60 @@ describe('transformer - typescript', () => {
expect(code).not.toContain('||=');
});
});

(IS_SVELTE_5_PLUS ? describe.skip : describe)('mixed imports', () => {
it('should strip unused and type imports', async () => {
const tpl = getFixtureContent('TypeScriptImports.svelte');

const opts = sveltePreprocess({
typescript: { tsconfigFile: false },
});

const { code } = await preprocess(tpl, opts);

// Test that imports are properly preserved
expect(code).toContain(`import { fly } from "svelte/transition"`);
expect(code).toContain(`import { flip } from "svelte/animate"`);
expect(code).toContain(`import Nested from "./Nested.svelte"`);
expect(code).toContain(`import { hello } from "./script"`);
expect(code).toContain(`import { AValue } from "./types"`);
expect(code).toContain(
`import { storeTemplateOnly, storeScriptOnly, store0 } from "./store"`,
);
expect(code).toContain(
`import { onlyUsedInModuleScript } from "./modulescript";`,
);
expect(code).toContain(
`import { storeModuleTemplateOnly, storeModuleScriptOnly } from "./store";`,
);
// Test that comments are properly preserved
expect(code).toContain('<!-- Some comment -->');
});

it('should strip unused and type imports in context="module" tags', async () => {
const tpl = getFixtureContent('TypeScriptImportsModule.svelte');

const opts = sveltePreprocess({
typescript: { tsconfigFile: false },
});

const { code } = await preprocess(tpl, opts);

expect(code).toContain(`import { AValue } from "./types";`);
});

it('should work when tsconfig contains outDir', async () => {
const opts = sveltePreprocess({
typescript: {
tsconfigFile: './test/fixtures/tsconfig.outdir.json',
handleMixedImports: true,
},
sourceMap: true,
});

const preprocessed = await preprocess(template, opts);

expect(preprocessed.toString?.()).toContain(EXPECTED_SCRIPT);
});
});
});
2 changes: 1 addition & 1 deletion test/utils.ts
Expand Up @@ -17,7 +17,7 @@ export const preprocess = async (input: string, opts: any) =>
const compile = async (input: string, opts: any) => {
const preprocessed = await exports.preprocess(input, opts);
const { js, css } = svelteCompile(preprocessed.toString?.(), {
css: true,
css: 'injected',
});

return { js, css };
Expand Down

0 comments on commit 923f437

Please sign in to comment.