说明:这是对Odoo官方Owl公开的资料的翻译。
OWL是一个基于Typescript的独立的前端网页框架,因为被正式应用于Odoo14(全球最大的一个开源ERP平台)的前端网页,从而进入了大家的视野。
这篇文章介绍如何独立使用OWL框架来构建一个反应式的网页工程。
每个软件项目都有其特定的需求。 这些需求中的许多需求都可以通过一些工具来解决:webpack,gulp,css预处理器,捆绑器,编译器,…
因此,仅启动一个项目通常并不简单。 一些框架提供了自己的工具来帮助实现这一目标。 但是随后,您必须集成并了解这些应用程序的工作方式。
Owl设计为完全不使用任何工具。 因此,Owl可以“轻松地”集成到现代构建工具链中。 在本节中,我们将讨论一些不同的设置来启动一个项目。 这些设置中的每一个在不同情况下都有优点和缺点。
可能的最简单设置如下:带有代码的简单javascript文件。 为此,让我们创建以下文件结构:
hello_owl/
index.html
owl.js
app.js
可以从
https://github.com/odoo/owl/releases发布的最新版本下载owl.js文件。 它是一个单个javascript文件,可将所有Owl导出到全局owl对象。
现在,index.html应该包含以下内容:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Owl</title>
<script src="owl.js"></script>
<script src="app.js"></script>
</head>
<body></body>
</html>
而且app.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);
现在,仅在浏览器中加载此html文件即可显示欢迎消息。 这种设置并不花哨,但非常简单。 完全不需要任何工具。 可以使用缩小的Owl版本对其进行稍微优化。
先前的设置有一个很大的缺点:应用程序代码位于单个文件中。 显然,我们可以将其拆分为多个文件,并在html页面中添加多个<script>标记,但是随后我们需要确保脚本以正确的顺序插入,我们需要将每个文件内容导出为全局变量,否则会丢失 跨文件自动完成。
解决此问题的技术含量较低:使用本机javascript模块。 但是,这有一个要求:出于安全原因,浏览器将不接受通过file 协议提供的内容模块。 这意味着我们需要使用静态服务器。
让我们开始一个具有以下文件结构的新项目:
hello_owl/
src/
app.js
index.html
main.js
owl.js
和以前一样,可以从
https://github.com/odoo/owl/releases发布的最新版本下载owl.js文件。
现在,index.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>
并不是说main.js脚本标记具有type =“ module”属性。 这意味着浏览器会将脚本解析为模块,并加载其所有依赖项。
这是app.js和main.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);
main.js文件导入app.js文件。 请注意,import语句具有.js后缀,这一点很重要。 大多数文本编辑器都可以理解此语法,并将提供自动补全功能。
现在,要执行此代码,我们需要静态提供src文件夹。 一种低技术的方法是使用例如python SimpleHTTPServer功能:
$ cd src
$ python -m SimpleHTTPServer 8022 # now content is available at localhost:8022
另一个更“ javascripty”的方法是创建一个npm应用程序。 为此,我们可以在项目的根目录下添加以下package.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"
}
}
现在,我们可以使用命令npm install安装serve 工具,然后使用简单的npm run serve命令启动静态服务器。
先前的设置有效,并且对于某些用例(包括快速原型制作)肯定是好的。 但是,它缺少一些有用的功能,例如livereload,测试套件或将代码捆绑在单个文件中。
这些功能中的每一个以及许多其他功能都可以通过许多不同的方式来完成。 由于配置这样的项目确实不是一件容易的事,因此我们在此处提供一个示例,可以作为起点。
我们的标准Owl项目具有以下文件结构:
hello_owl/
public/
index.html
src/
components/
App.js
main.js
tests/
components/
App.test.js
helpers.js
.gitignore
package.json
webpack.config.js
该项目作为public 文件夹,旨在包含所有静态资产,例如图像和样式。 src文件夹具有javascript源代码,最后,tests 包含测试套件。
以下是index.html的内容:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Owl</title>
</head>
<body></body>
</html>
请注意,这里没有<script>标记。 它们将通过webpack注入。 现在,让我们看一下javascript文件:
// 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"));
}
最后,这是配置文件.gitignore,package.json和webpack.config.js:
node_modules/
package-lock.json
dist/
{
"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"
}
}
}
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"),
}),
],
};
};
通过此设置,我们现在可以使用以下脚本命令:
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