Cómo empezar
Cada proyecto de software tiene sus necesidades específicas. Muchas de estas necesidades se pueden solucionar con algunas herramientas: webpack
, gulp
, preprocesadores de CSS, empaquetadores, transpiladores, …
Por eso, no suele ser sencillo empezar un proyecto. Algunos frameworks ofrecen sus propias herramientas para ayudar con eso, pero luego hay que integrar y aprender cómo funcionan estas aplicaciones.
Owl está diseñado para usarse sin herramientas. Por eso, Owl se puede integrar “fácilmente” en una cadena de herramientas de construcción moderna. En esta sección, analizaremos algunas configuraciones diferentes para iniciar un proyecto. Cada una de estas configuraciones tiene ventajas y desventajas en diferentes situaciones.
Un archivo html simple
La configuración más sencilla posible es la siguiente: un archivo javascript simple con su código. Para ello, vamos a crear la siguiente estructura de archivos:
hello_owl/ index.html owl.js app.js
El archivo owl.js
se puede descargar desde la última versión publicada en https://github.com/odoo/owl/releases. Es un único archivo javascript que exporta todo Owl al objeto global owl
. Tenga en cuenta que hay varios archivos y, en este caso, necesitamos uno de los dos archivos con el sufijo .iife
: están diseñados para usarse directamente en un navegador.
Ahora, index.html
debería contener lo siguiente:
<!DOCTYPE html><html lang="en"> <head> <title>Hello Owl</title> <script src="owl.js"></script> </head> <body> <script src="app.js"></script> </body></html>
Y app.js
debería verse así:
const { Component, mount, xml } = owl;
// Owl Componentsclass Root extends Component { static template = xml`<div>Hello Owl</div>`;}
mount(Root, document.body);
Ahora, con solo cargar este archivo html en un navegador debería aparecer un mensaje de bienvenida. Esta configuración no es complicada, pero es extremadamente simple. No se requieren herramientas de ningún tipo. Se puede optimizar ligeramente utilizando la compilación minimizada de Owl.
With a static server
La configuración anterior tiene una gran desventaja: el código de la aplicación se encuentra en un único archivo. Obviamente, podríamos dividirlo en varios archivos y agregar múltiples etiquetas <script>
en la página html, pero entonces debemos asegurarnos de que los scripts se insertan en el orden correcto, debemos exportar el contenido de cada archivo en variables globales y perdemos el autocompletado entre archivos.
Existe una solución de baja tecnología para este problema: utilizar módulos nativos de JavaScript. Sin embargo, esto tiene un requisito: por razones de seguridad, los navegadores no aceptarán módulos en el contenido que se sirve a través del protocolo file
. Esto significa que debemos utilizar un servidor estático.
Comencemos un nuevo proyecto con la siguiente estructura de archivos:
hello_owl/ src/ index.html main.js owl.js root.js
Como ya se ha dicho, el archivo owl.js
se puede descargar desde la última versión publicada en https://github.com/odoo/owl/releases. Ten en cuenta que hay varios archivos y, en este caso, necesitamos uno de los dos archivos con el sufijo .iife
: están diseñados para usarse directamente en un navegador.
Ahora, index.html
debería contener lo siguiente:
<!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>
No es que la etiqueta de script main.js
tenga el atributo type="module"
. Esto significa que el navegador analizará el script como un módulo y cargará todas sus dependencias.
Aquí está el contenido de root.js
y main.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);
El archivo main.js
importa el archivo root.js
. Tenga en cuenta que la declaración de importación tiene un sufijo .js
, lo cual es importante. La mayoría de los editores de texto pueden comprender esta sintaxis y proporcionarán la función de autocompletado.
Ahora, para ejecutar este código, necesitamos servir la carpeta src
de forma estática. Una forma sencilla de hacerlo es usar, por ejemplo, la función SimpleHTTPServer
de Python:
$ cd src$ python -m SimpleHTTPServer 8022 # now content is available at localhost:8022
Otra forma más “javascript” de hacerlo es crear una aplicación npm
. Para ello, podemos añadir el siguiente archivo package.json
en la raíz del proyecto:
{ "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" }}
Ahora podemos instalar la herramienta serve
con el comando npm install
, y luego, iniciar un servidor estático con el simple comando npm run serve
.
Proyecto Javascript estándar
La configuración anterior funciona y es ciertamente buena para algunos casos de uso, incluida la creación rápida de prototipos. Sin embargo, carece de algunas funciones útiles, como la recarga en vivo, un conjunto de pruebas o la posibilidad de agrupar el código en un solo archivo.
Cada una de estas funciones, y muchas otras, se pueden realizar de muchas maneras diferentes. Como no es nada fácil configurar un proyecto de este tipo, aquí ofrecemos un ejemplo que puede utilizarse como punto de partida.
Nuestro proyecto Owl estándar tiene la siguiente estructura de archivos:
hello_owl/ public/ index.html src/ components/ Root.js main.js tests/ components/ Root.test.js helpers.js .gitignore package.json webpack.config.js
Este proyecto es una carpeta pública destinada a contener todos los recursos estáticos, como imágenes y estilos. La carpeta src contiene el código fuente de JavaScript y, por último, la carpeta test contiene el conjunto de pruebas.
Aquí está el contenido de index.html
:
<!DOCTYPE html><html lang="en"> <head> <title>Hello Owl</title> </head> <body></body></html>
Tenga en cuenta que aquí no hay etiquetas <script>
. Webpack las inyectará. Ahora, echemos un vistazo a los archivos de JavaScript:
// 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"));}
Finalmente, aquí están los archivos de configuración .gitignore
, package.json
y webpack.config.js
:
node_modules/package-lock.jsondist/
{ "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"), }), ], };};
Con esta configuración, ahora podemos utilizar los siguientes comandos de script:
npm run build # construye la aplicación completa en modo prod en dist/
npm run dev # inicia un servidor de desarrollo con livereload
npm run test # ejecuta el conjunto de pruebas jest