Skip to content

Commit 6204cbe

Browse files
committed
fix: handle correctly the compilation errors from webpack
More info can be found here NativeScript/nativescript-dev-webpack#1051. Rel to: #3785
1 parent 75c9ea0 commit 6204cbe

File tree

3 files changed

+92
-5
lines changed

3 files changed

+92
-5
lines changed

lib/services/webpack/webpack-compiler-service.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { WEBPACK_COMPILATION_COMPLETE } from "../../constants";
77

88
export class WebpackCompilerService extends EventEmitter implements IWebpackCompilerService {
99
private webpackProcesses: IDictionary<child_process.ChildProcess> = {};
10+
private expectedHash: string = null;
1011

1112
constructor(
1213
private $childProcess: IChildProcess,
@@ -38,13 +39,14 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
3839
if (message.emittedFiles) {
3940
if (isFirstWebpackWatchCompilation) {
4041
isFirstWebpackWatchCompilation = false;
42+
this.expectedHash = message.hash;
4143
return;
4244
}
4345

4446
let result;
4547

4648
if (prepareData.hmr) {
47-
result = this.getUpdatedEmittedFiles(message.emittedFiles, message.chunkFiles);
49+
result = this.getUpdatedEmittedFiles(message.emittedFiles, message.chunkFiles, message.hash);
4850
} else {
4951
result = { emittedFiles: message.emittedFiles, fallbackFiles: <string[]>[], hash: "" };
5052
}
@@ -221,11 +223,24 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
221223
return args;
222224
}
223225

224-
private getUpdatedEmittedFiles(allEmittedFiles: string[], chunkFiles: string[]) {
225-
const hotHash = this.getCurrentHotUpdateHash(allEmittedFiles);
226-
const emittedHotUpdateFiles = _.difference(allEmittedFiles, chunkFiles);
226+
public getUpdatedEmittedFiles(allEmittedFiles: string[], chunkFiles: string[], nextHash: string) {
227+
const currentHash = this.getCurrentHotUpdateHash(allEmittedFiles);
227228

228-
return { emittedFiles: emittedHotUpdateFiles, fallbackFiles: chunkFiles, hash: hotHash };
229+
// This logic is needed as there are already cases when webpack doesn't emit any files physically.
230+
// We've set noEmitOnErrors in webpack.config.js based on noEmitOnError from tsconfig.json,
231+
// so webpack doesn't emit any files when noEmitOnErrors: true is set in webpack.config.js and
232+
// there is a compilation error in the source code. On the other side, hmr generates new hot-update files
233+
// on every change and the hash of the next hmr update is written inside hot-update.json file.
234+
// Although webpack doesn't emit any files, hmr hash is still generated. The hash is generated per compilation no matter
235+
// if files will be emitted or not. This way, the first successful compilation after fixing the compilation error generates
236+
// a hash that is not the same as the one expected in the latest emitted hot-update.json file.
237+
// As a result, the hmr chain is broken and the changes are not applied.
238+
const isHashValid = nextHash ? this.expectedHash === currentHash : true;
239+
this.expectedHash = nextHash;
240+
241+
const emittedHotUpdatesAndAssets = isHashValid ? _.difference(allEmittedFiles, chunkFiles) : allEmittedFiles;
242+
243+
return { emittedFiles: emittedHotUpdatesAndAssets, fallbackFiles: chunkFiles, hash: currentHash };
229244
}
230245

231246
private getCurrentHotUpdateHash(emittedFiles: string[]) {

lib/services/webpack/webpack.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ declare global {
3535
interface IWebpackEmitMessage {
3636
emittedFiles: string[];
3737
chunkFiles: string[];
38+
hash: string;
3839
}
3940

4041
interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectServiceBase {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { Yok } from "../../../lib/common/yok";
2+
import { WebpackCompilerService } from "../../../lib/services/webpack/webpack-compiler-service";
3+
import { assert } from "chai";
4+
5+
const chunkFiles = ["bundle.js", "runtime.js", "vendor.js"];
6+
7+
function getAllEmittedFiles(hash: string) {
8+
return ["bundle.js", "runtime.js", `bundle.${hash}.hot-update.js`, `${hash}.hot-update.json`];
9+
}
10+
11+
function createTestInjector(): IInjector {
12+
const testInjector = new Yok();
13+
testInjector.register("webpackCompilerService", WebpackCompilerService);
14+
testInjector.register("childProcess", {});
15+
testInjector.register("hooksService", {});
16+
testInjector.register("hostInfo", {});
17+
testInjector.register("logger", {});
18+
testInjector.register("mobileHelper", {});
19+
testInjector.register("cleanupService", {});
20+
21+
return testInjector;
22+
}
23+
24+
describe("WebpackCompilerService", () => {
25+
let testInjector: IInjector = null;
26+
let webpackCompilerService: WebpackCompilerService = null;
27+
28+
beforeEach(() => {
29+
testInjector = createTestInjector();
30+
webpackCompilerService = testInjector.resolve(WebpackCompilerService);
31+
});
32+
33+
describe("getUpdatedEmittedFiles", () => {
34+
// backwards compatibility with old versions of nativescript-dev-webpack
35+
it("should return only hot updates when nextHash is not provided", async () => {
36+
const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, null);
37+
const expectedEmittedFiles = ['bundle.hash1.hot-update.js', 'hash1.hot-update.json'];
38+
39+
assert.deepEqual(result.emittedFiles, expectedEmittedFiles);
40+
});
41+
// 2 successful webpack compilations
42+
it("should return only hot updates when nextHash is provided", async () => {
43+
webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2");
44+
const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash2"), chunkFiles, "hash3");
45+
46+
assert.deepEqual(result.emittedFiles, ['bundle.hash2.hot-update.js', 'hash2.hot-update.json']);
47+
});
48+
// 1 successful webpack compilation, n compilations with no emitted files
49+
it("should return all files when there is a webpack compilation with no emitted files", () => {
50+
webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2");
51+
const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash4"), chunkFiles, "hash5");
52+
53+
assert.deepEqual(result.emittedFiles, ['bundle.js', 'runtime.js', 'bundle.hash4.hot-update.js', 'hash4.hot-update.json']);
54+
});
55+
// 1 successful webpack compilation, n compilations with no emitted files, 1 successful webpack compilation
56+
it("should return only hot updates after fixing the compilation error", () => {
57+
webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2");
58+
webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash5"), chunkFiles, "hash6");
59+
const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash6"), chunkFiles, "hash7");
60+
61+
assert.deepEqual(result.emittedFiles, ['bundle.hash6.hot-update.js', 'hash6.hot-update.json']);
62+
});
63+
// 1 webpack compilation with no emitted files
64+
it("should return all files when first compilation on livesync change is not successful", () => {
65+
(<any>webpackCompilerService).expectedHash = "hash1";
66+
const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2");
67+
68+
assert.deepEqual(result.emittedFiles, ["bundle.hash1.hot-update.js", "hash1.hot-update.json"]);
69+
});
70+
});
71+
});

0 commit comments

Comments
 (0)