Skip to content
This repository was archived by the owner on Aug 7, 2021. It is now read-only.

Commit 363c4da

Browse files
committed
fix: handle correctly webpack compilation errors
Webpack doesn't notify NativeScript CLI when there is a compilation error. So, NativeScript CLI build the app, install it on device and start it. In most cases the application crashes runtime as the webpack compilcation is not successful. Most probably there would be a red/yellow message in the console printed during the compilation. The users don't see the error message as there is too much log outputed in the console. They don't understand the exact reason why their app crashes runtime. Webpack has a mechanism to skip the emitting phase whenever there are errors while compiling. This can be achieved using `optimization.noEmitOnErrors` property as it is described [here](https://webpack.js.org/configuration/optimization/#optimizationnoemitonerrors). This PR adds `noEmitOnErrors` property in all webpack.config files: * The default value is based on `noEmitOnError` property from `tsconfig.json` for `angular` and `typescript` projects * The default value is `true` for `javascript` and `vue` projects. Also this PR fixes the following problems: 1. Check for syntactic errors when running webpack compilation in ts projects Currently `ts-loader` is started in `transpileOnly` mode and webpack plugin (`ForkTsCheckerWebpackPlugin`) runs TypeScript type checker on a separate process in order to report for compilation errors. By default the plugin only checks for semantic errors and adds them to `compilation.errors` as can be seen [here](). On the other side, webpack relies on [compilation.errors]() array when deciding if should skip emitting phase. However, `ts-loader` used in `transpileOnly` mode still reports syntactic errors but adds them to `compilation.warnings`. This is a problem, as actually the compilation is not stopped when there is a syntactic error. Setting `checkSyntacticErrors: true` will ensure that `ForkTsCheckerWebpackPlugin` will check for both syntactic and semantic errors and after that will be added to `compilation.errors`. 2. Respect `noEmitOnError` from `tsconfig.json` when compiling in `ts` projects The problem is in `ForkTsCheckerWebpackPlugin` and in the way it is integrated with webpack hooks - TypeStrong/fork-ts-checker-webpack-plugin#337. 3. Send the hash of compilation to NativeScript CLI The `hmr` generates new hot-update files on every change and the hash of the next hmr update is written inside hot-update.json file. Although webpack doesn't emit any files, hmr hash is still generated. The hash is generated per compilation no matter if files will be emitted or not. This way, the first successful compilation after fixing the compilation error generates a hash that is not the same as the one expected in the latest emitted hot-update.json file. As a result, the hmr chain is broken and the changes are not applied. Rel to: NativeScript/nativescript-cli#3785
1 parent cf65ac1 commit 363c4da

8 files changed

+43
-15
lines changed

plugins/WatchStateLoggerPlugin.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,14 @@ export class WatchStateLoggerPlugin {
2929
console.log(messages.compilationComplete);
3030
}
3131

32-
let emittedFiles = Object
32+
const emittedFiles = Object
3333
.keys(compilation.assets)
3434
.filter(assetKey => compilation.assets[assetKey].emitted);
3535

3636
const chunkFiles = getChunkFiles(compilation);
37-
3837
process.send && process.send(messages.compilationComplete, error => null);
3938
// Send emitted files so they can be LiveSynced if need be
40-
process.send && process.send({ emittedFiles, chunkFiles }, error => null);
39+
process.send && process.send({ emittedFiles, chunkFiles, hash: compilation.hash }, error => null);
4140
});
4241
}
4342
}

