[DOC] large refactoring, add section on starting project

This commit is contained in:
Géry Debongnie
2020-02-02 09:13:54 +01:00
parent 50b116c56d
commit 8866905f33
17 changed files with 702 additions and 402 deletions
+12 -152
View File
@@ -1,4 +1,4 @@
<h1 align="center">🦉 <a href="https://odoo.github.io/owl/">OWL: the Odoo Web Library</a> 🦉</h1>
<h1 align="center">🦉 <a href="https://odoo.github.io/owl/">OWL Framework</a> 🦉</h1>
_Class based components with hooks, reactive state and concurrent mode_
@@ -11,11 +11,11 @@ simple and consistent way. Owl's main features are:
- a declarative component system,
- a reactivity system based on hooks,
- a store implementation (for state management),
- a small frontend router
- concurrent mode by default,
- a store and a frontend router
Owl components are defined with ES6 classes, they use QWeb templates, an
underlying virtual dom, integrates beautifully with hooks, and the rendering is
underlying virtual DOM, integrates beautifully with hooks, and the rendering is
asynchronous.
**Try it online!** An online playground is available at
@@ -29,12 +29,12 @@ Owl is currently stable. Possible future changes are explained in the
## Why Owl?
Why did Odoo decide to make Yet Another Framework? This is really a question
that deserves [a long answer](doc/why_owl.md). But in short, we believe that
that deserves [a long answer](doc/miscellaneous/why_owl.md). But in short, we believe that
while the current state of the art frameworks are excellent, they are not
optimized for our use case, and there is still room for something else.
If you are interested in a comparison with React or Vue, you will
find some more additional information [here](doc/comparison.md).
find some more additional information [here](doc/miscellaneous/comparison.md).
## Example
@@ -92,7 +92,8 @@ requirements are common and code needs to be maintained by large teams.
Owl is not designed to be fast nor small (even though it is quite good on those
two topics). It is a no nonsense framework to build applications. There is only
one way to define components (with classes).
one way to define components (with classes). There is no black magic. It just
works.
## Documentation
@@ -101,19 +102,18 @@ A complete documentation for Owl can be found here:
- [Main documentation page](doc/readme.md).
The most important sections are:
Some of the most important pages are:
- [Tutorial: TodoList application](doc/learning/tutorial_todoapp.md)
- [How to start an Owl project](doc/learning/quick_start.md)
- [QWeb templating language](doc/reference/qweb_templating_language.md)
- [Component](doc/reference/component.md)
- [Hooks](doc/reference/hooks.md)
Found an issue in the documentation? A broken link? Some outdated information?
Submit a PR!
## Installing/Building
## Installing Owl
Owl can be installed with the following command:
Owl is available on `npm` and can be installed with the following command:
```
npm install @odoo/owl
@@ -124,146 +124,6 @@ If you want to use a simple `<script>` tag, the last release can be downloaded h
- [owl-1.0.4.js](https://github.com/odoo/owl/releases/download/v1.0.4/owl.js)
- [owl-1.0.4.min.js](https://github.com/odoo/owl/releases/download/v1.0.4/owl.min.js)
Some npm scripts are available:
| Command | Description |
| ---------------- | -------------------------------------------------- |
| `npm install` | install every dependency required for this project |
| `npm run build` | build a bundle of _owl_ in the _/dist/_ folder |
| `npm run minify` | minify the prebuilt owl.js file |
| `npm run test` | run all (owl) tests |
## Quick Overview
Owl components in an application are used to define a (dynamic) tree of components.
```
Root
/ \
A B
/ \
C D
```
**State:** each component can manage its own local state. It is a simple ES6
class, there are no special rules:
```js
class Counter extends Component {
static template = xml`
<button t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>`;
state = { value: 0 };
increment() {
this.state.value++;
this.render();
}
}
```
The example above shows a component with a local state. Note that since there
is nothing magical to the `state` object, we need to manually call the `render`
function whenever we update it. This can quickly become annoying (and not
efficient if we do it too much). There is a better way: using the `useState`
hook, which transforms an object into a reactive version of itself:
```js
const { useState } = owl.hooks;
class Counter extends Component {
static template = xml`
<button t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>`;
state = useState({ value: 0 });
increment() {
this.state.value++;
}
}
```
Note that the `t-on-click` handler can even be replaced by an inline statement:
```xml
<button t-on-click="state.value++">
```
**Props:** sub components often needs some information from their parents. This
is done by adding the required information to the template. This will then be
accessible by the sub component in the `props` object. Note that there is an
important rule here: the information contained in the `props` object is not
owned by the sub component, and should never be modified.
```js
class Child extends Component {
static template = xml`<div>Hello <t t-esc="props.name"/></div>`;
}
class Parent extends Component {
static template = xml`
<div>
<Child name="'Owl'" />
<Child name="'Framework'" />
</div>`;
static components = { Child };
}
```
**Communication:** there are multiple ways to communicate information between
components. However, the two most important ways are the following:
- from parent to children: by using `props`,
- from a children to one of its parent: by triggering events.
The following example illustrate both mechanisms:
```js
class OrderLine extends Component {
static template = xml`
<div t-on-click="add">
<div><t t-esc="props.line.name"/></div>
<div>Quantity: <t t-esc="props.line.quantity"/></div>
</div>`;
add() {
this.trigger("add-to-order", { line: props.line });
}
}
class Parent extends Component {
static template = xml`
<div t-on-add-to-order="addToOrder">
<OrderLine
t-foreach="orders"
t-as="line"
line="line" />
</div>`;
static components = { OrderLine };
orders = useState([{ id: 1, name: "Coffee", quantity: 0 }, { id: 2, name: "Tea", quantity: 0 }]);
addToOrder(event) {
const line = event.detail.line;
line.quantity++;
}
}
```
In this example, the `OrderLine` component trigger a `add-to-order` event. This
will generate a DOM event which will bubble along the DOM tree. It will then be
intercepted by the parent component, which will then get the line (from the
`detail` key) and then increment its quantity. See the section on [event handling](doc/reference/component.md#event-handling)
for more details on how events work.
Note that this example would have also worked if the `OrderLine` component
directly modifies the `line` object. However, this is not a good practice: this
only works because the `props` object received by the child component is reactive,
so the child component is then coupled to the parents implementation.
## License
OWL is [LGPL licensed](./LICENSE).
+44
View File
@@ -0,0 +1,44 @@
# 🦉 How to debug Owl applications 🦉
Non trivial applications become quickly more difficult to understand. It is then
useful to have a solid understanding of what is going on. To help with that,
logging useful information is extremely valuable. There is a [javascript file](../../tools/debug.js) which can be evaluated in an application.
Once it is executed, it will log a lot of information on each component main hooks. The following code is a minified version to make it easier to copy/paste:
```
function debugOwl(t,e){let n,o="[OWL_DEBUG]";function r(t){let e;try{e=JSON.stringify(t||{})}catch(t){e="<JSON error>"}return e.length>200&&(e=e.slice(0,200)+"..."),e}if(Object.defineProperty(t.Component,"current",{get:()=>n,set(s){n=s;const i=s.constructor.name;if(e.componentBlackList&&e.componentBlackList.test(i))return;if(e.componentWhiteList&&!e.componentWhiteList.test(i))return;let l;Object.defineProperty(n,"__owl__",{get:()=>l,set(n){!function(n,s,i){let l=`${s}<id=${i}>`,c=t=>console.log(`${o} ${l} ${t}`),u=t=>(!e.methodBlackList||!e.methodBlackList.includes(t))&&!(e.methodWhiteList&&!e.methodWhiteList.includes(t));u("constructor")&&c(`constructor, props=${r(n.props)}`);u("willStart")&&t.hooks.onWillStart(()=>{c("willStart")});u("mounted")&&t.hooks.onMounted(()=>{c("mounted")});u("willUpdateProps")&&t.hooks.onWillUpdateProps(t=>{c(`willUpdateProps, nextprops=${r(t)}`)});u("willPatch")&&t.hooks.onWillPatch(()=>{c("willPatch")});u("patched")&&t.hooks.onPatched(()=>{c("patched")});u("willUnmount")&&t.hooks.onWillUnmount(()=>{c("willUnmount")});const d=n.__render.bind(n);n.__render=function(...t){c("rendering template"),d(...t)};const h=n.render.bind(n);n.render=function(...t){const e=n.__owl__;let o="render";return e.isMounted||e.currentFiber||(o+=" (warning: component is not mounted, this render has no effect)"),c(o),h(...t)};const p=n.mount.bind(n);n.mount=function(...t){return c("mount"),p(...t)}}(s,i,(l=n).id)}})}}),e.logScheduler){let e=t.Component.scheduler.start,n=t.Component.scheduler.stop;t.Component.scheduler.start=function(){this.isRunning||console.log(`${o} scheduler: start running tasks queue`),e.call(this)},t.Component.scheduler.stop=function(){this.isRunning&&console.log(`${o} scheduler: stop running tasks queue`),n.call(this)}}if(e.logStore){let e=t.Store.prototype.dispatch;t.Store.prototype.dispatch=function(t,...n){return console.log(`${o} store: action '${t}' dispatched. Payload: '${r(n)}'`),e.call(this,t,...n)}}}
debugOwl(owl, {
// componentBlackList: /App/, // regexp
// componentWhiteList: /SomeComponent/, // regexp
// methodBlackList: ["mounted"], // list of method names
// methodWhiteList: ["willStart"], // list of method names
logScheduler: false, // display/mute scheduler logs
logStore: true, // display/mute store logs
});
```
The above code, once pasted somewhere in the main javascript file of an owl
application, will log information looking like this:
```
[OWL_DEBUG] TodoApp<id=1> constructor, props={}
[OWL_DEBUG] TodoApp<id=1> mount
[OWL_DEBUG] TodoApp<id=1> willStart
[OWL_DEBUG] TodoApp<id=1> rendering template
[OWL_DEBUG] TodoItem<id=2> constructor, props={"id":2,"completed":false,"title":"hey"}
[OWL_DEBUG] TodoItem<id=2> willStart
[OWL_DEBUG] TodoItem<id=3> constructor, props={"id":4,"completed":false,"title":"aaa"}
[OWL_DEBUG] TodoItem<id=3> willStart
[OWL_DEBUG] TodoItem<id=2> rendering template
[OWL_DEBUG] TodoItem<id=3> rendering template
[OWL_DEBUG] TodoItem<id=3> mounted
[OWL_DEBUG] TodoItem<id=2> mounted
[OWL_DEBUG] TodoApp<id=1> mounted
```
Each component has an internal `id`, which is very useful when debugging.
Note that it is certainly useful to run this code at some point in an application,
just to get a feel of what each user action implies, for the framework.
@@ -1,4 +1,4 @@
# 🦉 Testing Owl components 🦉
# 🦉 How to test Components 🦉
## Content
@@ -11,8 +11,7 @@ It is a good practice to test applications and components to ensure that they
behave as expected. There are many ways to test a user interface: manual
testing, integration testing, unit testing, ...
In this section, we will discuss how to write unit tests for components, and
how to debug them if necessary.
In this section, we will discuss how to write unit tests for components.
## Unit Tests
+52
View File
@@ -0,0 +1,52 @@
# 🦉 How to write Single File Components 🦉
It is very useful to group code by feature instead of by type of file. It makes
it easier to scale application to larger size.
To do so, Owl has two small helpers that make it easy to define a
template or a stylesheet inside a javascript (or typescript) file: the
[`xml`](../reference/tags.md#xml-tag) and [`css`](../reference/tags.md#css-tag)
helper.
This means that the template, the style and the javascript code can be defined in
the same file. For example:
```js
const { Component } = owl;
const { xml, css } = owl.tags;
// -----------------------------------------------------------------------------
// TEMPLATE
// -----------------------------------------------------------------------------
const TEMPLATE = xml/* xml */ `
<div class="main">
<Sidebar/>
<Content />
</div>`;
// -----------------------------------------------------------------------------
// STYLE
// -----------------------------------------------------------------------------
const STYLE = css/* css */ `
.main {
display: grid;
grid-template-columns: 200px auto;
}
`;
// -----------------------------------------------------------------------------
// CODE
// -----------------------------------------------------------------------------
class Main extends Component {
static template = TEMPLATE;
static style = STYLE;
static components = { Sidebar, Content };
// rest of component...
}
```
Note that the above example has an inline xml comment, just after the `xml` call.
This is useful for some editor plugins, such as the VS Code addon
`Comment tagged template`, which, if installed, add syntax highlighting to the
content of the template string.
+131
View File
@@ -0,0 +1,131 @@
# 🦉 Quick Overview 🦉
Owl components in an application are used to define a (dynamic) tree of components.
```
Root
/ \
A B
/ \
C D
```
**State:** each component can manage its own local state. It is a simple ES6
class, there are no special rules:
```js
class Counter extends Component {
static template = xml`
<button t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>`;
state = { value: 0 };
increment() {
this.state.value++;
this.render();
}
}
```
The example above shows a component with a local state. Note that since there
is nothing magical to the `state` object, we need to manually call the `render`
function whenever we update it. This can quickly become annoying (and not
efficient if we do it too much). There is a better way: using the `useState`
hook, which transforms an object into a reactive version of itself:
```js
const { useState } = owl.hooks;
class Counter extends Component {
static template = xml`
<button t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>`;
state = useState({ value: 0 });
increment() {
this.state.value++;
}
}
```
Note that the `t-on-click` handler can even be replaced by an inline statement:
```xml
<button t-on-click="state.value++">
```
**Props:** sub components often needs some information from their parents. This
is done by adding the required information to the template. This will then be
accessible by the sub component in the `props` object. Note that there is an
important rule here: the information contained in the `props` object is not
owned by the sub component, and should never be modified.
```js
class Child extends Component {
static template = xml`<div>Hello <t t-esc="props.name"/></div>`;
}
class Parent extends Component {
static template = xml`
<div>
<Child name="'Owl'" />
<Child name="'Framework'" />
</div>`;
static components = { Child };
}
```
**Communication:** there are multiple ways to communicate information between
components. However, the two most important ways are the following:
- from parent to children: by using `props`,
- from a children to one of its parent: by triggering events.
The following example illustrate both mechanisms:
```js
class OrderLine extends Component {
static template = xml`
<div t-on-click="add">
<div><t t-esc="props.line.name"/></div>
<div>Quantity: <t t-esc="props.line.quantity"/></div>
</div>`;
add() {
this.trigger("add-to-order", { line: props.line });
}
}
class Parent extends Component {
static template = xml`
<div t-on-add-to-order="addToOrder">
<OrderLine
t-foreach="orders"
t-as="line"
line="line" />
</div>`;
static components = { OrderLine };
orders = useState([{ id: 1, name: "Coffee", quantity: 0 }, { id: 2, name: "Tea", quantity: 0 }]);
addToOrder(event) {
const line = event.detail.line;
line.quantity++;
}
}
```
In this example, the `OrderLine` component trigger a `add-to-order` event. This
will generate a DOM event which will bubble along the DOM tree. It will then be
intercepted by the parent component, which will then get the line (from the
`detail` key) and then increment its quantity. See the section on [event handling](../reference/component.md#event-handling)
for more details on how events work.
Note that this example would have also worked if the `OrderLine` component
directly modifies the `line` object. However, this is not a good practice: this
only works because the `props` object received by the child component is reactive,
so the child component is then coupled to the parents implementation.
+395 -77
View File
@@ -1,107 +1,425 @@
# 🦉 Quick Start 🦉
# 🦉 How to start an Owl project 🦉
## Static Server
## Content
Let us assume that we have a static server running somewhere. Let us start by
adding an html page with a few extra files:
- [Overview](#overview)
- [Simple html file](#simple-html-file)
- [With a static server](#with-a-static-server)
- [Standard Javascript project](#standard-javascript-project)
## Overview
Each software project has its specific needs. Many of these needs can be solved
with some tooling: `webpack`, `gulp`, css preprocessor, bundlers, transpilers, ...
Because of that, it is usually not simple to just start a project. Some
frameworks provide their own tooling to help with that. But then, you have to
integrate and learn how these applications work.
Owl is designed to be used with no tooling at all. Because of that, Owl can
"easily" be integrated in a modern build toolchain. In this section, we will
discuss a few different setups to start a project. Each of these setups has
advantages and disadvantages in different situations.
## Simple html file
The simplest possible setup is the following: a simple javascript file with your
code. To do that, let us create the following file structure:
```
my-app/
hello_owl/
index.html
app.css
owl.js
app.js
owl-X.Y.Z.js
templates.xml
```
### HTML and CSS
The file `owl.js` can be downloaded from the last release published at
[https://github.com/odoo/owl/releases](https://github.com/odoo/owl/releases). It
is a single javascript file which export all Owl into the global `owl` object.
In a file `index.html`:
Now, `index.html` should contain the following:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My OWL App</title>
<link href="app.css" rel="stylesheet" />
<script src="owl-X.Y.Z.js"></script>
<title>Hello Owl</title>
<script src="owl.js"></script>
<script src="app.js"></script>
</head>
<body>
<div id="main"></div>
<script src="app.js" type="module"></script>
</body>
<body></body>
</html>
```
In `app.css`:
And `app.js` should look like this:
```css
button {
color: darkred;
font-size: 30px;
width: 220px;
```js
const { Component } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
// Owl Components
class App extends Component {
static template = xml`<div>Hello Owl</div>`;
}
// Setup code
function setup() {
const app = new App();
app.mount(document.body);
}
whenReady(setup);
```
Now, simply loading this html file in a browser should display a welcome message.
This setup is not fancy, but it is extremely simple. There are no tooling at
all required. It can be slightly optimized by using the minified build of Owl.
## With a static server
The previous setup has a big disadvantage: the application code is located in a
single file. Obviously, we could split it in several files and add multiple
`<script>` tags in the html page, but then we need to make sure the script are
inserted in the proper order, we need to export each file content in global
variables and we lose autocompletion across files.
There is a low tech solution to this issue: using native javascript modules.
This however has a requirement: for security reasons, browsers will not accept
modules on content served through the `file` protocol. This means that we need
to use a static server.
Let us start a new project with the following file structure:
```
hello_owl/
src/
app.js
index.html
main.js
owl.js
```
As previously, the file `owl.js` can be downloaded from the last release published at
[https://github.com/odoo/owl/releases](https://github.com/odoo/owl/releases).
Now, `index.html` should contain the following:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Owl</title>
<script src="owl.js"></script>
<script src="main.js" type="module"></script>
</head>
<body></body>
</html>
```
Not that the `main.js` script tag has the `type="module"` attribute. This means
that the browser will parse the script as a module, and load all its dependencies.
Here is the content of `app.js` and `main.js`:
```js
// app.js ----------------------------------------------------------------------
const { Component } = owl;
const { xml } = owl.tags;
export class App extends Component {
static template = xml`<div>Hello Owl</div>`;
}
// main.js ---------------------------------------------------------------------
import { App } from "./app.js";
function setup() {
const app = new App();
app.mount(document.body);
}
owl.utils.whenReady(setup);
```
The `main.js` file import the `app.js` file. Note that the import statement has
a `.js` suffix, which is important. Most text editor can understand this syntax
and will provide autocompletion.
Now, to execute this code, we need to serve the `src` folder statically. A low
tech way to do that is to use for example the python `SimpleHTTPServer` feature:
```
$ cd src
$ python -m SimpleHTTPServer 8022 # now content is available at localhost:8022
```
Another more "javascripty" way to do it is to create a `npm` application. To do
that, we can add the following `package.json` file at the root of the project:
```json
{
"name": "hello_owl",
"version": "0.1.0",
"description": "Starting Owl app",
"main": "src/index.html",
"scripts": {
"serve": "serve src"
},
"author": "John",
"license": "ISC",
"devDependencies": {
"serve": "^11.3.0"
}
}
```
Also, let's not forget to add a release of OWL (`owl-X.Y.Z.js`)
We can now install the `serve` tool with the command `npm install`, and then,
start a static server with the simple `npm run serve` command.
### XML
In `templates.xml`:
## Standard Javascript project
```xml
<templates>
<button t-name="clickcounter" t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>
</templates>
The previous setup works, and is certainly good for some usecases, including
quick prototyping. However, it lacks some useful features, such as livereload,
a test suite, or bundling the code in a single file.
Each of these features, and many others, can be done in many different ways.
Since it is really not trivial to configure such a project, we provide here an
example that can be used as a starting point.
Our standard Owl project has the following file structure:
```
hello_owl/
public/
index.html
src/
components/
App.js
main.js
tests/
components/
App.test.js
helpers.js
.gitignore
package.json
webpack.config.js
```
### JS
This project as a `public` folder, meant to contain all static assets, such as
images and styles. The `src` folder has the javascript source code, and finally,
`tests` contains the test suite.
To build an application (or a sub-part of an application), we need two things:
Here is the content of `index.html`:
- an environment: it is the global context in which we are working. It needs to
contain a QWeb instance (preloaded with templates), and anything else that we
need. In practice, it could be used to contain some user session information, some
configuration keys (for example, isMobile = true/false if we are in mobile mode).
- a description of the user interface: there should be a root component, which can
have sub components
Here are a few steps that we may take to get started:
- get the templates
- create a qweb engine, with the templates
- create an environment
- create an instance of the root component
- mount the root component to a DOM element
Let us now add the javascript to make it work, in `app.js`:
```javascript
const useState = owl.hooks.useState;
class ClickCounter extends owl.Component {
static template = "clickcounter";
state = useState({ value: 0 });
increment() {
this.state.value++;
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadFile("templates.xml");
ClickCounter.env = { qweb: new owl.QWeb({ templates }) };
const counter = new ClickCounter();
const target = document.getElementById("main");
await counter.mount(target);
}
start();
```html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Owl</title>
</head>
<body></body>
</html>
```
Note that there are no `<script>` tag here. They will be injected by webpack.
Now, let's have a look at the javascript files:
```js
// src/components/App.js -------------------------------------------------------
import { Component, tags, useState } from "@odoo/owl";
const { xml } = tags;
export class App extends Component {
static template = xml`<div t-on-click="update">Hello <t t-esc="state.text"/></div>`;
state = useState({ text: "Owl" });
update() {
this.state.text = this.state.text === "Owl" ? "World" : "Owl";
}
}
// src/main.js -----------------------------------------------------------------
import { utils } from "@odoo/owl";
import { App } from "./components/App";
function setup() {
const app = new App();
app.mount(document.body);
}
utils.whenReady(setup);
// tests/components/App.test.js ------------------------------------------------
import { App } from "../../src/components/App";
import { makeTestFixture, nextTick, click } from "../helpers";
let fixture;
beforeEach(() => {
fixture = makeTestFixture();
});
afterEach(() => {
fixture.remove();
});
describe("App", () => {
test("Works as expected...", async () => {
const app = new App();
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div>Hello Owl</div>");
click(fixture, "div");
await nextTick();
expect(fixture.innerHTML).toBe("<div>Hello World</div>");
});
});
// tests/helpers.js ------------------------------------------------------------
import { Component } from "@odoo/owl";
import "regenerator-runtime/runtime";
export async function nextTick() {
return new Promise(function(resolve) {
setTimeout(() =>
Component.scheduler.requestAnimationFrame(() => resolve())
);
});
}
export function makeTestFixture() {
let fixture = document.createElement("div");
document.body.appendChild(fixture);
return fixture;
}
export function click(elem, selector) {
elem.querySelector(selector).dispatchEvent(new Event("click"));
}
```
Finally, here is the configuration files `.gitignore`, `package.json` and
`webpack.config.js`:
```
node_modules/
package-lock.json
dist/
```
```json
{
"name": "hello_owl",
"version": "0.1.0",
"description": "Demo app",
"main": "src/index.html",
"scripts": {
"test": "jest",
"build": "webpack --mode production",
"dev": "webpack-dev-server --mode development"
},
"author": "Someone",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"babel-jest": "^25.1.0",
"babel-loader": "^8.0.6",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"html-webpack-plugin": "^3.2.0",
"jest": "^25.1.0",
"regenerator-runtime": "^0.13.3",
"serve": "^11.3.0",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.2"
},
"dependencies": {
"@odoo/owl": "^1.0.4"
},
"babel": {
"plugins": [
"@babel/plugin-proposal-class-properties"
],
"env": {
"test": {
"plugins": ["transform-es2015-modules-commonjs"]
}
}
},
"jest": {
"verbose": false,
"testRegex": "(/tests/.*(test|spec))\\.js?$",
"moduleFileExtensions": [
"js"
],
"transform": {
"^.+\\.[t|j]sx?$": "babel-jest"
}
}
}
```
```js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const host = process.env.HOST || "localhost";
module.exports = function(env, argv) {
const mode = argv.mode || "development";
return {
mode: mode,
entry: "./src/main.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: "babel-loader",
exclude: /node_modules/
}
]
},
resolve: {
extensions: [".js", ".jsx"]
},
devServer: {
contentBase: path.resolve(__dirname, "public/index.html"),
compress: true,
hot: true,
host,
port: 3000,
publicPath: "/"
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: path.resolve(__dirname, "public/index.html")
})
]
};
};
```
With this setup, we can now use the following script commands:
```
npm run build # build the full application in prod mode in dist/
npm run dev # start a dev server with livereload
npm run test # run the jest test suite
```
+1 -1
View File
@@ -37,7 +37,7 @@ todoapp/
index.html
app.css
app.js
owl.min.js
owl.js
```
The entry point for this application is the file `index.html`, which should have
@@ -79,7 +79,7 @@ additional tools, we made a lot of effort to make the most of the web platform.
For example, Owl uses the standard `xml` parser that comes with every browser.
Because of that, Owl did not have to write its own template parser. Another
example is the [`xml`](reference/tags.md#xml-tag) tag helper function, which makes use of
example is the [`xml`](../reference/tags.md#xml-tag) tag helper function, which makes use of
native template literals to allow in a natural way to write `xml` templates
directly in the javascript code. This can be easily integrated with editor
plugins to have autocompletion inside the template.
@@ -127,7 +127,7 @@ structured than a template language. Note that the tooling is quite impressive:
there is a syntax highlighter for jsx here on github!
By comparison, here is the equivalent Owl component, written with the
[`xml`](reference/tags.md#xml-tag) tag helper:
[`xml`](../reference/tags.md#xml-tag) tag helper:
```js
class Clock extends Component {
@@ -251,7 +251,7 @@ keeps track of who get data, and retrigger a render when it was changed.
Owl store is a little bit like a mix of redux and vuex: it has actions (but not
mutations), and like VueX, it keeps track of the state changes. However, it does
not notify a component when the state changes. Instead, components need to connect
to the store like in redux, with the `useStore` hook (see the [store documentation](reference/store.md#connecting-a-component)).
to the store like in redux, with the `useStore` hook (see the [store documentation](../reference/store.md#connecting-a-component)).
```javascript
const actions = {
@@ -315,7 +315,7 @@ This work is based on the new ideas introduced by React hooks.
From the way React and Vue introduce their hooks, it may look like hooks are not
compatible with class components. However, this is not the case, as shown by
Owl [hooks](reference/hooks.md). They are inspired by both React and Vue. For example,
Owl [hooks](../reference/hooks.md). They are inspired by both React and Vue. For example,
the `useState` hook is named after React, but its API is closer to the `reactive`
Vue hook.
@@ -125,7 +125,7 @@ familiar for experienced developers.
## JIT compilation
There is also a clear trend in the frontent world to compile code
There is also a clear trend in the frontend world to compile code
as much as possible ahead of time. Most frameworks will compile templates ahead
of time. And now Svelte is trying to compile the JS code away, so it can remove
itself from the bundle.
@@ -170,11 +170,11 @@ now a very strong concurrent mode, which is simple and powerful at the same time
## Conclusion
This lengthy discussion showed that there are many small and not so small reason
This lengthy discussion showed that there are many small and not so small reasons
that current standard frameworks are not tailored to our needs. It is perfectly
fine, because they chose a different set of tradeoffs.
fine, because they each chose a different set of tradeoffs.
However, we feel that there is still room in the framework world for something
that is different. For a framework that make choices compatible with Odoo.
And that is why we built Owl.
And that is why we built Owl 🦉.
+25 -48
View File
@@ -1,9 +1,24 @@
# 🦉 OWL Documentation 🦉
## Learning Owl
Are you new to Owl? This is the place to start!
- [Tutorial: create a TodoList application](learning/tutorial_todoapp.md)
- [Quick Overview](learning/overview.md)
- [How to start an Owl project](learning/quick_start.md)
- [How to test Components](learning/how_to_test.md)
- [How to write Single File Components](learning/how_to_write_sfc.md)
- [How to write debug Owl applications](learning/how_to_debug.md)
## Reference
You will find here a complete reference of every feature, class or object
provided by Owl.
- [Animations](reference/animations.md)
- [Component](reference/component.md)
- [Content](reference/content.md)
- [Concurrency Model](reference/concurrency_model.md)
- [Configuration](reference/config.md)
- [Context](reference/context.md)
@@ -21,57 +36,19 @@
- [Tags](reference/tags.md)
- [Utils](reference/utils.md)
## Learning Resources
## Other Topics
- [Quick Start: create an (almost) empty Owl application](learning/quick_start.md)
- [Tutorial: create a TodoList application](learning/tutorial_todoapp.md)
- [Testing Owl components](learning/testing_components.md)
This section provides miscellaneous document that explains some topics
which cannot be considered either a tutorial, or reference documentation.
## Miscellaneous
- [Owl architecture: the Virtual DOM](miscellaneous/vdom.md)
- [Owl architecture: the rendering pipeline](miscellaneous/rendering.md)
- [Comparison with React/Vue](miscellaneous/comparison.md)
- [Why did Odoo built Owl?](miscellaneous/why_owl.md)
- [Comparison with React/Vue](comparison.md)
- [Tooling](tooling.md)
- [Templates to start Owl applications (external link)](https://github.com/ged-odoo/owl-templates)
## Architecture
---
This section explains in more detail the inner workings of Owl. It is targeted
for developers working on Owl itself.
Found an issue in the documentation? A broken link? Some outdated information?
Please open an issue or submit a PR!
- [Virtual DOM](architecture/vdom.md)
- [Rendering](architecture/rendering.md)
## Owl Content
Here is a complete visual representation of everything exported by the `owl`
global object (so, for example, `Component` is available at `owl.Component`,
and `EventBus` is exported as `owl.core.EventBus`):
```
Component misc
Context AsyncRoot
QWeb Portal
Store router
useState Link
config RouteComponent
mode Router
core tags
EventBus css
Observer xml
hooks utils
onWillStart debounce
onMounted escape
onWillUpdateProps loadJS
onWillPatch loadFile
onPatched shallowEqual
onWillUnmount whenReady
useContext
useState
useRef
useSubEnv
useStore
useDispatch
useGetters
```
Note that for convenience, the `useState` hook is also exported at the root of the `owl` object.
+1 -1
View File
@@ -137,7 +137,7 @@ Here is what Owl will do:
Tags are very small helpers to make it easy to write inline templates. There is
only one currently available tag: `xml`, but we plan to add other tags later,
such as a `css` tag, which will be used to write [single file components](../tooling.md#single-file-component).
such as a `css` tag, which will be used to write [single file components](../learning/how_to_write_sfc.md).
### Asynchronous Rendering
+36
View File
@@ -0,0 +1,36 @@
# 🦉 Owl Content 🦉
Here is a complete visual representation of everything exported by the `owl`
global object.
For example, `Component` is available at `owl.Component` and `EventBus` is
exported as `owl.core.EventBus`.
```
Component misc
Context AsyncRoot
QWeb Portal
Store router
useState Link
config RouteComponent
mode Router
core tags
EventBus css
Observer xml
hooks utils
onWillStart debounce
onMounted escape
onWillUpdateProps loadJS
onWillPatch loadFile
onPatched shallowEqual
onWillUnmount whenReady
useContext
useState
useRef
useSubEnv
useStore
useDispatch
useGetters
```
Note that for convenience, the `useState` hook is also exported at the root of the `owl` object.
+1 -1
View File
@@ -61,7 +61,7 @@ Its API is quite simple:
```
- **`render(name, context, extra)`**: renders a template. This returns a `vnode`,
which is a virtual representation of the DOM (see [vdom doc](../architecture/vdom.md)).
which is a virtual representation of the DOM (see [vdom doc](../miscellaneous/vdom.md)).
```js
const vnode = qweb.render("App", component);
+1 -1
View File
@@ -10,7 +10,7 @@
Tags are very small helpers intended to make it easy to write inline templates
or styles. There are currently two tags: `css` and `xml`. With these functions,
it is possible to write [single file components](../tooling.md#single-file-component).
it is possible to write [single file components](../learning/how_to_write_sfc.md).
## XML tag
-117
View File
@@ -1,117 +0,0 @@
# 🦉 Tooling 🦉
## Content
- [Overview](#overview)
- [Playground](#playground)
- [Benchmarks](#benchmarks)
- [Single File Component](#single-file-component)
- [Debugging Script](#debugging-script)
## Overview
To help work with/improve/learn OWL, there are a few extras tools/settings.
- development mode: enable better error reporting for the developer
- a playground application: a space to experiment and learn Owl.
- a benchmarks application: allow comparison with a few common frameworks
The two applications are available in the `tools/` folder, and can be accessed
by using a static http server. A simple python
server is available in `server.py`. There is also a npm script to start it:
`npm run tools` (and its version with a watcher: `npm run tools:watch`).
## Playground
The playground is an important application designed to help learning and
experimenting with Owl. The last published version of Owl can be tested [online](https://odoo.github.io/owl/playground/).
It is an application similar to `jsFiddle`, but specialized for Owl: there are
three tabs (`js`, `css` and `xml`), and a simple button `Run` to execute that
code in an iframe.
## Benchmarks
Note: This is more an internal tool, useful for people working on Owl.
The benchmarks application is a very small application, implemented in different
frameworks, and in different versions of Owl. This is a simple internal tool,
useful to compare various performance metrics on some tasks.
## Single File Component
It is very useful to group code by feature instead of by type of file. It makes
it easier to scale application to larger size.
To do so, Owl has two small helpers that make it easy to define a
template or a stylesheet inside a javascript (or typescript) file: the
[`xml`](reference/tags.md#xml-tag) and [`css`](reference/tags.md#css-tag)
helper.
This means that the template, the style and the javascript code can be defined in
the same file. For example:
```js
const { Component } = owl;
const { xml, css } = owl.tags;
// -----------------------------------------------------------------------------
// TEMPLATE
// -----------------------------------------------------------------------------
const TEMPLATE = xml/* xml */ `
<div class="main">
<Sidebar/>
<Content />
</div>`;
// -----------------------------------------------------------------------------
// STYLE
// -----------------------------------------------------------------------------
const STYLE = css/* css */ `
.main {
display: grid;
grid-template-columns: 200px auto;
}
`;
// -----------------------------------------------------------------------------
// CODE
// -----------------------------------------------------------------------------
class Main extends Component {
static template = TEMPLATE;
static style = STYLE;
static components = { Sidebar, Content };
// rest of component...
}
```
Note that the above example has an inline xml comment, just after the `xml` call.
This is useful for some editor plugins, such as the VS Code addon
`Comment tagged template`, which, if installed, add syntax highlighting to the
content of the template string.
## Debugging Script
## Debugging
Non trivial applications become quickly more difficult to understand. It is then
useful to have a solid understanding of what is going on. To help with that,
logging useful information is extremely valuable. There is a [javascript file](../tools/debug.js) which can be evaluated in an application.
Once it is executed, it will log a lot of information on each component main hooks. The following code is a minified version to make it easier to copy/paste:
```
function debugOwl(t,e){let n,o="[OWL_DEBUG]";function r(t){let e;try{e=JSON.stringify(t||{})}catch(t){e="<JSON error>"}return e.length>200&&(e=e.slice(0,200)+"..."),e}if(Object.defineProperty(t.Component,"current",{get:()=>n,set(s){n=s;const i=s.constructor.name;if(e.componentBlackList&&e.componentBlackList.test(i))return;if(e.componentWhiteList&&!e.componentWhiteList.test(i))return;let l;Object.defineProperty(n,"__owl__",{get:()=>l,set(n){!function(n,s,i){let l=`${s}<id=${i}>`,c=t=>console.log(`${o} ${l} ${t}`),u=t=>(!e.methodBlackList||!e.methodBlackList.includes(t))&&!(e.methodWhiteList&&!e.methodWhiteList.includes(t));u("constructor")&&c(`constructor, props=${r(n.props)}`);u("willStart")&&t.hooks.onWillStart(()=>{c("willStart")});u("mounted")&&t.hooks.onMounted(()=>{c("mounted")});u("willUpdateProps")&&t.hooks.onWillUpdateProps(t=>{c(`willUpdateProps, nextprops=${r(t)}`)});u("willPatch")&&t.hooks.onWillPatch(()=>{c("willPatch")});u("patched")&&t.hooks.onPatched(()=>{c("patched")});u("willUnmount")&&t.hooks.onWillUnmount(()=>{c("willUnmount")});const d=n.__render.bind(n);n.__render=function(...t){c("rendering template"),d(...t)};const h=n.render.bind(n);n.render=function(...t){const e=n.__owl__;let o="render";return e.isMounted||e.currentFiber||(o+=" (warning: component is not mounted, this render has no effect)"),c(o),h(...t)};const p=n.mount.bind(n);n.mount=function(...t){return c("mount"),p(...t)}}(s,i,(l=n).id)}})}}),e.logScheduler){let e=t.Component.scheduler.start,n=t.Component.scheduler.stop;t.Component.scheduler.start=function(){this.isRunning||console.log(`${o} scheduler: start running tasks queue`),e.call(this)},t.Component.scheduler.stop=function(){this.isRunning&&console.log(`${o} scheduler: stop running tasks queue`),n.call(this)}}if(e.logStore){let e=t.Store.prototype.dispatch;t.Store.prototype.dispatch=function(t,...n){return console.log(`${o} store: action '${t}' dispatched. Payload: '${r(n)}'`),e.call(this,t,...n)}}}
debugOwl(owl, {
// componentBlackList: /App/, // regexp
// componentWhiteList: /SomeComponent/, // regexp
// methodBlackList: ["mounted"], // list of method names
// methodWhiteList: ["willStart"], // list of method names
logScheduler: false, // display/mute scheduler logs
logStore: true, // display/mute store logs
});
```
Note that it is certainly useful to run this code at some point in an application,
just to get a feel of what each user action implies, for the framework.