Eliminate Python dependency: embed frontend assets in odoo-go
- Copy all OWL frontend assets (JS/CSS/XML/fonts/images) into frontend/ directory (2925 files, 43MB) — no more runtime reads from Python Odoo - Replace OdooAddonsPath config with FrontendDir pointing to local frontend/ - Rewire bundle.go, static.go, templates.go, webclient.go to read from frontend/ instead of external Python Odoo addons directory - Auto-detect frontend/ and build/ dirs relative to binary in main.go - Delete obsolete Python helper scripts (tools/*.py) The Go server is now fully self-contained: single binary + frontend/ folder. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"odoo-go/pkg/tools"
|
||||
)
|
||||
@@ -32,15 +33,16 @@ func init() {
|
||||
xmlFiles = loadAssetList("assets_xml.txt", assetsXMLFile)
|
||||
}
|
||||
|
||||
// loadXMLTemplate reads an XML template file from the Odoo addons paths.
|
||||
// loadXMLTemplate reads an XML template file from the frontend directory.
|
||||
func loadXMLTemplate(cfg *tools.Config, urlPath string) string {
|
||||
if cfg.FrontendDir == "" {
|
||||
return ""
|
||||
}
|
||||
rel := strings.TrimPrefix(urlPath, "/")
|
||||
for _, addonsDir := range cfg.OdooAddonsPath {
|
||||
fullPath := filepath.Join(addonsDir, rel)
|
||||
data, err := os.ReadFile(fullPath)
|
||||
if err == nil {
|
||||
return string(data)
|
||||
}
|
||||
fullPath := filepath.Join(cfg.FrontendDir, rel)
|
||||
data, err := os.ReadFile(fullPath)
|
||||
if err == nil {
|
||||
return string(data)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -92,15 +94,25 @@ func (s *Server) handleWebClient(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
|
||||
// Build script tags for all JS files (with cache buster)
|
||||
// Build script tags: module_loader must load first (defines odoo.loader),
|
||||
// then the concatenated bundle serves everything else in one request.
|
||||
// We suppress transient missing-dependency errors during loading by
|
||||
// temporarily replacing reportErrors with a no-op, then restore it
|
||||
// after the bundle has loaded.
|
||||
var scriptTags strings.Builder
|
||||
cacheBuster := "?v=odoo-go-1"
|
||||
for _, src := range jsFiles {
|
||||
if strings.HasSuffix(src, ".scss") {
|
||||
continue
|
||||
}
|
||||
scriptTags.WriteString(fmt.Sprintf(" <script type=\"text/javascript\" src=\"%s%s\"></script>\n", src, cacheBuster))
|
||||
}
|
||||
cacheBuster := fmt.Sprintf("?v=%d", time.Now().Unix())
|
||||
|
||||
// 1) module_loader.js — must run first to define odoo.define/odoo.loader
|
||||
scriptTags.WriteString(fmt.Sprintf(" <script type=\"text/javascript\" src=\"/web/static/src/module_loader.js%s\"></script>\n", cacheBuster))
|
||||
|
||||
// 2) Suppress transient reportErrors while the bundle loads
|
||||
scriptTags.WriteString(" <script>if (odoo.loader) { odoo.loader.__origReportErrors = odoo.loader.reportErrors.bind(odoo.loader); odoo.loader.reportErrors = function() {}; }</script>\n")
|
||||
|
||||
// 3) The concatenated JS bundle (all other modules + XML templates)
|
||||
scriptTags.WriteString(fmt.Sprintf(" <script type=\"text/javascript\" src=\"/web/assets/bundle.js%s\"></script>\n", cacheBuster))
|
||||
|
||||
// 4) Restore reportErrors and run a final check for genuine errors
|
||||
scriptTags.WriteString(" <script>if (odoo.loader && odoo.loader.__origReportErrors) { odoo.loader.reportErrors = odoo.loader.__origReportErrors; odoo.loader.reportErrors(odoo.loader.findErrors()); }</script>\n")
|
||||
|
||||
// Build link tags for CSS: compiled SCSS bundle + individual CSS files
|
||||
var linkTags strings.Builder
|
||||
@@ -140,20 +152,23 @@ func (s *Server) handleWebClient(w http.ResponseWriter, r *http.Request) {
|
||||
};
|
||||
odoo.loadMenusPromise = odoo.reloadMenus();
|
||||
|
||||
// Catch unhandled errors and log them
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
console.error('[odoo-go] Unhandled rejection:', e.reason);
|
||||
});
|
||||
|
||||
// Patch OWL to prevent infinite error-dialog recursion.
|
||||
// When ErrorDialog itself fails to render, stop retrying.
|
||||
window.__errorDialogCount = 0;
|
||||
var _origHandleError = null;
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (typeof owl !== 'undefined' && owl.App) {
|
||||
_origHandleError = owl.App.prototype.handleError;
|
||||
var _orig = owl.App.prototype.handleError;
|
||||
owl.App.prototype.handleError = function() {
|
||||
window.__errorDialogCount++;
|
||||
if (window.__errorDialogCount > 3) {
|
||||
console.error('[odoo-go] Error dialog recursion stopped. Check earlier errors for root cause.');
|
||||
console.error('[odoo-go] Error dialog recursion stopped.');
|
||||
return;
|
||||
}
|
||||
return _origHandleError.apply(this, arguments);
|
||||
return _orig.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -180,7 +195,7 @@ func (s *Server) buildSessionInfo(sess *Session) map[string]interface{} {
|
||||
"allowed_company_ids": []int64{sess.CompanyID},
|
||||
},
|
||||
"db": s.config.DBName,
|
||||
"registry_hash": "odoo-go-static",
|
||||
"registry_hash": fmt.Sprintf("odoo-go-%d", time.Now().Unix()),
|
||||
"server_version": "19.0-go",
|
||||
"server_version_info": []interface{}{19, 0, 0, "final", 0, "g"},
|
||||
"name": sess.Login,
|
||||
@@ -192,7 +207,8 @@ func (s *Server) buildSessionInfo(sess *Session) map[string]interface{} {
|
||||
"web.base.url": fmt.Sprintf("http://localhost:%d", s.config.HTTPPort),
|
||||
"active_ids_limit": 20000,
|
||||
"max_file_upload_size": 134217728,
|
||||
"home_action_id": false,
|
||||
"home_action_id": 1,
|
||||
"current_menu": 1,
|
||||
"support_url": "",
|
||||
"test_mode": false,
|
||||
"show_effect": true,
|
||||
|
||||
Reference in New Issue
Block a user