chore: 调整 lodash 引用大文件改成异步加载 (#7469)

This commit is contained in:
liaoxuezhi 2023-07-18 12:24:45 +08:00 committed by GitHub
parent 95d7451af6
commit f7373c5abf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 700 additions and 1607 deletions

View File

@ -282,6 +282,10 @@ function handleChange() {
amisScoped.unmount();
```
## vue
可以基于 SDK 版本封装成 component 供 vue 使用具体请参考示例https://github.com/aisuda/vue2-amis-demo
## react
初始项目请参考 <https://github.com/aisuda/amis-react-starter>

View File

@ -7,6 +7,7 @@ const package = require('./packages/amis/package.json');
const parserMarkdown = require('./scripts/md-parser');
const convertSCSSIE11 = require('./scripts/scss-ie11');
const parserCodeMarkdown = require('./scripts/code-md-parser');
const transformNodeEnvInline = require('./scripts/transform-node-env-inline');
fis.set('project.ignore', [
'public/**',
'scripts/**',
@ -28,10 +29,32 @@ Resource.extend({
}
const map = JSON.parse(resourceMap.substring(20, resourceMap.length - 2));
const self = this;
Object.keys(map.res).forEach(function (key) {
if (map.res[key].pkg) {
map.res[key].pkg = `${versionHash}-${map.res[key].pkg}`;
const item = map.res[key];
if (item.pkg) {
const pkgNode = self.getNode(item.pkg, 'pkg');
item.pkg = `${versionHash}-${item.pkg}`;
if (Array.isArray(item.deps) && pkgNode) {
item.deps = item.deps.filter(
dep =>
!pkgNode.has.find(id => {
const node = self.getNode(id);
const file = self.getFileById(id);
const moduleId =
(node.extras && node.extras.moduleId) ||
(file && file.moduleId) ||
id.replace(/\.js$/i, '');
return moduleId === dep;
})
);
if (!item.deps.length) {
delete item.deps;
}
}
}
});
Object.keys(map.pkg).forEach(function (key) {
@ -242,6 +265,10 @@ fis.match(
}
);
// 过滤掉 process.env.NODE_ENV 分支中无关代码
// 避免被分析成依赖,因为 fis 中是通过正则分析 require 语句的
fis.on('process:start', transformNodeEnvInline);
if (fis.project.currentMedia() === 'dev') {
fis.match('/packages/**/*.{ts,tsx,js}', {
isMod: true
@ -371,7 +398,7 @@ fis.match('{monaco-editor,amis,amis-core}/**', {
});
if (fis.project.currentMedia() === 'publish-sdk') {
const env = fis.media('publish-sdk');
const sdkEnv = fis.media('publish-sdk');
fis.on('compile:end', function (file) {
if (
@ -389,32 +416,32 @@ if (fis.project.currentMedia() === 'publish-sdk') {
}
});
env.get('project.ignore').push('sdk/**');
env.set('project.files', ['examples/sdk-placeholder.html']);
sdkEnv.get('project.ignore').push('sdk/**');
sdkEnv.set('project.files', ['examples/sdk-placeholder.html']);
env.match('/{examples,scss,src}/(**)', {
sdkEnv.match('/{examples,scss,src}/(**)', {
release: '/$1'
});
env.match('*.map', {
sdkEnv.match('*.map', {
release: false
});
env.match('/node_modules/(**)', {
sdkEnv.match('/node_modules/(**)', {
release: '/thirds/$1'
});
env.match('/node_modules/(*)/dist/(**)', {
sdkEnv.match('/node_modules/(*)/dist/(**)', {
release: '/thirds/$1/$2'
});
env.match('*.scss', {
sdkEnv.match('*.scss', {
parser: fis.plugin('sass', {
sourceMap: false
})
});
env.match('{*.ts,*.jsx,*.tsx,/examples/**.js,/src/**.js,/src/**.ts}', {
sdkEnv.match('{*.ts,*.jsx,*.tsx,/examples/**.js,/src/**.js,/src/**.ts}', {
parser: [
// docsGennerator,
fis.plugin('typescript', {
@ -440,19 +467,19 @@ if (fis.project.currentMedia() === 'publish-sdk') {
rExt: '.js'
});
env.match('/examples/mod.js', {
sdkEnv.match('/examples/mod.js', {
isMod: false,
optimizer: fis.plugin('terser')
});
env.match('*.{js,jsx,ts,tsx}', {
sdkEnv.match('*.{js,jsx,ts,tsx}', {
optimizer: fis.plugin('terser'),
moduleId: function (m, path) {
return fis.util.md5(package.version + 'amis-sdk' + path);
}
});
env.match('::package', {
sdkEnv.match('::package', {
packager: fis.plugin('deps-pack', {
'sdk.js': [
'examples/mod.js',
@ -481,6 +508,7 @@ if (fis.project.currentMedia() === 'publish-sdk') {
'!reactcss/**',
'!tinycolor2/**',
'!cropperjs/**',
'!react-json-view/**',
'!react-cropper/**',
'!jsbarcode/**',
'!amis-ui/lib/components/BarCode.js',
@ -496,7 +524,8 @@ if (fis.project.currentMedia() === 'publish-sdk') {
'!markdown-it-html5-media/**',
'!punycode/**',
'!office-viewer/**',
'!fflate/**'
'!fflate/**',
'!amis-formula/lib/doc.js'
],
'rich-text.js': [
@ -545,6 +574,8 @@ if (fis.project.currentMedia() === 'publish-sdk') {
],
'office-viewer.js': ['office-viewer/**', 'fflate/**'],
'json-view.js': 'react-json-view/**',
'fomula-doc.js': 'amis-formula/lib/doc.js',
'rest.js': [
'*.js',
@ -583,11 +614,11 @@ if (fis.project.currentMedia() === 'publish-sdk') {
]
});
env.match('{*.min.js,monaco-editor/min/**.js}', {
sdkEnv.match('{*.min.js,monaco-editor/min/**.js}', {
optimizer: null
});
env.match('monaco-editor/**.css', {
sdkEnv.match('monaco-editor/**.css', {
standard: false
});
@ -620,11 +651,11 @@ if (fis.project.currentMedia() === 'publish-sdk') {
}
});
env.match('/examples/loader.ts', {
sdkEnv.match('/examples/loader.ts', {
isMod: false
});
env.match('*', {
sdkEnv.match('*', {
domain: '.',
deploy: [
fis.plugin('skip-packed'),

View File

@ -43,6 +43,10 @@
"qs": "6.9.7"
},
"devDependencies": {
"@babel/generator": "^7.22.9",
"@babel/parser": "^7.22.7",
"@babel/traverse": "^7.22.8",
"@babel/types": "^7.22.5",
"@rollup/plugin-replace": "^5.0.1",
"@types/express": "^4.17.14",
"@types/jest": "^28.1.0",
@ -70,11 +74,11 @@
"fis3-preprocessor-js-require-css": "^0.1.3",
"fis3-preprocessor-js-require-file": "^0.1.3",
"husky": "^8.0.0",
"lint-staged": "^12.1.2",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"js-yaml": "^4.1.0",
"lerna": "^6.6.2",
"lint-staged": "^12.1.2",
"magic-string": "^0.26.7",
"marked": "^4.2.1",
"monaco-editor": "0.30.1",

View File

@ -47,7 +47,7 @@
],
"dependencies": {
"amis-formula": "^3.3.0-beta.4",
"classnames": "2.3.1",
"classnames": "2.3.2",
"file-saver": "^2.0.2",
"hoist-non-react-statics": "^3.3.2",
"lodash": "^4.17.15",

View File

@ -126,6 +126,37 @@ function transpileDynamicImportForCJS(options) {
};
}
// 参考https://github.com/theKashey/jsx-compress-loader/blob/master/src/index.js
function transpileReactCreateElement() {
return {
name: 'transpile-react-create-element',
enforce: 'post',
transform: (code, id) => {
if (
/\.(?:t|j)sx/.test(id) &&
code.indexOf('React.createElement') !== -1
) {
const separator = '\n\n;';
const appendText =
`\n` +
`var __react_jsx__ = require('react');\n` +
`var _J$X_ = (__react_jsx__["default"] || __react_jsx__).createElement;\n` +
`var _J$F_ = (__react_jsx__["default"] || __react_jsx__).Fragment;\n`;
const newSource = code
.replace(/React\.createElement\(/g, '_J$X_(')
.replace(/React\.createElement\(/g, '_J$F_(');
code = [appendText, newSource].join(separator);
}
return {
code
};
}
};
}
function getPlugins(format = 'esm') {
const overridePaths = ['amis-formula'].reduce(
(prev, current) => ({
@ -173,11 +204,12 @@ function getPlugins(format = 'esm') {
commonjs({
sourceMap: false
}),
format === 'esm' ? null : transpileReactCreateElement(),
license({
banner: `
${name} v${version}
Copyright 2018<%= moment().format('YYYY') > 2018 ? '-' + moment().format('YYYY') : null %> ${author}
`
})
];
].filter(item => item);
}

View File

@ -7,7 +7,7 @@ import {
registerAction
} from './Action';
import {createObject, filter, render} from '../index';
import {reject} from 'lodash';
import reject from 'lodash/reject';
export interface IAlertAction extends ListenerAction {
actionType: 'alert';

View File

@ -590,7 +590,9 @@ export default class Form extends React.Component<FormProps, object> {
.fetchInitData(initApi as any, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed,
onSuccess: () => {
onSuccess: (json: Payload, data: any) => {
store.setValues(data);
if (
!isEffectiveApi(initAsyncApi, store.data) ||
store.data[initFinishedField || 'finished']
@ -745,7 +747,12 @@ export default class Form extends React.Component<FormProps, object> {
);
}
reload(subPath?: string, query?: any, ctx?: any, silent?: boolean) {
async reload(
subPath?: string,
query?: any,
ctx?: any,
silent?: boolean
): Promise<any> {
if (query) {
return this.receive(query);
}
@ -763,44 +770,46 @@ export default class Form extends React.Component<FormProps, object> {
[initFinishedField || 'finished']: false
});
isEffectiveApi(initApi, store.data)
? store
.fetchInitData(initApi, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed,
silent,
onSuccess: () => {
if (
!isEffectiveApi(initAsyncApi, store.data) ||
store.data[initFinishedField || 'finished']
) {
return;
}
if (isEffectiveApi(initApi, store.data)) {
const result: Payload = await store.fetchInitData(initApi, store.data, {
successMessage: fetchSuccess,
errorMessage: fetchFailed,
silent,
onSuccess: (json: Payload, data: any) => {
store.setValues(data);
return until(
() => store.checkRemote(initAsyncApi, store.data),
(ret: any) => ret && ret[initFinishedField || 'finished'],
cancel => (this.asyncCancel = cancel)
);
}
})
.then(async (result: Payload) => {
// 派发初始化接口请求完成事件
await this.dispatchInited(result);
if (
!isEffectiveApi(initAsyncApi, store.data) ||
store.data[initFinishedField || 'finished']
) {
return;
}
if (result?.ok) {
this.initInterval(result);
store.reset(undefined, false);
}
})
: store.reset(undefined, false);
return until(
() => store.checkRemote(initAsyncApi, store.data),
(ret: any) => ret && ret[initFinishedField || 'finished'],
cancel => (this.asyncCancel = cancel)
);
}
});
// 派发初始化接口请求完成事件
await this.dispatchInited(result);
if (result?.ok) {
this.initInterval(result);
store.reset(undefined, false);
}
} else {
store.reset(undefined, false);
}
}
receive(values: object, name?: string, replace?: boolean) {
const {store} = this.props;
store.updateData(values, undefined, replace);
this.reload();
return this.reload();
}
silentReload(target?: string, query?: any) {
@ -1742,19 +1751,13 @@ export default class Form extends React.Component<FormProps, object> {
<input type="submit" style={{display: 'none'}} />
{debug
? render(
'form-debug-json',
extend(
{
type: 'json',
value: store.data,
ellipsisThreshold: 120,
className: cx('Form--debug')
},
/** 定制debug输出格式 */
isObject(debugConfig) ? debugConfig : {}
)
)
? render('form-debug-json', {
type: 'json',
value: store.data,
ellipsisThreshold: 120,
className: cx('Form--debug'),
...debugConfig
})
: null}
{render(
@ -2009,13 +2012,13 @@ export class FormRenderer extends Form {
scoped.close(target);
}
reload(
async reload(
target?: string,
query?: any,
ctx?: any,
silent?: boolean,
replace?: boolean
) {
): Promise<any> {
if (query) {
return this.receive(query, undefined, replace);
}
@ -2045,18 +2048,22 @@ export class FormRenderer extends Form {
) {
component.reload(subPath, subQuery, ctx);
} else if (target === '*') {
super.reload(target, query, ctx, silent);
await super.reload(target, query, ctx, silent);
const components = scoped.getComponents();
components.forEach(
(component: any) =>
component.reload && component.reload('', subQuery, ctx)
);
} else {
super.reload(target, query, ctx, silent);
return super.reload(target, query, ctx, silent);
}
}
receive(values: object, name?: string, replace?: boolean) {
async receive(
values: object,
name?: string,
replace?: boolean
): Promise<any> {
if (name) {
const scoped = this.context as IScopedContext;
const idx = name.indexOf('.');

View File

@ -18,7 +18,8 @@ import {
mapObject,
keyToPath,
isObject,
ValidateError
ValidateError,
extendObject
} from '../utils/helper';
import isEqual from 'lodash/isEqual';
import flatten from 'lodash/flatten';
@ -395,7 +396,7 @@ export const FormStore = ServiceStore.named('FormStore')
throw new ServerError(self.msg, json);
} else {
updateSavedData();
let ret = options && options.onSuccess && options.onSuccess(json);
let ret = options?.onSuccess?.(json, json.data);
if (ret?.then) {
ret = yield ret;
}

View File

@ -108,7 +108,7 @@ export const ServiceStore = iRendererStore
reInitData(data, replace);
self.hasRemoteData = true;
if (options && options.onSuccess) {
const ret = options.onSuccess(json);
const ret = options.onSuccess(json, data);
if (ret && ret.then) {
yield ret;
@ -215,7 +215,7 @@ export const ServiceStore = iRendererStore
);
} else {
if (options && options.onSuccess) {
const ret = options.onSuccess(json);
const ret = options.onSuccess(json, json.data);
if (ret && ret.then) {
yield ret;
@ -310,7 +310,7 @@ export const ServiceStore = iRendererStore
throw new ServerError(self.msg, json);
} else {
if (options && options.onSuccess) {
const ret = options.onSuccess(json);
const ret = options.onSuccess(json, json.data);
if (ret && ret.then) {
yield ret;

View File

@ -252,7 +252,7 @@ export interface fetchOptions {
errorMessage?: string;
autoAppend?: boolean;
beforeSend?: (data: any) => any;
onSuccess?: (json: Payload) => any;
onSuccess?: (json: Payload, data: any) => any;
onFailed?: (json: Payload) => any;
silent?: boolean;
[propName: string]: any;

View File

@ -5,12 +5,13 @@
import React, {Component, useEffect, useRef, useState} from 'react';
import cx from 'classnames';
import {findDOMNode, render} from 'react-dom';
import JsonView from 'react-json-view';
import {autorun, observable} from 'mobx';
import {observer} from 'mobx-react';
import {uuidv4} from './helper';
import position from './position';
export const JsonView = React.lazy(() => import('react-json-view'));
class Log {
@observable cat = '';
@observable level = '';

View File

@ -3,7 +3,7 @@
*/
import React from 'react';
import {isObject} from 'lodash';
import isObject from 'lodash/isObject';
import {ClassNamesFn} from '../theme';
interface IconCheckedSchema {

View File

@ -1,6 +1,6 @@
import {OptionValue, Option} from '../types';
import {isObject} from './helper';
import {isEqual} from 'lodash';
import isEqual from 'lodash/isEqual';
export function matchOptionValue(
a: OptionValue,

View File

@ -12,7 +12,7 @@ import {
RendererInfoResolveEventContext
} from '../plugin';
import {autobind} from '../util';
import {omit} from 'lodash';
import omit from 'lodash/omit';
export interface SubEditorProps {
store: EditorStoreType;

View File

@ -25,7 +25,7 @@ import type {Schema} from 'amis';
import type {DataScope} from 'amis-core';
import type {RendererConfig} from 'amis-core';
import type {SchemaCollection} from 'amis';
import {omit} from 'lodash';
import omit from 'lodash/omit';
// 创建 Node Store 并构建成树
export function makeWrapper(

View File

@ -1,7 +1,8 @@
import {registerEditorPlugin} from '../manager';
import {BaseEventContext, BasePlugin, BasicToolbarItem} from '../plugin';
import React from 'react';
import JsonView, {InteractionProps} from 'react-json-view';
import {InteractionProps} from 'react-json-view';
export const JsonView = React.lazy(() => import('react-json-view'));
/**
*
@ -98,16 +99,18 @@ export class DataDebugPlugin extends BasePlugin {
</ul>
</div>
<div className="aeDataChain-main">
<JsonView
name={false}
src={stacks[index]}
enableClipboard={false}
iconStyle="square"
onAdd={index === 0 && !readOnly ? emitChange : false}
onEdit={index === 0 && !readOnly ? emitChange : false}
onDelete={index === 0 && !readOnly ? emitChange : false}
collapsed={2}
/>
<React.Suspense fallback={<div>...</div>}>
<JsonView
name={false}
src={stacks[index]}
enableClipboard={false}
iconStyle="square"
onAdd={index === 0 && !readOnly ? emitChange : false}
onEdit={index === 0 && !readOnly ? emitChange : false}
onDelete={index === 0 && !readOnly ? emitChange : false}
collapsed={2}
/>
</React.Suspense>
</div>
</div>
);

View File

@ -9,7 +9,8 @@ import isPlainObject from 'lodash/isPlainObject';
import isNumber from 'lodash/isNumber';
import type {Schema} from 'amis';
import type {SchemaObject} from 'amis';
import {assign, cloneDeep} from 'lodash';
import assign from 'lodash/assign';
import cloneDeep from 'lodash/cloneDeep';
import {getGlobalData} from 'amis-theme-editor-helper';
import {isExpression, resolveVariableAndFilter} from 'amis-core';
import type {VariableItem} from 'amis-ui';

View File

@ -23,7 +23,10 @@ import {
RendererPluginAction,
RendererPluginEvent
} from 'amis-editor-core';
import {flattenDeep, fromPairs, isObject, remove} from 'lodash';
import flattenDeep from 'lodash/flattenDeep';
import fromPairs from 'lodash/fromPairs';
import isObject from 'lodash/isObject';
import remove from 'lodash/remove';
import type {ButtonSchema} from 'amis';
import type {FormSchema, SchemaObject} from 'amis';
import {findTree} from 'amis';

View File

@ -10,7 +10,8 @@ import {
} from 'amis-editor-core';
import {getSchemaTpl} from 'amis-editor-core';
import {isObject, isString} from 'lodash';
import isObject from 'lodash/isObject';
import isString from 'lodash/isString';
import defaultConfig, {
OperationMap
} from 'amis-ui/lib/components/condition-builder/config';

View File

@ -15,7 +15,7 @@ import {
tipedLabel
} from 'amis-editor-core';
import type {DSField} from 'amis-editor-core';
import {fromPairs} from 'lodash';
import fromPairs from 'lodash/fromPairs';
import type {TabsSchema} from 'amis';
import type {SchemaObject} from 'amis';
import {remarkTpl} from '../component/BaseControl';

View File

@ -11,7 +11,8 @@ import {
Spinner
} from 'amis';
import {FormControlProps, Renderer, RendererProps} from 'amis-core';
import {debounce, remove} from 'lodash';
import debounce from 'lodash/debounce';
import remove from 'lodash/remove';
import React from 'react';
import {EditorManager, EditorNodeType, autobind} from 'amis-editor-core';
import type {DSField, DSFieldGroup} from 'amis-editor-core';

View File

@ -7,7 +7,8 @@ import {findDOMNode} from 'react-dom';
import cx from 'classnames';
import {FormItem, Button, Icon, FormControlProps, autobind} from 'amis';
import {clone, remove} from 'lodash';
import clone from 'lodash/clone';
import remove from 'lodash/remove';
import {GoConfigControl} from './GoConfigControl';
import Sortable from 'sortablejs';

View File

@ -16,7 +16,8 @@ import type {SchemaCollection} from 'amis';
import type {IScopedContext} from 'amis-core';
import type {FormSchema} from 'amis';
import type {FormControlProps} from 'amis-core';
import {fromPairs, some} from 'lodash';
import fromPairs from 'lodash/fromPairs';
import some from 'lodash/some';
export interface SwitchMoreProps extends FormControlProps {
className?: string;

View File

@ -7,7 +7,8 @@ import {FormControlProps, FormItem, styleMap} from 'amis-core';
// @ts-ignore
import {parse as cssParse} from 'amis-postcss';
import {PlainObject} from './types';
import {debounce, isEmpty} from 'lodash';
import debounce from 'lodash/debounce';
import isEmpty from 'lodash/isEmpty';
import {Icon} from '../../icons/index';
import editorFactory from './themeLanguage';
import cx from 'classnames';

View File

@ -1,5 +1,5 @@
import {PlainObject} from 'amis-core';
import {isEmpty} from 'lodash';
import isEmpty from 'lodash/isEmpty';
const conf: any = {
ws: '[ \t\n\r\f]*',

View File

@ -6,7 +6,7 @@ import {
} from 'amis-editor-core';
import React from 'react';
import {buildApi, Html} from 'amis';
import {get} from 'lodash';
import get from 'lodash/get';
setSchemaTpl('api', (patch: any = {}) => {
const {name, label, value, description, sampleBuilder, apiDesc, ...rest} =

View File

@ -9,10 +9,13 @@ import {
import type {DSField} from 'amis-editor-core';
import type {SchemaObject} from 'amis';
import flatten from 'lodash/flatten';
import _ from 'lodash';
import {InputComponentName} from '../component/InputComponentName';
import {FormulaDateType} from '../renderer/FormulaControl';
import {VariableItem} from 'amis-ui/lib/components/formula/Editor';
import reduce from 'lodash/reduce';
import map from 'lodash/map';
import omit from 'lodash/omit';
import keys from 'lodash/keys';
/**
* @deprecated switch
@ -585,7 +588,7 @@ setSchemaTpl(
});
}
if (schema.options) {
let optionItem = _.reduce(
let optionItem = reduce(
schema.options,
function (result, item) {
return {...result, ...item};
@ -594,12 +597,12 @@ setSchemaTpl(
);
delete optionItem?.$$id;
optionItem = _.omit(
optionItem = omit(
optionItem,
_.map(children, item => item?.label)
map(children, item => item?.label)
);
let otherItem = _.map(_.keys(optionItem), item => ({
let otherItem = map(keys(optionItem), item => ({
label:
item === 'label' ? '选项文本' : item === 'value' ? '选项值' : item,
value: item,

View File

@ -1,5 +1,5 @@
import {setSchemaTpl, getSchemaTpl, defaultValue} from 'amis-editor-core';
import {isObject} from 'lodash';
import isObject from 'lodash/isObject';
import {tipedLabel} from 'amis-editor-core';
setSchemaTpl('horizontal-align', {

View File

@ -38,7 +38,7 @@
"@rc-component/mini-decimal": "^1.0.1",
"amis-core": "^3.3.0-beta.4",
"amis-formula": "^3.3.0-beta.4",
"classnames": "2.3.1",
"classnames": "2.3.2",
"codemirror": "^5.63.0",
"downshift": "6.1.12",
"echarts": "5.4.0",
@ -64,7 +64,7 @@
"react-hook-form": "7.39.0",
"react-json-view": "1.21.3",
"react-overlays": "5.1.1",
"react-textarea-autosize": "8.3.3",
"react-textarea-autosize": "8.5.2",
"react-transition-group": "4.4.2",
"react-visibility-sensor": "5.1.1",
"sortablejs": "1.15.0",

View File

@ -30,6 +30,7 @@ const external = id =>
`^(?:${Object.keys(dependencies)
.concat([
'linkify-it',
'react-is',
'markdown-it',
'markdown-it-html5-media',
'mdurl',
@ -145,6 +146,37 @@ function transpileDynamicImportForCJS(options) {
};
}
// 参考https://github.com/theKashey/jsx-compress-loader/blob/master/src/index.js
function transpileReactCreateElement() {
return {
name: 'transpile-react-create-element',
enforce: 'post',
transform: (code, id) => {
if (
/\.(?:t|j)sx/.test(id) &&
code.indexOf('React.createElement') !== -1
) {
const separator = '\n\n;';
const appendText =
`\n` +
`var __react_jsx__ = require('react');\n` +
`var _J$X_ = (__react_jsx__["default"] || __react_jsx__).createElement;\n` +
`var _J$F_ = (__react_jsx__["default"] || __react_jsx__).Fragment;\n`;
const newSource = code
.replace(/React\.createElement\(/g, '_J$X_(')
.replace(/React\.createElement\(/g, '_J$F_(');
code = [appendText, newSource].join(separator);
}
return {
code
};
}
};
}
function getPlugins(format = 'esm') {
const overridePaths = ['amis-formula', 'amis-core'].reduce(
(prev, current) => ({
@ -194,13 +226,16 @@ function getPlugins(format = 'esm') {
commonjs({
sourceMap: false
}),
format === 'esm' ? null : transpileReactCreateElement(),
license({
banner: `
${name} v${version}
Copyright 2018<%= moment().format('YYYY') > 2018 ? '-' + moment().format('YYYY') : null %> ${author}
`
})
];
].filter(item => item);
}
function processSass(context, payload) {

View File

@ -8,7 +8,7 @@ import {
Controller as ReactHookFormController,
RegisterOptions
} from 'react-hook-form';
import {method} from 'lodash';
import method from 'lodash/method';
export interface FormFieldProps extends LocaleProps, ThemeProps {
mode?: 'normal' | 'horizontal';

View File

@ -10,7 +10,7 @@ import {isObject} from 'amis-core';
import {validations} from 'amis-core';
import {Icon} from './icons';
import {isObjectShallowModified} from 'amis-core';
import {isEmpty} from 'lodash';
import isEmpty from 'lodash/isEmpty';
export type textPositionType = 'left' | 'right';

View File

@ -4,7 +4,7 @@
import React from 'react';
import Sortable from 'sortablejs';
import {findDOMNode} from 'react-dom';
import {cloneDeep} from 'lodash';
import cloneDeep from 'lodash/cloneDeep';
import {Option, Options} from './Select';
import {ThemeProps, themeable} from 'amis-core';

View File

@ -2,7 +2,9 @@
* ()
*/
import React from 'react';
import {cloneDeep, isEqual, omit} from 'lodash';
import cloneDeep from 'lodash/cloneDeep';
import isEqual from 'lodash/isEqual';
import omit from 'lodash/omit';
import {Option, Options} from './Select';
import {ThemeProps, themeable} from 'amis-core';

View File

@ -1,5 +1,4 @@
import React from 'react';
import _ from 'lodash';
import isInteger from 'lodash/isInteger';
import debounce from 'lodash/debounce';
import moment from 'moment';
@ -8,6 +7,7 @@ import {Icon} from './icons';
import {uncontrollable} from 'amis-core';
import {autobind, isMobile} from 'amis-core';
import {LocaleProps, localeable} from 'amis-core';
import chain from 'lodash/chain';
export interface HistoryRecord {
/** 历史记录值 */
@ -211,7 +211,7 @@ export class SearchBox extends React.Component<SearchBoxProps, SearchBoxState> {
try {
const storageValues = localStorage.getItem(key);
return _.chain(storageValues ? JSON.parse(storageValues) : [])
return chain(storageValues ? JSON.parse(storageValues) : [])
.uniqBy('value')
.orderBy(['timestamp'], ['desc'])
.slice(0, limit)
@ -257,7 +257,7 @@ export class SearchBox extends React.Component<SearchBoxProps, SearchBoxState> {
try {
const {key, limit} = this.getHistoryOptions();
const newDatasource = _.chain([
const newDatasource = chain([
...datasource,
{value, timestamp: moment().unix()}
])

View File

@ -7,7 +7,8 @@ import {Option} from './Select';
import {resolveVariable} from 'amis-core';
import {localeable} from 'amis-core';
import VirtualList, {AutoSizer, RenderedRows} from './virtual-list';
import {isEqual, forEach} from 'lodash';
import isEqual from 'lodash/isEqual';
import forEach from 'lodash/forEach';
export interface TableSelectionProps extends BaseSelectionProps {
/** 是否为结果渲染列表 */

View File

@ -1,13 +1,10 @@
import React from 'react';
import {
intersectionWith,
differenceWith,
includes,
debounce,
result,
isEqual,
unionWith
} from 'lodash';
import intersectionWith from 'lodash/intersectionWith';
import differenceWith from 'lodash/differenceWith';
import includes from 'lodash/includes';
import debounce from 'lodash/debounce';
import isEqual from 'lodash/isEqual';
import unionWith from 'lodash/unionWith';
import {ThemeProps, themeable, findTree} from 'amis-core';
import {BaseSelectionProps, BaseSelection, ItemRenderStates} from './Selection';

View File

@ -2,7 +2,7 @@
* Tranasfer搜索
*/
import React from 'react';
import {debounce} from 'lodash';
import debounce from 'lodash/debounce';
import {ThemeProps, themeable} from 'amis-core';
import {Icon} from './icons';

View File

@ -14,7 +14,6 @@ import {
eachTree
} from 'amis-core';
import {functionDocs} from 'amis-formula';
import {doc} from 'amis-formula/lib/doc';
import type {FunctionDocMap} from 'amis-formula/lib/types';
import {FormulaPlugin, editorFactory} from './plugin';
@ -107,6 +106,7 @@ export interface FunctionProps {
}
export interface FormulaState {
functions: FuncGroup[];
focused: boolean;
isCodeMode: boolean;
expandTree: boolean;
@ -121,9 +121,11 @@ export class FormulaEditor extends React.Component<
focused: false,
isCodeMode: false,
expandTree: false,
normalizeVariables: []
normalizeVariables: [],
functions: []
};
editorPlugin?: FormulaPlugin;
unmounted: boolean = false;
static buildDefaultFunctions(
doc: Array<{
@ -244,6 +246,7 @@ export class FormulaEditor extends React.Component<
componentDidMount(): void {
const {variables} = this.props;
this.normalizeVariables(variables as VariableItem[]);
this.buildFunctions();
}
componentDidUpdate(
@ -254,10 +257,33 @@ export class FormulaEditor extends React.Component<
if (prevProps.variables !== this.props.variables) {
this.normalizeVariables(this.props.variables as VariableItem[]);
}
if (prevProps.functions !== this.props.functions) {
this.buildFunctions();
}
}
componentWillUnmount() {
this.editorPlugin?.dispose();
this.unmounted = true;
}
async buildFunctions() {
const {doc} = await import('amis-formula/lib/doc');
if (this.unmounted) {
return;
}
const customFunctions = Array.isArray(this.props.functions)
? this.props.functions
: [];
const functionList = [
...FormulaEditor.buildDefaultFunctions(doc),
...FormulaEditor.buildCustomFunctions(functionDocs),
...customFunctions
];
this.setState({
functions: functionList
});
}
normalizeVariables(variables?: Array<VariableItem>) {
@ -414,13 +440,13 @@ export class FormulaEditor extends React.Component<
classPrefix,
selfVariableName
} = this.props;
const {focused, isCodeMode, expandTree, normalizeVariables} = this.state;
const customFunctions = Array.isArray(functions) ? functions : [];
const functionList = [
...FormulaEditor.buildDefaultFunctions(doc),
...FormulaEditor.buildCustomFunctions(functionDocs),
...customFunctions
];
const {
focused,
isCodeMode,
expandTree,
normalizeVariables,
functions: functionList
} = this.state;
return (
<div

View File

@ -27,6 +27,10 @@ export function FuncList(props: FuncListProps) {
const [filteredFuncs, setFiteredFuncs] = React.useState(props.data);
const [activeFunc, setActiveFunc] = React.useState<any>(null);
React.useEffect(() => {
setFiteredFuncs(props.data);
}, [props.data]);
function onSearch(term: string) {
const filtered = props.data
.map(item => {

View File

@ -101,7 +101,7 @@ import NewEdit from '../icons/new-edit.svg';
import RotateLeft from '../icons/rotate-left.svg';
import RotateRight from '../icons/rotate-right.svg';
import ScaleOrigin from '../icons/scale-origin.svg';
import {isObject} from 'lodash';
import isObject from 'lodash/isObject';
// 兼容原来的用法,后续不直接试用。

View File

@ -364,7 +364,7 @@ test('doAction:form setValue', async () => {
expect(container).toMatchSnapshot();
});
test('doAction:form reload', async () => {
test('doAction:form reload default', async () => {
const notify = jest.fn();
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
@ -427,31 +427,33 @@ test('doAction:form reload', async () => {
)
);
fireEvent.change(container.querySelector('[name="author"]')!, {
await wait(200); // 等待 initApi 加载完
expect(
(container.querySelector('[name="author"]') as HTMLInputElement).value
).toEqual('fex');
const author: HTMLInputElement = container.querySelector('[name="author"]')!;
fireEvent.change(author, {
target: {value: 'amis'}
});
await waitFor(() => {
expect((container.querySelector('[name="author"]') as any)?.value).toEqual(
'amis'
);
});
expect(
(container.querySelector('[name="author"]') as HTMLInputElement).value
).toEqual('amis');
expect(container).toMatchSnapshot();
// expect(container).toMatchSnapshot();
await waitFor(() => {
expect(getByText('刷新表单')).toBeInTheDocument();
});
await wait(200);
expect(getByText('刷新表单')).toBeInTheDocument();
fireEvent.click(getByText('刷新表单'));
fireEvent.click(getByText(/刷新表单/));
await wait(200);
await waitFor(() => {
expect((container.querySelector('[name="author"]') as any)?.value).toEqual(
'fex'
);
});
expect(
(container.querySelector('[name="author"]') as HTMLInputElement).value
).toEqual('fex');
expect(container).toMatchSnapshot();
// expect(container).toMatchSnapshot();
});
test('doAction:form reload with data', async () => {
@ -525,34 +527,23 @@ test('doAction:form reload with data', async () => {
)
);
fireEvent.change(container.querySelector('[name="author"]')!, {
await wait(200);
const author: HTMLInputElement = container.querySelector('[name="author"]')!;
expect(author).toBeInTheDocument();
fireEvent.change(author, {
target: {value: 'amis'}
});
await waitFor(() => {
expect((container.querySelector('[name="author"]') as any)?.value).toEqual(
'amis'
);
});
await wait(200);
expect(author.value).toEqual('amis');
expect(container).toMatchSnapshot();
expect(getByText('刷新表单')).toBeInTheDocument();
await waitFor(() => {
expect(getByText('刷新表单')).toBeInTheDocument();
});
fireEvent.click(getByText('刷新表单'));
await wait(200);
fireEvent.click(getByText(/刷新表单/));
await waitFor(() => {
expect((container.querySelector('[name="author"]') as any)?.value).toEqual(
'fex'
);
expect((container.querySelector('[name="age"]') as any)?.value).toEqual(
'18'
);
});
expect(container).toMatchSnapshot();
expect(author.value).toEqual('fex');
expect((container.querySelector('[name="age"]') as any)?.value).toEqual('18');
});
test('doAction:form reset', async () => {
@ -707,6 +698,7 @@ test('doAction:form clear', async () => {
)
);
await wait(200);
await waitFor(() => {
expect(getByText('清空表单')).toBeInTheDocument();
});

View File

@ -96,6 +96,7 @@ test('EventAction:inputRange', async () => {
)
);
await wait(200);
const inputs = container.querySelector('.cxd-InputRange-input input')!;
// input change

View File

@ -221,7 +221,157 @@ exports[`Renderer:input table 1`] = `
<div
class="cxd-Form-item cxd-Form-item--normal"
data-role="form-item"
/>
>
<div
class="cxd-InputTable cxd-Form-control"
>
<div
class="cxd-Table"
>
<div
class="cxd-Table-contentWrap"
>
<div
class="cxd-Table-content"
>
<table
class="cxd-Table-table"
>
<colgroup>
<col
data-index="3"
/>
<col
data-index="4"
/>
</colgroup>
<thead>
<tr
class=""
>
<th
class=""
data-index="3"
>
<div
class="cxd-TableCell--title"
>
<span
class="cxd-TplField"
>
<span>
A
</span>
</span>
</div>
<div
class="cxd-Table-content-colDragLine"
data-index="3"
/>
</th>
<th
class=""
data-index="4"
>
<div
class="cxd-TableCell--title"
>
<span
class="cxd-TplField"
>
<span>
B
</span>
</span>
</div>
<div
class="cxd-Table-content-colDragLine"
data-index="4"
/>
</th>
</tr>
</thead>
<tbody>
<tr
class="cxd-Table-tr--odd cxd-Table-tr--1th"
data-id="1"
data-index="0"
>
<td
class=""
>
<span
class="cxd-PlainField"
>
a1
</span>
</td>
<td
class=""
>
<span
class="cxd-PlainField"
>
b1
</span>
</td>
</tr>
<tr
class="cxd-Table-tr--even cxd-Table-tr--1th"
data-id="2"
data-index="1"
>
<td
class=""
>
<span
class="cxd-PlainField"
>
a2
</span>
</td>
<td
class=""
>
<span
class="cxd-PlainField"
>
b2
</span>
</td>
</tr>
<tr
class="cxd-Table-tr--odd cxd-Table-tr--1th"
data-id="3"
data-index="2"
>
<td
class=""
>
<span
class="cxd-PlainField"
>
a3
</span>
</td>
<td
class=""
>
<span
class="cxd-PlainField"
>
b3
</span>
</td>
</tr>
</tbody>
</table>
</div>
<span />
</div>
</div>
</div>
</div>
</form>
</div>
<div

View File

@ -2,7 +2,7 @@ import React = require('react');
import {fireEvent, render, screen} from '@testing-library/react';
import '../../../src';
import {render as amisRender} from '../../../src';
import {makeEnv} from '../../helper';
import {makeEnv, wait} from '../../helper';
test('Renderer:input-formula', async () => {
const {container, findByText, findByDisplayValue} = render(
@ -78,6 +78,7 @@ test('Renderer:input-formula', async () => {
)
);
await wait(200);
expect(container).toMatchSnapshot();
await findByDisplayValue('SUM(1 + 2)');
@ -103,7 +104,7 @@ test('Renderer:input-formula', async () => {
// });
});
test('Renderer:input-formula button', () => {
test('Renderer:input-formula button', async () => {
const {container} = render(
amisRender(
{
@ -179,10 +180,11 @@ test('Renderer:input-formula button', () => {
)
);
await wait(200);
expect(container).toMatchSnapshot();
});
test('Renderer:input-formula input-group', () => {
test('Renderer:input-formula input-group', async () => {
const {container} = render(
amisRender(
{
@ -258,5 +260,6 @@ test('Renderer:input-formula input-group', () => {
)
);
await wait(200);
expect(container).toMatchSnapshot();
});

View File

@ -14,7 +14,7 @@ afterEach(() => {
clearStoresCache();
});
test('Renderer:input table', () => {
test('Renderer:input table', async () => {
const {container} = render(
amisRender(
{
@ -62,6 +62,7 @@ test('Renderer:input table', () => {
)
);
await wait(300);
replaceReactAriaIds(container);
expect(container).toMatchSnapshot();
});
@ -175,6 +176,7 @@ test('Renderer:input table add', async () => {
)
);
await wait(200);
const add = await findByText(/新增/);
fireEvent.click(add);

View File

@ -1,10 +1,10 @@
import React = require('react');
import {render} from '@testing-library/react';
import {render, waitFor} from '@testing-library/react';
import '../../src';
import {render as amisRender} from '../../src';
import {makeEnv} from '../helper';
import {makeEnv, wait} from '../helper';
test('Renderer:json', () => {
test('Renderer:json', async () => {
const {container} = render(
amisRender(
{
@ -25,5 +25,6 @@ test('Renderer:json', () => {
)
);
await wait(1000);
expect(container).toMatchSnapshot();
});

View File

@ -41,7 +41,7 @@
"amis-ui": "^3.3.0-beta.4",
"attr-accept": "2.2.2",
"blueimp-canvastoblob": "2.1.0",
"classnames": "2.3.1",
"classnames": "2.3.2",
"downshift": "6.1.12",
"echarts": "5.4.0",
"echarts-stat": "^1.2.0",

View File

@ -114,6 +114,37 @@ function transpileDynamicImportForCJS(options) {
};
}
// 参考https://github.com/theKashey/jsx-compress-loader/blob/master/src/index.js
function transpileReactCreateElement() {
return {
name: 'transpile-react-create-element',
enforce: 'post',
transform: (code, id) => {
if (
/\.(?:t|j)sx/.test(id) &&
code.indexOf('React.createElement') !== -1
) {
const separator = '\n\n;';
const appendText =
`\n` +
`var __react_jsx__ = require('react');\n` +
`var _J$X_ = (__react_jsx__["default"] || __react_jsx__).createElement;\n` +
`var _J$F_ = (__react_jsx__["default"] || __react_jsx__).Fragment;\n`;
const newSource = code
.replace(/React\.createElement\(/g, '_J$X_(')
.replace(/React\.createElement\(/g, '_J$F_(');
code = [appendText, newSource].join(separator);
}
return {
code
};
}
};
}
function getPlugins(format = 'esm') {
const overridePaths = [
'amis-formula',
@ -169,11 +200,12 @@ function getPlugins(format = 'esm') {
commonjs({
sourceMap: false
}),
format === 'esm' ? null : transpileReactCreateElement(),
license({
banner: `
${name} v${version}
Copyright 2018<%= moment().format('YYYY') > 2018 ? '-' + moment().format('YYYY') : null %> ${author}
`
})
];
].filter(item => item);
}

View File

@ -15,7 +15,7 @@ import {ActionObject} from 'amis-core';
import {FormOptionsSchema} from '../../Schema';
import {supportStatic} from './StaticHoc';
import find from 'lodash/find';
import {isEmpty} from 'lodash';
import isEmpty from 'lodash/isEmpty';
/**
*

View File

@ -1,10 +1,12 @@
import React from 'react';
import {Renderer, RendererProps} from 'amis-core';
import JsonView, {InteractionProps} from 'react-json-view';
import type {InteractionProps} from 'react-json-view';
import {autobind, getPropValue, noop} from 'amis-core';
import {BaseSchema} from '../Schema';
import {resolveVariableAndFilter, isPureVariable} from 'amis-core';
export const JsonView = React.lazy(() => import('react-json-view'));
/**
* JSON
* https://aisuda.bce.baidu.com/amis/zh-CN/components/json
@ -158,21 +160,23 @@ export class JSONField extends React.Component<JSONProps, object> {
{typeof data === 'undefined' || data === null ? (
placeholder
) : (
<JsonView
name={false}
src={data}
theme={(jsonThemeValue as any) ?? 'rjv-default'}
shouldCollapse={this.shouldExpandNode}
enableClipboard={enableClipboard}
displayDataTypes={displayDataTypes}
collapseStringsAfterLength={ellipsisThreshold}
iconStyle={iconStyle}
quotesOnKeys={quotesOnKeys}
sortKeys={sortKeys}
onEdit={name && mutable ? this.emitChange : false}
onDelete={name && mutable ? this.emitChange : false}
onAdd={name && mutable ? this.emitChange : false}
/>
<React.Suspense fallback={<div>...</div>}>
<JsonView
name={false}
src={data}
theme={(jsonThemeValue as any) ?? 'rjv-default'}
shouldCollapse={this.shouldExpandNode}
enableClipboard={enableClipboard}
displayDataTypes={displayDataTypes}
collapseStringsAfterLength={ellipsisThreshold}
iconStyle={iconStyle}
quotesOnKeys={quotesOnKeys}
sortKeys={sortKeys}
onEdit={name && mutable ? this.emitChange : false}
onDelete={name && mutable ? this.emitChange : false}
onAdd={name && mutable ? this.emitChange : false}
/>
</React.Suspense>
)}
</div>
);

View File

@ -34,7 +34,7 @@ import {ActionSchema} from './Action';
import {tokenize, evalExpressionWithConditionBuilder} from 'amis-core';
import {StepSchema} from './Steps';
import isEqual from 'lodash/isEqual';
import {omit} from 'lodash';
import omit from 'lodash/omit';
export type WizardStepSchema = Omit<FormSchema, 'type'> &
StepSchema & {

View File

@ -12,27 +12,60 @@ const readInterface = readline.createInterface({
let currentModule = '';
let moduleSizeMap = {};
readInterface.on('line', (line) => {
if (line.startsWith(`;/*`)) {
currentModule = line.trim();
}
if (currentModule in moduleSizeMap) {
moduleSizeMap[currentModule] += line.length;
} else {
moduleSizeMap[currentModule] = line.length;
}
}).on('close', () => {
let sizeArray = [];
for (let module in moduleSizeMap) {
sizeArray.push([module, moduleSizeMap[module]]);
}
readInterface
.on('line', line => {
if (line.startsWith(`;/*`)) {
currentModule = line.trim();
}
if (currentModule in moduleSizeMap) {
moduleSizeMap[currentModule] += line.length;
} else {
moduleSizeMap[currentModule] = line.length;
}
})
.on('close', () => {
const moduleSize2Map = {};
let sizeArray = [];
for (let module in moduleSizeMap) {
sizeArray.push([module, moduleSizeMap[module]]);
sizeArray.sort(function(a, b) {
return a[1] - b[1];
const parts = module.substring(4, module.length - 2).split('/');
while (parts.length > 1 && parts[parts.length - 2] !== 'node_modules') {
if (
parts[parts.length - 3] === 'node_modules' &&
parts[parts.length - 2][0] === '@'
) {
break;
}
parts.pop();
}
if (parts[0] === 'node_modules') {
parts.shift();
}
const moduleName = parts.join('/');
moduleSize2Map[moduleName] = moduleSize2Map[moduleName] || 0;
moduleSize2Map[moduleName] += moduleSizeMap[module];
}
sizeArray.sort(function (a, b) {
return a[1] - b[1];
});
for (size of sizeArray) {
console.log(size[0], size[1]);
}
console.log('\n\n\npackages\n\n');
const sizeArray2 = [];
for (let module in moduleSize2Map) {
sizeArray2.push([module, moduleSize2Map[module]]);
}
sizeArray2.sort(function (a, b) {
return a[1] - b[1];
});
for (size of sizeArray2) {
console.log(size[0], size[1]);
}
});
for (size of sizeArray) {
console.log(size[0], size[1]);
}
});

View File

@ -0,0 +1,90 @@
const parse = require('@babel/parser').parse;
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const types = require('@babel/types');
module.exports = function transformNodeEnvInline(file) {
// if (file.subpath !== '/node_modules/mobx/lib/index.js') {
// return;
// }
if (!file.isJsLike || !file.isMod) {
return;
}
let contents = file.getContent();
const idx = contents.indexOf('process.env.NODE_ENV');
if (idx === -1) {
return;
}
const idx2 = contents.indexOf('require(', idx);
if (idx2 === -1) {
return;
}
const env = file.optimizer ? 'production' : 'development';
contents = contents.replace(/typeof\s+process\b/g, () =>
JSON.stringify('object')
);
const ast = parse(contents, {
sourceType: 'module'
});
traverse(ast, {
// 参考: https://www.trickster.dev/post/javascript-ast-manipulation-with-babel-removing-unreachable-code/
// 参考https://github.com/babel/minify/blob/master/packages/babel-plugin-transform-node-env-inline/src/index.js
MemberExpression(path) {
if (path.matchesPattern('process.env.NODE_ENV')) {
path.replaceWith(types.valueToNode(env));
let parentPath = path.parentPath;
if (parentPath.isBinaryExpression()) {
const evaluated = parentPath.evaluate();
if (evaluated.confident) {
parentPath.replaceWith(types.valueToNode(evaluated.value));
}
}
let ifStatement = null;
while (parentPath) {
if (
parentPath.isIfStatement() ||
parentPath.isConditionalExpression()
) {
ifStatement = parentPath;
}
parentPath = parentPath.parentPath;
}
if (ifStatement) {
let isTruthy = ifStatement.get('test').evaluateTruthy();
const node = ifStatement.node;
if (isTruthy) {
if (types.isBlockStatement(node.consequent)) {
ifStatement.replaceWithMultiple(node.consequent.body);
} else {
ifStatement.replaceWith(node.consequent);
}
} else if (node.alternate != null) {
if (types.isBlockStatement(node.alternate)) {
ifStatement.replaceWithMultiple(node.alternate.body);
} else {
ifStatement.replaceWith(node.alternate);
}
} else {
ifStatement.remove();
}
}
}
}
});
contents = generate(ast, {}).code;
file.setContent(contents);
// console.log('\n', file.subpath, '\n', file.getContent());
// process.exit(1);
};