Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

TypeScript support updates (Sapper + Svelte 3) #760

Closed
pnmcosta opened this issue Jun 20, 2019 · 55 comments
Closed

TypeScript support updates (Sapper + Svelte 3) #760

pnmcosta opened this issue Jun 20, 2019 · 55 comments

Comments

@pnmcosta
Copy link

Hi,

Just thought I'd share my experience with Sapper + TypeScript.

I have a monorepo with various "packages", and one of them is a Sapper app that I've mostly converted to TS with the help of some repos and issues from here. Thank you all for your efforts! I plan to share this monorepo boilerplate once it's more presentable than it is now :)

Only a few things stand out for me, that I'll summarize here for others who may want to adopt it too:

  • the new mjs generated modules into ./src/node_modules/@sapper are impossible to import with TS for the moment, see Support .mjs output microsoft/TypeScript#18442, TLDR: As far as I can tell at the moment it is not certain how Node will proceed with mjs therefore TS is still unsure how to proceed too. Workrounds involve other dependencies that i'd like to avoid.
  • sapper build/dev config expects entry points in .js so you have to keep client.js, server.js and service-worker.js, it would be nice if we could configure this, or sapper accept one or the other.
  • you need to configure rollup/webback to include a typescript plugin, e.g. rollup-plugin-typescript2
  • you can't use <script lang="ts"> on *.svelte components, for the moment, mostly because of point 1 here.

So far so good, and I'm really enjoying the sapper/svelte world, IMO is much more readable/understandable overall than most other frameworks out there.

Thanks,

@pnmcosta pnmcosta changed the title TypeScript support updates (Sappers + Svelte 3) TypeScript support updates (Sapper + Svelte 3) Jun 20, 2019
@teoxoy
Copy link
Contributor

teoxoy commented Jun 20, 2019

the new mjs generated modules into ./src/node_modules/@sapper are impossible to import with TS for the moment, see microsoft/TypeScript#18442, TLDR: As far as I can tell at the moment it is not certain how Node will proceed with mjs therefore TS is still unsure how to proceed too. Workrounds involve other dependencies that i'd like to avoid.

You can import the mjs files by passing the mjs extension to the options of rollup-plugin-node-resolve (i.e. resolve({ extensions: ['.mjs', '.js', '.ts', '.json'] })).

I also have a file with the type info if you are interested:

declare module '@sapper/app' {
    // from sapper/runtime/src/app/types.ts
    // sapper doesn't export its types yet
    interface Redirect {
        statusCode: number
        location: string
    }
    // end

    function goto(href: string, opts = { replaceState: false }): Promise<unknown>
    function prefetch(href: string): Promise<{ redirect?: Redirect; data?: unknown }>
    function prefetchRoutes(pathnames: string[]): Promise<unknown>
    function start(opts: { target: Node }): Promise<unknown>
    const stores: () => unknown

    export { goto, prefetch, prefetchRoutes, start, stores }
}

declare module '@sapper/server' {
    import { RequestHandler } from 'express'

    interface MiddlewareOptions {
        session?: (req: Express.Request, res: Express.Response) => unknown
        ignore?: unknown
    }

    function middleware(opts: MiddlewareOptions = {}): RequestHandler

    export { middleware }
}

declare module '@sapper/service-worker' {
    const timestamp: number
    const files: string[]
    const shell: string[]
    const routes: { pattern: RegExp }[]

    export { timestamp, files, files as assets, shell, routes }
}

sapper build/dev config expects entry points in .js so you have to keep client.js, server.js and service-worker.js, it would be nice if we could configure this, or sapper accept one or the other.

I have converted those 3 entry points to TS and added this to the config:

  • client: input: config.client.input().replace(/\.js$/, '.ts')
  • server: input: { server: config.server.input().server.replace(/\.js$/, '.ts') }
  • sw: input: config.serviceworker.input().replace(/\.js$/, '.ts'),

This is a hack and I would like to specify .ts somewhere. Maybe as an arg to the input function (i.e. config.client.input('.ts'))

