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

Typescript compiler --copy-files option to copy non-typescript files to dist folder #30835

Closed
5 tasks done
marcospgp opened this issue Apr 9, 2019 · 62 comments
Closed
5 tasks done
Labels
Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript

Comments

@marcospgp
Copy link

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:

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

yortus commented Apr 10, 2019

Duplicate of #15222?

@RyanCavanaugh RyanCavanaugh added Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript labels Apr 10, 2019
@RyanCavanaugh
Copy link
Member

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

@akashmugu
Copy link

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

@stuart-clark-45
Copy link

This would be really handy!

@yujiangshui
Copy link

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

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

@Mohit21GoJs
Copy link

Mohit21GoJs commented Sep 25, 2019

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"

@FredericLatour
Copy link

FredericLatour commented Oct 3, 2019

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"

bogdan0083 added a commit to bogdan0083/better-search that referenced this issue Oct 14, 2019
We need to copy samples folder to make our tests work, but [typescript compiler doesn't support copying of non-ts files (microsoft/TypeScript#30835)

We need to make sure `copyfiles` utility works file on Linux and OSX (tested only on Windows so far)
@kenberkeley
Copy link

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"
  }
}

@prograhammer
Copy link

prograhammer commented Nov 18, 2019

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

So why not TypeScript... 😄

@steve-at-bluescape
Copy link

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.

@ichtestemalwieder
Copy link

ichtestemalwieder commented Jan 18, 2020

@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.

@dchambers
Copy link

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.

@dchambers
Copy link

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.

@redevill
Copy link

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.

@dchambers
Copy link

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

@redevill
Copy link

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.

@dchambers
Copy link

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

@redevill, this was fixed in TypeScript 3.7.

@redevill
Copy link

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

@naufalkhalid
Copy link

has this been resolved?

@redevill
Copy link

@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)

@stieg
Copy link

stieg commented Apr 23, 2020

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.

@redevill
Copy link

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.

@Evaldas-B
Copy link

Evaldas-B commented Jan 31, 2022

There is a difference between process.cwd() and __dirname

process.cwd() returns the current working directory (where the script was launched from)
__dirname returns the directory of the current file.

If you are writing a CLI you can imagine that using process.cwd() could cause problems because you can run your CLI from any directory

If this helps anyone, I arrived at this issue because I was reading files with __dirname

import fs from "fs/promises";
import path from "path";

fs.readFile(path.join(__dirname, './hello.txt'))

Since then I've switched to using process.cwd(), so it works with Typescript/ts-node and the compiled JS/node.

import fs from "fs/promises";
import path from "path";
import process from "process";

fs.readFile(path.join(process.cwd(), './resources/hello.txt'))

The 2nd example assumes you run node/ts-node from the project directory with the file structure below

project
  resources
    hello.txt
  src
    foo.ts
  dist
    foo.js

There is a difference between process.cwd() and __dirname

process.cwd() returns the current working directory (where the script was launched from)
__dirname returns the directory of the current file.

If you are writing a CLI you can imagine that using process.cwd() could cause problems because you can run your CLI from any directory

@nmsobri
Copy link

nmsobri commented Apr 2, 2022

so as it stand right now, we basically need to copy manually? tsc should provide this functionality.. its really important, since we mostly work on web stuff, we normally work with file other than *.ts and *.js

@nopeless
Copy link

nopeless commented Apr 14, 2022

Honestly I don't get it, it feels like hypocrisy to me.

As mentioned here #30835 (comment)
tsc already provides a --watch feature in the first place when gulp, nodemon, scripts are an option

If tsc is already walking through all files in the source, copying one to another wouldn't be much of a hassle to implement

@slifty
Copy link

slifty commented Jul 7, 2022

I wanted to share my use case too.

I am using a light weight database library that uses .sql files to define migrations and queries.

I would love to NOT have to manually copy those files, and if I were using babel I wouldn't have to.
I would also like to avoid having to put migrations and query sql files outside of my /src.

For now it seems I'll just have to add a manual copy step for these items.

@bencergazda
Copy link

bencergazda commented Oct 17, 2022

Spoiler: I am the author of TSCP

I was struggling with the same problem, and then created typescript-cp.

It detects the source and output directories based on your project's tsconfig.json (using typescript's own config parser, to work as smooth as possible) and copies the non-TS files to the outDir.

It has a very basic CLI, with similar arguments to typescript's tsc, but this is tscp. It also supports package references.

# Copy
$ tscp

# Copy for TS project references
$ tscp -b

# Watcher
$ tscp -w

# Watcher for TS project references
$ tscp -b -w

# Custom compiler settings
$ tscp -p tsconfig.production.json

# Help
$ tscp -h

Example

package.json

{
  //...
  "scripts": {
    "start": "tsc -w & tscp -w",
    "build": "tsc && tscp"
  },
  //...
}

Feel free to open an issue if something isn't working as expected.

@stabai
Copy link

stabai commented Oct 28, 2022

