odoo17 web.assets_web.min.js 赏析

odoo17 web.assets_web.min.js 赏析

前文说到,odoo17的前端js做了大量裁剪优化,最终打包成了一个文件assets.web.js

稍微看一下这个文件的结构

web.assets_web.min.js
行      1: /* /web/static/src/module_loader.js */
行    173: /* /web/static/lib/luxon/luxon.js */
行   4585: /* /web/static/lib/owl/owl.js */
行   9954: /* /web/static/lib/owl/odoo_module.js */
行   9960: /* /web/static/src/env.js */
行  10085: /* /web/static/src/session.js */
行  10094: /* /web/static/src/core/action_swiper/action_swiper.js */
行  10273: /* /web/static/src/core/assets.js */
中间省略几百行
行 119389: /* /web/static/src/main.js */
行 119399: /* /web/static/src/start.js */

中间的不太重要,几百个文件也看不过来,稍微分析一下开头和结尾。

1、module_loader.js

这是一个引导程序,也是一个立即执行函数,odoo执行的第一个js文件。

这个文件的作用:

定义了odoo这个全局变量,并且定义了它的三个属性:

  • odoo.debug
  • odoo.loader
  • odoo.define

最重要的莫过于odoo.define, 因为后面有几百个模块都是调用这个函数进行加载。而加载好的模块都放到了odoo.loader.modules字典中。大概有七百多个

web.js中定义的全局变量除了odoo,还有两个,分别是luxon 和owl, owl我们都清楚,luxon是一个时间处理库,有时间研究一下。

odoo.define 有三个参数,分别是名称 ,依赖, 函数

ModuleLoader类中定义了一个jobs集合,每执行一次odoo.define函数,都会去jobs中遍历一遍,将满足依赖关系的模块加载。

然后将name和实例化后的对象作为key和value放在了modules字典中。

这个跟服务的加载差不多。都要解决依赖的问题。