you can't use <script lang="ts"> on *.svelte components

Try svelte-preprocess or svelte-ts-preprocess
They didn't work in my case but maybe you can get them to work

@pnmcosta
Copy link
Author

That definition file is priceless :) thank you.

Did try switching the endpoint, but now on yarn dev I get:

yarn run v1.16.0
warning package.json: No license field
$ sapper dev
• server
The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
1: var _this = this;
               ^
2: import * as tslib_1 from "tslib";
3: import 'dotenv/config';
✔ client (1.7s)
✔ service worker (16ms)
> Listening on http://localhost:3000

Still works tho, which is odd, not sure why it appended the tslib import to the server.ts, but I'm actually keener on the preprocess, will have to test that later.

Thanks for the input, much obliged.

@teoxoy
Copy link
Contributor

teoxoy commented Jun 28, 2019

Not sure what might cause that tbh.

I didn't have any luck with svelte-preprocess or svelte-ts-preprocess
But got @pyoner/svelte-ts-preprocess to work

@IlyaSemenov
Copy link

Here's the diff to enable typescript:

diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js
index 44cbdacd..41da3ec5 100644
--- a/frontend/rollup.config.js
+++ b/frontend/rollup.config.js
@@ -1,9 +1,15 @@
+import {
+	createEnv,
+	preprocess,
+	readConfigFile,
+} from "@pyoner/svelte-ts-preprocess"
 import babel from "rollup-plugin-babel"
 import commonjs from "rollup-plugin-commonjs"
 import resolve from "rollup-plugin-node-resolve"
 import replace from "rollup-plugin-replace"
 import svelte from "rollup-plugin-svelte"
 import { terser } from "rollup-plugin-terser"
+import typescript from "rollup-plugin-typescript2"
 import config from "sapper/config/rollup.js"
 
 import pkg from "./package.json"
@@ -19,6 +25,16 @@ const onwarn = (warning, onwarn) =>
 const dedupe = importee =>
 	importee === "svelte" || importee.startsWith("svelte/")
 
