Skip to content

fix: handle correctly the compilation errors from webpack #5039

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

Merged
merged 1 commit into from
Sep 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions lib/services/webpack/webpack-compiler-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { WEBPACK_COMPILATION_COMPLETE } from "../../constants";

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

constructor(
private $childProcess: IChildProcess,
Expand Down Expand Up @@ -38,13 +39,14 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
if (message.emittedFiles) {
if (isFirstWebpackWatchCompilation) {
isFirstWebpackWatchCompilation = false;
this.expectedHash = message.hash;
return;
}

let result;

if (prepareData.hmr) {
result = this.getUpdatedEmittedFiles(message.emittedFiles, message.chunkFiles);
result = this.getUpdatedEmittedFiles(message.emittedFiles, message.chunkFiles, message.hash);
} else {
result = { emittedFiles: message.emittedFiles, fallbackFiles: <string[]>[], hash: "" };
}
Expand Down Expand Up @@ -228,11 +230,24 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
return args;
}

private getUpdatedEmittedFiles(allEmittedFiles: string[], chunkFiles: string[]) {
const hotHash = this.getCurrentHotUpdateHash(allEmittedFiles);
const emittedHotUpdateFiles = _.difference(allEmittedFiles, chunkFiles);
public getUpdatedEmittedFiles(allEmittedFiles: string[], chunkFiles: string[], nextHash: string) {
const currentHash = this.getCurrentHotUpdateHash(allEmittedFiles);

return { emittedFiles: emittedHotUpdateFiles, fallbackFiles: chunkFiles, hash: hotHash };
// This logic is needed as there are already cases when webpack doesn't emit any files physically.
// We've set noEmitOnErrors in webpack.config.js based on noEmitOnError from tsconfig.json,
// so webpack doesn't emit any files when noEmitOnErrors: true is set in webpack.config.js and
// there is a compilation error in the source code. On the other side, 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.
const isHashValid = nextHash ? this.expectedHash === currentHash : true;
this.expectedHash = nextHash;

const emittedHotUpdatesAndAssets = isHashValid ? _.difference(allEmittedFiles, chunkFiles) : allEmittedFiles;

return { emittedFiles: emittedHotUpdatesAndAssets, fallbackFiles: chunkFiles, hash: currentHash };
}

private getCurrentHotUpdateHash(emittedFiles: string[]) {
Expand Down
1 change: 1 addition & 0 deletions lib/services/webpack/webpack.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ declare global {
interface IWebpackEmitMessage {
emittedFiles: string[];
chunkFiles: string[];
hash: string;
}

interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectServiceBase {
Expand Down
71 changes: 71 additions & 0 deletions test/services/webpack/webpack-compiler-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Yok } from "../../../lib/common/yok";
import { WebpackCompilerService } from "../../../lib/services/webpack/webpack-compiler-service";
import { assert } from "chai";

const chunkFiles = ["bundle.js", "runtime.js", "vendor.js"];

function getAllEmittedFiles(hash: string) {
return ["bundle.js", "runtime.js", `bundle.${hash}.hot-update.js`, `${hash}.hot-update.json`];
}

function createTestInjector(): IInjector {
const testInjector = new Yok();
testInjector.register("webpackCompilerService", WebpackCompilerService);
testInjector.register("childProcess", {});
testInjector.register("hooksService", {});
testInjector.register("hostInfo", {});
testInjector.register("logger", {});
testInjector.register("mobileHelper", {});
testInjector.register("cleanupService", {});

return testInjector;
}

describe("WebpackCompilerService", () => {
let testInjector: IInjector = null;
let webpackCompilerService: WebpackCompilerService = null;

beforeEach(() => {
testInjector = createTestInjector();
webpackCompilerService = testInjector.resolve(WebpackCompilerService);
});

describe("getUpdatedEmittedFiles", () => {
// backwards compatibility with old versions of nativescript-dev-webpack
it("should return only hot updates when nextHash is not provided", async () => {
const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, null);
const expectedEmittedFiles = ['bundle.hash1.hot-update.js', 'hash1.hot-update.json'];

assert.deepEqual(result.emittedFiles, expectedEmittedFiles);
});
// 2 successful webpack compilations
it("should return only hot updates when nextHash is provided", async () => {
webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2");
const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash2"), chunkFiles, "hash3");

assert.deepEqual(result.emittedFiles, ['bundle.hash2.hot-update.js', 'hash2.hot-update.json']);
});
// 1 successful webpack compilation, n compilations with no emitted files
it("should return all files when there is a webpack compilation with no emitted files", () => {
webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2");
const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash4"), chunkFiles, "hash5");

assert.deepEqual(result.emittedFiles, ['bundle.js', 'runtime.js', 'bundle.hash4.hot-update.js', 'hash4.hot-update.json']);
});
// 1 successful webpack compilation, n compilations with no emitted files, 1 successful webpack compilation
it("should return only hot updates after fixing the compilation error", () => {
webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2");
webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash5"), chunkFiles, "hash6");
const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash6"), chunkFiles, "hash7");

assert.deepEqual(result.emittedFiles, ['bundle.hash6.hot-update.js', 'hash6.hot-update.json']);
});
// 1 webpack compilation with no emitted files
it("should return all files when first compilation on livesync change is not successful", () => {
(<any>webpackCompilerService).expectedHash = "hash1";
const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2");

assert.deepEqual(result.emittedFiles, ["bundle.hash1.hot-update.js", "hash1.hot-update.json"]);
});
});
});