Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SWC Support #1549

Closed
1 task done
jackpordi opened this issue Feb 28, 2022 · 25 comments
Closed
1 task done

SWC Support #1549

jackpordi opened this issue Feb 28, 2022 · 25 comments
Labels

Comments

@jackpordi
Copy link

jackpordi commented Feb 28, 2022

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

Build times for large projects can be quite slow, which makes both local development and CI deploy times slow.

For a small project of mine (around 6k lines), tsc takes 5.2 seconds to build. swc on the other hand takes 0.38 seconds - that's over a 10x improvement.

Related to #731 in the sense that this is a request/discussion for a faster build system support, but as ESBuild are not going to support emitDecoratorMetadata anytime soon but SWC already does, I think it makes more sense to push for that.

Describe the solution you'd like

Allow nest build to use swc via some flag e.g. nest build --swc.

Teachability, documentation, adoption, migration strategy

The discussion in #731 is making it evident that we are much closer to being able to have SWC build NestJS projects - the boilerplate NestJS app already runs fine from SWC builds, there are just some kinks/edge cases that need to be tracked i.e. CLI plugins that require AST transformations, certain decorators, etc.

I understand a lot of the work that needs to be done is not necessarily on Nest's side, but rather on SWC's side to fix it's issues. Still, I think it's useful to have an issue dedicated specifically for SWC rather than hijacking a closed one about esbuild.

What is the motivation / use case for changing the behavior?

Speeeeed.

@jmcdo29
Copy link
Member

jmcdo29 commented Feb 28, 2022

@kamilmysliwiec I would actually be interested in adding this support and maintaining it. As an anecdote I just swapped my ogma repo to use swc and uvu for tests and went from 40 seconds down 10 is running each package through nx otherwise 9 seconds down to 800ms. Adding in decorator and path support was a single file config. We could make a flag on new repos to use swc and generate the .swcrc config file as well. Monorepos may be a bit tricky, but I think we could work something out.

Let me know if you'd like me to start working on this, or if you think it should be outside the nest CLI.

@manuschillerdev
Copy link

manuschillerdev commented Mar 1, 2022

I'd be in as well!

I experimented with a repository already (dev server, build and running jest tests via swc) which could maybe a starting point for experimenting with swc:
https://github.com/manuschillerdev/nestjs-swc

I haven't tested support in monorepos, though.

there were some great answers in the previous thread (originally about esbuild) here:
#731 (comment)

TLDR;
As mentioned by @kamilmysliwiec there are several plugins (e.g. swagger, graphql) that rely on manipulating the AST emitted by tsc (which is not compatible with the AST from swc out of the box). They won't work with swc right now.