+const env = createEnv()
+const compilerOptions = readConfigFile(env)
+const opts = {
+	env,
+	compilerOptions: {
+		...compilerOptions,
+		allowNonTsExtensions: true,
+	},
+}
+
 export default {
 	client: {
 		input: config.client.input(),
@@ -32,12 +48,14 @@ export default {
 				dev,
 				hydratable: true,
 				emitCss: true,
+				preprocess: preprocess(opts),
 			}),
 			resolve({
 				browser: true,
 				dedupe,
 			}),
 			commonjs(),
+			typescript(),
 
 			legacy &&
 				babel({
@@ -83,11 +101,13 @@ export default {
 			svelte({
 				generate: "ssr",
 				dev,
+				preprocess: preprocess(opts),
 			}),
 			resolve({
 				dedupe,
 			}),
 			commonjs(),
+			typescript(),
 		],
 		external: Object.keys(pkg.dependencies).concat(
 			require("module").builtinModules,
@@ -106,6 +126,7 @@ export default {
 				"process.env.NODE_ENV": JSON.stringify(mode),
 			}),
 			commonjs(),
+			typescript(),
 			!dev && terser(),
 		],
 

I didn't test if legacy output still works though.

@prescientmoon
Copy link

I know this question is kind of stupid, but where do i need to put that definition file? If i put it in the index.d.ts of a types folder and add that to my typeRoots option in the tsconfig i keep getting something among the lines of "cannot find module @sapper/server"

@teoxoy
Copy link
Contributor

teoxoy commented Sep 6, 2019

You need to add it to the types array too.
This is how I have it set up:

"typeRoots": ["node_modules/@types", "typings"],
"types": ["@pyoner/svelte-types", "@sapper"]

In my project typings is a directory at the root of the project which contains subfolders named the same as the modules the typedef files are for

@swyxio
Copy link

swyxio commented Sep 6, 2019

do you have to put it in types array? doesnt it just become an ambient module declaration?

@teoxoy
Copy link
Contributor

teoxoy commented Sep 6, 2019

From what I can remember, I also got the "cannot find module" error when I didn't do it. Adding it fixed it. Looking at the docs it should work without it too.

Some things might not work as expected because there is an extra node_modules folder in the src of sapper projects containing the @sapper package. The resolver might get confused because usually packages with @ are scoped packages but the @sapper package doesn't contain any other child packages (it's a package by itself).

This is more or less speculation on my part but the fact is that adding @sapper to the types array gets rid of the error - just tried it and it seems to still be the case.

@cortopy
Copy link

cortopy commented Sep 7, 2019

@teoxoy would you like to publish those in DefinitelyTyped until there's official support?

I needed to modify a bit as the start function can also take an Element

@teoxoy
Copy link
Contributor

teoxoy commented Sep 7, 2019

I don't think the maintainers of DefinitelyTyped would accept it because there is no @sapper package on npm and as I stated above there can't be either (npm would treat it as a scope not as a package).

The contents of @sapper are being generated by sapper and are not a proper npm package.

@ajbouh
Copy link

ajbouh commented Sep 25, 2019

To the folks using typescript for both components and routes: what rough edges have you encountered?

@khpatel4991
Copy link

Any status update on this?

@marcus-sa
Copy link

I'm going about implementing this here https://github.com/avantci/svelte-ts

@dimfeld
Copy link

dimfeld commented Oct 29, 2019

One issue I just encountered is that the route preload function doesn't work on the server when using Typescript. I'm not 100% sure but this looks like a similar problem to #594 where has_preload doesn't find the function if preprocessing is needed to generate valid standard Svelte source.

@eh-dub
Copy link

eh-dub commented Oct 30, 2019

Just got this all building; here's a Gist of the files I had to change. This gist captures all the comments made in this thread and makes a few updates.

https://gist.github.com/eh-dub/4bf3b9137f0cd10780b6383c855d7ad1

@the-homeless-god
Copy link

https://github.com/Zimtir/SENT-template
Sapper, Express, Node, Typescript in one the template repo based on https://gist.github.com/eh-dub/4bf3b9137f0cd10780b6383c855d7ad1

@antony
Copy link
Member

antony commented Apr 11, 2020

How far will sveltejs/svelte#4518 get you?

@Conduitry
Copy link
Member

The additional layer with Sapper is that, prior to your app being bundled, Sapper goes through each route component to see whether it exports a preload. It does this by attempting to compile each component and checking the vars metadata returned by the compiler. This is independent of any preprocessing you've configured in your bundler, and there is not currently a way to hook into this.

The actual logic use by Sapper during this preliminary phase is currently:

  • check whether the component contains the string preload. If not, assume it does not define a preload.
  • if it does contain the string preload, run it through svelte.compile(). if it fails, assume it does not define a preload(*)
  • if it contains the string preload, and it successfully runs through svelte.compile(), look whether vars reports a module-level export called preload

(*) it's actually slightly more complicated than this. Before compile-to-CSS preprocessors are fairly common, and since they will not affect whether the component has a preload, we actually blank out the <style> tag in the component before trying to compile it, so that we will not fail to recognize a preload merely because the styles are written in SCSS, for example.

It's the second phase that's a problem when component are written in typescript, because compiling the component as-is will fail. Telling Sapper what preprocessors to run on components before running this check would require some sort of configuration file, because it doesn't really make sense to pass code as a command line parameter. Either way, this is still kind of opinionitis-y. I'm not sure what a reasonable compromise is. The best I can think of is to assume that components that do contain the string preload but do not compile successfully do in fact have a preload (which is the opposite of what we're doing now). This might have some false positives, but probably not a whole lot. The only other thing I can think of is to check for errors from the bundler, and if it reports things like whatever.svelte does not export preload we can remove the 'has preload' flag on the component and try the bundling again, but this sounds like a pain. I don't know what the right answer is.

@leefan
Copy link

leefan commented Apr 25, 2020

So I've been trying for the past week to get sapper with webpack working with typescript - I can't seem for the life of me to get typescript routes working.
Starting from the rollup template, adding a tsconfig.json, @wessberg/rollup-plugin-ts, and a single line to rollup.config.js was sufficient to get .ts routes working:

  	server: {
		input: config.server.input(),
		output: config.server.output(),
		plugins: [
			replace({
				'process.browser': false,
				'process.env.NODE_ENV': JSON.stringify(mode)
			}),
			svelte({
				generate: 'ssr',
				dev
			}),
			resolve({
				dedupe: ['svelte']
			}),
			commonjs(),
+			typescript()
		],
		external: Object.keys(pkg.dependencies).concat(
			require('module').builtinModules || Object.keys(process.binding('natives'))
		),

		onwarn,
	},

With the webpack template, I did the equivalent of adding tsconfig.json, ts-loader, and an entry in webpack.config.js in server at the end of the chain (also added .ts to extensions):

  server: {
    entry: config.server.entry(),
    output: config.server.output(),
    target: 'node',
    resolve: { alias, extensions, mainFields },
    externals: Object.keys(pkg.dependencies).concat('encoding'),
    module: {
      rules: [
        {
          test: /\.(svelte|html)$/,
          use: {
            loader: 'svelte-loader',
            options: {
              css: false,
              generate: 'ssr',
              dev,
            },
          },
        },
+       { test: /\.ts$/, loader: 'ts-loader' },
      ],
    },
    mode: process.env.NODE_ENV,
    performance: {
      hints: false, // it doesn't matter if server.js is large
    },
  },

This results in issues with loading .ts routes. For example, if I make a simple

// src/routes/test.ts
export function get(req, res, next) {
  res.end('test')
}

then I get a 404 when I try navigating to /test. However if I create the same file but name it src/routes/test.js, I get notified that there's two handlers for the same path so clearly it's detecting the .ts path but not serving it. I can also name the file src/routes/_test.ts and import it from

// src/routes/test.js
export { get } from 'src/routes/_test'

which succeeds (but adds a ton of boilerplate).

I've tried studying https://github.com/MattNguyen/sapper-template-typescript which works but is based on an older version of sapper. Seems like something broke non-js routes for webpack builds?

@the-homeless-god
Copy link

So I've been trying for the past week to get sapper with webpack working with typescript - I can't seem for the life of me to get typescript routes working.
Starting from the rollup template, adding a tsconfig.json, @wessberg/rollup-plugin-ts, and a single line to rollup.config.js was sufficient to get .ts routes working:

  	server: {
		input: config.server.input(),
		output: config.server.output(),
		plugins: [
			replace({
				'process.browser': false,
				'process.env.NODE_ENV': JSON.stringify(mode)
			}),
			svelte({
				generate: 'ssr',
				dev
			}),
			resolve({
				dedupe: ['svelte']
			}),
			commonjs(),
+			typescript()
		],
		external: Object.keys(pkg.dependencies).concat(
			require('module').builtinModules || Object.keys(process.binding('natives'))
		),

		onwarn,
	},

With the webpack template, I did the equivalent of adding tsconfig.json, ts-loader, and an entry in webpack.config.js in server at the end of the chain (also added .ts to extensions):

  server: {
    entry: config.server.entry(),
    output: config.server.output(),
    target: 'node',
    resolve: { alias, extensions, mainFields },
    externals: Object.keys(pkg.dependencies).concat('encoding'),
    module: {
      rules: [
        {
          test: /\.(svelte|html)$/,
          use: {
            loader: 'svelte-loader',
            options: {
              css: false,
              generate: 'ssr',
              dev,
            },
          },
        },
+       { test: /\.ts$/, loader: 'ts-loader' },
      ],
    },
    mode: process.env.NODE_ENV,
    performance: {
      hints: false, // it doesn't matter if server.js is large
    },
  },

This results in issues with loading .ts routes. For example, if I make a simple

// src/routes/test.ts
export function get(req, res, next) {
  res.end('test')
}

then I get a 404 when I try navigating to /test. However if I create the same file but name it src/routes/test.js, I get notified that there's two handlers for the same path so clearly it's detecting the .ts path but not serving it. I can also name the file src/routes/_test.ts and import it from

// src/routes/test.js
export { get } from 'src/routes/_test'

which succeeds (but adds a ton of boilerplate).

I've tried studying https://github.com/MattNguyen/sapper-template-typescript which works but is based on an older version of sapper. Seems like something broke non-js routes for webpack builds?

take a moment to look at the following template https://github.com/Zimtir/SENT-template
may be it can solve your issue

@leefan
Copy link

leefan commented Apr 25, 2020

@Zimtir no it can't, I'm looking for a webpack solution. Additionally you don't use .ts routes in your template (altho I suspect they do work).

@ajbouh
Copy link

ajbouh commented Apr 30, 2020

So I've been trying for the past week to get sapper with webpack working with typescript - I can't seem for the life of me to get typescript routes working.

@leefan Perhaps the issue is that sapper is looking for files to be using module-style files and your tsconfig.json is configured with "module": "CommonJS"?

If you can't change the "module" compiler option in your tsconfig.json file, you can try overriding this value in your webpack config by changing ...

  { test: /\.ts$/, loader: 'ts-loader' },

To

{
  test: /\.ts$/,
  use: {
    loader: "ts-loader",
    options: {
      compilerOptions: {
        module: "ES2015",
      },
    },
  },
},

(making the above changes seemed to be enough for me)

@leefan
Copy link

leefan commented May 1, 2020

@ajbouh thanks so much!! That indeed was the problem, I had it configured for CommonJS. I guess I had a fundamental misconception about CommonJS, I had assumed ES6 modules were reverse compatible with CommonJS modules... TIL

@benmccann
Copy link
Member

the new mjs generated modules into ./src/node_modules/@sapper are impossible to import with TS for the moment, see microsoft/TypeScript#18442, TLDR: As far as I can tell at the moment it is not certain how Node will proceed with mjs therefore TS is still unsure how to proceed too. Workrounds involve other dependencies that i'd like to avoid.

Could we generate .js files and add type: "module" to package.json instead? That seems like it'd be a lot easier than adding a bunch of configuration and overrides to deal with .mjs files

@benmccann
Copy link
Member

benmccann commented May 22, 2020

The best I can think of is to assume that components that do contain the string preload but do not compile successfully do in fact have a preload (which is the opposite of what we're doing now). This might have some false positives, but probably not a whole lot.

@Conduitry could we search for export\s+.*?preload? That would probably cut down on false positives even more.

It looks to me like has_preload is only called for .svelte files, which should make the change pretty safe. I imagine the sapper documentation site would trigger the export async function preload check alone, but that's all in .md files. I can't imagine anything else would trigger the string check test.

@benmccann
Copy link
Member

This is a meta issue discussing multiple different issues. If the PR you're referring to is #1222 then I'm waiting for the maintainers to either provide additional feedback or approve and merge the PR

@sesteva
Copy link

sesteva commented Aug 2, 2020

https://github.com/Zimtir/SENT-template
Sapper, Express, Node, Typescript in one the template repo based on https://gist.github.com/eh-dub/4bf3b9137f0cd10780b6383c855d7ad1

@Zimtir In your template I experience the same challenge. The moment you use typescript in a component, then the preload will not work. It will be executed on the client but not on the server.

@benmccann
Copy link
Member

@sesteva that issue has been fixed and Sapper 0.28.0 will add TypeScript support. We're trying to wrap up a few other issues before doing a release

@sesteva
Copy link

sesteva commented Aug 2, 2020

@benmccann That's amazing news!!!!!!
Is there a main issue to track?

@benmccann
Copy link
Member

Sapper 0.28 has been released with TypeScript support, so I'm going to close this

There's a chance there are some outstanding rough edges since this is a new feature. If you encounter any bugs, it'd be best to file them as new issues since this one has gotten quite long and discussed many facets of the support

@plashenkov
Copy link

@benmccann Is there any info on how to turn on TypeScript support in Sapper?

@benmccann
Copy link
Member

Here's a template that I think is quite good: https://github.com/babichjacob/sapper-typescript-graphql-template

@PabloSzx
Copy link

PabloSzx commented Aug 8, 2020

Is there going to be official documentation of the feature in the sapper website?

@the-homeless-god
Copy link

@pngwn
Copy link
Member

pngwn commented Aug 8, 2020

Probably because he hasn't seen that one.

@robcresswell
Copy link

robcresswell commented Aug 9, 2020

Is this really considered "done"? I appreciate all the work thats gone on here, but without any documentation, examples, or even a changelog beyond "typescript support!", it doesn't feel quite "finished".

Can we reopen this? It'll be easier to reuse the references in this thread if we dont bury it, as there's a lot of valuable content. Sorry in advance if I'm missing something

@robcresswell
Copy link

robcresswell commented Aug 9, 2020

For example, the only way I could get sappers autogenerated internal node_modules/ to play nicely with TS was to dig up https://github.com/babichjacob/sapper-typescript-graphql-template/blob/main/types/%40sapper/index.d.ts, which isn't exactly a wonderful first-time sapper dev experience.

@plashenkov
Copy link

@pngwn
Copy link
Member

pngwn commented Aug 9, 2020

This issue was about enabling typescript support, we definitely need to improve the documentation around this added support.

I'd recommend opening a new issue, so it is easier to track and we can discuss the best way to document this, through examples, templates etc. If you have any suggestions then they would also be most welcome.

@plashenkov
Copy link

I've also found that adding TypeScript to Sapper via @babel/preset-typescript instead of "traditional" typescript is much smoother experience.

You basically have to install @babel/preset-typescript, turn it on in babel.config.js:

module.exports = {
  presets: [
    ['@babel/preset-env'],
    ['@babel/preset-typescript'],
  ],
  plugins: [
    // only if you need these (for TypeORM for example)
    ['babel-plugin-transform-typescript-metadata'],
    ['@babel/plugin-proposal-decorators', {legacy: true}],
    ['@babel/plugin-proposal-class-properties', {loose: true}],
  ]
}

Then in rollup.config.js specify extensions for babel plugin:

plugins: [
  // ...
  babel({extensions: ['.ts', '.js', '.mjs']}),
]

@benmccann
Copy link
Member

Good point about the index.d.ts file. I filed a new feature request to make it so that you don't have to manually add it to your project: #1381

Agreed that we should add more documentation as well. Though I think that we can just point people to the Svelte documentation for TypeScript once we generate the .d.ts file because at that point there shouldn't be anything unique about setting up a Sapper project for TypeScript

@regnaio
Copy link

regnaio commented Aug 15, 2020

Hope this diff can help others use the Sapper 0.28 Typescript enabled feature:
regnaio/sapper-typescript@712d329

Used snippets from: https://github.com/babichjacob/sapper-typescript-graphql-template

@brigraham
Copy link

Has anyone still had issues with preload not being called even with v.28.0? I'm using webpack with typescript support and still not seeing preload getting hit

@benmccann
Copy link
Member

benmccann commented Aug 25, 2020

Sapper 0.28.1 is out which provides the index.d.ts now so that you no longer have to do that

@PabloSzx
Copy link

PabloSzx commented Sep 4, 2020

Agreed that we should add more documentation as well. Though I think that we can just point people to the Svelte documentation for TypeScript once we generate the .d.ts file because at that point there shouldn't be anything unique about setting up a Sapper project for TypeScript

@benmccann Any updates on the documentation mentioned? and/or any official sapper-typescript template?

@benmccann
Copy link
Member

Someone started a PR to add that: sveltejs/sapper-template#252

If someone would like to take it over, I'd be happy to help review and get it checked in

@PabloSzx
Copy link

PabloSzx commented Sep 4, 2020

image

The definitions included import types that don't exist, and the alias @sapper to "sapper" doesn't work either

I would like to help, but at least I need to have access to the code from where you are testing this code, something working

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests