diff --git a/src/compiler/index.ts b/src/compiler/index.ts
index 6a0b6379..d79e9534 100644
--- a/src/compiler/index.ts
+++ b/src/compiler/index.ts
@@ -2,6 +2,7 @@ import type { TemplateSet } from "../runtime/template_set";
import type { BDom } from "../runtime/blockdom";
import { CodeGenerator, Config } from "./code_generator";
import { parse } from "./parser";
+import { OwlError } from "../runtime";
export type Template = (context: any, vnode: any, key?: string) => BDom;
@@ -27,5 +28,15 @@ export function compile(
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
const code = codeGenerator.generateCode();
// template function
- return new Function("app, bdom, helpers", code) as TemplateFunction;
+ try {
+ return new Function("app, bdom, helpers", code) as TemplateFunction;
+ } catch (originalError: any) {
+ const { name } = options;
+ const nameStr = name ? `template "${name}"` : "anonymous template";
+ const err = new OwlError(
+ `Failed to compile ${nameStr}: ${originalError.message}\n\ngenerated code:\nfunction(app, bdom, helpers) {\n${code}\n}`
+ );
+ err.cause = originalError;
+ throw err;
+ }
}
diff --git a/tests/compiler/validation.test.ts b/tests/compiler/validation.test.ts
index c57925d4..e90eaf9e 100644
--- a/tests/compiler/validation.test.ts
+++ b/tests/compiler/validation.test.ts
@@ -37,4 +37,22 @@ describe("basic validation", () => {
const template = `
test
`;
expect(() => renderToString(template)).toThrow("Unknown QWeb directive: 't-best-beer'");
});
+
+ test("compilation error", () => {
+ const template = `test
`;
+ expect(() => renderToString(template))
+ .toThrow(`Failed to compile anonymous template: Unexpected identifier
+
+generated code:
+function(app, bdom, helpers) {
+ let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+
+ let block1 = createBlock(\`test
\`);
+
+ return function template(ctx, node, key = "") {
+ let attr1 = ctx['a']ctx['b'];
+ return block1([attr1]);
+ }
+}`);
+ });
});
diff --git a/tests/components/__snapshots__/error_handling.test.ts.snap b/tests/components/__snapshots__/error_handling.test.ts.snap
index b4417a9f..e326292d 100644
--- a/tests/components/__snapshots__/error_handling.test.ts.snap
+++ b/tests/components/__snapshots__/error_handling.test.ts.snap
@@ -12,6 +12,18 @@ exports[`basics display a nice error if a component is not a component 1`] = `
}"
`;
+exports[`basics display a nice error if a non-root component template fails to compile 1`] = `
+"function anonymous(app, bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+ const comp1 = app.createComponent(\`Child\`, true, false, false, []);
+
+ return function template(ctx, node, key = \\"\\") {
+ return comp1({}, key + \`__1\`, node, this, null);
+ }
+}"
+`;
+
exports[`basics display a nice error if it cannot find component (in dev mode) 1`] = `
"function anonymous(app, bdom, helpers
) {
diff --git a/tests/components/error_handling.test.ts b/tests/components/error_handling.test.ts
index a24d4f9e..a444125c 100644
--- a/tests/components/error_handling.test.ts
+++ b/tests/components/error_handling.test.ts
@@ -143,6 +143,66 @@ describe("basics", () => {
);
});
+ test("display a nice error if the root component template fails to compile", async () => {
+ // This is a special case: mount throws synchronously and we don't have any
+ // node which can handle the error, hence the different structure of this test
+ class Comp extends Component {
+ static template = xml`test
`;
+ }
+ const app = new App(Comp);
+ let error: Error;
+ try {
+ await app.mount(fixture);
+ } catch (e) {
+ error = e as Error;
+ }
+ const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier
+
+generated code:
+function(app, bdom, helpers) {
+ let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+
+ let block1 = createBlock(\`test
\`);
+
+ return function template(ctx, node, key = "") {
+ let attr1 = ctx['a']ctx['b'];
+ return block1([attr1]);
+ }
+}`;
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe(expectedErrorMessage);
+ });
+
+ test("display a nice error if a non-root component template fails to compile", async () => {
+ class Child extends Component {
+ static template = xml`test
`;
+ }
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml``;
+ }
+ const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier
+
+generated code:
+function(app, bdom, helpers) {
+ let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+
+ let block1 = createBlock(\`test
\`);
+
+ return function template(ctx, node, key = "") {
+ let attr1 = ctx['a']ctx['b'];
+ return block1([attr1]);
+ }
+}`;
+ const app = new App(Parent as typeof Component);
+ let error: Error;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow(expectedErrorMessage);
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe(expectedErrorMessage);
+ });
+
test("simple catchError", async () => {
class Boom extends Component {
static template = xml``;
diff --git a/tests/misc/portal.test.ts b/tests/misc/portal.test.ts
index b5c230b4..8e5d0e87 100644
--- a/tests/misc/portal.test.ts
+++ b/tests/misc/portal.test.ts
@@ -988,7 +988,7 @@ describe("Portal: Props validation", () => {
error = e as Error;
}
expect(error!).toBeDefined();
- expect(error!.message).toBe(`Unexpected token ','`);
+ expect(error!.message).toContain(`Unexpected token ','`);
});
test("target must be a valid selector", async () => {