/***------------------------------------------------------------------------------* Odoo Web Boostrap Code*------------------------------------------------------------------------------*/
(function () {"use strict";class ModuleLoader {/** @type {Map<string,{fn: Function, deps: string[]}>} mapping name => deps/fn */factories = new Map();/** @type {Set<string>} names of modules waiting to be started */jobs = new Set();/** @type {Set<string>} names of failed modules */failed = new Set();/** @type {Map<string,any>} mapping name => value */modules = new Map();bus = new EventTarget();checkErrorProm = null;/*** @param {string} name* @param {string[]} deps* @param {Function} factory*/define(name, deps, factory) {if (typeof name !== "string") {throw new Error(`Invalid name definition: ${name} (should be a string)"`);}if (!(deps instanceof Array)) {throw new Error(`Dependencies should be defined by an array: ${deps}`);}if (typeof factory !== "function") {throw new Error(`Factory should be defined by a function ${factory}`);}if (!this.factories.has(name)) {this.factories.set(name, {deps,fn: factory,ignoreMissingDeps: globalThis.__odooIgnoreMissingDependencies,});this.addJob(name);this.checkErrorProm ||= Promise.resolve().then(() => {this.checkAndReportErrors();this.checkErrorProm = null;});}}addJob(name) {this.jobs.add(name);this.startModules();}findJob() {for (const job of this.jobs) {if (this.factories.get(job).deps.every((dep) => this.modules.has(dep))) {return job;}}return null;}startModules() {let job;while ((job = this.findJob())) {this.startModule(job);}}startModule(name) {const require = (name) => this.modules.get(name);this.jobs.delete(name);const factory = this.factories.get(name);let value = null;try {value = factory.fn(require);} catch (error) {this.failed.add(name);throw new Error(`Error while loading "${name}":\n${error}`);}this.modules.set(name, value);this.bus.dispatchEvent(new CustomEvent("module-started", { detail: { moduleName: name, module: value } }));}// 省略几万字。。。if (!globalThis.odoo) {globalThis.odoo = {};}const odoo = globalThis.odoo;if (odoo.debug && !new URLSearchParams(location.search).has("debug")) {// remove debug mode if not explicitely set in urlodoo.debug = "";}const loader = new ModuleLoader();odoo.define = loader.define.bind(loader);odoo.loader = loader;
})();

2、luxon.js

moment.js 的升级版,这里会生成一个全局的对象luxon, 用于处理时间和日期

3、owl.js

猫头鹰组件系统,熟悉odoo的都懂,这里会生成一个全局对象owl

4、odoo_module.js

这个文件有点意思,它将owl全局对象定义成了@odoo/owl 模块,我们经常从js中代码中看到下面这种ES6的写法,这样做可能是为了保证风格的统一吧

import { Component } from "@odoo/owl";
/* /web/static/lib/owl/odoo_module.js */
odoo.define("@odoo/owl", [], function() {"use strict";return owl;
});

5、env.js

这个文件非常重要,它定义了两个函数

makeEnv: 初始化env对象。

startServices: 启动所有的webService, 这个函数的实现非常巧妙,因为它要在不考虑服务加载顺序的前提下解决服务之间的依赖问题。 其中细节,值得一读。

不过这里只有这两个函数的定义,并没有执行这两个函数, 真正执行这两个函数的位置在 start.js中

    const env = makeEnv();await startServices(env);

服务启动完后, 会放入env的services对象中。 注意其中放的不是服务对象本身,而是start函数的返回值。

env.js

export function makeEnv() {return {bus: new EventBus(),services: {},debug: odoo.debug,get isSmall() {throw new Error("UI service not initialized!");},};
}// -----------------------------------------------------------------------------
// Service Launcher
// -----------------------------------------------------------------------------export async function startServices(env) {await Promise.resolve();const toStart = new Set();serviceRegistry.addEventListener("UPDATE", async (ev) => {// Wait for all synchronous code so that if new services that depend on// one another are added to the registry, they're all present before we// start them regardless of the order they're added to the registry.await Promise.resolve();const { operation, key: name, value: service } = ev.detail;if (operation === "delete") {// We hardly see why it would be usefull to remove a service.// Furthermore we could encounter problems with dependencies.// Keep it simple!return;}if (toStart.size) {const namedService = Object.assign(Object.create(service), { name });toStart.add(namedService);} else {await _startServices(env, toStart);}});await _startServices(env, toStart);
}async function _startServices(env, toStart) {if (startServicesPromise) {return startServicesPromise.then(() => _startServices(env, toStart));}const services = env.services;for (const [name, service] of serviceRegistry.getEntries()) {if (!(name in services)) {const namedService = Object.assign(Object.create(service), { name });toStart.add(namedService);}}// start as many services in parallel as possibleasync function start() {let service = null;const proms = [];while ((service = findNext())) {const name = service.name;toStart.delete(service);const entries = (service.dependencies || []).map((dep) => [dep, services[dep]]);const dependencies = Object.fromEntries(entries);let value;try {value = service.start(env, dependencies);} catch (e) {value = e;console.error(e);}if ("async" in service) {SERVICES_METADATA[name] = service.async;}if (value instanceof Promise) {proms.push(new Promise((resolve) => {value.then((val) => {services[name] = val || null;}).catch((error) => {services[name] = error;console.error("Can't load service '" + name + "' because:", error);}).finally(resolve);}));} else {services[service.name] = value || null;}}await Promise.all(proms);if (proms.length) {return start();}}startServicesPromise = start();await startServicesPromise;startServicesPromise = null;if (toStart.size) {const names = [...toStart].map((s) => s.name);const missingDeps = new Set();[...toStart].forEach((s) =>s.dependencies.forEach((dep) => {if (!(dep in services) && !names.includes(dep)) {missingDeps.add(dep);}}));const depNames = [...missingDeps].join(", ");throw new Error(`Some services could not be started: ${names}. Missing dependencies: ${depNames}`);}function findNext() {for (const s of toStart) {if (s.dependencies) {if (s.dependencies.every((d) => d in services)) {return s;}} else {return s;}}return null;}
}

6、session.js

// odoo.__session_info__ 是后端通过渲染模板生成的, 这也是前后端交互的一种方式,用的很少。export const session = odoo.__session_info__ || {};
delete odoo.__session_info__;

7、随便看一个odoo.define定义的模块

有几百个通过odoo.define定义的模块, 我们且看一个最简单的,也就是上面的这个session.js

下面这个文件是由py代码自动生成的,将ES6风格的js文件转化成 commonjs格式

其中require 是一个函数,在module_loader中定义的,根据名称,返回指定的模块的实例。

