十几年前使用了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和esm两套代码,但是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扩展名是否存在此文件。
(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中。如: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';
import B from '/y.js';
banel转换后,首先会把module.__esModule设置为true,对第2句,会用_interopRequireDefault函数提取default。
var {A}=require('/x.js');
var B=_interopRequireDefault(require('/y.js'));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
}
}