Typescript: Typescript compiler --copy-files option to copy non-typescript files to dist folder

Created on 9 Apr 2019  ·  34Comments  ·  Source: microsoft/TypeScript

Search Terms

typescript tsc copy files

Suggestion

The typescript compiler should have an option to copy non-compiled files, similarly to Babel: https://babeljs.io/docs/en/babel-cli#copy-files

Use Cases

Almost all use cases where typescript files are compiled into a different folder will require bringing along other non-typescript files.

For the frontend, usually there already is a build system in place so this is trivial.

In the backend however, there is rarely a need for a build system, and thus developers often go out of their way to set up a system where non-typescript files are watched for changes and copied.

Existing npm packages for this kind of functionality are all poorly maintained and unsafe. Currently my only option is either copying all files on any change or writing a custom script.

Examples

tsc -w --copy-files

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • [x] This feature would agree with the rest of TypeScript's Design Goals.
Out of Scope Suggestion

Most helpful comment

@RyanCavanaugh babel-cli provides this ability: https://babeljs.io/docs/en/babel-cli#copy-files

So why not TypeScript... 😄

All 34 comments

Duplicate of #15222?

If no one else has been able to write a good file copy script, I don't think we'll be the first.

Anyway explicit non-goal 4 is:

Provide an end-to-end build pipeline

This is a great idea. Especially for those who want have minimum build steps (e.g. just tsc) for a prototype or something

This would be really handy!

For those who need this feature, you can use copyfiles to implement it.

https://quangphamsoft.wordpress.com/2017/05/23/typescript-compiler-copy-html/

This is a great idea. Especially for those who want have minimum build steps (e.g. just tsc) for a prototype or something
add script { scripts: { copy-files: "copyfiles -u 1 src/**/*.html src/**/*.js lib"}} using copyfiles

and add it to postbuild script

For example my scripts looks like this

  build: "yarn run clean && tsc",
 "postbuild": "ef-tspm && yarn run copy-files",
 "clean": "rimraf lib/",
 "prepare": "yarn run build",
 "copy-files": "copyfiles -u 1 src/**/*.js lib"

honestly, I have just spent hours and hours on a very weird problem just to realize that the reason was that copyfiles does not work the same way under linux and windows.
Therefore, I would not recommend copyfiles especially if your build script is run as part of a docker build (under linux).
=== update =====
Well, after further testing, it seems that adding double quotes should do the trick (hopefully):
"copy-files": "copyfiles -u 1 \"src/**/*.js\" lib"

My version:

{
  "scripts": {
    "prebuild": "del lib/",
    "build": "tsc",
    "postbuild": "cpy '**/*' '!**/*.ts' ../lib/ --cwd=src/ --no-overwrite --parents"
  },
  "devDependencies": {
    "cpy-cli": "^2.0.0",
    "del-cli": "^3.0.0"
  }
}

@RyanCavanaugh babel-cli provides this ability: https://babeljs.io/docs/en/babel-cli#copy-files

So why not TypeScript... 😄

tsc -w --copy-files would end up copying all kinds of files (.md, etc.) you don't need/want in your /dist. I think it would be better for tsconfig to allow specifying which files you want to copy.

@RyanCavanaugh: Please please reopen this issue: The discussion here is absolutely misleading, putting this feature in the context of "build-piplines"... However what it is really about is "be polite, keep something in the state you encountered on arrival and dont steal stuff".

We do not want build pipelines we want to TS to keep our code directory as it was and do not mess this code/state up. The contents of any directory has been created under much thought. As already mentioned, especially on the serverside you might have other code-snippets, config and stuff in non .ts endings that you need colocated with your code. And deleting these files (by not copying them) is the worst DX you can imagine of. TS (and the transpilation process) should behave as I would have written directly a JS files instead of an TS file and keep the directory tree completely intact as it was.

Please include an option (as marcospgp has greatly described) as to simply "keep the state as TS encountered it", when using "outDir": "./dist". Over 50 Votes in half a year speaks for itself! Thanks very much.

