Repository: alibaba/lowcode-engine Files analyzed: 1001 Estimated tokens: 700.9k Directory structure: └── alibaba-lowcode-engine/ ├── index.ts ├── LICENSE ├── tsconfig.json ├── .editorconfig ├── .eslintrc.js ├── deploy-space/ ├── docs/ ├── modules/ │ ├── code-generator/ │ │ ├── tsconfig.json │ │ ├── bin/ │ │ ├── scripts/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── typings.d.ts │ │ │ ├── analyzer/ │ │ │ │ └── componentAnalyzer.ts │ │ │ ├── cli/ │ │ │ │ ├── index.ts │ │ │ │ ├── init-solution.ts │ │ │ │ ├── run.ts │ │ │ │ └── solutions/ │ │ │ │ └── example-solution.ts │ │ │ ├── config/ │ │ │ │ └── env.ts │ │ │ ├── const/ │ │ │ │ ├── file.ts │ │ │ │ ├── generator.ts │ │ │ │ └── index.ts │ │ │ ├── core/ │ │ │ │ └── jsx/ │ │ │ │ ├── handlers/ │ │ │ │ │ ├── transformJsExpression.ts │ │ │ │ │ └── transformThis2Context.ts │ │ │ │ └── util/ │ │ │ │ ├── isLiteralAtomicExpr.ts │ │ │ │ └── isSimpleStraightLiteral.ts │ │ │ ├── generator/ │ │ │ │ ├── CodeBuilder.ts │ │ │ │ └── ProjectBuilder.ts │ │ │ ├── parser/ │ │ │ │ └── SchemaParser.ts │ │ │ ├── plugins/ │ │ │ │ ├── common/ │ │ │ │ │ ├── esmodule.ts │ │ │ │ │ ├── requireUtils.ts │ │ │ │ │ └── styleImport.ts │ │ │ │ ├── component/ │ │ │ │ │ ├── rax/ │ │ │ │ │ │ ├── commonDeps.ts │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ ├── containerClass.ts │ │ │ │ │ │ ├── containerInitState.ts │ │ │ │ │ │ ├── containerInjectContext.ts │ │ │ │ │ │ ├── containerInjectDataSourceEngine.ts │ │ │ │ │ │ ├── containerInjectUtils.ts │ │ │ │ │ │ ├── containerLifeCycle.ts │ │ │ │ │ │ ├── containerMethods.ts │ │ │ │ │ │ └── jsx.ts │ │ │ │ │ ├── react/ │ │ │ │ │ │ ├── const.ts │ │ │ │ │ │ ├── containerClass.ts │ │ │ │ │ │ ├── containerInitState.ts │ │ │ │ │ │ ├── containerInjectConstants.ts │ │ │ │ │ │ ├── containerInjectContext.ts │ │ │ │ │ │ ├── containerInjectDataSourceEngine.ts │ │ │ │ │ │ ├── containerInjectI18n.ts │ │ │ │ │ │ ├── containerInjectUtils.ts │ │ │ │ │ │ ├── containerLifeCycle.ts │ │ │ │ │ │ ├── containerMethod.ts │ │ │ │ │ │ ├── jsx.ts │ │ │ │ │ │ └── reactCommonDeps.ts │ │ │ │ │ └── style/ │ │ │ │ │ └── css.ts │ │ │ │ └── project/ │ │ │ │ ├── constants.ts │ │ │ │ ├── i18n.ts │ │ │ │ ├── utils.ts │ │ │ │ └── framework/ │ │ │ │ ├── icejs/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── plugins/ │ │ │ │ │ │ ├── entry.ts │ │ │ │ │ │ ├── entryHtml.ts │ │ │ │ │ │ ├── globalStyle.ts │ │ │ │ │ │ ├── packageJSON.ts │ │ │ │ │ │ └── router.ts │ │ │ │ │ └── template/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── static-files.ts │ │ │ │ │ └── files/ │ │ │ │ │ ├── abc.json.ts │ │ │ │ │ ├── build.json.ts │ │ │ │ │ ├── editorconfig.ts │ │ │ │ │ ├── eslintignore.ts │ │ │ │ │ ├── eslintrc.js.ts │ │ │ │ │ ├── gitignore.ts │ │ │ │ │ ├── jsconfig.json.ts │ │ │ │ │ ├── prettierignore.ts │ │ │ │ │ ├── prettierrc.js.ts │ │ │ │ │ ├── README.md.ts │ │ │ │ │ ├── stylelintignore.ts │ │ │ │ │ ├── stylelintrc.js.ts │ │ │ │ │ ├── tsconfig.json.ts │ │ │ │ │ └── src/ │ │ │ │ │ └── layouts/ │ │ │ │ ├── icejs3/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── plugins/ │ │ │ │ │ │ ├── appConfig.ts │ │ │ │ │ │ ├── buildConfig.ts │ │ │ │ │ │ ├── globalStyle.ts │ │ │ │ │ │ ├── layout.ts │ │ │ │ │ │ └── packageJSON.ts │ │ │ │ │ └── template/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── static-files.ts │ │ │ │ │ └── files/ │ │ │ │ │ ├── browserslistrc.ts │ │ │ │ │ ├── document.ts │ │ │ │ │ ├── gitignore.ts │ │ │ │ │ ├── README.md.ts │ │ │ │ │ ├── tsconfig.ts │ │ │ │ │ ├── typings.ts │ │ │ │ │ └── src/ │ │ │ │ │ └── layouts/ │ │ │ │ └── rax/ │ │ │ │ ├── index.ts │ │ │ │ ├── plugins/ │ │ │ │ │ ├── appConfig.ts │ │ │ │ │ ├── buildConfig.ts │ │ │ │ │ ├── entry.ts │ │ │ │ │ ├── entryDocument.ts │ │ │ │ │ ├── globalStyle.ts │ │ │ │ │ └── packageJSON.ts │ │ │ │ ├── template/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── static-files.ts │ │ │ │ │ └── files/ │ │ │ │ │ ├── jsconfig.json.ts │ │ │ │ │ ├── README.md.ts │ │ │ │ │ ├── tsconfig.json.ts │ │ │ │ │ ├── .eslintignore.ts │ │ │ │ │ ├── .eslintrc.js.ts │ │ │ │ │ ├── .gitignore.ts │ │ │ │ │ ├── .prettierignore.ts │ │ │ │ │ ├── .prettierrc.js.ts │ │ │ │ │ ├── .stylelintignore.ts │ │ │ │ │ └── .stylelintrc.js.ts │ │ │ │ └── types/ │ │ │ │ └── RaxFrameworkOptions.ts │ │ │ ├── polyfills/ │ │ │ │ └── buffer.ts │ │ │ ├── postprocessor/ │ │ │ ├── publisher/ │ │ │ │ ├── disk/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── zip/ │ │ │ │ ├── index.ts │ │ │ │ └── utils.ts │ │ │ ├── solutions/ │ │ │ │ ├── icejs.ts │ │ │ │ ├── icejs3.ts │ │ │ │ └── rax-app.ts │ │ │ ├── types/ │ │ │ └── utils/ │ │ ├── standalone/ │ │ ├── standalone-loader/ │ │ ├── standalone-worker/ │ │ ├── static-files/ │ │ │ └── rax/ │ │ │ ├── jsconfig.json.template │ │ │ ├── README.md.template │ │ │ ├── tsconfig.json.template │ │ │ ├── .eslintignore.template │ │ │ ├── .eslintrc.js.template │ │ │ ├── .gitignore.template │ │ │ ├── .prettierignore.template │ │ │ ├── .prettierrc.js.template │ │ │ ├── .stylelintignore.template │ │ │ └── .stylelintrc.js.template │ │ └── tests/ │ └── material-parser/ ├── packages/ │ ├── designer/ │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── build.json │ │ ├── build.test.json │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── src/ │ │ │ ├── component-actions.ts │ │ │ ├── component-meta.ts │ │ │ ├── context-menu-actions.scss │ │ │ ├── context-menu-actions.ts │ │ │ ├── index.ts │ │ │ ├── less-variables.less │ │ │ ├── simulator.ts │ │ │ ├── builtin-simulator/ │ │ │ │ ├── README.md │ │ │ │ ├── context.ts │ │ │ │ ├── create-simulator.ts │ │ │ │ ├── host-view.tsx │ │ │ │ ├── host.less │ │ │ │ ├── host.ts │ │ │ │ ├── index.ts │ │ │ │ ├── renderer.ts │ │ │ │ ├── resource-consumer.ts │ │ │ │ ├── viewport.ts │ │ │ │ ├── bem-tools/ │ │ │ │ │ ├── bem-tools.less │ │ │ │ │ ├── border-container.tsx │ │ │ │ │ ├── border-detecting.tsx │ │ │ │ │ ├── border-resizing.tsx │ │ │ │ │ ├── border-selecting.tsx │ │ │ │ │ ├── borders.less │ │ │ │ │ ├── drag-resize-engine.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── insertion.less │ │ │ │ │ ├── insertion.tsx │ │ │ │ │ └── manager.ts │ │ │ │ ├── live-editing/ │ │ │ │ │ └── live-editing.ts │ │ │ │ ├── node-selector/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ └── utils/ │ │ │ │ ├── clickable.ts │ │ │ │ ├── parse-metadata.ts │ │ │ │ ├── path.ts │ │ │ │ └── throttle.ts │ │ │ ├── designer/ │ │ │ │ ├── active-tracker.ts │ │ │ │ ├── clipboard.ts │ │ │ │ ├── designer-view.tsx │ │ │ │ ├── designer.less │ │ │ │ ├── designer.ts │ │ │ │ ├── detecting.ts │ │ │ │ ├── dragon.ts │ │ │ │ ├── index.ts │ │ │ │ ├── location.ts │ │ │ │ ├── offset-observer.ts │ │ │ │ ├── scroller.ts │ │ │ │ ├── drag-ghost/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── ghost.less │ │ │ │ │ └── index.tsx │ │ │ │ └── setting/ │ │ │ │ ├── index.ts │ │ │ │ ├── setting-entry-type.ts │ │ │ │ ├── setting-field.ts │ │ │ │ ├── setting-prop-entry.ts │ │ │ │ ├── setting-top-entry.ts │ │ │ │ └── utils.ts │ │ │ ├── document/ │ │ │ │ ├── document-model.ts │ │ │ │ ├── document-view.tsx │ │ │ │ ├── history.ts │ │ │ │ ├── index.ts │ │ │ │ ├── selection.ts │ │ │ │ └── node/ │ │ │ │ ├── exclusive-group.ts │ │ │ │ ├── index.ts │ │ │ │ ├── modal-nodes-manager.ts │ │ │ │ ├── node-children.ts │ │ │ │ ├── node.ts │ │ │ │ ├── transform-stage.ts │ │ │ │ └── props/ │ │ │ │ ├── prop.ts │ │ │ │ ├── props.ts │ │ │ │ └── value-to-source.ts │ │ │ ├── icons/ │ │ │ │ ├── clone.tsx │ │ │ │ ├── component.tsx │ │ │ │ ├── container.tsx │ │ │ │ ├── hidden.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── lock.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── remove.tsx │ │ │ │ ├── setting.tsx │ │ │ │ └── unlock.tsx │ │ │ ├── locale/ │ │ │ │ ├── en-US.json │ │ │ │ ├── index.ts │ │ │ │ └── zh-CN.json │ │ │ ├── plugin/ │ │ │ │ ├── index.ts │ │ │ │ ├── plugin-context.ts │ │ │ │ ├── plugin-manager.ts │ │ │ │ ├── plugin-types.ts │ │ │ │ ├── plugin-utils.ts │ │ │ │ ├── plugin.ts │ │ │ │ └── sequencify.ts │ │ │ ├── project/ │ │ │ │ ├── index.ts │ │ │ │ ├── project-view.tsx │ │ │ │ ├── project.less │ │ │ │ └── project.ts │ │ │ ├── transducers/ │ │ │ │ └── index.ts │ │ │ ├── types/ │ │ │ │ └── index.ts │ │ │ └── utils/ │ │ │ ├── index.ts │ │ │ ├── invariant.ts │ │ │ ├── misc.ts │ │ │ ├── slot.ts │ │ │ └── tree.ts │ │ └── tests/ │ │ ├── __mocks__/ │ │ │ ├── document-model.ts │ │ │ └── node.ts │ │ ├── bugs/ │ │ │ ├── prop-variable-jse.test.ts │ │ │ └── why.md │ │ ├── builtin-simulator/ │ │ │ ├── host.test.ts │ │ │ ├── renderer.test.tsx │ │ │ ├── resource-consumer.test.ts │ │ │ ├── viewport.test.ts │ │ │ ├── bem-tools/ │ │ │ │ ├── drag-resize-engine.test.ts │ │ │ │ └── manager.test.tsx │ │ │ └── utils/ │ │ │ ├── parse-metadata.test.ts │ │ │ ├── path.test.ts │ │ │ └── throttle.test.ts │ │ ├── designer/ │ │ │ ├── active-tracker.test.ts │ │ │ ├── builtin-hotkey.test.ts │ │ │ ├── designer.test.ts │ │ │ ├── detecting.test.ts │ │ │ ├── dragon.test.ts │ │ │ ├── location.test.ts │ │ │ ├── scroller.test.ts │ │ │ └── setting/ │ │ │ ├── setting-field.test.ts │ │ │ ├── setting-prop-entry.test.ts │ │ │ ├── setting-top-entry.test.ts │ │ │ └── __snapshots__/ │ │ │ └── setting-field.test.ts.snap │ │ ├── document/ │ │ │ ├── selection.test.ts │ │ │ ├── document-model/ │ │ │ │ ├── document-model.test.ts │ │ │ │ └── __snapshots__/ │ │ │ │ └── document-model.test.ts.snap │ │ │ ├── history/ │ │ │ │ ├── history.test.ts │ │ │ │ ├── session.test.ts │ │ │ │ └── __snapshots__/ │ │ │ │ └── history.test.ts.snap │ │ │ └── node/ │ │ │ ├── modal-nodes-manager.test.ts │ │ │ ├── node-children.test.ts │ │ │ ├── node.add.test.ts │ │ │ ├── node.dragdrop.test.ts │ │ │ ├── node.modify.test.ts │ │ │ ├── node.remove.test.ts │ │ │ ├── node.test.ts │ │ │ └── props/ │ │ │ ├── prop.test.ts │ │ │ ├── props.test.ts │ │ │ ├── value-to-source.test.ts │ │ │ └── __snapshots__/ │ │ │ └── value-to-source.test.ts.snap │ │ ├── fixtures/ │ │ │ ├── disable-raf.ts │ │ │ ├── silent-console.ts │ │ │ ├── unhandled-rejection.ts │ │ │ ├── window.ts │ │ │ ├── component-metadata/ │ │ │ │ ├── abcgroup.ts │ │ │ │ ├── abcitem.ts │ │ │ │ ├── abcnode.ts │ │ │ │ ├── abcoption.ts │ │ │ │ ├── button.ts │ │ │ │ ├── dialog.ts │ │ │ │ ├── div.ts │ │ │ │ ├── div10.ts │ │ │ │ ├── div2.ts │ │ │ │ ├── div3.ts │ │ │ │ ├── div4.ts │ │ │ │ ├── div5.ts │ │ │ │ ├── div6.ts │ │ │ │ ├── div7.ts │ │ │ │ ├── div8.ts │ │ │ │ ├── div9.ts │ │ │ │ ├── form.ts │ │ │ │ ├── other.ts │ │ │ │ ├── page.ts │ │ │ │ ├── page2.ts │ │ │ │ ├── root-content.ts │ │ │ │ ├── root-footer.ts │ │ │ │ └── root-header.ts │ │ │ └── schema/ │ │ │ ├── form-with-modal.ts │ │ │ ├── form.ts │ │ │ └── setting.ts │ │ ├── main/ │ │ │ ├── simulator.test.ts │ │ │ └── meta/ │ │ │ └── component-meta.test.ts │ │ ├── plugin/ │ │ │ ├── plugin-manager.test.ts │ │ │ ├── plugin-utils.test.ts │ │ │ └── sequencify.test.ts │ │ ├── project/ │ │ │ ├── project-methods.test.ts │ │ │ └── project.test.ts │ │ ├── utils/ │ │ │ ├── bom.ts │ │ │ ├── event.ts │ │ │ ├── index.ts │ │ │ ├── misc.ts │ │ │ └── renderer.ts │ │ └── utils-ut/ │ │ ├── invariant.test.ts │ │ ├── misc.test.ts │ │ └── slot.test.ts │ ├── editor-core/ │ │ ├── build.json │ │ ├── build.plugin.js │ │ ├── build.test.json │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── src/ │ │ │ ├── command.ts │ │ │ ├── config.ts │ │ │ ├── editor.ts │ │ │ ├── event-bus.ts │ │ │ ├── hotkey.ts │ │ │ ├── index.ts │ │ │ ├── di/ │ │ │ │ ├── index.ts │ │ │ │ ├── ioc-context.ts │ │ │ │ └── setter.ts │ │ │ ├── intl/ │ │ │ │ ├── global-locale.ts │ │ │ │ └── index.ts │ │ │ ├── utils/ │ │ │ │ ├── app-preset.ts │ │ │ │ ├── assets-transform.ts │ │ │ │ ├── control.ts │ │ │ │ ├── focus-tracker.ts │ │ │ │ ├── get-public-path.ts │ │ │ │ ├── index.ts │ │ │ │ ├── logger.ts │ │ │ │ ├── obx.ts │ │ │ │ ├── preference.ts │ │ │ │ └── request.ts │ │ │ └── widgets/ │ │ │ ├── index.ts │ │ │ ├── tip/ │ │ │ │ ├── help-tips.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── style.less │ │ │ │ ├── tip-container.tsx │ │ │ │ ├── tip-handler.ts │ │ │ │ ├── tip-item.tsx │ │ │ │ ├── tip.tsx │ │ │ │ └── utils.ts │ │ │ └── title/ │ │ │ ├── index.tsx │ │ │ └── title.less │ │ └── test/ │ │ └── command.test.ts │ ├── editor-skeleton/ │ │ ├── build.json │ │ ├── build.test.json │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── src/ │ │ │ ├── area.ts │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ ├── less-variables.less │ │ │ ├── register-defaults.ts │ │ │ ├── skeleton.ts │ │ │ ├── types.ts │ │ │ ├── components/ │ │ │ │ ├── draggable-line/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── field/ │ │ │ │ │ ├── fields.tsx │ │ │ │ │ ├── index.less │ │ │ │ │ ├── index.ts │ │ │ │ │ └── inlinetip.tsx │ │ │ │ ├── popup/ │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.less │ │ │ │ ├── settings/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── main.ts │ │ │ │ │ ├── settings-pane.tsx │ │ │ │ │ ├── settings-primary-pane.tsx │ │ │ │ │ ├── style.less │ │ │ │ │ └── utils.ts │ │ │ │ ├── stage-box/ │ │ │ │ │ ├── index.less │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── stage-box.tsx │ │ │ │ │ ├── stage-chain.ts │ │ │ │ │ └── stage.tsx │ │ │ │ └── widget-views/ │ │ │ │ ├── index.less │ │ │ │ ├── index.tsx │ │ │ │ └── panel-operation-row.tsx │ │ │ ├── icons/ │ │ │ │ ├── arrow.tsx │ │ │ │ ├── clear.tsx │ │ │ │ ├── convert.tsx │ │ │ │ ├── exit.tsx │ │ │ │ ├── fix.tsx │ │ │ │ ├── float.tsx │ │ │ │ ├── slot.tsx │ │ │ │ └── variable.tsx │ │ │ ├── layouts/ │ │ │ │ ├── bottom-area.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── left-area.tsx │ │ │ │ ├── left-fixed-pane.tsx │ │ │ │ ├── left-float-pane.tsx │ │ │ │ ├── main-area.tsx │ │ │ │ ├── right-area.tsx │ │ │ │ ├── sub-top-area.tsx │ │ │ │ ├── theme.less │ │ │ │ ├── toolbar.tsx │ │ │ │ ├── top-area.tsx │ │ │ │ ├── workbench.less │ │ │ │ └── workbench.tsx │ │ │ ├── locale/ │ │ │ │ ├── en-US.json │ │ │ │ ├── index.ts │ │ │ │ └── zh-CN.json │ │ │ ├── transducers/ │ │ │ │ ├── addon-combine.ts │ │ │ │ ├── parse-func.ts │ │ │ │ └── parse-props.ts │ │ │ └── widget/ │ │ │ ├── dialog-dock.ts │ │ │ ├── dock.ts │ │ │ ├── index.ts │ │ │ ├── panel-dock.ts │ │ │ ├── panel.ts │ │ │ ├── stage.ts │ │ │ ├── utils.ts │ │ │ ├── widget-container.ts │ │ │ └── widget.ts │ │ └── tests/ │ │ └── widget/ │ │ └── utils.test.ts │ ├── engine/ │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── build.json │ │ ├── build.plugin.js │ │ ├── build.test.json │ │ ├── build.umd.json │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── README-zh_CN.md │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── engine-core.ts │ │ ├── index.ts │ │ ├── inner-plugins/ │ │ │ ├── builtin-hotkey.ts │ │ │ ├── component-meta-parser.ts │ │ │ ├── default-context-menu.ts │ │ │ ├── default-panel-registry.tsx │ │ │ └── setter-registry.ts │ │ ├── locale/ │ │ │ ├── en-US.json │ │ │ ├── index.ts │ │ │ └── zh-CN.json │ │ └── modules/ │ │ ├── classes.ts │ │ ├── designer-types.ts │ │ ├── live-editing.ts │ │ ├── lowcode-types.ts │ │ ├── shell-model-factory.ts │ │ ├── skeleton-types.ts │ │ └── symbols.ts │ ├── ignitor/ │ │ ├── babel.config.js │ │ ├── build.json │ │ ├── build.plugin.js │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── public/ │ │ └── index.html │ ├── plugin-command/ │ │ ├── README.md │ │ ├── build.json │ │ ├── build.test.json │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── __tests__/ │ │ │ └── node-command.test.ts │ │ └── src/ │ │ ├── history-command.ts │ │ ├── index.ts │ │ └── node-command.ts │ ├── plugin-designer/ │ │ ├── build.json │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── .gitignore │ │ └── src/ │ │ ├── index.scss │ │ └── index.tsx │ ├── plugin-outline-pane/ │ │ ├── build.json │ │ ├── package.json │ │ ├── .gitignore │ │ └── src/ │ │ ├── README.md │ │ ├── index.tsx │ │ ├── controllers/ │ │ │ ├── pane-controller.ts │ │ │ ├── ric-shim.d.ts │ │ │ ├── tree-master.ts │ │ │ ├── tree-node.ts │ │ │ └── tree.ts │ │ ├── helper/ │ │ │ ├── consts.ts │ │ │ ├── dwell-timer.ts │ │ │ └── indent-track.ts │ │ ├── icons/ │ │ │ ├── arrow-right.tsx │ │ │ ├── cond.tsx │ │ │ ├── delete.tsx │ │ │ ├── eye-close.tsx │ │ │ ├── eye.tsx │ │ │ ├── filter.tsx │ │ │ ├── index.ts │ │ │ ├── lock.tsx │ │ │ ├── loop.tsx │ │ │ ├── outline.tsx │ │ │ ├── radio-active.tsx │ │ │ ├── radio.tsx │ │ │ ├── setting.tsx │ │ │ └── unlock.tsx │ │ ├── locale/ │ │ │ ├── en-US.json │ │ │ ├── index.ts │ │ │ └── zh-CN.json │ │ └── views/ │ │ ├── filter-tree.ts │ │ ├── filter.tsx │ │ ├── pane.tsx │ │ ├── style.less │ │ ├── tree-branches.tsx │ │ ├── tree-node.tsx │ │ ├── tree-title.tsx │ │ └── tree.tsx │ ├── react-renderer/ │ │ ├── README.md │ │ ├── build.json │ │ ├── build.test.json │ │ ├── build.umd.json │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── demo/ │ │ │ ├── compose.md │ │ │ ├── dataSource.md │ │ │ ├── i18n.md │ │ │ ├── list.md │ │ │ ├── table.md │ │ │ ├── config/ │ │ │ │ ├── constants.js │ │ │ │ ├── utils.js │ │ │ │ └── components/ │ │ │ │ ├── A.jsx │ │ │ │ ├── Div.jsx │ │ │ │ ├── Image.jsx │ │ │ │ ├── index.js │ │ │ │ └── Text.jsx │ │ │ └── schemas/ │ │ │ ├── compose.js │ │ │ ├── dataSource.js │ │ │ ├── i18n.js │ │ │ ├── list.js │ │ │ └── table.js │ │ ├── src/ │ │ │ └── index.ts │ │ └── tests/ │ │ ├── index.test.tsx │ │ ├── __snapshots__/ │ │ │ └── index.test.tsx.snap │ │ └── fixtures/ │ │ └── schema/ │ │ └── basic.ts │ ├── react-simulator-renderer/ │ │ ├── babel.config.js │ │ ├── build.json │ │ ├── build.plugin.js │ │ ├── build.test.json │ │ ├── build.umd.json │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── .babelrc │ │ ├── src/ │ │ │ ├── README.md │ │ │ ├── host.ts │ │ │ ├── index.ts │ │ │ ├── renderer-view.tsx │ │ │ ├── renderer.less │ │ │ ├── renderer.ts │ │ │ ├── builtin-components/ │ │ │ │ ├── builtin-components.ts │ │ │ │ ├── leaf.tsx │ │ │ │ └── slot.tsx │ │ │ ├── locale/ │ │ │ │ ├── en-US.json │ │ │ │ ├── index.ts │ │ │ │ └── zh-CN.json │ │ │ └── utils/ │ │ │ ├── get-client-rects.ts │ │ │ ├── is-dom-node.ts │ │ │ ├── misc.ts │ │ │ ├── react-find-dom-nodes.ts │ │ │ └── url.ts │ │ └── test/ │ │ ├── schema/ │ │ │ └── basic.ts │ │ ├── src/ │ │ │ └── renderer/ │ │ │ ├── demo.test.tsx │ │ │ └── __snapshots__/ │ │ │ └── demo.test.tsx.snap │ │ └── utils/ │ │ ├── components.tsx │ │ └── host.ts │ ├── renderer-core/ │ │ ├── babel.config.js │ │ ├── build.json │ │ ├── build.test.json │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── adapter/ │ │ │ │ └── index.ts │ │ │ ├── components/ │ │ │ │ ├── Div.tsx │ │ │ │ └── VisualDom/ │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ │ ├── context/ │ │ │ │ └── index.ts │ │ │ ├── hoc/ │ │ │ │ ├── index.tsx │ │ │ │ └── leaf.tsx │ │ │ ├── renderer/ │ │ │ │ ├── addon.tsx │ │ │ │ ├── base.tsx │ │ │ │ ├── block.tsx │ │ │ │ ├── component.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── page.tsx │ │ │ │ ├── renderer.tsx │ │ │ │ └── temp.tsx │ │ │ ├── types/ │ │ │ │ └── index.ts │ │ │ └── utils/ │ │ │ ├── common.ts │ │ │ ├── data-helper.ts │ │ │ ├── index.ts │ │ │ ├── is-use-loop.ts │ │ │ ├── logger.ts │ │ │ └── request.ts │ │ └── tests/ │ │ ├── setup.ts │ │ ├── adapter/ │ │ │ └── adapter.test.ts │ │ ├── fixtures/ │ │ │ ├── unhandled-rejection.ts │ │ │ └── schema/ │ │ │ └── basic.ts │ │ ├── hoc/ │ │ │ ├── leaf.test.tsx │ │ │ └── __snapshots__/ │ │ │ └── leaf.test.tsx.snap │ │ ├── mock/ │ │ │ ├── loop.ts │ │ │ ├── sample.ts │ │ │ └── styleMock.js │ │ ├── renderer/ │ │ │ ├── base.test.tsx │ │ │ ├── renderer.test.tsx │ │ │ └── __snapshots__/ │ │ │ └── renderer.test.tsx.snap │ │ └── utils/ │ │ ├── common.test.ts │ │ ├── components.tsx │ │ ├── data-helper.test.ts │ │ ├── is-use-loop.test.ts │ │ ├── node.ts │ │ ├── react-env-init.ts │ │ └── request.test.ts │ ├── shell/ │ │ ├── build.json │ │ ├── build.test.json │ │ ├── package.json │ │ └── src/ │ │ ├── index.ts │ │ ├── symbols.ts │ │ ├── api/ │ │ │ ├── canvas.ts │ │ │ ├── command.ts │ │ │ ├── common.tsx │ │ │ ├── commonUI.tsx │ │ │ ├── config.ts │ │ │ ├── event.ts │ │ │ ├── hotkey.ts │ │ │ ├── index.ts │ │ │ ├── logger.ts │ │ │ ├── material.ts │ │ │ ├── plugins.ts │ │ │ ├── project.ts │ │ │ ├── setters.ts │ │ │ ├── simulator-host.ts │ │ │ ├── skeleton.ts │ │ │ └── workspace.ts │ │ ├── components/ │ │ │ └── context-menu.tsx │ │ └── model/ │ │ ├── active-tracker.ts │ │ ├── clipboard.ts │ │ ├── component-meta.ts │ │ ├── condition-group.ts │ │ ├── detecting.ts │ │ ├── document-model.ts │ │ ├── drag-object.ts │ │ ├── dragon.ts │ │ ├── drop-location.ts │ │ ├── editor-view.ts │ │ ├── history.ts │ │ ├── index.ts │ │ ├── locate-event.ts │ │ ├── modal-nodes-manager.ts │ │ ├── node-children.ts │ │ ├── node.ts │ │ ├── plugin-instance.ts │ │ ├── prop.ts │ │ ├── props.ts │ │ ├── resource.ts │ │ ├── selection.ts │ │ ├── setting-field.ts │ │ ├── setting-top-entry.ts │ │ ├── simulator-render.ts │ │ ├── skeleton-item.ts │ │ └── window.ts │ ├── types/ │ │ ├── build.json │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── .eslintignore │ │ ├── .prettierrc.js │ │ └── src/ │ │ ├── activity.ts │ │ ├── assets.ts │ │ ├── code-intermediate.ts │ │ ├── code-result.ts │ │ ├── editor.ts │ │ ├── index.ts │ │ ├── shell-model-factory.ts │ │ ├── utils.ts │ │ ├── deprecated/ │ │ │ ├── index.ts │ │ │ ├── isActionContentObject.ts │ │ │ ├── isCustomView.ts │ │ │ ├── isDOMText.ts │ │ │ ├── isDynamicSetter.ts │ │ │ ├── isI18nData.ts │ │ │ ├── isJSBlock.ts │ │ │ ├── isJSExpression.ts │ │ │ ├── isJSFunction.ts │ │ │ ├── isJSSlot.ts │ │ │ ├── isLowCodeComponentType.ts │ │ │ ├── isNodeSchema.ts │ │ │ ├── isPlainObject.ts │ │ │ ├── isProCodeComponentType.ts │ │ │ ├── isProjectSchema.ts │ │ │ ├── isReactClass.ts │ │ │ ├── isReactComponent.ts │ │ │ ├── isSetterConfig.ts │ │ │ └── isTitleConfig.ts │ │ ├── event/ │ │ │ ├── index.ts │ │ │ ├── node.ts │ │ │ └── prop.ts │ │ └── shell/ │ │ ├── index.ts │ │ ├── api/ │ │ │ ├── canvas.ts │ │ │ ├── command.ts │ │ │ ├── common.ts │ │ │ ├── commonUI.ts │ │ │ ├── event.ts │ │ │ ├── hotkey.ts │ │ │ ├── index.ts │ │ │ ├── logger.ts │ │ │ ├── material.ts │ │ │ ├── plugins.ts │ │ │ ├── project.ts │ │ │ ├── setters.ts │ │ │ ├── simulator-host.ts │ │ │ ├── skeleton.ts │ │ │ └── workspace.ts │ │ ├── enum/ │ │ │ ├── context-menu.ts │ │ │ ├── drag-object-type.ts │ │ │ ├── event-names.ts │ │ │ ├── index.ts │ │ │ ├── plugin-register-level.ts │ │ │ ├── prop-value-changed-type.ts │ │ │ ├── transform-stage.ts │ │ │ └── transition-type.ts │ │ ├── model/ │ │ │ ├── active-tracker.ts │ │ │ ├── clipboard.ts │ │ │ ├── component-meta.ts │ │ │ ├── detecting.ts │ │ │ ├── document-model.ts │ │ │ ├── drag-object.ts │ │ │ ├── dragon.ts │ │ │ ├── drop-location.ts │ │ │ ├── editor-view.ts │ │ │ ├── editor.ts │ │ │ ├── engine-config.ts │ │ │ ├── exclusive-group.ts │ │ │ ├── history.ts │ │ │ ├── index.ts │ │ │ ├── locate-event.ts │ │ │ ├── modal-nodes-manager.ts │ │ │ ├── node-children.ts │ │ │ ├── node.ts │ │ │ ├── plugin-context.ts │ │ │ ├── plugin-instance.ts │ │ │ ├── preference.ts │ │ │ ├── prop.ts │ │ │ ├── props.ts │ │ │ ├── resource.ts │ │ │ ├── scroll-target.ts │ │ │ ├── scroller.ts │ │ │ ├── selection.ts │ │ │ ├── sensor.ts │ │ │ ├── setting-field.ts │ │ │ ├── setting-prop-entry.ts │ │ │ ├── setting-target.ts │ │ │ ├── setting-top-entry.ts │ │ │ ├── simulator-render.ts │ │ │ ├── skeleton-item.ts │ │ │ └── window.ts │ │ └── type/ │ │ ├── action-content-object.ts │ │ ├── active-target.ts │ │ ├── advanced.ts │ │ ├── app-config.ts │ │ ├── assets-json.ts │ │ ├── block-schema.ts │ │ ├── command.ts │ │ ├── component-action.ts │ │ ├── component-description.ts │ │ ├── component-instance.ts │ │ ├── component-metadata.ts │ │ ├── component-schema.ts │ │ ├── component-sort.ts │ │ ├── composite-value.ts │ │ ├── config-transducer.ts │ │ ├── configure.ts │ │ ├── container-schema.ts │ │ ├── context-menu.ts │ │ ├── custom-view.ts │ │ ├── disposable.ts │ │ ├── dom-text.ts │ │ ├── drag-any-object.ts │ │ ├── drag-node-data-object.ts │ │ ├── drag-node-object.ts │ │ ├── drag-object.ts │ │ ├── dynamic-props.ts │ │ ├── dynamic-setter.ts │ │ ├── editor-get-options.ts │ │ ├── editor-get-result.ts │ │ ├── editor-register-options.ts │ │ ├── editor-value-key.ts │ │ ├── editor-view-config.ts │ │ ├── editor-view.ts │ │ ├── engine-options.ts │ │ ├── field-config.ts │ │ ├── field-extra-props.ts │ │ ├── hotkey-callback-config.ts │ │ ├── hotkey-callback.ts │ │ ├── hotkey-callbacks.ts │ │ ├── i18n-map.ts │ │ ├── i8n-data.ts │ │ ├── icon-config.ts │ │ ├── icon-type.ts │ │ ├── index.ts │ │ ├── location.ts │ │ ├── metadata-transducer.ts │ │ ├── metadata.ts │ │ ├── node-data-type.ts │ │ ├── node-data.ts │ │ ├── node-instance.ts │ │ ├── node-schema.ts │ │ ├── npm-info.ts │ │ ├── npm.ts │ │ ├── on-change-options.ts │ │ ├── package.ts │ │ ├── page-schema.ts │ │ ├── plugin-config.ts │ │ ├── plugin-creater.ts │ │ ├── plugin-declaration-property.ts │ │ ├── plugin-declaration.ts │ │ ├── plugin-meta.ts │ │ ├── plugin-register-options.ts │ │ ├── plugin.ts │ │ ├── preference-value-type.ts │ │ ├── project-schema.ts │ │ ├── prop-change-options.ts │ │ ├── prop-config.ts │ │ ├── prop-types.ts │ │ ├── props-list.ts │ │ ├── props-map.ts │ │ ├── props-transducer.ts │ │ ├── reference.ts │ │ ├── registered-setter.ts │ │ ├── remote-component-description.ts │ │ ├── resource-list.ts │ │ ├── resource-type-config.ts │ │ ├── resource-type.ts │ │ ├── root-schema.ts │ │ ├── scrollable.ts │ │ ├── set-value-options.ts │ │ ├── setter-config.ts │ │ ├── setter-type.ts │ │ ├── simulator-renderer.ts │ │ ├── slot-schema.ts │ │ ├── snippet.ts │ │ ├── tip-config.ts │ │ ├── tip-content.ts │ │ ├── title-config.ts │ │ ├── title-content.ts │ │ ├── transformed-component-metadata.ts │ │ ├── value-type.ts │ │ ├── widget-base-config.ts │ │ └── widget-config-area.ts │ ├── utils/ │ │ ├── build.json │ │ ├── build.test.json │ │ ├── jest.config.js │ │ ├── jest.setup.js │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── src/ │ │ │ ├── app-helper.ts │ │ │ ├── asset.ts │ │ │ ├── build-components.ts │ │ │ ├── check-prop-types.ts │ │ │ ├── clone-deep.ts │ │ │ ├── clone-enumerable-property.ts │ │ │ ├── context-menu.scss │ │ │ ├── context-menu.tsx │ │ │ ├── create-content.ts │ │ │ ├── create-defer.ts │ │ │ ├── create-icon.tsx │ │ │ ├── css-helper.ts │ │ │ ├── cursor.css │ │ │ ├── cursor.ts │ │ │ ├── env.ts │ │ │ ├── get-prototype-of.ts │ │ │ ├── has-own-property.ts │ │ │ ├── index.ts │ │ │ ├── is-css-url.ts │ │ │ ├── is-element.ts │ │ │ ├── is-es-module.ts │ │ │ ├── is-form-event.ts │ │ │ ├── is-function.ts │ │ │ ├── is-object.ts │ │ │ ├── is-plain-object.ts │ │ │ ├── is-plugin-event-name.ts │ │ │ ├── is-react.ts │ │ │ ├── is-shaken.ts │ │ │ ├── logger.ts │ │ │ ├── misc.ts │ │ │ ├── navtive-selection.ts │ │ │ ├── node-helper.ts │ │ │ ├── schema.ts │ │ │ ├── script.ts │ │ │ ├── set-prototype-of.ts │ │ │ ├── shallow-equal.ts │ │ │ ├── svg-icon.tsx │ │ │ ├── transaction-manager.ts │ │ │ ├── unique-id.ts │ │ │ ├── workspace.tsx │ │ │ └── check-types/ │ │ │ ├── index.ts │ │ │ ├── is-action-content-object.ts │ │ │ ├── is-basic-prop-type.ts │ │ │ ├── is-component-schema.ts │ │ │ ├── is-custom-view.ts │ │ │ ├── is-dom-text.ts │ │ │ ├── is-drag-any-object.ts │ │ │ ├── is-drag-node-data-object.ts │ │ │ ├── is-drag-node-object.ts │ │ │ ├── is-dynamic-setter.ts │ │ │ ├── is-function.ts │ │ │ ├── is-i18n-data.ts │ │ │ ├── is-isfunction.ts │ │ │ ├── is-jsblock.ts │ │ │ ├── is-jsexpression.ts │ │ │ ├── is-jsslot.ts │ │ │ ├── is-location-children-detail.ts │ │ │ ├── is-location-data.ts │ │ │ ├── is-lowcode-component-type.ts │ │ │ ├── is-lowcode-project-schema.ts │ │ │ ├── is-node-schema.ts │ │ │ ├── is-node.ts │ │ │ ├── is-object.ts │ │ │ ├── is-procode-component-type.ts │ │ │ ├── is-project-schema.ts │ │ │ ├── is-required-prop-type.ts │ │ │ ├── is-setter-config.ts │ │ │ ├── is-setting-field.ts │ │ │ └── is-title-config.ts │ │ └── test/ │ │ └── src/ │ │ ├── check-prop-types.test.ts │ │ ├── clone-deep.test.ts │ │ ├── clone-enumerable-property.test.ts │ │ ├── create-content.test.tsx │ │ ├── create-defer.test.ts │ │ ├── is-object.test.ts │ │ ├── is-react.test.tsx │ │ ├── is-shaken.test.ts │ │ ├── misc.test.ts │ │ ├── navtive-selection.test.ts │ │ ├── schema.test.ts │ │ ├── script.test.ts │ │ ├── svg-icon.test.tsx │ │ ├── transaction-manager.test.ts │ │ ├── unique-id.test.ts │ │ ├── __snapshots__/ │ │ │ ├── is-react.test.tsx.snap │ │ │ └── schema.test.ts.snap │ │ ├── build-components/ │ │ │ ├── buildComponents.test.tsx │ │ │ ├── getProjectUtils.test.ts │ │ │ └── getSubComponent.test.ts │ │ └── check-types/ │ │ ├── is-action-content-object.test.ts │ │ ├── is-basic-prop-type.test.ts │ │ ├── is-custom-view.test.tsx │ │ ├── is-dom-text.test.ts │ │ ├── is-drag-any-object.test.ts │ │ ├── is-drag-node-data-object.test.ts │ │ ├── is-drag-node-object.test.ts │ │ ├── is-dynamic-setter.test.ts │ │ ├── is-i18n-data.test.ts │ │ ├── is-isfunction.test.ts │ │ ├── is-jsblock.test.ts │ │ ├── is-jsexpression.test.ts │ │ ├── is-jsslot.test.ts │ │ ├── is-location-children-detail.test.ts │ │ ├── is-location-data.test.ts │ │ ├── is-lowcode-component-type.test.ts │ │ ├── is-lowcode-project-schema.test.ts │ │ ├── is-node-schema.test.ts │ │ ├── is-node.test.ts │ │ ├── is-procode-component-type.test.ts │ │ ├── is-project-schema.test.ts │ │ ├── is-required-prop-type.test.ts │ │ ├── is-setter-config.test.ts │ │ ├── is-setting-field.test.ts │ │ └── is-title-config.test.ts │ └── workspace/ │ ├── build.json │ ├── build.test.json │ ├── jest.config.js │ ├── package.json │ ├── tsconfig.json │ └── src/ │ ├── index.ts │ ├── less-variables.less │ ├── resource-type.ts │ ├── resource.ts │ ├── skeleton-context.ts │ ├── window.ts │ ├── workspace.ts │ ├── context/ │ │ ├── base-context.ts │ │ └── view-context.ts │ ├── inner-plugins/ │ │ └── webview.tsx │ ├── layouts/ │ │ └── workbench.tsx │ └── view/ │ ├── editor-view.tsx │ ├── resource-view.less │ ├── resource-view.tsx │ └── window-view.tsx ├── README-zh_CN.md -> README-zh_CN.md ├── README.md -> README.md ├── scripts/ └── .github/ ================================================ FILE: index.ts ================================================ import renderer from './packages/react-simulator-renderer/src/renderer'; if (typeof window !== 'undefined') { (window as any).SimulatorRenderer = renderer; } export default renderer; ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Alibaba Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "declaration": true, "lib": ["es2015", "dom"], // Target latest version of ECMAScript. "target": "esnext", // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. "module": "esnext", // Search under node_modules for non-relative imports. "moduleResolution": "node", // Enable strictest settings like strictNullChecks & noImplicitAny. "strict": true, "strictPropertyInitialization": false, // Allow default imports from modules with no default export. This does not affect code emit, just typechecking. "allowSyntheticDefaultImports": true, // Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. "esModuleInterop": true, // Specify JSX code generation: 'preserve', 'react-native', or 'react'. "jsx": "preserve", // Import emit helpers (e.g. __extends, __rest, etc..) from tslib "importHelpers": true, // Enables experimental support for ES7 decorators. "experimentalDecorators": true, "emitDecoratorMetadata": true, // Generates corresponding .map file. "sourceMap": true, // Disallow inconsistently-cased references to the same file. "forceConsistentCasingInFileNames": true, // Allow json import "resolveJsonModule": true, // skip type checking of declaration files "skipLibCheck": true, "baseUrl": "./packages", "useDefineForClassFields": true, "paths": { "@alilc/lowcode-*": ["./*/src"] }, "outDir": "lib" }, "exclude": ["**/tests/*", "**/*.test.ts", "**/lib", "**/es", "node_modules"] } ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true quote_type = single [*.md] trim_trailing_whitespace = false ================================================ FILE: .eslintrc.js ================================================ module.exports = { extends: 'eslint-config-ali/typescript/react', parserOptions: { project: [], // for lint performance createDefaultProgram: false, // for lint performance }, rules: { 'react/no-multi-comp': 0, 'no-unused-expressions': 0, 'implicit-arrow-linebreak': 1, 'no-nested-ternary': 1, 'no-mixed-operators': 1, '@typescript-eslint/ban-types': 1, 'no-shadow': 1, 'no-prototype-builtins': 1, 'no-useless-constructor': 1, 'no-empty-function': 1, 'lines-between-class-members': 0, 'no-await-in-loop': 0, 'no-plusplus': 0, '@typescript-eslint/no-parameter-properties': 0, 'no-restricted-exports': ['error'], 'no-multi-assign': 1, 'no-dupe-class-members': 1, 'react/no-deprecated': 1, 'no-useless-escape': 1, 'brace-style': 1, '@typescript-eslint/no-inferrable-types': 0, 'no-proto': 0, 'prefer-const': 0, 'eol-last': 0, 'react/no-find-dom-node': 0, 'no-case-declarations': 0, '@typescript-eslint/indent': 0, 'import/no-cycle': 0, '@typescript-eslint/no-shadow': 0, '@typescript-eslint/method-signature-style': 0, '@typescript-eslint/consistent-type-assertions': 0, '@typescript-eslint/no-useless-constructor': 0, '@typescript-eslint/dot-notation': 0, // for lint performance '@typescript-eslint/restrict-plus-operands': 0, // for lint performance 'no-unexpected-multiline': 1, 'no-multiple-empty-lines': ['error', { max: 1 }], 'lines-around-comment': ['error', { beforeBlockComment: true, afterBlockComment: false, afterLineComment: false, allowBlockStart: true, }], 'comma-dangle': ['error', 'always-multiline'], '@typescript-eslint/member-ordering': [ 'error', { default: ['signature', 'field', 'constructor', 'method'] } ], '@typescript-eslint/no-unused-vars': ['error'], 'no-redeclare': 0, '@typescript-eslint/no-redeclare': 1, }, }; ================================================ FILE: modules/code-generator/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "lib" }, "include": ["src/**/*.ts"], "exclude": ["./tests", "tests/fixtures/test-cases", "../types", "node_modules"] } ================================================ FILE: modules/code-generator/src/index.ts ================================================ /** * 低代码引擎的出码模块,负责将编排产出的 Schema 转换成实际可执行的代码。 * 注意:为了保持 API 的稳定性,这里所有导出的 API 均要显式命名方式导出 * (即用 export { xxx } from 'xx' 的方式,不要直接 export * from 'xxx') * 而且所有导出的 API 务必在 tests/public 中编写单元测试 */ import { createProjectBuilder } from './generator/ProjectBuilder'; import { createModuleBuilder } from './generator/ModuleBuilder'; import { createDiskPublisher } from './publisher/disk'; import { createZipPublisher } from './publisher/zip'; import createIceJsProjectBuilder, { plugins as icejsPlugins } from './solutions/icejs'; import createIceJs3ProjectBuilder, { plugins as icejs3Plugins } from './solutions/icejs3'; import createRaxAppProjectBuilder, { plugins as raxPlugins } from './solutions/rax-app'; // 引入说明 import { REACT_CHUNK_NAME } from './plugins/component/react/const'; import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from './const/generator'; // 引入通用插件组 import esmodule from './plugins/common/esmodule'; import requireUtils from './plugins/common/requireUtils'; import styleImport from './plugins/common/styleImport'; import css from './plugins/component/style/css'; import constants from './plugins/project/constants'; import i18n from './plugins/project/i18n'; import utils from './plugins/project/utils'; import prettier from './postprocessor/prettier'; // 引入全局常用工具 import * as globalUtils from './utils'; import * as CONSTANTS from './const'; // 引入内置解决方案模块 import icejs from './plugins/project/framework/icejs'; import icejs3 from './plugins/project/framework/icejs3'; import rax from './plugins/project/framework/rax'; export default { createProjectBuilder, createModuleBuilder, solutions: { icejs: createIceJsProjectBuilder, icejs3: createIceJs3ProjectBuilder, rax: createRaxAppProjectBuilder, }, solutionParts: { icejs, icejs3, rax, }, publishers: { disk: createDiskPublisher, zip: createZipPublisher, }, plugins: { common: { /** * 处理 ES Module * @deprecated please use esModule */ esmodule, esModule: esmodule, requireUtils, styleImport, }, style: { css, }, project: { constants, i18n, utils, }, icejs: { ...icejsPlugins, }, icejs3: { ...icejs3Plugins, }, rax: { ...raxPlugins, }, /** * @deprecated please use icejs */ react: { ...icejsPlugins, }, }, postprocessor: { prettier, }, utils: globalUtils, chunkNames: { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, REACT_CHUNK_NAME, }, defaultLinkAfter: { COMMON_DEFAULT_LINK_AFTER: DEFAULT_LINK_AFTER, }, constants: CONSTANTS, }; // 一些类型定义 export * from './types'; // 一些常量定义 export * from './const'; // 一些工具函数 export * from './analyzer/componentAnalyzer'; export * from './parser/SchemaParser'; export * from './generator/ChunkBuilder'; export * from './generator/CodeBuilder'; export * from './generator/ModuleBuilder'; export * from './generator/ProjectBuilder'; ================================================ FILE: modules/code-generator/src/typings.d.ts ================================================ declare module 'nanomatch'; ================================================ FILE: modules/code-generator/src/analyzer/componentAnalyzer.ts ================================================ import type { IPublicTypeNodeSchema, IPublicTypeCompositeObject } from '@alilc/lowcode-types'; import type { TComponentAnalyzer } from '../types'; import { handleSubNodes } from '../utils/schema'; export const componentAnalyzer: TComponentAnalyzer = (container) => { let hasRefAttr = false; const nodeValidator = (n: IPublicTypeNodeSchema) => { if (n.props) { const props = n.props as IPublicTypeCompositeObject; if (props.ref) { hasRefAttr = true; } } }; nodeValidator(container); if (!hasRefAttr && container.children) { // eslint-disable-next-line @typescript-eslint/no-invalid-void-type handleSubNodes( container.children, { node: nodeValidator, }, { rerun: true, }, ); } return { isUsingRef: hasRefAttr, }; }; ================================================ FILE: modules/code-generator/src/cli/index.ts ================================================ export * from './run'; export * from './init-solution'; ================================================ FILE: modules/code-generator/src/cli/init-solution.ts ================================================ /* eslint-disable no-console */ import * as fs from 'fs-extra'; import * as path from 'path'; import chalk from 'chalk'; import * as changeCase from 'change-case'; import { getErrorMessage } from '../utils/errors'; import { getLowcodeSolutionTemplateFiles } from './solutions/example-solution'; export async function initSolution(args: string[], options: { quiet?: boolean; verbose?: boolean; }) { try { const cwd = process.cwd(); let solutionName = args[0] || 'hello'; let solutionPath = path.resolve(cwd, solutionName); if (solutionName === '.') { solutionName = path.basename(cwd); solutionPath = cwd; } const modifyFileContent = (content: string) => content .replace(/hello-world/g, changeCase.paramCase(solutionName)) .replace(/HelloWorld/g, changeCase.pascalCase(solutionName)) .replace(/Hello World/g, changeCase.titleCase(solutionName)); await ensureDirExists(solutionPath); const templateFiles = getLowcodeSolutionTemplateFiles(); for (const templateFile of templateFiles) { if (options.verbose) { console.log('%s', chalk.gray(`creating file ${templateFile.file}`)); } const templateFilePath = path.join(solutionPath, templateFile.file); await ensureDirExists(path.dirname(templateFilePath)); await fs.writeFile(templateFilePath, modifyFileContent(templateFile.content), { encoding: 'utf-8' }); } if (!options.quiet) { console.log('%s', chalk.green(`solution ${solutionName} created successfully`)); } return 0; } catch (e) { console.log(chalk.red(getErrorMessage(e) || `Unexpected error: ${e}`)); if (typeof e === 'object' && (e as { stack: string } | null)?.stack && options.verbose) { console.log(chalk.gray((e as { stack: string }).stack)); } return 1; } } async function ensureDirExists(dirPath: string) { try { await fs.mkdir(dirPath, { recursive: true }); } catch (e) { if ((e as { code: string }).code === 'EEXIST') { return;// ignore existing error } throw e; } } ================================================ FILE: modules/code-generator/src/cli/run.ts ================================================ /* eslint-disable no-console */ import chalk from 'chalk'; import * as fs from 'fs-extra'; import JSON5 from 'json5'; import { jsonc } from 'jsonc'; import { spawnSync } from 'child_process'; import * as path from 'path'; import { getErrorMessage } from '../utils/errors'; import CodeGenerator from '..'; import type { IProjectBuilder } from '..'; import type { IPublicTypeProjectSchema } from '@alilc/lowcode-types'; /** * 执行出码 CLI 命令 * @param args 入参数组 * @param options 选项 * @returns {Promise} 错误码 */ export async function run( args: string[], options: { solution: string; input?: string; output?: string; quiet?: boolean; verbose?: boolean; solutionOptions?: string; }, ): Promise { try { const schemaFile = options.input || args[0]; if (!schemaFile) { throw new Error( 'a schema file must be specified by `--input ` or by the first positional argument', ); } if ((options.input && args.length > 0) || args.length > 1) { throw new Error( 'only one schema file can be specified, either by `--input ` or by the first positional argument', ); } let solutionOptions = {}; if (options.solutionOptions) { try { solutionOptions = JSON.parse(options.solutionOptions); } catch (err: any) { throw new Error( `solution options parse error, error message is "${err.message}"`, ); } } // 读取 Schema const schema = await loadSchemaFile(schemaFile); // 创建一个项目构建器 const createProjectBuilder = await getProjectBuilderFactory(options.solution, { quiet: options.quiet, }); const builder = createProjectBuilder(solutionOptions); // 生成代码 const generatedSourceCodes = await builder.generateProject(schema); // 输出到磁盘 const publisher = CodeGenerator.publishers.disk(); await publisher.publish({ project: generatedSourceCodes, outputPath: options.output || 'generated', projectSlug: 'example', createProjectFolder: false, }); return 0; } catch (e) { console.log(chalk.red(getErrorMessage(e) || `Unexpected error: ${e}`)); if (typeof e === 'object' && (e as { stack: string } | null)?.stack && options.verbose) { console.log(chalk.gray((e as { stack: string }).stack)); } return 1; } } async function getProjectBuilderFactory( solution: string, { quiet }: { quiet?: boolean }, ): Promise<(options: {[prop: string]: any}) => IProjectBuilder> { if (solution in CodeGenerator.solutions) { return CodeGenerator.solutions[solution as 'icejs' | 'rax']; } const solutionPackageName = isLocalSolution(solution) ? solution : `${solution.startsWith('@') ? solution : `@alilc/lowcode-solution-${solution}`}`; if (!isLocalSolution(solution)) { if (!quiet) { console.log(`"${solution}" is not internal, installing it as ${solutionPackageName}...`); } spawnSync('npm', ['i', solutionPackageName], { stdio: quiet ? 'ignore' : 'inherit', }); } // eslint-disable-next-line @typescript-eslint/no-require-imports const solutionExports = require(!isLocalSolution(solution) ? solutionPackageName : `${path.isAbsolute(solution) ? solution : path.join(process.cwd(), solution)}`); const projectBuilderFactory = solutionExports.createProjectBuilder || solutionExports.createAppBuilder || solutionExports.default; if (typeof projectBuilderFactory !== 'function') { throw new Error( `"${solutionPackageName}" should export project builder factory via named export 'createProjectBuilder' or via default export`, ); } return projectBuilderFactory; } function isLocalSolution(solution: string) { return solution.startsWith('.') || solution.startsWith('/') || solution.startsWith('~'); } async function loadSchemaFile(schemaFile: string): Promise { if (!schemaFile) { throw new Error('invalid schema file name'); } const schemaFileContent = await fs.readFile(schemaFile, 'utf8'); if (/\.json5/.test(schemaFile)) { return JSON5.parse(schemaFileContent); } // 默认用 JSONC 的格式解析(兼容 JSON) return jsonc.parse(schemaFileContent); } ================================================ FILE: modules/code-generator/src/cli/solutions/example-solution.ts ================================================ export function getLowcodeSolutionTemplateFiles() { return [ { file: '.editorconfig', content: `root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true quote_type = single [*.md] trim_trailing_whitespace = false `, }, { file: '.eslintignore', content: `# 忽略目录 node_modules/ build/ dist/ test-cases/ test/ tests/ output/ es/ lib/ coverage/ # 忽略文件 **/*.min.js **/*-min.js **/*.bundle.js `, }, { file: '.eslintrc.js', content: `module.exports = { extends: 'eslint-config-ali/typescript/react', rules: { 'max-len': ['error', { code: 200 }], 'comma-dangle': 0, }, }; `, }, { file: '.gitignore', content: `# project custom build es lib dist output package-lock.json deploy-space/packages deploy-space/.env generated # IDE .vscode .idea # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release lib # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache # next.js build output .next # nuxt.js build output .nuxt # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # mac config files .DS_Store # codealike codealike.json .node `, }, { file: '.prettierignore', content: '/test-cases/', }, { file: '.prettierrc', content: `{ "tabWidth": 2, "singleQuote": true, "trailingComma": "es5" } `, }, { file: 'CHANGELOG.md', content: '', }, { file: 'CONTRIBUTING.md', content: `# 欢迎共建 # 注意 - 注意解决 eslint 问题 - 注意代码格式化 -- 建议安装 prettier 插件 - 发布前注意要跑通 demo 和所有的单测 ## 本地调试运行 Demo \`\`\`sh > npm run demo \`\`\` ## 本地跑单测 \`\`\`sh > npm test \`\`\` `, }, { file: 'README.md', content: `# 低代码出码自定义方案之 Hello World ## 直接执行 \`\`\`sh > npx ali-lowcode-solution-hello-world demo-schema.json \`\`\` ## 本地调试运行 Demo \`\`\`sh > npm run demo \`\`\` `, }, { file: 'demo-schema.json', content: `{ "version": "1.0.0", "componentsMap": [ { "componentName": "Button", "package": "@alifd/next", "version": "1.19.18", "destructuring": true, "exportName": "Button" }, { "componentName": "Button.Group", "package": "@alifd/next", "version": "1.19.18", "destructuring": true, "exportName": "Button", "subName": "Group" }, { "componentName": "Input", "package": "@alifd/next", "version": "1.19.18", "destructuring": true, "exportName": "Input" }, { "componentName": "Form", "package": "@alifd/next", "version": "1.19.18", "destructuring": true, "exportName": "Form" }, { "componentName": "Form.Item", "package": "@alifd/next", "version": "1.19.18", "destructuring": true, "exportName": "Form", "subName": "Item" }, { "componentName": "NumberPicker", "package": "@alifd/next", "version": "1.19.18", "destructuring": true, "exportName": "NumberPicker" }, { "componentName": "Select", "package": "@alifd/next", "version": "1.19.18", "destructuring": true, "exportName": "Select" } ], "componentsTree": [ { "componentName": "Page", "id": "node$1", "meta": { "title": "测试", "router": "/" }, "props": { "ref": "outterView", "autoLoading": true }, "fileName": "test", "state": { "text": "outter" }, "lifeCycles": { "componentDidMount": { "type": "JSFunction", "value": "function componentDidMount() {\\n console.log('componentDidMount');\\n}" } }, "methodsModule": { "type": "JSModule", "source": "export function helloWorld() {\\n console.log('Hello world!');\\n}\\n" }, "dataSource": { "list": [ { "id": "urlParams", "type": "urlParams" }, { "id": "user", "type": "fetch", "options": { "method": "GET", "uri": "https://shs.xxx.com/mock/1458/demo/user", "isSync": true }, "dataHandler": { "type": "JSExpression", "value": "function (response) {\\nif (!response.data.success){\\n throw new Error(response.data.message);\\n }\\n return response.data.data;\\n}" } }, { "id": "orders", "type": "fetch", "options": { "method": "GET", "uri": "https://shs.xxx.com/mock/1458/demo/orders", "isSync": true }, "dataHandler": { "type": "JSExpression", "value": "function (response) {\\nif (!response.data.success){\\n throw new Error(response.data.message);\\n }\\n return response.data.data.result;\\n}" } } ], "dataHandler": { "type": "JSExpression", "value": "function (dataMap) {\\n console.info(\\"All datasources loaded:\\", dataMap);\\n}" } }, "children": [ { "componentName": "Form", "id": "node$2", "props": { "labelCol": { "type": "JSExpression", "value": "this.state.colNum" }, "style": {}, "ref": "testForm" }, "children": [ { "componentName": "Form.Item", "id": "node$3", "props": { "label": "姓名:", "name": "name", "initValue": "李雷" }, "children": [ { "componentName": "Input", "id": "node$4", "props": { "placeholder": "请输入", "size": "medium", "style": { "width": 320 } } } ] }, { "componentName": "Form.Item", "id": "node$5", "props": { "label": "年龄:", "name": "age", "initValue": "22" }, "children": [ { "componentName": "NumberPicker", "id": "node$6", "props": { "size": "medium", "type": "normal" } } ] }, { "componentName": "Form.Item", "id": "node$7", "props": { "label": "职业:", "name": "profession" }, "children": [ { "componentName": "Select", "id": "node$8", "props": { "dataSource": [ { "label": "教师", "value": "t" }, { "label": "医生", "value": "d" }, { "label": "歌手", "value": "s" } ] } } ] }, { "componentName": "Div", "id": "node$9", "props": { "style": { "textAlign": "center" } }, "children": [ { "componentName": "Button.Group", "id": "node$a", "props": {}, "children": [ { "componentName": "Button", "id": "node$b", "condition": { "type": "JSExpression", "value": "this.index >= 1" }, "loop": ["a", "b", "c"], "props": { "type": "primary", "style": { "margin": "0 5px 0 5px" } }, "children": [ { "type": "JSExpression", "value": "this.item" } ] } ] } ] } ] } ] } ], "constants": { "ENV": "prod", "DOMAIN": "xxx.xxx.com" }, "css": "body {font-size: 12px;} .table { width: 100px;}", "config": { "sdkVersion": "1.0.3", "historyMode": "hash", "targetRootID": "J_Container", "layout": { "componentName": "BasicLayout", "props": { "logo": "...", "name": "测试网站" } }, "theme": { "package": "@alife/theme-fusion", "version": "^0.1.0", "primary": "#ff9966" } }, "meta": { "name": "demo应用", "git_group": "appGroup", "project_name": "app_demo", "description": "这是一个测试应用", "spma": "spa23d", "creator": "月飞" } } `, }, { file: 'jest.config.js', content: `module.exports = { preset: 'ts-jest', testEnvironment: 'node', testPathIgnorePatterns: ['/node_modules/', '/test-cases/', '/static-files/', '/lib/'], }; `, }, { file: 'package.json', content: `{ "name": "ali-lowcode-solution-hello-world", "version": "1.0.0", "description": "AlLowCode Code Generate Solution - Hello World", "files": [ "src", "lib", "tests", "jest.config.js", ".editorconfig", ".eslintignore", ".eslintrc.js", ".gitignore", ".prettierignore", ".prettierrc", "CHANGELOG.md", "CONTRIBUTING.md", "demo-schema.json", "package.json", "README.md", "tsconfig.json" ], "main": "lib/index.js", "scripts": { "start": "jest --watch", "build": "npm run clean && concurrently 'npm run build:ts' 'npm run lint'", "build:ts": "tsc", "check:type": "tsc -p . --noEmit", "clean": "rm -rf build dist lib generated", "dev": "build-scripts start", "lint": "eslint --ext .tsx,.ts,.js,.jsx src", "lintfix": "eslint --fix --color --ext .tsx,.ts,.js,.jsx src", "lint-staged": "lint-staged", "prepublishOnly": "npm run build", "postpublish": "git push origin master --tags", "test": "jest", "test:watch": "jest --watch", "test:update-snapshots": "cross-env UPDATE_EXPECTED=true npx jest", "demo": "npm run build && npx @alilc/lowcode-code-generator --solution . --output generated demo-schema.json" }, "repository": { "type": "git", "url": "git@github.com:your-name/ali-lowcode-solution-hello-world.git" }, "author": "", "license": "ISC", "publishConfig": { "registry": "https://registry.npm.xxx.com" }, "dependencies": { "@alilc/lowcode-code-generator": "^1.0.0", "@alilc/lowcode-types": "^1.0.0", "tslib": "^2.3.0" }, "devDependencies": { "@types/async": "^3.2.3", "@types/jest": "^26.0.17", "@typescript-eslint/eslint-plugin": "^4.28.4", "@typescript-eslint/parser": "^4.28.4", "async": "^3.2.0", "babel-runtime": "^6.26.0", "concurrently": "^5.2.0", "cross-env": "^7.0.0", "debug": "^4.1.1", "eslint": "^7.31.0", "eslint-config-ali": "^12.1.0", "eslint-plugin-import": "^2.23.4", "eslint-plugin-react": "^7.24.0", "eslint-plugin-react-hooks": "^4.2.0", "glob": "^7.2.0", "husky": "4.2.5", "jest": "^26.6.3", "json5": "^2.2.0", "lint-staged": "10.1.x", "lodash": "^4.17.21", "md5": "^2.2.1", "prettier": "^2.3.2", "ts-jest": "^26.4.4", "ts-node": "^9.0.0", "typescript": "4.x" } } `, }, { file: 'tsconfig.json', content: `{ "compilerOptions": { "esModuleInterop": true, "declaration": true, "experimentalDecorators": true, "forceConsistentCasingInFileNames": true, "importHelpers": true, "incremental": false, "jsx": "react", "moduleResolution": "node", "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, "strict": true, "stripInternal": true, "outDir": "./lib", "declarationDir": "./lib", "rootDirs": ["./src"], "target": "es6", "module": "commonjs", "lib": ["esnext"], "types": ["jest", "node"], "noUnusedLocals": false, "noUnusedParameters": false }, "include": ["src/**/*", "typings/**/*"] } `, }, { file: 'src/index.ts', content: `import CodeGen from '@alilc/lowcode-code-generator'; import examplePlugin from './plugins/example'; export default function createHelloWorldProjectBuilder() { return CodeGen.createProjectBuilder({ template: CodeGen.solutionParts.icejs.template, plugins: { components: [ CodeGen.plugins.icejs.reactCommonDeps(), CodeGen.plugins.common.esModule({ fileType: 'jsx' }), CodeGen.plugins.common.styleImport(), CodeGen.plugins.icejs.containerClass(), CodeGen.plugins.icejs.containerInjectContext(), CodeGen.plugins.icejs.containerInjectUtils(), CodeGen.plugins.icejs.containerInjectDataSourceEngine(), CodeGen.plugins.icejs.containerInjectI18n(), CodeGen.plugins.icejs.containerInjectConstants(), CodeGen.plugins.icejs.containerInitState(), CodeGen.plugins.icejs.containerLifeCycle(), CodeGen.plugins.icejs.containerMethod(), examplePlugin(), CodeGen.plugins.icejs.jsx({ nodeTypeMapping: { Div: 'div', Component: 'div', Page: 'div', Block: 'div', }, }), CodeGen.plugins.style.css(), ], pages: [ CodeGen.plugins.icejs.reactCommonDeps(), CodeGen.plugins.common.esModule({ fileType: 'jsx' }), CodeGen.plugins.common.styleImport(), CodeGen.plugins.icejs.containerClass(), CodeGen.plugins.icejs.containerInjectContext(), CodeGen.plugins.icejs.containerInjectUtils(), CodeGen.plugins.icejs.containerInjectDataSourceEngine(), CodeGen.plugins.icejs.containerInjectI18n(), CodeGen.plugins.icejs.containerInjectConstants(), CodeGen.plugins.icejs.containerInitState(), CodeGen.plugins.icejs.containerLifeCycle(), CodeGen.plugins.icejs.containerMethod(), examplePlugin(), CodeGen.plugins.icejs.jsx({ nodeTypeMapping: { Div: 'div', Component: 'div', Page: 'div', Block: 'div', }, }), CodeGen.plugins.style.css(), ], router: [ CodeGen.plugins.common.esModule(), CodeGen.solutionParts.icejs.plugins.router(), ], entry: [CodeGen.solutionParts.icejs.plugins.entry()], constants: [CodeGen.plugins.project.constants()], utils: [ CodeGen.plugins.common.esModule(), CodeGen.plugins.project.utils('react'), ], i18n: [CodeGen.plugins.project.i18n()], globalStyle: [CodeGen.solutionParts.icejs.plugins.globalStyle()], htmlEntry: [CodeGen.solutionParts.icejs.plugins.entryHtml()], packageJSON: [CodeGen.solutionParts.icejs.plugins.packageJSON()], }, postProcessors: [CodeGen.postprocessor.prettier()], }); } `, }, { file: 'src/plugins/example.ts', content: `import { ICodeStruct, BuilderComponentPlugin, BuilderComponentPluginFactory, FileType, ChunkType, IContainerInfo, COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER, } from '@alilc/lowcode-code-generator'; export interface PluginConfig { fileType: string; } const pluginFactory: BuilderComponentPluginFactory = ( config? ) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo & { methodsModule?: { type?: 'JSModule'; source?: string; }; }; if (ir.methodsModule?.type !== 'JSModule' || !ir.methodsModule?.source) { return next; } // 创建 methods.jsx next.chunks.push({ type: ChunkType.STRING, subModule: 'methods', fileType: cfg.fileType, name: COMMON_CHUNK_NAME.CustomContent, content: ir.methodsModule.source, linkAfter: [], }); // 引入对应的模块 next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.InternalDepsImport, content: "import __$$methodsModule from './methods';", linkAfter: [...DEFAULT_LINK_AFTER[COMMON_CHUNK_NAME.InternalDepsImport]], }); // 将导出的东东都放到 class 上实例方法部分 next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: 'Object.assign(this, __$$methodsModule);', linkAfter: [ ...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent], ], }); return next; }; return plugin; }; export default pluginFactory; `, }, { file: 'tests/basic.test.ts', content: `test('basic functions should be ok', () => { // 这里放一些单元测试 expect(0).toBe(0); }); `, }, ]; } ================================================ FILE: modules/code-generator/src/config/env.ts ================================================ import * as path from 'path'; export const CODE_GENERATOR_ROOT = path.join(__dirname, '../..'); ================================================ FILE: modules/code-generator/src/const/file.ts ================================================ import { FileType } from '../types/core'; export const FILE_TYPE_FAMILY = [[FileType.TSX, FileType.TS, FileType.JSX, FileType.JS]]; ================================================ FILE: modules/code-generator/src/const/generator.ts ================================================ export const COMMON_CHUNK_NAME = { ExternalDepsImport: 'CommonExternalDependencyImport', InternalDepsImport: 'CommonInternalDependencyImport', ImportAliasDefine: 'CommonImportAliasDefine', FileVarDefine: 'CommonFileScopeVarDefine', FileUtilDefine: 'CommonFileScopeMethodDefine', FileMainContent: 'CommonFileMainContent', FileExport: 'CommonFileExport', StyleDepsImport: 'CommonStyleDepsImport', StyleCssContent: 'CommonStyleCssContent', HtmlContent: 'CommonHtmlContent', CustomContent: 'CommonCustomContent', }; export const CLASS_DEFINE_CHUNK_NAME = { Start: 'CommonClassDefineStart', ConstructorStart: 'CommonClassDefineConstructorStart', ConstructorContent: 'CommonClassDefineConstructorContent', ConstructorEnd: 'CommonClassDefineConstructorEnd', StaticVar: 'CommonClassDefineStaticVar', StaticMethod: 'CommonClassDefineStaticMethod', InsVar: 'CommonClassDefineInsVar', InsVarMethod: 'CommonClassDefineInsVarMethod', InsMethod: 'CommonClassDefineInsMethod', InsPrivateMethod: 'CommonClassDefineInsPrivateMethod', End: 'CommonClassDefineEnd', }; export const DEFAULT_LINK_AFTER = { [COMMON_CHUNK_NAME.ExternalDepsImport]: [], [COMMON_CHUNK_NAME.InternalDepsImport]: [COMMON_CHUNK_NAME.ExternalDepsImport], [COMMON_CHUNK_NAME.ImportAliasDefine]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, ], [COMMON_CHUNK_NAME.FileVarDefine]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, ], [COMMON_CHUNK_NAME.FileUtilDefine]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, ], [CLASS_DEFINE_CHUNK_NAME.Start]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], [CLASS_DEFINE_CHUNK_NAME.ConstructorStart]: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar, CLASS_DEFINE_CHUNK_NAME.StaticMethod, CLASS_DEFINE_CHUNK_NAME.InsVar, CLASS_DEFINE_CHUNK_NAME.InsVarMethod, ], [CLASS_DEFINE_CHUNK_NAME.ConstructorContent]: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart], [CLASS_DEFINE_CHUNK_NAME.ConstructorEnd]: [ CLASS_DEFINE_CHUNK_NAME.ConstructorStart, CLASS_DEFINE_CHUNK_NAME.ConstructorContent, ], [CLASS_DEFINE_CHUNK_NAME.StaticVar]: [CLASS_DEFINE_CHUNK_NAME.Start], [CLASS_DEFINE_CHUNK_NAME.StaticMethod]: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar, ], [CLASS_DEFINE_CHUNK_NAME.InsVar]: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar, CLASS_DEFINE_CHUNK_NAME.StaticMethod, ], [CLASS_DEFINE_CHUNK_NAME.InsVarMethod]: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar, CLASS_DEFINE_CHUNK_NAME.StaticMethod, CLASS_DEFINE_CHUNK_NAME.InsVar, ], [CLASS_DEFINE_CHUNK_NAME.InsMethod]: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar, CLASS_DEFINE_CHUNK_NAME.StaticMethod, CLASS_DEFINE_CHUNK_NAME.InsVar, CLASS_DEFINE_CHUNK_NAME.InsVarMethod, CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, ], [CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod]: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar, CLASS_DEFINE_CHUNK_NAME.StaticMethod, CLASS_DEFINE_CHUNK_NAME.InsVar, CLASS_DEFINE_CHUNK_NAME.InsVarMethod, CLASS_DEFINE_CHUNK_NAME.InsMethod, CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, ], [CLASS_DEFINE_CHUNK_NAME.End]: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar, CLASS_DEFINE_CHUNK_NAME.StaticMethod, CLASS_DEFINE_CHUNK_NAME.InsVar, CLASS_DEFINE_CHUNK_NAME.InsVarMethod, CLASS_DEFINE_CHUNK_NAME.InsMethod, CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, ], [COMMON_CHUNK_NAME.FileMainContent]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, CLASS_DEFINE_CHUNK_NAME.End, ], [COMMON_CHUNK_NAME.FileExport]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, CLASS_DEFINE_CHUNK_NAME.End, COMMON_CHUNK_NAME.FileMainContent, ], [COMMON_CHUNK_NAME.StyleDepsImport]: [], [COMMON_CHUNK_NAME.StyleCssContent]: [COMMON_CHUNK_NAME.StyleDepsImport], [COMMON_CHUNK_NAME.HtmlContent]: [], }; export const COMMON_SUB_MODULE_NAME = 'index'; ================================================ FILE: modules/code-generator/src/const/index.ts ================================================ export const NATIVE_ELE_PKG = 'native'; export const CONTAINER_TYPE = { COMPONENT: 'Component', BLOCK: 'Block', PAGE: 'Page', }; export const SUPPORT_SCHEMA_VERSION_LIST = ['0.0.1', '1.0.0']; // built-in slot names which have been handled in ProjectBuilder export const BUILTIN_SLOT_NAMES = [ 'pages', 'components', 'router', 'entry', 'appConfig', 'buildConfig', 'constants', 'utils', 'i18n', 'globalStyle', 'htmlEntry', 'packageJSON', 'demo', ]; export const isBuiltinSlotName = function (name: string) { return BUILTIN_SLOT_NAMES.includes(name); }; export * from './file'; export * from './generator'; ================================================ FILE: modules/code-generator/src/core/jsx/handlers/transformJsExpression.ts ================================================ import { IScope } from '../../../types'; import { parseExpression } from '../../../utils/expressionParser'; import { isLiteralAtomicExpr } from '../util/isLiteralAtomicExpr'; import { isSimpleStraightLiteral } from '../util/isSimpleStraightLiteral'; import { transformThis2Context } from './transformThis2Context'; export function transformJsExpr( expr: string, scope: IScope, { dontWrapEval = false, dontTransformThis2ContextAtRootScope = false } = {}, ) { if (!expr) { return 'undefined'; } if (isLiteralAtomicExpr(expr)) { return expr; } const exprAst = parseExpression(expr); // 对于下面这些比较安全的字面值,可以直接返回对应的表达式,而非包一层 if (isSimpleStraightLiteral(exprAst)) { return expr; } if (dontWrapEval) { return transformThis2Context(exprAst, scope, { ignoreRootScope: dontTransformThis2ContextAtRootScope, }); } switch (exprAst.type) { // 对于直接写个函数的,则不用再包下,因为这样不会抛出异常的 case 'ArrowFunctionExpression': case 'FunctionExpression': return transformThis2Context(exprAst, scope, { ignoreRootScope: dontTransformThis2ContextAtRootScope, }); default: break; } // 其他的都需要包一层 return `__$$eval(() => (${transformThis2Context(exprAst, scope, { ignoreRootScope: dontTransformThis2ContextAtRootScope, })}))`; } ================================================ FILE: modules/code-generator/src/core/jsx/handlers/transformThis2Context.ts ================================================ import { Expression } from '@babel/types'; import generate from '@babel/generator'; import { IScope } from '../../../types'; import { parseExpressionConvertThis2Context } from '../../../utils/expressionParser'; /** * 将所有的 this.xxx 替换为 __$$context.xxx * @param expr */ export function transformThis2Context( expr: string | Expression, scope: IScope, { ignoreRootScope = false } = {}, ): string { if (ignoreRootScope && scope.parent == null) { return typeof expr === 'string' ? expr : generate(expr).code; } // 下面这种字符串替换的方式虽然简单直接,但是对于复杂场景会误匹配,故后期改成了解析 AST 然后修改 AST 最后再重新生成代码的方式 // return expr // .replace(/\bthis\.item\./g, () => 'item.') // .replace(/\bthis\.index\./g, () => 'index.') // .replace(/\bthis\./g, () => '__$$context.'); return parseExpressionConvertThis2Context( expr, '__$$context', scope.bindings?.getAllBindings() || [], ); } ================================================ FILE: modules/code-generator/src/core/jsx/util/isLiteralAtomicExpr.ts ================================================ /** * 判断是否是原子类型的表达式 */ export function isLiteralAtomicExpr(expr: string): boolean { return ( expr === 'null' || expr === 'undefined' || expr === 'true' || expr === 'false' || /^-?\d+(\.\d+)?$/.test(expr) ); } ================================================ FILE: modules/code-generator/src/core/jsx/util/isSimpleStraightLiteral.ts ================================================ import { Expression } from '@babel/types'; /** 判断是非是一些简单直接的字面值 */ export function isSimpleStraightLiteral(expr: Expression): boolean { switch (expr.type) { case 'BigIntLiteral': case 'BooleanLiteral': case 'DecimalLiteral': case 'NullLiteral': case 'NumericLiteral': case 'RegExpLiteral': case 'StringLiteral': return true; default: return false; } } ================================================ FILE: modules/code-generator/src/generator/CodeBuilder.ts ================================================ import { ChunkContent, ChunkType, CodeGeneratorError, CodeGeneratorFunction, ICodeBuilder, ICodeChunk, } from '../types'; export class CodeBuilder implements ICodeBuilder { private chunkDefinitions: ICodeChunk[] = []; private generators: { [key: string]: CodeGeneratorFunction } = { [ChunkType.STRING]: (str: string) => str, // no-op for string chunks [ChunkType.JSON]: (json: Record) => JSON.stringify(json), // stringify json to string }; constructor(chunkDefinitions: ICodeChunk[] = []) { this.chunkDefinitions = chunkDefinitions; } /** * Links all chunks together based on their requirements. Returns an array * of ordered chunk names which need to be compiled and glued together. */ link(chunkDefinitions: ICodeChunk[] = []): string { const chunks = chunkDefinitions || this.chunkDefinitions; if (chunks.length <= 0) { return ''; } const unprocessedChunks = chunks.map((chunk) => { return { name: chunk.name, type: chunk.type, content: chunk.content, linkAfter: this.cleanupInvalidChunks(chunk.linkAfter, chunks), }; }); const resultingString: string[] = []; while (unprocessedChunks.length > 0) { let indexToRemove = 0; for (let index = 0; index < unprocessedChunks.length; index++) { if (unprocessedChunks[index].linkAfter.length <= 0) { indexToRemove = index; break; } } if (unprocessedChunks[indexToRemove].linkAfter.length > 0) { throw new CodeGeneratorError( 'Operation aborted. Reason: cyclic dependency between chunks.', ); } const { type, content, name } = unprocessedChunks[indexToRemove]; const compiledContent = this.generateByType(type, content); if (compiledContent) { resultingString.push(`${compiledContent}\n`); } unprocessedChunks.splice(indexToRemove, 1); if (!unprocessedChunks.some((ch) => ch.name === name)) { unprocessedChunks.forEach( // remove the processed chunk from all the linkAfter arrays from the remaining chunks (ch) => { // eslint-disable-next-line no-param-reassign ch.linkAfter = ch.linkAfter.filter((after) => after !== name); }, ); } } return resultingString.join('\n'); } generateByType(type: string, content: unknown): string { if (!content) { return ''; } if (Array.isArray(content)) { return content.map((contentItem) => this.generateByType(type, contentItem)).join('\n'); } if (!this.generators[type]) { throw new Error( `Attempted to generate unknown type ${type}. Please register a generator for this type in builder/index.ts`, ); } return this.generators[type](content); } // remove invalid chunks (which did not end up being created) from the linkAfter fields // one use-case is when you want to remove the import plugin private cleanupInvalidChunks(linkAfter: string[], chunks: ICodeChunk[]) { return linkAfter.filter((chunkName) => chunks.some((chunk) => chunk.name === chunkName)); } } export default CodeBuilder; ================================================ FILE: modules/code-generator/src/generator/ProjectBuilder.ts ================================================ import { ResultDir, ResultFile, IPublicTypeProjectSchema } from '@alilc/lowcode-types'; import { IModuleBuilder, IParseResult, IProjectBuilder, IProjectPlugins, IProjectTemplate, ISchemaParser, PostProcessor, } from '../types'; import { SchemaParser } from '../parser/SchemaParser'; import { createResultDir, addDirectory, addFile } from '../utils/resultHelper'; import { createModuleBuilder } from './ModuleBuilder'; import { ProjectPreProcessor, ProjectPostProcessor, IContextData } from '../types/core'; import { CodeGeneratorError } from '../types/error'; import { isBuiltinSlotName } from '../const'; interface IModuleInfo { moduleName?: string; path: string[]; files: ResultFile[]; } export interface ProjectBuilderInitOptions { /** 项目模板 */ template: IProjectTemplate; /** 项目插件 */ plugins: IProjectPlugins; /** 模块后置处理器 */ postProcessors: PostProcessor[]; /** Schema 解析器 */ schemaParser?: ISchemaParser; /** 项目级别的前置处理器 */ projectPreProcessors?: ProjectPreProcessor[]; /** 项目级别的后置处理器 */ projectPostProcessors?: ProjectPostProcessor[]; /** 是否处于严格模式 */ inStrictMode?: boolean; /** 一些额外的上下文数据 */ extraContextData?: Record; /** * Hook which is used to customize original options, we can reorder/add/remove plugins/processors * of the existing solution. */ customizeBuilderOptions?(originalOptions: ProjectBuilderInitOptions): ProjectBuilderInitOptions; } export class ProjectBuilder implements IProjectBuilder { /** 项目模板 */ private template: IProjectTemplate; /** 项目插件 */ private plugins: IProjectPlugins; /** 模块后置处理器 */ private postProcessors: PostProcessor[]; /** Schema 解析器 */ private schemaParser: ISchemaParser; /** 项目级别的前置处理器 */ private projectPreProcessors: ProjectPreProcessor[]; /** 项目级别的后置处理器 */ private projectPostProcessors: ProjectPostProcessor[]; /** 是否处于严格模式 */ readonly inStrictMode: boolean; /** 一些额外的上下文数据 */ readonly extraContextData: IContextData; constructor(builderOptions: ProjectBuilderInitOptions) { let customBuilderOptions = builderOptions; if (typeof builderOptions.customizeBuilderOptions === 'function') { customBuilderOptions = builderOptions.customizeBuilderOptions(builderOptions); } const { template, plugins, postProcessors, schemaParser = new SchemaParser(), projectPreProcessors = [], projectPostProcessors = [], inStrictMode = false, extraContextData = {}, } = customBuilderOptions; this.template = template; this.plugins = plugins; this.postProcessors = postProcessors; this.schemaParser = schemaParser; this.projectPreProcessors = projectPreProcessors; this.projectPostProcessors = projectPostProcessors; this.inStrictMode = inStrictMode; this.extraContextData = extraContextData; } async generateProject(originalSchema: IPublicTypeProjectSchema | string): Promise { // Init const { schemaParser } = this; let schema: IPublicTypeProjectSchema = typeof originalSchema === 'string' ? JSON.parse(originalSchema) : originalSchema; // Parse / Format // Preprocess for (const preProcessor of this.projectPreProcessors) { // eslint-disable-next-line no-await-in-loop schema = await preProcessor(schema); } // Validate if (!schemaParser.validate(schema)) { throw new CodeGeneratorError('Schema is invalid'); } // Collect Deps // Parse JSExpression const parseResult: IParseResult = schemaParser.parse(schema); const projectRoot = await this.template.generateTemplate(parseResult); let buildResult: IModuleInfo[] = []; const builders = this.createModuleBuilders({ extraContextData: { projectRemark: parseResult?.project?.projectRemark, template: this.template, }, }); // Generator Code module // components // pages const containerBuildResult: IModuleInfo[] = await Promise.all( parseResult.containers.map(async (containerInfo) => { let builder: IModuleBuilder; let path: string[]; if (containerInfo.containerType === 'Page') { builder = builders.pages; path = this.template.slots.pages.path; } else { builder = builders.components; path = this.template.slots.components.path; } const { files } = await builder.generateModule(containerInfo); return { moduleName: containerInfo.moduleName, path, files, }; }), ); buildResult = buildResult.concat(containerBuildResult); // router if (parseResult.globalRouter && builders.router) { const { files } = await builders.router.generateModule(parseResult.globalRouter); buildResult.push({ path: this.template.slots.router.path, files, }); } // entry if (parseResult.project && builders.entry) { const { files } = await builders.entry.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.entry.path, files, }); } // appConfig if (builders.appConfig) { const { files } = await builders.appConfig.generateModule(parseResult); buildResult.push({ path: this.template.slots.appConfig.path, files, }); } // buildConfig if (builders.buildConfig) { const { files } = await builders.buildConfig.generateModule(parseResult); buildResult.push({ path: this.template.slots.buildConfig.path, files, }); } // constants? if (parseResult.project && builders.constants && this.template.slots.constants) { const { files } = await builders.constants.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.constants.path, files, }); } // utils? if (parseResult.globalUtils && builders.utils && this.template.slots.utils) { const { files } = await builders.utils.generateModule(parseResult.globalUtils); buildResult.push({ path: this.template.slots.utils.path, files, }); } // i18n? if (builders.i18n && this.template.slots.i18n) { const { files } = await builders.i18n.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.i18n.path, files, }); } // globalStyle if (parseResult.project && builders.globalStyle) { const { files } = await builders.globalStyle.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.globalStyle.path, files, }); } // htmlEntry if (parseResult.project && builders.htmlEntry) { const { files } = await builders.htmlEntry.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.htmlEntry.path, files, }); } // packageJSON if (parseResult.project && builders.packageJSON) { const { files } = await builders.packageJSON.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.packageJSON.path, files, }); } // demo if (parseResult.project && builders.demo) { const { files } = await builders.demo.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.demo.path, files, }); } // handle extra slots await this.generateExtraSlots(builders, parseResult, buildResult); // Post Process const isSingleComponent = parseResult?.project?.projectRemark?.isSingleComponent; // Combine Modules buildResult.forEach((moduleInfo) => { let targetDir = getDirFromRoot(projectRoot, moduleInfo.path); // if project only contain single component, skip creation of directory. if (moduleInfo.moduleName && !isSingleComponent) { const dir = createResultDir(moduleInfo.moduleName); addDirectory(targetDir, dir); targetDir = dir; } moduleInfo.files.forEach((file) => addFile(targetDir, file)); }); // post-processors let finalResult = projectRoot; for (const projectPostProcessor of this.projectPostProcessors) { // eslint-disable-next-line no-await-in-loop finalResult = await projectPostProcessor(finalResult, schema, originalSchema, { template: this.template, parseResult, }); } return finalResult; } private createModuleBuilders(extraContextData: Record = {}): Record { const builders: Record = {}; Object.keys(this.plugins).forEach((pluginName) => { if (this.plugins[pluginName].length > 0) { const options: { mainFileName?: string } = {}; if (this.template.slots[pluginName] && this.template.slots[pluginName].fileName) { options.mainFileName = this.template.slots[pluginName].fileName; } builders[pluginName] = createModuleBuilder({ plugins: this.plugins[pluginName], postProcessors: this.postProcessors, contextData: { // template: this.template, inStrictMode: this.inStrictMode, tolerateEvalErrors: true, evalErrorsHandler: '', ...this.extraContextData, ...extraContextData, }, ...options, }); } }); return builders; } private async generateExtraSlots( builders: Record, parseResult: IParseResult, buildResult: IModuleInfo[], ) { for (const slotName in this.template.slots) { if (!isBuiltinSlotName(slotName)) { const { files } = await builders[slotName].generateModule(parseResult); buildResult.push({ path: this.template.slots[slotName].path, files, }); } } } } export function createProjectBuilder(initOptions: ProjectBuilderInitOptions): IProjectBuilder { return new ProjectBuilder(initOptions); } function getDirFromRoot(root: ResultDir, path: string[]): ResultDir { let current: ResultDir = root; path.forEach((p) => { const exist = current.dirs.find((d) => d.name === p); if (exist) { current = exist; } else { const newDir = createResultDir(p); addDirectory(current, newDir); current = newDir; } }); return current; } ================================================ FILE: modules/code-generator/src/parser/SchemaParser.ts ================================================ /** * 解析器是对输入的固定格式数据做拆解,使其符合引擎后续步骤预期,完成统一处理逻辑的步骤。 * 本解析器面向的是标准 schema 协议。 */ import changeCase from 'change-case'; import { IPublicTypeUtilItem, IPublicTypeNodeDataType, IPublicTypeNodeSchema, IPublicTypeContainerSchema, IPublicTypeProjectSchema, IPublicTypePropsMap, IPublicTypeNodeData, IPublicTypeNpmInfo, } from '@alilc/lowcode-types'; import { IPageMeta, CodeGeneratorError, CompatibilityError, DependencyType, IContainerInfo, IDependency, IExternalDependency, IInternalDependency, InternalDependencyType, IParseResult, ISchemaParser, INpmPackage, IRouterInfo, } from '../types'; import { SUPPORT_SCHEMA_VERSION_LIST } from '../const'; import { getErrorMessage } from '../utils/errors'; import { handleSubNodes, isValidContainerType, ContainerType } from '../utils/schema'; import { uniqueArray } from '../utils/common'; import { componentAnalyzer } from '../analyzer/componentAnalyzer'; import { ensureValidClassName } from '../utils/validate'; import type { ProjectRemark } from '../types/intermediate'; const defaultContainer: IContainerInfo = { containerType: 'Component', componentName: 'Component', moduleName: 'Index', fileName: 'Index', css: '', props: {}, }; function getRootComponentName(typeName: string, maps: Record): string { if (maps[typeName]) { const rec = maps[typeName]; if (rec.destructuring) { return rec.componentName || typeName; } const peerName = Object.keys(maps).find((depName: string) => { const depInfo = maps[depName]; return ( depName !== typeName && !depInfo.destructuring && depInfo.package === rec.package && depInfo.version === rec.version && depInfo.main === rec.main && depInfo.exportName === rec.exportName && depInfo.subName === rec.subName ); }); return peerName || typeName; } return typeName; } function processChildren(schema: IPublicTypeNodeSchema): void { if (schema.props) { if (Array.isArray(schema.props)) { // FIXME: is array type props description } else { const nodeProps = schema.props as IPublicTypePropsMap; if (nodeProps.children) { if (!schema.children) { // eslint-disable-next-line no-param-reassign schema.children = nodeProps.children as IPublicTypeNodeDataType; } else { let _children: IPublicTypeNodeData[] = []; if (Array.isArray(schema.children)) { _children = _children.concat(schema.children); } else { _children.push(schema.children); } if (Array.isArray(nodeProps.children)) { _children = _children.concat(nodeProps.children as IPublicTypeNodeData[]); } else { _children.push(nodeProps.children as IPublicTypeNodeData); } // eslint-disable-next-line no-param-reassign schema.children = _children; } delete nodeProps.children; } } } } function getInternalDep(internalDeps: Record, depName: string) { const dep = internalDeps[depName]; return (dep && dep.type !== InternalDependencyType.PAGE) ? dep : null; } export class SchemaParser implements ISchemaParser { validate(schema: IPublicTypeProjectSchema): boolean { if (SUPPORT_SCHEMA_VERSION_LIST.indexOf(schema.version) < 0) { throw new CompatibilityError(`Not support schema with version [${schema.version}]`); } return true; } parse(schemaSrc: IPublicTypeProjectSchema | string): IParseResult { // TODO: collect utils depends in JSExpression const compDeps: Record = {}; const internalDeps: Record = {}; let utilsDeps: IExternalDependency[] = []; const schema = this.decodeSchema(schemaSrc); // 解析三方组件依赖 schema.componentsMap.forEach((info: any) => { if (info.componentName) { compDeps[info.componentName] = { ...info, dependencyType: DependencyType.External, componentName: info.componentName, exportName: info.exportName ?? info.componentName, version: info.version || '*', destructuring: info.destructuring ?? false, }; } }); let containers: IContainerInfo[]; // Test if this is a lowcode component without container if (schema.componentsTree.length > 0) { const firstRoot: IPublicTypeContainerSchema = schema.componentsTree[0] as IPublicTypeContainerSchema; if (!firstRoot.fileName && !isValidContainerType(firstRoot)) { // 整个 schema 描述一个容器,且无根节点定义 const container: IContainerInfo = { ...firstRoot, ...defaultContainer, props: firstRoot.props || defaultContainer.props, css: firstRoot.css || defaultContainer.css, moduleName: (firstRoot as IContainerInfo).moduleName || defaultContainer.moduleName, children: schema.componentsTree as IPublicTypeNodeSchema[], }; containers = [container]; } else { // 普通带 1 到多个容器的 schema containers = schema.componentsTree.map((n) => { const subRoot = n as IPublicTypeContainerSchema; const container: IContainerInfo = { ...subRoot, componentName: getRootComponentName(subRoot.componentName, compDeps), containerType: subRoot.componentName, moduleName: ensureValidClassName(subRoot.componentName === ContainerType.Component ? subRoot.fileName : changeCase.pascalCase(subRoot.fileName)), }; return container; }); } } else { throw new CodeGeneratorError("Can't find anything to generate."); } // 分析引用能力的依赖 containers = containers.map((con) => ({ ...con, analyzeResult: componentAnalyzer(con as IPublicTypeContainerSchema), })); // 建立所有容器的内部依赖索引 containers.forEach((container) => { let type; switch (container.containerType) { case 'Page': type = InternalDependencyType.PAGE; break; case 'Block': type = InternalDependencyType.BLOCK; break; default: type = InternalDependencyType.COMPONENT; break; } const dep: IInternalDependency = { type, moduleName: container.moduleName, destructuring: false, exportName: container.moduleName, dependencyType: DependencyType.Internal, }; internalDeps[dep.moduleName] = dep; }); const containersDeps = ([] as IDependency[]).concat(...containers.map((c) => c.deps || [])); // TODO: 不应该在出码部分解决? // 处理 children 写在了 props 里的情况 containers.forEach((container) => { if (container.children) { // eslint-disable-next-line @typescript-eslint/no-invalid-void-type handleSubNodes( container.children, { node: (i: IPublicTypeNodeSchema) => processChildren(i), }, { rerun: true, }, ); } }); containers.forEach((container) => { const depNames = this.getComponentNames(container); // eslint-disable-next-line no-param-reassign container.deps = uniqueArray(depNames, (i: string) => i) .map((depName) => getInternalDep(internalDeps, depName) || compDeps[depName]) .filter(Boolean); // container.deps = Object.keys(compDeps).map((depName) => compDeps[depName]); }); // 分析路由配置 const routes: IRouterInfo['routes'] = containers .filter((container) => container.containerType === 'Page') .map((page) => { const { meta } = page; if (meta) { return { path: (meta as IPageMeta).router || `/${page.fileName}`, // 如果无法找到页面路由信息,则用 fileName 做兜底 fileName: page.fileName, componentName: page.moduleName, }; } return { path: '', fileName: page.fileName, componentName: page.moduleName, }; }); const routerDeps = routes .map((r) => internalDeps[r.componentName] || compDeps[r.componentName]) .filter((dep) => !!dep); // 分析 Utils 依赖 let utils: IPublicTypeUtilItem[]; if (schema.utils) { utils = schema.utils; utilsDeps = schema.utils .filter( (u): u is { name: string; type: 'npm' | 'tnpm'; content: IPublicTypeNpmInfo } => u.type !== 'function', ) .map( (u): IExternalDependency => ({ ...u.content, componentName: u.name, version: u.content.version || '*', destructuring: u.content.destructuring ?? false, exportName: u.content.exportName ?? u.name, }), ); } else { utils = []; } // 分析项目 npm 依赖 let npms: INpmPackage[] = []; containers.forEach((con) => { const p = (con.deps || []) .map((dep) => { return dep.dependencyType === DependencyType.External ? dep : null; }) .filter((dep) => dep !== null); const npmInfos: INpmPackage[] = p.filter(Boolean).map((i) => ({ package: (i as IExternalDependency).package, version: (i as IExternalDependency).version, })); npms.push(...npmInfos); }); npms.push( ...utilsDeps.map((utilsDep) => ({ package: utilsDep.package, version: utilsDep.version, })), ); npms = uniqueArray(npms, (i) => i.package).filter(Boolean); return { containers, globalUtils: { utils, deps: utilsDeps, }, globalI18n: schema.i18n, globalRouter: { routes, deps: routerDeps, }, project: { css: schema.css, constants: schema.constants, config: schema.config || {}, meta: schema.meta || {}, i18n: schema.i18n, containersDeps, utilsDeps, packages: npms || [], dataSourcesTypes: this.collectDataSourcesTypes(schema), projectRemark: this.getProjectRemark(containers), }, }; } getProjectRemark(containers: IContainerInfo[]): ProjectRemark { return { isSingleComponent: containers.length === 1 && containers[0].containerType === 'Component', }; } getComponentNames(children: IPublicTypeNodeDataType): string[] { return handleSubNodes( children, { node: (i: IPublicTypeNodeSchema) => i.componentName, }, { rerun: true, }, ); } decodeSchema(schemaSrc: string | IPublicTypeProjectSchema): IPublicTypeProjectSchema { let schema: IPublicTypeProjectSchema; if (typeof schemaSrc === 'string') { try { schema = JSON.parse(schemaSrc); } catch (error) { throw new CodeGeneratorError( `Parse schema failed: ${getErrorMessage(error) || 'unknown reason'}`, ); } } else { schema = schemaSrc; } return schema; } private collectDataSourcesTypes(schema: IPublicTypeProjectSchema): string[] { const dataSourcesTypes = new Set(); // 数据源的默认类型为 fetch const defaultDataSourceType = 'fetch'; // 收集应用级别的数据源 schema.dataSource?.list?.forEach((ds) => { dataSourcesTypes.add(ds.type || defaultDataSourceType); }); // 收集容器级别的数据源(页面/组件/区块) schema.componentsTree.forEach((rootNode) => { rootNode.dataSource?.list?.forEach((ds) => { dataSourcesTypes.add(ds.type || defaultDataSourceType); }); }); return Array.from(dataSourcesTypes.values()); } } export default SchemaParser; ================================================ FILE: modules/code-generator/src/plugins/common/esmodule.ts ================================================ import { flatMap, camelCase, get } from 'lodash'; import { COMMON_CHUNK_NAME } from '../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, CodeGeneratorError, DependencyType, FileType, ICodeChunk, ICodeStruct, IDependency, IExternalDependency, IInternalDependency, IWithDependency, } from '../../types'; import { isValidIdentifier } from '../../utils/validate'; // TODO: main 这个信息到底怎么用,是不是外部包不需要使用? const DEP_MAIN_BLOCKLIST = ['lib', 'lib/index', 'es', 'es/index', 'main']; const DEFAULT_EXPORT_NAME = '__default__'; function groupDepsByPack(deps: IDependency[]): Record { const depMap: Record = {}; const addDep = (pkg: string, dep: IDependency) => { if (!depMap[pkg]) { depMap[pkg] = []; } depMap[pkg].push(dep); }; deps.forEach((dep) => { if (dep.dependencyType === DependencyType.Internal) { addDep(`${(dep as IInternalDependency).moduleName}${dep.main ? `/${dep.main}` : ''}`, dep); } else { let depMain = ''; // TODO: 部分类型的 main 暂时认为没用 if (dep.main && DEP_MAIN_BLOCKLIST.indexOf(dep.main) < 0) { depMain = dep.main; } if (depMain.substring(0, 1) === '/') { depMain = depMain.substring(1); } addDep(`${(dep as IExternalDependency).package}${depMain ? `/${depMain}` : ''}`, dep); } }); return depMap; } interface IDependencyItem { exportName: string; aliasName?: string; isDefault?: boolean; subName?: string; nodeIdentifier?: string; // 与使用处的映射关系,理论上是不可变更的,如需变更需要提供额外信息 source: IDependency; } interface IExportItem { exportName: string; aliasNames: string[]; isDefault?: boolean; needOriginExport: boolean; } function getDependencyIdentifier(info: IDependencyItem): string { return info.aliasName || info.exportName; } function getExportNameOfDep(dep: IDependency): string { if (dep.destructuring) { return ( dep.exportName || dep.componentName || throwNewError('destructuring dependency must have exportName or componentName') ); } if (!dep.subName) { return ( dep.componentName || dep.exportName || throwNewError('dependency item must have componentName or exportName') ); } return ( dep.exportName || `__$${camelCase( get(dep, 'moduleName') || get(dep, 'package') || throwNewError('dep.moduleName or dep.package is undefined'), )}_default` ); } function throwNewError(msg: string): never { throw new Error(msg); } function buildPackageImport( pkg: string, deps: IDependency[], targetFileType: string, useAliasName: boolean, ): ICodeChunk[] { // 如果压根没有包,则不生成对应的 import 语句(生成了没有任何意义) if (!pkg || pkg === 'undefined' || pkg === 'null') { // TODO: 要不要加个 warning? return []; } const chunks: ICodeChunk[] = []; const exportItems: Record = {}; const defaultExportNames: string[] = []; const depsInfo: IDependencyItem[] = deps.map((dep) => { const info: IDependencyItem = { exportName: getExportNameOfDep(dep), isDefault: !dep.destructuring, subName: dep.subName || undefined, nodeIdentifier: dep.componentName || undefined, source: dep, }; // 下面 5 个逻辑是清理不必要的冗余信息,做到数据结构归一化 if (info.isDefault) { if (defaultExportNames.indexOf(info.exportName) < 0) { defaultExportNames.push(info.exportName); } } if (!info.subName) { if (info.nodeIdentifier === info.exportName) { info.nodeIdentifier = undefined; } if (info.isDefault) { info.aliasName = info.nodeIdentifier || info.exportName; info.exportName = DEFAULT_EXPORT_NAME; } if (info.nodeIdentifier) { info.aliasName = info.nodeIdentifier; info.nodeIdentifier = undefined; } } else { if (info.isDefault) { info.aliasName = info.exportName; info.exportName = DEFAULT_EXPORT_NAME; } if (info.nodeIdentifier === `${info.exportName}.${info.subName}`) { info.nodeIdentifier = undefined; } } return info; }); // 建立 export 项目的列表 depsInfo.forEach((info) => { if (!exportItems[info.exportName]) { exportItems[info.exportName] = { exportName: info.exportName, isDefault: info.isDefault, aliasNames: [], needOriginExport: false, }; } if (!info.nodeIdentifier && !info.aliasName) { exportItems[info.exportName].needOriginExport = true; } }); // 建立别名字典 depsInfo.forEach((info) => { if (info.aliasName) { const { aliasNames } = exportItems[info.exportName]; if (aliasNames.indexOf(info.aliasName) < 0) { aliasNames.push(info.aliasName); } } }); // fix: 父组件ImportAliasDefine, 与子组件import的父组件冲突情况 depsInfo.forEach((info) => { if (info.nodeIdentifier) { const exportItem = exportItems[info.exportName]; if (!exportItem.needOriginExport && exportItem.aliasNames.length > 0) { // eslint-disable-next-line no-param-reassign info.aliasName = exportItem.aliasNames[0]; } } }); // 发现 nodeIdentifier 与 exportName 或者 aliasName 冲突的场景 const nodeIdentifiers = depsInfo.map((info) => info.nodeIdentifier).filter(Boolean); const conflictInfos = flatMap(Object.keys(exportItems), (exportName) => { const exportItem = exportItems[exportName]; const usedNames = [ ...exportItem.aliasNames, ...(exportItem.needOriginExport || exportItem.aliasNames.length <= 0 ? [exportName] : []), ]; const conflictNames = usedNames.filter((n) => nodeIdentifiers.indexOf(n) >= 0); if (conflictNames.length > 0) { return [ ...(conflictNames.indexOf(exportName) >= 0 ? [[exportName, true, exportItem]] : []), ...conflictNames.filter((n) => n !== exportName).map((n) => [n, false, exportItem]), ]; } return []; }); const conflictExports = conflictInfos.filter((c) => c[1]).map((c) => c[0] as string); const conflictAlias = conflictInfos.filter((c) => !c[1]).map((c) => c[0] as string); const solutions: Record = {}; depsInfo.forEach((info) => { if (info.aliasName && conflictAlias.indexOf(info.aliasName) >= 0) { // find solution let solution = solutions[info.aliasName]; if (!solution) { solution = `${info.aliasName}Alias`; const conflictItem = (conflictInfos.find((c) => c[0] === info.aliasName) || [])[2] as IExportItem; conflictItem.aliasNames = conflictItem.aliasNames.filter((a) => a !== info.aliasName); conflictItem.aliasNames.push(solution); solutions[info.aliasName] = solution; } // eslint-disable-next-line no-param-reassign info.aliasName = solution; } if (conflictExports.indexOf(info.exportName) >= 0) { // find solution let solution = solutions[info.exportName]; if (!solution) { solution = `${info.exportName}Export`; const conflictItem = (conflictInfos.find((c) => c[0] === info.exportName) || [])[2] as IExportItem; conflictItem.aliasNames.push(solution); conflictItem.needOriginExport = false; solutions[info.exportName] = solution; } // eslint-disable-next-line no-param-reassign info.aliasName = solution; } }); // 判断是否所有依赖都有合法的 Identifier depsInfo.forEach((info) => { const name = info.aliasName || info.exportName; if (!isValidIdentifier(name)) { throw new CodeGeneratorError(`Invalid Identifier [${name}]`); } if (info.nodeIdentifier && !isValidIdentifier(info.nodeIdentifier)) { throw new CodeGeneratorError(`Invalid Identifier [${info.nodeIdentifier}]`); } }); const aliasDefineStatements: Record = {}; if (useAliasName) { Object.keys(exportItems).forEach((exportName) => { const aliasList = exportItems[exportName]?.aliasNames || []; if (aliasList.length > 0) { const srcName = exportItems[exportName].needOriginExport ? exportName : aliasList[0]; const aliasNameList = exportItems[exportName].needOriginExport ? aliasList : aliasList.slice(1); aliasNameList.forEach((a) => { if (!aliasDefineStatements[a]) { aliasDefineStatements[a] = `const ${a} = ${srcName};`; } }); } }); } function getDefaultExportName(info: IDependencyItem): string { if (info.isDefault) { return defaultExportNames[0]; } return info.exportName; } depsInfo.forEach((info) => { // 如果是子组件,则导出父组件,并且根据自组件命名规则,判断是否需要定义标识符 if (info.nodeIdentifier) { // 前提,存在 nodeIdentifier 一定是有 subName 的,不然前面会优化掉 const ownerName = getDependencyIdentifier(info); chunks.push({ type: ChunkType.STRING, fileType: targetFileType, name: COMMON_CHUNK_NAME.ImportAliasDefine, content: useAliasName ? `const ${info.nodeIdentifier} = ${ownerName}.${info.subName};` : '', linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport], ext: { originalName: `${getDefaultExportName(info)}.${info.subName}`, aliasName: info.nodeIdentifier, dependency: info.source, }, }); } else if (info.aliasName) { // default 方式的导入会生成单独de import 语句,无需生成赋值语句 if (info.isDefault && defaultExportNames.find((n) => n === info.aliasName)) { delete aliasDefineStatements[info.aliasName]; return; } let contentStatement = ''; if (aliasDefineStatements[info.aliasName]) { contentStatement = aliasDefineStatements[info.aliasName]; delete aliasDefineStatements[info.aliasName]; } chunks.push({ type: ChunkType.STRING, fileType: targetFileType, name: COMMON_CHUNK_NAME.ImportAliasDefine, content: contentStatement, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport], ext: { originalName: getDefaultExportName(info), aliasName: info.aliasName, dependency: info.source, }, }); } }); // 可能会剩余一些存在二次转换的定义 Object.keys(aliasDefineStatements).forEach((a) => { chunks.push({ type: ChunkType.STRING, fileType: targetFileType, name: COMMON_CHUNK_NAME.ImportAliasDefine, content: aliasDefineStatements[a], linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport], }); }); const exportItemList = Object.keys(exportItems).map((k) => exportItems[k]); const defaultExport = exportItemList.filter((item) => item.isDefault); const otherExports = exportItemList.filter((item) => !item.isDefault); const statementL = ['import']; if (defaultExport.length > 0) { if (useAliasName) { statementL.push(defaultExportNames[0]); } else { statementL.push(defaultExport[0].aliasNames[0]); } if (otherExports.length > 0) { statementL.push(', '); } } if (otherExports.length > 0) { const items = otherExports.map((item) => { return !useAliasName || item.needOriginExport || item.aliasNames.length <= 0 ? item.exportName : `${item.exportName} as ${item.aliasNames[0]}`; }); statementL.push(`{ ${items.join(', ')} }`); } statementL.push('from'); const getInternalDependencyModuleId = () => `@/${(deps[0] as IInternalDependency).type}/${pkg}`; if (deps[0].dependencyType === DependencyType.Internal) { // TODO: Internal Deps path use project slot setting statementL.push(`'${getInternalDependencyModuleId()}';`); chunks.push({ type: ChunkType.STRING, fileType: targetFileType, name: COMMON_CHUNK_NAME.InternalDepsImport, content: statementL.join(' '), linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); } else { statementL.push(`'${pkg}';`); chunks.push({ type: ChunkType.STRING, fileType: targetFileType, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: statementL.join(' '), linkAfter: [], }); } // 处理下一些额外的 default 方式的导入 if (defaultExportNames.length > 1) { if (deps[0].dependencyType === DependencyType.Internal) { defaultExportNames.slice(1).forEach((exportName) => { chunks.push({ type: ChunkType.STRING, fileType: targetFileType, name: COMMON_CHUNK_NAME.InternalDepsImport, content: `import ${exportName} from '${getInternalDependencyModuleId()}';`, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); }); } else { defaultExportNames.slice(1).forEach((exportName) => { chunks.push({ type: ChunkType.STRING, fileType: targetFileType, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: `import ${exportName} from '${pkg}';`, linkAfter: [], }); chunks.push({ type: ChunkType.STRING, fileType: targetFileType, name: COMMON_CHUNK_NAME.ImportAliasDefine, content: '', linkAfter: [], ext: { aliasName: exportName, originalName: exportName, dependency: { package: pkg, componentName: exportName, }, }, }); }); } } return chunks; } export interface PluginConfig { fileType?: string; // 导出的文件类型 useAliasName?: boolean; // 是否使用 componentName 重命名组件 identifier filter?: (deps: IDependency[]) => IDependency[]; // 支持过滤能力 } const pluginFactory: BuilderComponentPluginFactory = (config?: PluginConfig) => { const cfg = { fileType: FileType.JS, useAliasName: true, ...(config || {}), }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IWithDependency; if (ir && ir.deps && ir.deps.length > 0) { const deps = cfg.filter ? cfg.filter(ir.deps) : ir.deps; const packs = groupDepsByPack(deps); Object.keys(packs).forEach((pkg) => { const chunks = buildPackageImport(pkg, packs[pkg], cfg.fileType, cfg.useAliasName); next.chunks.push(...chunks); }); } return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/common/requireUtils.ts ================================================ import { COMMON_CHUNK_NAME } from '../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct } from '../../types'; // TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.InternalDepsImport, content: 'import * from \'react\';', linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/common/styleImport.ts ================================================ import changeCase from 'change-case'; import { FileType, BuilderComponentPluginFactory, BuilderComponentPlugin, ICodeStruct, IWithDependency, ChunkType, } from '../../types'; import { COMMON_CHUNK_NAME } from '../../const/generator'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IWithDependency; const { chunks } = next; if (ir && ir.deps && ir.deps.length > 0) { let lowcodeMaterialsStyleAdded = false; let fusionUIStyleAdded = false; let nextStyleAddedMap: Record = {}; ir.deps.forEach((dep: any) => { if (dep.package === '@alifd/next' && !nextStyleAddedMap[dep.exportName]) { chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.InternalDepsImport, content: `import '@alifd/next/lib/${changeCase.paramCase(dep.exportName)}/style';`, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); nextStyleAddedMap[dep.exportName] = true; } else if (dep.package === '@alilc/lowcode-materials' && !lowcodeMaterialsStyleAdded) { chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.InternalDepsImport, content: 'import \'@alilc/lowcode-materials/lib/style\';', linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); lowcodeMaterialsStyleAdded = true; } else if (dep.package === '@alifd/fusion-ui' && !fusionUIStyleAdded) { chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.InternalDepsImport, content: 'import \'@alifd/fusion-ui/lib/style\';', linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); fusionUIStyleAdded = true; } }); } return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/rax/commonDeps.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: ` // 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 // 例外:rax 框架的导出名和各种组件名除外。 import { createElement, Component } from 'rax'; import { getSearchParams as __$$getSearchParams } from 'rax-app'; `, linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/rax/const.ts ================================================ export const RAX_CHUNK_NAME = { ClassDidMountBegin: 'RaxComponentClassDidMountBegin', ClassDidMountContent: 'RaxComponentClassDidMountContent', ClassDidMountEnd: 'RaxComponentClassDidMountEnd', ClassWillUnmountBegin: 'RaxComponentClassWillUnmountBegin', ClassWillUnmountContent: 'RaxComponentClassWillUnmountContent', ClassWillUnmountEnd: 'RaxComponentClassWillUnmountEnd', ClassRenderBegin: 'RaxComponentClassRenderBegin', ClassRenderPre: 'RaxComponentClassRenderPre', ClassRenderJSX: 'RaxComponentClassRenderJSX', ClassRenderEnd: 'RaxComponentClassRenderEnd', MethodsBegin: 'RaxComponentMethodsBegin', MethodsContent: 'RaxComponentMethodsContent', MethodsEnd: 'RaxComponentMethodsEnd', LifeCyclesBegin: 'RaxComponentLifeCyclesBegin', LifeCyclesContent: 'RaxComponentLifeCyclesContent', LifeCyclesEnd: 'RaxComponentLifeCyclesEnd', }; ================================================ FILE: modules/code-generator/src/plugins/component/rax/containerClass.ts ================================================ import changeCase from 'change-case'; import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER, } from '../../../const/generator'; import { RAX_CHUNK_NAME } from './const'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; import { ensureValidClassName } from '../../../utils/validate'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; // 将模块名转换成 PascalCase 的格式,并添加特定后缀,防止命名冲突 const componentClassName = ensureValidClassName( `${changeCase.pascalCase(ir.moduleName)}$$Page`, ); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.Start, content: `class ${componentClassName} extends Component {`, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.End, content: '}', linkAfter: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, RAX_CHUNK_NAME.ClassRenderEnd, RAX_CHUNK_NAME.MethodsEnd, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.ConstructorStart, content: 'constructor(props, context) { super(props); ', linkAfter: DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, content: '} /* end of constructor */', linkAfter: DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorEnd], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: RAX_CHUNK_NAME.ClassDidMountBegin, content: 'componentDidMount() {', linkAfter: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.InsVar, CLASS_DEFINE_CHUNK_NAME.InsMethod, CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: RAX_CHUNK_NAME.ClassDidMountEnd, content: '} /* end of componentDidMount */', linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin, RAX_CHUNK_NAME.ClassDidMountContent], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: RAX_CHUNK_NAME.ClassWillUnmountBegin, content: 'componentWillUnmount() {', linkAfter: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.InsVar, CLASS_DEFINE_CHUNK_NAME.InsMethod, RAX_CHUNK_NAME.ClassDidMountEnd, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: RAX_CHUNK_NAME.ClassWillUnmountEnd, content: '} /* end of componentWillUnmount */', linkAfter: [RAX_CHUNK_NAME.ClassWillUnmountBegin, RAX_CHUNK_NAME.ClassWillUnmountContent], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: RAX_CHUNK_NAME.ClassRenderBegin, content: 'render() {', linkAfter: [RAX_CHUNK_NAME.ClassDidMountEnd, RAX_CHUNK_NAME.ClassWillUnmountEnd], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: RAX_CHUNK_NAME.ClassRenderEnd, content: '} /* end of render */', linkAfter: [ RAX_CHUNK_NAME.ClassRenderBegin, RAX_CHUNK_NAME.ClassRenderPre, RAX_CHUNK_NAME.ClassRenderJSX, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.FileExport, content: `export default ${componentClassName};`, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, CLASS_DEFINE_CHUNK_NAME.End, ], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/rax/containerInitState.ts ================================================ import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; import { generateCompositeType } from '../../../utils/compositeType'; import { Scope } from '../../../utils/Scope'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; export interface PluginConfig { fileType: string; implementType: 'inConstructor' | 'insMember' | 'hooks'; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, implementType: 'insMember', ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; const scope = Scope.createRootScope(); const state = ir.state || {}; const fields = Object.keys(state).map((stateName) => { // TODO: 这里用什么 handlers? const value = generateCompositeType(state[stateName], scope); return `${JSON.stringify(stateName)}: ${value}`; }); if (cfg.implementType === 'inConstructor') { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: `this.state = { ${fields.join(',')} };`, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]], }); } else if (cfg.implementType === 'insMember') { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: `state = { ${fields.join(',')} };`, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]], }); } // TODO: hooks state?? return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/rax/containerInjectContext.ts ================================================ /* eslint-disable @typescript-eslint/indent */ import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; import { RAX_CHUNK_NAME } from './const'; import { DEFAULT_LINK_AFTER } from '../../../const'; export interface PluginConfig { fileType: string; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; const useRef = !!ir.analyzeResult?.isUsingRef; next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.InternalDepsImport, content: "import __$$constants from '../../constants';", linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); // TODO: i18n 是可选的,如果没有 i18n 这个文件怎么办?该怎么判断? next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.InternalDepsImport, content: "import * as __$$i18n from '../../i18n';", linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: ` __$$i18n._inject2(this); `, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: ` _context = this._createContext(); `, linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], }); // TODO: 按照目前的实现方案,代码的插拔能力太弱了,需要有一些变化。 // Step 1: 增加前置的分析器 next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, content: ` _createContext() { const self = this; const context = { get state() { return self.state; }, setState(newState, callback) { self.setState(newState, callback); }, get dataSourceMap() { return self._dataSourceEngine.dataSourceMap || {}; }, async reloadDataSource() { await self._dataSourceEngine.reloadDataSource(); }, get utils() { return self._utils; }, get page() { return context; }, get component() { return context; }, get props() { return self.props; }, get constants() { return __$$constants; }, i18n: __$$i18n.i18n, i18nFormat: __$$i18n.i18nFormat, getLocale: __$$i18n.getLocale, setLocale(locale) { __$$i18n.setLocale(locale); self.forceUpdate(); },${ useRef ? ` $(refName) { return self._refsManager.get(refName); }, $$(refName) { return self._refsManager.getAll(refName); }, get _refsManager() { if (!self._refsManager) { self._refsManager = new RefsManager(); } return self._refsManager; }, ` : '' } ...this._methods, }; return context; } `, linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts ================================================ /* eslint-disable @typescript-eslint/indent */ import { IPublicTypeCompositeValue, IPublicTypeJSExpression, InterpretDataSourceConfig, isJSExpression, isJSFunction, } from '@alilc/lowcode-types'; import changeCase from 'change-case'; import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME } from '../../../const/generator'; import { Scope } from '../../../utils/Scope'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IScope, } from '../../../types'; import { generateCompositeType } from '../../../utils/compositeType'; import { parseExpressionConvertThis2Context } from '../../../utils/expressionParser'; import { isContainerSchema } from '../../../utils/schema'; import { RaxFrameworkOptions } from '../../project/framework/rax/types/RaxFrameworkOptions'; import { RAX_CHUNK_NAME } from './const'; export interface PluginConfig extends RaxFrameworkOptions { fileType?: string; /** * 数据源的 handlers 的映射配置 * @deprecated 请使用 datasourceConfig.handlersPackages 来配置 */ dataSourceHandlersPackageMap?: Record; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, dataSourceHandlersPackageMap: config?.dataSourceHandlersPackageMap || config?.datasourceConfig?.handlersPackages, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const scope = Scope.createRootScope(); const dataSourceConfig = isContainerSchema(pre.ir) ? pre.ir.dataSource : null; const dataSourceItems: InterpretDataSourceConfig[] = (dataSourceConfig && dataSourceConfig.list) || []; const dataSourceEngineOptions = { runtimeConfig: true }; if (dataSourceItems.length > 0) { const requestHandlersMap: Record = {}; dataSourceItems.forEach((ds) => { const dsType = ds.type || 'fetch'; if (!(dsType in requestHandlersMap) && dsType !== 'custom') { const handlerFactoryName = `__$$create${changeCase.pascal(dsType)}RequestHandler`; requestHandlersMap[dsType] = { type: 'JSExpression', value: `${handlerFactoryName}(${ dsType === 'urlParams' ? '__$$getSearchParams()' : '' })`, }; const handlerFactoryExportName = `create${changeCase.pascal(dsType)}Handler`; const handlerPkgName = cfg.dataSourceHandlersPackageMap?.[dsType] || `@alilc/lowcode-datasource-${changeCase.kebab(dsType)}-handler`; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: ` import { ${handlerFactoryExportName} as ${handlerFactoryName} } from '${handlerPkgName}'; `, linkAfter: [], }); } }); Object.assign(dataSourceEngineOptions, { requestHandlersMap }); } const datasourceEnginePackageName = cfg.datasourceConfig?.enginePackage || '@alilc/lowcode-datasource-engine'; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: ` import { create as __$$createDataSourceEngine } from '${datasourceEnginePackageName}/runtime'; `, linkAfter: [], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType!, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: ` _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine( this._dataSourceConfig, this._context, ${generateCompositeType(dataSourceEngineOptions, scope)} );`, linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType!, name: RAX_CHUNK_NAME.ClassDidMountContent, content: ` this._dataSourceEngine.reloadDataSource(); `, linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType!, name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, content: ` _defineDataSourceConfig() { const __$$context = this._context; return (${generateCompositeType( { ...dataSourceConfig, list: [ ...dataSourceItems.map((item) => ({ // 数据源引擎默认的 errorHandler 是空的,而且并不会触发组件重新渲染…… // 这会导致页面状态不能正常展示,故这里处理下: errorHandler: { type: 'JSFunction', value: `function (err){ setTimeout(() => { this.setState({ __refresh: Date.now() + Math.random() }); }, 0); throw err; }`, }, ...item, isInit: typeof item.isInit === 'boolean' || typeof item.isInit === 'undefined' ? item.isInit ?? true : wrapAsFunction(item.isInit, scope), options: wrapAsFunction(item.options, scope), })), ], }, scope, { handlers: { function: (jsFunc) => parseExpressionConvertThis2Context(jsFunc.value, '__$$context'), expression: (jsExpr) => parseExpressionConvertThis2Context(jsExpr.value, '__$$context'), }, }, )}); } `, linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd], }); return next; }; return plugin; }; export default pluginFactory; function wrapAsFunction(value: IPublicTypeCompositeValue, scope: IScope): IPublicTypeCompositeValue { if (isJSExpression(value) || isJSFunction(value)) { return { type: 'JSExpression', value: `function(){ return ((${value.value}))}`, }; } return { type: 'JSExpression', value: `function(){return((${generateCompositeType(value, scope)}))}`, }; } ================================================ FILE: modules/code-generator/src/plugins/component/rax/containerInjectUtils.ts ================================================ import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; import { RAX_CHUNK_NAME } from './const'; export interface PluginConfig { fileType: string; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; const useRef = !!ir.analyzeResult?.isUsingRef; next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.InternalDepsImport, // TODO: 下面这个路径有没有更好的方式来获取?而非写死 content: ` import __$$projectUtils${useRef ? ', { RefsManager }' : ''} from '../../utils'; `, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: '_utils = this._defineUtils();', linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, content: ` _defineUtils() { return { ...__$$projectUtils, }; }`, linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/rax/containerLifeCycle.ts ================================================ import _ from 'lodash'; import { isJSExpression, isJSFunction } from '@alilc/lowcode-types'; import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; import { RAX_CHUNK_NAME } from './const'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, FileType, ChunkType, ICodeStruct, IContainerInfo, } from '../../../types'; import { debug } from '../../../utils/debug'; import { isJSExpressionFn } from '../../../utils/common'; export interface PluginConfig { fileType: string; exportNameMapping: Record; normalizeNameMapping: Record; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, exportNameMapping: {}, normalizeNameMapping: { didMount: 'componentDidMount', willUnmount: 'componentWillUnmount', }, ...config, }; const exportNameMapping = new Map(Object.entries(cfg.exportNameMapping)); const normalizeNameMapping = new Map(Object.entries(cfg.normalizeNameMapping)); const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; // Rax 先只支持 didMount 和 willUnmount 吧 const ir = next.ir as IContainerInfo; const { lifeCycles } = ir; if (lifeCycles && !_.isEmpty(lifeCycles)) { Object.entries(lifeCycles).forEach(([lifeCycleName, lifeCycleMethodExpr]) => { // 过滤掉非法数据(有些场景下会误传入空字符串或 null) if ( !isJSFunction(lifeCycles[lifeCycleName]) && !isJSExpressionFn(lifeCycles[lifeCycleName]) && !isJSExpression(lifeCycles[lifeCycleName]) ) { return; } const normalizeName = normalizeNameMapping.get(lifeCycleName) || lifeCycleName; const exportName = exportNameMapping.get(lifeCycleName) || lifeCycleName; next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.LifeCyclesContent, content: `${exportName}: (${lifeCycleMethodExpr.value}),`, linkAfter: [RAX_CHUNK_NAME.LifeCyclesBegin], }); if (normalizeName === 'constructor') { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: `this._lifeCycles.${exportName}();`, linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart], }); } else if (normalizeName === 'componentDidMount') { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.ClassDidMountContent, content: `this._lifeCycles.${exportName}();`, linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin], }); } else if (normalizeName === 'componentWillUnmount') { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.ClassWillUnmountContent, content: `this._lifeCycles.${exportName}();`, linkAfter: [RAX_CHUNK_NAME.ClassWillUnmountBegin], }); } else { debug(`[CodeGen]: unknown life cycle: ${lifeCycleName}`); } }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: '_lifeCycles = this._defineLifeCycles();', linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.LifeCyclesBegin, content: ` _defineLifeCycles() { const __$$lifeCycles = ({ `, linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd, CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.LifeCyclesEnd, content: ` }); // 为所有的方法绑定上下文 Object.entries(__$$lifeCycles).forEach(([lifeCycleName, lifeCycleMethod]) => { if (typeof lifeCycleMethod === 'function') { __$$lifeCycles[lifeCycleName] = (...args) => { return lifeCycleMethod.apply(this._context, args); } } }); return __$$lifeCycles; } `, linkAfter: [RAX_CHUNK_NAME.LifeCyclesBegin, RAX_CHUNK_NAME.LifeCyclesContent], }); } return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/rax/containerMethods.ts ================================================ import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; import { RAX_CHUNK_NAME } from './const'; export interface PluginConfig { fileType: string; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: ` _methods = this._defineMethods(); `, linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.MethodsBegin, content: ` _defineMethods() { return ({ `, linkAfter: [ RAX_CHUNK_NAME.ClassRenderEnd, CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, RAX_CHUNK_NAME.LifeCyclesEnd, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.MethodsEnd, content: ` }); } `, linkAfter: [RAX_CHUNK_NAME.MethodsBegin, RAX_CHUNK_NAME.MethodsContent], }); if (ir.methods && Object.keys(ir.methods).length > 0) { Object.entries(ir.methods).forEach(([methodName, methodDefine]) => { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.MethodsContent, content: `${methodName}: (${methodDefine.value}),`, linkAfter: [RAX_CHUNK_NAME.MethodsBegin], }); }); } return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/rax/jsx.ts ================================================ import { IPublicTypeNodeSchema, IPublicTypeJSExpression, IPublicTypeNpmInfo, IPublicTypeCompositeValue, isJSExpression, } from '@alilc/lowcode-types'; import _ from 'lodash'; import changeCase from 'change-case'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, CodePiece, FileType, ICodeChunk, ICodeStruct, IContainerInfo, PIECE_TYPE, HandlerSet, IScope, NodeGeneratorConfig, NodePlugin, AttrPlugin, } from '../../../types'; import { RAX_CHUNK_NAME } from './const'; import { COMMON_CHUNK_NAME } from '../../../const/generator'; import { generateExpression } from '../../../utils/jsExpression'; import { createNodeGenerator, generateConditionReactCtrl, generateReactExprInJS, } from '../../../utils/nodeToJSX'; import { generateCompositeType } from '../../../utils/compositeType'; import { Scope } from '../../../utils/Scope'; import { parseExpressionGetGlobalVariables } from '../../../utils/expressionParser'; import { transformThis2Context } from '../../../core/jsx/handlers/transformThis2Context'; import { transformJsExpr } from '../../../core/jsx/handlers/transformJsExpression'; export interface PluginConfig { fileType: string; /** 是否要忽略小程序 */ ignoreMiniApp?: boolean; } // TODO: componentName 若并非大写字符打头,甚至并非是一个有效的 JS 标识符怎么办?? // FIXME: 我想了下,这块应该放到解析阶段就去做掉,对所有 componentName 做 identifier validate,然后对不合法的做统一替换。 const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; const rootScope = Scope.createRootScope(); const { tolerateEvalErrors = true, evalErrorsHandler = '' } = next.contextData; // Rax 构建到小程序的时候,不能给组件起起别名,得直接引用,故这里将所有的别名替换掉 // 先收集下所有的 alias 的映射 const componentsNameAliasMap = new Map(); next.chunks.forEach((chunk) => { if (isImportAliasDefineChunk(chunk)) { componentsNameAliasMap.set(chunk.ext.aliasName, chunk.ext.originalName); } }); // 注意:这里其实隐含了一个假设:schema 中的 componentName 应该是一个有效的 JS 标识符,而且是大写字母打头的 // FIXME: 为了快速修复临时加的逻辑,需要用 pre-process 的方式替代处理。 const mapComponentNameToAliasOrKeepIt = (componentName: string) => componentsNameAliasMap.get(componentName) || componentName; // 然后过滤掉所有的别名 chunks next.chunks = next.chunks.filter((chunk) => !isImportAliasDefineChunk(chunk)); // 如果直接按目前的 React 的方式之间出码 JSX 的话,会有 3 个问题: // 1. 小程序出码的时候,循环变量没法拿到 // 2. 小程序出码的时候,很容易出现 Uncaught TypeError: Cannot read property 'avatar' of undefined 这样的异常(如下图的 50 行) -- 因为若直接出码,Rax 构建到小程序的时候会立即计算所有在视图中用到的变量 // 3. 通过 this.xxx 能拿到的东西太多了,而且自定义的 methods 可能会无意间破坏 Rax 框架或小程序框架在页面 this 上的东东 const customHandlers: HandlerSet = { expression(input: IPublicTypeJSExpression, scope: IScope) { return transformJsExpr(generateExpression(input, scope), scope, { dontWrapEval: !tolerateEvalErrors, }); }, function(input, scope: IScope) { return transformThis2Context(input.value || 'null', scope); }, }; // 创建代码生成器 const commonNodeGenerator = createNodeGenerator({ handlers: customHandlers, tagMapping: mapComponentNameToAliasOrKeepIt, nodePlugins: [generateReactExprInJS, generateConditionReactCtrl, generateRaxLoopCtrl], attrPlugins: [generateNodeAttrForRax.bind({ cfg })], }); // 生成 JSX 代码 const jsxContent = commonNodeGenerator(ir, rootScope); if (!cfg.ignoreMiniApp) { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: "import { isMiniApp as __$$isMiniApp } from 'universal-env';", linkAfter: [], }); } next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.ClassRenderPre, // TODO: setState, dataSourceMap, reloadDataSource, utils, i18n, i18nFormat, getLocale, setLocale 这些在 Rax 的编译模式下不能在视图中直接访问,需要转化成 this.xxx content: ` const __$$context = this._context; const { state, setState, dataSourceMap, reloadDataSource, utils, constants, i18n, i18nFormat, getLocale, setLocale } = __$$context; `, linkAfter: [RAX_CHUNK_NAME.ClassRenderBegin], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.ClassRenderJSX, content: `return ${jsxContent};`, linkAfter: [RAX_CHUNK_NAME.ClassRenderBegin, RAX_CHUNK_NAME.ClassRenderPre], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.CustomContent, content: [ tolerateEvalErrors && ` function __$$eval(expr) { try { return expr(); } catch (error) { ${evalErrorsHandler} } } function __$$evalArray(expr) { const res = __$$eval(expr); return Array.isArray(res) ? res : []; } `, ` function __$$createChildContext(oldContext, ext) { return Object.assign({}, oldContext, ext); } `, ] .filter(Boolean) .join('\n'), linkAfter: [COMMON_CHUNK_NAME.FileExport], }); return next; function generateRaxLoopCtrl( nodeItem: IPublicTypeNodeSchema, scope: IScope, config?: NodeGeneratorConfig, next?: NodePlugin, ): CodePiece[] { if (nodeItem.loop) { const loopItemName = nodeItem.loopArgs?.[0] || 'item'; const loopIndexName = nodeItem.loopArgs?.[1] || 'index'; const subScope = scope.createSubScope([loopItemName, loopIndexName]); const pieces: CodePiece[] = next ? next(nodeItem, subScope, config) : []; const loopDataExpr = tolerateEvalErrors ? `__$$evalArray(() => (${transformThis2Context( generateCompositeType(nodeItem.loop, scope, { handlers: config?.handlers }), scope, )}))` : `(${transformThis2Context( generateCompositeType(nodeItem.loop, scope, { handlers: config?.handlers }), scope, )})`; pieces.unshift({ value: `${loopDataExpr}.map((${loopItemName}, ${loopIndexName}) => ((__$$context) => (`, type: PIECE_TYPE.BEFORE, }); pieces.push({ value: `))(__$$createChildContext(__$$context, { ${loopItemName}, ${loopIndexName} })))`, type: PIECE_TYPE.AFTER, }); return pieces; } return next ? next(nodeItem, scope, config) : []; } }; return plugin; }; export default pluginFactory; function isImportAliasDefineChunk(chunk: ICodeChunk): chunk is ICodeChunk & { ext: { aliasName: string; originalName: string; dependency: IPublicTypeNpmInfo; }; } { return ( chunk.name === COMMON_CHUNK_NAME.ImportAliasDefine && !!chunk.ext && typeof chunk.ext.aliasName === 'string' && typeof chunk.ext.originalName === 'string' && !!(chunk.ext.dependency as IPublicTypeNpmInfo | null)?.componentName ); } function generateNodeAttrForRax( this: { cfg: PluginConfig }, attrData: { attrName: string; attrValue: IPublicTypeCompositeValue }, scope: IScope, config?: NodeGeneratorConfig, next?: AttrPlugin, ): CodePiece[] { if (!this.cfg.ignoreMiniApp && /^on/.test(attrData.attrName)) { // else: onXxx 的都是事件处理函数需要特殊处理下 return generateEventHandlerAttrForRax(attrData.attrName, attrData.attrValue, scope, config); } if (attrData.attrName === 'ref') { return [ { name: attrData.attrName, value: `__$$context._refsManager.linkRef('${attrData.attrValue}')`, type: PIECE_TYPE.ATTR, }, ]; } return next ? next(attrData, scope, config) : []; } function generateEventHandlerAttrForRax( attrName: string, attrValue: IPublicTypeCompositeValue, scope: IScope, config?: NodeGeneratorConfig, ): CodePiece[] { // -- 事件处理函数中 JSExpression 转成 JSFunction 来处理,避免当 JSExpression 处理的时候多包一层 eval 而导致 Rax 转码成小程序的时候出问题 const valueExpr = generateCompositeType( isJSExpression(attrValue) ? { type: 'JSFunction', value: attrValue.value } : attrValue, scope, { handlers: config?.handlers, }, ); // 查询当前作用域下的变量 const currentScopeVariables = scope.bindings?.getAllBindings() || []; if (currentScopeVariables.length <= 0) { return [ { type: PIECE_TYPE.ATTR, name: attrName, value: valueExpr, }, ]; } // 提取出所有的未定义的全局变量 const undeclaredVariablesInValueExpr = parseExpressionGetGlobalVariables(valueExpr); const referencedLocalVariables = _.intersection( undeclaredVariablesInValueExpr, currentScopeVariables, ); if (referencedLocalVariables.length <= 0) { return [ { type: PIECE_TYPE.ATTR, name: attrName, value: valueExpr, }, ]; } const wrappedAttrValueExpr = [ '(...__$$args) => {', ' if (__$$isMiniApp) {', ' const __$$event = __$$args[0];', ...referencedLocalVariables.map( (localVar) => `const ${localVar} = __$$event.target.dataset.${localVar};`, ), ` return (${valueExpr}).apply(this, __$$args);`, ' } else {', ` return (${valueExpr}).apply(this, __$$args);`, ' }', '}', ].join('\n'); return [ ...referencedLocalVariables.map((localVar) => ({ type: PIECE_TYPE.ATTR, name: `data-${changeCase.snake(localVar)}`, value: localVar, })), { type: PIECE_TYPE.ATTR, name: attrName, value: wrappedAttrValueExpr, }, ]; } ================================================ FILE: modules/code-generator/src/plugins/component/react/const.ts ================================================ export const REACT_CHUNK_NAME = { ClassRenderStart: 'ReactComponentClassRenderStart', ClassRenderPre: 'ReactComponentClassRenderPre', ClassRenderEnd: 'ReactComponentClassRenderEnd', ClassRenderJSX: 'ReactComponentClassRenderJSX', ClassDidMountStart: 'ReactComponentClassDidMountStart', ClassDidMountEnd: 'ReactComponentClassDidMountEnd', ClassDidMountContent: 'ReactComponentClassDidMountContent', }; ================================================ FILE: modules/code-generator/src/plugins/component/react/containerClass.ts ================================================ import changeCase from 'change-case'; import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER, } from '../../../const/generator'; import { REACT_CHUNK_NAME } from './const'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; import { ensureValidClassName } from '../../../utils/validate'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; // 将模块名转换成 PascalCase 的格式,并添加特定后缀,防止命名冲突 const componentClassName = ensureValidClassName( `${changeCase.pascalCase(ir.moduleName)}$$${ir.containerType}`, ); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.Start, content: `class ${componentClassName} extends React.Component {`, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], }); if (ir.containerType === 'Component') { next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: `static displayName = '${ir.moduleName}';`, linkAfter: [ CLASS_DEFINE_CHUNK_NAME.Start, ], }); } next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.End, content: '}', linkAfter: [ ...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.End], REACT_CHUNK_NAME.ClassRenderEnd, REACT_CHUNK_NAME.ClassDidMountEnd, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.ConstructorStart, content: 'constructor(props, context) { super(props); ', linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, content: '}', linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorEnd]], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: REACT_CHUNK_NAME.ClassDidMountStart, content: 'componentDidMount() {', linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.End]], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: REACT_CHUNK_NAME.ClassDidMountEnd, content: '}', linkAfter: [REACT_CHUNK_NAME.ClassDidMountContent, REACT_CHUNK_NAME.ClassDidMountStart], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: REACT_CHUNK_NAME.ClassRenderStart, content: 'render() {', linkAfter: [ ...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.End], REACT_CHUNK_NAME.ClassDidMountEnd, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: REACT_CHUNK_NAME.ClassRenderEnd, content: '}', linkAfter: [ REACT_CHUNK_NAME.ClassRenderStart, REACT_CHUNK_NAME.ClassRenderPre, REACT_CHUNK_NAME.ClassRenderJSX, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.FileExport, content: `export default ${componentClassName};`, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, CLASS_DEFINE_CHUNK_NAME.End, ], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/react/containerInitState.ts ================================================ import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; import { generateCompositeType } from '../../../utils/compositeType'; import { Scope } from '../../../utils/Scope'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; export interface PluginConfig { fileType?: string; implementType: 'inConstructor' | 'insMember' | 'hooks'; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig & { fileType: string } = { fileType: FileType.JSX, implementType: 'inConstructor', ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; const scope = Scope.createRootScope(); const state = ir.state || {}; const fields = Object.keys(state).map((stateName) => { const value = generateCompositeType(state[stateName], scope); return `${stateName}: ${value},`; }); if (cfg.implementType === 'inConstructor') { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: `this.state = { ${fields.join('')} };`, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]], }); } else if (cfg.implementType === 'insMember') { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: `state = { ${fields.join('')} };`, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]], }); } return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/react/containerInjectConstants.ts ================================================ import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME, DEFAULT_LINK_AFTER, } from '../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../../types'; export interface PluginConfig { fileType: string; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.InternalDepsImport, content: "import __$$constants from '../../constants';", linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: ` get constants() { return __$$constants || {}; } `, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/react/containerInjectContext.ts ================================================ import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; import { Scope } from '../../../utils/Scope'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; import { DEFAULT_LINK_AFTER } from '../../../const'; export interface PluginConfig { fileType: string; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; const scope = Scope.createRootScope(); const { inStrictMode } = next.contextData; if (!inStrictMode) { // 非严格模式下,上下文就是自己 next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: ` _context = this; `, linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], }); } else { // 严格模式下的上下文只保留协议中规定的那些 next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: ` _context = this._createContext(); `, linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, content: ` _createContext() { const self = this; const context = { get state() { return self.state; }, setState(newState, callback) { self.setState(newState, callback); }, get dataSourceMap() { return self._dataSourceEngine.dataSourceMap || {}; }, async reloadDataSource() { await self._dataSourceEngine.reloadDataSource(); }, get utils() { return self.utils; }, get page() { return context; }, get component() { return context; }, get props() { return self.props; }, get constants() { return self.constants; }, get $() { return self.$ }, get $$() { return self.$$ }, ...this._methods, }; return context; } `, linkAfter: DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod], }); } return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/react/containerInjectDataSourceEngine.ts ================================================ /* eslint-disable @typescript-eslint/indent */ import { IPublicTypeCompositeValue, IPublicTypeJSExpression, InterpretDataSourceConfig, isJSExpression, isJSFunction, } from '@alilc/lowcode-types'; import changeCase from 'change-case'; import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME, DEFAULT_LINK_AFTER, } from '../../../const/generator'; import { Scope } from '../../../utils/Scope'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IScope, } from '../../../types'; import { generateCompositeType } from '../../../utils/compositeType'; import { parseExpressionConvertThis2Context } from '../../../utils/expressionParser'; import { isValidContainerType } from '../../../utils/schema'; import { REACT_CHUNK_NAME } from './const'; import { isJSExpressionFn } from '../../../utils/common'; export interface PluginConfig { fileType?: string; /** * 数据源配置 */ datasourceConfig?: { /** 数据源引擎的版本 */ engineVersion?: string; /** 数据源引擎的包名 */ enginePackage?: string; /** 数据源 handlers 的版本 */ handlersVersion?: { [key: string]: string; }; /** 数据源 handlers 的包名 */ handlersPackages?: { [key: string]: string; }; }; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg = { ...config, fileType: config?.fileType || FileType.JSX, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const scope = Scope.createRootScope(); const dataSourceConfig = isValidContainerType(pre.ir) ? pre.ir.dataSource : null; const dataSourceItems: InterpretDataSourceConfig[] = (dataSourceConfig && dataSourceConfig.list) || []; const dataSourceEngineOptions = { runtimeConfig: true }; if (dataSourceItems.length > 0) { const requestHandlersMap: Record = {}; dataSourceItems.forEach((ds) => { const dsType = ds.type || 'fetch'; if (!(dsType in requestHandlersMap) && dsType !== 'custom') { const handlerFactoryName = `__$$create${changeCase.pascal(dsType)}RequestHandler`; requestHandlersMap[dsType] = { type: 'JSExpression', value: handlerFactoryName + (dsType === 'urlParams' ? '(window.location.search)' : '()'), }; const handlerFactoryExportName = `create${changeCase.pascal(dsType)}Handler`; const handlerPkgName = cfg.datasourceConfig?.handlersPackages?.[dsType] || `@alilc/lowcode-datasource-${changeCase.kebab(dsType)}-handler`; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: ` import { ${handlerFactoryExportName} as ${handlerFactoryName} } from '${handlerPkgName}'; `, linkAfter: [], }); } }); Object.assign(dataSourceEngineOptions, { requestHandlersMap }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: ` import { create as __$$createDataSourceEngine } from '@alilc/lowcode-datasource-engine/runtime'; `, linkAfter: [], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: ` _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine( this._dataSourceConfig, this, ${generateCompositeType(dataSourceEngineOptions, scope)} ); get dataSourceMap() { return this._dataSourceEngine.dataSourceMap || {}; } reloadDataSource = async () => { await this._dataSourceEngine.reloadDataSource(); } `, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]], }); next.chunks.unshift({ type: ChunkType.STRING, fileType: cfg.fileType, name: REACT_CHUNK_NAME.ClassDidMountContent, content: ` this._dataSourceEngine.reloadDataSource(); `, linkAfter: [REACT_CHUNK_NAME.ClassDidMountStart], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsMethod, content: ` _defineDataSourceConfig() { const _this = this; return (${generateCompositeType( { ...dataSourceConfig, list: [ ...dataSourceItems.map((item) => ({ ...item, isInit: wrapAsFunction(item.isInit, scope), options: wrapAsFunction(item.options, scope), })), ], }, scope, { handlers: { function: (jsFunc) => parseExpressionConvertThis2Context(jsFunc.value, '_this'), expression: (jsExpr) => parseExpressionConvertThis2Context(jsExpr.value, '_this'), }, }, )}); } `, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], }); } return next; }; return plugin; }; export default pluginFactory; function wrapAsFunction(value: IPublicTypeCompositeValue, scope: IScope): IPublicTypeCompositeValue { if (isJSExpression(value) || isJSFunction(value) || isJSExpressionFn(value)) { return { type: 'JSExpression', value: `function(){ return ((${value.value}))}.bind(this)`, }; } return { type: 'JSExpression', value: `function(){return((${generateCompositeType(value, scope)}))}.bind(this)`, }; } ================================================ FILE: modules/code-generator/src/plugins/component/react/containerInjectI18n.ts ================================================ import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME, DEFAULT_LINK_AFTER, } from '../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../../types'; import { getSlotRelativePath } from '../../../utils/pathHelper'; export interface PluginConfig { fileType: string; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.InternalDepsImport, content: ` import * as __$$i18n from '${getSlotRelativePath({ contextData: next.contextData, from: 'components', to: 'i18n' })}'; `, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: ` __$$i18n._inject2(this); `, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/react/containerInjectUtils.ts ================================================ import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME, DEFAULT_LINK_AFTER, } from '../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; import { getSlotRelativePath } from '../../../utils/pathHelper'; export interface PluginConfig { fileType?: string; /** prefer using class property to define utils */ preferClassProperty?: boolean; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig & { fileType: string } = { fileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; next.contextData.useRefApi = true; const useRef = !!ir.analyzeResult?.isUsingRef; next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.InternalDepsImport, content: ` import utils${useRef ? ', { RefsManager }' : ''} from '${getSlotRelativePath({ contextData: next.contextData, from: 'components', to: 'utils' })}'; `, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); if (cfg.preferClassProperty) { // mode: class property next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsVar, content: 'utils = utils;', linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]], }); } else { // mode: assign in constructor next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: 'this.utils = utils;', linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart], }); } if (useRef) { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: 'this._refsManager = new RefsManager();', linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsMethod, content: ` $ = (refName) => { return this._refsManager.get(refName); } `, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsMethod, content: ` $$ = (refName) => { return this._refsManager.getAll(refName); } `, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], }); } else { // useRef 为 false 的时候是指没有组件在 props 中配置 ref 属性,但这个时候其实也可能有代码访问 this.$/$$ 所以还是加上个空的代码 next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsMethod, content: ' $ = () => null; ', linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsMethod, content: ' $$ = () => []; ', linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], }); } return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/react/containerLifeCycle.ts ================================================ import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; import { REACT_CHUNK_NAME } from './const'; import { generateFunction } from '../../../utils/jsExpression'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeChunk, ICodeStruct, IContainerInfo, } from '../../../types'; import { isJSFunction, isJSExpression } from '@alilc/lowcode-types'; import { isJSExpressionFn } from '../../../utils/common'; export interface PluginConfig { fileType?: string; exportNameMapping?: Record; normalizeNameMapping?: Record; exclude?: string[]; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg = { fileType: FileType.JSX, exportNameMapping: {}, normalizeNameMapping: {}, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; if (ir.lifeCycles) { const { lifeCycles } = ir; const chunks = Object.keys(lifeCycles).map((lifeCycleName) => { // 过滤掉非法数据(有些场景下会误传入空字符串或 null) if ( !isJSFunction(lifeCycles[lifeCycleName]) && !isJSExpressionFn(lifeCycles[lifeCycleName]) && !isJSExpression(lifeCycles[lifeCycleName]) ) { return null; } let normalizeName; // constructor会取到对象的构造函数 if (lifeCycleName === 'constructor') { normalizeName = lifeCycleName; } else { normalizeName = cfg.normalizeNameMapping[lifeCycleName] || lifeCycleName; } if (cfg?.exclude?.includes(normalizeName)) { return null; } const exportName = cfg.exportNameMapping[lifeCycleName] || lifeCycleName; if (normalizeName === 'constructor') { return { type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: generateFunction(lifeCycles[lifeCycleName], { isBlock: true }), linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]], }; } if (normalizeName === 'componentDidMount') { return { type: ChunkType.STRING, fileType: cfg.fileType, name: REACT_CHUNK_NAME.ClassDidMountContent, content: generateFunction(lifeCycles[lifeCycleName], { isBlock: true }), linkAfter: [REACT_CHUNK_NAME.ClassDidMountStart], }; } if (normalizeName === 'render') { return { type: ChunkType.STRING, fileType: cfg.fileType, name: REACT_CHUNK_NAME.ClassRenderPre, content: generateFunction(lifeCycles[lifeCycleName], { isBlock: true }), linkAfter: [REACT_CHUNK_NAME.ClassRenderStart], }; } return { type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsMethod, content: generateFunction(lifeCycles[lifeCycleName], { name: exportName, isMember: true, }), linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], }; }).filter((i) => !!i); next.chunks.push(...chunks.filter((x): x is ICodeChunk => x !== null)); } return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/react/containerMethod.ts ================================================ import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; import { generateFunction } from '../../../utils/jsExpression'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeChunk, ICodeStruct, IContainerInfo, } from '../../../types'; import { isValidIdentifier } from '../../../utils/validate'; export interface PluginConfig { fileType: string; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; const { inStrictMode } = next.contextData; if (!ir.methods) { return next; } // 将这些 methods 都定义到 class 上 const { methods } = ir; const chunks = Object.keys(methods).map((methodName) => ({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsMethod, content: generateFunction(methods[methodName], { name: methodName, isMember: true }), linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], })); next.chunks.push(...chunks); // 严格模式下需要将这些方法都挂到上下文中 if (inStrictMode) { next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: Object.keys(ir.methods) .map((methodName) => isValidIdentifier(methodName) ? `.${methodName}` : `[${JSON.stringify(methodName)}]`, ) .map((method) => `this._context${method} = this${method};`) .join('\n'), linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]], }); } return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/react/jsx.ts ================================================ import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, HandlerSet, ICodeStruct, IContainerInfo, IScope, NodeGeneratorConfig, PIECE_TYPE, } from '../../../types'; import { REACT_CHUNK_NAME } from './const'; import { COMMON_CHUNK_NAME } from '../../../const/generator'; import { createReactNodeGenerator } from '../../../utils/nodeToJSX'; import { Scope } from '../../../utils/Scope'; import { IPublicTypeJSExpression } from '@alilc/lowcode-types'; import { generateExpression } from '../../../utils/jsExpression'; import { transformJsExpr } from '../../../core/jsx/handlers/transformJsExpression'; import { transformThis2Context } from '../../../core/jsx/handlers/transformThis2Context'; import { generateCompositeType } from '../../../utils/compositeType'; export interface PluginConfig { fileType?: string; nodeTypeMapping?: Record; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg = { fileType: FileType.JSX, // eslint-disable-next-line @typescript-eslint/consistent-type-assertions nodeTypeMapping: {} as Record, ...config, }; const { nodeTypeMapping } = cfg; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const { tolerateEvalErrors = true, evalErrorsHandler = '' } = next.contextData; // 这里会将内部的一些子上下文的访问(this.xxx)转换为 __$$context.xxx 的形式 // 与 Rax 所不同的是,这里不会将最顶层的 this 转换掉 const customHandlers: HandlerSet = { expression(input: IPublicTypeJSExpression, scope: IScope, config) { return transformJsExpr(generateExpression(input, scope), scope, { dontWrapEval: !(config?.tolerateEvalErrors ?? tolerateEvalErrors), dontTransformThis2ContextAtRootScope: true, }); }, function(input, scope: IScope) { return transformThis2Context( generateCompositeType( { type: 'JSFunction', value: input.value || 'function () {}', }, Scope.createRootScope(), ), scope, { ignoreRootScope: true }, ); }, }; const generatorPlugins: NodeGeneratorConfig = { handlers: customHandlers, tagMapping: (v) => nodeTypeMapping[v] || v, tolerateEvalErrors, }; if (next.contextData.useRefApi) { generatorPlugins.attrPlugins = [ (attrData, scope, pluginCfg, nextFunc) => { if (attrData.attrName === 'ref') { return [ { name: attrData.attrName, value: `this._refsManager.linkRef('${attrData.attrValue}')`, type: PIECE_TYPE.ATTR, }, ]; } return nextFunc ? nextFunc(attrData, scope, pluginCfg) : []; }, ]; } const generator = createReactNodeGenerator(generatorPlugins); const ir = next.ir as IContainerInfo; const scope: IScope = Scope.createRootScope(); const jsxContent = generator(ir, scope); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: REACT_CHUNK_NAME.ClassRenderJSX, content: ` const __$$context = this._context || this; const { state } = __$$context; return ${jsxContent}; `, linkAfter: [REACT_CHUNK_NAME.ClassRenderStart, REACT_CHUNK_NAME.ClassRenderPre], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.CustomContent, content: [ tolerateEvalErrors && ` function __$$eval(expr) { try { return expr(); } catch (error) { ${evalErrorsHandler} } } function __$$evalArray(expr) { const res = __$$eval(expr); return Array.isArray(res) ? res : []; } `, ` function __$$createChildContext(oldContext, ext) { const childContext = { ...oldContext, ...ext, }; childContext.__proto__ = oldContext; return childContext; } `, ] .filter(Boolean) .join('\n'), linkAfter: [COMMON_CHUNK_NAME.FileExport], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/react/reactCommonDeps.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: ` // 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 // 例外:react 框架的导出名和各种组件名除外。 import React from \'react\';`, linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/component/style/css.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; export interface PluginConfig { fileType: string; moduleFileType: string; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.CSS, moduleFileType: FileType.JSX, ...config, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: COMMON_CHUNK_NAME.StyleCssContent, content: ir.css, linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], }); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.moduleFileType, name: COMMON_CHUNK_NAME.InternalDepsImport, content: `import './index.${cfg.fileType}';`, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/constants.ts ================================================ import { COMMON_CHUNK_NAME } from '../../const/generator'; import { generateCompositeType } from '../../utils/compositeType'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../types'; import { Scope } from '../../utils/Scope'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IProjectInfo; const scope = Scope.createRootScope(); const constantStr = generateCompositeType(ir.constants || {}, scope); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileVarDefine, content: ` const __$$constants = (${constantStr}); `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileExport, content: ` export default __$$constants; `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileMainContent, ], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/i18n.ts ================================================ import { COMMON_CHUNK_NAME } from '../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IProjectInfo; const i18nStr = ir.i18n ? JSON.stringify(ir.i18n, null, 2) : '{}'; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileMainContent, content: ` const i18nConfig = ${i18nStr}; let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; const setLocale = (target) => { locale = target; }; const isEmptyVariables = variables => ( Array.isArray(variables) && variables.length === 0 || typeof variables === 'object' && (!variables || Object.keys(variables).length === 0) ); // 按低代码规范里面的要求进行变量替换 const format = (msg, variables) => ( typeof msg === 'string' ? msg.replace(/\\\$?\\{(\\w+)\\}/g, (match, key) => variables?.[key] ?? '') : msg ); const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); return fallback === undefined ? \`\${id}\` : fallback; } return format(msg, variables); } const i18n = (id, params) => { return i18nFormat({ id }, params); }; // 将国际化的一些方法注入到目标对象&上下文中 const _inject2 = (target) => { target.i18n = i18n; target.getLocale = getLocale; target.setLocale = (locale) => { setLocale(locale); target.forceUpdate(); }; target._i18nText = (t) => { // 优先取直接传过来的语料 const localMsg = t[locale] ?? t[String(locale).replace('-', '_')] if (localMsg != null) { return format(localMsg, t.params); } // 其次用项目级别的 const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); if (projectMsg != null) { return projectMsg; } // 兜底用 use 指定的或默认语言的 return format(t[t.use || "zh-CN"] ?? t.en_US, t.params); } // 注入到上下文中去 if (target._context && target._context !== target) { Object.assign(target._context, { i18n, getLocale, setLocale: target.setLocale }); } } `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileExport, content: ` export { getLocale, setLocale, i18n, i18nFormat, _inject2, }; `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileMainContent, ], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/utils.ts ================================================ import { COMMON_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IUtilInfo, } from '../../types'; const pluginFactory: BuilderComponentPluginFactory = (baseFramework?: string) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const framework = baseFramework || 'react'; const next: ICodeStruct = { ...pre, }; const ir = next.ir as IUtilInfo; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: ` import { createRef } from '${framework}'; `, linkAfter: [], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileUtilDefine, content: ` export class RefsManager { constructor() { this.refInsStore = {}; } clearNullRefs() { Object.keys(this.refInsStore).forEach((refName) => { const filteredInsList = this.refInsStore[refName].filter(insRef => !!insRef.current); if (filteredInsList.length > 0) { this.refInsStore[refName] = filteredInsList; } else { delete this.refInsStore[refName]; } }); } get(refName) { this.clearNullRefs(); if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) { return this.refInsStore[refName][0].current; } return null; } getAll(refName) { this.clearNullRefs(); if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) { return this.refInsStore[refName].map(i => i.current); } return []; } linkRef(refName) { const refIns = createRef(); this.refInsStore[refName] = this.refInsStore[refName] || []; this.refInsStore[refName].push(refIns); return refIns; } } `, linkAfter: [...DEFAULT_LINK_AFTER[COMMON_CHUNK_NAME.FileUtilDefine]], }); if (ir.utils) { next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileExport, content: ` export default { `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileMainContent, ], }); ir.utils.forEach((util) => { if (util.type === 'function') { next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileVarDefine, content: ` const ${util.name} = ${util.content.value}; `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, ], }); } next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileExport, content: `${util.name},`, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileMainContent, ], }); }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileExport, content: ` }; `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileMainContent, ], }); } return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/index.ts ================================================ import template from './template'; import entry from './plugins/entry'; import entryHtml from './plugins/entryHtml'; import globalStyle from './plugins/globalStyle'; import packageJSON from './plugins/packageJSON'; import router from './plugins/router'; export default { template, plugins: { entry, entryHtml, globalStyle, packageJSON, router, }, }; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/plugins/entry.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../../../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: ` import { createApp } from 'ice'; `, linkAfter: [], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileMainContent, content: ` const appConfig = { app: { rootId: 'app', }, router: { type: 'hash', }, }; createApp(appConfig); `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/plugins/entryHtml.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../../../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IProjectInfo; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.HTML, name: COMMON_CHUNK_NAME.HtmlContent, content: ` ${ir?.meta?.name || 'Ice App'}
`, linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/plugins/globalStyle.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../../../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IProjectInfo; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.SCSS, name: COMMON_CHUNK_NAME.StyleDepsImport, content: ` // 引入默认全局样式 @import '@alifd/next/reset.scss'; `, linkAfter: [], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.SCSS, name: COMMON_CHUNK_NAME.StyleCssContent, content: ` body { -webkit-font-smoothing: antialiased; } `, linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.SCSS, name: COMMON_CHUNK_NAME.StyleCssContent, content: ir.css || '', linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/plugins/packageJSON.ts ================================================ import { PackageJSON } from '@alilc/lowcode-types'; import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../../../../types'; import { buildDataSourceDependencies } from '../../../../../utils/dataSource'; interface IIceJsPackageJSON extends PackageJSON { ideMode: { name: string; }; iceworks: { type: string; adapter: string; }; originTemplate: string; } export type IceJsPackageJsonPluginConfig = { /** * 数据源配置 */ datasourceConfig?: { /** 数据源引擎的版本 */ engineVersion?: string; /** 数据源引擎的包名 */ enginePackage?: string; /** 数据源 handlers 的版本 */ handlersVersion?: { [key: string]: string; }; /** 数据源 handlers 的包名 */ handlersPackages?: { [key: string]: string; }; }; /** 包名 */ packageName?: string; /** 版本 */ packageVersion?: string; }; const pluginFactory: BuilderComponentPluginFactory = (cfg) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IProjectInfo; const packageJson: IIceJsPackageJSON = { name: cfg?.packageName || 'icejs-demo-app', version: cfg?.packageVersion || '0.1.5', description: '轻量级模板,使用 JavaScript,仅包含基础的 Layout。', dependencies: { moment: '^2.24.0', react: '^16.4.1', 'react-dom': '^16.4.1', 'react-router': '^5.2.1', '@alifd/theme-design-pro': '^0.x', 'intl-messageformat': '^9.3.6', '@ice/store': '^1.4.3', '@loadable/component': '^5.15.2', // 数据源相关的依赖: ...buildDataSourceDependencies(ir, cfg?.datasourceConfig), }, devDependencies: { '@ice/spec': '^1.0.0', 'build-plugin-fusion': '^0.1.0', 'build-plugin-moment-locales': '^0.1.0', eslint: '^6.0.1', 'ice.js': '^1.0.0', stylelint: '^13.2.0', }, scripts: { start: 'icejs start', build: 'icejs build', lint: 'npm run eslint && npm run stylelint', eslint: 'eslint --cache --ext .js,.jsx ./', stylelint: 'stylelint ./**/*.scss', }, ideMode: { name: 'ice-react', }, iceworks: { type: 'react', adapter: 'adapter-react-v3', }, engines: { node: '>=8.0.0', }, repository: { type: 'git', url: 'http://gitlab.xxx.com/msd/leak-scan/tree/master', }, private: true, originTemplate: '@alifd/scaffold-lite-js', }; ir.packages.forEach((packageInfo) => { packageJson.dependencies[packageInfo.package] = packageInfo.version; }); next.chunks.push({ type: ChunkType.JSON, fileType: FileType.JSON, name: COMMON_CHUNK_NAME.FileMainContent, content: packageJson, linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/plugins/router.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IRouterInfo, } from '../../../../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IRouterInfo; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.InternalDepsImport, content: ` import BasicLayout from '@/layouts/BasicLayout'; `, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileVarDefine, content: ` const routerConfig = [ { path: '/', component: BasicLayout, children: [ ${ir.routes .map( (route) => ` { path: '${route.path}', component: ${route.componentName}, } `, ) .join(',')} ], }, ]; `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileExport, content: ` export default routerConfig; `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileMainContent, ], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/index.ts ================================================ import { ResultDir } from '@alilc/lowcode-types'; import { IProjectTemplate } from '../../../../../types'; import { generateStaticFiles } from './static-files'; const icejsTemplate: IProjectTemplate = { slots: { components: { path: ['src', 'components'], }, pages: { path: ['src', 'pages'], }, router: { path: ['src'], fileName: 'routes', }, entry: { path: ['src'], fileName: 'app', }, constants: { path: ['src'], fileName: 'constants', }, utils: { path: ['src'], fileName: 'utils', }, i18n: { path: ['src'], fileName: 'i18n', }, globalStyle: { path: ['src'], fileName: 'global', }, htmlEntry: { path: ['public'], fileName: 'index', }, packageJSON: { path: [], fileName: 'package', }, }, generateTemplate(): ResultDir { return generateStaticFiles(); }, }; export default icejsTemplate; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/static-files.ts ================================================ import { ResultDir } from '@alilc/lowcode-types'; import { createResultDir } from '../../../../../utils/resultHelper'; import { runFileGenerator } from '../../../../../utils/templateHelper'; import file12 from './files/abc.json'; import file11 from './files/build.json'; import file10 from './files/editorconfig'; import file9 from './files/eslintignore'; import file8 from './files/eslintrc.js'; import file7 from './files/gitignore'; import file6 from './files/jsconfig.json'; import file5 from './files/prettierignore'; import file4 from './files/prettierrc.js'; import file13 from './files/README.md'; import file16 from './files/src/layouts/BasicLayout/components/Footer/index.jsx'; import file17 from './files/src/layouts/BasicLayout/components/Footer/index.style'; import file18 from './files/src/layouts/BasicLayout/components/Logo/index.jsx'; import file19 from './files/src/layouts/BasicLayout/components/Logo/index.style'; import file20 from './files/src/layouts/BasicLayout/components/PageNav/index.jsx'; import file14 from './files/src/layouts/BasicLayout/index.jsx'; import file15 from './files/src/layouts/BasicLayout/menuConfig.js'; import file3 from './files/stylelintignore'; import file2 from './files/stylelintrc.js'; import file1 from './files/tsconfig.json'; export function generateStaticFiles(root = createResultDir('.')): ResultDir { runFileGenerator(root, file1); runFileGenerator(root, file2); runFileGenerator(root, file3); runFileGenerator(root, file4); runFileGenerator(root, file5); runFileGenerator(root, file6); runFileGenerator(root, file7); runFileGenerator(root, file8); runFileGenerator(root, file9); runFileGenerator(root, file10); runFileGenerator(root, file11); runFileGenerator(root, file12); runFileGenerator(root, file13); runFileGenerator(root, file14); runFileGenerator(root, file15); runFileGenerator(root, file16); runFileGenerator(root, file17); runFileGenerator(root, file18); runFileGenerator(root, file19); runFileGenerator(root, file20); return root; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/abc.json.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; export default function getFile(): [string[], ResultFile] { return [ [], { name: 'abc', ext: 'json', content: ` { "type": "ice-app", "builder": "@ali/builder-ice-app" } `, }, ]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/build.json.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; export default function getFile(): [string[], ResultFile] { return [ [], { name: 'build', ext: 'json', content: ` { "entry": "src/app.js", "plugins": [ [ "build-plugin-fusion", { "themePackage": "@alifd/theme-design-pro" } ], [ "build-plugin-moment-locales", { "locales": [ "zh-cn" ] } ] ] } `, }, ]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/editorconfig.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( '.editorconfig', '', ` # http://editorconfig.org root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/eslintignore.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( '.eslintignore', '', ` # 忽略目录 build/ tests/ demo/ .ice/ # node 覆盖率文件 coverage/ # 忽略文件 **/*-min.js **/*.min.js package-lock.json yarn.lock `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/eslintrc.js.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( '.eslintrc', 'js', ` const { eslint } = require('@ice/spec'); module.exports = eslint; `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/gitignore.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( '.gitignore', '', ` # See https://help.github.com/ignore-files/ for more about ignoring files. # dependencies node_modules/ # production build/ dist/ tmp/ lib/ # misc .idea/ .happypack .DS_Store *.swp *.dia~ .ice npm-debug.log* yarn-debug.log* yarn-error.log* index.module.scss.d.ts `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/jsconfig.json.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( 'jsconfig', 'json', ` { "compilerOptions": { "baseUrl": ".", "jsx": "react", "paths": { "@/*": ["./src/*"], "ice": [".ice/index.ts"], "ice/*": [".ice/pages/*"] } } } `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/prettierignore.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( '.prettierignore', '', ` build/ tests/ demo/ .ice/ coverage/ **/*-min.js **/*.min.js package-lock.json yarn.lock `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/prettierrc.js.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( '.prettierrc', 'js', ` const { prettier } = require('@ice/spec'); module.exports = prettier; `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/README.md.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( 'README', 'md', ` ## Scaffold Lite > 轻量级模板,使用 JavaScript,仅包含基础的 Layout。 ## 使用 \`\`\`bash # 安装依赖 $ npm install # 启动服务 $ npm start # visit http://localhost:3333 \`\`\` [More docs](https://ice.work/docs/guide/about). ## 目录 \`\`\`md ├── build/ # 构建产物 ├── mock/ # 本地模拟数据 │ ├── index.[j,t]s ├── public/ │ ├── index.html # 应用入口 HTML │ └── favicon.png # Favicon ├── src/ # 源码路径 │ ├── components/ # 自定义业务组件 │ │ └── Guide/ │ │ ├── index.[j,t]sx │ │ ├── index.module.scss │ ├── layouts/ # 布局组件 │ │ └── BasicLayout/ │ │ ├── index.[j,t]sx │ │ └── index.module.scss │ ├── pages/ # 页面 │ │ └── Home/ # home 页面,约定路由转成小写 │ │ ├── components/ # 页面级自定义业务组件 │ │ ├── models.[j,t]sx # 页面级数据状态 │ │ ├── index.[j,t]sx # 页面入口 │ │ └── index.module.scss # 页面样式文件 │ ├── configs/ # [可选] 配置文件 │ │ └── menu.[j,t]s # [可选] 菜单配置 │ ├── models/ # [可选] 应用级数据状态 │ │ └── user.[j,t]s │ ├── utils/ # [可选] 工具库 │ ├── global.scss # 全局样式 │ ├── routes.[j,t]s # 路由配置 │ └── app.[j,t]s[x] # 应用入口脚本 ├── build.json # 工程配置 ├── README.md ├── package.json ├── .editorconfig ├── .eslintignore ├── .eslintrc.[j,t]s ├── .gitignore ├── .stylelintignore ├── .stylelintrc.[j,t]s ├── .gitignore └── [j,t]sconfig.json \`\`\` `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/stylelintignore.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( '.stylelintignore', '', ` # 忽略目录 build/ tests/ demo/ # node 覆盖率文件 coverage/ `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/stylelintrc.js.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( '.stylelintrc', 'js', ` const { stylelint } = require('@ice/spec'); module.exports = stylelint; `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs/template/files/tsconfig.json.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( 'tsconfig', 'json', ` { "compileOnSave": false, "buildOnSave": false, "compilerOptions": { "baseUrl": ".", "outDir": "build", "module": "esnext", "target": "es6", "jsx": "react", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "lib": ["es6", "dom"], "sourceMap": true, "allowJs": true, "rootDir": "./", "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": true, "noImplicitAny": false, "importHelpers": true, "strictNullChecks": true, "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": true, "skipLibCheck": true, "paths": { "@/*": ["./src/*"], "ice": [".ice/index.ts"], "ice/*": [".ice/pages/*"] } }, "include": ["src/*", ".ice"], "exclude": ["node_modules", "build", "public"] } `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/index.ts ================================================ import template from './template'; import globalStyle from './plugins/globalStyle'; import packageJSON from './plugins/packageJSON'; import layout from './plugins/layout'; import appConfig from './plugins/appConfig'; import buildConfig from './plugins/buildConfig'; export default { template, plugins: { appConfig, buildConfig, globalStyle, packageJSON, layout, }, }; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/plugins/appConfig.ts ================================================ import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../../../../types'; import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; export interface AppConfigPluginConfig { } function getContent() { return `import { defineAppConfig } from 'ice'; // App config, see https://v3.ice.work/docs/guide/basic/app export default defineAppConfig(() => ({ // Set your configs here. app: { rootId: 'App', }, router: { type: 'browser', basename: '/', }, }));`; } const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.TS, name: COMMON_CHUNK_NAME.FileMainContent, content: getContent(), linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/plugins/buildConfig.ts ================================================ import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../../../../types'; import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { format } from '../../../../../utils/format'; import { getThemeInfo } from '../../../../../utils/theme'; export interface BuildConfigPluginConfig { /** 包名 */ themePackage?: string; } function getContent(cfg?: BuildConfigPluginConfig, routesContent?: string) { return ` import { join } from 'path'; import { defineConfig } from '@ice/app'; import _ from 'lodash'; import fusion from '@ice/plugin-fusion'; import locales from '@ice/plugin-moment-locales'; import type { Plugin } from '@ice/app/esm/types'; interface PluginOptions { id: string; } const plugin: Plugin = (options) => ({ // name 可选,插件名称 name: 'plugin-name', // setup 必选,用于定制工程构建配置 setup: ({ onGetConfig, modifyUserConfig }) => { modifyUserConfig('codeSplitting', 'page'); onGetConfig((config) => { config.entry = { web: join(process.cwd(), '.ice/entry.client.tsx'), }; config.cssFilename = '[name].css'; config.configureWebpack = config.configureWebpack || []; config.configureWebpack?.push((webpackConfig) => { if (webpackConfig.output) { webpackConfig.output.filename = '[name].js'; webpackConfig.output.chunkFilename = '[name].js'; } return webpackConfig; }); config.swcOptions = _.merge(config.swcOptions, { compilationConfig: { jsc: { transform: { react: { runtime: 'classic', }, }, }, } }); // 解决 webpack publicPath 问题 config.transforms = config.transforms || []; config.transforms.push((source: string, id: string) => { if (id.includes('.ice/entry.client.tsx')) { let code = \` if (!__webpack_public_path__?.startsWith('http') && document.currentScript) { // @ts-ignore __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\\\/)[^/]+$/, '$1'); window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {}; window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__; } \`; code += source; return { code }; } }); }); }, }); // The project config, see https://v3.ice.work/docs/guide/basic/config const minify = process.env.NODE_ENV === 'production' ? 'swc' : false; export default defineConfig(() => ({ ssr: false, ssg: false, minify, ${routesContent} externals: { react: 'React', 'react-dom': 'ReactDOM', 'react-dom/client': 'ReactDOM', '@alifd/next': 'Next', lodash: 'var window._', '@alilc/lowcode-engine': 'var window.AliLowCodeEngine', }, plugins: [ fusion(${cfg?.themePackage ? `{ importStyle: 'sass', themePackage: '${getThemeInfo(cfg.themePackage).name}', }` : `{ importStyle: 'sass', }`}), locales(), plugin(), ] })); `; } function getRoutesContent(navData: any, needShell = true) { const routes = [ 'routes: {', ' defineRoutes: route => {', ]; function _getRoutes(nav: any, _routes: string[] = []) { const { slug, children } = nav; if (children && children.length > 0) { children.forEach((_nav: any) => _getRoutes(_nav, _routes)); } else if (slug) { _routes.push(`route('/${slug}', '${slug}/index.jsx');`); } } if (needShell) { routes.push(" route('/', 'layout.jsx', () => {"); } navData?.forEach((nav: any) => { _getRoutes(nav, routes); }); if (needShell) { routes.push(' });'); } routes.push(' }'); // end of defineRoutes routes.push(' },'); // end of routes return routes.join('\n'); } const pluginFactory: BuilderComponentPluginFactory = (cfg?) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const { navConfig } = next.contextData; const routesContent = navConfig?.data ? getRoutesContent(navConfig.data, true) : ''; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.MTS, name: COMMON_CHUNK_NAME.FileMainContent, content: format(getContent(cfg, routesContent)), linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/plugins/globalStyle.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../../../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IProjectInfo; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.SCSS, name: COMMON_CHUNK_NAME.StyleDepsImport, content: ` // 引入默认全局样式 @import '@alifd/next/reset.scss'; `, linkAfter: [], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.SCSS, name: COMMON_CHUNK_NAME.StyleCssContent, content: ` body { -webkit-font-smoothing: antialiased; } `, linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.SCSS, name: COMMON_CHUNK_NAME.StyleCssContent, content: ir.css || '', linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/plugins/layout.ts ================================================ import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../../../../types'; import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.FileMainContent, content: ` import { Outlet } from 'ice'; import BasicLayout from '@/layouts/BasicLayout'; export default function Layout() { return ( );; } `, linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/plugins/packageJSON.ts ================================================ import { PackageJSON } from '@alilc/lowcode-types'; import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../../../../types'; import { buildDataSourceDependencies } from '../../../../../utils/dataSource'; interface IIceJs3PackageJSON extends PackageJSON { originTemplate: string; } export type IceJs3PackageJsonPluginConfig = { /** * 数据源配置 */ datasourceConfig?: { /** 数据源引擎的版本 */ engineVersion?: string; /** 数据源引擎的包名 */ enginePackage?: string; /** 数据源 handlers 的版本 */ handlersVersion?: { [key: string]: string; }; /** 数据源 handlers 的包名 */ handlersPackages?: { [key: string]: string; }; }; /** 包名 */ packageName?: string; /** 版本 */ packageVersion?: string; }; const pluginFactory: BuilderComponentPluginFactory = (cfg) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IProjectInfo; const packageJson: IIceJs3PackageJSON = { name: cfg?.packageName || 'icejs3-demo-app', version: cfg?.packageVersion || '0.1.5', description: 'icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。', dependencies: { moment: '^2.24.0', react: '^18.2.0', 'react-dom': '^18.2.0', 'react-router': '^6.9.0', 'react-router-dom': '^6.9.0', 'intl-messageformat': '^9.3.6', '@alifd/next': '1.26.15', '@ice/runtime': '~1.1.0', // 数据源相关的依赖: ...buildDataSourceDependencies(ir, cfg?.datasourceConfig), }, devDependencies: { '@ice/app': '~3.1.0', '@types/react': '^18.0.0', '@types/react-dom': '^18.0.0', '@types/node': '^18.11.17', '@ice/plugin-fusion': '^1.0.1', '@ice/plugin-moment-locales': '^1.0.0', eslint: '^6.0.1', stylelint: '^13.2.0', }, scripts: { start: 'ice start', build: 'ice build', lint: 'npm run eslint && npm run stylelint', eslint: 'eslint --cache --ext .js,.jsx ./', stylelint: 'stylelint ./**/*.scss', }, engines: { node: '>=14.0.0', }, repository: { type: 'git', url: 'http://gitlab.xxx.com/msd/leak-scan/tree/master', }, private: true, originTemplate: '@alifd/scaffold-lite-js', }; ir.packages.forEach((packageInfo) => { packageJson.dependencies[packageInfo.package] = packageInfo.version; }); next.chunks.push({ type: ChunkType.JSON, fileType: FileType.JSON, name: COMMON_CHUNK_NAME.FileMainContent, content: packageJson, linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/template/index.ts ================================================ import { IProjectTemplate } from '../../../../../types'; import { generateStaticFiles } from './static-files'; const icejs3Template: IProjectTemplate = { slots: { components: { path: ['src', 'components'], fileName: 'index', }, pages: { path: ['src', 'pages'], fileName: 'index', }, entry: { path: ['src'], fileName: 'app', }, constants: { path: ['src'], fileName: 'constants', }, utils: { path: ['src'], fileName: 'utils', }, i18n: { path: ['src'], fileName: 'i18n', }, globalStyle: { path: ['src'], fileName: 'global', }, packageJSON: { path: [], fileName: 'package', }, appConfig: { path: ['src'], fileName: 'app', }, buildConfig: { path: [], fileName: 'ice.config', }, layout: { path: ['src', 'pages'], fileName: 'layout', }, }, generateTemplate() { return generateStaticFiles(); }, }; export default icejs3Template; ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/template/static-files.ts ================================================ import { ResultDir } from '@alilc/lowcode-types'; import { createResultDir } from '../../../../../utils/resultHelper'; import { runFileGenerator } from '../../../../../utils/templateHelper'; import file1 from './files/gitignore'; import file2 from './files/README.md'; import file3 from './files/browserslistrc'; import file4 from './files/typings'; import file5 from './files/document'; import file6 from './files/src/layouts/BasicLayout/components/Footer/index.jsx'; import file7 from './files/src/layouts/BasicLayout/components/Footer/index.style'; import file8 from './files/src/layouts/BasicLayout/components/Logo/index.jsx'; import file9 from './files/src/layouts/BasicLayout/components/Logo/index.style'; import file10 from './files/src/layouts/BasicLayout/components/PageNav/index.jsx'; import file11 from './files/src/layouts/BasicLayout/index.jsx'; import file12 from './files/src/layouts/BasicLayout/menuConfig.js'; export function generateStaticFiles(root = createResultDir('.')): ResultDir { runFileGenerator(root, file1); runFileGenerator(root, file2); runFileGenerator(root, file3); runFileGenerator(root, file4); runFileGenerator(root, file5); runFileGenerator(root, file6); runFileGenerator(root, file7); runFileGenerator(root, file8); runFileGenerator(root, file9); runFileGenerator(root, file10); runFileGenerator(root, file11); runFileGenerator(root, file12); return root; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/template/files/browserslistrc.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( '.browserslistrc', '', `defaults ios_saf 9 `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/template/files/document.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; /* eslint-disable max-len */ export default function getFile(): [string[], ResultFile] { const file = createResultFile( 'document', 'tsx', `import React from 'react'; import { Meta, Title, Links, Main, Scripts } from 'ice'; export default function Document() { return ( <Links /> </head> <body> <Main /> <script crossOrigin="anonymous" src="//g.alicdn.com/code/lib/react/18.2.0/umd/react.development.js" /> <script crossOrigin="anonymous" src="//g.alicdn.com/code/lib/react-dom/18.2.0/umd/react-dom.development.js" /> <script crossOrigin="anonymous" src="//g.alicdn.com/code/lib/??react-router/6.9.0/react-router.production.min.js,react-router-dom/6.9.0/react-router-dom.production.min.js" /> <script crossOrigin="anonymous" src="//g.alicdn.com/code/lib/alifd__next/1.26.22/next.min.js" /> <script crossOrigin="anonymous" src="//g.alicdn.com/code/lib/prop-types/15.7.2/prop-types.js" /> <script crossOrigin="anonymous" src="//g.alicdn.com/platform/c/??lodash/4.6.1/lodash.min.js,immutable/3.7.6/dist/immutable.min.js" /> <Scripts /> </body> </html> ); }`, ); return [['src'], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/template/files/gitignore.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( '.gitignore', '', ` # See https://help.github.com/ignore-files/ for more about ignoring files. # dependencies node_modules/ # production build/ dist/ tmp/ lib/ # misc .idea/ .happypack .DS_Store *.swp *.dia~ .ice npm-debug.log* yarn-debug.log* yarn-error.log* index.module.scss.d.ts `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/template/files/README.md.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( 'README', 'md', 'This project is generated by lowcode-code-generator & lowcode-solution-icejs3.', ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/template/files/tsconfig.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( 'tsconfig', 'json', ` { "compilerOptions": { "baseUrl": "./", "module": "ESNext", "target": "ESNext", "lib": ["DOM", "ESNext", "DOM.Iterable"], "jsx": "react-jsx", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": true, "noImplicitAny": false, "importHelpers": true, "strictNullChecks": true, "suppressImplicitAnyIndexErrors": true, "skipLibCheck": true, "paths": { "@/*": ["./src/*"], "ice": [".ice"] } }, "include": ["src", ".ice"], "exclude": ["build"] } `, ); return [[], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/icejs3/template/files/typings.ts ================================================ import { ResultFile } from '@alilc/lowcode-types'; import { createResultFile } from '../../../../../../utils/resultHelper'; export default function getFile(): [string[], ResultFile] { const file = createResultFile( 'typings.d', 'ts', `/// <reference types="@ice/app/types" /> export {}; declare global { interface Window { g_config: Record<string, any>; } } `, ); return [['src'], file]; } ================================================ FILE: modules/code-generator/src/plugins/project/framework/rax/index.ts ================================================ import template from './template'; import entry from './plugins/entry'; import appConfig from './plugins/appConfig'; import buildConfig from './plugins/buildConfig'; import entryDocument from './plugins/entryDocument'; import globalStyle from './plugins/globalStyle'; import packageJSON from './plugins/packageJSON'; export default { template, plugins: { appConfig, buildConfig, entry, entryDocument, globalStyle, packageJSON, }, }; ================================================ FILE: modules/code-generator/src/plugins/project/framework/rax/plugins/appConfig.ts ================================================ import changeCase from 'change-case'; import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IParseResult, } from '../../../../../types'; import { ensureValidClassName } from '../../../../../utils/validate'; import { RaxFrameworkOptions } from '../types/RaxFrameworkOptions'; const pluginFactory: BuilderComponentPluginFactory<RaxFrameworkOptions> = (cfg) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IParseResult; const routes = ir.globalRouter?.routes?.map((route) => ({ path: route.path, source: `pages/${ensureValidClassName(changeCase.pascalCase(route.fileName))}/index`, })) || [{ path: '/', source: 'pages/Home/index' }]; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSON, name: COMMON_CHUNK_NAME.CustomContent, content: ` { "routes": ${JSON.stringify(routes, null, 2)}, "window": { "title": ${JSON.stringify( cfg?.title || ir.project?.meta?.title || ir.project?.meta?.name || '', )} } } `, linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/rax/plugins/buildConfig.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IParseResult, } from '../../../../../types'; import type { RaxFrameworkOptions } from '../types/RaxFrameworkOptions'; const pluginFactory: BuilderComponentPluginFactory<RaxFrameworkOptions> = (cfg) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IParseResult; const miniAppBuildType = cfg?.buildConfig?.miniAppBuildType || ir.project?.config?.miniAppBuildType; const targets = cfg?.targets || ['web']; const buildCfg = { inlineStyle: false, plugins: [], targets, miniapp: miniAppBuildType ? { buildType: miniAppBuildType, ...cfg?.buildConfig?.miniapp, } : cfg?.buildConfig?.miniapp, ...cfg?.buildConfig, }; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSON, name: COMMON_CHUNK_NAME.CustomContent, content: `${JSON.stringify(buildCfg, null, 2)}\n`, linkAfter: [], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/rax/plugins/entry.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../../../../types'; import { RaxFrameworkOptions } from '../types/RaxFrameworkOptions'; const pluginFactory: BuilderComponentPluginFactory<RaxFrameworkOptions> = (cfg) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: ` import { runApp } from 'rax-app'; import './global.${cfg?.globalStylesFileType || 'css'}'; `, linkAfter: [], }); // 应用配置 const appConfig = cfg?.appConfig || {}; Object.assign(appConfig, { // 路由配置 router: { mode: 'hash', ...appConfig.router, }, }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileMainContent, content: ` runApp(${JSON.stringify(appConfig, null, 2)}); `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], }); return next; }; return plugin; }; export default pluginFactory; ================================================ FILE: modules/code-generator/src/plugins/project/framework/rax/plugins/entryDocument.ts ================================================ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../../../../types'; import { RaxFrameworkOptions } from '../types/RaxFrameworkOptions'; /** * 这种方式已经不推荐使用了 */ const pluginFactory: BuilderComponentPluginFactory<RaxFrameworkOptions> = (cfg) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IProjectInfo; next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.CustomContent, content: ` import { createElement } from 'rax'; import { Root, Style, Script } from 'rax-document'; function Document() { return ( <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover" /> <title>${cfg?.title || ir?.meta?.name || 'Rax App'} `, ); } } } parseAssetList(vendors); const styleFrags = Object.keys(styles) .map((key) => { return `${styles[key].join('\n')}`; }) .join(''); const scriptFrags = Object.keys(scripts) .map((key) => { return scripts[key].join('\n'); }) .join(''); doc.open(); doc.write(` ${styleFrags} ${scriptFrags} `); doc.close(); return new Promise((resolve) => { const renderer = win.SimulatorRenderer; if (renderer) { return resolve(renderer); } const loaded = () => { resolve(win.SimulatorRenderer || host.renderer); win.removeEventListener('load', loaded); }; win.addEventListener('load', loaded); }); } ================================================ FILE: packages/designer/src/builtin-simulator/host-view.tsx ================================================ import React, { Component } from 'react'; import { observer } from '@alilc/lowcode-editor-core'; import { BuiltinSimulatorHost, BuiltinSimulatorProps } from './host'; import { BemTools } from './bem-tools'; import { Project } from '../project'; import './host.less'; /* Simulator 模拟器,可替换部件,有协议约束,包含画布的容器,使用场景:当 Canvas 大小变化时,用来居中处理 或 定位 Canvas Canvas(DeviceShell) 设备壳层,通过背景图片来模拟,通过设备预设样式改变宽度、高度及定位 CanvasViewport CanvasViewport 页面编排场景中宽高不可溢出 Canvas 区 Content(Shell) 内容外层,宽高紧贴 CanvasViewport,禁用边框,禁用 margin BemTools 辅助显示层,初始相对 Content 位置 0,0,紧贴 Canvas, 根据 Content 滚动位置,改变相对位置 */ type SimulatorHostProps = BuiltinSimulatorProps & { project: Project; onMount?: (host: BuiltinSimulatorHost) => void; }; export class BuiltinSimulatorHostView extends Component { readonly host: BuiltinSimulatorHost; constructor(props: any) { super(props); const { project, onMount, designer } = this.props; this.host = (project.simulator as BuiltinSimulatorHost) || new BuiltinSimulatorHost(project, designer); this.host.setProps(this.props); onMount?.(this.host); } shouldComponentUpdate(nextProps: BuiltinSimulatorProps) { this.host.setProps(nextProps); return false; } render() { return (
{/* progressing.visible ? : null */}
); } } @observer class Canvas extends Component<{ host: BuiltinSimulatorHost }> { render() { const sim = this.props.host; let className = 'lc-simulator-canvas'; const { canvas = {}, viewport = {} } = sim.deviceStyle || {}; if (sim.deviceClassName) { className += ` ${sim.deviceClassName}`; } else if (sim.device) { className += ` lc-simulator-device-${sim.device}`; } return (
sim.mountViewport(elmt)} className="lc-simulator-canvas-viewport" style={viewport}>
); } } @observer class Content extends Component<{ host: BuiltinSimulatorHost }> { state = { disabledEvents: false, }; private dispose?: () => void; componentDidMount() { const editor = this.props.host.designer.editor; const onEnableEvents = (type: boolean) => { this.setState({ disabledEvents: type, }); }; editor.eventBus.on('designer.builtinSimulator.disabledEvents', onEnableEvents); this.dispose = () => { editor.removeListener('designer.builtinSimulator.disabledEvents', onEnableEvents); }; } componentWillUnmount() { this.dispose?.(); } render() { const sim = this.props.host; const { disabledEvents } = this.state; const { viewport, designer } = sim; const frameStyle: any = { transform: `scale(${viewport.scale})`, height: viewport.contentHeight, width: viewport.contentWidth, }; if (disabledEvents) { frameStyle.pointerEvents = 'none'; } const { viewName } = designer; return (