【Owl】🦉 如何开始一个Owl项目 🦉

178 阅读5分钟

github.com/odoo/owl/bl…

内容

  • 概览
  • 简单的HTML文件
  • 使用静态服务器
  • 标准的JavaScript项目

概览

每个软件项目都有其特定的需求。这些需求中的许多可以通过一些工具来解决:webpackgulpCSS预处理器、打包器、转译器等。

正因为如此,通常不是简单地就能开始一个项目。一些框架提供了他们自己的工具来帮助解决这个问题。但然后,你必须集成并学习这些应用程序是如何工作的。

Owl被设计为不需要任何工具即可使用。正因为如此,Owl可以“轻松”地集成到现代构建工具链中。在这一部分,我们将讨论几种不同的设置来启动一个项目。每种设置在不同情况下都有优点和缺点。

简单的HTML文件

最简单的设置如下:一个包含你代码的简单JavaScript文件。为此,让我们创建以下文件结构:

hello_owl/
  index.html
  owl.js
  app.js

文件owl.js可以从github.com/odoo/owl/re…发布的最新版本中下载。它是一个单独的JavaScript文件,将所有Owl导出到全局owl对象中。注意有多个文件,这种情况下,我们需要一个以.iife结尾的文件:它们是为了直接在浏览器中使用而构建的。

现在,index.html应该包含以下内容:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Hello Owl</title>
    <script src="owl.iife.js"></script>
  </head>
  <body>
    <script src="app.js"></script>
  </body>
</html>

app.js应该看起来像这样:

const { Component, mount, xml } = owl;

// Owl组件
class Root extends Component {
  static template = xml`<div>Hello Owl</div>`;
}
mount(Root, document.body);

现在,只需在浏览器中加载此HTML文件,应该就会显示一条欢迎消息。这个设置不花哨,但是非常简单。它不需要任何工具。它可以通过使用Owl的压缩版本进行轻微优化。

使用静态服务器

之前的设置有一个很大的缺点:应用程序代码位于一个单独的文件中。显然,我们可以将它分割成几个文件,并在HTML页面中添加多个

有一个低技术解决方案来解决这个问题:使用原生JavaScript模块。然而,这有一个要求:出于安全原因,浏览器不会接受通过文件协议提供的模块。这意味着我们需要使用静态服务器。

让我们以以下文件结构开始一个新项目:

hello_owl/
  src/
    index.html
    main.js
    owl.js
    root.js

像以前一样,文件owl.js可以从github.com/odoo/owl/re…发布的最新版本中下载。注意有多个文件,这种情况下,我们需要一个以.iife结尾的文件:它们是为了直接在浏览器中使用而构建的。

现在,index.html应该包含以下内容:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Hello Owl</title>
    <script src="owl.iife.js"></script>
  </head>
  <body>
    <script src="main.js" type="module"></script>
  </body>
</html>

注意main.js脚本标签具有type="module"属性。这意味着浏览器将解析脚本作为一个模块,并加载其所有依赖项。

这里是root.js和main.js的内容:

// root.js ----------------------------------------------------------------------
const { Component, mount, xml } = owl;

export class Root extends Component {
  static template = xml`<div>Hello Owl</div>`;
}
// main.js ----------------------------------------------------------------------
const { mount } = owl;
import { Root } from "./root.js";

mount(Root, document.body);

main.js文件导入了root.js文件。注意import语句有一个.js后缀,这很重要。大多数文本编辑器可以理解此语法并提供自动完成。

现在,要执行此代码,我们需要静态地提供src文件夹。一个低技术的方法是使用例如python http.server功能:

$ cd src
$ python -m http.server 8022    # 现在内容可在localhost:8022上获得

另一种更"javascript"的方式是创建一个npm应用程序。为此,我们可以在项目根目录下添加以下package.json文件:

{
  "name": "hello_owl",
  "version": "0.1.0",
  "description": "启动Owl应用程序",
  "main": "src/index.html",
  "scripts": {
    "serve": "serve src"
  },
  "author": "John",
  "license": "ISC",
  "devDependencies": {
    "serve": "^11.3.0"
  }
}

我们现在可以使用npm install命令安装serve工具,然后,使用简单的npm run serve命令启动静态服务器。

标准JavaScript项目

之前的设置有效,对于某些用例(包括快速原型设计)来说确实不错。然而,它缺少一些有用的功能,如实时重载、测试套件或将代码打包到一个文件中。

所有这些功能,以及许多其他功能,都可以通过许多不同的方式完成。由于配置这样的项目确实不是微不足道的,我们在这里提供一个示例,可以用作起点。

我们的标准Owl项目具有以下文件结构:

hello_owl/
  public/
    index.html
  src/
    components/
      Root.js
    main.js
  tests/
    components/
      Root.test.js
    helpers.js
  .gitignore
  package.json
  webpack.config.js

此项目有一个public文件夹,旨在包含所有静态资源,如图像和样式。src文件夹有JavaScript源代码,最后,tests包含测试套件。

这里是index.html的内容:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Hello Owl</title>
  </head>
  <body>
  </body>
</html>

注意这里没有

// src/components/Root.js -------------------------------------------------------
import { Component, xml, useState } from "@odoo/owl";

export class Root 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, mount } from "@odoo/owl";
import { Root } from "./components/Root";

mount(Root, document.body);
// tests/components/Root.test.js ------------------------------------------------
import { Root } from "../../src/components/Root";
import { makeTestFixture, nextTick, click } from "../helpers";
import { mount } from "@odoo/owl";

//------------------------------------------------------------------------------
// Setup
//------------------------------------------------------------------------------
let fixture;

beforeEach(() => {
  fixture = makeTestFixture();
});

afterEach(() => {
  fixture.remove();
});

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
describe("Root", () => {
  test("按预期工作...", async () => {
    await mount(Root, 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() {
  await new Promise((resolve) => setTimeout(resolve));
  await new Promise((resolve) => 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/
// package.json
{
  "name": "hello_owl",
  "version": "0.1.0",
  "description": "Owl示例",
  "main": "src/index.html",
  "scripts": {
    "test": "jest",
    "build": "webpack --mode production",
    "dev": "webpack-dev-server --mode development"
  },
  "author": "Jesse Chang",
  "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": "^2.2.10"
  },
  "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"
  }
}
}
// webpack.config.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"),
      }),
    ],
  };
};

通过此设置,我们现在可以使用以下脚本命令:

npm run build # 在dist/中以生产模式构建完整应用程序

npm run dev # 启动具有实时重载的开发服务器

npm run test # 运行jest测试套件