【Odoo】OWL-如何开始一个项目

序说科技 2022年10月11日 251次浏览

上海序说科技,专注于基于Odoo项目实施,实现企业数智化,助力企业成长。
老韩头的开发日常,博客园分享(2022年前博文)

🦉 如何开始一个项目🦉

目录

概述

每一个软件项目都有其特殊需求。分别对应也不用的脚手架工具:webpack, gulp, css preprocessor, bundlers, transpilers, …

正因为如此,开始一个项目并不容易。一些框架提供了官方的脚手架工具,但这就意味着我们不得不学习如何使用这些工具。

Owl并不依赖于任何额外的工具。因此Owl可以很容易的整合到构建工具中。下面我们将分步构建一个新的任务,每步都有优劣不同的解决方案。

简单的HTML文件

创建如下结构的项目:

hello_owl/
  index.html
  owl.js
  app.js

owl.js文件可以在https://github.com/odoo/owl/releases下载。这是一个简单的js文件,将所有Owl集成在owl对象。

index.html 内容如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Hello Owl</title>
    <script src="owl.js"></script>
  </head>
  <body>
    <script src="app.js"></script>
  </body>
</html>

app.js 内容如下:

const { Component, mount, xml } = owl;

// Owl Components
class Root extends Component {
  static template = xml`<div>Hello Owl</div>`;
}

mount(Root, document.body);

此刻,我们打开html文件,可以看到在页面展示的Hello Owl

静态服务器

前面的代码有个很大的问题是应用代码只有一个文件。很明显,我们应该将js代码拆分到几个文件,并在html中引用。但是为了确保脚本正确加载,因此我们需要将以全局变量的方书写相关代码,并会丢失一些跨文件自动补全功能。

原文
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.

有一个简单的方案是使用原生js模块。但由于安全隐私,浏览器并不接受通过file协议的文件内容。因此,我们需要使用静态服务器。

新项目结构如下:

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

index.html 内容如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Hello Owl</title>
    <script src="owl.js"></script>
  </head>
  <body>
    <script src="main.js" type="module"></script>
  </body>
</html>

此处代码中main.jstype="module" 。浏览器在解析时将把脚本作为模块并加载它的所有依赖项。

下面是root.jsmain.js的代码:

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

export class Root extends Component {
  static template = xml`<div>Hello Owl</div>`;
}

// main.js ---------------------------------------------------------------------
import { Root } from "./root.js";

mount(Root, document.body);

main.js 引用了root.js 文件。我们注意到引用的文件是.js后缀的,对于大部分编辑器而言都可以正常解析该文件并提供自动补全功能。

现在我们可以使用python的SimpleHTTPServer功能加载src文件下的代码了。

$ cd src
$ python -m SimpleHTTPServer 8022    # now content is available at localhost:8022

当前,我们也可以创建 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 启用服务。

标准的js项目

前面的代码可以快速创建可运行的项目原型。但也缺失了诸如热重载、测试套件及编译为单一文件的特性。

有很多中方式可以实现上面所提到的特性,在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 文件夹,其中包含一些静态资源,比如images and styles.。src 文件夹中包含源码,tests 中包含测试套件。

以下是index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Hello Owl</title>
  </head>
  <body></body>
</html>

我们注意到代码中并未包含<script>标签,相关js将通过webpack注入。js代码如下:

// 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";

let fixture;

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

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

describe("Root", () => {
  test("Works as expected...", 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"));
}

最后配置 .gitignorepackage.jsonwebpack.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