111

I'm using TS 1.7 and I'm trying to compile my project to one big file that I will be able to include in my html file.

My project structure looks like this:

-build // Build directory
-src // source root
--main.ts // my "Main" file that uses the imports my outer files
--subDirectories with more ts files.
-package.json
-tsconfig.json

my tsconfig file is:

 {
  "compilerOptions": {
    "module":"amd",
    "target": "ES5",
    "removeComments": true,
    "preserveConstEnums": true,
    "outDir": "./build",
    "outFile":"./build/build.js",
    "sourceRoot": "./src/",
    "rootDir": "./src/",
    "sourceMap": true
  }
}

When I build my project I expect the build.js file to be one big file compiled from my source. But ths build.js file is empty and I get all of my files compiled o js files.

Each of my TS files look a bit like this

import {blabla} from "../../bla/blas";

export default class bar implements someThing {
    private variable : string;
}

What am I doing wrong ?

David Limkys
  • 4,907
  • 5
  • 26
  • 38
  • My favorite way to accomplish this is with esbuild. It is written in go and therefore is much faster than TSC and 100x faster than webpack. See https://esbuild.github.io/. It supports the outFile and outDir options. – José Mancharo Aug 05 '21 at 18:17

5 Answers5

81

Option 1 - if you are not using modules

If your code contains only regular Typescript, without modules (import/export) you just need to add parameter outfile to your tsconfig.json.

// tsconfig.json
{
    "compilerOptions": {
        "lib": ["es5", "es6", "dom"],
        "rootDir": "./src/",
        "outFile": "./build/build.js"
    }
}

But this option have some limitations.

If you get this error:

Cannot compile modules using option 'outFile' unless the '--module' flag is 'amd' or 'system'

Try "Option 2" below.

Option 2 - using a module loader

If you are using modules (import/export), you will need a module loader to run your compiled script in the browser.

When you compile to a single file (using outFile), Typescript natively supports compiling to amd and system module loaders.

In tsconfig, you need to set module to amd or system:

// tsconfig.json
{
    "compilerOptions": {
        "module": "AMD",
        "lib": ["es5", "es6", "dom"],
        "rootDir": "./src/",
        "outFile": "./build/build.js"
    }
}

If you choose AMD, you need to use the require.js runtime. AMD requires an AMD loader, require.js is the most popular option (https://requirejs.org/).

If you choose System, you need to use the SystemJS module loader (https://github.com/systemjs/systemjs).

Option 3 - use a module bundler / build tool

Probably, the best option is to use a module bundler / build tool, like Webpack.

Webpack will compile all your TypeScript files to a single JavaScript bundle.

So, you will use webpack to compile, instead of tsc.

First install webpack, webpack-cli and ts-loader:

npm install --save-dev webpack webpack-cli typescript ts-loader

If you are using webpack with Typescript, it's best to use module with commonjs:

// tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "lib": ["es5", "es6", "dom"],
        "rootDir": "src"
    }
}

Webpack webpack.config.js example:

//webpack.config.js
const path = require('path');

module.exports = {
  mode: "development",
  devtool: "inline-source-map",
  entry: {
    main: "./src/YourEntryFile.ts",
  },
  output: {
    path: path.resolve(__dirname, './build'),
    filename: "[name]-bundle.js" // <--- Will be compiled to this single file
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js"],
  },
  module: {
    rules: [
      { 
        test: /\.tsx?$/,
        loader: "ts-loader"
      }
    ]
  }
};

Now, to compile, instead of executing using tsc command, use webpack command.

package.json example:

{
  "name": "yourProject",
  "version": "0.1.0",
  "private": true,
  "description": "",
  "scripts": {
    "build": "webpack" // <---- compile ts to a single file
  },
  "devDependencies": {
    "ts-loader": "^8.0.2",
    "typescript": "^3.9.7",
    "webpack": "^4.44.1",
    "webpack-cli": "^3.3.12"
  }
}

Webpack's TypeScript Documentation

Lastly, compile everything (under watch mode preferably) with:

npx webpack -w
Daniel Barral
  • 3,896
  • 2
  • 35
  • 47
56

This will be implemented in TypeScript 1.8. With that version the outFile option works when module is amd or system.

At this time the feature is available in the development version of Typescript. To install that run:

$ npm install -g typescript@next

For previous versions even if it's not obvious the module and the outFile options can not work together.

You can check this issue for more details.


If you want to output a single file with versions lower than 1.8 you can not use the module option in tsconfig.json. Instead you have to make namespaces using the module keyword.

Your tsconfig.json file should look like this:

{
  "compilerOptions": {
    "target": "ES5",
    "removeComments": true,
    "preserveConstEnums": true,
    "outFile": "./build/build.js",
    "sourceRoot": "./src/",
    "rootDir": "./src/",
    "sourceMap": true
  }
}

Also your TS files should look like this:

module SomeModule {
  export class RaceTrack {
    constructor(private host: Element) {
      host.appendChild(document.createElement("canvas"));
    }
  }
}

And instead of using the import statement you'll have to refer to the imports by namespace.

window.addEventListener("load", (ev: Event) => {
  var racetrack = new SomeModule.RaceTrack(document.getElementById("content"));
});
ruffin
  • 16,507
  • 9
  • 88
  • 138