templates/webpack.angular.js

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { nsReplaceBootstrap } = require("nativescript-dev-webpack/transformers/ns
77
const { nsReplaceLazyLoader } = require("nativescript-dev-webpack/transformers/ns-replace-lazy-loader");
88
const { nsSupportHmrNg } = require("nativescript-dev-webpack/transformers/ns-support-hmr-ng");
99
const { getMainModulePath } = require("nativescript-dev-webpack/utils/ast-utils");
10+
const { getNoEmitOnErrorFromTSConfig } = require("nativescript-dev-webpack/utils/tsconfig-utils");
1011
const CleanWebpackPlugin = require("clean-webpack-plugin");
1112
const CopyWebpackPlugin = require("copy-webpack-plugin");
1213
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
@@ -107,6 +108,8 @@ module.exports = env => {
107108
itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "build", "configurations", "nativescript-android-snapshot")}`);
108109
}
109110

111+
const noEmitOnErrorFromTSConfig = getNoEmitOnErrorFromTSConfig(join(projectRoot, tsConfigName));
112+
110113
nsWebpack.processAppComponents(appComponents, platform);
111114
const config = {
112115
mode: production ? "production" : "development",
@@ -158,6 +161,7 @@ module.exports = env => {
158161
devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
159162
optimization: {
160163
runtimeChunk: "single",
164+
noEmitOnErrors: noEmitOnErrorFromTSConfig,
161165
splitChunks: {
162166
cacheGroups: {
163167
vendor: {

templates/webpack.config.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const webpackConfigAngular = proxyquire('./webpack.angular', {
4848
'nativescript-dev-webpack/transformers/ns-replace-lazy-loader': { nsReplaceLazyLoader: () => { return FakeLazyTransformerFlag } },
4949
'nativescript-dev-webpack/transformers/ns-support-hmr-ng': { nsSupportHmrNg: () => { return FakeHmrTransformerFlag } },
5050
'nativescript-dev-webpack/utils/ast-utils': { getMainModulePath: () => { return "fakePath"; } },
51+
'nativescript-dev-webpack/utils/tsconfig-utils': { getNoEmitOnErrorFromTSConfig: () => { return false; } },
5152
'nativescript-dev-webpack/plugins/NativeScriptAngularCompilerPlugin': { getAngularCompilerPlugin: () => { return AngularCompilerStub; } },
5253
'@ngtools/webpack': {
5354
AngularCompilerPlugin: AngularCompilerStub
@@ -58,6 +59,7 @@ const webpackConfigAngular = proxyquire('./webpack.angular', {
5859
const webpackConfigTypeScript = proxyquire('./webpack.typescript', {
5960
'nativescript-dev-webpack': nativeScriptDevWebpack,
6061
'nativescript-dev-webpack/nativescript-target': emptyObject,
62+
'nativescript-dev-webpack/utils/tsconfig-utils': { getNoEmitOnErrorFromTSConfig: () => { return false; } },
6163
'terser-webpack-plugin': TerserJsStub
6264
});
6365

templates/webpack.javascript.js

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ module.exports = env => {
121121
devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
122122
optimization: {
123123
runtimeChunk: "single",
124+
noEmitOnErrors: true,
124125
splitChunks: {
125126
cacheGroups: {
126127
vendor: {

templates/webpack.typescript.js

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const { join, relative, resolve, sep } = require("path");
33
const webpack = require("webpack");
44
const nsWebpack = require("nativescript-dev-webpack");
55
const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target");
6+
const { getNoEmitOnErrorFromTSConfig } = require("nativescript-dev-webpack/utils/tsconfig-utils");
67
const CleanWebpackPlugin = require("clean-webpack-plugin");
78
const CopyWebpackPlugin = require("copy-webpack-plugin");
89
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
@@ -71,6 +72,8 @@ module.exports = env => {
7172
itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "build", "configurations", "nativescript-android-snapshot")}`);
7273
}
7374

