types.ts files do not trigger rebuild in webpack-dev-server
abirmingham opened this issue Β· 58 comments
First - thanks for the plugin and associated speed improvements!
One issue I've seen is that webpack-dev-server will not rebuild when a change is made in a source file that will not emit js. For example, if I have a types.js file that exports nothing but types/interfaces, and I make a change to that file that introduces a type error, I will not see a warning for that error until I change a file that does emit js. Here is an example:
// types.ts
export interface Person {
name:string;
};
// mainEntry.ts
import {Person} from 'types';
const sally:Person = {name: 'Sally'};
Changes to mainEntry.ts will trigger a rebuild, but updating types.ts with name:number
does not trigger a rebuild, and the error is unnoticed.
Relevant webpack config is as follows:
var threadLoader = {
loader: 'thread-loader',
options: {
// leave one cpu for the fork-ts-plugin
workers: require('os').cpus().length - 1,
},
};
var babelLoader = {
// XXX: .babelrc doesn't seem to be respected so we specify options here
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: ['es2015'],
},
};
<snip>
modules: {
rules: [
{
test: tsSrc,
exclude: babelExcludes,
use: [
{ loader: 'cache-loader' },
threadLoader,
babelLoader,
{ loader: 'ts-loader', options: { happyPackMode: true } },
],
},
<snip>
plugins: [
new ForkTsCheckerWebpackPlugin({
tslint: true,
watch: ['./src', './test', './test_util'], // optional but improves performance (less stat calls)
}),
<snip>
I've been working with @abirmingham and have seen this myself. Do you have any ideas @piotr-oles? Thanks!
This plugin has no influence to the build watcher. You can comment out plugin and build should not trigger after .d.ts
change. I suspect that issue is in ts-loader
or combination of ts-loader
and HappyPack
. :)
Hi @piotr-oles, thanks for responding! The issue is not phrased very clearly. Let me share my experience:
If tweaking a file which is interfaces only, fork-ts-checker-webpack-plugin does not seem to notice the change and perform checking again. It's like it only watches files that would produce JavaScript if TSC was pointed at them. Have you any ideas why this might be?
To be clear: it's that fork-ts-checker-webpack-plugin does not seem to detect changes to interface only files. You'd imagine that it would, as a change to an interface could change a successful type check into a failed one. (Hope that makes sense)
fork-ts-checker-webpack-plugin is hooked to the compilation run and if compilation doesn't run after file change, it will not run checker. I think it doesn't run because of some optimization in loader in transpileOnly mode - there is no need to recompile project if only typings have been changed.
Maybe it's bad design to rely on compilation run in async mode - maybe I should make it totally async... It can check files that are not a part of webpack's compilation so maybe it will be better behavior :)
Thanks for the clarification @piotr-oles. It would be good if we could resolve this - I'm open to making changes in ts-loader if that would help. Though what you've suggested might be the better design choice...
In the meantime is it worth noting this down on the readme as a known issue for now? Happy to submit a docs PR....
I guess an ugly workaround which might / might not work would be to have some actual JavaScript in interface only files to ensure that file changes always trigger compilation. Sounds like a terrible hack. Not recommended...
Always happy to see PR :)
Thanks for the replies, @johnnyreilly and @piotr-oles!
I don't have much experience with webpack plugin development, but if I'm reading awesome-ts-loader correct, it appears that they hook into 'after-compile' here, and add dependencies in that function here. Should fork-ts-checker-webpack-plugin do the same? Apologies if I'm misinterpreting the issue!
Completely by the by but for interest you can see the corresponding code in ts-loader here and here. That said, I think @piotr-oles is proposing not depending on ts-loader's compilation (I think)
That makes sense. @piotr-oles if you have a suggested implementation I can take a shot at it and create a PR :)
In the meantime is it worth noting this down on the readme as a known issue for now? Happy to submit a docs PR....
I've made the suggested docs PR now.
fork-ts-checker-webpack-plugin is hooked to the compilation run and if compilation doesn't run after file change, it will not run checker.
Like @abirmingham I'd be happy to help out on a suggested implementation to fix this problem. Do you have an idea that you'd like to try?
I haven't looked in depth at the reasons why this is happening but I'd speculate that this is to do with webpack seeing that the output from ts-loader hasn't changed (outputBefore === ''
and outputAfter === ''
) so doesn't trigger compile
. My knowledge of the plugin API is a little sketchy though and so I can't be sure. (This is useful by the way: https://webpack.js.org/api/plugins/)
@sokra - if you have a moment, is that a reasonable assumption of webpack behaviour on my part?
There is an interesting proposal in TypeScript project (thanks @johnnyreilly for cc ;) ): microsoft/TypeScript#17493 so I think the best idea is to wait for it and implement when it will be ready :)
Another solution would be to just watch files from fork-ts-checker plugin but it would add another watcher :/ The hard part would be to make sure it watches all files it should and to be consistent with TypeScript or WebPack. The proposal I've mentioned makes WebPack, TypeScript and fork-ts-checker consistent.
I'm glad you noticed @piotr-oles - I'm never too sure if everyone sees their cc's. π»
so I think the best idea is to wait for it and implement when it will be ready :)
I think you may be right; do feel free to pitch in on the comment thread. Your input will be very valuable to them and the TypeScript team are friendly and receptive people!
On another matter, from what @TheLarkInn has said about "deprecated accessing of tapable
instances inside the loaders", I get the impression that transpileMode
will become the only "approved" way to implement a typescript loader for webpack in the future. That is to say; ts-loader presently does 2 things:
- transpile
- check types and report them to webpack
And doing "2" involves hooking into webpack internals. I don't have the details but it sounds like the only way to do "2" in a webpack friendly way will be through moving type checking into a plugin (just as fork-ts-checker-webpack-plugin does) and only to run ts-loader in transpileMode
. If that is the case then that has serious implications for the design of any typescript loaders.
My understanding may not be 100% on this and so I'd appreciate anyone who can set me straight doing so.
cc @jbrantly
The hard part would be to make sure it watches all files it should and to be consistent with TypeScript or WebPack. The proposal I've mentioned makes WebPack, TypeScript and fork-ts-checker consistent.
How does this makes all of them consistent? createWatchMode
sounds for me like an additional watcher that runs independently of webpack? If this is the case, how can we synchronize them for non-async mode?
Btw this issue is already solvable when you pass all type checked files back to webpack, so webpack can detect changes on this file and trigger another build. But this will work only for non-async mode.
With microsoft/TypeScript#17493 now being resolved, is there a chance of seeing a fix for this now?
Would really love seeing a fix for this :) pushes politely
It looks like it's working it's not π
@piecyk that's exciting, what version is it working on? Where/when was it fixed?
@abirmingham hard to say, just testing fork-ts
with
"webpack": "4.8.3",
"typescript": "2.8.3",
"ts-loader": "4.3.0",
"fork-ts-checker-webpack-plugin": "^0.4.1",
Basic changing ts file with only type triggers rebuild, and correct type diagnostic to be presented.
Maybe @johnnyreilly has more insides ?
If it's fixed that's awesome! I believe there's been a couple of PRs along the way that I have not been involved with. Maybe it was one of them that resolved it?
@piecyk hm..., the same versions of packages., however, changing ts(x) files which have only types definitions does not trigger a rebuild.
@johnnyreilly @a-tarasyuk @abirmingham false alarm Guys, it's not working, tested on repo from scratch... It looks like on my more complex repo that mislead me something is triggering the watch to run...
Bump :)
Is this issue fixed yet?
I've not tested it but I wonder if useTypescriptIncrementalApi: true
would resolve this? Details on how to use here:
https://blog.johnnyreilly.com/2019/01/typescript-and-webpack-watch-it.html
@johnnyreilly i just tried, and i have lost all the type checking after setting
useTypescriptIncrementalApi: true
Typescript: 3.3.1
fork-ts-checker-webpack-plugin: ^1.0.0-alpha.6
new ForkTsCheckerWebpackPlugin({
vue: true,
tsconfig: '......',
async: false,
useTypescriptIncrementalApi: true
}),
Setting useTypescriptIncrementalApi: false
restores the type checking but still no changes are emitted when changing .ts files that contains only interfaces/types etc...
Try renaming type files to *.d.ts instead of *.ts Works in my case
@johnnyreilly Actually I've noticed this behavior on normal .ts files that were not just interfaces. This happens to me on typescript files I use as helper utilities which contain classes and other code which I specifically exclude from being transpiled to javascript. I use webpack split chunks to merge all my helper files into a single file I include manually. Because these excluded files were not "registered" with webpack specifically, they are not being detected as being watched.
To make this more clear. Here is a sample of my webpack config. I am wanting to include TypeScript files that end with "-main.ts". My TypeScript util files will not have the "-main" on them. If I replace the if statement to include all TS files (ignoring .d.ts) then this all works as expected.
Also you can see my TS rules below.
function getFilesFromDir(dir, fileTypes) {
//var filesToReturn = [];
function walkDir(currentPath) {
var files = fs.readdirSync(currentPath);
for (var i in files) {
var currFile = path.join(currentPath, files[i]);
//----> REPLACE STATEMENT BELOW TO FIX -----if (fs.statSync(currFile).isFile() && currFile.indexOf(".ts") >= 0 && currFile.indexOf(".d.ts") < 0)
if (fs.statSync(currFile).isFile() && currFile.indexOf("-main.ts") >= 0) {
var entryName = path.basename(currFile).replace(".ts", "");
tsEntries[entryName] = path.resolve(currFile);
} else if (fs.statSync(currFile).isDirectory()) {
walkDir(currFile);
}
}
}
walkDir(dir);
}
//Get all files that end in -main.ts noting that they are the main entry points
getFilesFromDir("./src/", [".ts"]);
module.exports = {
entry: tsEntries,
// Rules...
module: {
rules: [
{
test: /\.tsx?$/,
include: path.resolve(__dirname, "src"),
use: [
{
loader: "babel-loader",
options: babelOptions
},
{
loader: "thread-loader",
options: {
// there should be 1 cpu for the fork-ts-checker-webpack-plugin
workers: require("os").cpus().length - 1
}
},
{
loader: "ts-loader",
options: {
happyPackMode: true,
transpileOnly: true,
experimentalWatchApi: true,
}
}
]
},
//Plugins
plugins: [
new ForkTsCheckerWebpackPlugin({
checkSyntacticErrors: true,
})
I think your issue is different to the one in this thread (which I think is solved now).
Moreover, your setup seems somewhat unusual. Before you go further I'd suggest getting a more straightforward config in place; here's an example to get you started:
https://github.com/TypeStrong/ts-loader/tree/master/examples/fork-ts-checker-webpack-plugin
@johnnyreilly What part of my config looks off? I'm using the latest versions which should contain any fixes.
"devDependencies": {
"assets-webpack-plugin": "3.9.10",
"babel-loader": "8.0.5",
"cache-loader": "2.0.1",
"clean-webpack-plugin": "2.0.1",
"copy-webpack-plugin": "5.0.1",
"cross-env": "^5.2.0",
"css-loader": "2.1.1",
"dart-sass": "1.17.3",
"expose-loader": "0.7.5",
"file-loader": "3.0.1",
"fork-ts-checker-webpack-plugin": "1.0.0",
"hard-source-webpack-plugin": "^0.13.1",
"imports-loader": "0.8.0",
"mini-css-extract-plugin": "0.5.0",
"sass-loader": "7.1.0",
"source-map-loader": "0.2.4",
"style-loader": "0.23.1",
"thread-loader": "2.1.2",
"ts-loader": "5.3.3",
"tsconfig-paths-webpack-plugin": "3.2.0",
"tslint": "5.11.0",
"typescript": "3.4.0-rc",
"webpack": "4.29.6",
"webpack-bundle-analyzer": "3.1.0",
"webpack-cli": "3.3.0",
"webpack-dev-server": "3.2.1",
"webpack-merge": "4.2.1",
"acorn": "6.1.1"
},
I forgot to mention that I once I replaced that if statement to include all TS files(which started making everything working) and then went back to my old if statement (which excluded non -main.ts files) then everything was STILL working. It's almost as if there was something in cache or alike that got reset. I am still able to edit these helper files and watch is still compiling upon save.
I'd advise ditching thread-loader and changing your ts-loader options to happyPackMode: false, transpileOnly: true, experimentalWatchApi: false
Also:
new ForkTsCheckerWebpackPlugin({ checkSyntacticErrors: false, })
See also: https://blog.johnnyreilly.com/2018/12/you-might-not-need-thread-loader.html
Thanks, I'll test it out. I'm thinking that microsoft/TypeScript#29813 will pan out better than thread loader.
I think I will be able to resolve it with 5.0.0 release - so stay tuned :)
@piotr-oles wouldn't typescript adding specific syntax for import type
help with this ? https://devblogs.microsoft.com/typescript/announcing-typescript-3-8/#type-only-imports-exports
@oviava that new syntax doesn't replace the old one. It is an addition, not a replacement.
Well they added something new with
In conjunction with import type, weβve also added a new compiler flag to control what happens with imports that wonβt be utilized at runtime: importsNotUsedAsValues. This flag takes 3 different values:
remove: this is todayβs behavior of dropping these imports. Itβs going to continue to be the default, and is a non-breaking change.
preserve: this preserves all imports whose values are never used. This can cause imports/side-effects to be preserved.
error: this preserves all imports (the same as the preserve option), but will error when a value import is only used as a type. This might be useful if you want to ensure no values are being accidentally imported, but still make side-effect imports explicit.
So now with fork-ts-checker-webpack-plugin, if you set the flag to preserve
it actually triggers on types only TS files
The issue is somewhere else - in the webpack
+ ts-loader
/ babel
. If you use ts-loader
with transpileOnly: true
or babel
, it omits files that contain only types (it doesn't add it to the dependencies Set in webpack compilation). Because of that, webpack will not rebuild the project after changing something in the types only file. And fork-ts-checker-webpack-plugin is controlled by webpack - so if webpack doesn't run the compilation, the plugin cannot run checks and report errors.
Here is how I solved this. Maybe will be helpful for someone else :)
webpack.config.js:
const glob = require("glob");
const tsConfigEditorJson = require("./tsconfig.editor.json");
const tsIncludePatterns = tsConfigEditorJson.include;
const addToFileDeps = {
apply(compiler) {
compiler.hooks.afterCompile.tap("AddToFileDepsPlugin", compilation => {
tsIncludePatterns
.reduce(
(filePaths, pattern) => filePaths.concat(glob.sync(pattern)),
[]
)
.forEach(filePath => {
compilation.fileDependencies.add(path.resolve(filePath));
});
});
}
};
module.exports = env => {
return {
...
module: {
rules: [
{
test: /\.ts$/,
loader: "ts-loader",
options: {
configFile: "tsconfig.editor.json",
transpileOnly: true
}
}
]
},
plugins: [
new ForkTsCheckerWebpackPlugin({
tsconfig: "tsconfig.editor.json"
}),
addToFileDeps
]
}
}
tsconfig.editor.json:
{
...
"include": [
"src/dir1/**/*.ts",
"src/dir2/**/dir3/**/*.ts",
"src/dir2/**/dir4/**/*.ts"
]
}
The issue is somewhere else - in the
webpack
+ts-loader
/babel
. If you usets-loader
withtranspileOnly: true
orbabel
, it omits files that contain only types (it doesn't add it to the dependencies Set in webpack compilation). Because of that, webpack will not rebuild the project after changing something in the types only file. And fork-ts-checker-webpack-plugin is controlled by webpack - so if webpack doesn't run the compilation, the plugin cannot run checks and report errors.
see my comment, if you use importsNotUsedAsValues: preserve
in tsconfig, apparently it does add it to the dep tree, it fixed it for me at least - only have the value set in dev mode
Oh, that's cool :) Unfortunately, we can't fix it on the plugin side - so either you have to set it manually or change the configuration/behavior of the ts-loader
/ babel
.
Thank you @oviava for the comment about importsNotUsedAsValues: preserve
- I documented it in the README.md
in the new alpha release :) I will close this issue as this configuration solves the problem :)
This flag doesn't seem to help me :(
.babelrc.js
module.exports = api => {
const isTest = api.env('test')
return {
"presets": [
"@babel/preset-typescript",
"@babel/preset-react",
[
"@babel/preset-env",
isTest ? { // babel needs to target node for jest tests instead of what's in the browserslist in package.json
"useBuiltIns": "entry",
"corejs": "3",
"targets": {
"node": "current"
}
} : {
"useBuiltIns": "entry",
"corejs": "3"
}
]
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"babel-plugin-styled-components"
]
}
}
tsconfig.json
{
"compilerOptions": {
"baseUrl": "src",
"outDir": "./dist/",
"noImplicitAny": false,
"module": "es6",
"moduleResolution": "Node",
"target": "es5",
"jsx": "react",
"allowJs": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"noEmit": true,
"importsNotUsedAsValues": "preserve"
}
}
webpack.config.js
...
new ForkTsCheckerWebpackPlugin({
typescript: {
diagnosticOptions: {
semantic: true,
syntactic: true,
},
},
}),
...
I reopened this as @sangeeth96 opened an issue which seems to be this. It could be worth trying the importsNotUsedAsValues: preserve
tip anyone that comes here - but perhaps it doesn't solve all issues?
It seems like an issue with the babel-loader
- it works with ts-loader
as far as I know (we have e2e tests for it - but only for ts-loader). I have to check some ideas on why it might not work.
Thanks @piotr-oles - in one of those "no buses come and then 3 arrive at once" things we've had a second person raise this issue. I've closed it as a duplicate and linked to this
Same issue here. I use graphql-code-generator to create an api-types.d.ts
file for my API types. Since v5, any time that d.ts
file gets updated, I have to completely restart webpack to get it to recognize the updated types. I've tried setting importsNotUsedAsValues
to preserve
but it didn't help. In v4, updates to my d.ts
files always worked fine.
A little more info: I'm using babel-loader
and it's definitely skipping over the d.ts
files since they only include types. That's unrelated to the v4->v5 upgrade for this plugin.
In both v4 and v5, updating a d.ts
wouldn't trigger a rebuild. However, I could go to the .tsx
file that was importing the d.ts
file, re-save it, and webpack would then rebuild successfully, with this plugin recognizing the updated types.
In v5, re-saving the .tsx
file triggers a rebuild, but the rebuild fails with a type error (for example, if I add property bar
to type Foo
and try to reference it, it'll still say TS2339: Property 'bar' does not exist on type 'Foo'
until I fully restart webpack). So it seems that the d.ts
file isn't being re-read even though it has been changed; instead, it's using a cached version from a previous run.
Current relevant dependencies:
fork-ts-checker-webpack-plugin
@ 5.1.0babel-loader
@ 8.0.6ts-loader
@ 4.5.0
Checking up on whether there is any progress on this issue?
Webpack is not recompiling when a type (e.g, interface) is modified and then saved.
This occurred after upgrading fork-ts-checker-webpack-plugin
from version 4
to 5+
.
The same problem:
fork-ts-checker-webpack-plugin
5.1.0babel-loader
7.1.5ts-loader
8.0.3
Rolling fork-ts-checker-webpack-plugin back to 4.1.6 resolves the problem.
I'm working on a fix, there is progress :) Unfortunately, I'm not able to give you time frame when it will be fixed
I've released a new version on the alpha channel - β6.0.0-alpha.1β. It should detect changes in type-only files without additional TypeScript/Babel configuration :)
Nice work!
I'm not sure if this case is unique, but we use css-modules-typescript-loader
to generate typings (.d.ts
files) for CSS modules. Using fork-ts-checker-webpack-plugin@6.0.0-alpha.1
we still see an issue where types from these generated files don't get picked up until webpack is restarted.
@bhollis Would you be able to create a small reproduction repository?
Here you go! I hope it's minimal enough: https://github.com/bhollis/fork-ts-checker-repro
@bhollis Your reproduction repo uses fork-ts-checker-webpack-plugin@^5.0.5
- switching to ^6.0.0-alpha.1
resolves the issue :)
Fantastic! For future reference, what was breaking it for us was this line, which was once recommended as part of the css-modules-typescript-loader
instructions:
new webpack.WatchIgnorePlugin([/scss\.d\.ts$/]),
That was disabling Webpack's watcher which meant your fix wasn't able to run.
I'm closing this as it seems to be resolved with the 6.0.0 release :)