If no one else has been able to write a good file copy script, I don't think we'll be the first.

Another thing to consider. Although I could write a build step to do this for me, the fact that TypeScript already has the list of projects where I need this to happen (via project references), means we'll now have to maintain this list in two places.

I ended up creating a tscopy command for use within our company monorepo to copy all non JavaScript / TypeScript assets for any project references in the given tsconfig.json:

⚠️This command pays no attention to your include directives, and assumes that you follow the convention of putting all of your source code within src, like we do.

Another use case, is in a mixed source environment. I want my javascript source code to be "transpiled" (the reason I use quotes, is that this may only be a nop / straight copy, or it could convert from ES?? to requested target)
to the destination.

This SHOULD BE (imho) in the purview of the tsc compiler.
I have found that the "declaration" compiler option excludes me from this scenario.

@redevill, is what you want different to what allowJs: true provides?

Yes, because I would still like to output the *.d.ts files. However if you use the allowJs:true, you may not also use the declaration: true.

However if you use the allowJs:true, you may not also use the declaration: true.

@redevill, this was fixed in TypeScript 3.7.

OH!! sweet - Missed that part, I'm on the previous version. Thank you!

has this been resolved?

@naufalkhalid - if you are looking to have things like assets *.jpg files copied to the dist folder, my understanding is that the compiler will not do it - and the OP feature request remains unresolved. The argument above, is to keep the responsibility of the compiler strictly to compilation. Package assembly, to be delegated as a separate responsibility, to some other software lib.

In my special case I wanted Javascript files copied to the dist folder. In this case you can tell the compiler to include javascript files in your transpile process, and they will be copied to the dist folder as part of this process. (compiling javascript as well as typescript is part of the transpilers job)

If TS is unwilling to implement an option like this is there any sort of hook that could be used at the end of TS running to activate a copy script? Perhaps that could be an acceptable solution where TS doesn't do the copying itself but provides a way to hook into it post run to allow the dev to do whatever else they please.

I have seen a several implementations (not hooks) but build scripts where such copy procedures are bundled together with the Compilation request... Gulp, Grunt... or even script section of the package.json can be used to launch your own JS to perform these actions in a "postMyScriptNameHere" script.

For archeological purposes, I'm posting here a dirty file scripts/tsc.ts which runs both tsc (even ttsc actually due to another strong lack of feature in TS - being able to relativize paths in the output) and cpx, both for full and for watch modes. It copies (and watches) all other files from src/ to dist. So much code.

#!/usr/bin/env TS_NODE_COMPILER_OPTIONS={"module":"commonjs"} yarn -s ts-node
import concurrently, { CommandObj, Options } from "concurrently";
import glob from "glob";
import { basename, dirname } from "path";
import { chdir } from "process";
import { randomColor } from ".";

const watch = process.argv.includes("--watch");
const parallel = watch;

// Fastest time & best config:
// - using incremental build (watch-incremental is ~20% slower than
//   watch-non-incremental, thus acceptable)
// - for the full build, use -b
// - for watch, use WITHOUT -b (much faster, ~0.5s without -b vs. 3s with -b)
// Seems related: https://github.com/microsoft/TypeScript/issues/31932

chdir(`${__dirname}/..`);

const options: Options = {
  killOthers: ["failure"],
  prefix: "[{time} {name}]",
  timestampFormat: "mm:ss.S",
};

let commands: CommandObj[] = [];
if (!parallel) {
  commands.push({
    command: "ttsc -b --preserveWatchOutput",
    name: "tsc",
    prefixColor: "yellow",
  });
}
for (const src of glob.sync("packages/*/src")) {
  if (parallel) {
    const dir = dirname(src);
    commands.push({
      command:
        `ttsc --preserveWatchOutput --project '${dir}/tsconfig.json'` +
        (watch ? " --watch" : ""),
      name: basename(dir),
      prefixColor: randomColor(),
    });
  }
  // Copy all files BUT *.ts and *.tsx (which will be turned into JS by TS).
  // Notice that we include *.d.ts, they're perfectly valid to copy.
  commands.push({
    command:
      `cpx '${src}/**/{*.d.ts,!(*.ts|*.tsx)}' '${src}/../dist'` +
      (watch ? " --watch" : ""),
    name: "cpx",
  });
}