toskv
  • 30,680
  • 7
  • 72
  • 74
  • 1
    The issue that you pointed out seemed to be resolved, also https://github.com/Microsoft/TypeScript/wiki/Compiler-Options suggests that the --outFile flag as to be used with the --module flag. – David Limkys Dec 26 '15 at 20:44
  • indeed, but it does not seem to be working. :( Maybe make an issue out of it on github? – toskv Dec 26 '15 at 21:10
  • btw, the resolutions of the issues on github do not seem to point to any actual changes to the code base, they always close with this being the default behaviour. – toskv Dec 26 '15 at 21:14
  • I just noticed this. It looks like the fix for the issue I mentioned will be available in version 1.8. Maybe use typescript@next to get the latest now? https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes – toskv Dec 26 '15 at 21:22
  • And I can confirm this. if using typescript@next this works just fine. :) I'll edit my answer to include this too. – toskv Dec 26 '15 at 21:26
  • @toskv My *module* is set to *commonjs* and my output file is still empty, is this to be expected too? – JMK Jan 04 '16 at 22:51
  • if you are using typescript 1.7.5 (latest released) yes – toskv Jan 04 '16 at 22:51
  • it's just something that was never implemented. if you want to use it though you can get the development version of tsc. just run: npm install -g typescript@next – toskv Jan 04 '16 at 22:53
  • Understand now, sorry I'm a bit slow sometimes! – JMK Jan 04 '16 at 22:53
  • 3
    I edited the answer to include the @next part too, just so it's clearer. :) – toskv Jan 04 '16 at 22:55
  • @toskv and what if I want to compile each directory to its own file? For example I have `script/x/a.ts`, `script/x/b.ts`, `script/y/c.ts`, `script/y/d.ts`, `script/y/e.ts` and I want to get `script/x.js` and `script/y.js`. Now I'm adding `tsconfig.json` in each folder which combines all script files into one, but I'm looking for more elegant solution, because I want to have only one single `tsconfig.json` for such case. Is it possible at all? – Alex Zhukovskiy Dec 07 '16 at 13:32
  • I could see that doable with something like gulp-typescript. with tsc alone you'll need a tsconfig in each folder that you want compiled separately (as far as I know). – toskv Dec 07 '16 at 13:33
  • 62
    Is there a better way to do this with TypeScript 3.x? – vitaly-t Sep 10 '18 at 14:15
  • @vitaly-t not natively, but there's a neat way with [ncc](https://www.npmjs.com/package/@zeit/ncc). Check my [answer](https://stackoverflow.com/a/60175555/3873510). – Paul Razvan Berg Jun 10 '20 at 22:07
24

The Easiest Way

You can do this with a custom tool called ncc, built and maintained by Vercel. Here's how they're using it in create-next-app:

ncc build ./index.ts -w -o dist/

Install it with yarn:

yarn add @vercel/ncc

Or npm:

npm install @vercel/ncc
Paul Razvan Berg
  • 16,949
  • 9
  • 76
  • 114
  • Does this create a code that you can run directly in the browser ? My browser says that "__dirname" is not defined – rambi Jun 05 '21 at 21:08
  • I don't know, but based on what you shared, the answer seems to be no. `__dirname` is a node.js-specific environment variable. – Paul Razvan Berg Jun 06 '21 at 15:11
  • ncc is for Node.js project, eg if you want to build your database seeding scripts from TypeScript to JavaScript. Often those scripts are written "by hand" in JavaScript, with ncc you can instead reuse the code you have written in TypeScript for your app. – Eric Burel Sep 06 '21 at 13:53
  • 1
    @rambi You can run it in the browser with a small hack. In your HTML file, add `var __dirname = "", module = {}` somewhere before the output from `ncc`. It's probably easiest to do it in a separate script tag. – Matthew Ratzloff Sep 25 '21 at 23:26
1

I similarly needed to compile multiple typescript files with import and exports to a single JS file, and found a solution.

  1. Use this tsconfig to generate a single JS file with the System module. This tsconfig should be placed wherever you have your typescript files. Run tsc to generate the single js file
{
  "compilerOptions" : {
      "module" : "System",
      "declaration" : false,
      "baseUrl" : "./",
      "outFile" : "../libs/bundle.js",
      "alwaysStrict" : true,
      "removeComments" : true,
  },
  "include": [
      "./**/*.ts"
  ],
  "exclude": [
      "./out.d.ts"
  ]
}

  1. In your index.html you will need to include the system.js bundle JS file (found at https://github.com/systemjs/systemjs/blob/main/dist/system.js). Additionally, you will need to append the systemjs extra naming support found at https://github.com/systemjs/systemjs/blob/main/dist/extras/named-register.js to system.js

  2. Include your compile bundle.js file in your index.html

  3. Include a script that imports all the named imports found in your registry.

(steps 2-4 shown below)

<html>
   <head>
    <script src = "libs/system.js"></script>
    <script src = "libs/bundle.js"></script>
    <script>
      Object.keys(System.registerRegistry).forEach((key) => System.import(key))
    </script>
  </head>
</html>

0

I found this question when I needed to do something similar to bundle my Lambdas. The thing is, I only wanted to bundle my modules into single files with their imports included from my own code, not the node_modules.

I ended up using esbuild to solve my problem:

esbuild ./src/lambda/** --outdir=./dist/lambda --bundle --platform=node --target=node18 --packages=external
Eddy Vinck
  • 410
  • 1
  • 4
  • 16