GoogleFeud / ts-runtime-checks

A typescript transformer that automatically generates validation code from your types.
https://googlefeud.github.io/ts-runtime-checks/
MIT License
315 stars 7 forks source link

[BUG] Webpack bundles typescript package when ts-runtime-checks is used #39

Open jirutka opened 1 year ago

jirutka commented 1 year ago

Describe the bug I’m using Webpack to bundle a TypeScript project into a single JS file that can be executed with Node.js. When I import and use the check function from ts-runtime-checks, I end up with a huge bundle that includes even typescript.js.

Expected behavior Development dependencies should not be bundled.

Additional context

import { check } from 'ts-runtime-checks'

const [config, errors] = check<SomeType>(obj)

Relevant devDependencies:

tsconfig.json:

{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment",
    "moduleResolution": "node",
    "target": "ES2022",
    "module": "ES2022",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "paths": {
      "react": ["./node_modules/preact/compat"],
      "react-dom": ["./node_modules/preact/compat"]
    },
    "strictNullChecks": true,
    "plugins": [
      {
        "transform": "ts-runtime-checks"
      }
    ]
  },
  "include": ["src/**/*.ts", "src/**/*.tsx"]
}

webpack.config.mjs:

// @ts-check
import * as Path from 'node:path'
import webpack from 'webpack'

/** @type {import('webpack').Configuration} */
const cli = {
  name: 'cli',
  entry: './src/main.ts',
  target: 'node',
  plugins: [
    // Don't split the output bundle into multiple files.
    new webpack.optimize.LimitChunkCountPlugin({
      maxChunks: 1,
    }),
  ],
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
    alias: {
      react: 'preact/compat',
      'react-dom/test-utils': 'preact/test-utils',
      'react-dom': 'preact/compat',
    },
  },
  output: {
    filename: 'cli.js',
    path: Path.resolve('./dist'),
  },
  optimization: {
    minimize: false,
  },
}

export default [cli]
GoogleFeud commented 1 year ago

You could configure webpack to not bundle the library with the externals option, I haven't tried it for myself but it should work. The value can be anything since the transformer won't leave any of the function calls in the code:

  externals: {
    "ts-runtime-checks": '...',
  },

alternatively, you can declare the function yourself:

export declare function myCheckFn<T, _rawErrorData extends boolean = false, _M = { __$marker: "check" }>(prop: unknown) : [T, Array<_rawErrorData extends true ? ValidationError : string>];

so you end up importing only types from the library and therefore it doesn't get bundled.

jirutka commented 1 year ago
  externals: {
    "ts-runtime-checks": '...',
  },

I’ve already tried this, but it didn’t help. EDIT: It does work, I must have made a mistake somewhere before. But it cannot be anything, but some valid value (e.g. true) because it finds its way into the bundle (but would be removed by minimizer):

;// CONCATENATED MODULE: external "true"
const external_true_namespaceObject = true;
;// CONCATENATED MODULE: ./src/config.ts

alternatively, you can declare the function yourself:

This works!

I’m considering migrating to Rollup, which has a better tree-shaking algorithm. Tbh, I use Webpack in this project just because I inherited it.

Anyway, thanks for the quick answer and this awesome project!

jirutka commented 1 year ago

I’m considering migrating to Rollup, which has a better tree-shaking algorithm.

Hm, I have the same problem with Rollup, so it’s not Webpack-specific.

jirutka commented 1 year ago

I’m looking into your code, and now understand why it’s happening. It’s needed to clearly separate the build-time code (transformer) and runtime code (actually just type declarations).

jirutka commented 1 year ago

A better workaround (but still just a mere workaround):

ts-runtime-checks.ts:

// Workaround for https://github.com/GoogleFeud/ts-runtime-checks/issues/39
import type {
  check as checkFn,
  createMatch as createMatchFn,
  is as isFn,
} from 'ts-runtime-checks'

export type * from 'ts-runtime-checks'

export declare const check: typeof checkFn
export declare const createMatch: typeof createMatchFn
export declare const is: typeof isFn
danehansen commented 2 months ago

whenever i attempt to use any of the lower cased methods from ts-runtime-checks (check, is, as, etc...), my build breaks. i get errors of WARNING in ../reponame/node_modules/typescript/lib/typescript.js 5288:14-36 Critical dependency: the request of a dependency is an expression and ERROR in ../reponame/node_modules/ts-runtime-checks/dist/transformer.js 224:25-40 Module not found: Error: Can't resolve 'path' in '/Users/username/Documents/reponame/node_modules/ts-runtime-checks/dist' any ideas what im doing wrong? thanks.