concurrently(commands, options).catch(() => process.exit(1));

If you are hunting for solutions, you may find some here: https://stackoverflow.com/questions/40471152/tell-typescript-to-compile-json-files "Tell TypeScript to compile json files"

Since I don't want to define all sorts of locations on what files needs to be included and what not, I tried re-using the tsconfig.json and make use of the getParsedCommandLineOfConfigFile provided by the Typescript API. You can trick this function into resolving more files using the extraFileExtensions. I ended up creating a Gulp script to easily copy the asset files and leave all others up to Typescript:

const {src, dest} = require('gulp');
const {relative, extname} = require('path');
const {getParsedCommandLineOfConfigFile, sys, ScriptKind, Extension} = require('typescript');

function asExtension(extension) {
  return { extension, scriptKind: ScriptKind.Deferred };
}

async function assets() {
  // File extensions we want to copy as assets.
  const assetExtensions = ['.yaml'];

  // Let Typescript handle these file extensions and retrieve all other files with the given asset extensions.
  const typescriptExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx];
  const parsed = getParsedCommandLineOfConfigFile('tsconfig.json', {}, sys, undefined, undefined, assetExtensions.map(asExtension));
  const assetFiles = parsed.fileNames.filter(fileName => !typescriptExtensions.includes(extname(fileName)))
    .map(fileName => relative(parsed.options.baseUrl, fileName));

  return src(assetFiles, { base: './' }).pipe(dest(parsed.options.outDir))
}

module.exports = { assets };

Hope this helps someone.

Any hope this will be reopened? TS compiler mess with config json files as they're referenced at run time and not copied to the destination path.

I'm using tsc to compile src modules. Now I just want to copy my templates ejs files to dist folder. I use nodejs fs module to read the files, but it can not be found in dist. 我想我应该只会分发 dist 目录,而不会发布 src 目录,所以templates 理应在 dist 中包含。

My version:

{
  "scripts": {
    "prebuild": "del lib/",
    "build": "tsc",
    "postbuild": "cpy '**/*' '!**/*.ts' ../lib/ --cwd=src/ --no-overwrite --parents"
  },
  "devDependencies": {
    "cpy-cli": "^2.0.0",
    "del-cli": "^3.0.0"
  }
}

Worked for me, but only without the '' in the copy script. Tested on Windows 10.

My copy script: cpy **/* !**/*.ts ../dist --cwd=src --no-overwrite --parents

This is partial, because watch mode is not covered, and we all spend 99% of time working with tsc in watch mode typically. Build from scratch is the least interesting thing typically.

I know that cpy supports watch mode too, but it all complicates package.json scripts a lot.

+1! I'm also struggling with getting /public included in my Typescript build. Of course I could copy it into /dist, but it only works as a local solution. I'm deploying to Heroku, and they prefer to build their own bundle, so it must be included during build time.

Will this be considered?

@tomcatmwi same issue.
Im not sure how it works with Heroku because I have never used it.
But localy this works for me:

```
"scripts": {
"start": "node dist/server.js",
"build": "tsc && cp -R src/public dist",
"dev": "nodemon --exec ts-node src/server.ts"
},

I try to keep things simple. tsc practically my only build tool. It would be nice if I could list my html and css files that are in my src folder in either "include" or "files" and have them copied to outDir along with everything else without having to resort to making my own script or other build tools...

If someone has a problem by copying their .d.ts files. I found on stackoverflow an answer really clear : https://stackoverflow.com/questions/56018167/typescript-does-not-copy-d-ts-files-to-build

Was this page helpful?
0 / 5 - 0 ratings