阿里低代码引擎lowcode-engine踩坑记录

707 阅读3分钟

初始化项目安装依赖失败

image.png

依赖包有版本冲突,根据提示忽略即可

npm install --force
// 或者
npm install --legacy-peer-deps

初始化编辑器

import { init } from '@alilc/lowcode-engine';

async function registerPlugins() {
  // 注册插件
}

(async function main() {
  await registerPlugins();

  // 配置文档: https://lowcode-engine.cn/site/docs/api/configOptions#%E9%85%8D%E7%BD%AE%E8%AF%A6%E6%83%85
  init(document.getElementById('lce-container')!, {
    // 语言,默认值:'zh-CN'
    locale: 'zh-CN',
    // 是否开启 condition(条件渲染) 的能力,默认在设计器中不管 condition 是啥都正常展示
    enableCondition: true,
    // 打开画布的锁定操作,默认值:false
    enableCanvasLock: true,
    // 开启设计器右键菜单能力
    enableContextMenu :true,
    // 开启应用级设计模式(多页面)
    enableWorkspaceMode: true,
    // 应用级设计模式下,自动打开第一个窗口
    enableAutoOpenFirstWindow: false,
    // 是否禁止默认的设置器
    disableDefaultSetters: true,
    // 默认绑定变量
    supportVariableGlobally: true,
    simulatorUrl: [
      'externals/css/react-simulator-renderer.css',
      'externals/js/react-simulator-renderer.js',
    ],
    requestHandlersMap: {
      fetch: createFetchHandler(),
    },
    appHelper,
    //自定义设备
    device: 'phone',
  });
})();

ArraySetter渲染异常

版本信息

image.png

异常信息

TypeError: this.context.create is not a function

fff3e993cd6b220e7ec9f87b7d54ee2.png

问题定位

查看官方issue,发现其他人也有类似问题

image.png

也尝试切换使用@alilc/lowcode-engine@alilc/lowcode-engine-ext的多个版本,也还是报错

通过debug和查看源码发现,根本原因是ObjectSetter内部的错误,决定重写ObjectSetter

import { IPublicModelPluginContext } from '@alilc/lowcode-types';
import AliLowCodeEngineExt from '@alilc/lowcode-engine-ext';
import ObjectSetter from './object-setter';
import { isPlainObject } from './utils';

const DataObjectSetter = {
  component: ObjectSetter,
  // todo: defaultProps
  defaultProps: {},
  title: 'ObjectSetter', // TODO
  condition: (field: any) => {
    const v = field.getValue();
    return v == null || isPlainObject(v);
  },
  initialValue: {},
  recommend: true,
};

// 设置内置 setter 和事件绑定、插件绑定面板
const DefaultSettersRegistryPlugin = (ctx: IPublicModelPluginContext) => {
  return {
    async init() {
      AliLowCodeEngineExt.setters.ObjectSetter = ObjectSetter;
      AliLowCodeEngineExt.setterMap.ObjectSetter = DataObjectSetter;
      const { setterMap } = AliLowCodeEngineExt;
      const { setters } = ctx;
      // 注册 setterMap
      setters.registerSetter(setterMap);
    },
  };
};
DefaultSettersRegistryPlugin.pluginName = 'DefaultSettersRegistryPlugin';
export default DefaultSettersRegistryPlugin;
  1. 重写ObjectSetterrender方法
render() {
  const { mode, forceInline = 0, ...props } = this.props;
  let children;
  if (forceInline || mode === 'popup') {
    if (forceInline > 2 || mode === 'popup') {
      // popup
      children = <RowSetter {...props} primaryButton={!forceInline} />;
    } else {
      children = <RowSetter columns={forceInline > 1 ? 2 : 4} {...props} />;
    }
  } else {
    children = <FormSetter {...props} />;
  }
  // 主要是这里套了一层PopupService
  return <PopupService>{children}</PopupService>;
}
  1. 解决popup多次弹窗的问题
export class PopupPipe {
  private emitter: IEventBus = createModuleEventBus('PopupPipe');
  // 重写popup方法,加入actionKey的条件判断即可
  private popup(props: PopupProps, target?: Element) {
    if (!props.actionKey) return;
    Promise.resolve().then(() => {
      this.emitter.emit('popupchange', props, target);
    });
  }
}

