CodeFixesHub
    programming tutorial

    Allowing JavaScript Files in a TypeScript Project (allowJs, checkJs) — Comprehensive Guide

    Enable JavaScript in TypeScript safely with allowJs and checkJs. Learn config, migration, linting, and best practices. Start converting confidently today!

    article details

    Quick Overview

    TypeScript
    Category
    Aug 21
    Published
    23
    Min Read
    3K
    Words
    article summary

    Enable JavaScript in TypeScript safely with allowJs and checkJs. Learn config, migration, linting, and best practices. Start converting confidently today!

    Allowing JavaScript Files in a TypeScript Project (allowJs, checkJs) — Comprehensive Guide

    Introduction

    Many codebases start in JavaScript and later adopt TypeScript incrementally. TypeScript's flexibility enables this gradual migration by allowing JavaScript files to coexist and even be type-checked inside a TypeScript project. Two compiler options, allowJs and checkJs, control this behavior: allowJs permits .js and .jsx files to be included in the compilation pipeline, while checkJs enables type-checking of those files via JSDoc and inference. Properly using these flags helps teams adopt TypeScript at their own pace without sacrificing type quality or build performance.

    In this article you'll learn when and why to enable allowJs and checkJs, how to configure tsconfig and build tooling, strategies for incremental migration, how to apply JSDoc to improve type safety, and practical tips for working with mixed JS/TS projects. We'll also cover common pitfalls, troubleshooting steps, and advanced techniques like leveraging type declaration files and bundler integration. By the end you will be able to safely include JavaScript in TypeScript builds, progressively convert files, and keep a maintainable workflow.

    This guide is aimed at intermediate developers who are comfortable with TypeScript basics and project configuration. Expect concrete code examples, step-by-step instructions, recommended patterns, and references to related TypeScript features such as module strategies, decorators, and type utilities where relevant.

    Background & Context

    TypeScript is designed for gradual adoption. Projects often contain legacy JavaScript, third-party scripts, or files generated by tools that aren't in TypeScript. The TypeScript compiler (tsc) has options to accommodate these realities. allowJs is a boolean compiler option that permits the compiler to include .js and .jsx files in its program (so they can be transpiled and emit JavaScript output). checkJs is another boolean that turns on type-checking for .js files. When checkJs is true, TypeScript attempts to analyze JavaScript using type inference and JSDoc annotations, helping to catch many classes of bugs before runtime.

    Understanding module boundaries, declaration files, and how TypeScript infers types from JavaScript is crucial for a smooth migration. Decisions here impact build performance, editor feedback, and the safety of your migrating codebase. If you rely on older modular patterns or mixing namespace/module approaches, consult deeper discussions about modules to align your migration strategy with the best module architecture for TypeScript projects.

    Key Takeaways

    • allowJs lets the TypeScript compiler include JavaScript files in the project and optionally emits them.
    • checkJs enables type-checking for .js files; pair it with JSDoc for stronger typings.
    • Use exclude and include in tsconfig.json to control which JS files are compiled or checked.
    • Convert files incrementally using .ts/.tsx and // @ts-nocheck where necessary.
    • Create .d.ts declaration files or use allowSyntheticDefaultImports to improve typing across boundaries.
    • Integrate checkJs with linters and CI for consistent quality checks.
    • Plan migration with module and mixin strategies; learn advanced typing utilities to model patterns found in JS.

    Prerequisites & Setup

    Before changing compiler settings, ensure you have:

    • Node.js and npm/yarn installed.
    • TypeScript installed locally: npm install --save-dev typescript.
    • An existing JS or mixed JS/TS project with a tsconfig.json or ready to add one: npx tsc --init.
    • An editor with TypeScript language support (VS Code recommended).

    Start with a baseline tsconfig.json and ensure your build tooling (webpack, rollup, esbuild) is compatible with TypeScript settings. If you use bundlers, configure their TypeScript loaders or plugins to respect tsconfig flags. When working with complex legacy code, add JSDoc gradually to improve inference without converting to .ts immediately.

    Main Tutorial Sections

    1) What allowJs and checkJs Do (Quick Reference)

    allowJs controls whether .js and .jsx files are included in the TypeScript compilation process. With allowJs: true, the compiler will parse and emit JavaScript files as part of the build. checkJs controls error reporting for .js files; when checkJs: true, TypeScript performs type checking like it does for .ts files (though inference is more limited). Example tsconfig.json snippet:

    json
    {
      "compilerOptions": {
        "allowJs": true,
        "checkJs": true,
        "outDir": "dist",
        "strict": true
      },
      "include": ["src/**/*"]
    }

    If you only want to compile JS but not type-check it, use allowJs: true and checkJs: false. Conversely, checkJs: true without allowJs has no effect for including files — allowJs must include .js files in the program for them to be checked.

    2) Configuring tsconfig.json for Mixed Projects

    A realistic tsconfig.json for mixed projects usually contains allowJs, checkJs, and precise include/exclude globs to avoid pulling in unwanted files (like node_modules or build artifacts). Example with incremental migration:

    json
    {
      "compilerOptions": {
        "target": "ES2020",
        "module": "commonjs",
        "allowJs": true,
        "checkJs": true,
        "declaration": true,
        "outDir": "dist",
        "strict": true
      },
      "include": ["src/**/*", "types/**/*"],
      "exclude": ["node_modules", "dist", "**/*.test.js"]
    }

    If you emit .d.ts files for TypeScript consumers, set declaration: true. Use exclude aggressively to prevent temporary or generated JS files from being checked, which can reduce noise and speed up the project.

    3) Controlling Emission: When to Emit JS and Declarations

    allowJs enables emission of original JavaScript files by the compiler unless you turn off emitDeclarationOnly or set declaration: false. If your pipeline already handles JS output (e.g., Babel or bundler), you might prefer TypeScript to only type-check and not emit transformed JS. Example tweaks:

    • To prevent emitting compiled JS from .ts files, set noEmit: true for type-check-only CI steps.
    • To prevent emitting JS for .js files specifically, use build tooling configuration (some tools allow separate handling) or avoid including JS in the emission phase by splitting tsconfig into multiple tsconfig.app.json and tsconfig.check.json files.

    A common pattern is to have a tsconfig.check.json with noEmit: true for CI/static checks and a tsconfig.build.json for production builds.

    4) Incremental Migration Strategies

    Migrating file-by-file is practical for large codebases. Start by enabling allowJs and checkJs but keep strict off initially to reduce friction. Convert the most critical or easiest modules to .ts first. Helpful tactics:

    • Use // @ts-nocheck at the top of particularly noisy JS files you plan to convert later.
    • Rename safe files from .js to .ts or .tsx to opt them into full TypeScript checking.
    • Add JSDoc to improve types in JS without renaming: /** @param {string} name */ function greet(name) { ... }.
    • Maintain a migration checklist per module: convert exports, add types, and adjust imports.

    If your project uses classic modular patterns, consult material on module choices to ensure the output matches your expectations; misaligned module systems can cause runtime issues and extra migration work. See Namespaces vs Modules (Deeper Dive): Choosing the Right Approach for guidance.

    5) Using JSDoc to Improve JavaScript Typing

    JSDoc annotations allow explicit typing inside .js files without renaming to .ts. TypeScript understands common JSDoc tags like @param, @returns, @type, and @typedef. Example:

    js
    /**
     * @typedef {{id: number, name: string}} User
     */
    
    /**
     * @param {User} user
     * @returns {string}
     */
    function getName(user) {
      return user.name;
    }

    checkJs will use these annotations to produce editor hints and compile-time checks. This approach is great for teams that want type safety but can't rename files immediately. You can also progressively add // @ts-check to enable checking for specific files.

    6) Interoperability: Declarations and Third-party JS

    When integrating third-party plain JS libraries, you might need .d.ts declaration files to represent types. You can:

    • Install community types from DefinitelyTyped (npm install --save-dev @types/some-lib).
    • Write minimal types/some-lib.d.ts for custom libs.
    • Use declare module 'legacy-lib'; for quick allow-lists.

    Type declaration files help TypeScript treat legacy JS modules as typed consumers. When wrapping JS that uses complex patterns (factory functions, dynamic properties), consider using TypeScript utilities like InstanceType and ConstructorParameters to model runtime constructs. For deeper techniques on extracting instance types and constructor parameter types, refer to our guide on Utility Type: InstanceType for Class Instance Types and Utility Type: ConstructorParameters for Class Constructor Parameter Types.

    7) Editor Experience and Tooling (VS Code)

    Editor feedback is essential when mixing JS and TS. VS Code uses the TypeScript language server and respects checkJs to show diagnostics for .js files. To optimize the editor experience:

    • Ensure your workspace has the correct typescript version set (use workspace TypeScript via the VS Code command palette).
    • Install ESLint with TypeScript parser/plugins and configure eslint --ext .js,.jsx,.ts,.tsx.
    • Use jsconfig.json for JS-only projects, but prefer tsconfig.json for mixed projects to centralize settings.

    A consistent editor setup prevents mismatch between CLI tsc errors and in-editor diagnostics. To enforce consistent checks in CI, run npx tsc -p tsconfig.check.json --noEmit.

    8) Testing and CI Integration

    Integrate type checking into CI to keep mixed code healthy. Recommended steps:

    1. Create a dedicated tsconfig.check.json with noEmit: true and checkJs enabled.
    2. In CI, run npx tsc -p tsconfig.check.json to fail builds on new type errors.
    3. Combine with ESLint (eslint --ext .js,.ts src) to enforce style and catch runtime issues.

    This separation allows local builds to emit artifacts while CI strictly enforces type quality. If tests rely on compiled output, configure test runners (Jest, Mocha) to use ts-node or precompiled builds.

    9) Working with Advanced Patterns: Mixins & Decorators

    Mixed JS/TS code often contains advanced OOP patterns like mixins and decorators. When converting or enabling checking, preserve runtime behaviors and express types safely. For mixins, you may use TypeScript mixin patterns and preserve dynamic composition. See our tutorial on Implementing Mixins in TypeScript: A Practical, In-Depth Tutorial for patterns to preserve types while keeping runtime semantics.

    Decorators are still an advanced feature; migrating files that use decorators requires attention to emit metadata and TS compiler configuration. If your codebase uses class or method decorators, you will find related examples and patterns in our series: Decorators in TypeScript: Usage and Common Patterns, Property Decorators Explained, Method Decorators Explained, and Parameter Decorators Explained. When enabling checkJs, TypeScript will attempt to infer decorator-related types in JavaScript but converting to .ts yields stronger typing guarantees and better integration with metadata emit.

    10) Performance Considerations and Scaling

    Type-checking many .js files can slow down incremental builds or editor responsiveness. Strategies to optimize performance:

    • Limit include globs to the directories you actively work on.
    • Use multiple tsconfig files: one for checks (noEmit: true) and one for production builds with a narrower scope.
    • Disable checkJs globally and enable it selectively by adding // @ts-check only to files you want to verify.
    • Use incremental compilation (tsc --incremental) and tsbuildinfo to speed up repeated builds.

    Consider performing heavy linting and type checks in CI rather than every local save to balance developer productivity and code quality.

    Advanced Techniques

    After you have a stable mixed codebase, apply advanced techniques to tighten typings and reduce runtime issues. Use JSDoc + @template to express generics in JS. Emit declaration files (declaration: true) and convert critical modules to .ts to provide strongly-typed public APIs. For tricky this contexts, leverage utility types like ThisParameterType<T> and OmitThisParameter<T> to model function this safely; see our deep dives: Deep Dive: ThisParameterType in TypeScript and Utility Type: OmitThisParameter — Remove 'this' from Function Types.

    For complicated type extraction in transition code, use infer with functions or objects in conditional types to derive shapes from runtime factories — our guides on using infer cover patterns you can apply while you craft declaration files for legacy modules: Using infer with Functions in Conditional Types and Using infer with Objects in Conditional Types — Practical Guide.

    If you need to model deep transformations or remap keys during migration, consider recursive mapped types and conditional types (see Recursive Mapped Types for Deep Transformations in TypeScript and Advanced Mapped Types: Key Remapping with Conditional Types). These are especially useful when generating typed facades for legacy APIs.

    Best Practices & Common Pitfalls

    Dos:

    • Do enable allowJs early for compatibility, but gate checkJs behind a migration plan.
    • Do use JSDoc and @ts-check to incrementally add type safety without renaming files.
    • Do maintain a separate tsconfig for CI checks and production builds.
    • Do create declaration files for public legacy modules and install community type packages when available.

    Don'ts:

    • Don't enable checkJs across an entire large codebase without a plan — it will generate many errors and slow editors.
    • Don't assume inference will catch all edge cases; add JSDoc or convert to .ts where runtime invariants matter.
    • Don't ignore module system mismatches — double-check module/target settings.

    Common pitfalls:

    • Duplicate types or incompatible module resolutions caused by mixing default and named imports. Use allowSyntheticDefaultImports or write interoperable imports.
    • Emission of both original JS and compiled JS leading to duplicate files in build outputs; solve with carefully chosen outDir and separate check/build configs.
    • Tooling mismatches between TypeScript, Babel, and bundlers. Keep plugin settings aligned and test builds thoroughly.

    Real-World Applications

    Mixed JS/TS setups are common in many scenarios:

    • Legacy Node.js backends that migrate route handlers file by file.
    • Frontend apps where UI components are progressively converted to TypeScript.
    • Monorepos with packages that have different migration timelines.

    For example, in a React codebase you might convert utility modules first to strengthen the overall type surface while leaving components in JS until their prop types stabilize. When dealing with decorators or advanced class features in migration, consult decorator-specific guides to ensure runtime metadata and types remain consistent, such as Decorators in TypeScript: Usage and Common Patterns.

    Conclusion & Next Steps

    Enabling JavaScript files inside a TypeScript project using allowJs and checkJs is a pragmatic approach to incremental migration. Start conservatively: enable allowJs, selectively use checkJs and @ts-check, then gradually convert files to .ts. Use JSDoc to gain stronger editor support in the interim, generate declarations for public APIs, and integrate checks into CI. As you convert, leverage TypeScript utilities and advanced typing guides to capture complex patterns.

    Next steps: create a migration plan with prioritized modules, add a tsconfig.check.json for CI, and begin converting low-risk files first. Use the linked resources to deepen knowledge about decorators, mixins, advanced utility types, and module design.

    Enhanced FAQ

    Q1: When should I enable allowJs and checkJs together?
    A1: Enable allowJs when you want TypeScript to include .js/.jsx files in the program — for example, to allow imports from JS files or to use a single build step. Enable checkJs when you want TypeScript to perform type-checking on those JS files. A reasonable approach is to start with allowJs: true and checkJs: false to get compilation working, then enable checkJs selectively (or in CI) as you add JSDoc or convert files. This reduces noise and lets teams iterate.

    Q2: Will checkJs give me the same level of safety as converting to .ts?
    A2: Not always. checkJs provides excellent inference, and combined with JSDoc it can be very powerful. However, .ts files get stricter type checking, better IDE support for advanced TypeScript types (generics, mapped types), and access to TypeScript-specific language features. Use checkJs as an intermediate step; for public APIs and complex modules, convert to .ts for maximum confidence.

    Q3: How do I handle workspace performance when checkJs is slow?
    A3: Several tactics help: restrict include and exclude globs, use selective // @ts-check annotations, split tsconfig into check/build variants, enable incremental compilation (--incremental), and run heavy checks in CI rather than on every local save. Also exclude test artifacts and generated code from being checked.

    Q4: How do I create minimal .d.ts files for legacy JS modules?
    A4: Start with the API surface that your code consumes. Write a small types/legacy-lib.d.ts file describing the exported functions, classes, or objects. Use declare module 'legacy-lib' { export function foo(x: string): number; }. Test by importing in TS files and refining the declaration. For repetitive patterns, consider generic utilities like ReturnType<T> and Parameters<T> to model shapes; see Utility Type: ReturnType for Function Return Types and Deep Dive: Parameters — Extracting Function Parameter Types in TypeScript.

    Q5: Can I use checkJs with React and JSX files?
    A5: Yes. Enable allowJs: true and checkJs: true and ensure your jsx compiler option is set correctly (e.g., jsx: react-jsx for modern React). Add JSDoc or propTypes annotations if you need stronger typing in legacy components, or convert components to .tsx progressively to leverage full TypeScript JSX typing.

    Q6: What's the best way to handle dynamic patterns like this or factories in JS?
    A6: Use JSDoc's @this annotation or convert modules to .ts when this shapes are important. On the TypeScript side, utility types like ThisParameterType<T> and OmitThisParameter<T> help model functions that change this context. For factories and runtime type extraction, consider InstanceType<T> and ConstructorParameters<T> for creating accurate declarations; see Utility Type: InstanceType for Class Instance Types and Utility Type: ConstructorParameters for Class Constructor Parameter Types.

    Q7: How do I keep Babel and TypeScript from conflicting when both are present?
    A7: Decide which tool will handle transpilation. If Babel is handling transform and you use TypeScript for type-checking only, set noEmit: true in your checker tsconfig and let Babel handle ts/jsx transpilation via @babel/preset-typescript. If TypeScript emits the final code, configure Babel accordingly and avoid double-transpiling. Keep module/target settings aligned across tools to avoid runtime mismatches.

    Q8: Are there patterns to avoid when migrating?
    A8: Avoid enabling checkJs on a large unannotated codebase without a plan — it generates noise and blocks progress. Avoid leaving @ts-ignore comments long-term; prefer @ts-expect-error for deliberate, temporary exceptions. Also, be cautious with any as a migration crutch — prefer narrow JSDoc or precise .d.ts files when possible.

    Q9: How do decorators impact migration from JS to TS?
    A9: Decorators are runtime constructs that may require experimentalDecorators and emitDecoratorMetadata compiler options when converting to TypeScript. If your JS uses decorators, ensure equivalent transpilation and metadata support is configured. For examples and patterns on decorators, including property, method, and parameter decorators, see Property Decorators Explained: A Practical Guide for TypeScript Developers, Method Decorators Explained: Practical Guide for Intermediate Developers, and Parameter Decorators Explained: An In-Depth Guide for Intermediate Developers.

    Q10: Where should I read next to level up my skills?
    A10: After you stabilize mixed JS/TS configuration, dive into advanced typing techniques: conditional types, infer, and recursive mapped types to model complex transformations. Useful references include Using infer with Functions in Conditional Types, Recursive Conditional Types for Complex Type Manipulations, and Recursive Mapped Types for Deep Transformations in TypeScript.

    article completed

    Great Work!

    You've successfully completed this TypeScript tutorial. Ready to explore more concepts and enhance your development skills?

    share this article

    Found This Helpful?

    Share this TypeScript tutorial with your network and help other developers learn!

    continue learning

    Related Articles

    Discover more programming tutorials and solutions related to this topic.

    No related articles found.

    Try browsing our categories for more content.

    Content Sync Status
    Offline
    Changes: 0
    Last sync: 11:20:05 PM
    Next sync: 60s
    Loading CodeFixesHub...