There would be several ways to solve this:

  • Rewrite the plugins as swc plugins (I think that's the way nextjs does it with their plugins for styled-components and relay)
  • Add a transfomer for swc <-> tsc AST compat?
  • Rewrite the plugins to use the swc AST?

I'd be really interested to help exploring more possibilities to use swc with nest :)

EDIT:
See https://github.com/vercel/next.js/tree/canary/packages/next-swc/crates/core/src for reference implementations of nextjs transforms

@kamilmysliwiec
Copy link
Member

@kamilmysliwiec I would actually be interested in adding this support and maintaining it. As an anecdote I just swapped my ogma repo to use swc and uvu for tests and went from 40 seconds down 10 is running each package through nx otherwise 9 seconds down to 800ms. Adding in decorator and path support was a single file config. We could make a flag on new repos to use swc and generate the .swcrc config file as well. Monorepos may be a bit tricky, but I think we could work something out.

Let me know if you'd like me to start working on this, or if you think it should be outside the nest CLI.

Sounds great @jmcdo29! AFAIR swc can be used in conjunction with webpack as well, correct? In this case, simply adding --swc flag and swc: boolean option to the nest-cli.json should be sufficient. We can obviously flag it as experimental and print a nicely formatted warning when they do so.

Rewrite the plugins as swc plugins (I think that's the way nextjs does it with their plugins for styled-components and relay)

@manuschillerdev CLI plugins extensively use TypeScript's type checker to guess types while parsing ts files and this doesn't seem to be feasible with SWC AST (type-checker is what actually slows down the tsc compiler).

@jackpordi
Copy link
Author

jackpordi commented Mar 1, 2022

If the type checker is a must-have requirement of the CLI plugins, then tsc AST compat seems like the only option, but with the type-checking being the largest bottleneck of tsc I'm wondering if it'll end up being worth it or not.

@manuschillerdev CLI plugins extensively use TypeScript's type checker to guess types while parsing ts files and this doesn't seem to be feasible with SWC AST (type-checker is what actually slows down the tsc compiler).

Is that because the SWC AST has already had types removed? Or do you mean you actually need to use type inference? E.g. I'm thinking something like the Swagger cli plugin just needs the types in the AST, not necessarily inferred?

Also worth mentioning that SWC is developing its own type-checker as well, though will probably not be ready anytime soon. That said, may be worth holding out for.

@kamilmysliwiec
Copy link
Member

If the type checker is a must-have requirement of the CLI plugins, then tsc AST compat seems like the only option, but with the type-checking being the largest bottleneck of tsc I'm wondering if it'll end up being worth it or not.

Perhaps we could run some benchmarks just to see if there's at least a minor performance improvement (?)

Also worth mentioning that SWC is developing its own type-checker as well, though will probably not be ready anytime soon. That said, may be worth holding out for.

One thing to keep in mind: even if they implement their own type-checker, I don't think we'll be capable of maintaining 2 versions of the plugin alongside this package and the whole ecosystem (the maintenance cost is simply too high in this case).

@manuschillerdev
Copy link

manuschillerdev commented Mar 1, 2022

Sounds great @jmcdo29! AFAIR swc can be used in conjunction with webpack as well, correct? In this case, simply adding --swc flag and swc: boolean option to the nest-cli.json should be sufficient. We can obviously flag it as experimental and print a nicely formatted warning when they do so.

@kamilmysliwiec the only way to use swc in combination with webpack is swc-loader.

Example webpack-config with HMR (I didn't find HMR to work really reliable with and without swc, but didn't spend too much time with it):

npm task: "start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch",

// webpack-hmr.config.js
const nodeExternals = require('webpack-node-externals');
const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = function (options, webpack) {
  options.module.rules.forEach((rule) => {
    rule.use?.forEach((use) => {
      if (use.loader === 'ts-loader') {
        use.loader = 'swc-loader';
        delete use.options;
      }
    });
  });

  options.plugins = options.plugins.filter(
    (plugin) => !(plugin instanceof ForkTsCheckerWebpackPlugin),
  );

  return {
    ...options,
    entry: ['webpack/hot/poll?100', options.entry],
    externals: [
      nodeExternals({
        allowlist: ['webpack/hot/poll?100'],
      }),
    ],
    plugins: [
      ...options.plugins,
      new webpack.HotModuleReplacementPlugin(),
      new webpack.WatchIgnorePlugin({
        paths: [/\.js$/, /\.d\.ts$/],
      }),
      new RunScriptWebpackPlugin({ name: options.output.filename }),
    ],
  };
};

.swcrc

{
  "sourceMaps": "inline",
  "module": {
    "type": "commonjs",
    "strict": true
  },
  "jsc": {
    "target": "es2020",
    "parser": {
      "syntax": "typescript",
      "decorators": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    },
    "keepClassNames": true
  }
}

@manuschillerdev CLI plugins extensively use TypeScript's type checker to guess types while parsing ts files and this doesn't seem to be feasible with SWC AST (type-checker is what actually slows down the tsc compiler).

Is that because the SWC AST has already had types removed? Or do you mean you actually need to use type inference? E.g. I'm thinking something like the Swagger cli plugin just needs the types in the AST, not necessarily inferred?

@jackel119 @kamilmysliwiec
Right now I know too little about SWC AST Details, I'd need to check that and come back to you..

@manuschillerdev
Copy link

@jackel119 @kamilmysliwiec
the generated AST can be seen in swc's online repl (please see the switch at the top right View: Result -> View: AST)

https://play.swc.rs/?version=1.2.146&code=H4sIAAAAAAAAAyupLEhVcMvP90vMTVWwVSguKcrMS7fm4korzUsuyczPU0jLz9fIA0pawVRpKlRzKQBBcn5ecX5Oql5OfrqGekZqTk6%2BgrqCtgJIraY1Vy0AWmkKHlkAAAA%3D&config=H4sIAAAAAAAAA0WNSwrDMAxE76J1FiHL3KGHMK4SHPxD40CN8d0rF5fsJM2bp0YXLO2NshGwjAk1FvOhnUrNDCsuF1qoQE%2BH8eCui5GTiyKMbd1WjX1K4AksFFx0Rx0ym0IWBp7IxNP%2Fya6ukN73OLTfP3VqJaR4gfojmmWH16SL3Ny%2F3SauU70AAAA%3D

Custom Types are also part of the AST.
Are those type information sufficient for the plugins?

@thebergamo
Copy link

Is there anyone working in such feature? I might be interested and I could also help with the implementation following maybe @manuschillerdev approach.

I believe the future (ironically) of JS/TS tooling would be in using other compiled languages that can give us better performance on the tooling side, so migrating to those give us great benefits over actual JS only tooling.

@jmcdo29
Copy link
Member

jmcdo29 commented Jul 18, 2022

I played around with the swc-loader the other day, and while it mostly worked, it does fail on swagger-cli-plugin and the graphql-cli-plugin. The AST differs and we'd need to end up supporting both Typescript's direct AST and SWC's AST which might be more of a burden then we are expecting to take on.

If you aren't using those plugins, you should be able to drop the swc-loader into a webpack.config.js file and be on your merry way, but those plugins do seem to cause issues. Of course, YMMV, but I'm not sure how feasible it will be, at least right now, to support swc. It's a shame, but it's a part of modern tooling y'know?

@thekip
Copy link

thekip commented Dec 28, 2022

Regarding cli plugins, the current implementation indeed uses a typescript type checker to determine types. And this is a problem even without SWC context.
On our project after some point of grow we faced slow execution of tests (compiling of project is still fine). One way to speedup tests in Jest is to use isolatedModules: true which disabling type checker, but if you use one of the plugins it will not work because type checker is required for them.

So it would be a good step for the nestjs to rewrite those plugins to not use Typechecker at all and make it more opinionated on the way how users should write code. Then it would be possible to rewrite them to SWC in rust.

Some examples:

You don't need a TypeChecker to transform this code:

import {WeekDay} from "./week-day.model.ts";

export class PoiWorkingHoursEntry {
    public readonly days: WeekDay[];
}

But need to transform this code, because of Type Alias and some magic with null:

import {WeekDay} from "./week-day.model.ts";

type MyTypeAlias = WeekDay | null;

export class PoiWorkingHoursEntry {
    public readonly days: MyTypeAlias[];
}

Actually, transformer could produce something like this:

// pseudocode, actual transformer produce diffferent code
type MyTypeAlias = WeekDay | null;

    @Field(() => {
      if (typeof MyTypeAlias === void 0) {
        throw new Error("`PoiWorkingHoursEntry.days` has unsupported type. You probably passed a typealias instead of real type.")
      }
      return MyTypeAlias;
    })
    public readonly days: MyTypeAlias[];

But instead, current transformer uses TypeChecker and trying to resolve all possible ways how user can write code in TS.

I would like to participate in this, but first it should be discussed with @kamilmysliwiec and formulated development / migration plan.

@qaynam
Copy link

qaynam commented Jan 24, 2023

Sounds great @jmcdo29! AFAIR swc can be used in conjunction with webpack as well, correct? In this case, simply adding --swc flag and swc: boolean option to the nest-cli.json should be sufficient. We can obviously flag it as experimental and print a nicely formatted warning when they do so.

@kamilmysliwiec the only way to use swc in combination with webpack is swc-loader.

Example webpack-config with HMR (I didn't find HMR to work really reliable with and without swc, but didn't spend too much time with it):

npm task: "start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch",

// webpack-hmr.config.js
const nodeExternals = require('webpack-node-externals');
const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = function (options, webpack) {
  options.module.rules.forEach((rule) => {
    rule.use?.forEach((use) => {
      if (use.loader === 'ts-loader') {
        use.loader = 'swc-loader';
        delete use.options;
      }
    });
  });

  options.plugins = options.plugins.filter(
    (plugin) => !(plugin instanceof ForkTsCheckerWebpackPlugin),
  );

  return {
    ...options,
    entry: ['webpack/hot/poll?100', options.entry],
    externals: [
      nodeExternals({
        allowlist: ['webpack/hot/poll?100'],
      }),
    ],
    plugins: [
      ...options.plugins,
      new webpack.HotModuleReplacementPlugin(),
      new webpack.WatchIgnorePlugin({
        paths: [/\.js$/, /\.d\.ts$/],
      }),
      new RunScriptWebpackPlugin({ name: options.output.filename }),
    ],
  };
};

.swcrc

{
  "sourceMaps": "inline",
  "module": {
    "type": "commonjs",
    "strict": true
  },
  "jsc": {
    "target": "es2020",
    "parser": {
      "syntax": "typescript",
      "decorators": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    },
    "keepClassNames": true
  }
}

@manuschillerdev CLI plugins extensively use TypeScript's type checker to guess types while parsing ts files and this doesn't seem to be feasible with SWC AST (type-checker is what actually slows down the tsc compiler).

Is that because the SWC AST has already had types removed? Or do you mean you actually need to use type inference? E.g. I'm thinking something like the Swagger cli plugin just needs the types in the AST, not necessarily inferred?

@jackel119 @kamilmysliwiec Right now I know too little about SWC AST Details, I'd need to check that and come back to you..

This part of the code doesn't work.

options.plugins = options.plugins.filter(
    (plugin) => !(plugin instanceof ForkTsCheckerWebpackPlugin),
  );

i change to 👇 , and it work perfect.

options.plugins = options.plugins.filter(
    (plugin) =>
      Object.getPrototypeOf(plugin).constructor.name !==
      'ForkTsCheckerWebpackPlugin'
  );

@Ryan-yang125
Copy link

Ryan-yang125 commented Feb 1, 2023

my .swcrc file is:

{
  "$schema": "https://json.schemastore.org/swcrc",
  "sourceMaps": true,
  "module": {
    "type": "commonjs",
    "strict": true
  },
  "jsc": {
    "target": "es2017",
    "parser": {
      "syntax": "typescript",
      "decorators": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    },
    "keepClassNames": true,
    "baseUrl": "./",
    "paths": {
      "@/*": [
        "src/*"
      ],
      "src/*": [
        "src/*"
      ]
    }
  },
  "minify": false
}

and the build script is:
swc --ignore \"**/*.spec.ts,src/hmr.ts\" --delete-dir-on-start --out-dir dist/ src/
it works well on my nest project.
If you want to watch file then rebuild and restart nest, you can use nodemon, here is my full swc npm script:

{
"scripts": {    
    "start:dev": "node dist/main",
    "build:swc": "swc --ignore \"**/*.spec.ts,src/hmr.ts\" --delete-dir-on-start --out-dir dist/ src/",
    "dev:swc": "npm run build:swc && npm run start:dev",
    "start:swc": "cross-env NODE_ENV=development npx nodemon --quiet --watch \"src/**\" --ext \"ts,json\" --ignore \"src/**/*.spec.ts\" --exec npm run dev:swc"}
}

@kamilmysliwiec
Copy link
Member

https://docs.nestjs.com/recipes/swc

@thekip
Copy link

thekip commented Feb 28, 2023

@kamilmysliwiec i can rewrite GraphQL plugin to SWC. Please DM me if you're interested in that.

Also, this phrase in the docs:

If you use TypeORM/MikroORM or any other ORM in your application, you may stumble upon circular import issues. SWC doesn't handle circular imports well, so you should use the following workaround:

Is incorrect. SWC plays well with circular dependencies. The input is wrong. SWC just strictly followed standard, instead of relying on quirks as typescript and babel do.

And this statement also appliable to GraphQL nestjs integration. It also may have circular issues related to emitDecoratorMetadata.

@kamilmysliwiec
Copy link
Member

Contributions are more than welcome

@lukasholzer
Copy link

lukasholzer commented Mar 11, 2023

I think it would be even more interesting to give rspack a try https://www.rspack.dev/ as it's a full webpack compatible bundler that uses swc under the hood. Same API but with the speed of swc.

Even a lot faster than webpack + swc:

CleanShot 2023-03-11 at 12 31 26

So you could provide the same feature set including HMR with the speed improvement and hopefully no big refactoring. Should be more less a drop in replacement.

@kamilmysliwiec
Copy link
Member

Rspack's default configuration should be sufficient for NestJS projects. Would you like to create a PR to the docs, adding the Rspack recipe?

@lukasholzer
Copy link

@kamilmysliwiec sure. I can do that! What's against swapping it with webpack by default?

@kamilmysliwiec
Copy link
Member

It is not yet stable

@rujorgensen
Copy link

rujorgensen commented Mar 26, 2023

@lukasholzer Any progress on the Rspack recipe--it would be absolutely awesome(!)--any unforeseen obstacles?

@ZimGil
Copy link

ZimGil commented Mar 26, 2023

Worth mentioning here that currently SWC has an issue with Path Aliases from outside the Root Dir
swc-project/swc#7074

@ianzone
Copy link
Contributor

ianzone commented Apr 22, 2023

https://docs.nestjs.com/recipes/swc

Would like to see the instruction for utilizing vite HMR and vite build.

@hardfist
Copy link

https://github.com/web-infra-dev/rspack/tree/main/examples/nestjs you can use rspack build nestjs app now.

@nino-vrijman
Copy link

nino-vrijman commented Jun 2, 2023

Kamil shared a sneak peek of SWC integrated within the Nest.js CLI on LinkedIn: https://www.linkedin.com/posts/kamil-mysliwiec-992bbb105_nodejs-nestjs-typescript-activity-7069957494017523713-u6kn?utm_source=share&utm_medium=member_desktop

One of his comments:

And it fully supports existing CLI plugins (Swagger & GraphQL auto-code generation) - which isn't the case for custom SWC setups 👨‍🍳

It will also be integrated with nest build and Rspack might be used in monorepo setups.

@kamilmysliwiec
Copy link
Member

#2107

@nestjs nestjs locked and limited conversation to collaborators Jun 14, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests