Compare commits

...

7 Commits

Author SHA1 Message Date
Samuel Degueldre e6c3b62ef0 [REL] v2.2.10
# v2.2.10

 - [IMP] runtime: allow using any class as a type in props validation
 - Update reactivity.md
 - [IMP] owl-vision: Better snippets
 - [FIX] docs: code in example cannot run
2024-04-02 12:25:50 +02:00
Samuel Degueldre 97b69f164f [IMP] runtime: allow using any class as a type in props validation
Previously, we had a fixed whitelist for types that were allowed during
props validation. The implementation however supports using arbitrary
classes, and in practice it's desirable to do so, and already done when
not using typescript (when using typescript, it will error if the class
is not whitelisted), eg in Odoo, we use "Element" for the arch in the
standard view props, but this causes all view controllers to fail type
checking because it's not whitelisted.

This commit simply replaces existing constructors by a generic
constructor type, and adds a test with a validation success and a test
with a validation failure.
2024-03-26 14:10:30 +01:00
Mohamed Alkobrosli 33dfeb1b41 Update reactivity.md
of a of a repeatition, it is fixed by removing "of a"
2024-03-21 23:33:00 +01:00
Arnaud Baes dd292472b9 [IMP] owl-vision: Better snippets
- Adds a basic XML owl template
- Make use of `$TM_FILENAME_BASE` and `$RELATIVE_FILEPATH` and attempt
  to predict the component and template names.

See: https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables
2024-03-14 12:40:37 +01:00
Trịnh Đức Độ 9b18b57fdf [FIX] docs: code in example cannot run 2024-03-14 08:10:42 +01:00
Samuel Degueldre 68f491cd32 [REL] v2.2.9
# v2.2.9

 - [IMP] reactivity: replace sets with small arrays for performance
2024-01-12 15:44:13 +01:00
Samuel Degueldre 7b3e39ba27 [IMP] reactivity: replace sets with small arrays for performance
While Sets have better lookup complexity than arrays, because of the
large constant factors, small arrays can perform better than small sets
when checking for inclusion.

In practice, replacing both of the raw types sets with arrays can
improve performance of reactive-heavy workloads by as much as 30%.

Considering the reactivity code is very hot when rendering data-heavy
components, and the low impact on readability of the fix, the
cost-benefit analysis is clearly in favour of making the fix.
2024-01-12 15:40:14 +01:00
10 changed files with 133 additions and 30 deletions
+2 -2
View File
@@ -152,7 +152,7 @@ This may seem counter-intuitive, but it makes perfect sense in the context of co
```js
class DoubleCounter extends Component {
static template = xml`
<t t-esc="state.selected + ': ' + state[state.selected].value"/>
<t t-esc="'selected: ' + state.selected + ', value: ' + state[state.selected]"/>
<button t-on-click="() => this.state.count1++">increment count 1</button>
<button t-on-click="() => this.state.count2++">increment count 2</button>
<button t-on-click="changeCounter">Switch counter</button>
@@ -193,7 +193,7 @@ to be able to opt out of creating them in the first place. This is the purpose o
### `markRaw`
Marks an object so that it is ignored by the reactivity system, meaning that if this object is ever
part of a of a reactive object, it will be returned as is, and no keys in that object will be
part of a reactive object, it will be returned as is, and no keys in that object will be
observed.
```js
+8 -7
View File
@@ -1850,8 +1850,9 @@ const NO_CALLBACK = () => {
};
const objectToString = Object.prototype.toString;
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
// Use arrays because Array.includes is faster than Set.has for small arrays
const SUPPORTED_RAW_TYPES = ["Object", "Array", "Set", "Map", "WeakMap"];
const COLLECTION_RAW_TYPES = ["Set", "Map", "WeakMap"];
/**
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
* many native objects such as Promise (whose toString is [object Promise])
@@ -1874,7 +1875,7 @@ function canBeMadeReactive(value) {
if (typeof value !== "object") {
return false;
}
return SUPPORTED_RAW_TYPES.has(rawType(value));
return SUPPORTED_RAW_TYPES.includes(rawType(value));
}
/**
* Creates a reactive from the given object/callback if possible and returns it,
@@ -2044,7 +2045,7 @@ function reactive(target, callback = NO_CALLBACK) {
const reactivesForTarget = reactiveCache.get(target);
if (!reactivesForTarget.has(callback)) {
const targetRawType = rawType(target);
const handler = COLLECTION_RAWTYPES.has(targetRawType)
const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
? collectionsProxyHandler(target, callback, targetRawType)
: basicProxyHandler(callback);
const proxy = new Proxy(target, handler);
@@ -5537,7 +5538,7 @@ function compile(template, options = {}) {
}
// do not modify manually. This file is generated by the release script.
const version = "2.2.8";
const version = "2.2.10";
// -----------------------------------------------------------------------------
// Scheduler
@@ -5966,6 +5967,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
export { App, Component, EventBus, OwlError, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
__info__.date = '2024-01-12T10:02:46.131Z';
__info__.hash = '7b454da';
__info__.date = '2024-04-02T10:25:32.577Z';
__info__.hash = '97b69f1';
__info__.url = 'https://github.com/odoo/owl';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.2.8",
"version": "2.2.10",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.2.8",
"version": "2.2.10",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"module": "dist/owl.es.js",
+5 -4
View File
@@ -20,8 +20,9 @@ type CollectionRawType = "Set" | "Map" | "WeakMap";
const objectToString = Object.prototype.toString;
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
// Use arrays because Array.includes is faster than Set.has for small arrays
const SUPPORTED_RAW_TYPES = ["Object", "Array", "Set", "Map", "WeakMap"];
const COLLECTION_RAW_TYPES = ["Set", "Map", "WeakMap"];
/**
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
@@ -45,7 +46,7 @@ function canBeMadeReactive(value: any): boolean {
if (typeof value !== "object") {
return false;
}
return SUPPORTED_RAW_TYPES.has(rawType(value));
return SUPPORTED_RAW_TYPES.includes(rawType(value));
}
/**
* Creates a reactive from the given object/callback if possible and returns it,
@@ -220,7 +221,7 @@ export function reactive<T extends Target>(target: T, callback: Callback = NO_CA
const reactivesForTarget = reactiveCache.get(target)!;
if (!reactivesForTarget.has(callback)) {
const targetRawType = rawType(target);
const handler = COLLECTION_RAWTYPES.has(targetRawType)
const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
? collectionsProxyHandler(target as Collection, callback, targetRawType as CollectionRawType)
: basicProxyHandler<T>(callback);
const proxy = new Proxy(target, handler as ProxyHandler<T>) as Reactive<T>;
+1 -10
View File
@@ -1,16 +1,7 @@
import { OwlError } from "../common/owl_error";
import { toRaw } from "./reactivity";
type BaseType =
| typeof String
| typeof Boolean
| typeof Number
| typeof Date
| typeof Object
| typeof Array
| typeof Function
| true
| "*";
type BaseType = { new (...args: any[]): any } | true | "*";
interface TypeInfo {
type?: TypeDescription;
+1 -1
View File
@@ -1,2 +1,2 @@
// do not modify manually. This file is generated by the release script.
export const version = "2.2.8";
export const version = "2.2.10";
@@ -167,6 +167,45 @@ exports[`props validation can specify that additional props are allowed (object)
}"
`;
exports[`props validation can use custom class as type 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"customObj\\"]);
return function template(ctx, node, key = \\"\\") {
const props1 = {customObj: ctx['customObj']};
helpers.validateProps(\`Child\`, props1, this);
return comp1(props1, key + \`__1\`, node, this, null);
}
}"
`;
exports[`props validation can use custom class as type 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].customObj.val);
}
}"
`;
exports[`props validation can use custom class as type: validation failure 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"customObj\\"]);
return function template(ctx, node, key = \\"\\") {
const props1 = {customObj: ctx['customObj']};
helpers.validateProps(\`Child\`, props1, this);
return comp1(props1, key + \`__1\`, node, this, null);
}
}"
`;
exports[`props validation can validate a prop with multiple types 1`] = `
"function anonymous(app, bdom, helpers
) {
+46
View File
@@ -829,6 +829,52 @@ describe("props validation", () => {
expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'Child': 'message' is missing");
});
test("can use custom class as type", async () => {
class CustomClass {
val = "hey";
}
class Child extends Component {
static props = { customObj: CustomClass };
static template = xml`<t t-esc="props.customObj.val"/>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`<Child customObj="customObj" />`;
customObj = new CustomClass();
}
const app = new App(Parent, { test: true });
await app.mount(fixture);
expect(fixture.innerHTML).toBe("hey");
});
test("can use custom class as type: validation failure", async () => {
class CustomClass {}
class Child extends Component {
static props = { customObj: CustomClass };
static template = xml`<div>hey</div>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`<Child customObj="customObj" />`;
customObj = {};
}
const app = new App(Parent, { test: true });
let error: OwlError | undefined;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
"Invalid props for component 'Child': 'customObj' is not a customclass"
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
"Invalid props for component 'Child': 'customObj' is not a customclass"
);
});
});
//------------------------------------------------------------------------------
+29 -4
View File
@@ -1,18 +1,43 @@
{
"Basic owl component": {
"Basic OWL Component": {
"prefix": "owlcomponent",
"scope": "javascript,typescript",
"body": [
"export class ${1:component-name} extends Component {",
" static template = \"${2:template-name}\";",
"import { Component } from \"@odoo/owl\";",
"",
"class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g}} extends ${2:Component} {",
"",
" static template = \"${3:${RELATIVE_FILEPATH/(.*[\\|\\/])??([a-zA-Z_]+)([\\|\\/]static[\\|\\/].*)/${2}/g}}.${4:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g}}\";",
" static components = {};",
" static props = {};",
"",
" setup() {",
"",
" ${5:super.setup();}",
" }",
"",
" ${6:// Do Something}",
"}",
""
],
"description": "The starting base for an owl component"
},
"Basic OWL Template": {
"prefix": "owltemplate",
"scope": "xml",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>",
"",
"<templates xml:space=\"preserve\">",
"",
" <t t-name=\"${2:${RELATIVE_FILEPATH/(.*[\\|\\/])??([a-zA-Z_]+)([\\|\\/]static[\\|\\/].*)/${2}/g}}.${3:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g}}\">",
" ${3:<h1>Hello World</h1>}",
" </t>",
"",
"</templates>",
""
],
"description": "Generate a basic OWL template XML file"
}
}