const require = (name) => this.modules.get(name);
/* /web/static/src/session.js */
odoo.define('@web/session', [], function(require) {'use strict';let __exports = {};const session = __exports.session = odoo.__session_info__ || {};delete odoo.__session_info__;return __exports;
});

每调用一次odoo.define,系统都会去循环遍历所有需要加载的模块,查找那些满足依赖关系的模块进行加载。

8、main.js

现在看看最后的两个文件。

这个文件是入口文件,按理说应该放在最后,可是odoo却把它放在了倒数第二,难道是为了证明模块的加载顺序并不会因为模块之间的依赖关系而影响模块加载吗?

/** @odoo-module **/import { startWebClient } from "./start";
import { WebClient } from "./webclient/webclient";startWebClient(WebClient);

startWebClient(WebClient); 当所有模块都加载到内存中之后执行这一句。前端的UI页面也就渲染出来了。

9、start.js

定义了startWebClient 函数供main.js调用

在startWebClient 中,还做了两件事,前文提到过

调用了env.js中的两个函数,初始化了env并加载了所有的服务。

    const env = makeEnv();await startServices(env);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/152241.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据中心标签的重要性

布线标签的实施是为了为用户今后的维护和管理带来最大的 便利&#xff0c;提高其管理水平和工作效率&#xff0c;减少网络配置时间&#xff0c;标签标识系统包括三个方面:标识分类及定义,标签和建立文档。 标签、标识的分类有哪些? 数据中心内的每一电缆、光缆、配线设备、端…

解决网络编程中的EOF违反协议问题:requests库与SSL错误案例分析

1. 问题背景 近期&#xff0c;一个用户在使用requests库进行网络编程时遭遇到了一个不寻常的问题&#xff0c;涉及SSL错误&#xff0c;并提示错误消息为SSLError(SSLEOFError(8, uEOF occurred in violation of protocol (_ssl.c:661)),))。该用户表示已经采取了多种方法来解决…

109.firefly-extboot的生成脚本

内核版本&#xff1a; 4.4.194 在firefly的sdk 2.5.1c及以后的版本都是extboot.img&#xff08;对应表中的extboot&#xff09; 但是之前的并不是&#xff0c;而且一个boot.img&#xff0c;&#xff08;对应表中rkboot&#xff09; rkboot的生成方法可以参考解决linux5.15编…

【数据机构】最小生成树(prim算法)

一.引例 在n个城市之间建设通信网络&#xff0c;至少需要架设多少条通信线路&#xff1f;如果每两个城市之间架设通信线路的造价是不一样的&#xff0c;那么如何设计才能使得总造价最小&#xff1f; 二.生成树与生成森林 生成树&#xff1a;n个顶点的连通图G的生成树是包含G中…

解决 vite 4 开发环境和生产环境打包后空白、配置axios跨域、nginx代理本地后端接口问题

1、解决打包本地无法访问空白 首先是pnpm build 打包后直接在dist访问&#xff0c;是访问不了的&#xff0c;需要开启服务 终端输入 npm install -g serve 然后再输入 serve -s dist 就可以访问了 但要保证 路由模式是&#xff1a;createWebHashHistory 和vite.conffig.j…

【MySQL】MVCC(多版本并发控制)详解

MVCC MVCC概述 MVCC&#xff0c;全称 Multi-Version Concurrency Control &#xff0c;即多版本并发控制。MVCC 是一种并发控制的方法&#xff0c;一般在数据库管理系统中&#xff0c;实现对数据库的并发访问&#xff0c;在编程语言中实现事务内存。 MVCC就是在ReadCommitte…

docker 容器

目录 简介 什么是docker 容器和镜像&#xff08;contalners&#xff09; 容器和虚拟机 了解docker三个重要概念 &#xff08;1&#xff09;image镜像 &#xff08;2&#xff09;container容器 &#xff08;3&#xff09;repostory仓库 docker的简单使用 安装docker …

车载通信架构 —— 新车载总线类型下(以太网)的通信架构

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消耗你的人和事&#xff0c;多看一眼都是你的不…

HTTP四种请求方式,状态码,请求和响应报文

