Skip to content

Commit cf52452

Browse files
authored
docs: update babel-loader docs for type-only modules watch (#486)
1 parent 756d214 commit cf52452

File tree

6 files changed

+240
-17
lines changed

6 files changed

+240
-17
lines changed

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,14 @@ module.exports = {
6262
};
6363
```
6464

65-
If you are using **TypeScript >= 3.8.0**, it's recommended to set `"importsNotUsedAsValues": "preserve"` [compiler option](https://www.typescriptlang.org/docs/handbook/compiler-options.html)
66-
in the `tsconfig.json`. [Here is an explanation.](#type-only-modules-watching)
65+
If you are using **TypeScript >= 3.8.0**, it's recommended to:
66+
* for `ts-loader` set `"importsNotUsedAsValues": "preserve"` [compiler option](https://www.typescriptlang.org/docs/handbook/compiler-options.html) in the [`tsconfig.json`](./examples/ts-loader/tsconfig.json)
67+
* for `babel-loader` set `"onlyRemoveTypeImports": true` [preset option](https://babeljs.io/docs/en/babel-preset-typescript#onlyremovetypeimports) in the [babel configuration](./examples/babel-loader/.babelrc.js)
6768

68-
> You can find examples how to configure it with [babel-loader](https://github.com/babel/babel-loader), [ts-loader](https://github.com/TypeStrong/ts-loader),
69-
> [eslint](https://github.com/eslint/eslint) and [Visual Studio Code](https://code.visualstudio.com/) in the
69+
[Read more](#type-only-modules-watching) about type-only modules watching.
70+
71+
> Examples how to configure it with [babel-loader](https://github.com/babel/babel-loader), [ts-loader](https://github.com/TypeStrong/ts-loader),
72+
> [eslint](https://github.com/eslint/eslint) and [Visual Studio Code](https://code.visualstudio.com/) are in the
7073
> [**examples**](./examples) directory.
7174
7275
## Modules resolution
@@ -312,10 +315,14 @@ declare module "*.vue" {
312315

313316
## Type-Only modules watching
314317

315-
At present there is an issue with the `transpileOnly` mode regarding the triggering of type-checking when a change is made in a source file that will not emit js.
316-
If you have a file that contains only `interface`s and/or `type`s then, by default, changes to it will **not** trigger the type checker whilst in watch mode.
318+
At present `ts-loader` with `transpileOnly` mode and `babel-loader` will not add type-only files (files that contains only interfaces and/or types)
319+
to the webpack dependencies set. Webpack watches only files that are in the dependencies set. This means that
320+
changes in type-only files will **not** trigger new compilation and therefore type-checker in watch mode.
321+
322+
If you use **TypeScript >=3.8.0**, you can fix it:
323+
* for `ts-loader` set `"importsNotUsedAsValues": "preserve"` [compiler option](https://www.typescriptlang.org/docs/handbook/compiler-options.html) in the [`tsconfig.json`](./examples/ts-loader/tsconfig.json)
324+
* for `babel-loader` set `"onlyRemoveTypeImports": true` [preset option](https://babeljs.io/docs/en/babel-preset-typescript#onlyremovetypeimports) in the [babel configuration](./examples/babel-loader/.babelrc.js)
317325

318-
If you use **TypeScript >=3.8.0**, you can fix it by passing `"importsNotUsedAsValues": "preserve"` option to the compiler options in the `tsconfig.json`.
319326

320327
## Plugin hooks
321328

examples/babel-loader/.babelrc.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
module.exports = {
2-
"presets": [
3-
"@babel/preset-env",
4-
"@babel/preset-typescript"
5-
]
6-
}
2+
presets: [
3+
'@babel/preset-env',
4+
[
5+
'@babel/preset-typescript',
6+
{
7+
onlyRemoveTypeImports: true, // this is important for proper files watching
8+
},
9+
],
10+
],
11+
};

examples/babel-loader/tsconfig.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
"skipDefaultLibCheck": true,
1010
"strict": true,
1111
"baseUrl": "./src",
12-
"outDir": "./dist",
13-
"forceConsistentCasingInFileNames": true,
14-
"importsNotUsedAsValues": "preserve" // this is important for proper files watching
12+
"outDir": "./dist"
1513
},
1614
"include": ["./src"],
1715
"exclude": ["node_modules"]

examples/babel-loader/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ module.exports = {
1010
rules: [
1111
{
1212
test: /\.tsx?$/,
13-
loader: 'babel-loader',
1413
exclude: /node_modules/,
14+
loader: 'babel-loader',
1515
},
1616
],
1717
},

test/e2e/TypeScriptWatchApi.spec.ts

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('TypeScript Watch API', () => {
2828
{ async: false, webpack: '^5.0.0-beta.16' },
2929
{ async: true, webpack: '^5.0.0-beta.16' },
3030
])(
31-
'reports semantic error for %p with importsNotUsedAsValues configuration',
31+
'reports semantic error for %p with importsNotUsedAsValues configuration with ts-loader',
3232
async ({ async, webpack }) => {
3333
await sandbox.load([
3434
await readFixture(join(__dirname, 'fixtures/environment/typescript-basic.fixture'), {
@@ -149,6 +149,127 @@ describe('TypeScript Watch API', () => {
149149
}
150150
);
151151

152+
it.each([
153+
{ async: false, webpack: '4.0.0' },
154+
{ async: true, webpack: '^4.0.0' },
155+
{ async: false, webpack: '^5.0.0-beta.16' },
156+
{ async: true, webpack: '^5.0.0-beta.16' },
157+
])(
158+
'reports semantic error for %p with onlyRemoveTypeImports configuration with babel-loader',
159+
async ({ async, webpack }) => {
160+
await sandbox.load([
161+
await readFixture(
162+
join(__dirname, 'fixtures/environment/typescript-basic-babel-loader.fixture'),
163+
{
164+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
165+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
166+
),
167+
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
168+
WEBPACK_VERSION: JSON.stringify(webpack),
169+
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
170+
WEBPACK_DEV_SERVER_VERSION: JSON.stringify(WEBPACK_DEV_SERVER_VERSION),
171+
ASYNC: JSON.stringify(async),
172+
}
173+
),
174+
await readFixture(join(__dirname, 'fixtures/implementation/typescript-basic.fixture')),
175+
]);
176+
177+
const driver = createWebpackDevServerDriver(
178+
sandbox.spawn('npm run webpack-dev-server'),
179+
async
180+
);
181+
let errors: string[];
182+
183+
// first compilation is successful
184+
await driver.waitForNoErrors();
185+
186+
// then we introduce semantic error by removing "admin" role
187+
await sandbox.patch(
188+
'src/model/Role.ts',
189+
'type Role = "admin" | "client" | "provider";',
190+
'type Role = "client" | "provider";'
191+
);
192+
193+
// we should receive only one semantic error
194+
errors = await driver.waitForErrors();
195+
expect(errors).toEqual([
196+
[
197+
'ERROR in src/index.ts 34:7-28',
198+
`TS2367: This condition will always return 'false' since the types 'Role' and '"admin"' have no overlap.`,
199+
' 32 | const user = await login(email, password);',
200+
' 33 | ',
201+
` > 34 | if (user.role === 'admin') {`,
202+
' | ^^^^^^^^^^^^^^^^^^^^^',
203+
' 35 | console.log(`Logged in as ${getUserName(user)} [admin].`);',
204+
' 36 | } else {',
205+
' 37 | console.log(`Logged in as ${getUserName(user)}`);',
206+
].join('\n'),
207+
]);
208+
209+
// fix the semantic error by changing condition branch related to the "admin" role
210+
await sandbox.patch(
211+
'src/index.ts',
212+
[
213+
" if (user.role === 'admin') {",
214+
' console.log(`Logged in as ${getUserName(user)} [admin].`);',
215+
' } else {',
216+
' console.log(`Logged in as ${getUserName(user)}`);',
217+
' }',
218+
].join('\n'),
219+
[
220+
" if (user.role === 'provider') {",
221+
' console.log(`Logged in as ${getUserName(user)} [provider].`);',
222+
' } else {',
223+
' console.log(`Logged in as ${getUserName(user)}`);',
224+
' }',
225+
].join('\n')
226+
);
227+
228+
await driver.waitForNoErrors();
229+
230+
// delete module to trigger another error
231+
await sandbox.remove('src/model/Role.ts');
232+
233+
// filter-out ts-loader related errors
234+
errors = (await driver.waitForErrors()).filter(
235+
(error) => !error.includes('Module build failed') && !error.includes('Module not found')
236+
);
237+
expect(errors).toEqual([
238+
[
239+
'ERROR in src/model/User.ts 1:22-30',
240+
"TS2307: Cannot find module './Role'.",
241+
" > 1 | import { Role } from './Role';",
242+
' | ^^^^^^^^',
243+
' 2 | ',
244+
' 3 | type User = {',
245+
' 4 | id: string;',
246+
].join('\n'),
247+
]);
248+
249+
// re-create deleted module
250+
await sandbox.write(
251+
'src/model/Role.ts',
252+
['type Role = "admin" | "client";', '', 'export { Role };'].join('\n')
253+
);
254+
255+
// we should receive again the one semantic error but now for "provider" role
256+
errors = await driver.waitForErrors();
257+
expect(errors).toEqual([
258+
[
259+
'ERROR in src/index.ts 34:7-31',
260+
"TS2367: This condition will always return 'false' since the types 'Role' and '\"provider\"' have no overlap.",
261+
' 32 | const user = await login(email, password);',
262+
' 33 | ',
263+
" > 34 | if (user.role === 'provider') {",
264+
' | ^^^^^^^^^^^^^^^^^^^^^^^^',
265+
' 35 | console.log(`Logged in as ${getUserName(user)} [provider].`);',
266+
' 36 | } else {',
267+
' 37 | console.log(`Logged in as ${getUserName(user)}`);',
268+
].join('\n'),
269+
]);
270+
}
271+
);
272+
152273
it.each([
153274
{ async: true, webpack: '^4.0.0', typescript: '2.7.1', tsloader: '^5.0.0' },
154275
{ async: false, webpack: '^4.0.0', typescript: '~3.0.0', tsloader: '^6.0.0' },
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/// package.json
2+
{
3+
"name": "typescript-basic-fixture",
4+
"version": "1.0.0",
5+
"main": "dist/index.js",
6+
"license": "MIT",
7+
"scripts": {
8+
"webpack": "webpack -p",
9+
"webpack-dev-server": "webpack-dev-server"
10+
},
11+
"devDependencies": {
12+
"@babel/core": "^7.10.0",
13+
"@babel/preset-env": "^7.10.0",
14+
"@babel/preset-typescript": "^7.10.0",
15+
"babel-loader": "^8.1.0",
16+
"fork-ts-checker-webpack-plugin": ${FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION},
17+
"typescript": ${TYPESCRIPT_VERSION},
18+
"webpack": ${WEBPACK_VERSION},
19+
"webpack-cli": ${WEBPACK_CLI_VERSION},
20+
"webpack-dev-server": ${WEBPACK_DEV_SERVER_VERSION}
21+
}
22+
}
23+
24+
/// tsconfig.json
25+
{
26+
"compilerOptions": {
27+
"target": "es5",
28+
"module": "commonjs",
29+
"lib": ["ES6", "DOM"],
30+
"moduleResolution": "node",
31+
"esModuleInterop": true,
32+
"skipLibCheck": true,
33+
"skipDefaultLibCheck": true,
34+
"strict": true,
35+
"baseUrl": "./src",
36+
"outDir": "./dist"
37+
},
38+
"include": ["./src"],
39+
"exclude": ["node_modules"]
40+
}
41+
42+
/// .babelrc.js
43+
module.exports = {
44+
presets: [
45+
'@babel/preset-env',
46+
[
47+
'@babel/preset-typescript',
48+
{
49+
onlyRemoveTypeImports: true, // this is important for proper files watching
50+
},
51+
],
52+
],
53+
};
54+
55+
56+
/// webpack.config.js
57+
const path = require('path');
58+
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
59+
60+
module.exports = {
61+
entry: './src/index.ts',
62+
output: {
63+
filename: 'index.js',
64+
path: path.resolve(__dirname, 'dist'),
65+
},
66+
resolve: {
67+
extensions: ['.tsx', '.ts', '.js'],
68+
},
69+
module: {
70+
rules: [
71+
{
72+
test: /\.tsx?$/,
73+
exclude: /node_modules/,
74+
loader: "babel-loader",
75+
},
76+
],
77+
},
78+
plugins: [
79+
new ForkTsCheckerWebpackPlugin({
80+
async: ${ASYNC},
81+
typescript: {
82+
diagnosticOptions: {
83+
semantic: true,
84+
syntactic: true,
85+
},
86+
},
87+
logger: {
88+
infrastructure: 'console'
89+
}
90+
})
91+
]
92+
};

0 commit comments

Comments
 (0)