ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

浏览器require加载devextreme-react组件

2026/9/16 21:07:49 拓冰建站 浏览量
浏览器require加载devextreme-react组件

十几年前使用了devexpress公司的delphi元件,功能很强。它们的html元件devextreme,功能表现类似,也行强。

devextreme和devextreme-react,我使用的是23.2.3版本。

官方推荐的用法,都是要经过build:

npx devextreme-cli new react-app app-name
cd app-name
npm run start

经过约12秒的build,出来的html中有一个bundle.js,大小为12.5M。

它的模式是MPA,用babel把react组件转换成了js代码。

如果我要做SPA,不要每次都build,如何做?

devextreme和devextrem-react提供了cjs(es5)和esm(es6)两套代码,但是react本身没有esm的代码版本,如果devextreme使用esm版本,还是要经过babel转成es5来使用。

我们用es5的cjs版本吧。

(1)首先要在浏览器实现一个require函数,我是用breq - npm来改的。

(2)后台如何提供模组加载,我用nodejs实现静态文件。

会遇到几个问题:

(1)引用的组件不是相对路径,不是.、..、/打头的。

如:

import {TabPanel,Item as TabPanelItem} from 'devextreme-react/tab-panel.js';
import {locale,loadMessages} from 'devextreme/localization.js';

require需要仿照import实现一个importMap,建立文件映射表,如:

{"devextreme-react/tab-panel.js":'/devextreme-react/cjs/tab-panel.js',"devextreme/localization.js":'/devextreme-react/cjs/localization.js',
}

但是devextreme组件文件有500个以上,用绝对路径加载的也有好几十个,如果一个个加到importMap中也挺累的。可以实现一个目录映射来一网打尽,如:

"devextreme/":"/devextreme/cjs/",

实现后,发现其实import的importMap也支持这样的目录映射,好多文章没讲。

(2)引用的组件没有扩展名,如:

import {TabPanel,Item as TabPanelItem} from 'devextreme-react/tab-panel';
import {locale,loadMessages} from 'devextreme/localization';var _file_saver = require("./exporter/file_saver");
var _image_creator = require("./exporter/image_creator");
var _svg_creator = require("./exporter/svg_creator");
var _type = require("./core/utils/type");
var _deferred = require("./core/utils/deferred");
var _pdf_creator = require("./exporter/pdf_creator");

这需要后台的static函数支持这种检查,看看加上.js、.cjs扩展名是否存在此文件。

(3)引用的组件是个目录。

后台的static要尝试查找此目录下的index.js文件,如果没有index.js,看看有没有package.json文件,解析使用其中的main属性指向的文件。

组件是目录时,还导致一个额外的问题,浏览器发起者不知道这是个目录,以为是个文件,所以接下来再加载相对路径的组件时,就会导致路径错误,如:

let require('a/b/c'),实际是加载了/a/b/c/index.js,浏览器以为是来自/a/b/c.js,当前目录是/a/b,实际当前目录应该是/a/b/c,如果index.js中有一句require('../d.js'),浏览器就会解析为require('/a/d.js'),导致找不到文件,实际应该是a/b/d.js。

有两种方法解决:

a.后台的实际文件,返回到res的headers的location中,参考重定向的规范:http响应状态码(Header常见属性 — Location属性)_http location-CSDN博客。如:res.set('location',req.url);前台获得后,按location值设定当前目录。

b.在importMap中特殊配置一个映射,如:“a/b/c”:"/a/b/c/index.js"

(4)循环引用。就是一个引用文件还没解析执行完,又被其它文件引用到了。devextreme-react中发现2处,引用栈如下:

0: "/devextreme-react/cjs/core/component-base"
1: "/devextreme-react/cjs/core/template-manager"
2: "/devextreme-react/cjs/core/template-wrapper"
3: "/devextreme-react/cjs/core/component-base"

0: "/devextreme-react/cjs/core/component-base"
1: "/devextreme-react/cjs/core/template-manager"
2: "/devextreme-react/cjs/core/component-base"

devextreme中发现1处:

0: "/devextreme/cjs/ui/popup"
1: "/devextreme/cjs/ui/popup/ui.popup.full"
2: "/devextreme/cjs/ui/toolbar"
3: "/devextreme/cjs/ui/toolbar/ui.toolbar"
4: "/devextreme/cjs/ui/toolbar/strategy/toolbar.singleline"
5: "/devextreme/cjs/ui/toolbar/internal/ui.toolbar.menu"
6: "/devextreme/cjs/ui/popup"

为了避免循环引用的死循环栈溢出,require的cache需要在eval前就加入,不能在之后加入。

循环引用实际没有导致devextreme元件出bug,因为export的是一个object,加上js代码执行时的”Hoisting"(变量和函数提升),不马上使用require结果时没有问题。

如:\devextreme-react\cjs\core\template-wrapper.js

var component_base_1 = require("./component-base");
...
var TemplateWrapperComponent = function (_a) {component_base_1 = require("./component-base");//add by mustapha...
}

第1行代码不能删除,否则可能导致其它组件没有加载进来。TemplateWrapperComponent 中的那行其实也不用加,加了看起来更保险。

(5)require函数返回的module.exports如何处理default?

不要做任何处理,直接返回module.exports。

对于:

import {A} from '/x.js';
A.name='shanghai';
import B from '/y.js';
B.name='chongqing';
export default {A,B};
export {A,B};

banel转换后,首先会把module.__esModule设置为true,对import B,会用_interopRequireDefault函数为非模组化元件准备default值,后面统一用y['default']赋值给B。

"use strict";Object.defineProperty(exports, "__esModule", {value: true
});
Object.defineProperty(exports, "A", {enumerable: true,get: function get() {return _x.A;}
});
Object.defineProperty(exports, "B", {enumerable: true,get: function get() {return _y["default"];}
});
exports["default"] = void 0;
var _x = require("/x.js");
var _y = _interopRequireDefault(require("/y.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
_x.A.name = 'shanghai';
_y["default"].name = 'chongqing';
var _default = {A: _x.A,B: _y["default"]
};
exports["default"] = _default;