Sapper: TypeScript support updates (Sapper + Svelte 3)

Created on 20 Jun 2019  Â·  55Comments  Â·  Source: sveltejs/sapper

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 https://github.com/Microsoft/TypeScript/issues/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,

Most helpful comment

@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

All 55 comments

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

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

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

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.

@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

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.

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

Any status update on this?

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

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.

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

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

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.

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?

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

@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).

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)

@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

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

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.

I'm trying to compile a project with strict settings.

A few of the default packages could use TypeScript types:

After I got past that, I got an error:

> @rollup/plugin-typescript TS6133: 'routes' is declared but its value is never read.

I have no idea where this is coming from because there's no file name or line number

Has anyone figured out how to convert service-worker to TypeScript?

It's not happy with the default return statements:

> @rollup/plugin-typescript TS2345: Argument of type 'Promise<Response | undefined>' is not assignable to parameter of type 'Response | Promise<Response>'.
  Type 'Promise<Response | undefined>' is not assignable to type 'Promise<Response>'.
    Type 'Response | undefined' is not assignable to type 'Response'.
      Type 'undefined' is not assignable to type 'Response'.

Another idea: we could have Sapper spit out the .d.ts from above (https://github.com/sveltejs/sapper/issues/760#issuecomment-504230103) into node_modules/@types/@sapper/index.d.ts while building

There are four major issues we'll have to address to really support TypeScript with Sapper:

I've sent PRs for the first three since they're in Sapper

@benmccann

1222 (TypeScript in templates)

I am a little confused here, the title of this pr is

TypeScript support: don't try to compile templates

so which is it? Does this pr typecheck templates, or does it bypass template typechecking?

It stops Sapper from trying to first compile a template to find out if there's a preload method in it and then doing a second compilation again later to actually compile the app. With the PR it only compiles the templates when it's actually compiling the app. This should significantly speed up build time by reducing duplicate compilations as well

I created a fork of Sapper that has a few pending PRs merged in. It enables TypeScript by fixing the first three of the four above issues and adds a couple other bug fixes that are waiting to be merged in this repo as well. I didn't publish it to npm, but you can get it directly from GitHub:

  "scripts": {
    "postinstall": "cd ./node_modules/sapper && npm install && npm run build"
  },
  "devDependencies": {
    "sapper": "git+https://[email protected]/zopost/sapper.git",
  }

You should be able to use Sapper with TypeScript now!

What's the state of this PR?

This is a meta issue discussing multiple different issues. If the PR you're referring to is https://github.com/sveltejs/sapper/pull/1222 then I'm waiting for the maintainers to either provide additional feedback or approve and merge the PR

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.

@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

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

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

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

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

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

Probably because he hasn't seen that one.

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

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.

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.

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']}),
]

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: https://github.com/sveltejs/sapper/issues/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

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

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

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?

Someone started a PR to add that: https://github.com/sveltejs/sapper-template/pull/252

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

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

milosdjakovic picture milosdjakovic  Â·  3Comments

keyvan-m-sadeghi picture keyvan-m-sadeghi  Â·  4Comments

UnwrittenFun picture UnwrittenFun  Â·  4Comments

nolanlawson picture nolanlawson  Â·  4Comments

matt3224 picture matt3224  Â·  4Comments