🦉 Как начать проект на Owl 🦉

Содержание

Введение

Каждый проект по разарботке ПО имеет свои потребности. Множество этих потребностей могут быть решены такими инструментами как webpack, gulp, препроцессор css , упакочщики, траспиляторы, и т.д.

Именно по этому бывает не просто начать проект. Некоторые фреймворки предоставляют свой собственный инструментарий для того чтобы помочь с этим шагом. Но когда вам нужно будет все это вместе собрать и итегрировать вам все равно придется изучать как эти приложения работают.

Owl предназначен для использования без каких-либо дополниельных инструментов. Из-за этого Owl можно «легко» интегрировать в современную инструметарий сборки. В этом разделе мы обсудим несколько различных настроек для запуска проекта. Каждая из этих установок имеет свои преимущества и недостатки в различных ситуациях.

Обычный html файл

Самым простым из возможных способов установки является обычный файл javascript с вашим кодом. Для этого создадим следующую файловую структуру:

hello_owl/
  index.html
  owl.js
  app.js

Файл owl.js может быть скачан вот отсюда https://github.com/odoo/owl/releases. Это один файл 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>

And app.js should look like this:

const { Component, mount } = 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() {
  mount(App, target: { document.body })
}

whenReady(setup);

Теперь просто открываем html файл с помощью браузера, где мы должны увидеть наше привествие. Данный подход не претендует на изысканность, но зато очень прост. Для его реализации не требуется вообще никаких вспомогательных иструментов. Его можно немного оптимизировать, используя минимизрованную сборку Owl.

С сервером статических ресурсов

У предыдущего подхода есть большой недостаток: код приложения находится в одном файле. Мы могли бы разделить его на несколько файлов и добавить несколько тегов <script> на html-страницу, но тогда нам нужно тратить свои ресурсы на то, что бы скрипты были вставлены в правильном порядке. Более того нам придется экспортировать содержимое каждого файла в глобальные переменные и мы теряем автодополнение между пре преходе из одного файла в другой.

Существует низкотехнологичное решение этой проблемы: использование собственных модулей javascript. Однако у такого подхода есть ограничения: по соображениям безопасности браузеры не будут принимать модули для контента, обслуживаемого через протокол file. Это означает, что нам нужно использовать статический сервер.

Давайте начнем новый проект со следующей файловой структурой:

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

Как и в прошлый раз файл owl.js может быть загружен отсюда https://github.com/odoo/owl/releases.

Теперь файл 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>

Обратите внимание что тег script для main.js имее атриубт type="module". Это означает, что браузер будет анализировать скрипт как модуль и загружать все его зависимости.

Вот содержимое app.js и main.js:

// app.js ----------------------------------------------------------------------
const { Component, mount } = 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() {
  mount(App, { target: document.body });
}

owl.utils.whenReady(setup);

Файл main.js импортирует файл app.js. Обратите внимание что иструкция импорта имеет суффикс .js и это очень важно. Большиство редакторов могу понимать данный синтаксис и осуществлять автоподстановку.

Теперь, для того, чтобы выполнить этот код, нам нужно натравить на каталог src сервер, который буде отдавать статические ресурсы из этого каталога. Наименее затратный способ это сделать - это использовать SimpleHTTPServer, который входит в стандартную поставку python:

$ cd src
$ python -m SimpleHTTPServer 8022    # теперь контент доступен по адресу 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"
  }
}

Теперь мы можем уставновить serve с помощью команды npm install, и затем запустить сервер статических ресурсов с помощью команды npm run serve

Стандартный проект Javascript

Предыдущий подход неплохо работает, и очень хорошо подходить для некоторых сценариев, включая быстрое прототипирование. Однако в нем отсутствуют некоторые полезные функции, такие как 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, mount } from "@odoo/owl";
import { App } from "./components/App";

function setup() {
  mount(App, { target: document.body });
}

utils.whenReady(setup);

// tests/components/App.test.js ------------------------------------------------
import { App } from "../../src/components/App";
import { makeTestFixture, nextTick, click } from "../helpers";
import { mount } from "@odoo/owl";

let fixture;

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

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

describe("App", () => {
  test("Works as expected...", async () => {
    await mount(App, { target: 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 # собирает пиложение в режиме prod в дистрибутив/

npm run dev # запускает сервер разработки с механизмом livereload

npm run test # запускает jest для тестирования