Knowing this is likely never happening anyways, I felt the need to propose a response to this comment:

Anyway explicit non-goal 4 is:
Provide an end-to-end build pipeline

I think the point that is missed here is that this isn't a random build issue people want the TypeScript compiler to solve for them. This is a problem created by TypeScript. A lot of npm packages don't need to have a build process at all, since the source code is directly executed (there's nothing to build). But the nature of TypeScript always requires a build process, and in an ecosystem that is often used to not needing it. Since the build output is dumped in a different folder, all of the existing resource files are lost in that process.

If someone wants to obfuscate their code, then I agree, that's not the responsibility of tsc. They would need a build process to do that anyways. But when tsc is the sole reason for the problem existing, it feels like it should provide a solution.

All that said, I think the right option would be for this to be handled via an entry in tsconfig.json rather than a command line flag. Manually specifying what input files your code requires at runtime with glob patterns seems like a pretty straightforward solution.

@redevill
Copy link

@stabai - You could even surmise that: You may NOT use Typescript unless you also also choose to use some kind of CI. (as TSC - does not provide copy services.)

@twasink
Copy link

twasink commented Feb 1, 2023

I'd like to add my use case here. I'm experimenting with TypeScript as part of an AWS SAM application. In this application, I've got a few different folders with related files in them, and each has a package.json and corresponding node_modules folder.

When I run tsc over these, the node_modules folders aren't copied. Resource files aren't copied. As a result, I can't run tests - if I point it at the generated code, important files aren't present, but if I point it at the original, it can't understand the code

Similarly, I can't use sam-local to test and debug running code, because the transpired version doesn't have the node modules. Using sam build works around this, because it does an NPM ci step as part of building, but then SAM is running off that build artefact.

Simply copying the JS files, as is, instead of transpiling, would go a long way to solve this. In particular, making this work with tsc --watch is important, so that the other resources are copied when modified. A workaround would be to have a second watch process running to copy those files, but that's frankly a ridiculous hack.

@nopeless
Copy link

nopeless commented Feb 2, 2023

@twasink

What do you mean by "node_modules folders aren't copied"? They aren't supposed to be copied. Also, what do you mean by "simply copying the js files?" If you are currently migrating, you can enable js files in tsconfig

@twasink
Copy link

twasink commented Feb 2, 2023

@nopeless

I can't run the generated JS files if they're in a directory by themselves. They need to have the rest of the files of the project with them - e.g. the node_modules folder, any resource files, etc. Otherwise I can't even run tests.

So having tsc --watch generate files to an output directory is useless if it doesn't also put the non-TS files in the output directory as well - at least as an option.

As for "simply copying the JS files" - I would like a mode where the JS files were copied - not transpiled to the target ES version. Using allowJS does not put the exact JS files in - it puts a transpired version in, possibly with significant changes. And, in my test case, the transpiled version broke tests, whilst putting the original version in did not.

Now, this can be mitigated by not using an outDir - having the TS files transpile to JS in the same folder, and putting up with any warnings for the existing JS files that tsc tries to overwrite with themselves. But this is shitty, because it's a bad idea for generated files to go into the same directory as source code, and it's especially shitty when only some of the JS files are generated - people edit the generated file by accident, it's annoying to manage with version control, and you need to make sure your CI bundling doesn't include the TS files (because why would you want to?)

Simply put, it's important to be able to have a directory, that has the JS files that you're going to publish, in a spot where you can use them locally, and to have that be updated during development. This is not a CI concern - it's an issue for local development.

@nopeless
Copy link

nopeless commented Feb 2, 2023

@nopeless

I can't run the generated JS files if they're in a directory by themselves. They need to have the rest of the files of the project with them - e.g. the node_modules folder, any resource files, etc. Otherwise I can't even run tests.

So having tsc --watch generate files to an output directory is useless if it doesn't also put the non-TS files in the output directory as well - at least as an option.

As for "simply copying the JS files" - I would like a mode where the JS files were copied - not transpiled to the target ES version. Using allowJS does not put the exact JS files in - it puts a transpired version in, possibly with significant changes. And, in my test case, the transpiled version broke tests, whilst putting the original version in did not.

Now, this can be mitigated by not using an outDir - having the TS files transpile to JS in the same folder, and putting up with any warnings for the existing JS files that tsc tries to overwrite with themselves. But this is shitty, because it's a bad idea for generated files to go into the same directory as source code, and it's especially shitty when only some of the JS files are generated - people edit the generated file by accident, it's annoying to manage with version control, and you need to make sure your CI bundling doesn't include the TS files (because why would you want to?)

Simply put, it's important to be able to have a directory, that has the JS files that you're going to publish, in a spot where you can use them locally, and to have that be updated during development. This is not a CI concern - it's an issue for local development.

This seems like a different issue; Do you have a repo with minimal repro? @twasink

@twasink
Copy link

twasink commented Feb 2, 2023

I'll be making one for a blog post/rant this weekend. The repo I tried this out with is a private work one.

But this is the issue that I'm talking about: having a directory that I can use for things such as running unit tests, that has a consistent view of all of my code base (including dependencies, non-JS files, etc). If the only tool I'm using that produces transformed code is tsc, then that should be something tsc should be able to do...

@AlexanderStoyanov
Copy link

For anyone struggling to work around this issue, this is what worked for my use case (docker multistage build):

  1. Remove/comment out outDir from tsconfig.json entirely, allowing npx tsc command to generate JS files exactly where their TS siblings are.
  2. Compile TS to JS - npx tsc (or whatever your command is).
  3. Remove any unneeded files with rm -rf, in my case it was rm -rf src/**/*.ts src/*.ts

It's a moderately jank solution, but no extra packages/installations required & it's relatively fast in execution time.

@jmcilhargey
Copy link

A couple of other approaches which may be helpful to someone.. (Copying a ../templates folder to ../../dist/templates in this example).

Using fs-extra and execute node my-script-name.js in package.json

const fs = require('fs-extra')
const { resolve } = require('path')

fs.copySync(
  resolve(__dirname, '..', 'templates'),
  resolve(__dirname, '..', '..', 'dist', 'templates')
)

Using webpack and execute webpack --mode production in package.json

const CopyWebpackPlugin = require('copy-webpack-plugin');
const { resolve } = require('path')

module.exports = {
  // ...other configuration options...
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        { from: resolve(__dirname, '..', 'templates'), to: resolve(__dirname, '..', '..', 'dist', 'templates')},
      ],
    }),
  ],
};

@mathieuprog
Copy link

mathieuprog commented May 16, 2023

I had to copy css files (I need to be able to import the css files from another package), this worked for me:

npm install copyfiles -g

Inside "scripts" of package.json:

tsc --build && copyfiles -u 1 src/**/*.css dist

@SanariSan
Copy link

Had to copy .sql files deep in src dir, doing like that:

"postbuild": "node ./node_modules/copyfiles/copyfiles -u 1 -e \"**/*.js\" -e \"**/*.ts\" \"src/**/*\" dist"

@alexisperez4
Copy link

Just created a new NodeJS package called tsc-hooks. It adds hooks to the TypeScript compiler, I was originally writing it for the sole purpose of this issue, but then thought there might be more short comings of tsc.
So if you would like a seamless solution for this you can install tsc-hooks as a dev dependency using yarn add tsc-hooks --dev or if you want to install it to your global tsc command run yarn global add tsc-hooks.
After installation use the new hooks option in your tsconfig like so:
tsconfig.json

{
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": [ "src/**/*" ],
  "exclude": [ "src/**/*.txt" ],
  "hooks": [ "copy-files" ] /* <-- NEW OPTION HERE */ 
}

Whatever is in include and not exclude will be copied over to the outDir.
And that's it! Run tsc from your scripts in package.json or if you installed it globally just run tsc
Note: For some reason there have been some issues with nvm when using npm, so I'd recommend using yarn.
If you have any questions or need help, just open up a discussion here or if there are any issues open up an issue here.

Seem like this does not work on macOS Monterey (tested with npm and typescript 4.5)

Just created a new NodeJS package called tsc-hooks. It adds hooks to the TypeScript compiler, I was originally writing it for the sole purpose of this issue, but then thought there might be more short comings of tsc.
So if you would like a seamless solution for this you can install tsc-hooks as a dev dependency using yarn add tsc-hooks --dev or if you want to install it to your global tsc command run yarn global add tsc-hooks.
After installation use the new hooks option in your tsconfig like so:
tsconfig.json

{
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": [ "src/**/*" ],
  "exclude": [ "src/**/*.txt" ],
  "hooks": [ "copy-files" ] /* <-- NEW OPTION HERE */ 
}

Whatever is in include and not exclude will be copied over to the outDir.
And that's it! Run tsc from your scripts in package.json or if you installed it globally just run tsc
Note: For some reason there have been some issues with nvm when using npm, so I'd recommend using yarn.
If you have any questions or need help, just open up a discussion here or if there are any issues open up an issue here.

Seem like this does not work on macOS Monterey (tested with npm and typescript 4.5)

Thanks for the tip. It worked for me! Cheers!

@yang-wang11
Copy link

The below way works for me

# Install the dependence
npm install --save-dev copyfiles

# Update the scripts section in the package.json file
{
  "copy-files": "copyfiles -u 1 src/**/*.js dist/",
}

@zanminkian
Copy link

zanminkian commented Apr 29, 2024

I have created a simple tsc wrapper @rnm/tscx to solve this problem.

npm install typescript @rnm/tscx -D
{
  "scripts": {
    "build": "tscx -p tsconfig.json", // same as "tsc -p tsconfig.json"
    "dev": "tscx -p tsconfig.json --watch --copyfiles --exec bootstrap.js"
  }
}
npm run dev

It works and makes my life easier.🤣

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests