├── .changeset ├── README.md └── config.json ├── .editorconfig ├── .github └── workflows │ ├── alpine-nginx-slim.yml │ ├── alpine-node.yml │ ├── dependency-review.yml │ ├── release.yml │ ├── snapshot-release.yml │ └── test-and-lint.yml ├── .gitignore ├── .yarn └── releases │ └── yarn-4.5.3.cjs ├── .yarnrc.yml ├── CONTRIBUTING.MD ├── README.md ├── commitlint.config.js ├── package.json ├── packages ├── alpine-node-nginx │ ├── CHANGELOG.md │ ├── Dockerfile │ ├── Dockerfile-nginx-slim │ ├── Dockerfile-slim │ ├── README.md │ ├── gpg │ │ ├── kandaurov.key │ │ ├── maxim.key │ │ ├── mdounin.key │ │ ├── sb.key │ │ └── thresh.key │ ├── nginx.conf │ └── package.json ├── arui-scripts-modules │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── global-definitions.d.ts │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── module-loader │ │ │ ├── __tests__ │ │ │ ├── create-module-fetcher.tests.ts │ │ │ ├── create-module-loader.tests.ts │ │ │ ├── create-server-state-module-fetcher.tests.ts │ │ │ └── execute-module-factory.tests.ts │ │ │ ├── create-module-fetcher.ts │ │ │ ├── create-module-loader.ts │ │ │ ├── create-server-state-module-fetcher.ts │ │ │ ├── execute-module-factory.ts │ │ │ ├── get-server-state-module-fetcher-params.ts │ │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ ├── use-module-factory.tests.ts │ │ │ │ ├── use-module-loader.tests.ts │ │ │ │ ├── use-module-mount-target.tests.ts │ │ │ │ └── use-module-mounter.tests.ts │ │ │ ├── types.ts │ │ │ ├── use-id.ts │ │ │ ├── use-module-factory.ts │ │ │ ├── use-module-loader.ts │ │ │ ├── use-module-mount-target.ts │ │ │ └── use-module-mounter.ts │ │ │ ├── index.ts │ │ │ ├── module-types.ts │ │ │ ├── types.ts │ │ │ └── utils │ │ │ ├── __tests__ │ │ │ ├── consumer-counter.tests.ts │ │ │ ├── fetch-app-manifest.tests.ts │ │ │ ├── get-module.tests.ts │ │ │ ├── normalize-url-segment.tests.ts │ │ │ └── unwrap-default-export.tests.ts │ │ │ ├── clean-global.ts │ │ │ ├── consumers-counter.ts │ │ │ ├── dom-utils.ts │ │ │ ├── fetch-app-manifest.ts │ │ │ ├── fetch-resources.ts │ │ │ ├── get-module.ts │ │ │ ├── modules-cache.ts │ │ │ ├── normalize-url-segment.ts │ │ │ └── unwrap-default-export.ts │ ├── tsconfig.eslint.json │ ├── tsconfig.esm.json │ └── tsconfig.json ├── arui-scripts-server │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ └── read-assets-manifest.tests.ts │ │ ├── express.ts │ │ ├── hapi-16.ts │ │ ├── hapi-20.ts │ │ ├── index.ts │ │ ├── modules │ │ │ ├── __tests__ │ │ │ │ └── create-get-modules-method.tests.ts │ │ │ ├── create-get-modules-method.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── read-assets-manifest.ts │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── arui-scripts │ ├── .browserslistrc │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ │ └── build.sh │ ├── docs │ │ ├── artifact.md │ │ ├── caveats.md │ │ ├── client-only.md │ │ ├── commands.md │ │ ├── compilers.md │ │ ├── examples.md │ │ ├── modules.md │ │ ├── overrides.md │ │ ├── presets.md │ │ └── settings.md │ ├── jest-preset.js │ ├── package.json │ ├── src │ │ ├── bin │ │ │ └── index.ts │ │ ├── commands │ │ │ ├── archive-build │ │ │ │ └── index.ts │ │ │ ├── build │ │ │ │ ├── build-wrapper.ts │ │ │ │ ├── client.ts │ │ │ │ ├── index.ts │ │ │ │ └── server.ts │ │ │ ├── bundle-analyze │ │ │ │ └── index.ts │ │ │ ├── changelog │ │ │ │ └── index.ts │ │ │ ├── docker-build-compiled │ │ │ │ └── index.ts │ │ │ ├── docker-build │ │ │ │ ├── .gitignore │ │ │ │ └── index.ts │ │ │ ├── ensure-yarn │ │ │ │ └── index.ts │ │ │ ├── start-prod │ │ │ │ ├── client.ts │ │ │ │ ├── index.ts │ │ │ │ └── server.ts │ │ │ ├── start │ │ │ │ ├── client.ts │ │ │ │ ├── index.ts │ │ │ │ ├── print-compiler-output.ts │ │ │ │ └── server.ts │ │ │ ├── test │ │ │ │ ├── get-config.ts │ │ │ │ └── index.ts │ │ │ └── util │ │ │ │ ├── client-assets-sizes.ts │ │ │ │ ├── docker-build.ts │ │ │ │ ├── exec.ts │ │ │ │ ├── format-webpack-messages.ts │ │ │ │ ├── make-tmp-dir.ts │ │ │ │ ├── run-client-dev-server.ts │ │ │ │ ├── run-compilers.ts │ │ │ │ ├── run-server-watch-compiler.ts │ │ │ │ ├── tsc.ts │ │ │ │ └── yarn.ts │ │ ├── configs │ │ │ ├── app-configs │ │ │ │ ├── __tests__ │ │ │ │ │ ├── update-with-env.tests.ts │ │ │ │ │ ├── update-with-package.tests.ts │ │ │ │ │ ├── update-with-presets.tests.ts │ │ │ │ │ └── validate-settings-keys.tests.ts │ │ │ │ ├── calculate-dependent-config.ts │ │ │ │ ├── get-defaults.ts │ │ │ │ ├── index.ts │ │ │ │ ├── read-config-file.ts │ │ │ │ ├── types.ts │ │ │ │ ├── update-with-config-file.ts │ │ │ │ ├── update-with-env.ts │ │ │ │ ├── update-with-package.ts │ │ │ │ ├── update-with-presets.ts │ │ │ │ ├── validate-settings-keys.ts │ │ │ │ └── warn-about-deprecations.ts │ │ │ ├── babel-client.ts │ │ │ ├── babel-dependencies.ts │ │ │ ├── babel-server.ts │ │ │ ├── client-env-config │ │ │ │ ├── add-env-to-html-template.ts │ │ │ │ ├── client-config-plugin.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── get-env-config.ts │ │ │ │ └── index.ts │ │ │ ├── config-extras │ │ │ │ └── minimizers │ │ │ │ │ ├── imagemin │ │ │ │ │ ├── imagemin.ts │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ ├── dev-server.ts │ │ │ ├── jest │ │ │ │ ├── babel-transform.js │ │ │ │ ├── css-mock.js │ │ │ │ ├── file-transform.js │ │ │ │ └── settings.js │ │ │ ├── modules.ts │ │ │ ├── mq.css │ │ │ ├── postcss.config.ts │ │ │ ├── postcss.ts │ │ │ ├── process-assets-plugin-output.ts │ │ │ ├── server-externals-exemptions.ts │ │ │ ├── stats-options.ts │ │ │ ├── supporting-browsers.ts │ │ │ ├── swc.ts │ │ │ ├── util │ │ │ │ ├── __tests__ │ │ │ │ │ ├── apply-overrides.tests.ts │ │ │ │ │ ├── find-plugin.tests.ts │ │ │ │ │ └── get-polyfills.tests.ts │ │ │ │ ├── apply-overrides.ts │ │ │ │ ├── check-node-version.ts │ │ │ │ ├── find-loader.ts │ │ │ │ ├── find-plugin.ts │ │ │ │ ├── get-entry.ts │ │ │ │ ├── get-polyfills.ts │ │ │ │ ├── get-webpack-cache-dependencies.ts │ │ │ │ ├── install-sourcemap-support.js │ │ │ │ ├── node-assets-ignore.js │ │ │ │ ├── noop.js │ │ │ │ ├── register-ts-node.ts │ │ │ │ └── resolve.ts │ │ │ ├── webpack.client.dev.ts │ │ │ ├── webpack.client.prod.ts │ │ │ ├── webpack.client.ts │ │ │ ├── webpack.server.dev.ts │ │ │ ├── webpack.server.prod.ts │ │ │ └── webpack.server.ts │ │ ├── index.ts │ │ ├── plugins │ │ │ ├── arui-runtime │ │ │ │ ├── arui-runtime-module.ts │ │ │ │ └── index.ts │ │ │ ├── postcss-global-variables │ │ │ │ ├── postcss-global-variables.ts │ │ │ │ └── utils │ │ │ │ │ ├── __tests__ │ │ │ │ │ ├── add-global-variable.tests.ts │ │ │ │ │ ├── get-media-query-name.tests.ts │ │ │ │ │ └── parse-variables.tests.ts │ │ │ │ │ └── utils.ts │ │ │ ├── postcss-prefix-selector.ts │ │ │ ├── reload-server-plugin.ts │ │ │ ├── turn-off-split-remote-entry.ts │ │ │ └── watch-missing-node-modules-plugin.ts │ │ └── templates │ │ │ ├── base-nginx.conf.template.ts │ │ │ ├── dockerfile-compiled.template.ts │ │ │ ├── dockerfile.template.ts │ │ │ ├── html.template.ts │ │ │ ├── nginx.conf.template.ts │ │ │ └── start.template.ts │ ├── stylelint.config.js │ ├── tsconfig-local.json │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── client-event-bus │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ └── implementation.test.ts │ │ ├── custom-event.ts │ │ ├── get-event-bus.ts │ │ ├── implementation.ts │ │ ├── index.ts │ │ ├── types │ │ │ ├── abstract-types.ts │ │ │ └── types.ts │ │ └── use-event-bus-value.ts │ ├── tsconfig.eslint.json │ ├── tsconfig.esm.json │ └── tsconfig.json ├── eslint-config-custom │ ├── common.js │ └── package.json ├── example-modules │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── arui-scripts.config.ts │ ├── arui-scripts.overrides.ts │ ├── global-definitions.d.ts │ ├── jest.config.validate-build.js │ ├── package.json │ ├── src │ │ ├── bootstrap.tsx │ │ ├── client.tsx │ │ ├── components │ │ │ └── app.tsx │ │ ├── modules │ │ │ ├── factory-module-compat │ │ │ │ └── index.ts │ │ │ ├── module-abstract │ │ │ │ └── index.ts │ │ │ ├── module-compat │ │ │ │ ├── compat-module.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.css │ │ │ ├── module │ │ │ │ ├── example-module.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.css │ │ │ ├── server-state-factory-module │ │ │ │ └── index.ts │ │ │ ├── server-state-module-compat │ │ │ │ ├── index.tsx │ │ │ │ └── server-state-module-compat.tsx │ │ │ └── server-state-module │ │ │ │ ├── index.tsx │ │ │ │ ├── server-state-module.module.css │ │ │ │ └── server-state-module.tsx │ │ ├── server │ │ │ ├── index.ts │ │ │ └── read-assets.ts │ │ └── shared │ │ │ └── postcss-features │ │ │ ├── index.ts │ │ │ ├── postcss-features.tsx │ │ │ ├── styles.css │ │ │ └── styles.module.css │ ├── tsconfig.eslint.json │ ├── tsconfig.json │ └── validate-build │ │ ├── __snapshots__ │ │ └── build.spec.ts.snap │ │ ├── build.spec.ts │ │ └── tsconfig.json └── example │ ├── .eslintrc.js │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ └── setup.js │ ├── arui-scripts.config.ts │ ├── arui-scripts.overrides.ts │ ├── global-definitions.d.ts │ ├── jest.config.validate-build.js │ ├── package.json │ ├── src │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── app.tests.tsx.snap │ │ ├── app.tests.tsx │ │ ├── server.tests.ts │ │ └── utils.tests.ts │ ├── client.tsx │ ├── clock.svg │ ├── components │ │ ├── app.module.css │ │ ├── app.tsx │ │ ├── client.png │ │ ├── clock.svg │ │ ├── modules-tabs.tsx │ │ ├── postcss-features │ │ │ ├── index.ts │ │ │ ├── postcss-features.tsx │ │ │ ├── styles.css │ │ │ └── styles.module.css │ │ └── style.css │ ├── module-mounters │ │ ├── abstract-module.tsx │ │ ├── compat-module-mounter.tsx │ │ ├── factory-compat-module-mounter.tsx │ │ ├── module-mounter.tsx │ │ ├── server-state-compat-module-mounter.tsx │ │ ├── server-state-factory-module-mounter.tsx │ │ └── server-state-module-mounter.tsx │ ├── polyfills.js │ ├── server │ │ ├── index.ts │ │ └── server.png │ ├── utils.ts │ └── worker.ts │ ├── tsconfig.eslint.json │ ├── tsconfig.json │ └── validate-build │ ├── __snapshots__ │ └── build.spec.ts.snap │ ├── build.spec.ts │ └── tsconfig.json ├── turbo.json └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { "repo": "core-ds/arui-scripts" } 6 | ], 7 | "commit": false, 8 | "fixed": [], 9 | "linked": [], 10 | "access": "public", 11 | "baseBranch": "master", 12 | "updateInternalDependencies": "patch", 13 | "ignore": [] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.github/workflows/alpine-nginx-slim.yml: -------------------------------------------------------------------------------- 1 | name: Build docker nginx images 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - packages/alpine-node-nginx/** 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | versions: [ 16 | { alpine: '3.20', dockerfile: Dockerfile-nginx-slim, tag: 'nginx-1.27.1-slim' }, 17 | ] 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v2 22 | 23 | - name: Build and push Docker images 24 | uses: docker/build-push-action@v1 25 | with: 26 | path: packages/alpine-node-nginx/ 27 | dockerfile: packages/alpine-node-nginx/${{ matrix.versions.dockerfile }} 28 | username: ${{ secrets.DOCKER_USERNAME }} 29 | password: ${{ secrets.DOCKER_ACCESS_TOKEN }} 30 | repository: alfabankui/arui-scripts 31 | build_args: ALPINE_VERSION=${{ matrix.versions.alpine }} 32 | tags: ${{ matrix.versions.tag }} 33 | -------------------------------------------------------------------------------- /.github/workflows/alpine-node.yml: -------------------------------------------------------------------------------- 1 | name: Build docker images 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - packages/alpine-node-nginx/** 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | versions: [ 16 | { node: '18.20.8', alpine: '3.21', image: node, dockerfile: Dockerfile, tag: '18.20.8' }, 17 | { node: '20.19.2', alpine: '3.21', image: node, dockerfile: Dockerfile, tag: '20.19.2' }, 18 | { node: '22.16.0', alpine: '3.21', image: node, dockerfile: Dockerfile, tag: '22.16.0' }, 19 | 20 | { node: '18.20.8', alpine: '3.21', image: node, dockerfile: Dockerfile-slim, tag: '18.20.8-slim' }, 21 | { node: '20.19.2', alpine: '3.21', image: node, dockerfile: Dockerfile-slim, tag: '20.19.2-slim' }, 22 | { node: '22.16.0', alpine: '3.21', image: node, dockerfile: Dockerfile-slim, tag: '22.16.0-slim' }, 23 | ] 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v2 28 | 29 | - name: Build and push Docker images 30 | uses: docker/build-push-action@v1 31 | with: 32 | path: packages/alpine-node-nginx/ 33 | dockerfile: packages/alpine-node-nginx/${{ matrix.versions.dockerfile }} 34 | username: ${{ secrets.DOCKER_USERNAME }} 35 | password: ${{ secrets.DOCKER_ACCESS_TOKEN }} 36 | repository: alfabankui/arui-scripts 37 | build_args: NODE_VERSION=${{ matrix.versions.node }},ALPINE_VERSION=${{ matrix.versions.alpine }},NODE_BASE_IMAGE=${{ matrix.versions.image }} 38 | tags: ${{ matrix.versions.tag }} 39 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: 'Dependency review' 2 | on: 3 | pull_request: 4 | paths: 5 | - packages/arui-scripts/** 6 | 7 | permissions: 8 | discussions: write 9 | contents: read 10 | pull-requests: write 11 | 12 | jobs: 13 | dependency-review: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v4 18 | - name: Use Node.js 22.x 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: '22' 22 | - name: yarn install 23 | run: | 24 | yarn install --immutable 25 | - name: Run Audit 26 | id: audit 27 | run: | 28 | yarn turbo audit 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Node.js 22.x 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 22.x 22 | 23 | - name: Install Dependencies 24 | run: yarn install --immutable 25 | 26 | - name: Create Release Pull Request or Publish to npm 27 | id: changesets 28 | uses: changesets/action@v1 29 | with: 30 | publish: yarn publish-packages 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 33 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/workflows/snapshot-release.yml: -------------------------------------------------------------------------------- 1 | name: Create snapshot release of packages 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | prefix: 7 | description: 'Custom prefix for snapshot version' 8 | required: true 9 | default: 'next' 10 | 11 | 12 | jobs: 13 | release: 14 | name: Release 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout Repo 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup Node.js 22.x 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 22.x 24 | 25 | - name: Install Dependencies 26 | run: yarn install --immutable 27 | 28 | - name: Creating .npmrc 29 | run: | 30 | cat << EOF > "$HOME/.npmrc" 31 | //registry.npmjs.org/:_authToken=$NPM_TOKEN 32 | EOF 33 | env: 34 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 35 | 36 | - name: Publish snapshot release 37 | id: release 38 | run: | 39 | yarn run turbo test build 40 | yarn run changeset version --snapshot ${{ github.event.inputs.prefix }} 41 | yarn run changeset publish --tag next --no-git-tag --snapshot 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 44 | -------------------------------------------------------------------------------- /.github/workflows/test-and-lint.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [18.x, 20.x, 22.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: yarn install and test 21 | run: | 22 | yarn install --immutable 23 | yarn run test 24 | env: 25 | CI: true 26 | 27 | lint: 28 | 29 | runs-on: ubuntu-latest 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | - name: Use Node.js 22.x 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version: '22.x' 37 | - name: yarn install and lint 38 | run: | 39 | yarn install --immutable 40 | yarn run lint 41 | env: 42 | CI: true 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules 3 | package-lock.json 4 | *.log 5 | .yarn/* 6 | !.yarn/releases 7 | !.yarn/plugins 8 | !.yarn/sdks 9 | !.yarn/versions 10 | .pnp.* 11 | tsconfig.tsbuildinfo 12 | .turbo 13 | build 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | nodeLinker: node-modules 4 | 5 | yarnPath: .yarn/releases/yarn-4.5.3.cjs 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.MD: -------------------------------------------------------------------------------- 1 | 2 | ## Выпуск релизной версии 3 | Данный проект использует [changesets](https://github.com/changesets/changesets/tree/main) для управления релизами. 4 | Каждый пул-реквест, который должен выпустить новый релиз должен содержать файл с описанием изменений в формате changesets. 5 | Для этого нужно выполнить команду `yarn changeset` и ответить на вопросы интерактивного интерфейса (выбор опций делается пробелом). 6 | Файл, который будет сгенерирован, нужно добавить в пул-реквест. 7 | 8 | :information_source: При вводе summary в команде `yarn changeset` стоит указывать с пользовательской точки зрения. 9 | Эта информация по итогу попадает в ченжлог. 10 | 11 | Примеры хорошего summary:
12 | :white_check_mark: `Исправление реализации префиксов для css правил в модулях`
13 | :white_check_mark: `Исправление обработки color-mod функций в css при использовании keepCssVars: false` 14 | 15 | Примеры некорректного summary:
16 | :x: `ignore css order to prevent conflicts` 17 | 18 | После мержа пул реквеста в мастер, будет создан пул-реквест с новым релизом. После мержа этого пул-реквеста в мастер, будет создан новый релиз в npm. 19 | 20 | ## Выпуск snapshot-версии 21 | Snapshot-версия может быть полезна для тестирования изменений перед релизом. Для ее выпуска можно воспользоваться ручным запуском 22 | github action [Create snapshot release of packages](https://github.com/core-ds/arui-scripts/actions?query=workflow%3A%22Create+snapshot+release+of+packages%22). Перед запуском github action необходимо не забыть создать changeset командой `yarn changeset` 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | arui-scripts 2 | === 3 | 4 | [Пользовательская документация](./packages/arui-scripts/README.md) 5 | 6 | ## Разработка 7 | Проект является монорепозиторием, в котором находятся следующие пакеты: 8 | 9 | - [arui-scripts](./packages/arui-scripts/) - Код конфигураций сборщиков 10 | - [alpine-node-nginx](./packages/alpine-node-nginx/) - Базовый образ docker контейнера, на котором будут основываться контейнеры проектов 11 | - [@alfalab/scripts-server](./packages/arui-scripts-server) - Утилиты для использования на сервере 12 | - [@alfalab/scripts-modules](./packages/arui-scripts-modules) - Библиотека для работы с модулями приложений 13 | - [@alfalab/client-event-bus](./packages/client-event-bus/) - Библиотека для работы c Event Bus 14 | - [example](./packages/example) - тестовый проект, на котором проводится проверка сборщиков 15 | - [example-modules](./packages/example-modules) - тестовый проект с примерами реализации модулей 16 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve('arui-presets-lint/commitlint')], 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": [ 4 | "packages/*" 5 | ], 6 | "devDependencies": { 7 | "@changesets/changelog-github": "^0.4.8", 8 | "@changesets/cli": "^2.26.2", 9 | "arui-presets-lint": "6.1.0", 10 | "commitlint": "^11.0.0", 11 | "conventional-changelog-cli": "3.0.0", 12 | "eslint": "^8.20.0", 13 | "eslint-config-custom": "workspace:*", 14 | "husky": "^4.3.8", 15 | "prettier": "^2.7.1", 16 | "stylelint": "^14.9.1", 17 | "turbo": "^1.10.12" 18 | }, 19 | "husky": { 20 | "hooks": { 21 | "pre-commit": "yarn test", 22 | "commit-msg": "yarn commitlint -E HUSKY_GIT_PARAMS" 23 | } 24 | }, 25 | "scripts": { 26 | "publish-packages": "yarn test && changeset version && changeset publish", 27 | "build": "turbo run build", 28 | "test": "turbo run build build:app test validate", 29 | "lint": "turbo run lint" 30 | }, 31 | "prettier": "arui-presets-lint/prettier", 32 | "eslintConfig": { 33 | "extends": "./node_modules/arui-presets-lint/eslint" 34 | }, 35 | "stylelint": { 36 | "extends": "arui-presets-lint/stylelint" 37 | }, 38 | "commitlint": { 39 | "extends": [ 40 | "./node_modules/arui-presets-lint/commitlint" 41 | ], 42 | "rules": { 43 | "subject-case": [ 44 | 2, 45 | "never", 46 | [ 47 | "start-case", 48 | "pascal-case", 49 | "upper-case" 50 | ] 51 | ], 52 | "header-max-length": [ 53 | 2, 54 | "always", 55 | 120 56 | ] 57 | } 58 | }, 59 | "packageManager": "yarn@4.5.3" 60 | } 61 | -------------------------------------------------------------------------------- /packages/alpine-node-nginx/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # arui-scripts-docker 2 | 3 | ## 15.1.0 4 | 5 | ### Minor Changes 6 | 7 | - [#198](https://github.com/core-ds/arui-scripts/pull/198) [`c2d6b5d`](https://github.com/core-ds/arui-scripts/commit/c2d6b5d6a54363f32b2e4f80863e6bd477c42c80) Thanks [@heymdall-legal](https://github.com/heymdall-legal)! - Добавлен новый вид базовых образов - slim версии уже существовавших. 8 | В slim версиях отсутствуют многочисленные модули nginx, которые обычно не нужны в проектах. 9 | -------------------------------------------------------------------------------- /packages/alpine-node-nginx/gpg/mdounin.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v1.4.11 (FreeBSD) 3 | 4 | mQENBE7SKu8BCADQo6x4ZQfAcPlJMLmL8zBEBUS6GyKMMMDtrTh3Yaq481HB54oR 5 | 0cpKL05Ff9upjrIzLD5TJUCzYYM9GQOhguDUP8+ZU9JpSz3yO2TvH7WBbUZ8FADf 6 | hblmmUBLNgOWgLo3W+FYhl3mz1GFS2Fvid6Tfn02L8CBAj7jxbjL1Qj/OA/WmLLc 7 | m6BMTqI7IBlYW2vyIOIHasISGiAwZfp0ucMeXXvTtt14LGa8qXVcFnJTdwbf03AS 8 | ljhYrQnKnpl3VpDAoQt8C68YCwjaNJW59hKqWB+XeIJ9CW98+EOAxLAFszSyGanp 9 | rCqPd0numj9TIddjcRkTA/ZbmCWK+xjpVBGXABEBAAG0IU1heGltIERvdW5pbiA8 10 | bWRvdW5pbkBtZG91bmluLnJ1PokBOAQTAQIAIgUCTtIq7wIbAwYLCQgHAwIGFQgC 11 | CQoLBBYCAwECHgECF4AACgkQUgqZk6HAUvj+iwf/b4FS6zVzJ5T0v1vcQGD4ZzXe 12 | D5xMC4BJW414wVMU15rfX7aCdtoCYBNiApPxEd7SwiyxWRhRA9bikUq87JEgmnyV 13 | 0iYbHZvCvc1jOkx4WR7E45t1Mi29KBoPaFXA9X5adZkYcOQLDxa2Z8m6LGXnlF6N 14 | tJkxQ8APrjZsdrbDvo3HxU9muPcq49ydzhgwfLwpUs11LYkwB0An9WRPuv3jporZ 15 | /XgI6RfPMZ5NIx+FRRCjn6DnfHboY9rNF6NzrOReJRBhXCi6I+KkHHEnMoyg8XET 16 | 9lVkfHTOl81aIZqrAloX3/00TkYWyM2zO9oYpOg6eUFCX/Lw4MJZsTcT5EKVxIhG 17 | BBARAgAGBQJO01Y/AAoJEOzw6QssFyCDVyQAn3qwTZlcZgyyzWu9Cs8gJ0CXREaS 18 | AJ92QjGLT9DijTcbB+q9OS/nl16Z/IhGBBARAgAGBQJO02JDAAoJEKk3YTmlJMU+ 19 | P64AnjCKEXFelSVMtgefJk3+vpyt3QX1AKCH9M3MbTWPeDUL+MpULlfdyfvjj7kB 20 | DQRO0irvAQgA0LjCc8S6oZzjiap2MjRNhRFA5BYjXZRZBdKF2VP74avt2/RELq8G 21 | W0n7JWmKn6vvrXabEGLyfkCngAhTq9tJ/K7LPx/bmlO5+jboO/1inH2BTtLiHjAX 22 | vicXZk3oaZt2Sotx5mMI3yzpFQRVqZXsi0LpUTPJEh3oS8IdYRjslQh1A7P5hfCZ 23 | wtzwb/hKm8upODe/ITUMuXeWfLuQj/uEU6wMzmfMHb+jlYMWtb+v98aJa2FODeKP 24 | mWCXLa7bliXp1SSeBOEfIgEAmjM6QGlDx5sZhr2Ss2xSPRdZ8DqD7oiRVzmstX1Y 25 | oxEzC0yXfaefC7SgM0nMnaTvYEOYJ9CH3wARAQABiQEfBBgBAgAJBQJO0irvAhsM 26 | AAoJEFIKmZOhwFL4844H/jo8icCcS6eOWvnen7lg0FcCo1fIm4wW3tEmkQdchSHE 27 | CJDq7pgTloN65pwB5tBoT47cyYNZA9eTfJVgRc74q5cexKOYrMC3KuAqWbwqXhkV 28 | s0nkWxnOIidTHSXvBZfDFA4Idwte94Thrzf8Pn8UESudTiqrWoCBXk2UyVsl03gJ 29 | blSJAeJGYPPeo+Yj6m63OWe2+/S2VTgmbPS/RObn0Aeg7yuff0n5+ytEt2KL51gO 30 | QE2uIxTCawHr12PsllPkbqPk/PagIttfEJqn9b0CrqPC3HREePb2aMJ/Ctw/76CO 31 | wn0mtXeIXLCTvBmznXfaMKllsqbsy2nCJ2P2uJjOntw= 32 | =Tavt 33 | -----END PGP PUBLIC KEY BLOCK----- 34 | -------------------------------------------------------------------------------- /packages/alpine-node-nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes auto; 2 | worker_rlimit_nofile 20000; 3 | daemon off; 4 | 5 | error_log /var/log/nginx/error.log; 6 | pid /var/run/nginx.pid; 7 | 8 | events { 9 | worker_connections 19000; 10 | use epoll; 11 | } 12 | 13 | http { 14 | include /etc/nginx/mime.types; 15 | default_type application/octet-stream; 16 | 17 | server_tokens off; 18 | access_log off; 19 | keepalive_timeout 65; 20 | proxy_read_timeout 200; 21 | sendfile on; 22 | tcp_nopush on; 23 | tcp_nodelay on; 24 | 25 | gzip_static on; 26 | brotli on; 27 | brotli_static on; 28 | brotli_types application/atom+xml application/javascript application/json application/rss+xml 29 | application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype 30 | application/x-font-ttf application/x-javascript application/xhtml+xml application/xml 31 | font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon 32 | image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml application/wasm; 33 | 34 | 35 | # Only retry if there was a communication error, not a timeout 36 | # on the Node server (to avoid propagating "queries of death" 37 | # to all frontends) 38 | proxy_next_upstream error; 39 | 40 | #cache 41 | proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=all:32m max_size=1g; 42 | 43 | include /etc/nginx/conf.d/*.conf; 44 | } 45 | -------------------------------------------------------------------------------- /packages/alpine-node-nginx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arui-scripts-docker", 3 | "version": "15.1.0", 4 | "description": "Base docker image for arui-scripts", 5 | "private": true 6 | } 7 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | .turbo -------------------------------------------------------------------------------- /packages/arui-scripts-modules/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['custom/common'], 4 | parserOptions: { 5 | tsconfigRootDir: __dirname, 6 | project: [ 7 | './tsconfig.eslint.json', 8 | ], 9 | }, 10 | overrides: [ 11 | { 12 | files: ['**/__tests__/**/*.{ts,tsx}'], 13 | rules: { 14 | 'import/no-extraneous-dependencies': 'off', 15 | }, 16 | } 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | build/tsconfig.tsbuildinfo 3 | __tests__ 4 | .turbo 5 | coverage 6 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/global-definitions.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention,no-underscore-dangle */ 2 | declare const __webpack_share_scopes__: { 3 | default: unknown; 4 | [customScope: string]: unknown; 5 | }; 6 | /* eslint-enable @typescript-eslint/naming-convention,no-underscore-dangle */ 7 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'jsdom', 5 | testPathIgnorePatterns: [ 6 | '/node_modules/', 7 | '/build/', 8 | ], 9 | collectCoverageFrom: [ 10 | 'src/**/*.{ts,tsx}', 11 | '!src/**/*.d.ts', 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alfalab/scripts-modules", 3 | "version": "1.7.1", 4 | "main": "./build/index.js", 5 | "module": "./build/esm/index.js", 6 | "typings": "./build/index.d.ts", 7 | "license": "MPL-2.0", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/core-ds/arui-scripts.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/core-ds/arui-scripts/issues" 14 | }, 15 | "homepage": "https://github.com/core-ds/arui-scripts/tree/master/packages/arui-scripts-modules#readme", 16 | "scripts": { 17 | "build:commonjs": "tsc --project tsconfig.json", 18 | "build:esm": "tsc --project tsconfig.esm.json", 19 | "build": "yarn build:commonjs && yarn build:esm", 20 | "test": "jest", 21 | "lint:scripts": "eslint \"**/*.{js,jsx,ts,tsx}\" --ext .js,.jsx,.ts,.tsx", 22 | "lint": "yarn lint:scripts", 23 | "lint:fix": "yarn lint:scripts --fix", 24 | "format": "prettier --write $INIT_CWD/{config,src}/**/*.{ts,tsx,js,jsx,css}", 25 | "audit": "yarn npm audit --severity high --environment production" 26 | }, 27 | "peerDependencies": { 28 | "react": ">16.18.0" 29 | }, 30 | "devDependencies": { 31 | "@testing-library/react-hooks": "^8.0.1", 32 | "@types/react": "17.0.64", 33 | "@types/uuid": "^9.0.5", 34 | "eslint": "^8.20.0", 35 | "eslint-config-custom": "workspace:*", 36 | "jest": "28.1.3", 37 | "jest-environment-jsdom": "^29.6.2", 38 | "prettier": "^2.7.1", 39 | "react": "17.0.2", 40 | "react-dom": "17.0.2", 41 | "ts-jest": "28.0.8", 42 | "typescript": "4.9.5" 43 | }, 44 | "dependencies": { 45 | "abortcontroller-polyfill": "^1.7.5", 46 | "uuid": "^9.0.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './module-loader'; 2 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/create-server-state-module-fetcher.ts: -------------------------------------------------------------------------------- 1 | import { urlSegmentWithoutEndSlash } from './utils/normalize-url-segment'; 2 | import { ModuleResourcesGetter } from './create-module-loader'; 3 | import { getServerStateModuleFetcherParams } from './get-server-state-module-fetcher-params'; 4 | import { BaseModuleState } from './types'; 5 | 6 | type CreateServerResourcesFetcherParams = { 7 | baseUrl: string; 8 | headers?: Record; 9 | }; 10 | 11 | /** 12 | * Функция, которая создает метод для получения ресурсов модуля с серверным состоянием 13 | * @param baseUrl 14 | * @param headers 15 | */ 16 | export function createServerStateModuleFetcher({ 17 | baseUrl, 18 | headers = {}, 19 | }: CreateServerResourcesFetcherParams): ModuleResourcesGetter { 20 | return async function fetchServerResources(params) { 21 | const { relativePath, method } = getServerStateModuleFetcherParams(); 22 | const url = `${urlSegmentWithoutEndSlash(baseUrl)}${relativePath}`; 23 | 24 | return new Promise((resolve, reject) => { 25 | const xhr = new XMLHttpRequest(); 26 | 27 | xhr.open(method, url, true); 28 | xhr.setRequestHeader('Content-Type', 'application/json'); 29 | Object.keys(headers).forEach((headerName) => { 30 | xhr.setRequestHeader(headerName, headers[headerName]); 31 | }); 32 | xhr.onload = () => { 33 | if (xhr.status === 200) { 34 | resolve(JSON.parse(xhr.responseText)); 35 | } else { 36 | reject(new Error(xhr.statusText)); 37 | } 38 | }; 39 | xhr.onerror = () => reject(new Error(xhr.statusText)); 40 | xhr.send(JSON.stringify(params)); 41 | }); 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/execute-module-factory.ts: -------------------------------------------------------------------------------- 1 | import { unwrapDefaultExport } from './utils/unwrap-default-export'; 2 | import type { FactoryModule } from './module-types'; 3 | import type { BaseModuleState } from './types'; 4 | 5 | export async function executeModuleFactory( 6 | module: FactoryModule, 7 | serverState: ServerState, 8 | runParams?: RunParams, 9 | ): Promise { 10 | /** 11 | * Делаем 3 возможных варианта доставки фабрики: 12 | * Для compat модулей фабрику можно записать прямо в window 13 | * Для compat и для mf модулей делаем также возможным записи в поля factory и default 14 | */ 15 | const unwrappedModule = unwrapDefaultExport(module); 16 | 17 | if (typeof unwrappedModule === 'function') { 18 | return unwrappedModule(runParams as RunParams, serverState); 19 | } 20 | 21 | if ( 22 | unwrappedModule.factory && 23 | typeof unwrappedModule.factory === 'function' 24 | ) { 25 | return unwrappedModule.factory( 26 | runParams as RunParams, 27 | serverState, 28 | ); 29 | } 30 | 31 | /** 32 | * При использовании в модуле createMountableFunction из '@corp-front/module-loader' (для сохранения обратной совместимости) 33 | * функция пишется в поле mount 34 | */ 35 | if ( 36 | unwrappedModule.mount && 37 | typeof unwrappedModule.mount === 'function' 38 | ) { 39 | return unwrappedModule.mount( 40 | runParams as RunParams, 41 | serverState, 42 | ); 43 | } 44 | 45 | throw new Error( 46 | `Module ${serverState.hostAppId} does not present a factory function, 47 | try using another hook, e.g. 'useModuleLoader' or 'useModuleMounter'`, 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/get-server-state-module-fetcher-params.ts: -------------------------------------------------------------------------------- 1 | export function getServerStateModuleFetcherParams() { 2 | return { 3 | relativePath: '/api/getModuleResources', 4 | method: 'POST', 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/hooks/types.ts: -------------------------------------------------------------------------------- 1 | export type LoadingState = 'unknown' | 'pending' | 'fulfilled' | 'rejected'; 2 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/hooks/use-id.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { v4 } from 'uuid'; 3 | 4 | export const useId = (React as any).useId || function useUuid() { 5 | /* 6 | * Utilize useState instead of useMemo because React 7 | * makes no guarantees that the memo store is durable 8 | */ 9 | const id = React.useState(() => v4())[0]; 10 | 11 | return id; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/hooks/use-module-mount-target.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | 3 | import { useId } from './use-id'; 4 | 5 | type UseModuleMountTargetParams = { 6 | useShadowDom?: boolean; 7 | createTargetNode?: () => HTMLElement; 8 | }; 9 | 10 | const MOUNT_ID_ATTRIBUTE = 'data-module-mount-id'; 11 | 12 | export function useModuleMountTarget({ 13 | useShadowDom, 14 | createTargetNode, 15 | }: UseModuleMountTargetParams) { 16 | const [mountTargetNode, setMountTargetNode] = useState(); 17 | const currentMountId = useId(); 18 | // Мы не можем использовать useRef тут, useRef не будет тригерить ререндер, так как он не меняет ничего 19 | // внутри хуков. https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node 20 | const afterTargetMountCallback = useCallback((node: HTMLElement | null) => { 21 | if (!node) { 22 | return; 23 | } 24 | 25 | node.setAttribute(MOUNT_ID_ATTRIBUTE, currentMountId); 26 | 27 | // мы не можем маунтить реакт-приложение в элемент, созданный другим реакт-приложением, поэтому создаем элемент, лежащий 28 | // прямо внутри того, что пришло через ref 29 | const realTarget = createTargetNode ? createTargetNode() : document.createElement('div'); 30 | 31 | if (useShadowDom && node.attachShadow) { 32 | const shadowRoot = node.attachShadow({ 33 | mode: 'open', 34 | delegatesFocus: true, 35 | }); 36 | 37 | const contentTarget = document.createElement('div'); 38 | 39 | shadowRoot.appendChild(contentTarget); 40 | contentTarget.appendChild(realTarget); 41 | setMountTargetNode(realTarget); 42 | } else { 43 | node.appendChild(realTarget); 44 | setMountTargetNode(realTarget); 45 | } 46 | }, [createTargetNode, useShadowDom, currentMountId]); 47 | 48 | return { 49 | afterTargetMountCallback, 50 | mountTargetNode, 51 | cssTargetSelector: useShadowDom ? `[${MOUNT_ID_ATTRIBUTE}="${currentMountId}"]` : 'head', 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/index.ts: -------------------------------------------------------------------------------- 1 | import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'; 2 | 3 | export * from './types'; 4 | export * from './module-types'; 5 | 6 | export { createModuleLoader } from './create-module-loader'; 7 | export { createModuleFetcher } from './create-module-fetcher'; 8 | export { createServerStateModuleFetcher } from './create-server-state-module-fetcher'; 9 | export { getServerStateModuleFetcherParams } from './get-server-state-module-fetcher-params'; 10 | export { executeModuleFactory } from './execute-module-factory'; 11 | 12 | export { useModuleLoader } from './hooks/use-module-loader'; 13 | export { useModuleMounter } from './hooks/use-module-mounter'; 14 | export { useModuleFactory } from './hooks/use-module-factory'; 15 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/__tests__/consumer-counter.tests.ts: -------------------------------------------------------------------------------- 1 | import { getConsumerCounter } from '../consumers-counter'; 2 | 3 | describe('ConsumersCounter', () => { 4 | it('getCounter should return 0 if module never get increased', () => { 5 | const counter = getConsumerCounter(); 6 | 7 | expect(counter.getCounter('test')).toBe(0); 8 | }); 9 | 10 | it('getCounter should return consume counter if module get increased', () => { 11 | const counter = getConsumerCounter(); 12 | 13 | counter.increase('test'); 14 | expect(counter.getCounter('test')).toBe(1); 15 | }); 16 | 17 | it('getCounter should return 0 if module get increased and decreased', () => { 18 | const counter = getConsumerCounter(); 19 | 20 | counter.increase('test'); 21 | counter.decrease('test'); 22 | expect(counter.getCounter('test')).toBe(0); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/__tests__/fetch-app-manifest.tests.ts: -------------------------------------------------------------------------------- 1 | import { fetchAppManifest } from '../fetch-app-manifest'; 2 | 3 | describe('fetchAppManifest', () => { 4 | let xhrMock = { 5 | open: jest.fn(), 6 | send: jest.fn(), 7 | setRequestHeader: jest.fn(), 8 | onload: jest.fn(), 9 | onerror: jest.fn(), 10 | status: 200, 11 | responseText: '', 12 | statusText: '', 13 | }; 14 | 15 | beforeEach(() => { 16 | window.XMLHttpRequest = jest.fn(() => xhrMock) as any; 17 | xhrMock = { 18 | open: jest.fn(), 19 | send: jest.fn(), 20 | setRequestHeader: jest.fn(), 21 | onload: jest.fn(), 22 | onerror: jest.fn(), 23 | status: 200, 24 | responseText: '', 25 | statusText: '', 26 | }; 27 | }); 28 | 29 | it('should return parsed manifest', async () => { 30 | xhrMock.responseText = JSON.stringify('Hello World!'); 31 | const manifestPromise = fetchAppManifest('http://test/manifest.json'); 32 | 33 | xhrMock.onload?.({} as any); 34 | 35 | await expect(manifestPromise).resolves.toEqual('Hello World!'); 36 | }); 37 | 38 | it('should reject promise if status is not 200', async () => { 39 | xhrMock.status = 404; 40 | xhrMock.statusText = 'Not Found'; 41 | const manifestPromise = fetchAppManifest('http://test/manifest.json'); 42 | 43 | xhrMock.onload(); 44 | 45 | await expect(manifestPromise).rejects.toEqual(new Error('Not Found')); 46 | }); 47 | 48 | it('should reject promise if request was errored', async () => { 49 | const manifestPromise = fetchAppManifest('http://test/manifest.json'); 50 | 51 | xhrMock.onerror(); 52 | 53 | await expect(manifestPromise).rejects.toEqual(new Error('')); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/__tests__/normalize-url-segment.tests.ts: -------------------------------------------------------------------------------- 1 | import { normalizeUrlSegment, urlSegmentWithoutEndSlash } from '../normalize-url-segment'; 2 | 3 | describe('normalizeUrlSegment', () => { 4 | it('should return slash for empty string', () => { 5 | expect(normalizeUrlSegment('')).toBe('/'); 6 | }); 7 | it('should return slash for falsy values', () => { 8 | expect(normalizeUrlSegment('')).toBe('/'); 9 | }); 10 | 11 | it('should append trailing slash if it is not present', () => { 12 | expect(normalizeUrlSegment('/test')).toBe('/test/'); 13 | }); 14 | 15 | it('should not append trailing slash if it is present', () => { 16 | expect(normalizeUrlSegment('/test/')).toBe('/test/'); 17 | }); 18 | 19 | it('should append leading slash if it is not present', () => { 20 | expect(normalizeUrlSegment('test/')).toBe('/test/'); 21 | }); 22 | 23 | it('should not append leading slash if it is present', () => { 24 | expect(normalizeUrlSegment('/test/')).toBe('/test/'); 25 | }); 26 | 27 | it('should not append leading slash if url is absolute', () => { 28 | expect(normalizeUrlSegment('http://test/')).toBe('http://test/'); 29 | }); 30 | }); 31 | 32 | describe('urlSegmentWithoutEndSlash', () => { 33 | it('should return normalized url without trailing slash', () => { 34 | expect(urlSegmentWithoutEndSlash('/test/')).toBe('/test'); 35 | expect(urlSegmentWithoutEndSlash('/test')).toBe('/test'); 36 | expect(urlSegmentWithoutEndSlash('test/')).toBe('/test'); 37 | expect(urlSegmentWithoutEndSlash('test')).toBe('/test'); 38 | 39 | expect(urlSegmentWithoutEndSlash('http://test/')).toBe('http://test'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/__tests__/unwrap-default-export.tests.ts: -------------------------------------------------------------------------------- 1 | import { unwrapDefaultExport } from '../unwrap-default-export'; 2 | 3 | describe('unwrapDefaultExport', () => { 4 | it('should return the default export if it exists', () => { 5 | const module = { 6 | default: 'default export', 7 | }; 8 | 9 | const result = unwrapDefaultExport(module); 10 | 11 | expect(result).toBe('default export'); 12 | }); 13 | 14 | it('should return the module export if default export does not exist', () => { 15 | const module = { 16 | otherExport: 'other export', 17 | }; 18 | 19 | const result = unwrapDefaultExport(module); 20 | 21 | expect(result).toBe(module); 22 | }); 23 | 24 | it('should return the module export if default export is null', () => { 25 | const module = { 26 | default: null, 27 | }; 28 | 29 | const result = unwrapDefaultExport(module); 30 | 31 | expect(result).toBe(module); 32 | }); 33 | 34 | it('should return the module export if default export is undefined', () => { 35 | const module = { 36 | default: undefined, 37 | }; 38 | 39 | const result = unwrapDefaultExport(module); 40 | 41 | expect(result).toBe(module); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/clean-global.ts: -------------------------------------------------------------------------------- 1 | export function cleanGlobal(globalVariableName: string) { 2 | if ((window as any)[globalVariableName]) { 3 | delete (window as any)[globalVariableName]; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/consumers-counter.ts: -------------------------------------------------------------------------------- 1 | export function getConsumerCounter() { 2 | const counters: Record = {}; 3 | 4 | function ensureCounter(moduleId: string) { 5 | if (!Object.prototype.hasOwnProperty.call(counters, moduleId)) { 6 | counters[moduleId] = 0; 7 | } 8 | } 9 | 10 | return { 11 | increase(moduleId: string) { 12 | ensureCounter(moduleId); 13 | counters[moduleId] += 1; 14 | }, 15 | decrease(moduleId: string) { 16 | ensureCounter(moduleId); 17 | if (counters[moduleId] > 0) { 18 | counters[moduleId] -= 1; 19 | } 20 | }, 21 | getCounter(moduleId: string) { 22 | ensureCounter(moduleId); 23 | 24 | return counters[moduleId]; 25 | } 26 | }; 27 | } 28 | 29 | export type ConsumersCounter = ReturnType; 30 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/fetch-app-manifest.ts: -------------------------------------------------------------------------------- 1 | import { AruiAppManifest } from '../types'; 2 | 3 | export function fetchAppManifest(url: string) { 4 | return new Promise((resolve, reject) => { 5 | const xhr = new XMLHttpRequest(); 6 | 7 | xhr.open('GET', url, true); 8 | xhr.setRequestHeader('Content-Type', 'application/json'); 9 | xhr.setRequestHeader('Cache-Control', 'no-cache'); 10 | xhr.onload = () => { 11 | if (xhr.status === 200) { 12 | resolve(JSON.parse(xhr.responseText)); 13 | } else { 14 | reject(new Error(xhr.statusText)); 15 | } 16 | }; 17 | xhr.onerror = () => reject(new Error(xhr.statusText)); 18 | xhr.send(); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/get-module.ts: -------------------------------------------------------------------------------- 1 | import { ModuleFederationContainer } from '../types'; 2 | 3 | /** 4 | * Метод для получения контента уже загруженного модуля 5 | * @param containerId 6 | * @param moduleId 7 | * @param [shareScope='default'] 8 | */ 9 | export async function getModule( 10 | containerId: string, 11 | moduleId: string, 12 | shareScope = 'default', 13 | ) { 14 | // module federation работает таким образом: 15 | // 1. Инициализация shared скоупа. Фактически загружает в него все известные приложению на данный момент модули (и из себя, и из других remote, если есть) 16 | // 2. вебпак пишет нужный "контейнер" в window. Под контейнером понимается совокупность модулей от какого то приложения 17 | // 3. мы инициализируем контейнер. Он может записать шареные модули в общий скоуп 18 | // 4. мы получаем из контейнера тот модуль, который нас интересовал. Собственно в нашем случае в контейнере будет только один модуль 19 | // 5. "запускаем" модуль. Он вернет нам то, что заэкспорчено из файла, который предоставляет module federation 20 | await __webpack_init_sharing__(shareScope); 21 | const container = (window as unknown as Record)[containerId]; 22 | 23 | if (!container || !container.init) { 24 | throw new Error( 25 | `Cannot load external remote: ${containerId}, unable to locate module federation init function`, 26 | ); 27 | } 28 | 29 | // webpack любит двойные подчеркивания для внутренних функций 30 | // eslint-disable-next-line @typescript-eslint/naming-convention 31 | await container.init(__webpack_share_scopes__[shareScope]); 32 | const factory = await container.get(moduleId); 33 | 34 | if (!factory) { 35 | throw new Error( 36 | `Cannot load external remote: ${moduleId}, unable to locate module inside a container`, 37 | ); 38 | } 39 | 40 | return factory(); 41 | } 42 | 43 | /** 44 | * Метод для получения контента уже загруженного compat модуля 45 | */ 46 | export function getCompatModule(moduleId: string) { 47 | const module = (window as unknown as Record)[moduleId]; 48 | 49 | if (!module) { 50 | throw new Error( 51 | `Cannot load compat module: ${moduleId}, unable to locate module in window`, 52 | ); 53 | } 54 | 55 | return module; 56 | } 57 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/modules-cache.ts: -------------------------------------------------------------------------------- 1 | import { ModuleResources } from '../types'; 2 | 3 | const modulesCache: Record> = {}; 4 | 5 | export function getModulesCache() { 6 | return modulesCache; 7 | } 8 | 9 | const modulesCleanupMethods: Record void> = {}; 10 | 11 | export function cleanupModule(moduleId: string) { 12 | if (modulesCache[moduleId]) { 13 | delete modulesCache[moduleId]; 14 | } 15 | 16 | if (modulesCleanupMethods[moduleId]) { 17 | modulesCleanupMethods[moduleId](); 18 | delete modulesCleanupMethods[moduleId]; 19 | } 20 | } 21 | 22 | export function cleanupModulesCache() { 23 | Object.keys(modulesCache).forEach((key) => { 24 | delete modulesCache[key]; 25 | }); 26 | 27 | Object.keys(modulesCleanupMethods).forEach((key) => { 28 | modulesCleanupMethods[key](); 29 | delete modulesCleanupMethods[key]; 30 | }); 31 | } 32 | 33 | export function addCleanupMethod(moduleId: string, cleanupFn: () => void) { 34 | modulesCleanupMethods[moduleId] = cleanupFn; 35 | } 36 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/normalize-url-segment.ts: -------------------------------------------------------------------------------- 1 | export function normalizeUrlSegment(segment: string) { 2 | if (!segment) { 3 | return '/'; 4 | } 5 | 6 | let innerSegment = segment; 7 | 8 | if (segment[segment.length - 1] !== '/') { 9 | innerSegment = `${innerSegment}/`; 10 | } 11 | 12 | if (innerSegment.indexOf('http') === 0) { 13 | return innerSegment; 14 | } 15 | 16 | if (innerSegment[0] !== '/') { 17 | innerSegment = `/${innerSegment}`; 18 | } 19 | 20 | return innerSegment; 21 | } 22 | 23 | export function urlSegmentWithoutEndSlash(segment: string) { 24 | const normalized = normalizeUrlSegment(segment); 25 | 26 | return normalized.substring(0, normalized.length - 1); 27 | } 28 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/src/module-loader/utils/unwrap-default-export.ts: -------------------------------------------------------------------------------- 1 | export function unwrapDefaultExport(module: ModuleExportType): ModuleExportType { 2 | return (module as any).default ?? module; 3 | } 4 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | // для корректной работы @typescript-eslint/parser 4 | "include": ["src", "./*.js", "./*.ts", ".eslintrc.js"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "outDir": "build/esm", 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/arui-scripts-modules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../arui-scripts/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2016", 5 | "module": "commonjs", 6 | "skipLibCheck": true, 7 | "outDir": "./build", 8 | "declaration": true 9 | }, 10 | "include": [ 11 | "src/**/*.ts", 12 | "global-definitions.d.ts" 13 | ], 14 | "exclude": ["build"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | .turbo -------------------------------------------------------------------------------- /packages/arui-scripts-server/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['custom/common'], 4 | parserOptions: { 5 | tsconfigRootDir: __dirname, 6 | project: [ 7 | './tsconfig.eslint.json' 8 | ], 9 | }, 10 | }; -------------------------------------------------------------------------------- /packages/arui-scripts-server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | build/tsconfig.tsbuildinfo 3 | __tests__ 4 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @alfalab/scripts-server 2 | 3 | ## 1.3.0 4 | 5 | ### Minor Changes 6 | 7 | - [#244](https://github.com/core-ds/arui-scripts/pull/244) [`2b7664b`](https://github.com/core-ds/arui-scripts/commit/2b7664bea1f8cb247f8248b3793b33961d4d9cf2) Thanks [@e-ogri](https://github.com/e-ogri)! - Добавление дженерика для функции createGetModulesMethod 8 | 9 | ## 1.2.3 10 | 11 | ### Patch Changes 12 | 13 | - [#215](https://github.com/core-ds/arui-scripts/pull/215) [`bfb00ce`](https://github.com/core-ds/arui-scripts/commit/bfb00cec20095d43046669c9bf2ef189d1a5b1e8) Thanks [@Haskiro](https://github.com/Haskiro)! - Исправление обработки массива assets из manifest в функции readAssetsManifest 14 | 15 | ## 1.2.2 16 | 17 | ### Patch Changes 18 | 19 | - [#200](https://github.com/core-ds/arui-scripts/pull/200) [`607bd73`](https://github.com/core-ds/arui-scripts/commit/607bd73504e8ecb72b30c60b9f18784a45dedd98) Thanks [@dependabot](https://github.com/apps/dependabot)! - bump express from 4.18.2 to 4.19.2 20 | 21 | ## 1.2.1 22 | 23 | ### Patch Changes 24 | 25 | - [#98](https://github.com/core-ds/arui-scripts/pull/98) [`ed40587`](https://github.com/core-ds/arui-scripts/commit/ed4058763981be72124be3f29269563df748b627) Thanks [@heymdall-legal](https://github.com/heymdall-legal)! - Удалены неиспользуемые типы. 26 | 27 | ## 1.2.0 28 | 29 | ### Minor Changes 30 | 31 | - [#67](https://github.com/core-ds/arui-scripts/pull/67) [`5f0b9bb`](https://github.com/core-ds/arui-scripts/commit/5f0b9bbb2ed995a8888492b389a5ad340e783d0a) Thanks [@chermashentsau](https://github.com/chermashentsau)! - Add factory modules support 32 | 33 | ## 1.1.0 34 | 35 | ### Minor Changes 36 | 37 | - [#56](https://github.com/core-ds/arui-scripts/pull/56) [`1c64198`](https://github.com/core-ds/arui-scripts/commit/1c641989791c4ff1e7a20d05c115f8a1d7817e30) Thanks [@heymdall-legal](https://github.com/heymdall-legal)! - Add modules support and various tools to work with them correctly. 38 | 39 | Modules provides a way to define reusable piece of code to consume from different applications. 40 | Additional tooling provides a convenient way to consume this modules in applications. 41 | Full documentation is available in [separate doc](https://github.com/core-ds/arui-scripts/blob/master/packages/arui-scripts/docs/modules.md). 42 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | testPathIgnorePatterns: [ 6 | '/node_modules/', 7 | '/build/', 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alfalab/scripts-server", 3 | "version": "1.3.0", 4 | "main": "./build/index.js", 5 | "typings": "./build/index.d.ts", 6 | "license": "MPL-2.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/core-ds/arui-scripts.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/core-ds/arui-scripts/issues" 13 | }, 14 | "homepage": "https://github.com/core-ds/arui-scripts/tree/master/packages/arui-scripts-server#readme", 15 | "scripts": { 16 | "build": "tsc --project tsconfig.json", 17 | "test": "jest", 18 | "lint:scripts": "eslint \"**/*.{js,jsx,ts,tsx}\" --ext .js,.jsx,.ts,.tsx", 19 | "lint": "yarn lint:scripts", 20 | "lint:fix": "yarn lint:scripts --fix", 21 | "format": "prettier --write $INIT_CWD/{config,src}/**/*.{ts,tsx,js,jsx,css}", 22 | "audit": "yarn npm audit --severity high --environment production" 23 | }, 24 | "devDependencies": { 25 | "@alfalab/scripts-modules": "workspace:*", 26 | "@types/express": "^4.17.17", 27 | "@types/hapi16": "npm:@types/hapi@16.1.12", 28 | "@types/hapi20": "npm:@types/hapi__hapi@20", 29 | "eslint": "^8.20.0", 30 | "eslint-config-custom": "workspace:*", 31 | "express": "^4.20.0", 32 | "jest": "28.1.3", 33 | "prettier": "^2.7.1", 34 | "ts-jest": "28.0.8", 35 | "typescript": "4.9.5" 36 | }, 37 | "peerDependencies": { 38 | "@hapi/hapi": ">20.0.0", 39 | "express": "^4.0.0", 40 | "hapi": "^16.0.0" 41 | }, 42 | "peerDependenciesMeta": { 43 | "@hapi/hapi": { 44 | "optional": true 45 | }, 46 | "express": { 47 | "optional": true 48 | }, 49 | "hapi": { 50 | "optional": true 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/src/__tests__/read-assets-manifest.tests.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs'; 2 | 3 | import { readAssetsManifest } from '../read-assets-manifest'; 4 | 5 | jest.mock('fs', () => ({ 6 | readFile: jest.fn(), 7 | })); 8 | 9 | describe('readAssetsManifest', () => { 10 | it('should throw error if manifest file not found', async () => { 11 | (readFile as any).mockImplementationOnce(() => { 12 | throw new Error('File not found'); 13 | }); 14 | await expect(readAssetsManifest(['vendor', 'main'])).rejects.toThrowError(); 15 | }); 16 | 17 | it('should return js and css assets', async () => { 18 | (readFile as any).mockImplementationOnce((path: any, options: any, done: any) => 19 | done( 20 | null, 21 | JSON.stringify({ 22 | vendor: { 23 | js: 'vendor.js', 24 | css: 'vendor.css', 25 | }, 26 | main: { 27 | js: ['main1.js', 'main2.js'], 28 | css: 'main.css', 29 | }, 30 | }), 31 | ), 32 | ); 33 | 34 | const result = await readAssetsManifest(['vendor', 'main']); 35 | 36 | expect(result).toEqual({ 37 | js: ['vendor.js', 'main1.js', 'main2.js'], 38 | css: ['vendor.css', 'main.css'], 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/src/express.ts: -------------------------------------------------------------------------------- 1 | import { Request, Router } from 'express'; 2 | 3 | import { createGetModulesMethod, ModulesConfig } from './modules'; 4 | 5 | export function createGetModulesExpress(modules: ModulesConfig<[Request]>): Router { 6 | const router = Router(); 7 | 8 | const modulesMethodSettings = createGetModulesMethod(modules); 9 | 10 | (router as unknown as Record)[ 11 | modulesMethodSettings.method.toLowerCase() 12 | ](modulesMethodSettings.path, async (req, res) => { 13 | try { 14 | const response = await modulesMethodSettings.handler(req.body, req); 15 | 16 | res.send(response); 17 | } catch (e: any) { 18 | res.status(500).send({ 19 | error: e.message, 20 | status: 500, 21 | }); 22 | } 23 | }); 24 | 25 | return router; 26 | } 27 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/src/hapi-16.ts: -------------------------------------------------------------------------------- 1 | import type { HTTP_METHODS_PARTIAL, PluginFunction, Request } from 'hapi16'; 2 | 3 | import { createGetModulesMethod, ModulesConfig } from './modules'; 4 | 5 | export function createGetModulesHapi16Plugin( 6 | modules: ModulesConfig<[Request]>, 7 | routeParams?: Record, 8 | ) { 9 | const modulesMethodSettings = createGetModulesMethod(modules); 10 | const register: PluginFunction> = (server, options, next) => { 11 | server.route({ 12 | method: modulesMethodSettings.method as HTTP_METHODS_PARTIAL, 13 | path: modulesMethodSettings.path, 14 | config: routeParams, 15 | handler: async (request, reply) => { 16 | try { 17 | const response = await modulesMethodSettings.handler(request.payload, request); 18 | 19 | reply(response); 20 | } catch (e: any) { 21 | reply({ 22 | error: e.message, 23 | status: 500, 24 | }).code(500); 25 | } 26 | }, 27 | }); 28 | 29 | next(); 30 | }; 31 | 32 | register.attributes = { 33 | name: `@alfalab/scripts-server${modulesMethodSettings.path}`, 34 | }; 35 | 36 | return register; 37 | } 38 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/src/hapi-20.ts: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable-next-line 2 | import type { Plugin, Request } from 'hapi20'; 3 | 4 | import { createGetModulesMethod, ModulesConfig } from './modules'; 5 | 6 | export function createGetModulesHapi20Plugin( 7 | modules: ModulesConfig<[Request]>, 8 | routeParams?: Record, 9 | ) { 10 | const modulesMethodSettings = createGetModulesMethod(modules); 11 | 12 | const plugin: Plugin> = { 13 | name: `@alfalab/scripts-server${modulesMethodSettings.path}`, 14 | version: '1.0.0', 15 | register: (server) => { 16 | server.route({ 17 | method: modulesMethodSettings.method, 18 | path: modulesMethodSettings.path, 19 | options: { 20 | ...routeParams, 21 | }, 22 | // eslint-disable-next-line consistent-return 23 | handler: async (request, h) => { 24 | try { 25 | return await modulesMethodSettings.handler(request.payload as any, request); 26 | } catch (e: any) { 27 | h.response({ 28 | error: e.message, 29 | status: 500, 30 | }).code(500); 31 | } 32 | }, 33 | }); 34 | }, 35 | }; 36 | 37 | return plugin; 38 | } 39 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './read-assets-manifest'; 2 | export * from './modules'; 3 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/src/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './create-get-modules-method'; 3 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/src/modules/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | GetModuleStateResult, 3 | GetResourcesRequest, 4 | MountMode, 5 | } from '@alfalab/scripts-modules'; 6 | 7 | export type ModuleDescriptor = { 8 | mountMode: MountMode; 9 | version?: string; 10 | getModuleState: ( 11 | getResourcesRequest: GetResourcesRequest, 12 | ...params: FrameworkParams 13 | ) => Promise; 14 | }; 15 | 16 | export type ModulesConfig = { 17 | [moduleId: string]: ModuleDescriptor; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/src/read-assets-manifest.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { promisify } from 'util'; 4 | 5 | import type { AruiAppManifest } from '@alfalab/scripts-modules'; 6 | 7 | const readFile = promisify(fs.readFile); 8 | 9 | const DEFAULT_BUNDLE_NAMES = ['vendor', 'internalVendor', 'main']; 10 | 11 | let appManifest: AruiAppManifest; 12 | 13 | export async function getAppManifest() { 14 | if (!appManifest) { 15 | const manifestPath = path.join(process.cwd(), '.build/webpack-assets.json'); 16 | const fileContent = await readFile(manifestPath, 'utf8'); 17 | 18 | appManifest = JSON.parse(fileContent); 19 | } 20 | 21 | return appManifest; 22 | } 23 | 24 | export async function readAssetsManifest(bundleNames: string[] = DEFAULT_BUNDLE_NAMES) { 25 | const manifest = await getAppManifest(); 26 | let jsArray: string[] = []; 27 | let cssArray: string[] = []; 28 | 29 | bundleNames.forEach((key) => { 30 | if (!manifest[key]) return; 31 | 32 | const { js, css } = manifest[key]; 33 | 34 | if (js) jsArray = jsArray.concat(js); 35 | if (css) cssArray = cssArray.concat(css); 36 | }); 37 | 38 | return { 39 | js: jsArray, 40 | css: cssArray, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | // для корректной работы @typescript-eslint/parser 4 | "include": ["src", "./*.js", "./*.ts", ".eslintrc.js"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/arui-scripts-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../arui-scripts/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2016", 5 | "module": "commonjs", 6 | "skipLibCheck": true, 7 | "outDir": "./build", 8 | "declaration": true 9 | }, 10 | "include": [ 11 | "src/**/*.ts", 12 | ], 13 | "exclude": ["build"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/arui-scripts/.browserslistrc: -------------------------------------------------------------------------------- 1 | last 2 versions 2 | ie >= 10 3 | Android >= 4 4 | iOS >= 9 5 | not dead 6 | -------------------------------------------------------------------------------- /packages/arui-scripts/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | .turbo -------------------------------------------------------------------------------- /packages/arui-scripts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['custom/common'], 4 | parserOptions: { 5 | tsconfigRootDir: __dirname, 6 | project: ['./tsconfig.eslint.json'], 7 | }, 8 | ignorePatterns: ['./src/templates/dockerfile-compiled.template.ts', '.turbo'], 9 | rules: { 10 | 'import/no-default-export': 'warn', 11 | 'import/no-named-as-default': 'warn', 12 | // чтобы могли использовать for и генераторы 13 | 'no-restricted-syntax': 'off', 14 | // проект - cli-тулза, и она должна писать в консоль 15 | 'no-console': 'off', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /packages/arui-scripts/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/arui-scripts/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | build/tsconfig.tsbuildinfo 3 | __tests__ 4 | -------------------------------------------------------------------------------- /packages/arui-scripts/README.md: -------------------------------------------------------------------------------- 1 | ARUI-scripts 2 | === 3 | 4 | Простой и гибкий инструмент для одновременной сборки клиентской и серверной части react-приложений. 5 | 6 | Использование 7 | === 8 | 9 | 0. Пакет требует использовать следующие версии: 10 | 11 | Зависимость | Версия 12 | -- | -- 13 | `nodejs` | `16.20.2, 18.0.0+, 20.0.0+, 22.0.0+` 14 | `react` | `16.13.0+` 15 | `react-dom` | `16.13.0+` 16 | 17 | 1. Установите `arui-scripts` в свой проект как dev зависимость 18 | 19 | ```bash 20 | yarn add arui-scripts --dev 21 | ``` 22 | или 23 | ```bash 24 | npm install arui-scripts --save-dev 25 | ``` 26 | 27 | 2. Создайте необходимые файлы 28 | - `src/index.{js,jsx,ts,tsx}` - входная точка для клиентского приложения. 29 | - `src/server/index.{js,jsx,tsx}` - входная точка для серверного приложения. 30 | - `node_modules/arui-feather/polyfills` - полифилы для клиентского приложения. 31 | 32 | При желании вы можете изменить эти пути с помощью настроек. 33 | 34 | 3. Используйте команды из `arui-scripts`! 35 | 36 | Документация 37 | === 38 | - [Настройки](docs/settings.md) 39 | - [Команды](docs/commands.md) 40 | - [Примеры входных точек](docs/examples.md) 41 | - [Пресеты](docs/presets.md) 42 | - [Тонкая настройка](docs/overrides.md) 43 | - [Настройки сборки артефакта](docs/artifact.md) 44 | - [Настройки компиляторов](docs/compilers.md) 45 | - [Особенности поведения](docs/caveats.md) 46 | - [Использование модулей](docs/modules.md) 47 | - [Client-only режим](./docs/client-only.md) 48 | -------------------------------------------------------------------------------- /packages/arui-scripts/bin/build.sh: -------------------------------------------------------------------------------- 1 | set -e; 2 | tsc --project tsconfig-local.json;cp ./src/configs/mq.css build/configs/mq.css; 3 | -------------------------------------------------------------------------------- /packages/arui-scripts/docs/compilers.md: -------------------------------------------------------------------------------- 1 | ## Конфигурация typescripts 2 | 3 | Компиляция TS работает из коробки, если в корне проекта есть файл `tsconfig.json`. 4 | За основу можно использовать дефолтный конфиг: 5 | 6 | ```json 7 | { 8 | "extends": "./node_modules/arui-scripts/tsconfig.json" 9 | } 10 | ``` 11 | 12 | По умолчанию TS будет компилироваться через babel, но у этого есть ряд ограничений: 13 | - нельзя использовать namespace 14 | - Нельзя использовать устаревший синтаксис import/export (`import foo = require(...)`, `export = foo`) 15 | - enum merging 16 | 17 | Если вы используете что-то из вышеперечисленного - вы можете вернуться к использованию tsc для компиляции ts файлов используя 18 | настройку [`useTscLoader`](settings.md#usetscloader). 19 | -------------------------------------------------------------------------------- /packages/arui-scripts/jest-preset.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | const settings = require('./build/configs/jest/settings'); 4 | 5 | module.exports = settings; 6 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/bin/index.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | // TODO: remove eslint-disable and eslint-disable-next-line 3 | /* eslint-disable global-require */ 4 | /* eslint import/no-dynamic-require: 0 */ 5 | 6 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 7 | // @ts-ignore 8 | const commands: Record void> = { 9 | start: () => require('../commands/start'), 10 | 'start:prod': () => require('../commands/start-prod'), 11 | build: () => require('../commands/build'), 12 | 'docker-build': () => require('../commands/docker-build'), 13 | 'docker-build:compiled': () => require('../commands/docker-build-compiled'), 14 | test: () => require('../commands/test'), 15 | 'ensure-yarn': () => require('../commands/ensure-yarn'), 16 | 'archive-build': () => require('../commands/archive-build'), 17 | 'bundle-analyze': () => require('../commands/bundle-analyze'), 18 | changelog: () => require('../commands/changelog'), 19 | }; 20 | 21 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 22 | // @ts-ignore 23 | const command = process.argv[2] as string; 24 | 25 | if (!command || !commands[command]) { 26 | console.error( 27 | `Please specify one of available commands: ${Object.keys(commands) 28 | .map((c) => `"${c}"`) 29 | .join(' ')}`, 30 | ); 31 | 32 | process.exit(-1); 33 | } 34 | 35 | commands[command](); 36 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/build/build-wrapper.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: 0 */ 2 | /* eslint no-console: 0 */ 3 | 4 | import chalk from 'chalk'; 5 | import { rspack, Stats, MultiStats, Configuration } from '@rspack/core'; 6 | import formatWebpackMessages from '../util/format-webpack-messages'; 7 | 8 | type BuildResult = { 9 | stats: Stats | MultiStats; 10 | warnings: string[]; 11 | previousFileSizes: unknown; 12 | }; 13 | 14 | function build( 15 | config: Configuration | Configuration[], 16 | previousFileSizes?: unknown, 17 | ) { 18 | let compiler = rspack(config); 19 | return new Promise((resolve, reject) => { 20 | compiler.run((err: any, stats: any) => { 21 | if (err) { 22 | return reject(err); 23 | } 24 | const messages = formatWebpackMessages(stats?.toJson({})); 25 | 26 | if (messages.errors.length) { 27 | // Only keep the first error. Others are often indicative 28 | // of the same problem, but confuse the reader with noise. 29 | if (messages.errors.length > 1) { 30 | messages.errors.length = 1; 31 | } 32 | return reject(new Error(messages.errors.join('\n\n'))); 33 | } 34 | if ( 35 | process.env.CI && 36 | process.env.CI.toLowerCase() !== 'false' && 37 | messages.warnings.length 38 | ) { 39 | console.log( 40 | chalk.yellow( 41 | '\nTreating warnings as errors because process.env.CI = true.\n' + 42 | 'Most CI servers set it automatically.\n', 43 | ), 44 | ); 45 | return reject(new Error(messages.warnings.join('\n\n'))); 46 | } 47 | return resolve({ 48 | stats, 49 | warnings: messages.warnings, 50 | previousFileSizes, 51 | }); 52 | }); 53 | }); 54 | } 55 | 56 | export default build; 57 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/build/client.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: 0 */ 2 | /* eslint no-console: 0 */ 3 | import chalk from 'chalk'; 4 | import printBuildError from 'react-dev-utils/printBuildError'; 5 | import { Configuration, Stats, MultiStats } from '@rspack/core'; 6 | import build from './build-wrapper'; 7 | import { calculateAssetsSizes, printAssetsSizes } from '../util/client-assets-sizes'; 8 | import config from '../../configs/webpack.client.prod'; 9 | 10 | console.log(chalk.magenta('Building client...')); 11 | 12 | build(config) 13 | .then(({ stats, warnings }) => { 14 | if (warnings.length) { 15 | console.log(chalk.yellow('Client compiled with warnings.\n')); 16 | console.log(warnings.join('\n\n')); 17 | console.log( 18 | `Search for the ${chalk.underline( 19 | chalk.yellow('keywords'), 20 | )} to learn more about each warning.`, 21 | ); 22 | console.log( 23 | `To ignore, add ${chalk.cyan('// eslint-disable-next-line')} to the line before.`, 24 | ); 25 | } else { 26 | console.log(chalk.green('Client compiled successfully.\n')); 27 | } 28 | 29 | function printOutputSizes(webpackConfig: Configuration, stats: Stats) { 30 | const sizes = calculateAssetsSizes(stats, webpackConfig?.output?.path); 31 | printAssetsSizes(sizes); 32 | } 33 | 34 | if (Array.isArray(config)) { 35 | config.forEach((conf, index) => 36 | printOutputSizes(conf as any, (stats as MultiStats).stats[index]), 37 | ); 38 | } else { 39 | printOutputSizes(config as any, stats as Stats); 40 | } 41 | }) 42 | .catch((err) => { 43 | console.log(chalk.red('Failed to compile client.\n')); 44 | printBuildError(err); 45 | process.exit(1); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/build/index.ts: -------------------------------------------------------------------------------- 1 | import { runCompilers } from '../util/run-compilers'; 2 | import { getTscWatchCommand } from '../util/tsc'; 3 | import { configs } from '../../configs/app-configs'; 4 | 5 | process.env.BABEL_ENV = 'production'; 6 | process.env.NODE_ENV = 'production'; 7 | process.env.BROWSERSLIST_CONFIG = 8 | process.env.BROWSERSLIST_CONFIG || require.resolve('../../../.browserslistrc'); 9 | 10 | const compilersCommands: Array = [ 11 | require.resolve('./client'), 12 | ]; 13 | 14 | if (!configs.clientOnly) { 15 | compilersCommands.push(require.resolve('./server')); 16 | } 17 | 18 | runCompilers(compilersCommands); 19 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/build/server.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: 0 */ 2 | /* eslint no-console: 0 */ 3 | import chalk from 'chalk'; 4 | import printBuildError from 'react-dev-utils/printBuildError'; 5 | import build from './build-wrapper'; 6 | 7 | import config from '../../configs/webpack.server.prod'; 8 | 9 | console.log(chalk.magenta('Building server...')); 10 | 11 | build(config as any) 12 | .then(({ warnings }) => { 13 | if (warnings.length) { 14 | console.log(chalk.yellow('Server compiled with warnings.\n')); 15 | console.log(warnings.join('\n\n')); 16 | console.log( 17 | `Search for the ${chalk.underline( 18 | chalk.yellow('keywords'), 19 | )} to learn more about each warning.`, 20 | ); 21 | console.log( 22 | `To ignore, add ${chalk.cyan('// eslint-disable-next-line')} to the line before.`, 23 | ); 24 | } else { 25 | console.log(chalk.green('Server compiled successfully.\n')); 26 | } 27 | }) 28 | .catch((err) => { 29 | console.log(chalk.red('Failed to compile server.\n')); 30 | printBuildError(err); 31 | process.exit(1); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/bundle-analyze/index.ts: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable 2 | /* eslint-disable no-param-reassign */ 3 | import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin'; 4 | import { rspack, WebpackPluginInstance } from '@rspack/core'; 5 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 6 | 7 | import { configs } from '../../configs/app-configs'; 8 | import clientConfig from '../../configs/webpack.client.prod'; 9 | import makeTmpDir from '../util/make-tmp-dir'; 10 | 11 | (async () => { 12 | const clientWebpackConfigs = Array.isArray(clientConfig) ? clientConfig : [clientConfig]; 13 | 14 | const promises = clientWebpackConfigs.map(async (webpackConfig, i) => { 15 | const tmpDir = await makeTmpDir(i.toString()); 16 | 17 | webpackConfig.plugins = [ 18 | ...(webpackConfig.plugins || []), 19 | new BundleAnalyzerPlugin({ 20 | generateStatsFile: true, 21 | statsFilename: configs.statsOutputPath, 22 | analyzerPort: 'auto', 23 | }) as unknown as WebpackPluginInstance, // webpack-bundle-analyzer has incorrect types 24 | new RsdoctorRspackPlugin({}), 25 | ]; 26 | webpackConfig.output = { 27 | ...webpackConfig.output, 28 | path: tmpDir, 29 | }; 30 | }); 31 | 32 | await Promise.all(promises); 33 | 34 | rspack(clientWebpackConfigs).run(() => {}); 35 | })(); 36 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/docker-build-compiled/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | 3 | import configs from '../../configs/app-configs'; 4 | import nginxBaseConfTemplate from '../../templates/base-nginx.conf.template'; 5 | import dockerfileTemplate from '../../templates/dockerfile-compiled.template'; 6 | import nginxConfTemplate from '../../templates/nginx.conf.template'; 7 | import startScript from '../../templates/start.template'; 8 | import { 9 | getBuildParamsFromArgs, 10 | getDockerBuildCommand, 11 | prepareFilesForDocker, 12 | } from '../util/docker-build'; 13 | import exec from '../util/exec'; 14 | 15 | (async () => { 16 | const { imageFullName, pathToTempDir, tempDirName } = getBuildParamsFromArgs(); 17 | 18 | try { 19 | console.log(`Build docker image ${imageFullName}`); 20 | console.time('Total time'); 21 | 22 | await prepareFilesForDocker({ 23 | pathToTempDir, 24 | dockerfileTemplate, 25 | nginxConfTemplate, 26 | nginxBaseConfTemplate, 27 | startScriptTemplate: startScript, 28 | allowLocalDockerfile: false, 29 | allowLocalStartScript: false, 30 | addNodeModulesToDockerIgnore: true, 31 | }); 32 | 33 | await exec(getDockerBuildCommand({ tempDirName, imageFullName })); 34 | await fs.remove(pathToTempDir); 35 | 36 | // guard against pushing the image during tests 37 | if (!configs.debug) { 38 | await exec(`docker push ${imageFullName}`); 39 | } 40 | 41 | console.timeEnd('Total time'); 42 | } catch (err) { 43 | await fs.remove(pathToTempDir); 44 | console.error('Error during docker-build.'); 45 | if (configs.debug) { 46 | console.error(err); 47 | } 48 | process.exit(1); 49 | } 50 | })(); 51 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/docker-build/.gitignore: -------------------------------------------------------------------------------- 1 | nginx.conf -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/ensure-yarn/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | 3 | if (process.env.npm_config_user_agent && process.env.npm_config_user_agent.indexOf('yarn') === -1) { 4 | console.log(process.env); 5 | console.error('You must use yarn to install dependencies, now using', process.env.npm_execpath); 6 | console.log('Recommended version is yarn@0.27.5'); 7 | process.exit(1); 8 | } 9 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/start-prod/client.ts: -------------------------------------------------------------------------------- 1 | import clientConfig from '../../configs/webpack.client.prod'; 2 | import { runClientDevServer } from '../util/run-client-dev-server'; 3 | 4 | runClientDevServer(clientConfig); 5 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/start-prod/index.ts: -------------------------------------------------------------------------------- 1 | import { configs } from '../../configs/app-configs'; 2 | import { runCompilers } from '../util/run-compilers'; 3 | import { getTscWatchCommand } from '../util/tsc'; 4 | 5 | process.env.BROWSERSLIST_CONFIG = 6 | process.env.BROWSERSLIST_CONFIG || require.resolve('../../../.browserslistrc'); 7 | 8 | const compilersCommands: Array = [ 9 | require.resolve('./client'), 10 | ]; 11 | 12 | if (!configs.clientOnly) { 13 | compilersCommands.push(require.resolve('./server')); 14 | } 15 | 16 | if (configs.tsconfig && configs.disableDevWebpackTypecheck) { 17 | compilersCommands.push(getTscWatchCommand(configs.tsconfig)); 18 | } 19 | 20 | runCompilers(compilersCommands); 21 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/start-prod/server.ts: -------------------------------------------------------------------------------- 1 | import serverConfig from '../../configs/webpack.server.prod'; 2 | import { runServerWatchCompiler } from '../util/run-server-watch-compiler'; 3 | 4 | runServerWatchCompiler(serverConfig); 5 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/start/client.ts: -------------------------------------------------------------------------------- 1 | import clientConfig from '../../configs/webpack.client.dev'; 2 | import { runClientDevServer } from '../util/run-client-dev-server'; 3 | 4 | runClientDevServer(clientConfig); 5 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/start/index.ts: -------------------------------------------------------------------------------- 1 | import { configs } from '../../configs/app-configs'; 2 | import { runCompilers } from '../util/run-compilers'; 3 | import { getTscWatchCommand } from '../util/tsc'; 4 | 5 | process.env.BROWSERSLIST_CONFIG = 6 | process.env.BROWSERSLIST_CONFIG || require.resolve('../../../.browserslistrc'); 7 | 8 | const compilersCommands: Array = [ 9 | require.resolve('./client'), 10 | ]; 11 | 12 | if (!configs.clientOnly) { 13 | compilersCommands.push(require.resolve('./server')); 14 | } 15 | 16 | if (configs.tsconfig && configs.disableDevWebpackTypecheck) { 17 | compilersCommands.push(getTscWatchCommand(configs.tsconfig)); 18 | } 19 | 20 | runCompilers(compilersCommands); 21 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/start/print-compiler-output.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import statsOptions from '../../configs/stats-options'; 4 | 5 | export default function printCompilerOutput(compilerName: string, stats: any) { 6 | const output = stats 7 | .toString(statsOptions) 8 | .split('\n') 9 | .map((line: string) => `${chalk.cyan(`[${compilerName}]`)} ${line}`) 10 | .join('\n'); 11 | 12 | console.log(output); 13 | } 14 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/start/server.ts: -------------------------------------------------------------------------------- 1 | import serverConfig from '../../configs/webpack.server.dev'; 2 | import { runServerWatchCompiler } from '../util/run-server-watch-compiler'; 3 | 4 | runServerWatchCompiler(serverConfig); 5 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/test/get-config.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | import { replaceRootDirInPath } from 'jest-config'; 5 | import Resolver from 'jest-resolve'; 6 | import merge from 'lodash.merge'; 7 | 8 | import { configs } from '../../configs/app-configs'; 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | import defaultJestConfig from '../../configs/jest/settings'; 12 | 13 | const PRESET_EXTENSIONS = ['.json', '.js', '.cjs', '.mjs']; 14 | const PRESET_NAME = 'jest-preset'; 15 | 16 | export const getJestConfig = async () => { 17 | const {preset, ...appJestConfig} = await getAppJestConfig(); 18 | 19 | let presetConfig = {}; 20 | 21 | if (preset) { 22 | presetConfig = await getPresetConfig(preset); 23 | } 24 | 25 | return merge(defaultJestConfig, presetConfig, appJestConfig); 26 | }; 27 | 28 | async function getAppJestConfig() { 29 | const jestConfigPath = path.resolve(process.cwd(), 'jest.config.js'); 30 | 31 | if (fs.existsSync(jestConfigPath)) { 32 | return (await import(jestConfigPath)).default; 33 | } 34 | 35 | if (configs.appPackage.jest) { 36 | return configs.appPackage.jest; 37 | } 38 | 39 | return {}; 40 | } 41 | 42 | async function getPresetConfig(presetPath?: string) { 43 | if (!presetPath) { 44 | return {}; 45 | } 46 | const rootDir = process.cwd(); 47 | 48 | const normalizedPresetPath = replaceRootDirInPath(rootDir, presetPath); 49 | const presetModule = Resolver.findNodeModule( 50 | normalizedPresetPath.startsWith('.') 51 | ? normalizedPresetPath 52 | : path.join(normalizedPresetPath, PRESET_NAME), 53 | { 54 | basedir: rootDir, 55 | extensions: PRESET_EXTENSIONS, 56 | }, 57 | ); 58 | 59 | if (!presetModule) { 60 | throw new Error(`Cannot find module '${normalizedPresetPath}'`); 61 | } 62 | 63 | const { preset: subPreset, ...preset } = (await import(presetModule)).default; 64 | 65 | if (subPreset) { 66 | console.warn(`Jest can't handle preset chaining. Preset "${subPreset}" will be ignored.`); 67 | } 68 | 69 | return preset; 70 | } 71 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as jestRunner from 'jest'; 2 | 3 | import { getJestConfig } from './get-config'; 4 | 5 | // Do this as the first thing so that any code reading it knows the right env. 6 | process.env.BABEL_ENV = 'test'; 7 | process.env.NODE_ENV = 'test'; 8 | process.env.PUBLIC_URL = ''; 9 | 10 | // Makes the script crash on unhandled rejections instead of silently 11 | // ignoring them. In the future, promise rejections that are not handled will 12 | // terminate the Node.js process with a non-zero exit code. 13 | process.on('unhandledRejection', (err) => { 14 | // eslint-disable-next-line @typescript-eslint/no-throw-literal 15 | throw err; 16 | }); 17 | 18 | // Skip 'node', 'arui-scripts' and 'test' arguments and take all the rest (or none if there is no more arguments). 19 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 20 | // @ts-ignore 21 | const argv = process.argv.slice(3); 22 | 23 | const runJest = async () => { 24 | const jestConfig = await getJestConfig(); 25 | 26 | argv.push('--config', JSON.stringify(jestConfig)); 27 | 28 | jestRunner.run(argv); 29 | }; 30 | 31 | runJest().catch((error) => { 32 | console.error('Error running Jest:', error); 33 | process.exit(1); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/util/exec.ts: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable-next-line 2 | import shell from 'shelljs'; 3 | 4 | function exec(command: string) { 5 | return new Promise((resolve, reject) => { 6 | console.log(`Executing command: ${command}`); 7 | // eslint-disable-next-line consistent-return 8 | shell.exec(command, (code) => { 9 | if (code === 0) { 10 | return resolve(code); 11 | } 12 | reject(code); 13 | }); 14 | }); 15 | } 16 | 17 | export default exec; 18 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/util/make-tmp-dir.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import os from 'os'; 3 | import path from 'path'; 4 | import util from 'util'; 5 | 6 | const nodeMakeTmpDir = util.promisify(fs.mkdtemp); 7 | 8 | /** 9 | * Создает и возвращает временную папку 10 | * @returns {Promise} 11 | */ 12 | async function makeTmpDir(prefix?: string) { 13 | return nodeMakeTmpDir(path.join(os.tmpdir(), prefix || '')); 14 | } 15 | 16 | export default makeTmpDir; 17 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/util/run-client-dev-server.ts: -------------------------------------------------------------------------------- 1 | import { choosePort } from 'react-dev-utils/WebpackDevServerUtils'; 2 | import { Configuration, rspack } from '@rspack/core'; 3 | import { RspackDevServer } from '@rspack/dev-server'; 4 | 5 | import devServerConfig from '../../configs/dev-server'; 6 | import printCompilerOutput from '../start/print-compiler-output'; 7 | 8 | export async function runClientDevServer(configuration: Configuration | Configuration[]) { 9 | const clientCompiler = rspack(configuration); 10 | const clientDevServer = new RspackDevServer(devServerConfig, clientCompiler); 11 | 12 | clientCompiler.hooks.invalid.tap('client', () => console.log('Compiling client...')); 13 | clientCompiler.hooks.done.tap('client', (stats: any) => printCompilerOutput('Client', stats)); 14 | 15 | const DEFAULT_PORT = devServerConfig.port; 16 | const HOST = '0.0.0.0'; 17 | 18 | try { 19 | const port = await choosePort(HOST, +(DEFAULT_PORT || 0)); 20 | 21 | if (!port) { 22 | // We have not found a port. 23 | return; 24 | } 25 | 26 | clientDevServer.startCallback(() => { 27 | console.log(`Client dev server running at http://${HOST}:${port}...`); 28 | }); 29 | } catch (err: any) { 30 | if (err && err.message) { 31 | console.log(err.message); 32 | } 33 | 34 | process.exit(1); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/util/run-compilers.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | 3 | import fs from 'fs-extra'; 4 | 5 | import { configs } from '../../configs/app-configs'; 6 | 7 | export function runCompilers(pathToCompilers: Array) { 8 | if (fs.pathExistsSync(configs.serverOutputPath)) { 9 | fs.removeSync(configs.serverOutputPath); 10 | } 11 | 12 | const compilers = pathToCompilers.map((pathToCompiler) => { 13 | if (Array.isArray(pathToCompiler)) { 14 | const compiler = spawn('node', pathToCompiler, { 15 | stdio: 'inherit', 16 | cwd: configs.cwd, 17 | }); 18 | 19 | compiler.on('error', onProcessExit); 20 | compiler.on('close', onProcessExit); 21 | 22 | return compiler; 23 | } 24 | 25 | const compiler = spawn('node', [pathToCompiler], { 26 | stdio: 'inherit', 27 | }); 28 | 29 | compiler.on('error', onProcessExit); 30 | compiler.on('close', onProcessExit); 31 | 32 | return compiler; 33 | }); 34 | 35 | function onProcessExit(code: number) { 36 | if (code !== 0) { 37 | compilers.forEach((compiler) => compiler.kill()); 38 | process.exit(code); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/util/run-server-watch-compiler.ts: -------------------------------------------------------------------------------- 1 | import { Configuration,rspack } from '@rspack/core'; 2 | 3 | import configs from '../../configs/app-configs'; 4 | import printCompilerOutput from '../start/print-compiler-output'; 5 | 6 | export function runServerWatchCompiler(config: Configuration) { 7 | 8 | const serverCompiler = rspack(config); 9 | 10 | serverCompiler.hooks.compile.tap('server', () => console.log('Compiling server...')); 11 | serverCompiler.hooks.invalid.tap('server', () => console.log('Compiling server...')); 12 | serverCompiler.hooks.done.tap('server', (stats: any) => printCompilerOutput('Server', stats)); 13 | 14 | serverCompiler.watch( 15 | { 16 | aggregateTimeout: 50, // Делаем это значение меньше чем у клиента, чтобы сервер пересобирался быстрее 17 | ignored: new RegExp(configs.watchIgnorePath.join('|')), 18 | }, 19 | () => {}, 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/util/tsc.ts: -------------------------------------------------------------------------------- 1 | export function getTscWatchCommand(tsconfig: string) { 2 | return [ 3 | require.resolve('typescript/lib/tsc.js'), 4 | '--watch', 5 | '--noEmit', 6 | '--project', 7 | tsconfig, 8 | '--skipLibCheck', 9 | ]; 10 | } 11 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/commands/util/yarn.ts: -------------------------------------------------------------------------------- 1 | import shell from 'shelljs'; 2 | 3 | import { configs } from '../../configs/app-configs'; 4 | 5 | type YarnVersion = '1' | '2+' | 'unavailable'; 6 | 7 | export function getYarnVersion(): YarnVersion { 8 | if (configs.useYarn && shell.which('yarn')) { 9 | const yarnVersion = shell.exec('yarn -v', { silent: true }); 10 | const yarnMajorVersion = Number(yarnVersion.split('.')[0]); 11 | 12 | return yarnMajorVersion > 1 ? '2+' : '1'; 13 | } 14 | 15 | return 'unavailable'; 16 | } 17 | 18 | export function getPruningCommand(): string { 19 | if (configs.clientOnly) { 20 | return 'echo "Skipping pruning in client only mode"'; 21 | } 22 | const yarnVersion = getYarnVersion(); 23 | 24 | switch (yarnVersion) { 25 | case '1': { 26 | return 'yarn install --production --ignore-optional --frozen-lockfile --ignore-scripts --prefer-offline'; 27 | } 28 | case '2+': { 29 | return 'yarn workspaces focus --production --all'; 30 | } 31 | case 'unavailable': { 32 | return 'npm prune --production'; 33 | } 34 | default: { 35 | return ''; 36 | } 37 | } 38 | } 39 | 40 | export function getInstallProductionCommand(): string { 41 | const yarnVersion = getYarnVersion(); 42 | 43 | switch (yarnVersion) { 44 | case '1': { 45 | return 'yarn install --production --ignore-optional --frozen-lockfile --ignore-scripts --prefer-offline'; 46 | } 47 | case '2+': { 48 | return 'yarn workspaces focus --production --all'; 49 | } 50 | case 'unavailable': { 51 | return 'npm install --production'; 52 | } 53 | default: { 54 | return ''; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/__tests__/update-with-env.tests.ts: -------------------------------------------------------------------------------- 1 | import { AppConfigs } from '../types'; 2 | import { updateWithEnv } from '../update-with-env'; 3 | 4 | describe('update-with-env', () => { 5 | const OLD_ENV = process.env; 6 | 7 | beforeEach(() => { 8 | jest.resetModules(); 9 | process.env = { ...OLD_ENV }; 10 | jest.spyOn(console, 'warn').mockImplementation(() => {}); 11 | jest.spyOn(console, 'error').mockImplementation(() => {}); 12 | }); 13 | 14 | afterEach(() => { 15 | jest.resetAllMocks(); 16 | }) 17 | 18 | afterAll(() => { 19 | process.env = OLD_ENV; 20 | }); 21 | 22 | it('should return unmodified config if env `ARUI_SCRIPTS_CONFIG` is not set', () => { 23 | process.env.ARUI_SCRIPTS_CONFIG = undefined; 24 | const baseConfig = { dockerRegistry: 'docker.my-company.com' } as AppConfigs; 25 | 26 | const updatedConfig = updateWithEnv(baseConfig); 27 | 28 | expect(baseConfig).toEqual(updatedConfig); 29 | }); 30 | 31 | it('should throw an error if env `ARUI_SCRIPTS_CONFIG` contains not valid JSON', () => { 32 | process.env.ARUI_SCRIPTS_CONFIG = 'not a valid json'; 33 | const baseConfig = { dockerRegistry: 'docker.my-company.com' } as AppConfigs; 34 | 35 | expect(() => { 36 | updateWithEnv(baseConfig); 37 | }).toThrowError(); 38 | }); 39 | 40 | it('should update config keys with values from env', () => { 41 | process.env.ARUI_SCRIPTS_CONFIG = JSON.stringify({ 42 | dockerRegistry: 'docker.other-company.com', 43 | }); 44 | const baseConfig = { dockerRegistry: 'docker.my-company.com' } as AppConfigs; 45 | 46 | const updatedConfig = updateWithEnv(baseConfig); 47 | 48 | expect(updatedConfig.dockerRegistry).toEqual('docker.other-company.com'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/__tests__/update-with-package.tests.ts: -------------------------------------------------------------------------------- 1 | import { AppConfigs, AppContext } from '../types'; 2 | import { updateWithPackage } from '../update-with-package'; 3 | 4 | describe('update-with-package', () => { 5 | it('should merge keys from "appPackage.aruiScripts" field into base config', () => { 6 | const baseConfig = { 7 | dockerRegistry: 'docker.my-company.com', 8 | compatModules: { 9 | shared: { 10 | react: 'react', 11 | }, 12 | }, 13 | } as unknown as AppConfigs; 14 | const context = { 15 | appPackage: { 16 | aruiScripts: { 17 | dockerRegistry: 'docker.other-company.com', 18 | compatModules: { 19 | exposes: { 20 | example: { 21 | entry: 'foo.js', 22 | }, 23 | }, 24 | }, 25 | }, 26 | }, 27 | } as AppContext; 28 | 29 | const updatedConfig = updateWithPackage(baseConfig, context); 30 | 31 | expect(updatedConfig.dockerRegistry).toBe('docker.other-company.com'); 32 | expect(updatedConfig.compatModules).toEqual({ 33 | shared: { 34 | react: 'react', 35 | }, 36 | exposes: { 37 | example: { 38 | entry: 'foo.js', 39 | }, 40 | }, 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/__tests__/validate-settings-keys.tests.ts: -------------------------------------------------------------------------------- 1 | import { validateSettingsKeys } from '../validate-settings-keys'; 2 | 3 | describe('validate-settings-keys', () => { 4 | it('should warn with console.warn if object contains unknown properties', () => { 5 | const objectWithSettings = { 6 | name: 'vasia', 7 | country: 'russia', 8 | }; 9 | const baseSettings = { 10 | name: 'ivan', 11 | }; 12 | 13 | jest.spyOn(console, 'warn').mockImplementationOnce(() => {}); 14 | 15 | validateSettingsKeys(baseSettings, objectWithSettings); 16 | 17 | expect(console.warn).toHaveBeenCalledTimes(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/index.ts: -------------------------------------------------------------------------------- 1 | import { calculateDependentConfig, calculateDependentContext } from './calculate-dependent-config'; 2 | import { getDefaultAppConfig, getDefaultAppContext } from './get-defaults'; 3 | import { AppConfigs, AppContext, AppContextWithConfigs } from './types'; 4 | import { updateWithConfigFile } from './update-with-config-file'; 5 | import { updateWithEnv } from './update-with-env'; 6 | import { updateWithPackage } from './update-with-package'; 7 | import { updateWithPresets } from './update-with-presets'; 8 | import { warnAboutDeprecations } from './warn-about-deprecations'; 9 | 10 | import '../util/register-ts-node'; 11 | 12 | let tmpConfig: AppConfigs = getDefaultAppConfig(); 13 | let tmpContext: AppContext = getDefaultAppContext(); 14 | 15 | tmpConfig = updateWithPresets(tmpConfig, tmpContext); 16 | tmpConfig = updateWithPackage(tmpConfig, tmpContext); 17 | tmpConfig = updateWithConfigFile(tmpConfig, tmpContext); 18 | tmpConfig = updateWithEnv(tmpConfig); 19 | tmpConfig = calculateDependentConfig(tmpConfig); 20 | tmpContext = calculateDependentContext(tmpConfig, tmpContext); 21 | 22 | export const appContext = tmpContext; 23 | export const appConfigs = tmpConfig; 24 | 25 | export const configs: AppContextWithConfigs = { 26 | ...appConfigs, 27 | ...appContext, 28 | }; 29 | 30 | warnAboutDeprecations(configs); 31 | 32 | export default configs; 33 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/read-config-file.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import { tryResolve } from '../util/resolve'; 4 | 5 | export function readConfigFile(cwd: string) { 6 | const appConfigPath = getConfigFilePath(cwd); 7 | 8 | if (appConfigPath) { 9 | // Мы не можем использовать импорты, нам нужен именно require, потому что мы не знаем заранее не только путь до файла, 10 | // но и то, на каком языке он написан 11 | // eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires 12 | let appSettings = require(appConfigPath); 13 | 14 | // ts-node импортирует esModules, из них надо вытягивать default именно так 15 | // eslint-disable-next-line no-underscore-dangle 16 | if (appSettings.__esModule) { 17 | appSettings = appSettings.default; 18 | } 19 | 20 | return appSettings; 21 | } 22 | 23 | return null; 24 | } 25 | 26 | export function getConfigFilePath(cwd: string) { 27 | return tryResolve(path.join(cwd, '/arui-scripts.config')); 28 | } 29 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/update-with-config-file.ts: -------------------------------------------------------------------------------- 1 | import merge from 'lodash.merge'; 2 | 3 | import { readConfigFile } from './read-config-file'; 4 | import { AppConfigs, AppContext } from './types'; 5 | import { validateSettingsKeys } from './validate-settings-keys'; 6 | 7 | export function updateWithConfigFile(config: AppConfigs, context: AppContext) { 8 | const appSettings = readConfigFile(context.cwd); 9 | 10 | if (appSettings) { 11 | validateSettingsKeys(config, appSettings, context.cwd); 12 | 13 | return merge(config, appSettings); 14 | } 15 | 16 | return config; 17 | } 18 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/update-with-env.ts: -------------------------------------------------------------------------------- 1 | import merge from 'lodash.merge'; 2 | 3 | import { AppConfigs } from './types'; 4 | import { validateSettingsKeys } from './validate-settings-keys'; 5 | 6 | export function updateWithEnv(config: AppConfigs) { 7 | if (!process.env.ARUI_SCRIPTS_CONFIG) { 8 | return config; 9 | } 10 | 11 | try { 12 | console.warn('Используйте ARUI_SCRIPTS_CONFIG только для отладки'); 13 | const envSettings = JSON.parse(process.env.ARUI_SCRIPTS_CONFIG); 14 | 15 | validateSettingsKeys(config, envSettings, 'ENV'); 16 | 17 | return merge(config, envSettings); 18 | } catch (e) { 19 | console.error(e); 20 | throw Error( 21 | 'Not valid JSON passed. Correct it. For example: ARUI_SCRIPTS_CONFIG="{"serverPort":3333}"', 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/update-with-package.ts: -------------------------------------------------------------------------------- 1 | import merge from 'lodash.merge'; 2 | 3 | import { AppConfigs, AppContext } from './types'; 4 | import { validateSettingsKeys } from './validate-settings-keys'; 5 | 6 | export function updateWithPackage(config: AppConfigs, context: AppContext) { 7 | const packageSettings = context.appPackage.aruiScripts || {}; 8 | 9 | validateSettingsKeys(config, packageSettings, 'package.json'); 10 | 11 | return merge(config, packageSettings); 12 | } 13 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/update-with-presets.ts: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable-next-line 2 | import merge from 'lodash.merge'; 3 | 4 | import { tryResolve } from '../util/resolve'; 5 | 6 | import { AppConfigs, AppContext } from './types'; 7 | import { validateSettingsKeys } from './validate-settings-keys'; 8 | 9 | export function updateWithPresets(config: AppConfigs, context: AppContext) { 10 | if (!config.presets) { 11 | return config; 12 | } 13 | 14 | const presetsConfigPath = getPresetsConfigPath(config, context.cwd); 15 | const presetsOverridesPath = tryResolve(`${config.presets}/arui-scripts.overrides`, { 16 | paths: [context.cwd], 17 | }); 18 | 19 | if (presetsConfigPath) { 20 | // eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires, global-require 21 | let presetsSettings = require(presetsConfigPath); 22 | 23 | // eslint-disable-next-line no-underscore-dangle 24 | if (presetsSettings.__esModule) { 25 | // ts-node импортирует esModules, из них надо вытягивать default именно так 26 | presetsSettings = presetsSettings.default; 27 | } 28 | validateSettingsKeys(config, presetsSettings, presetsConfigPath); 29 | // eslint-disable-next-line no-param-reassign 30 | config = merge(config, presetsSettings); 31 | } 32 | if (presetsOverridesPath) { 33 | context.overridesPath.unshift(presetsOverridesPath); 34 | } 35 | 36 | return config; 37 | } 38 | 39 | export function getPresetsConfigPath(config: AppConfigs, cwd: string) { 40 | return tryResolve(`${config.presets}/arui-scripts.config`, { 41 | paths: [cwd], 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/validate-settings-keys.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Функция проверяет что все ключи объекта settingsObject есть в уже существующей конфигурации 3 | */ 4 | export function validateSettingsKeys( 5 | existingConfig: Record, 6 | settingsObject: Record, 7 | source?: string, 8 | ) { 9 | Object.keys(settingsObject).forEach((setting) => { 10 | if (typeof existingConfig[setting] === 'undefined') { 11 | console.warn(`Неизвестная настройка "${setting}" в ${source || 'конфигурации'}`); 12 | } 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/app-configs/warn-about-deprecations.ts: -------------------------------------------------------------------------------- 1 | import { RspackDevServer } from '@rspack/dev-server'; 2 | 3 | import { AppContextWithConfigs } from './types'; 4 | 5 | type ProxyConfigArrayItem = RspackDevServer['options']['proxy'][0]; 6 | 7 | export function warnAboutDeprecations(config: AppContextWithConfigs) { 8 | if (!Array.isArray(config.proxy) && config.proxy) { 9 | console.warn( 10 | 'Передача config.proxy как объекта больше не поддерживается. ', 11 | 'arui-scripts попробует привести конфигурацию к корректному виду, но это не всегда может работать корректно', 12 | 'Правильный формат конфигурации можно посмотреть в документации rspack: https://rspack.dev/guide/features/dev-server#proxy' 13 | ); 14 | 15 | // eslint-disable-next-line no-param-reassign 16 | config.proxy = convertObjectProxyConfigurationToArray(config.proxy); 17 | } 18 | } 19 | 20 | function convertObjectProxyConfigurationToArray(proxyConfiguration: Record) { 21 | const arrayProxy: ProxyConfigArrayItem[] = []; 22 | 23 | Object.keys(proxyConfiguration).forEach((context) => { 24 | const itemConfig = proxyConfiguration[context]; 25 | 26 | arrayProxy.push({ 27 | context, 28 | ...(typeof itemConfig === 'string' ? { 29 | target: itemConfig, 30 | }: itemConfig), 31 | }); 32 | }); 33 | 34 | return arrayProxy; 35 | } 36 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/babel-dependencies.ts: -------------------------------------------------------------------------------- 1 | import applyOverrides from './util/apply-overrides'; 2 | import configs from './app-configs'; 3 | 4 | export const babelDependencies = applyOverrides('babelDependencies', { 5 | sourceType: 'unambiguous', 6 | presets: [ 7 | [ 8 | require.resolve('@babel/preset-env'), 9 | { 10 | // Allow importing core-js in entrypoint and use browserlist to select polyfills 11 | useBuiltIns: 'entry', 12 | // Set the corejs version we are using to avoid warnings in console 13 | // This will need to change once we upgrade to corejs@3 14 | corejs: '3.32', 15 | // Exclude transforms that make all code slower 16 | exclude: ['transform-typeof-symbol'], 17 | }, 18 | ], 19 | ], 20 | plugins: [ 21 | [ 22 | require.resolve('@babel/plugin-transform-runtime'), 23 | { 24 | corejs: false, 25 | helpers: true, 26 | // By default, babel assumes babel/runtime version 7.0.0-beta.0, 27 | // explicitly resolving to match the provided helper functions. 28 | // https://github.com/babel/babel/issues/10261 29 | version: configs.babelRuntimeVersion, 30 | regenerator: true, 31 | }, 32 | ], 33 | ], 34 | }); 35 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/babel-server.ts: -------------------------------------------------------------------------------- 1 | import applyOverrides from './util/apply-overrides'; 2 | import configs from './app-configs'; 3 | 4 | const config = applyOverrides(['babel', 'babelServer'], { 5 | presets: [ 6 | [ 7 | require.resolve('@babel/preset-env'), 8 | { modules: false, targets: { node: 'current' }, loose: true }, 9 | ], 10 | configs.tsconfig !== null && 11 | configs.codeLoader !== 'tsc' && 12 | require.resolve('@babel/preset-typescript'), 13 | require.resolve('@babel/preset-react'), 14 | ].filter(Boolean), 15 | plugins: [ 16 | require.resolve('@babel/plugin-syntax-dynamic-import'), 17 | require.resolve('@babel/plugin-transform-proto-to-assign'), 18 | [require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }], 19 | [require.resolve('@babel/plugin-transform-class-properties'), { loose: true }], 20 | [require.resolve('@babel/plugin-transform-private-methods'), { loose: true }], 21 | [require.resolve('@babel/plugin-transform-private-property-in-object'), { loose: true }], 22 | require.resolve('@babel/plugin-transform-numeric-separator'), 23 | require.resolve('@babel/plugin-proposal-export-default-from'), 24 | require.resolve('@babel/plugin-transform-export-namespace-from'), 25 | [require.resolve('@babel/plugin-transform-object-rest-spread'), { useBuiltIns: true }], 26 | [require.resolve('@babel/plugin-transform-runtime'), { helpers: false }], 27 | require.resolve('@babel/plugin-transform-nullish-coalescing-operator'), 28 | require.resolve('@babel/plugin-transform-optional-chaining'), 29 | configs.collectCoverage && require.resolve('babel-plugin-istanbul'), 30 | ].filter(Boolean), 31 | env: { 32 | production: { 33 | plugins: [ 34 | require.resolve('@babel/plugin-transform-react-constant-elements'), 35 | require.resolve('@babel/plugin-transform-react-inline-elements'), 36 | require.resolve('babel-plugin-transform-react-remove-prop-types'), 37 | ], 38 | }, 39 | test: { 40 | plugins: [require.resolve('@babel/plugin-transform-modules-commonjs')], 41 | }, 42 | }, 43 | }); 44 | 45 | export default config; 46 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/client-env-config/add-env-to-html-template.ts: -------------------------------------------------------------------------------- 1 | import { getEnvConfigContent } from './get-env-config'; 2 | 3 | export function addEnvToHtmlTemplate(html: string) { 4 | return html.replace('<%= envConfig %>', getEnvConfigContent()); 5 | } 6 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/client-env-config/client-config-plugin.ts: -------------------------------------------------------------------------------- 1 | import { Compiler } from '@rspack/core'; 2 | 3 | import { ENV_CONFIG_FILENAME } from './constants'; 4 | import { getEnvConfigContent } from './get-env-config'; 5 | 6 | export class ClientConfigPlugin { 7 | protected cachedContent: string | null = null; 8 | 9 | // eslint-disable-next-line class-methods-use-this 10 | apply(compiler: Compiler) { 11 | const pluginName = ClientConfigPlugin.name; 12 | 13 | const { webpack } = compiler; 14 | 15 | compiler.hooks.thisCompilation.tap(pluginName, (compilation) => { 16 | // Tapping to the assets processing pipeline on a specific stage. 17 | compilation.hooks.processAssets.tap( 18 | { 19 | name: pluginName, 20 | stage: webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE, 21 | }, 22 | () => { 23 | const content = getEnvConfigContent(); 24 | 25 | compilation.emitAsset( 26 | `../${ENV_CONFIG_FILENAME}`, 27 | new webpack.sources.RawSource(content) 28 | ); 29 | } 30 | ); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/client-env-config/constants.ts: -------------------------------------------------------------------------------- 1 | export const ENV_CONFIG_FILENAME = 'env-config.json'; 2 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/client-env-config/get-env-config.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | import { configs } from '../app-configs'; 5 | 6 | import { ENV_CONFIG_FILENAME } from './constants'; 7 | 8 | export function replaceTemplateVariables(template: string, variables: Record) { 9 | return template.replace(/\$\{(\w+)}/g, (match, varName) => variables[varName] || ''); 10 | } 11 | 12 | let cachedEnvConfig: string | null = null; 13 | 14 | export function getEnvConfigContent() { 15 | if (cachedEnvConfig) { 16 | return cachedEnvConfig; 17 | } 18 | 19 | const configTemplate = path.join(configs.cwd, ENV_CONFIG_FILENAME); 20 | 21 | if (!fs.existsSync(configTemplate)) { 22 | cachedEnvConfig = '{}'; 23 | 24 | return cachedEnvConfig 25 | } 26 | 27 | const templateContent = fs.readFileSync(configTemplate, 'utf8'); 28 | 29 | cachedEnvConfig = replaceTemplateVariables(templateContent, process.env); 30 | 31 | return cachedEnvConfig 32 | } 33 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/client-env-config/index.ts: -------------------------------------------------------------------------------- 1 | export { ENV_CONFIG_FILENAME } from './constants' 2 | export { ClientConfigPlugin } from './client-config-plugin'; 3 | export { addEnvToHtmlTemplate } from './add-env-to-html-template'; 4 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/config-extras/minimizers/imagemin/index.ts: -------------------------------------------------------------------------------- 1 | export * from './imagemin'; 2 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/config-extras/minimizers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './imagemin'; 2 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/jest/babel-transform.js: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable-next-line 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | const babelJest = require('babel-jest'); 4 | const babelPresets = require('../babel-server'); 5 | 6 | module.exports = babelJest.createTransformer(babelPresets.default); 7 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/jest/css-mock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/jest/file-transform.js: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable-next-line 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return { code: `module.exports = ${JSON.stringify(path.basename(filename))};` }; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/mq.css: -------------------------------------------------------------------------------- 1 | @custom-media --small screen; 2 | @custom-media --small-only screen and (max-width: 47.9375em); 3 | @custom-media --medium screen and (min-width: 48em); 4 | @custom-media --medium-only screen and (min-width: 48em) and (max-width: 63.9375em); 5 | @custom-media --large screen and (min-width 64em); 6 | @custom-media --large-only screen and (min-width: 64em) and (max-width: 89.9375em); 7 | @custom-media --xlarge screen and (min-width 90em); 8 | @custom-media --xlarge-only screen and (min-width: 90em) and (max-width: 119.9375em); 9 | @custom-media --xxlarge screen and (min-width 120em); 10 | @custom-media --xxlarge-only screen and (min-width: 120em) and (max-width: 99999999em); 11 | @custom-media --mobile-s (min-width: 320px); 12 | @custom-media --mobile-m (min-width: 375px); 13 | @custom-media --mobile-l (min-width: 412px); 14 | @custom-media --mobile (max-width: 599px); 15 | @custom-media --tablet-s (min-width: 600px); 16 | @custom-media --tablet-m (min-width: 768px); 17 | @custom-media --tablet (min-width: 600px) and (max-width: 1023px); 18 | @custom-media --desktop-s (min-width: 1024px); 19 | @custom-media --desktop-m (min-width: 1280px); 20 | @custom-media --desktop-l (min-width: 1440px); 21 | @custom-media --desktop-xl (min-width: 1920px); 22 | @custom-media --desktop (min-width: 1024px); 23 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/postcss.ts: -------------------------------------------------------------------------------- 1 | import applyOverrides from './util/apply-overrides'; 2 | import { createPostcssConfig, postcssPlugins, postcssPluginsOptions } from './postcss.config'; 3 | 4 | const postcssConfig = applyOverrides( 5 | 'postcss', 6 | createPostcssConfig(postcssPlugins, postcssPluginsOptions), 7 | // тк дается возможность переопределять options для плагинов импортируемых напрямую 8 | // инициализировать их нужно после оверайдов 9 | ).map((plugin) => typeof plugin === 'string' || Array.isArray(plugin) 10 | ? plugin 11 | : plugin.plugin(plugin.options)); 12 | 13 | export default postcssConfig; 14 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/process-assets-plugin-output.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import { Assets } from 'assets-webpack-plugin'; 4 | 5 | import configs from './app-configs'; 6 | import { MODULES_ENTRY_NAME } from './modules'; 7 | 8 | export function processAssetsPluginOutput(assets: Assets) { 9 | const adjustedAssets = assets; 10 | 11 | Object.keys(adjustedAssets).forEach((key) => { 12 | // заменяем путь к файлам на корректный в случае если в нем есть 'auto/' 13 | adjustedAssets[key] = { 14 | css: replaceAutoPath(adjustedAssets[key].css) as any, 15 | js: replaceAutoPath(adjustedAssets[key].js) as any, 16 | }; 17 | }); 18 | 19 | // добавляем в манифест js-файлы для модулей 20 | Object.keys(configs.modules?.exposes || {}).forEach((moduleName) => { 21 | if (configs.compatModules?.exposes?.[moduleName]) { 22 | throw new Error( 23 | `Модуль ${moduleName} определен как module и как compat. Поменяйте название одного из модулей или удалите его`, 24 | ); 25 | } 26 | adjustedAssets[moduleName] = { 27 | mode: 'default', 28 | js: path.join(configs.publicPath, MODULES_ENTRY_NAME), 29 | }; 30 | }); 31 | 32 | const result = { 33 | ...adjustedAssets, 34 | __metadata__: { 35 | version: configs.version, 36 | name: configs.normalizedName, 37 | }, 38 | }; 39 | 40 | return JSON.stringify(result); 41 | } 42 | 43 | function replaceAutoPath(assets: string | string[] | undefined) { 44 | if (!assets) { 45 | return assets; 46 | } 47 | if (Array.isArray(assets)) { 48 | return assets.map((asset) => asset.replace(/^auto\//, configs.publicPath)); 49 | } 50 | 51 | return assets.replace(/^auto\//, configs.publicPath); 52 | } 53 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/server-externals-exemptions.ts: -------------------------------------------------------------------------------- 1 | import applyOverrides from './util/apply-overrides'; 2 | 3 | export const serverExternalsExemptions = applyOverrides('serverExternalsExemptions', [ 4 | /^arui-feather/, 5 | /^arui-ft-private/, 6 | /^arui-private/, 7 | /^alfaform-core-ui/, 8 | /^@alfa-bank\/newclick-composite-components/, 9 | /^#/, 10 | /^@alfalab\/icons/, 11 | /^@alfalab\/core-components/, 12 | /^date-fns/, 13 | /^@corp-front\//, 14 | /^corp-sign-ui-react/, 15 | /^@alfalab\/scripts-/, 16 | ]); 17 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/stats-options.ts: -------------------------------------------------------------------------------- 1 | import type { RspackOptionsNormalized } from '@rspack/core'; 2 | 3 | import applyOverrides from './util/apply-overrides'; 4 | 5 | const statsOptions: RspackOptionsNormalized['stats'] = applyOverrides('stats', { 6 | // Add asset Information 7 | assets: false, 8 | // Add information about cached (not built) modules 9 | cached: false, 10 | // Show cached assets (setting this to `false` only shows emitted files) 11 | cachedAssets: false, 12 | // Add children information 13 | children: false, 14 | // Add chunk information (setting this to `false` allows for a less verbose output) 15 | chunks: false, 16 | // Add built modules information to chunk information 17 | chunkModules: false, 18 | // Add the origins of chunks and chunk merging info 19 | chunkOrigins: false, 20 | // `webpack --colors` equivalent 21 | colors: true, 22 | // Add errors 23 | errors: true, 24 | // Add details to errors (like resolving log) 25 | errorDetails: true, 26 | // Add the hash of the compilation 27 | hash: false, 28 | // Add built modules information 29 | modules: true, 30 | // Show dependencies and origin of warnings/errors (since webpack 2.5.0) 31 | moduleTrace: true, 32 | // Show performance hint when file size exceeds `performance.maxAssetSize` 33 | performance: true, 34 | // Add public path information 35 | publicPath: false, 36 | // Add information about the reasons why modules are included 37 | reasons: false, 38 | // Add timing information 39 | timings: true, 40 | // Add webpack version information 41 | version: false, 42 | // Add warnings 43 | warnings: true, 44 | // ignore warning from missing types 45 | warningsFilter: /export .* was not found in/, 46 | }); 47 | 48 | export default statsOptions; 49 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/supporting-browsers.ts: -------------------------------------------------------------------------------- 1 | import applyOverrides from './util/apply-overrides'; 2 | 3 | const supportingBrowsers = applyOverrides( 4 | ['browsers', 'supportingBrowsers'], 5 | ['last 2 versions', 'not dead', 'Android >= 6', 'iOS >= 14'], 6 | ); 7 | 8 | export default supportingBrowsers; 9 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/swc.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from '@swc/core'; 2 | 3 | import applyOverrides from './util/apply-overrides'; 4 | import { configs } from './app-configs'; 5 | 6 | const swcConfig: Options = { 7 | env: { coreJs: '3', mode: 'entry' }, 8 | jsc: { 9 | parser: { 10 | syntax: 'typescript', 11 | tsx: true, 12 | decorators: true, 13 | }, 14 | loose: true, 15 | transform: { 16 | legacyDecorator: true, 17 | react: { 18 | runtime: 'automatic', 19 | }, 20 | }, 21 | experimental: configs.collectCoverage 22 | ? { 23 | plugins: [ 24 | ['swc-plugin-coverage-instrument', {}], 25 | ], 26 | } 27 | : {}, 28 | }, 29 | }; 30 | 31 | export const swcClientConfig = applyOverrides(['swc', 'swcClient'], swcConfig); 32 | 33 | export const swcServerConfig = applyOverrides(['swc', 'swcServer'], swcConfig); 34 | 35 | export const swcJestConfig = applyOverrides(['swc', 'swcJest'], swcConfig); 36 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/__tests__/get-polyfills.tests.ts: -------------------------------------------------------------------------------- 1 | import { AppConfigs } from '../../app-configs/types'; 2 | import { getPolyfills } from '../get-polyfills'; 3 | 4 | describe('getPolyfills', () => { 5 | it('should return array when original config contain string as polyfills entry', () => { 6 | const appConfig: Partial = { 7 | clientPolyfillsEntry: 'custom-polyfills', 8 | }; 9 | 10 | const polyfills = getPolyfills(appConfig as AppConfigs); 11 | 12 | expect(polyfills).toMatchObject(['custom-polyfills']); 13 | }); 14 | 15 | it('should return array when original config contains multiple polyfills entry', () => { 16 | const appConfig: Partial = { 17 | clientPolyfillsEntry: ['custom1', 'custom2'], 18 | }; 19 | 20 | const polyfills = getPolyfills(appConfig as AppConfigs); 21 | 22 | expect(polyfills).toMatchObject(['custom1', 'custom2']); 23 | }); 24 | 25 | it('should return empty array when original config is empty', () => { 26 | const appConfig: Partial = { 27 | clientPolyfillsEntry: null, 28 | }; 29 | 30 | const polyfills = getPolyfills(appConfig as AppConfigs); 31 | 32 | expect(polyfills).toMatchObject([]); 33 | }); 34 | 35 | it('should add feather polyfills when original config is empty and feather polyfills is available', () => { 36 | const appConfig: Partial = { 37 | clientPolyfillsEntry: null, 38 | }; 39 | const requireResolve = jest.fn(() => 'feather-polyfills-path'); 40 | 41 | const polyfills = getPolyfills(appConfig as AppConfigs, requireResolve as any); 42 | 43 | expect(polyfills).toMatchObject(['feather-polyfills-path']); 44 | expect(requireResolve).toHaveBeenCalledWith('arui-feather/polyfills'); 45 | }); 46 | 47 | it('should not add feather polyfills when original config has any polyfills', () => { 48 | const appConfig: Partial = { 49 | clientPolyfillsEntry: [], 50 | }; 51 | const requireResolve = jest.fn(() => 'feather-polyfills-path'); 52 | 53 | const polyfills = getPolyfills(appConfig as AppConfigs, requireResolve as any); 54 | 55 | expect(polyfills).toMatchObject([]); 56 | expect(requireResolve).not.toHaveBeenCalled(); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/check-node-version.ts: -------------------------------------------------------------------------------- 1 | function checkNodeVersion(majorVersion: number) { 2 | const actualVersion = process.versions.node.split('.'); 3 | 4 | return parseInt(actualVersion[0], 10) >= majorVersion; 5 | } 6 | 7 | export default checkNodeVersion; 8 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/find-loader.ts: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable and eslint-disable-next-line 2 | /* eslint-disable no-restricted-syntax */ 3 | import type { Configuration, RuleSetRule } from '@rspack/core'; 4 | 5 | export function findLoader( 6 | config: Configuration, 7 | testRule: string, 8 | ): RuleSetRule | undefined { 9 | for (const rule of config.module!.rules!) { 10 | if (rule === '...' || !rule) { 11 | // Webpack имеет странный тип для rules, который позволяет в него положить строку '...'. Успокаиваем TS 12 | // eslint-disable-next-line no-continue 13 | continue; 14 | } 15 | 16 | if (rule.test && rule.test.toString() === testRule) { 17 | return rule; 18 | } 19 | 20 | if (rule.oneOf) { 21 | for (const oneOfRule of rule.oneOf) { 22 | if (oneOfRule && oneOfRule.test && oneOfRule?.test?.toString() === testRule) { 23 | return oneOfRule; 24 | } 25 | } 26 | } 27 | } 28 | 29 | return undefined; 30 | } 31 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/get-entry.ts: -------------------------------------------------------------------------------- 1 | export type Entry = string | string[] | Record; 2 | 3 | /** 4 | * @param {string|string[]|object} entryPoint Строка, массив строк или объект с энтрипоинтами 5 | * @param {Function} getSingleEntry Функция, возвращающая конфигурацию для одного entryPoint 6 | * @param args Дополнительные аргументы, которые будут переданы в getSingleEntry функцию 7 | * @returns {*} 8 | */ 9 | function getEntry( 10 | entryPoint: Entry, 11 | getSingleEntry: (entry: string[], ...args: AdditionalArgs) => string[], 12 | ...args: AdditionalArgs 13 | ): string[] | Record { 14 | if (typeof entryPoint === 'string') { 15 | return getSingleEntry([entryPoint], ...args); 16 | } 17 | if (Array.isArray(entryPoint)) { 18 | return getSingleEntry(entryPoint, ...args); 19 | } 20 | 21 | // client entry also can be an object, so we must add hot loader to each entry point 22 | return Object.keys(entryPoint).reduce((result, entryPointName) => { 23 | const entry = getEntry(entryPoint[entryPointName], getSingleEntry, ...args); 24 | 25 | return { 26 | ...result, 27 | [entryPointName]: entry, 28 | }; 29 | }, {}); 30 | } 31 | 32 | export default getEntry; 33 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/get-polyfills.ts: -------------------------------------------------------------------------------- 1 | import { AppConfigs } from '../app-configs/types'; 2 | 3 | // require.resolve вынесен как параметр функции для того, чтоб это можно было протестировать. 4 | // на данный момент jest не дает возможности мокать require.resolve https://github.com/facebook/jest/issues/9543 5 | export function getPolyfills(config: AppConfigs, requireResolve = require.resolve) { 6 | const polyfills: string[] = []; 7 | 8 | if (config.clientPolyfillsEntry && Array.isArray(config.clientPolyfillsEntry)) { 9 | polyfills.push(...config.clientPolyfillsEntry); 10 | } else if (config.clientPolyfillsEntry) { 11 | polyfills.push(config.clientPolyfillsEntry); 12 | } else { 13 | // Для сохранения обратной совместимости нам надо добавлять полифилы из физера только тогда, когда пользователь 14 | // не задал свои кастомные полифилы. 15 | try { 16 | const aruiPolyfills = requireResolve('arui-feather/polyfills'); 17 | 18 | polyfills.push(aruiPolyfills); 19 | } catch (error) { 20 | // just ignore it 21 | } 22 | } 23 | 24 | return polyfills; 25 | } 26 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/get-webpack-cache-dependencies.ts: -------------------------------------------------------------------------------- 1 | import configs from '../app-configs'; 2 | import { getConfigFilePath } from '../app-configs/read-config-file'; 3 | import { getPresetsConfigPath } from '../app-configs/update-with-presets'; 4 | 5 | export function getWebpackCacheDependencies(): Record { 6 | return { 7 | overrides: configs.overridesPath, 8 | appConfigs: [ 9 | getConfigFilePath(configs.cwd), 10 | getPresetsConfigPath(configs, configs.cwd), 11 | ].filter(Boolean) as string[], 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/install-sourcemap-support.js: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable or add override to .eslintrc.js 2 | /* eslint-disable global-require */ 3 | /* eslint-disable import/no-extraneous-dependencies */ 4 | /* eslint-disable @typescript-eslint/no-var-requires */ 5 | try { 6 | require('source-map-support').install(); 7 | } catch (e) { 8 | console.error( 9 | 'unable to install source map support. Please add `source-map-support` as a dependency.', 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/node-assets-ignore.js: -------------------------------------------------------------------------------- 1 | // ignore attempts to require any types of assets 2 | (() => { 3 | // simply ignore css files, it wont cause any damage 4 | const ignoreExtensions = ['.css']; 5 | // warn about other requires, because it may lead to unexpected behaviour in production 6 | const warnExtensions = [ 7 | '.gif', 8 | '.jpeg', 9 | '.jpg', 10 | '.ico', 11 | '.png', 12 | '.xml', 13 | '.svg', 14 | '.mp4', 15 | '.webm', 16 | '.ogv', 17 | '.aac', 18 | '.mp3', 19 | '.wav', 20 | '.ogg', 21 | ]; 22 | const noop = () => {}; 23 | const warn = (_, path) => 24 | console.warn(`\u001B[0;31mWARNING! 25 | Trying to require ${path} in node.js. 26 | Non-js files is ignored when required in node_modules\u001B[0m`); 27 | 28 | ignoreExtensions.forEach((e) => { 29 | require.extensions[e] = noop; 30 | }); 31 | warnExtensions.forEach((e) => { 32 | require.extensions[e] = warn; 33 | }); 34 | })(); 35 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/noop.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/register-ts-node.ts: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | // Мы используем ts-node для работы c конфигами, описаными на ts 4 | require('ts-node').register({ 5 | transpileOnly: true, 6 | ignore: [], 7 | compilerOptions: { 8 | target: 'ES2016', 9 | module: 'CommonJS', 10 | skipLibCheck: true, 11 | allowJs: false, 12 | allowSyntheticDefaultImports: true, 13 | moduleResolution: 'node', 14 | esModuleInterop: true, 15 | }, 16 | skipProject: true, 17 | }); 18 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/util/resolve.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export function tryResolve(...args: Parameters) { 4 | try { 5 | return require.resolve(...args); 6 | } catch (e) { 7 | return undefined; 8 | } 9 | } 10 | 11 | /** 12 | * Возвращает полный путь до модуля из node_modules относительно базового пути. 13 | * Может быть полезно для определения пути до модуля, когда в проекте установлено сразу несколько модулей. 14 | * /project 15 | * --/node_modules 16 | * ----/module1 // через эту функцию мы получим этот модуль 17 | * ----/arui-scripts 18 | * ------/node_modules 19 | * --------/module1 // через require.resolve мы получим этот модуль 20 | */ 21 | export function resolveNodeModuleRelativeTo(basePath: string, moduleName: string) { 22 | return path.resolve(basePath, `node_modules/${moduleName}`); 23 | } 24 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/webpack.client.dev.ts: -------------------------------------------------------------------------------- 1 | import applyOverrides from './util/apply-overrides'; 2 | import { findLoader } from './util/find-loader'; 3 | import { findPlugin } from './util/find-plugin'; 4 | import { createClientWebpackConfig, createSingleClientWebpackConfig } from './webpack.client'; 5 | 6 | const config = applyOverrides( 7 | ['webpack', 'webpackClient', 'webpackDev', 'webpackClientDev'], 8 | createClientWebpackConfig('dev'), 9 | { 10 | createSingleClientWebpackConfig: createSingleClientWebpackConfig.bind(null, 'dev'), 11 | findLoader, 12 | findPlugin: findPlugin<'client'>(), 13 | }, 14 | ); 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/webpack.client.prod.ts: -------------------------------------------------------------------------------- 1 | import applyOverrides from './util/apply-overrides'; 2 | import { findLoader } from './util/find-loader'; 3 | import { findPlugin } from './util/find-plugin'; 4 | import { createClientWebpackConfig, createSingleClientWebpackConfig } from './webpack.client'; 5 | 6 | const config = applyOverrides( 7 | ['webpack', 'webpackClient', 'webpackProd', 'webpackClientProd'], 8 | createClientWebpackConfig('prod'), 9 | { 10 | createSingleClientWebpackConfig: createSingleClientWebpackConfig.bind(null, 'prod'), 11 | findLoader, 12 | findPlugin: findPlugin<'client'>(), 13 | }, 14 | ); 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/webpack.server.dev.ts: -------------------------------------------------------------------------------- 1 | import applyOverrides from './util/apply-overrides'; 2 | import { findLoader } from './util/find-loader'; 3 | import { findPlugin } from './util/find-plugin'; 4 | import { createServerConfig } from './webpack.server'; 5 | 6 | const config = applyOverrides( 7 | ['webpack', 'webpackServer', 'webpackDev', 'webpackServerDev'], 8 | createServerConfig('dev'), 9 | { findLoader, findPlugin: findPlugin<'server'>() }, 10 | ); 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/configs/webpack.server.prod.ts: -------------------------------------------------------------------------------- 1 | import applyOverrides from './util/apply-overrides'; 2 | import { findLoader } from './util/find-loader'; 3 | import { findPlugin } from './util/find-plugin'; 4 | import { createServerConfig } from './webpack.server'; 5 | 6 | const config = applyOverrides( 7 | ['webpack', 'webpackServer', 'webpackProd', 'webpackServerProd'], 8 | createServerConfig('prod'), 9 | { findLoader, findPlugin: findPlugin<'server'>() }, 10 | ); 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { OverrideFile } from './configs/util/apply-overrides'; 2 | 3 | export type { AppConfigs, PackageSettings } from './configs/app-configs/types'; 4 | export { prepareFilesForDocker } from './commands/util/docker-build'; 5 | export { getBuildParamsFromArgs } from './commands/util/docker-build'; 6 | export { getDockerBuildCommand } from './commands/util/docker-build'; 7 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/plugins/arui-runtime/arui-runtime-module.ts: -------------------------------------------------------------------------------- 1 | import { RuntimeGlobals, RuntimeModule as RsPackRuntimeModule,Template } from '@rspack/core'; 2 | 3 | export const ARUI_RUNTIME_VARIABLE_NAME = '$ARUI'; 4 | export const FULL_ARUI_RUNTIME_PATH = `${RuntimeGlobals.require}.${ARUI_RUNTIME_VARIABLE_NAME}`; 5 | 6 | export class RuntimeModule extends RsPackRuntimeModule { 7 | constructor() { 8 | super('AruiRuntimeModule', RsPackRuntimeModule.STAGE_BASIC); 9 | } 10 | 11 | // eslint-disable-next-line class-methods-use-this 12 | generate() { 13 | return Template.asString([ 14 | "if (typeof __webpack_modules__ !== 'undefined' && typeof document !== 'undefined') {", // По какой-то причине вебпак пытается выполнить этот код не только в браузере, но и при сборке. 15 | `${FULL_ARUI_RUNTIME_PATH} = { scriptSource: document.currentScript };`, 16 | '}', 17 | ]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/plugins/arui-runtime/index.ts: -------------------------------------------------------------------------------- 1 | import { Compiler, RuntimeGlobals } from '@rspack/core'; 2 | 3 | import { RuntimeModule } from './arui-runtime-module'; 4 | 5 | export class AruiRuntimePlugin { 6 | apply(compiler: Compiler) { 7 | compiler.hooks.compilation.tap( 8 | this.constructor.name, 9 | (compilation) => { 10 | compilation.hooks.runtimeRequirementInTree 11 | .for(RuntimeGlobals.require) 12 | .tap(this.constructor.name, (chunk) => { 13 | compilation.addRuntimeModule(chunk, new RuntimeModule()); 14 | 15 | return true; 16 | }); 17 | } 18 | ); 19 | } 20 | } 21 | 22 | /** 23 | * Метод для вставки runtime чанков css в документ. Нужен для того, чтобы модули могли использовать shadow-dom. 24 | * Функция, которую возвращает этот метод - НЕ прогоняется через babel, поэтому он должен быть написан на чистом js, 25 | * без использования синтаксиса, который не поддерживается браузерами. 26 | * Более того, мы не можем использовать импорты или require внутри, это так же сломает сборку. 27 | */ 28 | export function getInsertCssRuntimeMethod(): (linkTag: HTMLLinkElement) => void { 29 | /* eslint-disable */ 30 | return function insertCssRuntime(linkTag) { 31 | if (__webpack_require__ && __webpack_require__.$ARUI.scriptSource) { 32 | var scriptSource = __webpack_require__.$ARUI.scriptSource; 33 | var targetElementSelector = scriptSource.getAttribute('data-resources-target-selector'); 34 | if (targetElementSelector) { 35 | var targetElement = document.querySelector(targetElementSelector); 36 | if (targetElement) { 37 | if (targetElement.shadowRoot) { 38 | targetElement.shadowRoot.appendChild(linkTag); 39 | return; 40 | } 41 | targetElement.appendChild(linkTag); 42 | return; 43 | } 44 | } 45 | } 46 | document.head.appendChild(linkTag); 47 | } 48 | /* eslint-enable */ 49 | } 50 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/plugins/postcss-global-variables/postcss-global-variables.ts: -------------------------------------------------------------------------------- 1 | import type { AtRule, Plugin, PluginCreator, Rule } from 'postcss'; 2 | 3 | import { insertParsedCss, parseImport, parseMediaQuery, parseVariables } from './utils/utils'; 4 | 5 | type PluginOptions = { 6 | files?: string[]; 7 | }; 8 | 9 | const postCssGlobalVariables: PluginCreator = (opts?: PluginOptions) => { 10 | const options = { 11 | files: [], 12 | ...opts, 13 | }; 14 | 15 | const parsedVariables: Record = {}; 16 | const parsedCustomMedia: Record = {}; 17 | 18 | let rulesSelectors = new Set(); 19 | 20 | return { 21 | postcssPlugin: '@alfalab/postcss-global-variables', 22 | prepare(): Plugin { 23 | return { 24 | postcssPlugin: '@alfalab/postcss-global-variables', 25 | Once(root, postcssHelpers): void { 26 | if (!Object.keys(parsedVariables).length) { 27 | options.files.forEach((filePath) => { 28 | const importedCss = parseImport(root, postcssHelpers, filePath); 29 | 30 | parseVariables(importedCss, parsedVariables); 31 | parseMediaQuery(importedCss, parsedCustomMedia); 32 | }); 33 | } 34 | 35 | const rootRule = insertParsedCss(root, parsedVariables, parsedCustomMedia); 36 | 37 | root.append(rootRule); 38 | rulesSelectors.add(rootRule) 39 | }, 40 | OnceExit(): void { 41 | rulesSelectors.forEach((rule) => { 42 | rule.remove(); 43 | }); 44 | rulesSelectors = new Set(); 45 | }, 46 | }; 47 | }, 48 | }; 49 | }; 50 | 51 | postCssGlobalVariables.postcss = true; 52 | 53 | export { postCssGlobalVariables }; -------------------------------------------------------------------------------- /packages/arui-scripts/src/plugins/postcss-global-variables/utils/__tests__/get-media-query-name.tests.ts: -------------------------------------------------------------------------------- 1 | import type { AtRule } from 'postcss'; 2 | 3 | import { getMediaQueryName, } from '../utils'; 4 | 5 | describe('getMediaQueryName', () => { 6 | it('Должен возвращать имя медиа-запроса', () => { 7 | const rule: AtRule = { params: 'screen and (min-width: 768px)' } as AtRule; 8 | 9 | expect(getMediaQueryName(rule)).toBe('screen'); 10 | }); 11 | 12 | it('Должен возвращать пустую строку, если params пустой', () => { 13 | const rule: AtRule = { params: '' } as AtRule; 14 | 15 | expect(getMediaQueryName(rule)).toBe(''); 16 | }); 17 | }); -------------------------------------------------------------------------------- /packages/arui-scripts/src/plugins/postcss-global-variables/utils/__tests__/parse-variables.tests.ts: -------------------------------------------------------------------------------- 1 | import type { Declaration,Root } from 'postcss'; 2 | 3 | import { parseVariables } from '../utils'; 4 | 5 | describe('parseVariables', () => { 6 | it('Должен корректно заполнять объект переменными на основе импортируемого файла', () => { 7 | const parsedVariables: Record = {}; 8 | 9 | const mockImportedFile = { 10 | walkDecls: (callback: (decl: Declaration, index: number) => false | void) => { 11 | const mockDeclarations: Declaration[] = [ 12 | { prop: '--color-primary', value: '#3498db' } as Declaration, 13 | { prop: '--font-size', value: 'var(--gap-24)' } as Declaration, 14 | ]; 15 | 16 | mockDeclarations.forEach(callback); 17 | 18 | return false; 19 | } 20 | }; 21 | 22 | parseVariables(mockImportedFile as Root, parsedVariables); 23 | 24 | expect(parsedVariables).toEqual({ 25 | '--color-primary': '#3498db', 26 | '--font-size': 'var(--gap-24)', 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/plugins/reload-server-plugin.ts: -------------------------------------------------------------------------------- 1 | import cluster from 'cluster'; 2 | import path from 'path'; 3 | 4 | import { type Compiler } from 'webpack'; 5 | 6 | const defaultOptions = { 7 | script: 'server.js', 8 | }; 9 | 10 | export class ReloadServerPlugin { 11 | workers: cluster.Worker[] = []; 12 | 13 | done: null | (() => void) = null; 14 | 15 | constructor({ script } = defaultOptions) { 16 | this.done = null; 17 | this.workers = []; 18 | 19 | cluster.setupMaster({ 20 | exec: path.resolve(process.cwd(), script), 21 | }); 22 | 23 | cluster.on('online', (worker) => { 24 | this.workers.push(worker); 25 | 26 | if (this.done) { 27 | this.done(); 28 | } 29 | }); 30 | } 31 | 32 | apply(compiler: Compiler) { 33 | compiler.hooks.afterEmit.tapAsync('ReloadServerPlugin', (compilation, callback) => { 34 | this.done = callback; 35 | this.workers.forEach((worker) => { 36 | try { 37 | process.kill(worker.process.pid, 'SIGTERM'); 38 | } catch (e) { 39 | console.warn(`Unable to kill process #${worker.process.pid}`); 40 | } 41 | }); 42 | 43 | this.workers = []; 44 | 45 | cluster.fork(); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/plugins/watch-missing-node-modules-plugin.ts: -------------------------------------------------------------------------------- 1 | import { type Compiler } from 'webpack'; 2 | 3 | export class WatchMissingNodeModulesPlugin { 4 | nodeModulesPath: string; 5 | 6 | constructor(nodeModulesPath: string) { 7 | this.nodeModulesPath = nodeModulesPath; 8 | } 9 | 10 | apply(compiler: Compiler) { 11 | compiler.hooks.emit.tap('WatchMissingNodeModulesPlugin', (compilation) => { 12 | const missingDeps = Array.from(compilation.missingDependencies); 13 | const { nodeModulesPath } = this; 14 | 15 | // If any missing files are expected to appear in node_modules... 16 | if (missingDeps.some((file) => file.includes(nodeModulesPath))) { 17 | // ...tell webpack to watch node_modules recursively until they appear. 18 | compilation.contextDependencies.add(nodeModulesPath); 19 | } 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/templates/dockerfile-compiled.template.ts: -------------------------------------------------------------------------------- 1 | import { getInstallProductionCommand, getYarnVersion } from '../commands/util/yarn'; 2 | import configs from '../configs/app-configs'; 3 | import applyOverrides from '../configs/util/apply-overrides'; 4 | 5 | const installProductionCommand = getInstallProductionCommand(); 6 | const yarnVersion = getYarnVersion(); 7 | 8 | const { nginx } = configs; 9 | 10 | // В зависимости от используемого мендежера зависимостей для их установки нужно копировать разный набор файлов 11 | const filesRequiredToInstallDependencies = [ 12 | 'package.json', 13 | 'yarn.lock', 14 | yarnVersion === '2+' && '.yarnrc.yml', 15 | yarnVersion === '2+' && '.yarn', 16 | yarnVersion === 'unavailable' && 'package-lock.json', 17 | ].filter(Boolean); 18 | 19 | const dockerfileTemplate = ` 20 | FROM ${configs.baseDockerImage} 21 | ARG START_SH_LOCATION 22 | ARG NGINX_CONF_LOCATION 23 | ARG NGINX_BASE_CONF_LOCATION 24 | 25 | WORKDIR /src 26 | 27 | # Полу-статичные файлы, могут легко кешироваться 28 | ADD $START_SH_LOCATION /src/start.sh 29 | ADD $NGINX_CONF_LOCATION /src/nginx.conf 30 | ${nginx ? 'ADD $NGINX_BASE_CONF_LOCATION /etc/nginx/nginx.conf' : ''} 31 | 32 | # Зависимости. При некоторой удаче могут кешироваться и соответственно кешировать установку зависимостей 33 | ${filesRequiredToInstallDependencies 34 | .map((file) => `ADD --chown=nginx:nginx ${file} /src/${file}`) 35 | .join('\n')} 36 | 37 | RUN ${installProductionCommand} && \\ 38 | ${yarnVersion === 'unavailable' ? 'npm cache clean --force' : 'yarn cache clean'} 39 | 40 | ADD --chown=nginx:nginx . /src 41 | 42 | # Создаем директории для nginx и выставляем правильные права 43 | RUN mkdir -p /var/lib/nginx && \ 44 | chown -R nginx:nginx /var/lib/nginx && \ 45 | chown -R nginx:nginx /var/log/nginx && \ 46 | chown -R nginx:nginx /etc/nginx/conf.d 47 | RUN touch /var/run/nginx.pid && \ 48 | chown -R nginx:nginx /var/run/nginx.pid 49 | USER nginx 50 | `; 51 | 52 | export default applyOverrides('DockerfileCompiled', dockerfileTemplate); 53 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/templates/dockerfile.template.ts: -------------------------------------------------------------------------------- 1 | import { configs } from '../configs/app-configs'; 2 | import applyOverrides from '../configs/util/apply-overrides'; 3 | 4 | const appPathToAdd = configs.clientOnly ? configs.buildPath : '.'; 5 | const appTargetPath = configs.clientOnly ? `/src/${configs.buildPath}` : '/src'; 6 | const nginxConfTargetLocation = configs.clientOnly ? '/etc/nginx/conf.d/default.conf' : '/src/nginx.conf'; 7 | const { nginx } = configs; 8 | 9 | const nginxNonRootPart = configs.runFromNonRootUser 10 | ? `RUN chown -R nginx:nginx /src && \\ 11 | mkdir -p /var/lib/nginx && \\ 12 | chown -R nginx:nginx /var/lib/nginx && \\ 13 | chown -R nginx:nginx /var/log/nginx && \\ 14 | chown -R nginx:nginx /etc/nginx/conf.d 15 | 16 | RUN touch /var/run/nginx.pid && \\ 17 | chown -R nginx:nginx /var/run/nginx.pid 18 | 19 | USER nginx` 20 | : ''; 21 | 22 | const dockerfileTemplate = ` 23 | FROM ${configs.baseDockerImage} 24 | ARG START_SH_LOCATION 25 | ARG NGINX_CONF_LOCATION 26 | ARG NGINX_BASE_CONF_LOCATION 27 | 28 | WORKDIR /src 29 | ADD $START_SH_LOCATION /src/start.sh 30 | ADD $NGINX_CONF_LOCATION ${nginxConfTargetLocation} 31 | ${nginx ? 'ADD $NGINX_BASE_CONF_LOCATION /etc/nginx/nginx.conf' : ''} 32 | 33 | ${nginxNonRootPart} 34 | 35 | ${configs.runFromNonRootUser ? `ADD --chown=nginx:nginx ${appPathToAdd} ${appTargetPath}` : `ADD ${appPathToAdd} ${appTargetPath}`} 36 | ${configs.clientOnly ? 'COPY env-config.jso[n] /src/' : ''} 37 | ${configs.clientOnly ? 'CMD ["nginx"]' : ''} 38 | `; 39 | 40 | export default applyOverrides('Dockerfile', dockerfileTemplate); 41 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/templates/html.template.ts: -------------------------------------------------------------------------------- 1 | import applyOverrides from '../configs/util/apply-overrides'; 2 | 3 | const template = ` 4 | 5 | 6 | 7 | React-app 8 | 12 | 13 | 14 |
15 | 18 | 19 | `; 20 | 21 | export const htmlTemplate = applyOverrides('html', template); 22 | -------------------------------------------------------------------------------- /packages/arui-scripts/src/templates/nginx.conf.template.ts: -------------------------------------------------------------------------------- 1 | import { configs } from '../configs/app-configs'; 2 | import applyOverrides from '../configs/util/apply-overrides'; 3 | 4 | const nginxTemplate = `client_max_body_size 20m; 5 | 6 | server { 7 | listen ${configs.clientServerPort}; 8 | server_tokens off; 9 | 10 | ${configs.clientOnly 11 | ? `location / { 12 | root ${configs.nginxRootPath}/${configs.buildPath}; 13 | index index.html; 14 | }` 15 | : ` location / { 16 | proxy_set_header Host $host; 17 | proxy_pass http://127.0.0.1:${configs.serverPort}; 18 | }`} 19 | 20 | location /${configs.publicPath} { 21 | expires max; 22 | add_header Cache-Control public; 23 | root ${configs.nginxRootPath}/${configs.buildPath}; 24 | } 25 | 26 | location ~ /${configs.publicPath}.*\\.js$ { 27 | expires max; 28 | add_header Cache-Control public; 29 | root ${configs.nginxRootPath}/${configs.buildPath}; 30 | types { 31 | text/javascript js; 32 | } 33 | } 34 | }`; 35 | 36 | export default applyOverrides('nginx', nginxTemplate); 37 | -------------------------------------------------------------------------------- /packages/arui-scripts/stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('arui-presets-lint/stylelint'), 3 | rules: { 4 | 'selector-pseudo-class-no-unknown': [ 5 | true, 6 | { 7 | ignorePseudoClasses: ['global'], 8 | }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/arui-scripts/tsconfig-local.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2016", 5 | "module": "CommonJS", 6 | "skipLibCheck": true, 7 | "outDir": "./build", 8 | "allowJs": true, 9 | "declaration": true 10 | }, 11 | "include": [ 12 | "src/**/*.ts", 13 | "src/**/*.js" 14 | ], 15 | "exclude": ["build"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/arui-scripts/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | // для корректной работы @typescript-eslint/parser 4 | "include": ["src", "./*.js", "./*.ts", ".eslintrc.js"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/client-event-bus/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | .turbo -------------------------------------------------------------------------------- /packages/client-event-bus/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['custom/common'], 4 | parserOptions: { 5 | tsconfigRootDir: __dirname, 6 | project: [ 7 | './tsconfig.eslint.json', 8 | ], 9 | }, 10 | overrides: [ 11 | { 12 | files: ['**/__tests__/**/*.{ts,tsx}'], 13 | rules: { 14 | 'import/no-extraneous-dependencies': 'off', 15 | }, 16 | } 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /packages/client-event-bus/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | -------------------------------------------------------------------------------- /packages/client-event-bus/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | build/tsconfig.tsbuildinfo 3 | __tests__ 4 | .turbo 5 | coverage 6 | -------------------------------------------------------------------------------- /packages/client-event-bus/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @alfalab/client-event-bus 2 | 3 | ## 2.0.2 4 | 5 | ### Patch Changes 6 | 7 | - [#293](https://github.com/core-ds/arui-scripts/pull/293) [`1157100`](https://github.com/core-ds/arui-scripts/commit/115710068391b7977b7c5c134f537bb57d70ff4b) Thanks [@heymdall-legal](https://github.com/heymdall-legal)! - Исправлен тип для useEventBusValue, теперь он принимает то, что возвращает getEventBus без приведения типов 8 | 9 | ## 2.0.1 10 | 11 | ### Patch Changes 12 | 13 | - [#267](https://github.com/core-ds/arui-scripts/pull/267) [`ba36194`](https://github.com/core-ds/arui-scripts/commit/ba36194fb13fef3b5b424c3ae9068761e3600aa5) Thanks [@denis0ff](https://github.com/denis0ff)! - Обновлен README.md 14 | 15 | ## 2.0.0 16 | 17 | ### Major Changes 18 | 19 | - [#255](https://github.com/core-ds/arui-scripts/pull/255) [`9b99b3d`](https://github.com/core-ds/arui-scripts/commit/9b99b3d654d555200d32eb8d35aa9f6275633068) Thanks [@denis0ff](https://github.com/denis0ff)! - Добавлена библиотека для обмена данными через общую шину 20 | -------------------------------------------------------------------------------- /packages/client-event-bus/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'jsdom', 5 | testPathIgnorePatterns: [ 6 | '/node_modules/', 7 | '/build/', 8 | ], 9 | collectCoverageFrom: [ 10 | 'src/**/*.{ts,tsx}', 11 | '!src/**/*.d.ts', 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /packages/client-event-bus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alfalab/client-event-bus", 3 | "version": "2.0.2", 4 | "main": "./build/index.js", 5 | "module": "./build/esm/index.js", 6 | "typings": "./build/index.d.ts", 7 | "license": "MPL-2.0", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/core-ds/arui-scripts.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/core-ds/arui-scripts/issues" 14 | }, 15 | "homepage": "https://github.com/core-ds/arui-scripts/tree/master/packages/client-event-bus#readme", 16 | "scripts": { 17 | "build:commonjs": "tsc --project tsconfig.json", 18 | "build:esm": "tsc --project tsconfig.esm.json", 19 | "build": "yarn build:commonjs && yarn build:esm", 20 | "test": "jest", 21 | "lint:scripts": "eslint \"**/*.{js,jsx,ts,tsx}\" --ext .js,.jsx,.ts,.tsx", 22 | "lint": "yarn lint:scripts", 23 | "lint:fix": "yarn lint:scripts --fix", 24 | "format": "prettier --write $INIT_CWD/{config,src}/**/*.{ts,tsx,js,jsx,css}", 25 | "audit": "yarn npm audit --severity high --environment production" 26 | }, 27 | "peerDependencies": { 28 | "react": ">16.18.0" 29 | }, 30 | "devDependencies": { 31 | "@types/jest": "^23.3.14", 32 | "eslint": "^8.20.0", 33 | "eslint-config-custom": "workspace:*", 34 | "jest": "28.1.3", 35 | "prettier": "^2.7.1", 36 | "react": "18.2.0", 37 | "ts-jest": "28.0.8", 38 | "typescript": "4.9.5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/client-event-bus/src/custom-event.ts: -------------------------------------------------------------------------------- 1 | // CustomEvent не поддерживается нормально в ie, поэтому полифилим его прям тут 2 | function isNativeCustomEventAvailable() { 3 | try { 4 | const p = new global.CustomEvent('cat', { detail: { foo: 'bar' } }); 5 | 6 | return p.type === 'cat' && p.detail.foo === 'bar'; 7 | } catch (e) { 8 | // just ignore it 9 | } 10 | 11 | return false; 12 | } 13 | 14 | function CustomEventPolyfill(type: string, params: CustomEventInit) { 15 | const e = document.createEvent('CustomEvent'); 16 | 17 | if (params) { 18 | e.initCustomEvent(type, Boolean(params.bubbles), Boolean(params.cancelable), params.detail); 19 | } else { 20 | e.initCustomEvent(type, false, false, undefined); 21 | } 22 | 23 | return e; 24 | } 25 | 26 | export const CustomEvent = ( 27 | isNativeCustomEventAvailable() ? global.CustomEvent : CustomEventPolyfill 28 | ) as unknown as typeof global.CustomEvent; 29 | -------------------------------------------------------------------------------- /packages/client-event-bus/src/get-event-bus.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Возвращает event-bus для конкретной системы. Если event-bus в текущем контексте не доступен - вернет undefined 3 | * @param busKey ключ конкретной системы 4 | */ 5 | /* eslint-disable no-underscore-dangle */ 6 | export function getEventBus(busKey: string) { 7 | if (window.__alfa_event_buses) { 8 | return window.__alfa_event_buses[busKey]; 9 | } 10 | 11 | return null; 12 | } 13 | /* eslint-enable no-underscore-dangle */ 14 | -------------------------------------------------------------------------------- /packages/client-event-bus/src/index.ts: -------------------------------------------------------------------------------- 1 | import './types/types'; 2 | 3 | export { getEventBus } from './get-event-bus'; 4 | export { createBus, EventBus } from './implementation'; 5 | export { useEventBusValue } from './use-event-bus-value'; 6 | 7 | export type { AbstractAppEventBus, AbstractKnownEventTypes } from './types/abstract-types'; 8 | -------------------------------------------------------------------------------- /packages/client-event-bus/src/types/types.ts: -------------------------------------------------------------------------------- 1 | import type { AbstractAppEventBus, AbstractKnownEventTypes } from './abstract-types'; 2 | 3 | declare global { 4 | /* eslint-disable no-var,@typescript-eslint/naming-convention,no-underscore-dangle,vars-on-top */ 5 | var __alfa_event_buses: Record>; 6 | /* eslint-enable no-var,@typescript-eslint/naming-convention,no-underscore-dangle,vars-on-top */ 7 | 8 | type EventBusParams = { 9 | targetNode?: Node; 10 | debugMode?: boolean; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/client-event-bus/src/use-event-bus-value.ts: -------------------------------------------------------------------------------- 1 | import { useEffect,useState } from 'react'; 2 | 3 | import type { AbstractAppEventBus, AbstractKnownEventTypes } from './types/abstract-types'; 4 | 5 | export function useEventBusValue< 6 | EventTypes extends AbstractKnownEventTypes, 7 | Event extends keyof EventTypes, 8 | >( 9 | eventBus: AbstractAppEventBus | undefined | null, 10 | eventName: Event, 11 | ): EventTypes[Event] | undefined { 12 | const [lastValue, setLastValue] = useState(eventBus?.getLastEventDetail?.(eventName)); 13 | 14 | useEffect(() => { 15 | const eventHandler = (event: CustomEvent) => setLastValue(event.detail); 16 | 17 | eventBus?.addEventListener?.(eventName, eventHandler); 18 | 19 | return () => { 20 | eventBus?.removeEventListener?.(eventName, eventHandler); 21 | }; 22 | }, [eventBus, eventName, setLastValue]); 23 | 24 | return lastValue; 25 | } 26 | -------------------------------------------------------------------------------- /packages/client-event-bus/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src", "./*.js", "./*.ts", ".eslintrc.js"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/client-event-bus/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "outDir": "build/esm", 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/client-event-bus/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../arui-scripts/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2016", 5 | "module": "commonjs", 6 | "skipLibCheck": true, 7 | "outDir": "./build", 8 | "declaration": true 9 | }, 10 | "include": [ 11 | "src/**/*.ts", 12 | "global-definitions.d.ts" 13 | ], 14 | "exclude": ["build"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/common.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const project = resolve(process.cwd(), 'tsconfig.eslint.json'); 4 | 5 | module.exports = { 6 | extends: [require.resolve('arui-presets-lint/eslint')], 7 | parserOptions: { 8 | tsconfigRootDir: __dirname, 9 | project: [project], 10 | }, 11 | overrides: [ 12 | { 13 | files: [ 14 | './arui-scripts.config.ts', 15 | './arui-scripts.overrides.ts', 16 | './global-definitions.d.ts', 17 | ], 18 | rules: { 19 | 'import/no-default-export': 'off', 20 | }, 21 | }, 22 | ], 23 | rules: { 24 | 'no-trailing-spaces': 'error', 25 | 'no-multiple-empty-lines': 'error', 26 | 'react-hooks/exhaustive-deps': 'warn', 27 | 'import/no-extraneous-dependencies': [ 28 | 'error', 29 | { 30 | devDependencies: ['cypress/**/*.ts', '**/*.test.{ts,tsx,js,jsx}'], 31 | }, 32 | ], 33 | 'import/no-default-export': 'error', 34 | indent: 'off', // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/indent.md 35 | }, 36 | ignorePatterns: ['coverage'], 37 | }; 38 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-custom", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "devDependencies": { 7 | "@typescript-eslint/eslint-plugin": "^5.30.6", 8 | "@typescript-eslint/parser": "^5.30.6", 9 | "arui-presets-lint": "6.1.0", 10 | "eslint": "^8.20.0", 11 | "eslint-config-airbnb": "^19.0.4", 12 | "eslint-config-airbnb-typescript": "^17.0.0", 13 | "eslint-config-prettier": "^8.5.0", 14 | "eslint-import-resolver-typescript": "~3.1.5", 15 | "eslint-plugin-cypress": "^2.12.1", 16 | "eslint-plugin-dirnames": "^1.0.3", 17 | "eslint-plugin-import": "^2.26.0", 18 | "eslint-plugin-jsx-a11y": "^6.6.1", 19 | "eslint-plugin-react": "^7.30.1", 20 | "eslint-plugin-react-hooks": "^4.6.0", 21 | "eslint-plugin-simple-import-sort": "^7.0.0", 22 | "eslint-plugin-turbo": "latest", 23 | "eslint-plugin-unicorn": "^42.0.0", 24 | "prettier": "^2.7.1", 25 | "stylelint": "^14.9.1", 26 | "stylelint-config-prettier": "^9.0.3" 27 | }, 28 | "prettier": "arui-presets-lint/prettier", 29 | "eslintConfig": { 30 | "extends": "./node_modules/arui-presets-lint/eslint" 31 | }, 32 | "stylelint": { 33 | "extends": "arui-presets-lint/stylelint" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/example-modules/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['custom/common'], 4 | parserOptions: { 5 | tsconfigRootDir: __dirname, 6 | project: [ 7 | './tsconfig.eslint.json' 8 | ], 9 | }, 10 | }; -------------------------------------------------------------------------------- /packages/example-modules/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .build 3 | build.tar 4 | .cache-loader 5 | -------------------------------------------------------------------------------- /packages/example-modules/README.md: -------------------------------------------------------------------------------- 1 | # example-modules 2 | 3 | Репозиторий с примерами реализации модулей для arui-scripts. 4 | -------------------------------------------------------------------------------- /packages/example-modules/arui-scripts.config.ts: -------------------------------------------------------------------------------- 1 | import { PackageSettings } from 'arui-scripts'; 2 | 3 | const aruiScriptsConfig: PackageSettings = { 4 | presets: './presets', 5 | serverPort: 3001, 6 | clientServerPort: 8082, 7 | devServerCors: true, 8 | clientPolyfillsEntry: null, 9 | serverEntry: './src/server/index', 10 | clientEntry: './src/client', 11 | componentsTheme: '../../node_modules/@alfalab/core-components/themes/corp.css', 12 | keepCssVars: false, 13 | codeLoader: 'swc', 14 | jestCodeTransformer: 'swc', 15 | debug: true, 16 | compatModules: { 17 | shared: { 18 | react: 'react', 19 | 'react-dom': 'reactDOM', 20 | }, 21 | exposes: { 22 | 'ModuleCompat': { 23 | entry: './src/modules/module-compat/index', 24 | }, 25 | 'FactoryModuleCompat': { 26 | entry: './src/modules/factory-module-compat/index', 27 | }, 28 | 'ServerStateModuleCompat': { 29 | entry: './src/modules/server-state-module-compat/index', 30 | externals: { 31 | react: 'react', 32 | 'react-dom': 'reactDOM', 33 | } 34 | }, 35 | 'ModuleAbstractCompat': { 36 | entry: './src/modules/module-abstract/index', 37 | } 38 | } 39 | }, 40 | modules: { 41 | shared: { 42 | 'react': '^17.0.0', 43 | 'react-dom': '^17.0.0', 44 | }, 45 | exposes: { 46 | 'Module': './src/modules/module/index', 47 | 'ServerStateFactoryModule': './src/modules/server-state-factory-module/index', 48 | 'ServerStateModule': './src/modules/server-state-module/index', 49 | 'ModuleAbstract': './src/modules/module-abstract/index', 50 | } 51 | } 52 | } 53 | 54 | export default aruiScriptsConfig; 55 | -------------------------------------------------------------------------------- /packages/example-modules/global-definitions.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png' { 2 | const url: string; 3 | export default url; 4 | } 5 | 6 | declare module '*.svg' { 7 | const url: string; 8 | export default url; 9 | } 10 | 11 | declare module '*.module.css' { 12 | const styles: Record; 13 | export default styles; 14 | } 15 | -------------------------------------------------------------------------------- /packages/example-modules/jest.config.validate-build.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | module.exports = { 3 | testRegex: '.*\\.spec\\.ts$', 4 | transform: { 5 | '^.+\\.tsx?$': require.resolve('ts-jest'), 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /packages/example-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-modules", 3 | "version": "1.1.61", 4 | "private": true, 5 | "scripts": { 6 | "build": "arui-scripts build", 7 | "build:app": "arui-scripts build", 8 | "build:docker": "arui-scripts docker-build", 9 | "start": "NODE_ENV=localhost arui-scripts start", 10 | "start:prod": "NODE_ENV=localhost arui-scripts start:prod", 11 | "validate": "jest \"validate-build/*\" --config jest.config.validate-build.js", 12 | "lint:css": "stylelint ./src/**/*.css", 13 | "lint:scripts": "eslint \"**/*.{js,jsx,ts,tsx}\" --ext .js,.jsx,.ts,.tsx", 14 | "lint": "yarn lint:css && yarn lint:scripts", 15 | "lint:fix": "yarn lint:css --fix && yarn lint:scripts --fix", 16 | "format": "prettier --write $INIT_CWD/{config,src}/**/*.{ts,tsx,js,jsx,css}" 17 | }, 18 | "jest": { 19 | "preset": "arui-scripts" 20 | }, 21 | "dependencies": { 22 | "@alfalab/scripts-modules": "workspace:*", 23 | "@alfalab/scripts-server": "workspace:*", 24 | "arui-scripts": "workspace:*", 25 | "body-parser": "^1.20.3", 26 | "express": "^4.20.0", 27 | "lodash": "^4.17.21", 28 | "react": "17.0.2", 29 | "react-dom": "17.0.2" 30 | }, 31 | "devDependencies": { 32 | "@types/enzyme": "^3.10.13", 33 | "@types/express": "^4.17.17", 34 | "@types/jest": "^23.3.14", 35 | "@types/lodash": "^4.14.197", 36 | "@types/node": "^14.18.54", 37 | "@types/react": "17.0.64", 38 | "@types/react-dom": "17.0.20", 39 | "@types/webpack-env": "^1.18.1", 40 | "enzyme": "3.11.0", 41 | "enzyme-adapter-react-16": "^1.15.4", 42 | "eslint": "^8.20.0", 43 | "eslint-config-custom": "workspace:*", 44 | "jest": "28.1.3", 45 | "prettier": "^2.7.1", 46 | "stylelint": "^14.9.1", 47 | "ts-jest": "28.0.8", 48 | "typescript": "^4.9.5" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/example-modules/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable-next-line 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | import { App } from './components/app'; 6 | 7 | const targetElement = document.getElementById('app'); 8 | 9 | if (process.env.NODE_ENV !== 'production' && module.hot) { 10 | ReactDOM.render(, targetElement); 11 | 12 | module.hot.accept('./components/app', () => { 13 | // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires 14 | const NextAppAssignments = require('./components/app').App; 15 | 16 | ReactDOM.render(, targetElement); 17 | }); 18 | } else { 19 | ReactDOM.render(, targetElement); 20 | } 21 | -------------------------------------------------------------------------------- /packages/example-modules/src/client.tsx: -------------------------------------------------------------------------------- 1 | import('./bootstrap'); 2 | -------------------------------------------------------------------------------- /packages/example-modules/src/components/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const App = () => ( 4 |
5 |

Example-modules app

6 | 7 |

It's just an empty application that doesn't do anything.

8 |
9 | ); 10 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/factory-module-compat/index.ts: -------------------------------------------------------------------------------- 1 | import type { FactoryModule, WindowWithModule } from '@alfalab/scripts-modules'; 2 | 3 | const factory: FactoryModule = (runParams, moduleState) => ({ 4 | someData: 'Some data here', 5 | saySomething: () => alert('something'), 6 | runParams, 7 | ...moduleState, 8 | }); 9 | 10 | (window as WindowWithModule).FactoryModuleCompat = factory; 11 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/module-abstract/index.ts: -------------------------------------------------------------------------------- 1 | import { WindowWithModule } from '@alfalab/scripts-modules'; 2 | 3 | /** 4 | * Этот модуль является "абстрактным", то есть он не имеет какой-либо заданной структуры. Он может содержать любые 5 | * функции, переменные, классы, интерфейсы, типы и т.д. 6 | */ 7 | 8 | export function justSomeRandomFunctionThatWeWantToExport() { 9 | return 'hello'; 10 | } 11 | 12 | export const someRandomVariableThatWeWantToExport = 'hello'; 13 | 14 | (window as WindowWithModule).ModuleAbstract = { 15 | justSomeRandomFunctionThatWeWantToExport, 16 | someRandomVariableThatWeWantToExport, 17 | }; 18 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/module-compat/compat-module.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PostcssFeatures } from '#/shared/postcss-features'; 4 | 5 | export const CompatModule = () => ( 6 |
7 | Это модуль ModuleCompat, который был загружен в режиме compat. 8 | 9 |
10 | У виджета могут быть свои стили, которые автоматически будут изолированы от других стилей на странице. 11 | Единственное условие - виджет сам должен добавлять class="module-ID виджета" к корневому элементу. 12 | 13 |
14 | Этот текст должен быть синего цвета 15 |
16 |
17 |
18 | 19 | 20 |
21 | ) 22 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/module-compat/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | 4 | import type { WindowWithModule } from '@alfalab/scripts-modules'; 5 | import { MountableModule } from '@alfalab/scripts-modules/src/module-loader/module-types'; 6 | 7 | import { CompatModule } from '#/modules/module-compat/compat-module'; 8 | 9 | import './styles.css'; 10 | 11 | type ModuleType = MountableModule>; 12 | 13 | const ModuleCompat: ModuleType = { 14 | mount: (targetNode, runParams) => { 15 | console.log('ModuleCompat: mount', { runParams }, targetNode); 16 | 17 | render(, targetNode); 18 | }, 19 | unmount: (targetNode) => { 20 | console.log('ModuleCompat: cleanup'); 21 | 22 | unmountComponentAtNode(targetNode); 23 | }, 24 | }; 25 | 26 | (window as WindowWithModule).ModuleCompat = ModuleCompat; 27 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/module-compat/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-primary: red; 3 | --gap: 2px; 4 | } 5 | .primary { 6 | color: color-mod(var(--color-primary) a(0.5)); 7 | margin-top: calc(var(--gap) * 2); 8 | 9 | &__footer { 10 | color: blue; 11 | } 12 | 13 | @media (min-width: 1024px) { 14 | color: coral; 15 | } 16 | 17 | &:hover &__footer { 18 | color: green; 19 | animation: someRandomAnimation 1s ease-in-out infinite alternate; 20 | } 21 | 22 | &.leftBorder { 23 | @media (--desktop-m) { 24 | &:before { 25 | border-right: 1px solid var(--color-light-border-underline); 26 | } 27 | } 28 | } 29 | } 30 | 31 | @keyframes someRandomAnimation { 32 | 0% { 33 | transform: scaleX(0); 34 | } 35 | 36 | 100% { 37 | transform: scaleX(1); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/module/example-module.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PostcssFeatures } from '#/shared/postcss-features'; 4 | 5 | import './styles.css'; 6 | 7 | export const Module = () => ( 8 |
9 |

Модуль, загруженный через module-federation

10 | 11 |

12 | Виджеты никак не ограничены в использовании каких либо библиотек. В этом примере мы 13 | используем react для рендринга виджета. 14 |

15 | 16 |

Проверка стилей через shadow-dom

17 |
18 | ); 19 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/module/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules'; 5 | 6 | import { Module } from './example-module'; 7 | 8 | export const mount: ModuleMountFunction = (targetNode, runParams, serverState) => { 9 | console.log('Module: mount', { runParams, serverState }); 10 | if (!targetNode) { 11 | throw new Error('Target node is not defined for module'); 12 | } 13 | 14 | ReactDOM.render(, targetNode); 15 | }; 16 | export const unmount: ModuleUnmountFunction = (targetNode) => { 17 | console.log('Module: unmount'); 18 | if (!targetNode) { 19 | return; 20 | } 21 | 22 | ReactDOM.unmountComponentAtNode(targetNode); 23 | }; 24 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/module/styles.css: -------------------------------------------------------------------------------- 1 | .module-shadow-dom-style { 2 | color: red; 3 | 4 | &:after { 5 | content: 'Content from shadow dom'; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/server-state-factory-module/index.ts: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable 2 | /* eslint-disable no-restricted-globals */ 3 | /* eslint-disable import/no-default-export */ 4 | import type { FactoryModule } from '@alfalab/scripts-modules'; 5 | 6 | const factory: FactoryModule = (runParams, moduleState) => ({ 7 | someData: 'Some data here', 8 | reloadPage: () => location.reload(), 9 | runParams, 10 | ...moduleState, 11 | }); 12 | 13 | export default factory; 14 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/server-state-module-compat/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import type { ModuleMountFunction, ModuleUnmountFunction, WindowWithModule } from '@alfalab/scripts-modules'; 5 | 6 | import { ServerStateModuleCompat } from '#/modules/server-state-module-compat/server-state-module-compat'; 7 | 8 | const mountModule: ModuleMountFunction> = (targetNode, runParams, serverState) => { 9 | console.log('ServerStateModuleCompat: mount', { runParams, serverState }); 10 | 11 | ReactDOM.render( 12 | , 13 | targetNode, 14 | ); 15 | }; 16 | 17 | const unmountModule: ModuleUnmountFunction = (targetNode) => { 18 | console.log('ServerStateModuleCompat: cleanup'); 19 | 20 | if (targetNode) { 21 | ReactDOM.unmountComponentAtNode(targetNode); 22 | } 23 | }; 24 | 25 | (window as WindowWithModule).ServerStateModuleCompat = { 26 | mount: mountModule, 27 | unmount: unmountModule, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/server-state-module-compat/server-state-module-compat.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const ServerStateModuleCompat = (props: { runParams: Record; serverState: Record }) => ( 4 |
5 | Это модуль ServerStateModuleCompat, который был загружен в режиме compat. Главное его 6 | отличие от ModuleCompat - это то, что при его загрузке на страницу, он сразу же получает 7 | данные с сервера. 8 |
9 | Например сейчаc виджет получил данные: 10 |
{JSON.stringify(props.serverState, null, 4)}
11 |

Данные, полученные из клиента:

12 |
{JSON.stringify(props.runParams, null, 4)}
13 |
14 |
15 | ); 16 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/server-state-module/index.tsx: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable 2 | /* eslint-disable import/no-default-export */ 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules'; 7 | 8 | import { ServerStateModule } from './server-state-module'; 9 | 10 | const mount: ModuleMountFunction> = (targetNode, runParams, serverState) => { 11 | console.log('ServerStateModule: mount', { runParams, serverState }); 12 | ReactDOM.render( 13 | , 14 | targetNode, 15 | ); 16 | }; 17 | const unmount: ModuleUnmountFunction = (targetNode) => { 18 | console.log('ServerStateModule: unmount'); 19 | 20 | ReactDOM.unmountComponentAtNode(targetNode); 21 | }; 22 | 23 | export default { 24 | mount, 25 | unmount, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/server-state-module/server-state-module.module.css: -------------------------------------------------------------------------------- 1 | .redText { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /packages/example-modules/src/modules/server-state-module/server-state-module.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import styles from './server-state-module.module.css'; 4 | 5 | export const ServerStateModule = (props: { runParams: Record; serverState: Record }) => ( 6 |
7 |

ServerStateModule

8 | 9 |

Данные, полученные из сервера:

10 | 11 |
{JSON.stringify(props.serverState, null, 4)}
12 | 13 |

Данные, полученные из клиента:

14 | 15 |
{JSON.stringify(props.runParams, null, 4)}
16 |
17 | ); 18 | -------------------------------------------------------------------------------- /packages/example-modules/src/server/read-assets.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | export function readAssetsManifest() { 5 | const manifestPath = path.join(process.cwd(), '.build/webpack-assets.json'); 6 | const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); 7 | const js: string[] = []; 8 | const css: string[] = []; 9 | 10 | ['vendor', 'main'].forEach((key) => { 11 | if (!manifest[key]) { 12 | return; 13 | } 14 | if (manifest[key].js) { 15 | js.push(manifest[key].js.replace(/^auto/, 'assets')); 16 | } 17 | if (manifest[key].css) { 18 | css.push(manifest[key].css.replace(/^auto/, 'assets')); 19 | } 20 | }); 21 | 22 | return { 23 | js, 24 | css, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/example-modules/src/shared/postcss-features/index.ts: -------------------------------------------------------------------------------- 1 | export * from './postcss-features'; 2 | -------------------------------------------------------------------------------- /packages/example-modules/src/shared/postcss-features/postcss-features.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './styles.css'; 4 | import styles from './styles.module.css'; 5 | 6 | export const PostcssFeatures = () => ( 7 |
8 | postcss features check: 9 |

module.css paragraph

10 |

native css paragraph

11 |
12 | ); 13 | -------------------------------------------------------------------------------- /packages/example-modules/src/shared/postcss-features/styles.css: -------------------------------------------------------------------------------- 1 | .module-postcss-features { 2 | margin-block: var(--gap-xl); 3 | 4 | &__paragraph { 5 | @mixin accent_caps; 6 | 7 | padding-inline-start: var(--gap-s); 8 | padding-block: var(--gap-3xs); 9 | box-shadow: var(--shadow-xs-hard-up); 10 | color: color-mod(var(--color-static-status-red) blend(rgba(255, 255, 255, 0.5) 50%)); 11 | background-color: var(--color-static-graphic-milano-red); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/example-modules/src/shared/postcss-features/styles.module.css: -------------------------------------------------------------------------------- 1 | .paragraph { 2 | @mixin accent_caps; 3 | 4 | padding-inline-start: var(--gap-s); 5 | padding-block: var(--gap-3xs); 6 | box-shadow: var(--shadow-xs-hard-up); 7 | color: color-mod(var(--color-static-status-red) blend(rgba(255, 255, 255, 0.5) 50%)); 8 | background-color: var(--color-static-graphic-milano-red); 9 | } 10 | -------------------------------------------------------------------------------- /packages/example-modules/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | // для корректной работы @typescript-eslint/parser 4 | "include": ["src", "./*.js", "./*.ts", "./validate-build/*.spec.ts", ".eslintrc.js", "bin"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/example-modules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "arui-scripts/tsconfig.json", 3 | "include": [ 4 | "global-definitions.d.ts", 5 | "src/**/*.ts", 6 | "src/**/*.tsx" 7 | ], 8 | "compilerOptions": { 9 | "baseUrl": "./", 10 | "paths": { 11 | "#/*": ["src/*"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/example-modules/validate-build/__snapshots__/build.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`modules should create valid css for ModuleCompat 1`] = ` 4 | ".module-ModuleCompat .module-postcss-features{ 5 | margin-block:24px; 6 | } 7 | 8 | .module-ModuleCompat .module-postcss-features__paragraph{ 9 | font-size:12px; 10 | line-height:16px; 11 | font-weight:700; 12 | letter-spacing:1.25px; 13 | text-transform:uppercase; 14 | 15 | padding-inline-start:12px; 16 | padding-block:2px; 17 | box-shadow:0 0 4px rgba(0, 0, 0, 0.02), 0 -2px 4px rgba(0, 0, 0, 0.04), 0 -2px 4px rgba(0, 0, 0, 0.16); 18 | color:rgb(236, 142, 133); 19 | background-color:#cd1501; 20 | } 21 | 22 | .module-ModuleCompat .styles-module-paragraph-UdpKs{ 23 | font-size:12px; 24 | line-height:16px; 25 | font-weight:700; 26 | letter-spacing:1.25px; 27 | text-transform:uppercase; 28 | 29 | padding-inline-start:12px; 30 | padding-block:2px; 31 | box-shadow:0 0 4px rgba(0, 0, 0, 0.02), 0 -2px 4px rgba(0, 0, 0, 0.04), 0 -2px 4px rgba(0, 0, 0, 0.16); 32 | color:rgb(236, 142, 133); 33 | background-color:#cd1501; 34 | } 35 | 36 | .module-ModuleCompat .primary{ 37 | color:rgba(255, 0, 0, 0.5); 38 | margin-top:4px; 39 | } 40 | 41 | .module-ModuleCompat .primary__footer{ 42 | color:blue; 43 | } 44 | 45 | @media (min-width: 1024px){.module-ModuleCompat .primary{ 46 | color:coral 47 | } 48 | } 49 | 50 | .module-ModuleCompat .primary:hover .primary__footer{ 51 | color:green; 52 | animation:someRandomAnimation 1s ease-in-out infinite alternate; 53 | } 54 | 55 | @media (min-width: 1280px){ 56 | .module-ModuleCompat .primary.leftBorder:before{ 57 | border-right:1px solid #c5c5c7; 58 | } 59 | } 60 | @keyframes someRandomAnimation{ 61 | 0%{ 62 | transform:scaleX(0); 63 | } 64 | 65 | 100%{ 66 | transform:scaleX(1); 67 | } 68 | } 69 | 70 | " 71 | `; 72 | -------------------------------------------------------------------------------- /packages/example-modules/validate-build/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "arui-scripts/tsconfig.json", 3 | "include": [ 4 | "**/*.ts" 5 | ], 6 | "compilerOptions": { 7 | "baseUrl": "./", 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['custom/common'], 4 | parserOptions: { 5 | tsconfigRootDir: __dirname, 6 | project: ['./tsconfig.eslint.json', './validate-build/tsconfig.json'], 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .build 3 | build.tar 4 | .cache-loader 5 | -------------------------------------------------------------------------------- /packages/example/README.md: -------------------------------------------------------------------------------- 1 | # arui-scripts-test 2 | 3 | Пакет для тестирования работы `arui-scripts`. Позволяет протестировать сборку, дев режим и другие команды 4 | arui-scripts. 5 | -------------------------------------------------------------------------------- /packages/example/__tests__/setup.js: -------------------------------------------------------------------------------- 1 | // setup file 2 | import { configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | configure({ adapter: new Adapter() }); 6 | -------------------------------------------------------------------------------- /packages/example/arui-scripts.config.ts: -------------------------------------------------------------------------------- 1 | import { PackageSettings } from 'arui-scripts'; 2 | 3 | const aruiScriptsConfig: PackageSettings = { 4 | presets: './presets', 5 | clientPolyfillsEntry: './src/polyfills.js', 6 | serverEntry: './src/server/index', 7 | clientEntry: './src/client', 8 | componentsTheme: '../../node_modules/@alfalab/core-components/themes/corp.css', 9 | keepCssVars: false, 10 | debug: true, 11 | codeLoader: 'swc', 12 | jestCodeTransformer: 'swc', 13 | compatModules: { 14 | shared: { 15 | 'react': 'react', 16 | 'react-dom': 'reactDOM', 17 | } 18 | }, 19 | modules: { 20 | shared: { 21 | 'react': { 22 | eager: true, 23 | requiredVersion: '^17.0.0', 24 | }, 25 | 'react-dom': { 26 | eager: true, 27 | requiredVersion: '^17.0.0', 28 | } 29 | } 30 | } 31 | } 32 | 33 | export default aruiScriptsConfig; 34 | -------------------------------------------------------------------------------- /packages/example/global-definitions.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png' { 2 | const url: string; 3 | export default url; 4 | } 5 | 6 | declare module '*.svg' { 7 | const url: string; 8 | export default url; 9 | } 10 | 11 | declare module '*.module.css' { 12 | const styles: Record; 13 | export default styles; 14 | } 15 | -------------------------------------------------------------------------------- /packages/example/jest.config.validate-build.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | module.exports = { 3 | testRegex: '.*\\.spec\\.ts$', 4 | transform: { 5 | '^.+\\.tsx?$': require.resolve('ts-jest'), 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /packages/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.1.61", 4 | "private": true, 5 | "scripts": { 6 | "build:app": "arui-scripts build", 7 | "build:docker": "arui-scripts docker-build", 8 | "bundle-analyze": "arui-scripts bundle-analyze", 9 | "start": "NODE_ENV=localhost arui-scripts start", 10 | "test": "arui-scripts test", 11 | "lint:css": "stylelint ./src/**/*.css", 12 | "lint:scripts": "eslint \"**/*.{js,jsx,ts,tsx}\" --ext .js,.jsx,.ts,.tsx", 13 | "lint": "yarn lint:css && yarn lint:scripts", 14 | "lint:fix": "yarn lint:css --fix && yarn lint:scripts --fix", 15 | "format": "prettier --write $INIT_CWD/{config,src}/**/*.{ts,tsx,js,jsx,css}", 16 | "validate": "jest \"validate-build/*\" --config jest.config.validate-build.js" 17 | }, 18 | "jest": { 19 | "preset": "arui-scripts", 20 | "setupFiles": [ 21 | "/__tests__/setup.js" 22 | ] 23 | }, 24 | "dependencies": { 25 | "@alfalab/core-components": "^42.1.0", 26 | "@alfalab/scripts-modules": "workspace:*", 27 | "@alfalab/scripts-server": "workspace:*", 28 | "@hapi/hapi": "^21.3.2", 29 | "arui-scripts": "workspace:*", 30 | "enzyme": "3.11.0", 31 | "enzyme-adapter-react-16": "^1.15.4", 32 | "express": "^4.20.0", 33 | "lodash": "^4.17.21", 34 | "react": "17.0.2", 35 | "react-dom": "17.0.2" 36 | }, 37 | "devDependencies": { 38 | "@types/enzyme": "^3.10.13", 39 | "@types/express": "^4.17.17", 40 | "@types/jest": "^23.3.14", 41 | "@types/lodash": "^4.14.197", 42 | "@types/node": "^14.18.54", 43 | "@types/react": "17.0.64", 44 | "@types/react-dom": "17.0.20", 45 | "@types/webpack-env": "^1.18.1", 46 | "eslint": "^8.20.0", 47 | "eslint-config-custom": "workspace:*", 48 | "jest": "28.1.3", 49 | "postcss-preset-env": "^8.4.1", 50 | "prettier": "^2.7.1", 51 | "stylelint": "^14.9.1", 52 | "ts-jest": "28.0.8", 53 | "typescript": "^4.9.5" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/example/src/__tests__/__snapshots__/app.tests.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`app should be match to snapshot 1`] = `ShallowWrapper {}`; 4 | -------------------------------------------------------------------------------- /packages/example/src/__tests__/app.tests.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import { App } from '#/components/app'; 5 | 6 | describe('app', () => { 7 | it('should be match to snapshot', () => { 8 | expect(shallow()).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/example/src/__tests__/server.tests.ts: -------------------------------------------------------------------------------- 1 | import hapi from '@hapi/hapi'; 2 | 3 | describe('server side tests', () => { 4 | let server: hapi.Server; 5 | 6 | beforeEach(() => { 7 | server = hapi.server({ 8 | port: 4137, 9 | host: 'localhost', 10 | }); 11 | }); 12 | 13 | afterEach(() => { 14 | server.stop(); 15 | }); 16 | 17 | it('should work', () => { 18 | expect(1 + 1).toBe(2); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/example/src/__tests__/utils.tests.ts: -------------------------------------------------------------------------------- 1 | import { isSmaller, YesNoEnum } from '../utils'; 2 | 3 | describe('isSmaller', () => { 4 | it('should return no if a not greater than b', () => { 5 | const result = isSmaller(1, 2); 6 | 7 | expect(result).toBe(YesNoEnum.No); 8 | }); 9 | 10 | it('should return yes if a > b', () => { 11 | const result = isSmaller(2, 1); 12 | 13 | expect(result).toBe(YesNoEnum.Yes); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/example/src/client.tsx: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable-next-line 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | import { App } from './components/app'; 6 | 7 | const targetElement = document.getElementById('react-app'); 8 | 9 | if (process.env.NODE_ENV !== 'production' && module.hot) { 10 | ReactDOM.render(, targetElement); 11 | 12 | module.hot.accept('./components/app', () => { 13 | // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires 14 | const NextAppAssignments = require('./components/app').App; 15 | 16 | ReactDOM.render(, targetElement); 17 | }); 18 | } else { 19 | ReactDOM.render(, targetElement); 20 | } 21 | 22 | if ('serviceWorker' in navigator) { 23 | navigator.serviceWorker.register('/assets/worker.js'); 24 | } 25 | -------------------------------------------------------------------------------- /packages/example/src/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Created with Sketch. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/example/src/components/app.module.css: -------------------------------------------------------------------------------- 1 | @import '~@alfalab/core-components/vars/index.css'; 2 | 3 | .root { 4 | padding: 10px; 5 | } 6 | @media (--tablet) { 7 | .root { 8 | padding: var(--gap-xl); 9 | } 10 | } 11 | 12 | @media (--desktop) { 13 | .root { 14 | padding: var(--gap-2xl); 15 | } 16 | } 17 | 18 | @media (--mobile) { 19 | .root { 20 | padding: var(--gap-xs); 21 | } 22 | } 23 | 24 | .title { 25 | color: var(--button-accent-base-bg-color); 26 | border-radius: var(--border-radius-pill); 27 | } 28 | 29 | .inlineSvgIcon { 30 | width: 22px; 31 | height: 22px; 32 | background: url("./clock.svg") no-repeat; 33 | } 34 | -------------------------------------------------------------------------------- /packages/example/src/components/client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/core-ds/arui-scripts/828352ef3505ddc8ba41daac9dd9747645fbe03a/packages/example/src/components/client.png -------------------------------------------------------------------------------- /packages/example/src/components/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/example/src/components/postcss-features/index.ts: -------------------------------------------------------------------------------- 1 | export * from './postcss-features'; 2 | -------------------------------------------------------------------------------- /packages/example/src/components/postcss-features/postcss-features.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './styles.css'; 4 | import styles from './styles.module.css'; 5 | 6 | export const PostcssFeatures = () => ( 7 |
8 | postcss features check: 9 |

module.css paragraph

10 |

native css paragraph

11 |

red text for not desktop rule

12 |

red text for desktop or mobile rule

13 |

red text for desktop-m rule

14 |
15 | ); 16 | -------------------------------------------------------------------------------- /packages/example/src/components/postcss-features/styles.css: -------------------------------------------------------------------------------- 1 | .postcss-features { 2 | margin-block: var(--gap-xl); 3 | 4 | &__paragraph { 5 | @mixin accent_caps; 6 | 7 | padding-inline-start: var(--gap-s); 8 | padding-block: var(--gap-3xs); 9 | box-shadow: var(--shadow-xs-hard-up); 10 | color: color-mod(var(--color-static-status-red) blend(rgba(255, 255, 255, 0.5) 50%)); 11 | background-color: var(--color-static-graphic-milano-red); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/example/src/components/postcss-features/styles.module.css: -------------------------------------------------------------------------------- 1 | .paragraph { 2 | @mixin accent_caps; 3 | 4 | padding-inline-start: var(--gap-s); 5 | padding-block: var(--gap-3xs); 6 | box-shadow: var(--shadow-xs-hard-up); 7 | color: color-mod(var(--color-static-status-red) blend(rgba(255, 255, 255, 0.5) 50%)); 8 | background-color: var(--color-static-graphic-milano-red); 9 | } 10 | 11 | @media not screen and (--desktop) { 12 | .mediaTest { 13 | color: red; 14 | } 15 | } 16 | 17 | @media (--desktop), (--mobile) { 18 | .mediaTest2 { 19 | color: red; 20 | } 21 | } 22 | 23 | @media (--desktop-m) { 24 | .mediaTest3 { 25 | color: red; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/example/src/components/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-red: #f00; 3 | } 4 | body { 5 | background: aliceblue; 6 | } 7 | 8 | .btn { 9 | border-radius: 5px; 10 | border: 0; 11 | margin: 10px; 12 | font-size: 20px; 13 | padding: 15px; 14 | color: var(--color-red); 15 | background: linear-gradient(345deg, #aacac2, #3929bb); 16 | background-size: 400% 400%; 17 | animation: Insanity 2s ease infinite; 18 | } 19 | 20 | @keyframes Insanity { 21 | 0% { 22 | background-position: 0 50%; 23 | } 24 | 50% { 25 | background-position: 100% 50%; 26 | } 27 | 100% { 28 | background-position: 0 50%; 29 | } 30 | } 31 | 32 | .icon { 33 | padding-left: 5px; 34 | vertical-align: middle; 35 | } 36 | -------------------------------------------------------------------------------- /packages/example/src/module-mounters/abstract-module.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Spinner } from '@alfalab/core-components/spinner'; 4 | import { Underlay } from '@alfalab/core-components/underlay'; 5 | import { createModuleFetcher, createModuleLoader, useModuleLoader } from '@alfalab/scripts-modules'; 6 | 7 | type ModuleType = { 8 | justSomeRandomFunctionThatWeWantToExport: () => void; 9 | someRandomVariableThatWeWantToExport: string; 10 | } 11 | 12 | const loader = createModuleLoader({ 13 | hostAppId: 'example', 14 | moduleId: 'ModuleAbstract', 15 | getModuleResources: createModuleFetcher({ 16 | baseUrl: 'http://localhost:8082', 17 | }), 18 | }); 19 | 20 | export const AbstractModule = () => { 21 | const { loadingState, module } = useModuleLoader({ loader }); 22 | 23 | return ( 24 | 31 | {loadingState === 'pending' && } 32 | {loadingState === 'rejected' &&
Failed to load module
} 33 | 34 | {module && ( 35 |
36 |                     {JSON.stringify(
37 |                         module,
38 |                         (key, value) =>
39 |                             typeof value === 'function' ? `[Function ${value.name}]` : value,
40 |                         2,
41 |                     )}
42 |                 
43 | )} 44 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /packages/example/src/module-mounters/compat-module-mounter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Spinner } from '@alfalab/core-components/spinner'; 4 | import { Underlay } from '@alfalab/core-components/underlay'; 5 | import { 6 | createModuleFetcher, 7 | createModuleLoader, 8 | MountableModule, 9 | useModuleMounter, 10 | } from '@alfalab/scripts-modules'; 11 | 12 | const loader = createModuleLoader({ 13 | hostAppId: 'example', 14 | moduleId: 'ModuleCompat', 15 | getModuleResources: createModuleFetcher({ 16 | baseUrl: 'http://localhost:8082', 17 | }), 18 | }); 19 | 20 | export const CompatModuleMounter = () => { 21 | const { loadingState, targetElementRef } = useModuleMounter({ 22 | loader, 23 | useShadowDom: true, 24 | }); 25 | 26 | return ( 27 | 34 | {loadingState === 'pending' && } 35 | {loadingState === 'rejected' &&
Failed to load module
} 36 | 37 |
38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /packages/example/src/module-mounters/factory-compat-module-mounter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Button } from '@alfalab/core-components/button'; 4 | import { Spinner } from '@alfalab/core-components/spinner'; 5 | import { Typography } from '@alfalab/core-components/typography'; 6 | import { Underlay } from '@alfalab/core-components/underlay'; 7 | import { 8 | createModuleFetcher, 9 | createModuleLoader, 10 | FactoryModule, 11 | useModuleFactory, 12 | } from '@alfalab/scripts-modules'; 13 | 14 | const loader = createModuleLoader({ 15 | hostAppId: 'example', 16 | moduleId: 'FactoryModuleCompat', 17 | getModuleResources: createModuleFetcher({ 18 | baseUrl: 'http://localhost:8082', 19 | }), 20 | }); 21 | 22 | export const FactoryCompatModuleMaunter = () => { 23 | const { loadingState, module } = useModuleFactory({ loader }); 24 | 25 | return ( 26 | 33 |
34 | {loadingState === 'pending' && } 35 | {loadingState === 'rejected' &&
Failed to load module
} 36 | 37 | {module && ( 38 |
39 |                         {JSON.stringify(
40 |                             module,
41 |                             (key, value) =>
42 |                                 typeof value === 'function' ? `[Function ${value.name}]` : value,
43 |                             2,
44 |                         )}
45 |                     
46 | )} 47 | 48 | 49 | Снизу кнопка, на которую навешена функция из модуля 50 | 51 | 52 |
53 |
54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /packages/example/src/module-mounters/module-mounter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Spinner } from '@alfalab/core-components/spinner'; 4 | import { Underlay } from '@alfalab/core-components/underlay'; 5 | import { 6 | BaseModuleState, 7 | createModuleFetcher, 8 | createModuleLoader, 9 | MountableModule, 10 | useModuleMounter, 11 | } from '@alfalab/scripts-modules'; 12 | 13 | const loader = createModuleLoader>({ 14 | hostAppId: 'example', 15 | moduleId: 'Module', 16 | getModuleResources: createModuleFetcher({ 17 | baseUrl: 'http://localhost:8082', 18 | }), 19 | }); 20 | 21 | export const ModuleMounter = () => { 22 | const { loadingState, targetElementRef } = useModuleMounter({ 23 | loader, 24 | useShadowDom: true, 25 | }); 26 | 27 | return ( 28 | 29 |
30 | К этому элементу не должны примениться стили из модуля 31 |
32 | 33 | 40 | { loadingState === 'pending' && } 41 | { loadingState === 'rejected' &&
Failed to load module
} 42 | 43 |
44 | 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /packages/example/src/module-mounters/server-state-compat-module-mounter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Spinner } from '@alfalab/core-components/spinner'; 4 | import { Underlay } from '@alfalab/core-components/underlay'; 5 | import { 6 | createModuleLoader, 7 | createServerStateModuleFetcher, 8 | MountableModule, 9 | useModuleMounter, 10 | } from '@alfalab/scripts-modules'; 11 | 12 | type ModuleRunParams = { 13 | test: string; 14 | } 15 | 16 | const loader = createModuleLoader, { something: string }>({ 17 | hostAppId: 'example', 18 | moduleId: 'ServerStateModuleCompat', 19 | getModuleResources: createServerStateModuleFetcher({ baseUrl: 'http://localhost:8082' }), 20 | }); 21 | 22 | export const ServerStateCompatModuleMounter = () => { 23 | const { loadingState, targetElementRef } = useModuleMounter({ 24 | loader, 25 | runParams: { test: 'test' }, 26 | loaderParams: { something: 'foo' }, 27 | }); 28 | 29 | return ( 30 | 37 | {loadingState === 'pending' && } 38 | {loadingState === 'rejected' &&
Failed to load module
} 39 | 40 |
41 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /packages/example/src/module-mounters/server-state-module-mounter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Spinner } from '@alfalab/core-components/spinner'; 4 | import { Underlay } from '@alfalab/core-components/underlay'; 5 | import { 6 | BaseModuleState, 7 | createModuleLoader, 8 | createServerStateModuleFetcher, 9 | MountableModule, 10 | useModuleMounter, 11 | } from '@alfalab/scripts-modules'; 12 | 13 | const loader = createModuleLoader>({ 14 | hostAppId: 'example', 15 | moduleId: 'ServerStateModule', 16 | getModuleResources: createServerStateModuleFetcher({ baseUrl: 'http://localhost:8082' }), 17 | resourcesCache: 'single-item', 18 | }); 19 | 20 | export const ServerStateModuleMounter = () => { 21 | const { loadingState, targetElementRef } = useModuleMounter({ 22 | loader, 23 | runParams: { some: 'anything that you want' }, 24 | }); 25 | 26 | return ( 27 | 34 | {loadingState === 'pending' && } 35 | {loadingState === 'rejected' &&
Failed to load module
} 36 | 37 |
38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /packages/example/src/polyfills.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/core-ds/arui-scripts/828352ef3505ddc8ba41daac9dd9747645fbe03a/packages/example/src/polyfills.js -------------------------------------------------------------------------------- /packages/example/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import express from 'express'; 4 | 5 | import { readAssetsManifest } from '@alfalab/scripts-server'; 6 | 7 | import icon from './server.png'; 8 | 9 | const app = express(); 10 | 11 | app.use('/assets', express.static(path.join(process.cwd(), '.build', 'assets'))); 12 | 13 | app.get('/', async (req, res) => { 14 | const assets = await readAssetsManifest(); 15 | 16 | const response = ` 17 | 18 | 19 | 20 | ${assets.css.map((c) => ``).join('')} 21 | 22 | 23 |
24 | 25 |

Картинка с сервера:

26 | Картика с сервера 27 | ${assets.js.map((c) => ``).join('')} 28 | 29 | 30 | `; 31 | 32 | res.send(response); 33 | }); 34 | 35 | app.listen(3000, () => { 36 | console.log('Test server is listening on :3000'); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/example/src/server/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/core-ds/arui-scripts/828352ef3505ddc8ba41daac9dd9747645fbe03a/packages/example/src/server/server.png -------------------------------------------------------------------------------- /packages/example/src/utils.ts: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable 2 | /* eslint-disable no-param-reassign */ 3 | /* eslint-disable operator-assignment */ 4 | export enum YesNoEnum { 5 | Yes = 'Yes', 6 | No = 'No', 7 | } 8 | 9 | export function isSmaller(a: number, b: number) { 10 | a = a * 10_000; 11 | b = b * 10_000; 12 | 13 | return a > b ? YesNoEnum.Yes : YesNoEnum.No; 14 | } 15 | 16 | type OptionalChainingTest = { 17 | foo?: { 18 | bar?: string; 19 | }; 20 | }; 21 | 22 | export function testOptionalChaining(smth: OptionalChainingTest) { 23 | return smth?.foo?.bar; 24 | } 25 | 26 | export class SomethingWithPrivateFields { 27 | #name = 'really private'; 28 | 29 | getName() { 30 | return this.#name; 31 | } 32 | } 33 | 34 | export function withNullishCoalescing(something: null | string) { 35 | return something ?? 'other value'; 36 | } 37 | 38 | export const constObject = { 39 | name: 'someString', 40 | } as const; 41 | -------------------------------------------------------------------------------- /packages/example/src/worker.ts: -------------------------------------------------------------------------------- 1 | // TODO: remove eslint-disable 2 | /* eslint-disable no-console */ 3 | /* eslint-disable no-restricted-globals */ 4 | // This code executes in its own worker or thread 5 | self.addEventListener('install', () => { 6 | console.log('Service worker installed'); 7 | }); 8 | self.addEventListener('activate', () => { 9 | console.log('Service worker activated'); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/example/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | // для корректной работы @typescript-eslint/parser 4 | "include": ["src", "__tests__", "./*.js", "./*.ts", ".eslintrc.js", "bin"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "arui-scripts/tsconfig.json", 3 | "include": [ 4 | "global-definitions.d.ts", 5 | "src/**/*.ts", 6 | "src/**/*.tsx", 7 | "__tests__/*" 8 | ], 9 | "compilerOptions": { 10 | "baseUrl": "./", 11 | "paths": { 12 | "#/*": ["src/*"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/example/validate-build/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "arui-scripts/tsconfig.json", 3 | "include": [ 4 | "**/*.ts" 5 | ], 6 | "compilerOptions": { 7 | "baseUrl": "./", 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "outputs": ["build/**"], 6 | "dependsOn": ["^build"] 7 | }, 8 | "build:app": { 9 | "outputs": [".build/**"], 10 | "dependsOn": ["^build"] 11 | }, 12 | "test": { 13 | "dependsOn": ["^build"] 14 | }, 15 | "lint": {}, 16 | "validate": { 17 | "dependsOn": ["build:app"] 18 | }, 19 | "audit": {} 20 | } 21 | } 22 | --------------------------------------------------------------------------------