跨页面复制节点重新生成节点ID

在开启了应用级设计模式时,可能存在跨页面复制节点又不需要重新生成节点ID的情况

// ctx 为插件上下文
const { material, canvas, common } = ctx;
const { clipboard } = canvas;

function getNodesSchema(nodes: IPublicModelNode[]) {
  const componentsTree = nodes.map((node) => node?.exportSchema(IPublicEnumTransformStage.Render));
  const data = { type: 'nodeSchema', componentsMap: {}, componentsTree };
  return data;
}

material.addContextMenuOption({
  name: '跨页面复制',
  title: '跨页面复制'
  action(nodes, event) {
    if (!nodes || nodes.length < 1) return;
    const data = getNodesSchema(nodes);
    clipboard.setData(data);
  }
});

Page根组件添加属性

默认情况下,页面的根节点组件是没有属性的

image.png

翻看源码可以发现,如果物料组件里有Page组件,那么就会使用传递过来的Page,反之用默认的 github.com/alibaba/low…

所以我们只需要在我们的物料工程里编译后的lowcode文件夹里新增一个page的meta.ts文件即可

import { IPublicTypeComponentMetadata, IPublicTypeSnippet } from '@alilc/lowcode-types';
import { genBaseSetting, layoutSetting } from '../../src/utils/baseSetting';

const PageMeta: IPublicTypeComponentMetadata = {
  'componentName': 'Page',
  'title': 'Page',
  'docUrl': '',
  'screenshot': '',
  'devMode': 'proCode',
  'npm': {
    'package': 'material-components',
    'version': '0.1.0', 
    'exportName': 'Page',
    'main': 'src\\index.tsx',
    'destructuring': true,
    'subName': '',
  },
  // 'configure': {
  //   'props': [
  //     {
  //       'title': {
  //         'label': {
  //           'type': 'i18n',
  //           'en-US': 'isShowFooter',
  //           'zh-CN': '展示页尾',
  //         },
  //         'tip': '展示页尾',
  //       },
  //       'name': 'isShowFooter',
  //       condition: () => false,
  //       'description': '展示页尾',
  //       'setter': {
  //         'componentName': 'BoolSetter',
  //         'isRequired': true,
  //         'initialValue': false,
  //       },
  //     },
  //   ],
  //   'supports': {
  //     'style': true,
  //     events: ['init'],
  //   },

  //   'component': {
  //     isContainer: true,
  //   },
  // },
  props: [
    {
      name: 'style',
      propType: 'object',
      defaultValue: {
        padding: 12,
      },
    },
  ],
  configure: {
    props: [
      genBaseSetting('Page'),
      {
        'title': '常规设置',
        'display': 'accordion',
        'type': 'group',
        'items': [],
      },
      layoutSetting(),
    ],
    supports: {
      style: true,
      className: true,
      events: ['init'],
    },
    component: {
      isContainer: true,
      disableBehaviors: '*',
    },
  },
};
const snippets: IPublicTypeSnippet[] = [
  {
    'title': 'Page',
    'screenshot': '',
    'schema': {
      'componentName': 'Page',
      'props': {},
    },
  },
];

export default {
  ...PageMeta,
  snippets,
};

注入环境变量

// build.plugin.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');

module.exports = ({ onGetWebpackConfig, onHook }) => {
  onGetWebpackConfig((config) => {
    config.plugin('define').use(webpack.DefinePlugin, [
      {
        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
        // ....
      },
    ]);
  });
};

编译多入口

// build.plugin.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');

module.exports = ({ onGetWebpackConfig, onHook }) => {
  onGetWebpackConfig((config) => {
    config.plugin('index').use(HtmlWebpackPlugin, [
      {
        inject: false,
        minify: false,
        templateParameters: {
          version,
        },
        template: require.resolve('./public/index.ejs'),
        filename: 'index.html',
      },
    ]);
    config.plugin('preview').use(HtmlWebpackPlugin, [
      {
        inject: false,
        templateParameters: {},
        template: require.resolve('./public/preview.html'),
        filename: 'preview.html',
      },
    ]);
  });
};