75+
const noEmitOnErrorFromTSConfig = getNoEmitOnErrorFromTSConfig(tsConfigPath);
76+
7477
nsWebpack.processAppComponents(appComponents, platform);
7578
const config = {
7679
mode: production ? "production" : "development",
@@ -124,6 +127,7 @@ module.exports = env => {
124127
devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
125128
optimization: {
126129
runtimeChunk: "single",
130+
noEmitOnErrors: noEmitOnErrorFromTSConfig,
127131
splitChunks: {
128132
cacheGroups: {
129133
vendor: {
@@ -253,6 +257,7 @@ module.exports = env => {
253257
tsconfig: tsConfigPath,
254258
async: false,
255259
useTypescriptIncrementalApi: true,
260+
checkSyntacticErrors: true,
256261
memoryLimit: 4096
257262
})
258263
],

templates/webpack.vue.js

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ module.exports = env => {
130130
devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"),
131131
optimization: {
132132
runtimeChunk: "single",
133+
noEmitOnErrors: true,
133134
splitChunks: {
134135
cacheGroups: {
135136
vendor: {

utils/ast-utils.ts

+3-12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { dirname, join, relative } from "path";
1818
import * as ts from "typescript";
1919
import { readFileSync, existsSync } from "fs";
2020
import { collectDeepNodes } from "@ngtools/webpack/src/transformers";
21+
import { getCompilerOptionsFromTSConfig } from "./tsconfig-utils";
2122

2223
export function getMainModulePath(entryFilePath: string, tsConfigName: string) {
2324
try {
@@ -43,23 +44,13 @@ export function getMainModulePath(entryFilePath: string, tsConfigName: string) {
4344
function tsResolve(moduleName: string, containingFilePath: string, tsConfigName: string) {
4445
let result = moduleName;
4546
try {
46-
const parseConfigFileHost: ts.ParseConfigFileHost = {
47-
getCurrentDirectory: ts.sys.getCurrentDirectory,
48-
useCaseSensitiveFileNames: false,
49-
readDirectory: ts.sys.readDirectory,
50-
fileExists: ts.sys.fileExists,
51-
readFile: ts.sys.readFile,
52-
onUnRecoverableConfigFileDiagnostic: undefined
53-
};
54-
55-
const tsConfig = ts.getParsedCommandLineOfConfigFile(tsConfigName, ts.getDefaultCompilerOptions(), parseConfigFileHost);
56-
57-
const compilerOptions: ts.CompilerOptions = tsConfig.options || ts.getDefaultCompilerOptions();
5847
const moduleResolutionHost: ts.ModuleResolutionHost = {
5948
fileExists: ts.sys.fileExists,
6049
readFile: ts.sys.readFile
6150
};
6251

52+
const compilerOptions = getCompilerOptionsFromTSConfig(tsConfigName);
53+
6354
const resolutionResult = ts.resolveModuleName(moduleName, containingFilePath, compilerOptions, moduleResolutionHost);
6455

6556
if (resolutionResult && resolutionResult.resolvedModule && resolutionResult.resolvedModule.resolvedFileName) {

utils/tsconfig-utils.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as ts from "typescript";
2+
3+
export function getCompilerOptionsFromTSConfig(tsConfigPath: string): ts.CompilerOptions {
4+
const parseConfigFileHost: ts.ParseConfigFileHost = {
5+
getCurrentDirectory: ts.sys.getCurrentDirectory,
6+
useCaseSensitiveFileNames: false,
7+
readDirectory: ts.sys.readDirectory,
8+
fileExists: ts.sys.fileExists,
9+
readFile: ts.sys.readFile,
10+
onUnRecoverableConfigFileDiagnostic: undefined
11+
};
12+
13+
const tsConfig = ts.getParsedCommandLineOfConfigFile(tsConfigPath, ts.getDefaultCompilerOptions(), parseConfigFileHost);
14+
15+
const compilerOptions: ts.CompilerOptions = tsConfig.options || ts.getDefaultCompilerOptions();
16+
17+
return compilerOptions;
18+
}
19+
20+
export function getNoEmitOnErrorFromTSConfig(tsConfigPath: string): boolean {
21+
const compilerOptions = getCompilerOptionsFromTSConfig(tsConfigPath);
22+
const noEmitOnError = !!compilerOptions.noEmitOnError;
23+
24+
return noEmitOnError;
25+
}

0 commit comments

Comments
 (0)