1.get请求 一般用于获取数据请求参数在URL后面请求参数的大小有限制 2.post请求 一般用于修改数据提交的数据在请求体中提交数据的大小没有限制 3.put请求 一般用于添加数据 4.delete请求 一般用于删除数据 5.一次完整的http请求过程 域名解析&#xff1a;使用DNS协议…

如何进行手动脱壳

脱壳的目的就是找到被隐藏起来的OEP&#xff08;入口点&#xff09; 这里我一共总结了三种方法&#xff0c;都是些自己的理解希望对你们有用 单步跟踪法 一个程序加了壳后&#xff0c;我们需要找到真正的OEP入口点&#xff0c;先运行&#xff0c;找到假的OEP入口点后&#x…

多参数训练Isolation Forest

如果你要使用两个指标作为入参训练 Isolation Forest 模型&#xff0c;你需要将这两个指标合并成一个特征向量&#xff0c;然后将这个特征向量作为模型的输入。具体来说&#xff0c;你需要将每个数据点的两个指标组合成一个二元组&#xff0c;然后将这些二元组组成一个矩阵&…

SSD主控

《深入浅出SSD》学习中… 文章目录 《深入浅出SSD》学习中.....一、SSD主控二、PCIe和NVMe控制器前端子系统1.PCIe控制器2.NVMe控制器 一、SSD主控 就是类似电脑CPU的东西&#xff0c;在SSD中收取处理Host端的命令&#xff0c;管理NAND闪存 二、PCIe和NVMe控制器前端子系统 主…

AIGC之Stable Diffusion

AIGC是什么? AIGC:Artificial Intelligence Generated Content,生成式人工智能。通俗一点来讲,对AI下达指令任务,通过处理人的自然语言,自动生成图片、视频、音频等等。 Stable Diffusion 官网:https://stablediffusionweb.com/ 介绍:stablediffusionweb.com is an eas…

利用小批量训练的方法在子图中进行消息传递

如果用户只想更新图中的部分节点&#xff0c;可以先通过想要囊括的节点编号创建一个子图&#xff0c; 然后在子图上调用 update_all() 方法。例如&#xff1a; nid [0, 2, 3, 6, 7, 9] sg g.subgraph(nid) sg.update_all(message_func, reduce_func, apply_node_func)同时也…

关于Unity自带的保存简单且持久化数据PlayerPrefs类的使用

Unity的PlayerPrefs类是用于在游戏中保存和读取玩家偏好设置或其他简单数据的工具。它提供了一种简单的键值对存储方式&#xff0c;可以在游戏中持久化保存数据。 PlayerPrefs提供了三种类型的数据的处理&#xff1a;分别是int,float,string。 具体使用方法如下&#xff1a; …

深度学习之基于CT影像图像分割检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于CT影像的图像分割检测系统可以被设计成能够自动地检测出CT图像中的病变部位或解剖结构&#xff0c;以协助医生进…

4种经典的限流算法与集群限流

0、基础知识 1000毫秒内&#xff0c;允许2个请求&#xff0c;其他请求全部拒绝。 不拒绝就可能往db打请求&#xff0c;把db干爆~ interval 1000 rate 2&#xff1b; 一、固定窗口限流 固定窗口限流算法&#xff08;Fixed Window Rate Limiting Algorithm&#xff09;是…

Java自定义异常类详解及示例

引言&#xff1a; 在Java编程中&#xff0c;有时候我们需要处理一些特定的错误或非预期情况&#xff0c;而Java提供了自定义异常类的机制&#xff0c;使得我们能够根据业务需求创建和管理自定义的异常。本篇博客将详细讨论Java中自定义异常类的相关知识&#xff0c;包括…

原来Python内置了一个微型数据库,超实用!!!

更多精彩的Python文章请关注微信公众号&#xff1a;愤怒的it男 一、简单介绍 Python标准库提供了一个dbm模块。它允许用户使用键值对存储和检索数据&#xff0c;工作原理类似于字典&#xff0c;但不同之处在于它将数据存储在磁盘上&#xff0c;而不是在内存中。这使得它适用于…

自定义歌曲试听SeekBar

看到这个效果&#xff0c;可能会想到完全自定义一个控件&#xff0c;其实我们在系统Seekbar的基础上&#xff0c;将progressDrawable中progress背景设为透明后&#xff0c;叠加绘制试听状态下的进度区域即可 class PlayerSeekBar JvmOverloads constructor(context: Context,a…