├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ └── config.yml └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .prettierrc ├── .yarn └── releases │ └── yarn-berry.js ├── .yarnrc.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTING.zh-CN.md ├── LICENSE ├── README.md ├── README.zh-CN.md ├── examples ├── component-a │ ├── .gitignore │ ├── package.json │ ├── src │ │ └── index.tsx │ └── tsconfig.json ├── rspack-react │ ├── .gitignore │ ├── components │ │ └── mime-component │ │ │ └── index.jsx │ ├── package.json │ ├── public │ │ └── index.html │ ├── rspack.config.js │ └── src │ │ ├── App.jsx │ │ └── main.jsx └── webpack-react │ ├── .babelrc │ ├── components │ └── mime-component │ │ └── index.jsx │ ├── examples-pnpm │ └── webpack-react │ │ ├── .babelrc │ │ ├── package.json │ │ ├── pnpm-lock.yaml │ │ ├── public │ │ └── index.html │ │ ├── src │ │ ├── App.jsx │ │ └── main.jsx │ │ └── webpack.config.js │ ├── package.json │ ├── public │ └── index.html │ ├── src │ ├── App.jsx │ └── main.jsx │ └── webpack.config.js ├── lerna.json ├── package.json ├── packages ├── plugin-vite-react │ ├── .gitignore │ ├── CHANGELOG.md │ ├── CHANGLOG.zh-CN.md │ ├── README.md │ ├── README.zh-CN.md │ ├── package.json │ ├── src │ │ ├── arco-design-plugin │ │ │ ├── config.ts │ │ │ ├── icon.ts │ │ │ ├── index.ts │ │ │ ├── less.ts │ │ │ ├── transform.ts │ │ │ ├── typings.d.ts │ │ │ └── utils.ts │ │ └── index.ts │ └── tsconfig.json ├── plugin-vite-vue │ ├── .gitignore │ ├── CHANGELOG.md │ ├── CHANGLOG.zh-CN.md │ ├── README.md │ ├── README.zh-CN.md │ ├── package.json │ ├── src │ │ ├── arco-design-plugin │ │ │ ├── config.ts │ │ │ ├── icon.ts │ │ │ ├── index.ts │ │ │ ├── less.ts │ │ │ ├── transform.ts │ │ │ ├── typings.d.ts │ │ │ └── utils.ts │ │ └── index.ts │ └── tsconfig.json ├── plugin-webpack-react │ ├── .gitignore │ ├── CHANGELOG.md │ ├── CHANGELOG.zh-CN.md │ ├── README.md │ ├── README.zh-CN.md │ ├── package.json │ ├── src │ │ ├── arco-design-plugin │ │ │ ├── config │ │ │ │ ├── babel.config.ts │ │ │ │ ├── index.ts │ │ │ │ └── matchers.ts │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── loaders │ │ │ │ ├── append.ts │ │ │ │ ├── remove-font-face.ts │ │ │ │ ├── replace-default-language.ts │ │ │ │ ├── replace-icon.ts │ │ │ │ └── transform-import.ts │ │ │ ├── plugin-for-import.ts │ │ │ ├── plugin-for-remove-font-face.ts │ │ │ ├── plugin-for-replace-default-language.ts │ │ │ ├── plugin-for-replace-icon.ts │ │ │ ├── plugin-for-theme.ts │ │ │ └── utils │ │ │ │ ├── index.ts │ │ │ │ └── transform-import.ts │ │ ├── global.d.ts │ │ └── index.ts │ └── tsconfig.json ├── unplugin-react │ ├── .eslintrc.json │ ├── .gitignore │ ├── CHANGELOG.md │ ├── CHANGELOG.zh-CN.md │ ├── README.md │ ├── README.zh-CN.md │ ├── package.json │ ├── src │ │ ├── config │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── loaders │ │ │ ├── append.ts │ │ │ ├── remove-font-face.ts │ │ │ ├── replace-default-language.ts │ │ │ └── replace-icon.ts │ │ ├── plugins │ │ │ ├── import.ts │ │ │ ├── remove-font-face.ts │ │ │ ├── replace-default-language.ts │ │ │ ├── replace-icon.ts │ │ │ └── theme.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── index.ts │ │ │ └── theme.ts │ └── tsconfig.json └── utils │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ ├── arrify.ts │ ├── index.ts │ ├── pathUtils.ts │ └── print.ts │ └── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | bin 4 | *.json 5 | *.md 6 | *.config 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "plugin:@typescript-eslint/recommended", 5 | "airbnb-base", 6 | "plugin:prettier/recommended" 7 | ], 8 | "env": { 9 | "node": true 10 | }, 11 | "rules": { 12 | "@typescript-eslint/no-var-requires": 0, 13 | "@typescript-eslint/no-unused-vars": ["error"], 14 | "import/extensions": 0, 15 | "import/no-unresolved": 0, 16 | "import/prefer-default-export": 0, 17 | "consistent-return": 0, 18 | "no-plusplus": 0, 19 | "no-continue": 0, 20 | "no-param-reassign": 0, 21 | "lines-between-class-members": 0, 22 | "class-methods-use-this": 0, 23 | "no-underscore-dangle": 0, 24 | "no-useless-catch": 0, 25 | "prefer-destructuring": 0, 26 | "func-names": 0 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Create new issue 4 | url: https://arco.design/issue-helper?repo=arco-design-pro 5 | about: Please use the following link to create a new issue. 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | ## Types of changes 10 | 11 | 12 | - [ ] New feature 13 | - [ ] Bug fix 14 | - [ ] Documentation change 15 | - [ ] Coding style change 16 | - [ ] Refactoring 17 | - [ ] Performance improvement 18 | - [ ] Test cases 19 | - [ ] Continuous integration 20 | - [ ] Typescript definition change 21 | - [ ] Breaking change 22 | 23 | ## Background and context 24 | 25 | 26 | 27 | 28 | ## Solution 29 | 30 | 31 | 32 | ## How is the change tested? 33 | 34 | 35 | 36 | 37 | ## Changelog 38 | 39 | | Changelog(CN) | Changelog(EN) | Related issues | 40 | | ------------- | ------------- | -------------- | 41 | 42 | ## Checklist: 43 | 44 | - [ ] Provide changelog for relevant changes (e.g. bug fixes and new features) if applicable. 45 | - [ ] Changes are submitted to the appropriate branch (e.g. features should be submitted to `feature` branch and others should be submitted to `master` branch) 46 | 47 | ## Other information 48 | 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # ide 4 | .DS_Store 5 | .idea 6 | .vscode 7 | 8 | # yarn v2 9 | .yarn/* 10 | # !.yarn/cache 11 | !.yarn/patches 12 | !.yarn/plugins 13 | !.yarn/releases 14 | !.yarn/sdks 15 | !.yarn/versions 16 | 17 | lerna-debug.log 18 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "semi": true, 4 | "singleQuote": true, 5 | "jsxBracketSameLine": false, 6 | "jsxSingleQuote": false, 7 | "printWidth": 100, 8 | "useTabs": false, 9 | "tabWidth": 2, 10 | "trailingComma": "es5" 11 | } 12 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | npmRegistryServer: "https://registry.npmjs.org/" 4 | 5 | yarnPath: .yarn/releases/yarn-berry.js 6 | 7 | checksumBehavior: "update" 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at pengjiyuan@bytendance.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, 44 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 45 | 46 | For answers to common questions about this code of conduct, see 47 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | > English | [简体中文](./CONTRIBUTING.zh-CN.md) 3 | # Contributing 4 | 5 | Thank you for taking your time to contribute and make this project better! Here are some guidelines to help you get started. Please make sure to take a moment and read through them before submitting your contributions. 6 | 7 | ## Code of Conduct 8 | 9 | This project is governed by the [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md). By participating, you are expected to adhere to it. 10 | 11 | ## Open Development 12 | 13 | All work happens directly on GitHub. Both core team members and external contributors send pull requests which go through the same review process. 14 | 15 | ## Semantic Versioning 16 | 17 | This project follows semantic versioning. We release patch versions for bug fixes or other changes that do not change the behavior of the API, minor versions for new features that are backward-compatible, and major versions for any breaking changes. 18 | 19 | Every significant change is documented in the changelog file. 20 | 21 | ## Reporting Issues 22 | 23 | We use [Github issues](https://github.com/arco-design/arco-plugins/issues) for bug reports and feature requests. Before reporting an issue, please make sure you have searched for similar [issues](https://github.com/arco-design/arco-plugins/issues) as they may have been already answered or being fixed. A new issue should be submitted via [issue helper](https://arco.design/issue-helper?repo=arco-plugins). For bug reporting, please include the minimum code that can be used to reproduce the problem. For feature request, please specify what changes you want and what behavior you expect. 24 | 25 | ## Sending a pull request 26 | 27 | 1. Fork [the repository](https://github.com/arco-design/arco-plugins) and create your branch from `main`. For new feature, please submit your changes directly to the `feature` branch. Other changes should go against `main` branch. 28 | 2. Use `npm install -g` to install basic packages such as `lerna` and `yarn`. 29 | 3. Use `yarn install` to install the dependencies of each package in `workspaces` (If you encounter a `YN0018` error, you can use `YARN_CHECKSUM_BEHAVIOR=update yarn` to install). 30 | 4. Make changes to the codebase. Please add tests if applicable. 31 | 5. Commit your changes, adhering to the [Commit Guidelines](#commit-guidelines) 32 | 6. Open a new pull request, [referencing corresponding issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) if available. 33 | 34 | ## Commit Guidelines 35 | 36 | Commit messages are required to follow the [conventional-changelog standard](https://www.conventionalcommits.org/en/v1.0.0/): 37 | 38 | ```bash 39 | [optional scope]: 40 | 41 | [optional body] 42 | 43 | [optional footer(s)] 44 | ``` 45 | 46 | ### Commit types 47 | 48 | The following is a list of commit types: 49 | 50 | - feat: A new feature or functionality 51 | - fix: A bug fix 52 | - docs: Documentation only changes 53 | - style: Code formatting or component style changes 54 | - refactor: Code changes that neither fixes a bug nor adds a feature. 55 | - perf: Improve performance. 56 | - test: Add missing or correct existing tests. 57 | - chore: Other commits that don’t modify src or test files. 58 | 59 | ## License 60 | 61 | By contributing your code to the repository, you agree to license your contribution under the [MIT license](./LICENSE). 62 | -------------------------------------------------------------------------------- /CONTRIBUTING.zh-CN.md: -------------------------------------------------------------------------------- 1 | 2 | > [English](./CONTRIBUTING.md) | 简体中文 3 | 4 | # 贡献指南 5 | 6 | 感谢你的宝贵时间。你的贡献将使这个项目变得更好!在提交贡献之前,请务必花点时间阅读下面的入门指南。 7 | 8 | ## 行为准则 9 | 10 | 该项目有一份 [行为准则](./CODE_OF_CONDUCT.md),希望参与项目的贡献者都能严格遵守。 11 | 12 | ## 透明的开发 13 | 14 | 所有工作都直接透明地在 GitHub 上进行。核心团队成员和外部贡献者的 pull requests 都需要经过相同的 review 流程。 15 | 16 | ## 语义化版本 17 | 18 | 该项目遵循语义化版本。我们对重要的漏洞修复发布修订号,对新特性或不重要的变更发布次版本号,对重大且不兼容的变更发布主版本号。 19 | 20 | 每个重大更改都将记录在 changelog 中。 21 | 22 | ## 报告 Issues 23 | 24 | 我们使用 [Github issues](https://github.com/arco-design/arco-plugins/issues) 进行 bug 报告和新 feature 建议。在报告 bug 之前,请确保已经搜索过类似的 [问题](https://github.com/arco-design/arco-plugins/issues),因为它们可能已经得到解答或正在被修复。新问题应通过 [问题助手](https://arco.design/issue-helper?repo=arco-plugins) 提交。对于 bug 报告,请包含可用于重现问题的代码。对于新 feature 建议,请指出你想要的更改以及期望的行为。 25 | 26 | ## 提交 Pull Request 27 | 28 | 1. Fork [此仓库](https://github.com/arco-design/arco-plugins),从 `main` 创建分支。新功能实现请发 pull request 到 `feature` 分支。其他更改发到 `main` 分支。 29 | 2. 使用 `npm install -g` 安装 `lerna` 和 `yarn`。 30 | 3. 执行 `yarn install` 安装 `workspaces` 中各个包的依赖(如果遇到 `YN0018` 错误,可以使用 `YARN_CHECKSUM_BEHAVIOR=update yarn` 进行安装)。 31 | 4. 对代码库进行更改。如果适用的话,请确保写了相应的测试。 32 | 5. 提交 git commit, 请同时遵守 [Commit 规范](#commit-指南)。 33 | 6. 提交 pull request, 如果有对应的 issue,请进行[关联](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)。 34 | 35 | ## Commit 指南 36 | 37 | Commit messages 请遵循[conventional-changelog 标准](https://www.conventionalcommits.org/en/v1.0.0/): 38 | 39 | ```bash 40 | <类型>[可选 范围]: <描述> 41 | 42 | [可选 正文] 43 | 44 | [可选 脚注] 45 | ``` 46 | 47 | ### Commit 类型 48 | 49 | 以下是 commit 类型列表: 50 | 51 | - feat: 新特性或功能 52 | - fix: 缺陷修复 53 | - docs: 文档更新 54 | - style: 代码风格或者组件样式更新 55 | - refactor: 代码重构,不引入新功能和缺陷修复 56 | - perf: 性能优化 57 | - test: 单元测试 58 | - chore: 其他不修改 src 或测试文件的提交 59 | 60 | ## License 61 | 62 | [MIT 协议](./LICENSE). 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bytedance, Inc. and its affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arco-design plugin library 2 | 3 | A collection of plugins serving the Arco ecosystem. 4 | 5 | ## Background 6 | 7 | The purpose of the plugin is to solve the problem of using the Arco component library and connect ecological products from the architectural level to minimize the use cost. 8 | 9 | ## Features 10 | 11 | The main functions are as follows: 12 | 13 | 1. `Load on demand`: Components, icons and styles are loaded on demand in one step. 14 | 2. `Theme import`: The product of the style configuration platform, it only needs to be imported on the plugin to take effect. 15 | 3. `Icon Replacement`: Specify the package name of the icon library, and the plugin will read the icons in the package and replace the icons with the same name used in the component library. 16 | 17 | ## Plugin list 18 | 19 | The functions of different architectures are different due to different progress. Go to the plugin details page to find more useful functions: 20 | 21 | 1. [unplugin for React](./packages/unplugin-react/README.md) 22 | 2. [webpack plugin for React](./packages/plugin-webpack-react/README.md) 23 | 3. [vite plugin for React](./packages/plugin-vite-react/README.md) 24 | 4. [vite plugin for Vue](./packages/plugin-vite-vue/README.md) 25 | 26 | ## Contributing 27 | 28 | Developers interested in contributing should read the [Code of Conduct](./CODE_OF_CONDUCT.md) and 29 | the [Contributing Guide](./CONTRIBUTING.md). 30 | 31 | Thank you to all the people who already contributed to ArcoDesign! 32 | 33 | 34 | 35 | ## License 36 | 37 | Ths project is [MIT licensed](./LICENSE). 38 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # arco-design 插件库 2 | 3 | 服务于 Arco 生态的插件合集。 4 | 5 | ## 背景 6 | 7 | 插件的目的是从架构的层面来解决 Arco 组件库的使用问题以及串联生态产品,最大程度地减少使用成本。 8 | 9 | ## 功能 10 | 11 | 主要功能如下: 12 | 13 | 1. `按需加载`:组件,图标和样式按需加载一步到位。 14 | 2. `主题引入`:风格配置平台的产物,只需要在插件上引入即可生效。 15 | 3. `图标替换`:指定图标库的包名,插件会读取包内图标对组件库内用到的同名的图标进行替换。 16 | 17 | ## 插件列表 18 | 19 | 不同架构的功能因为进步不一样有所差异,快进入插件详情页发现更多实用的功能: 20 | 21 | 1. [webpack plugin](./packages/plugin-webpack-react/README.md) 22 | 2. [vite plugin](./packages/plugin-vite-react/README.md) 23 | 24 | 25 | ## 参与贡献 26 | 27 | 28 | 贡献之前请先阅读 [行为准则](./CODE_OF_CONDUCT.md) 和 [贡献指南](./CONTRIBUTING.zh-CN.md)。 29 | 30 | 感谢所有为 ArcoDesign 做过贡献的人! 31 | 32 | 33 | 34 | ## License 35 | 36 | [MIT 协议](./LICENSE) 37 | -------------------------------------------------------------------------------- /examples/component-a/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | -------------------------------------------------------------------------------- /examples/component-a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-component-a", 3 | "version": "1.0.0", 4 | "description": "componentA", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "rm -rf lib && tsc" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@arco-design/web-react": "^2.35.1", 15 | "@types/react": "^18.0.14", 16 | "react": "^18.2.0", 17 | "typescript": "^4.5.2" 18 | }, 19 | "peerDependencies": { 20 | "@arco-design/web-react": "^2.35.1", 21 | "react": "^18.2.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/component-a/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Statistic } from '@arco-design/web-react'; 3 | 4 | export function ComponentA() { 5 | return ( 6 |
7 |

I am ComponentA

8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /examples/component-a/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "outDir": "./lib", 5 | "jsx": "react", 6 | "declaration": true, 7 | "moduleResolution": "node", 8 | "module": "es6", 9 | "target": "es5", 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "skipLibCheck": true 13 | }, 14 | "include": ["src"], 15 | "exclude": ["node_modules"] 16 | } 17 | -------------------------------------------------------------------------------- /examples/rspack-react/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib 3 | -------------------------------------------------------------------------------- /examples/rspack-react/components/mime-component/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Breadcrumb } from '@arco-design/web-react'; 3 | 4 | export function MimeComponent() { 5 | return ( 6 |
7 |

mime component

8 | 9 | Home 10 | 11 | Channel 12 | 13 | News 14 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /examples/rspack-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-rspack-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "dev": "rspack dev", 8 | "build": "rspack build" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@rspack/cli": "^0.1.8" 14 | }, 15 | "dependencies": { 16 | "@arco-design/web-react": "^2.47.1", 17 | "@arco-iconbox/react-partial-bits": "^0.0.3", 18 | "@arco-plugins/unplugin-react": "workspace:*", 19 | "@arco-themes/react-asuka": "^0.0.1", 20 | "example-component-a": "workspace:*", 21 | "ky": "^0.33.3", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/rspack-react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/rspack-react/rspack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const { ArcoDesignPlugin } = require('@arco-plugins/unplugin-react'); 3 | const path = require('path'); 4 | 5 | /** @type {import('@rspack/core').Configuration} */ 6 | module.exports = { 7 | mode: 'development', 8 | entry: './src/main.jsx', 9 | output: { 10 | publicPath: '/', 11 | }, 12 | devtool: 'source-map', 13 | builtins: { 14 | html: [ 15 | { 16 | filename: 'index.html', 17 | template: 'public/index.html', 18 | inject: 'body', 19 | }, 20 | ], 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | type: 'css', 26 | test: /\.less$/, 27 | use: [ 28 | { 29 | loader: 'less-loader', 30 | options: { 31 | lessOptions: { 32 | javascriptEnabled: true, 33 | }, 34 | }, 35 | }, 36 | ], 37 | }, 38 | ], 39 | }, 40 | resolve: { 41 | alias: { 42 | '@arco-design/web-react': path.resolve(__dirname, 'node_modules/@arco-design/web-react'), 43 | }, 44 | }, 45 | devServer: { 46 | devMiddleware: { 47 | writeToDisk: true, 48 | }, 49 | }, 50 | plugins: [ 51 | new ArcoDesignPlugin({ 52 | theme: '@arco-themes/react-asuka', 53 | iconBox: '@arco-iconbox/react-partial-bits', 54 | removeFontFace: true, 55 | defaultLanguage: 'ja-JP', 56 | }), 57 | ], 58 | }; 59 | -------------------------------------------------------------------------------- /examples/rspack-react/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Button, Space, Upload } from '@arco-design/web-react'; 3 | import { IconUser } from '@arco-design/web-react/icon'; 4 | import ky from 'ky'; 5 | 6 | function App() { 7 | const [usingWoff, setUsingWoff] = useState('loading'); 8 | useEffect(() => { 9 | ky('/main.css') 10 | .text() 11 | .then((content) => { 12 | const matched = content.match(/url\(.*?nunito_for_arco.*?\)/); 13 | if (matched) { 14 | setUsingWoff(matched[0]); 15 | } else { 16 | setUsingWoff('EMPTY'); 17 | } 18 | }); 19 | }); 20 | return ( 21 |
22 |
    23 | 24 |
  1. 25 |

    The color of button should be orange (theme: '@arco-themes/react-asuka'):

    26 | 27 |
  2. 28 |
  3. 29 |

    The nunito font file should not exist (removeFontFace: true for <= 2.23.0):

    30 |
    {usingWoff}
    31 |
  4. 32 |
  5. 33 |

    This upload component should display Japanese (defaultLanguage: 'ja-JP'):

    34 | 35 |
  6. 36 |
  7. 37 |

    38 | This icon should display the logo of Tiktok (iconBox: 39 | '@arco-iconbox/react-partial-bits'): 40 |

    41 | 42 |
  8. 43 |
    44 |
45 |
46 | ); 47 | } 48 | 49 | export default App; 50 | -------------------------------------------------------------------------------- /examples/rspack-react/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App.jsx'; 4 | 5 | console.log(123123); 6 | ReactDOM.render(, document.getElementById('root')); 7 | -------------------------------------------------------------------------------- /examples/webpack-react/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "edge": 17, 8 | "firefox": 60, 9 | "chrome": 67, 10 | "safari": 11.1 11 | }, 12 | "useBuiltIns": "usage" 13 | } 14 | ], 15 | "@babel/preset-react" 16 | ], 17 | "plugins": [ 18 | "@babel/plugin-proposal-export-default-from", 19 | "@babel/plugin-transform-runtime", 20 | "@babel/plugin-syntax-class-properties", 21 | "@babel/plugin-proposal-class-properties", 22 | "@babel/plugin-transform-react-jsx-source" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /examples/webpack-react/components/mime-component/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Breadcrumb } from '@arco-design/web-react'; 3 | 4 | export function MimeComponent() { 5 | return ( 6 |
7 |

mime component

8 | 9 | Home 10 | 11 | Channel 12 | 13 | News 14 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /examples/webpack-react/examples-pnpm/webpack-react/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "edge": 17, 8 | "firefox": 60, 9 | "chrome": 67, 10 | "safari": 11.1 11 | }, 12 | "useBuiltIns": "usage" 13 | } 14 | ], 15 | "@babel/preset-react" 16 | ], 17 | "plugins": [ 18 | "@babel/plugin-proposal-export-default-from", 19 | "@babel/plugin-transform-runtime", 20 | "@babel/plugin-syntax-class-properties", 21 | "@babel/plugin-proposal-class-properties", 22 | "@babel/plugin-transform-react-jsx-source" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /examples/webpack-react/examples-pnpm/webpack-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-webpack-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "dev": "webpack serve" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@babel/core": "^7.14.3", 13 | "@babel/plugin-proposal-class-properties": "^7.14.5", 14 | "@babel/plugin-proposal-export-default-from": "^7.14.5", 15 | "@babel/plugin-syntax-class-properties": "^7.12.13", 16 | "@babel/plugin-transform-react-jsx-source": "^7.14.5", 17 | "@babel/plugin-transform-runtime": "^7.15.0", 18 | "@babel/preset-env": "^7.14.4", 19 | "@babel/preset-react": "^7.13.13", 20 | "babel-loader": "^8.2.5", 21 | "css-loader": "^6.7.1", 22 | "html-webpack-plugin": "^5.5.0", 23 | "less": "^4.1.3", 24 | "less-loader": "^11.0.0", 25 | "style-loader": "^3.3.1", 26 | "webpack": "^5.73.0", 27 | "webpack-cli": "^4.10.0", 28 | "webpack-dev-server": "^4.9.2" 29 | }, 30 | "dependencies": { 31 | "@arco-design/web-react": "^2.35.1", 32 | "@arco-themes/react-plugin-test": "^0.0.1", 33 | "arco-pro-radio": "^0.1.0", 34 | "install": "^0.13.0", 35 | "npm": "^8.12.2", 36 | "react": "^18.2.0", 37 | "react-dom": "^18.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/webpack-react/examples-pnpm/webpack-react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Webpack React 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/webpack-react/examples-pnpm/webpack-react/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Space, 4 | Button, 5 | DatePicker, 6 | Avatar, 7 | Input, 8 | Tag, 9 | Slider, 10 | Switch, 11 | Rate, 12 | Empty, 13 | } from '@arco-design/web-react'; 14 | import ProRadio from 'arco-pro-radio'; 15 | 16 | function App() { 17 | return ( 18 |
19 | 20 | 21 | 22 | Arco 23 | 24 | Tag 25 | 26 | 27 | 28 | 29 | 30 | 31 | A 32 | 33 | B 34 | C 35 | 36 | 37 |
38 | ); 39 | } 40 | 41 | export default App; 42 | -------------------------------------------------------------------------------- /examples/webpack-react/examples-pnpm/webpack-react/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App.jsx'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /examples/webpack-react/examples-pnpm/webpack-react/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const webpack = require('webpack'); 3 | const ArcoWebpackPlugin = require('../../packages/plugin-webpack-react/lib'); 4 | 5 | module.exports = { 6 | mode: 'development', 7 | entry: './src/main.jsx', 8 | output: { 9 | publicPath: '/', 10 | }, 11 | devtool: 'inline-source-map', 12 | devServer: { 13 | historyApiFallback: true, 14 | open: true, 15 | hot: true, 16 | port: 8080, 17 | host: 'localhost', 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | use: 'babel-loader', 24 | exclude: /node_modules/, 25 | }, 26 | { 27 | test: /\.less$/, 28 | use: [ 29 | 'style-loader', 30 | 'css-loader', 31 | { 32 | loader: 'less-loader', 33 | options: { 34 | lessOptions: { 35 | javascriptEnabled: true, 36 | }, 37 | }, 38 | }, 39 | ], 40 | }, 41 | ], 42 | }, 43 | plugins: [ 44 | new HtmlWebpackPlugin({ 45 | filename: 'index.html', 46 | template: 'public/index.html', 47 | inject: 'body', 48 | }), 49 | new ArcoWebpackPlugin({ 50 | include: ['src', 'node_modules/arco-pro-radio'], 51 | webpackImplementation: webpack, 52 | theme: '@arco-themes/react-plugin-test', 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /examples/webpack-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-webpack-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "dev": "webpack serve" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@arco-plugins/webpack-react": "^1.4.0", 13 | "@babel/core": "^7.14.3", 14 | "@babel/plugin-proposal-class-properties": "^7.14.5", 15 | "@babel/plugin-proposal-export-default-from": "^7.14.5", 16 | "@babel/plugin-syntax-class-properties": "^7.12.13", 17 | "@babel/plugin-transform-react-jsx-source": "^7.14.5", 18 | "@babel/plugin-transform-runtime": "^7.15.0", 19 | "@babel/preset-env": "^7.14.4", 20 | "@babel/preset-react": "^7.13.13", 21 | "babel-loader": "^8.2.5", 22 | "css-loader": "^6.7.1", 23 | "html-webpack-plugin": "^5.5.0", 24 | "less": "^4.1.3", 25 | "less-loader": "^11.0.0", 26 | "style-loader": "^3.3.1", 27 | "webpack": "^5.73.0", 28 | "webpack-cli": "^4.10.0", 29 | "webpack-dev-server": "^4.9.2" 30 | }, 31 | "dependencies": { 32 | "@arco-design/web-react": "^2.35.1", 33 | "@arco-themes/react-plugin-test": "^0.0.1", 34 | "example-component-a": "^1.0.0", 35 | "react": "^18.2.0", 36 | "react-dom": "^18.2.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/webpack-react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Webpack React 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/webpack-react/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Space, 4 | Button, 5 | DatePicker, 6 | Avatar, 7 | Input, 8 | Tag, 9 | Slider, 10 | Switch, 11 | Rate, 12 | Empty, 13 | } from '@arco-design/web-react'; 14 | import { ComponentA } from 'example-component-a'; 15 | import { MimeComponent } from '../components/mime-component/index.jsx'; 16 | 17 | function App() { 18 | return ( 19 |
20 | 21 | 22 | 23 | Arco 24 | 25 | Tag 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | ); 35 | } 36 | 37 | export default App; 38 | -------------------------------------------------------------------------------- /examples/webpack-react/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App.jsx'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /examples/webpack-react/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const ArcoWebpackPlugin = require('@arco-plugins/webpack-react'); 3 | const webpack = require('webpack'); 4 | 5 | module.exports = { 6 | mode: 'development', 7 | entry: './src/main.jsx', 8 | output: { 9 | publicPath: '/', 10 | }, 11 | devtool: 'inline-source-map', 12 | devServer: { 13 | historyApiFallback: true, 14 | open: true, 15 | hot: true, 16 | port: 8080, 17 | host: 'localhost', 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | use: 'babel-loader', 24 | exclude: /node_modules/, 25 | }, 26 | { 27 | test: /\.less$/, 28 | use: [ 29 | 'style-loader', 30 | 'css-loader', 31 | { 32 | loader: 'less-loader', 33 | options: { 34 | lessOptions: { 35 | javascriptEnabled: true, 36 | }, 37 | }, 38 | }, 39 | ], 40 | }, 41 | ], 42 | }, 43 | plugins: [ 44 | new HtmlWebpackPlugin({ 45 | filename: 'index.html', 46 | template: 'public/index.html', 47 | inject: 'body', 48 | }), 49 | new ArcoWebpackPlugin({ 50 | include: ['src', /\/components\//, '../component-a'], 51 | webpackImplementation: webpack, 52 | theme: '@arco-themes/react-plugin-test', 53 | }), 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "npmClient": "yarn", 6 | "useWorkspaces": false, 7 | "version": "independent" 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arco-plugins", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev:webpack-react": "yarn workspace example-webpack-react dev" 7 | }, 8 | "workspaces": [ 9 | "packages/*", 10 | "examples/*" 11 | ], 12 | "engines": { 13 | "node": ">=12.0.0" 14 | }, 15 | "husky": { 16 | "hooks": { 17 | "pre-commit": "lint-staged" 18 | } 19 | }, 20 | "lint-staged": { 21 | "packages/*/src/**/*.{ts,js}": [ 22 | "eslint", 23 | "prettier --write" 24 | ] 25 | }, 26 | "keywords": [ 27 | "arco", 28 | "arco-design", 29 | "webpack", 30 | "vite", 31 | "plugin" 32 | ], 33 | "author": "arco-design", 34 | "license": "MIT", 35 | "devDependencies": { 36 | "@types/babel__core": "^7.20.0", 37 | "@typescript-eslint/eslint-plugin": "^5.4.0", 38 | "@typescript-eslint/parser": "^5.4.0", 39 | "eslint": "^7.2.0", 40 | "eslint-config-airbnb-base": "^14.2.0", 41 | "eslint-config-prettier": "^6.15.0", 42 | "eslint-plugin-import": "^2.22.1", 43 | "eslint-plugin-prettier": "^3.1.4", 44 | "husky": "^4.3.0", 45 | "lerna": "^3.22.1", 46 | "lint-staged": "^10.5.0", 47 | "prettier": "^2.1.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /types -------------------------------------------------------------------------------- /packages/plugin-vite-react/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.5 2 | 3 | `2022-01-21` 4 | 5 | ### 💎 Optimization 6 | 7 | - Improve icons' loading speed 8 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/CHANGLOG.zh-CN.md: -------------------------------------------------------------------------------- 1 | ## 1.0.5 2 | 3 | `2022-01-21` 4 | 5 | ### 💎 功能优化 6 | 7 | - 提升图标的加载速度 8 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/README.md: -------------------------------------------------------------------------------- 1 | # @arco-plugins/vite-react 2 | 3 | ## Feature 4 | 5 | 1. `Style lazy load` 6 | 2. `Theme import` 7 | 3. `Icon replacement` 8 | 9 | > `Style lazy load` doesn't work during development for better experience. 10 | 11 | ## Install 12 | 13 | ```bash 14 | npm i @arco-plugins/vite-react -D 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```js 20 | // vite.config.js 21 | 22 | import { vitePluginForArco } from '@arco-plugins/vite-react' 23 | 24 | export default { 25 | ... 26 | plugins: [ 27 | vitePluginForArco(options), 28 | ], 29 | } 30 | ``` 31 | 32 | ```tsx 33 | // react 34 | import { Button } from '@arco-design/web-react'; 35 | 36 | export default () => ( 37 |
38 | 39 | 40 |
41 | ); 42 | ``` 43 | 44 | ## Options 45 | 46 | The plugin supports the following parameters: 47 | 48 | | Params | Type | Default Value | Description | 49 | | :--------------: | :----------------: | :-----------: | :------------------------ | 50 | | **`theme`** | `{String}` | `''` | Theme package name | 51 | | **`iconBox`** | `{String}` | `''` | Icon library package name | 52 | | **`modifyVars`** | `{Object}` | `{}` | Less variables | 53 | | **`style`** | `{'css'\|Boolean}` | `true` | Style import method | 54 | |**`varsInjectScope`**|`{(string\|RegExp)[]}`|`[]`| Scope of injection of less variables (modifyVars and the theme package's variables) | 55 | 56 | **Style import methods ** 57 | 58 | `style: true` will import less file 59 | 60 | ```js 61 | import '@arco-design/web-react/Affix/style'; 62 | ``` 63 | 64 | `style: 'css'` will import css file 65 | 66 | ```js 67 | import '@arco-design/web-react/Affix/style/css'; 68 | ``` 69 | 70 | `style: false` will not import any style file 71 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # @arco-plugins/vite-react 2 | 3 | ## 特性 4 | 5 | 1. `样式按需加载` 6 | 2. `主题引入` 7 | 3. `图标替换` 8 | 9 | > 为了开发体验,开发环境下样式为全量引入 10 | 11 | ## 安装 12 | 13 | ```bash 14 | npm i @arco-plugins/vite-react -D 15 | ``` 16 | 17 | ## 用法 18 | 19 | ```js 20 | // vite.config.js 21 | 22 | import { vitePluginForArco } from '@arco-plugins/vite-react' 23 | 24 | export default { 25 | ... 26 | plugins: [ 27 | vitePluginForArco(options), 28 | ], 29 | } 30 | ``` 31 | 32 | ```tsx 33 | // react 34 | import { Button } from '@arco-design/web-react'; 35 | 36 | export default () => ( 37 |
38 | 39 | 40 |
41 | ); 42 | ``` 43 | 44 | ## 参数 45 | 46 | 插件支持以下参数: 47 | 48 | | 参数名 | 类型 | 默认值 | 描述 | 49 | | :--------------: | :----------------: | :----: | :----------- | 50 | | **`theme`** | `{String}` | `''` | 主题包名 | 51 | | **`iconBox`** | `{String}` | `''` | 图标库包名 | 52 | | **`modifyVars`** | `{Object}` | `{}` | Less 变量 | 53 | | **`style`** | `{'css'\|Boolean}` | `true` | 样式引入方式 | 54 | |**`varsInjectScope`**|`string[]`|`[]`| less 变量(modifyVars 和主题包的变量)注入的范围 | 55 | 56 | **样式引入方式 ** 57 | 58 | `style: true` 将引入 less 文件 59 | 60 | ```js 61 | import '@arco-design/web-react/Affix/style'; 62 | ``` 63 | 64 | `style: 'css'` 将引入 css 文件 65 | 66 | ```js 67 | import '@arco-design/web-react/Affix/style/css'; 68 | ``` 69 | 70 | `style: false` 不引入样式 71 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@arco-plugins/vite-react", 3 | "version": "1.3.3", 4 | "description": "For Vite build, load Arco Design styles on demand", 5 | "main": "lib/index.js", 6 | "types": "types/index.d.ts", 7 | "files": [ 8 | "lib", 9 | "types" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:arco-design/arco-plugins.git", 14 | "directory": "packages/plugin-vite-react" 15 | }, 16 | "keywords": [ 17 | "arco", 18 | "arco-design", 19 | "arco-plugin", 20 | "plugin", 21 | "vite" 22 | ], 23 | "scripts": { 24 | "clean": "rm -fr lib types", 25 | "compile": "yarn clean && npx tsc", 26 | "prepublishOnly": "yarn compile" 27 | }, 28 | "author": "arco-design", 29 | "license": "MIT", 30 | "dependencies": { 31 | "@babel/generator": "^7.12.11", 32 | "@babel/helper-module-imports": "^7.12.5", 33 | "@babel/parser": "^7.12.11", 34 | "@babel/traverse": "^7.12.12", 35 | "@babel/types": "^7.12.12", 36 | "@types/node": "^16.11.10" 37 | }, 38 | "devDependencies": { 39 | "typescript": "^4.5.2", 40 | "vite": "^2.6.14" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/src/arco-design-plugin/config.ts: -------------------------------------------------------------------------------- 1 | type Matchers = [string, number?]; 2 | 3 | export const libraryName = '@arco-design/web-react'; 4 | 5 | export const iconCjsListMatchers: Matchers = [`${libraryName}/icon/index\\.js[^/]*$`]; 6 | 7 | export const iconComponentMatchers: Matchers = [ 8 | `${libraryName}/icon/react-icon/([^/]+)/index\\.js[^/]*$`, 9 | 1, 10 | ]; 11 | 12 | export const lessMatchers: Matchers = [`${libraryName}/.+\\.less[^/]*$`]; 13 | 14 | export const fullLessMatchers: Matchers = [`${libraryName}/dist/css/index\\.less[^/]*$`]; 15 | 16 | export const globalLessMatchers: Matchers = [`${libraryName}/(es|lib)/style/index\\.less[^/]*$`]; 17 | 18 | export const componentLessMatchers: Matchers = [ 19 | `${libraryName}/(es|lib)/([^/]+)/style/index\\.less[^/]*$`, 20 | 2, 21 | ]; 22 | 23 | export const fullCssMatchers: Matchers = [`${libraryName}/dist/css/arco\\.css[^/]*$`]; 24 | 25 | export const globalCssMatchers: Matchers = [`${libraryName}/(es|lib)/style/index\\.css[^/]*$`]; 26 | 27 | export const componentCssMatchers: Matchers = [ 28 | `${libraryName}/(es|lib)/([^/]+)/style/index\\.css[^/]*$`, 29 | 2, 30 | ]; 31 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/src/arco-design-plugin/icon.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | import type { PluginBuild, OnLoadArgs } from 'esbuild'; 3 | 4 | import { iconCjsListMatchers, iconComponentMatchers } from './config'; 5 | import { pathMatch } from './utils'; 6 | 7 | const filter = new RegExp(`(${iconCjsListMatchers[0]})|(${iconComponentMatchers[0]})`); 8 | 9 | export function loadIcon(id: string, iconBox: string, iconBoxLib: any) { 10 | if (!iconBox || !iconBoxLib) { 11 | return; 12 | } 13 | 14 | // cjs -> es 15 | if (pathMatch(id, iconCjsListMatchers)) { 16 | return `export * from './index.es.js'`; 17 | } 18 | 19 | const componentName = pathMatch(id, iconComponentMatchers); 20 | if (componentName && iconBoxLib[componentName]) { 21 | return `export { default } from '${iconBox}/esm/${componentName}/index.js'`; 22 | } 23 | } 24 | 25 | export function modifyIconConfig(config: UserConfig, iconBox: string, iconBoxLib: any) { 26 | if (!iconBox || !iconBoxLib) { 27 | return; 28 | } 29 | // Pre-Bundling 30 | config.optimizeDeps = config.optimizeDeps || {}; 31 | config.optimizeDeps.esbuildOptions = config.optimizeDeps.esbuildOptions || {}; 32 | config.optimizeDeps.esbuildOptions.plugins = config.optimizeDeps.esbuildOptions.plugins || []; 33 | config.optimizeDeps.esbuildOptions.plugins.push({ 34 | name: 'arcoIconReplace', 35 | setup(build: PluginBuild) { 36 | build.onLoad( 37 | { 38 | namespace: 'file', 39 | filter, 40 | }, 41 | ({ path: id }: OnLoadArgs) => { 42 | const contents = loadIcon(id, iconBox, iconBoxLib); 43 | if (contents) { 44 | return { 45 | contents, 46 | loader: 'js', 47 | }; 48 | } 49 | return null; 50 | } 51 | ); 52 | }, 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/src/arco-design-plugin/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin, ResolvedConfig, UserConfig, ConfigEnv } from 'vite'; 2 | import { modifyCssConfig } from './less'; 3 | import { modifyIconConfig, loadIcon } from './icon'; 4 | import { transformCssFile, transformJsFiles } from './transform'; 5 | 6 | const pkg = require('../../package.json'); 7 | 8 | type Vars = Record; 9 | type Style = boolean | 'css'; 10 | 11 | interface PluginOption { 12 | theme?: string; // Theme package name 13 | iconBox?: string; // Icon library package name 14 | modifyVars?: Vars; // less modifyVars 15 | style?: Style; // Style lazy load 16 | varsInjectScope?: (string | RegExp)[]; // Less vars inject 17 | } 18 | 19 | export default function vitePluginArcoImport(options: PluginOption = {}): Plugin { 20 | const { theme = '', iconBox = '', modifyVars = {}, style = true, varsInjectScope = [] } = options; 21 | let iconBoxLib: any; 22 | let resolvedConfig: ResolvedConfig; 23 | let isDevelopment = false; 24 | 25 | if (iconBox) { 26 | try { 27 | iconBoxLib = require(iconBox); // eslint-disable-line 28 | } catch (e) { 29 | throw new Error(`IconBox ${iconBox} not existed`); 30 | } 31 | } 32 | return { 33 | name: pkg.name, 34 | config(config: UserConfig, { command }: ConfigEnv) { 35 | // dev mode 36 | isDevelopment = command === 'serve'; 37 | 38 | // css preprocessorOptions 39 | modifyCssConfig(pkg.name, config, theme, modifyVars, varsInjectScope); 40 | 41 | // iconbox 42 | modifyIconConfig(config, iconBox, iconBoxLib); 43 | }, 44 | async load(id: string) { 45 | const res = loadIcon(id, iconBox, iconBoxLib); 46 | if (res !== undefined) { 47 | return res; 48 | } 49 | // other ids should be handled as usually 50 | return null; 51 | }, 52 | configResolved(config: ResolvedConfig) { 53 | resolvedConfig = config; 54 | // console.log('viteConfig', resolvedConfig) 55 | }, 56 | transform(code, id) { 57 | // transform css files 58 | const res = transformCssFile({ 59 | code, 60 | id, 61 | theme, 62 | }); 63 | if (res !== undefined) { 64 | return res; 65 | } 66 | 67 | // css lazy load 68 | return transformJsFiles({ 69 | code, 70 | id, 71 | theme, 72 | style, 73 | isDevelopment, 74 | sourceMaps: isDevelopment || Boolean(resolvedConfig?.build?.sourcemap), 75 | }); 76 | }, 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/src/arco-design-plugin/less.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | import { writeFileSync } from 'fs'; 3 | import { lessMatchers, globalLessMatchers, componentLessMatchers } from './config'; 4 | import { getThemeComponentList, pathMatch, readFileStrSync, parseInclude2RegExp } from './utils'; 5 | 6 | type Vars = Record; 7 | 8 | // eslint-disable-next-line import/prefer-default-export 9 | export function modifyCssConfig( 10 | pkgName: string, 11 | config: UserConfig, 12 | theme: string, 13 | modifyVars: Vars, 14 | varsInjectScope: (string | RegExp)[] 15 | ) { 16 | let modifyLess: string | boolean = ''; 17 | if (theme) { 18 | modifyLess = readFileStrSync(`${theme}/tokens.less`); 19 | if (modifyLess === false) { 20 | throw new Error(`Theme ${theme} not existed`); 21 | } 22 | } 23 | Object.entries(modifyVars).forEach(([k, v]) => { 24 | modifyLess += `@${k}:${v};`; 25 | }); 26 | 27 | config.css = config.css || {}; 28 | config.css.preprocessorOptions = config.css.preprocessorOptions || {}; 29 | 30 | const { preprocessorOptions } = config.css; 31 | preprocessorOptions.less = preprocessorOptions.less || {}; 32 | preprocessorOptions.less.javascriptEnabled = true; 33 | if (modifyLess) { 34 | writeFileSync(`${__dirname}/../../.tokens.less`, modifyLess, { 35 | flag: 'w', 36 | }); 37 | const modifyLessFile = `${pkgName}/.tokens.less`; 38 | const includeRegExp = parseInclude2RegExp(varsInjectScope); 39 | preprocessorOptions.less.plugins = preprocessorOptions.less.plugins || []; 40 | preprocessorOptions.less.plugins.push({ 41 | install(_lessObj: any, pluginManager: any) { 42 | pluginManager.addPreProcessor( 43 | { 44 | process(src: string, extra: any) { 45 | const { 46 | fileInfo: { filename }, 47 | } = extra; 48 | 49 | // arco less vars inject 50 | const varsInjectMatch = 51 | pathMatch(filename, lessMatchers) || 52 | (includeRegExp && pathMatch(filename, [includeRegExp])); 53 | if (!varsInjectMatch) return src; 54 | 55 | if (theme) { 56 | // arco global style 57 | const globalMatch = pathMatch(filename, globalLessMatchers); 58 | if (globalMatch) { 59 | src += `; @import '${theme}/theme.less';`; 60 | } 61 | 62 | // arco component style 63 | const componentName = pathMatch(filename, componentLessMatchers); 64 | if (componentName) { 65 | if (getThemeComponentList(theme).includes(componentName)) { 66 | src += `; @import '${theme}/components/${componentName}/index.less';`; 67 | } 68 | } 69 | } 70 | 71 | src += `; @import '${modifyLessFile}';`; 72 | 73 | return src; 74 | }, 75 | }, 76 | 1000 77 | ); 78 | }, 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/src/arco-design-plugin/transform.ts: -------------------------------------------------------------------------------- 1 | import * as parser from '@babel/parser'; 2 | import * as types from '@babel/types'; 3 | import traverse, { NodePath } from '@babel/traverse'; 4 | import generate from '@babel/generator'; 5 | import { addSideEffect } from '@babel/helper-module-imports'; 6 | 7 | import { libraryName, fullCssMatchers } from './config'; 8 | import { isModExist, pathMatch, readFileStrSync } from './utils'; 9 | 10 | interface Specifier { 11 | imported: { 12 | name: string; 13 | }; 14 | } 15 | 16 | type TransformedResult = undefined | { code: string; map: any }; 17 | 18 | type Style = boolean | 'css'; 19 | 20 | export function transformCssFile({ 21 | id, 22 | theme, 23 | }: { 24 | code: string; 25 | id: string; 26 | theme: string; 27 | }): TransformedResult { 28 | if (theme) { 29 | const matches = pathMatch(id, fullCssMatchers); 30 | if (matches) { 31 | const themeCode = readFileStrSync(`${theme}/css/arco.css`); 32 | if (themeCode !== false) { 33 | return { 34 | code: themeCode, 35 | map: null, 36 | }; 37 | } 38 | } 39 | } 40 | return undefined; 41 | } 42 | 43 | export function transformJsFiles({ 44 | code, 45 | id, 46 | theme, 47 | style, 48 | isDevelopment, 49 | sourceMaps, 50 | }: { 51 | code: string; 52 | id: string; 53 | theme?: string; 54 | style: Style; 55 | isDevelopment: boolean; 56 | sourceMaps: boolean; 57 | }): TransformedResult { 58 | if (style === false || !/\.(js|jsx|ts|tsx)$/.test(id)) { 59 | return undefined; 60 | } 61 | 62 | const fullStyleFile = `${libraryName}/dist/css/${style === 'css' ? 'arco.css' : 'index.less'}`; 63 | 64 | // edge case && dev faster 65 | if (isDevelopment) { 66 | return { 67 | code: `${code}\nimport '${fullStyleFile}';`, 68 | map: null, 69 | }; 70 | } 71 | 72 | const ast = parser.parse(code, { 73 | sourceType: 'module', 74 | plugins: ['jsx'], 75 | }) as any; 76 | 77 | traverse(ast, { 78 | enter(path: NodePath) { 79 | const { node } = path; 80 | // import { Button, InputNumber, TimeLine } from '@arco-design/web-react' 81 | if (types.isImportDeclaration(node)) { 82 | const { value } = node.source; 83 | if (value === libraryName) { 84 | // lazy load (css files don't support lazy load with theme) 85 | if (style !== 'css' || !theme) { 86 | node.specifiers.forEach((spec) => { 87 | if (types.isImportSpecifier(spec)) { 88 | const importedName = (spec as Specifier).imported.name; 89 | const stylePath = `${libraryName}/es/${importedName}/style/${ 90 | style === 'css' ? 'css.js' : 'index.js' 91 | }`; 92 | if (isModExist(stylePath)) addSideEffect(path, stylePath); 93 | } 94 | }); 95 | } 96 | // import full less/css bundle file 97 | else { 98 | addSideEffect(path, fullStyleFile); 99 | } 100 | } 101 | } 102 | }, 103 | }); 104 | 105 | return generate(ast, { sourceMaps, sourceFileName: id }); 106 | } 107 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/src/arco-design-plugin/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.json'; 2 | declare module '@babel/helper-module-imports'; 3 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/src/arco-design-plugin/utils.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, readdirSync } from 'fs'; 2 | import { dirname, extname, resolve, sep, win32, posix } from 'path'; 3 | 4 | // read file content 5 | export function readFileStrSync(path: string): false | string { 6 | try { 7 | const resolvedPath = require.resolve(path); 8 | return readFileSync(resolvedPath).toString(); 9 | } catch (error) { 10 | return false; 11 | } 12 | } 13 | 14 | // check if a module existed 15 | const modExistObj: Record = {}; 16 | export function isModExist(path: string) { 17 | if (modExistObj[path] === undefined) { 18 | try { 19 | require.resolve(path); 20 | modExistObj[path] = true; 21 | } catch (error) { 22 | modExistObj[path] = false; 23 | } 24 | } 25 | return modExistObj[path]; 26 | } 27 | 28 | // the theme package's component list 29 | const componentsListObj: Record = {}; 30 | export function getThemeComponentList(theme: string) { 31 | if (!theme) return []; 32 | if (!componentsListObj[theme]) { 33 | try { 34 | const packageRootDir = dirname(require.resolve(`${theme}/package.json`)); 35 | const dirPath = `${packageRootDir}/components`; 36 | componentsListObj[theme] = readdirSync(dirPath) || []; 37 | } catch (error) { 38 | componentsListObj[theme] = []; 39 | } 40 | } 41 | return componentsListObj[theme]; 42 | } 43 | 44 | export const parse2PosixPath = (path: string) => 45 | sep === win32.sep ? path.replaceAll(win32.sep, posix.sep) : path; 46 | 47 | // filePath match 48 | export function pathMatch(path: string, conf: [string | RegExp, number?]): false | string { 49 | const [regStr, order = 0] = conf; 50 | const reg = new RegExp(regStr); 51 | const posixPath = parse2PosixPath(path); 52 | const matches = posixPath.match(reg); 53 | if (!matches) return false; 54 | return matches[order]; 55 | } 56 | 57 | export function parseInclude2RegExp(include: (string | RegExp)[] = [], context?: string) { 58 | if (include.length === 0) return false; 59 | context = context || process.cwd(); 60 | const regStrList = []; 61 | const folders = include 62 | .map((el) => { 63 | if (el instanceof RegExp) { 64 | const regStr = el.toString(); 65 | if (regStr.slice(-1) === '/') { 66 | regStrList.push(`(${regStr.slice(1, -1)})`); 67 | } 68 | return false; 69 | } 70 | const absolutePath = parse2PosixPath(resolve(context, el)); 71 | const idx = absolutePath.indexOf('/node_modules/'); 72 | const len = '/node_modules/'.length; 73 | const isFolder = extname(absolutePath) === ''; 74 | if (idx > -1) { 75 | const prexPath = absolutePath.slice(0, idx + len); 76 | const packagePath = absolutePath.slice(idx + len); 77 | return `(${prexPath}(\\.pnpm/.+/)?${packagePath}${isFolder ? '/' : ''})`; 78 | } 79 | return `(${absolutePath}${isFolder ? '/' : ''})`; 80 | }) 81 | .filter((el) => el !== false); 82 | if (folders.length) { 83 | regStrList.push(`(^${folders.join('|')})`); 84 | } 85 | if (regStrList.length > 0) { 86 | return new RegExp(regStrList.join('|')); 87 | } 88 | return false; 89 | } 90 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/src/index.ts: -------------------------------------------------------------------------------- 1 | import vitePluginForArco from './arco-design-plugin'; 2 | 3 | export default vitePluginForArco; 4 | 5 | export { vitePluginForArco }; 6 | -------------------------------------------------------------------------------- /packages/plugin-vite-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "outDir": "./lib", 5 | "declarationDir": "./types", 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "experimentalDecorators": true, 9 | "allowSyntheticDefaultImports": true, 10 | "noUnusedParameters": true, 11 | "noUnusedLocals": true, 12 | "module": "commonjs", 13 | "target": "esnext", 14 | "declaration": true, 15 | "lib": ["esnext", "dom"], 16 | "types": ["node"], 17 | "resolveJsonModule": true 18 | }, 19 | "include": ["src"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /types -------------------------------------------------------------------------------- /packages/plugin-vite-vue/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.4 2 | 3 | `2023-03-13` 4 | 5 | 🐛 BugFix 6 | 7 | - Fix the problem of icon not working 8 | ## 1.4.3 9 | 10 | `2023-03-03` 11 | 12 | 🐛 BugFix 13 | 14 | - Fix the problem of custom components replaced by Arco's with same name 15 | 16 | ## 1.4.2 17 | 18 | `2022-10-15` 19 | 20 | 🐛 BugFix 21 | 22 | - Fix the problem of automatic import component recognition 23 | 24 | 25 | ## 1.4.1 26 | 27 | `2022-10-14` 28 | 29 | 🐛 BugFix 30 | 31 | - Fix the problem of automatic import of low version component library 32 | - Fix the problem of automatic import component recognition 33 | 34 | 35 | ## 1.4.0 36 | 37 | `2022-10-11` 38 | 39 | ### 🆕 Feature 40 | 41 | - The plugin adds the function of automatically importing components and icons 42 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/CHANGLOG.zh-CN.md: -------------------------------------------------------------------------------- 1 | ## 1.4.4 2 | 3 | `2023-03-13` 4 | 5 | 🐛 BugFix 6 | 7 | - 修正Arco图标引用失效 8 | ## 1.4.3 9 | 10 | `2023-03-03` 11 | 12 | 🐛 BugFix 13 | 14 | - 修正自定义组件组件被Arco同名组件替换的bug 15 | 16 | ## 1.4.2 17 | 18 | `2022-10-15` 19 | 20 | 🐛 BugFix 21 | 22 | - 修复自动导入组件识别的问题 23 | 24 | 25 | ## 1.4.1 26 | 27 | `2022-10-14` 28 | 29 | 🐛 BugFix 30 | 31 | 修复低版本组件库自动导入的问题 32 | - 修复自动导入组件识别的问题 33 | 34 | 35 | ## 1.4.0 36 | 37 | `2022-10-11` 38 | 39 | ### 🆕 Feature 40 | 41 | - 插件增加组件和图标的自动导入功能 42 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/README.md: -------------------------------------------------------------------------------- 1 | # @arco-plugins/vite-vue 2 | 3 | ## Feature 4 | 5 | 1. `Style lazy load` 6 | 2. `Theme import` 7 | 3. `Icon replacement` 8 | 9 | > `Style lazy load` doesn't work during development for better experience. 10 | 11 | ## Install 12 | 13 | ```bash 14 | npm i @arco-plugins/vite-vue -D 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```js 20 | // vite.config.js 21 | 22 | import { vitePluginForArco } from '@arco-plugins/vite-vue' 23 | 24 | export default { 25 | ... 26 | plugins: [ 27 | vitePluginForArco(Options), 28 | ], 29 | } 30 | ``` 31 | 32 | ```diff 33 | // main.js 34 | 35 | import { createApp } from 'vue' 36 | - import ArcoVue from '@arco-design/web-vue'; 37 | - import ArcoVueIcon from '@arco-design/web-vue/es/icon'; 38 | import App from './App.vue'; 39 | - import '@arco-design/web-vue/dist/arco.css'; 40 | 41 | const app = createApp(App); 42 | - app.use(ArcoVue); 43 | - app.use(ArcoVueIcon); 44 | app.mount('#app'); 45 | ``` 46 | 47 | ```tsx 48 | // *.vue 49 | 50 | 53 | 54 | 61 | ``` 62 | 63 | ## Options 64 | 65 | The plugin supports the following parameters: 66 | 67 | | Params | Type | Default Value | Description | 68 | | :-------------------: | :--------------------: | :-----------: | :---------------------------------------------------------------------------------- | 69 | | **`theme`** | `{String}` | `-` | Theme package name | 70 | | **`iconBox`** | `{String}` | `-` | Icon library package name | 71 | | **`modifyVars`** | `{Object}` | `-` | Less variables | 72 | | **`style`** | `{'css'\|Boolean}` | `true` | Style import method | 73 | | **`varsInjectScope`** | `{(string\|RegExp)[]}` | `-` | Scope of injection of less variables (modifyVars and the theme package's variables) | 74 | | **`componentPrefix`** | `{String}` | `'a'` | Component prefix | 75 | | **`iconPrefix`** | `{String}` | `icon` | Icon component prefix | 76 | 77 | **Style import methods ** 78 | 79 | `style: true` will import less file 80 | 81 | ```js 82 | import '@arco-design/web-vue/Affix/style'; 83 | ``` 84 | 85 | `style: 'css'` will import css file 86 | 87 | ```js 88 | import '@arco-design/web-vue/Affix/style/css'; 89 | ``` 90 | 91 | `style: false` will not import any style file 92 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # @arco-plugins/vite-vue 2 | 3 | ## 特性 4 | 5 | 1. `样式按需加载` 6 | 2. `主题引入` 7 | 3. `图标替换` 8 | 9 | > 为了开发体验,开发环境下样式为全量引入 10 | 11 | ## 安装 12 | 13 | ```bash 14 | npm i @arco-plugins/vite-vue -D 15 | ``` 16 | 17 | ## 用法 18 | 19 | ```js 20 | // vite.config.js 21 | 22 | import { vitePluginForArco } from '@arco-plugins/vite-vue' 23 | 24 | export default { 25 | ... 26 | plugins: [ 27 | vitePluginForArco(Options), 28 | ], 29 | } 30 | ``` 31 | 32 | ```diff 33 | // main.js 34 | 35 | import { createApp } from 'vue' 36 | - import ArcoVue from '@arco-design/web-vue'; 37 | - import ArcoVueIcon from '@arco-design/web-vue/es/icon'; 38 | import App from './App.vue'; 39 | - import '@arco-design/web-vue/dist/arco.css'; 40 | 41 | const app = createApp(App); 42 | - app.use(ArcoVue); 43 | - app.use(ArcoVueIcon); 44 | app.mount('#app'); 45 | ``` 46 | 47 | ```tsx 48 | // *.vue 49 | 50 | 53 | 54 | 61 | ``` 62 | 63 | ## 参数 64 | 65 | 插件支持以下参数: 66 | 67 | | 参数名 | 类型 | 默认值 | 描述 | 68 | | :-------------------: | :----------------: | :----: | :----------------------------------------------- | 69 | | **`theme`** | `{String}` | `-` | 主题包名 | 70 | | **`iconBox`** | `{String}` | `-` | 图标库包名 | 71 | | **`modifyVars`** | `{Object}` | `-` | Less 变量 | 72 | | **`style`** | `{'css'\|Boolean}` | `true` | 样式引入方式 | 73 | | **`varsInjectScope`** | `string[]` | `-` | less 变量(modifyVars 和主题包的变量)注入的范围 | 74 | | **`componentPrefix`** | `{String}` | `'a'` | 组件前缀 | 75 | | **`iconPrefix`** | `{String}` | `icon` | 图标组件前缀 | 76 | 77 | **样式引入方式 ** 78 | 79 | `style: true` 将引入 less 文件 80 | 81 | ```js 82 | import '@arco-design/web-vue/Affix/style'; 83 | ``` 84 | 85 | `style: 'css'` 将引入 css 文件 86 | 87 | ```js 88 | import '@arco-design/web-vue/Affix/style/css'; 89 | ``` 90 | 91 | `style: false` 不引入样式 92 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@arco-plugins/vite-vue", 3 | "version": "1.4.5", 4 | "description": "For Vite build, load Arco Design styles on demand", 5 | "main": "lib/index.js", 6 | "types": "types/index.d.ts", 7 | "files": [ 8 | "lib", 9 | "types" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:arco-design/arco-plugins.git", 14 | "directory": "packages/plugin-vite-vue" 15 | }, 16 | "keywords": [ 17 | "arco", 18 | "arco-design", 19 | "arco-plugin", 20 | "plugin", 21 | "vite" 22 | ], 23 | "scripts": { 24 | "clean": "rm -fr lib types", 25 | "compile": "yarn clean && npx tsc", 26 | "prepublishOnly": "yarn compile" 27 | }, 28 | "author": "arco-design", 29 | "license": "MIT", 30 | "dependencies": { 31 | "@babel/generator": "^7.12.11", 32 | "@babel/helper-module-imports": "^7.12.5", 33 | "@babel/parser": "^7.12.11", 34 | "@babel/traverse": "^7.12.12", 35 | "@babel/types": "^7.12.12", 36 | "@types/node": "^16.11.10" 37 | }, 38 | "devDependencies": { 39 | "typescript": "^4.5.2", 40 | "vite": "^2.6.14" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/src/arco-design-plugin/config.ts: -------------------------------------------------------------------------------- 1 | type Matchers = [string, number?]; 2 | 3 | export const libraryName = '@arco-design/web-vue'; 4 | 5 | export const iconCjsListMatchers: Matchers = [`${libraryName}/lib/icon/index\\.js[^/]*$`]; 6 | 7 | export const iconComponentMatchers: Matchers = [ 8 | `${libraryName}/(es|lib)/icon/([^/]+)/index\\.js[^/]*$`, 9 | 2, 10 | ]; 11 | 12 | export const lessMatchers: Matchers = [`${libraryName}/.+\\.less[^/]*$`]; 13 | 14 | export const fullLessMatchers: Matchers = [`${libraryName}/dist/arco\\.less[^/]*$`]; 15 | 16 | export const globalLessMatchers: Matchers = [`${libraryName}/(es|lib)/style/index\\.less[^/]*$`]; 17 | 18 | export const componentLessMatchers: Matchers = [ 19 | `${libraryName}/(es|lib)/([^/]+)/style/index\\.less[^/]*$`, 20 | 2, 21 | ]; 22 | 23 | export const fullCssMatchers: Matchers = [`${libraryName}/dist/arco\\.(min\\.)?css[^/]*$`]; 24 | 25 | export const globalCssMatchers: Matchers = [`${libraryName}/(es|lib)/style/index\\.css[^/]*$`]; 26 | 27 | export const componentCssMatchers: Matchers = [ 28 | `${libraryName}/(es|lib)/([^/]+)/style/index\\.css[^/]*$`, 29 | 2, 30 | ]; 31 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/src/arco-design-plugin/icon.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | import type { PluginBuild, OnLoadArgs } from 'esbuild'; 3 | 4 | import { iconCjsListMatchers, iconComponentMatchers } from './config'; 5 | import { kebabCaseToPascalCase, pathMatch } from './utils'; 6 | 7 | const filter = new RegExp(`(${iconCjsListMatchers[0]})|(${iconComponentMatchers[0]})`); 8 | 9 | export function loadIcon(id: string, iconBox: string, iconBoxLib: any) { 10 | if (!iconBox || !iconBoxLib) { 11 | return; 12 | } 13 | 14 | // cjs -> es 15 | if (pathMatch(id, iconCjsListMatchers)) { 16 | return `export * from '../es/index.js'`; 17 | } 18 | 19 | let componentName = pathMatch(id, iconComponentMatchers); 20 | if (componentName) { 21 | // icon-edit => IconEdit 22 | componentName = kebabCaseToPascalCase(componentName); 23 | if (iconBoxLib[componentName]) { 24 | return `export { default } from '${iconBox}/esm/${componentName}/index.js'`; 25 | } 26 | } 27 | } 28 | 29 | export function modifyIconConfig(config: UserConfig, iconBox: string, iconBoxLib: any) { 30 | if (!iconBox || !iconBoxLib) { 31 | return; 32 | } 33 | // Pre-Bundling 34 | config.optimizeDeps = config.optimizeDeps || {}; 35 | config.optimizeDeps.esbuildOptions = config.optimizeDeps.esbuildOptions || {}; 36 | config.optimizeDeps.esbuildOptions.plugins = config.optimizeDeps.esbuildOptions.plugins || []; 37 | config.optimizeDeps.esbuildOptions.plugins.push({ 38 | name: 'arcoIconReplace', 39 | setup(build: PluginBuild) { 40 | build.onLoad( 41 | { 42 | namespace: 'file', 43 | filter, 44 | }, 45 | ({ path: id }: OnLoadArgs) => { 46 | const contents = loadIcon(id, iconBox, iconBoxLib); 47 | if (contents) { 48 | return { 49 | contents, 50 | loader: 'js', 51 | }; 52 | } 53 | return null; 54 | } 55 | ); 56 | }, 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/src/arco-design-plugin/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin, ResolvedConfig, UserConfig, ConfigEnv } from 'vite'; 2 | import { modifyCssConfig } from './less'; 3 | import { modifyIconConfig, loadIcon } from './icon'; 4 | import { transformCssFile, transformJsFiles } from './transform'; 5 | 6 | const pkg = require('../../package.json'); 7 | 8 | type Vars = Record; 9 | type Style = boolean | 'css'; 10 | 11 | interface PluginOption { 12 | theme?: string; // Theme package name 13 | iconBox?: string; // Icon library package name 14 | modifyVars?: Vars; // less modifyVars 15 | style?: Style; // Style lazy load 16 | varsInjectScope?: (string | RegExp)[]; // Less vars inject 17 | componentPrefix?: string; 18 | iconPrefix?: string; 19 | } 20 | 21 | export default function vitePluginArcoImport(options: PluginOption = {}): Plugin { 22 | const { 23 | theme = '', 24 | iconBox = '', 25 | modifyVars = {}, 26 | style = true, 27 | varsInjectScope = [], 28 | componentPrefix = 'a', 29 | iconPrefix = 'icon', 30 | } = options; 31 | let styleOptimization: boolean; 32 | let iconBoxLib: any; 33 | let resolvedConfig: ResolvedConfig; 34 | let isDevelopment = false; 35 | 36 | if (iconBox) { 37 | try { 38 | iconBoxLib = require(iconBox); // eslint-disable-line 39 | } catch (e) { 40 | throw new Error(`IconBox ${iconBox} not existed`); 41 | } 42 | } 43 | return { 44 | name: pkg.name, 45 | config(config: UserConfig, { command }: ConfigEnv) { 46 | isDevelopment = command === 'serve'; 47 | // Lay load 48 | styleOptimization = command === 'build'; 49 | 50 | // css preprocessorOptions 51 | modifyCssConfig(pkg.name, config, theme, modifyVars, varsInjectScope); 52 | 53 | // iconbox 54 | modifyIconConfig(config, iconBox, iconBoxLib); 55 | }, 56 | async load(id: string) { 57 | const res = loadIcon(id, iconBox, iconBoxLib); 58 | if (res !== undefined) { 59 | return res; 60 | } 61 | // other ids should be handled as usually 62 | return null; 63 | }, 64 | configResolved(config: ResolvedConfig) { 65 | resolvedConfig = config; 66 | // console.log('viteConfig', resolvedConfig) 67 | }, 68 | transform(code, id) { 69 | // transform css files 70 | const res = transformCssFile({ 71 | code, 72 | id, 73 | theme, 74 | }); 75 | if (res !== undefined) { 76 | return res; 77 | } 78 | 79 | // css lazy load 80 | return transformJsFiles({ 81 | code, 82 | id, 83 | theme, 84 | style, 85 | styleOptimization, 86 | sourceMaps: isDevelopment || Boolean(resolvedConfig?.build?.sourcemap), 87 | componentPrefix, 88 | iconPrefix, 89 | }); 90 | }, 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/src/arco-design-plugin/less.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | import { writeFileSync } from 'fs'; 3 | import { lessMatchers, globalLessMatchers, componentLessMatchers } from './config'; 4 | import { getThemeComponentList, pathMatch, readFileStrSync, parseInclude2RegExp } from './utils'; 5 | 6 | type Vars = Record; 7 | 8 | // eslint-disable-next-line import/prefer-default-export 9 | export function modifyCssConfig( 10 | pkgName: string, 11 | config: UserConfig, 12 | theme: string, 13 | modifyVars: Vars, 14 | varsInjectScope: (string | RegExp)[] 15 | ) { 16 | let modifyLess: string | boolean = ''; 17 | if (theme) { 18 | modifyLess = readFileStrSync(`${theme}/tokens.less`); 19 | if (modifyLess === false) { 20 | throw new Error(`Theme ${theme} not existed`); 21 | } 22 | } 23 | Object.entries(modifyVars).forEach(([k, v]) => { 24 | modifyLess += `@${k}:${v};`; 25 | }); 26 | 27 | config.css = config.css || {}; 28 | config.css.preprocessorOptions = config.css.preprocessorOptions || {}; 29 | 30 | const { preprocessorOptions } = config.css; 31 | preprocessorOptions.less = preprocessorOptions.less || {}; 32 | preprocessorOptions.less.javascriptEnabled = true; 33 | if (modifyLess) { 34 | writeFileSync(`${__dirname}/../../.tokens.less`, modifyLess, { 35 | flag: 'w', 36 | }); 37 | const modifyLessFile = `${pkgName}/.tokens.less`; 38 | const includeRegExp = parseInclude2RegExp(varsInjectScope); 39 | preprocessorOptions.less.plugins = preprocessorOptions.less.plugins || []; 40 | preprocessorOptions.less.plugins.push({ 41 | install(_lessObj: any, pluginManager: any) { 42 | pluginManager.addPreProcessor( 43 | { 44 | process(src: string, extra: any) { 45 | const { 46 | fileInfo: { filename }, 47 | } = extra; 48 | 49 | // arco less vars inject 50 | const varsInjectMatch = 51 | pathMatch(filename, lessMatchers) || 52 | (includeRegExp && pathMatch(filename, [includeRegExp])); 53 | if (!varsInjectMatch) return src; 54 | 55 | if (theme) { 56 | // arco global style 57 | const globalMatch = pathMatch(filename, globalLessMatchers); 58 | if (globalMatch) { 59 | src += `; @import '${theme}/theme.less';`; 60 | } 61 | 62 | // arco component style 63 | const componentName = pathMatch(filename, componentLessMatchers); 64 | if (componentName) { 65 | if (getThemeComponentList(theme).includes(componentName)) { 66 | src += `; @import '${theme}/components/${componentName}/index.less';`; 67 | } 68 | } 69 | } 70 | 71 | src += `; @import '${modifyLessFile}';`; 72 | 73 | return src; 74 | }, 75 | }, 76 | 1000 77 | ); 78 | }, 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/src/arco-design-plugin/transform.ts: -------------------------------------------------------------------------------- 1 | import * as parser from '@babel/parser'; 2 | import * as types from '@babel/types'; 3 | import traverse, { NodePath } from '@babel/traverse'; 4 | import generate from '@babel/generator'; 5 | 6 | import { libraryName, fullCssMatchers } from './config'; 7 | import { 8 | getComponentConfig, 9 | importComponent, 10 | importStyle, 11 | kebabCaseToPascalCase, 12 | pathMatch, 13 | readFileStrSync, 14 | } from './utils'; 15 | 16 | interface Specifier { 17 | imported: { 18 | name: string; 19 | }; 20 | } 21 | 22 | type TransformedResult = undefined | { code: string; map: any }; 23 | 24 | type Style = boolean | 'css'; 25 | 26 | export function transformCssFile({ 27 | id, 28 | theme, 29 | }: { 30 | code: string; 31 | id: string; 32 | theme: string; 33 | }): TransformedResult { 34 | if (theme) { 35 | const matches = pathMatch(id, fullCssMatchers); 36 | if (matches) { 37 | const themeCode = readFileStrSync(`${theme}/css/arco.css`); 38 | if (themeCode !== false) { 39 | return { 40 | code: themeCode, 41 | map: null, 42 | }; 43 | } 44 | } 45 | } 46 | return undefined; 47 | } 48 | 49 | export function transformJsFiles({ 50 | code, 51 | id, 52 | theme, 53 | style, 54 | styleOptimization, 55 | sourceMaps, 56 | componentPrefix, 57 | iconPrefix, 58 | }: { 59 | code: string; 60 | id: string; 61 | theme?: string; 62 | style: Style; 63 | styleOptimization: boolean; 64 | sourceMaps: boolean; 65 | componentPrefix: string; 66 | iconPrefix: string; 67 | }): TransformedResult { 68 | if (style === false || !/\.(js|jsx|ts|tsx|vue)$/.test(id)) { 69 | return undefined; 70 | } 71 | const ast = parser.parse(code, { 72 | sourceType: 'module', 73 | plugins: ['jsx'], 74 | }) as any; 75 | 76 | traverse(ast, { 77 | enter(path: NodePath) { 78 | const { node } = path; 79 | // 80 | if (types.isCallExpression(node)) { 81 | const { callee, arguments: args } = node as any; 82 | const funcName = callee.name; 83 | const importedName = args?.[0]?.value; 84 | // a-input-number => InputNumber 85 | // AInputNumber => InputNumber 86 | let componentName: string; 87 | if (typeof importedName === 'string') { 88 | // to PascalCase 89 | componentName = kebabCaseToPascalCase(importedName); 90 | const componentRegExp = new RegExp(`^${kebabCaseToPascalCase(componentPrefix)}[A-Z]`); 91 | const iconComponentRegExp = new RegExp(`^${kebabCaseToPascalCase(iconPrefix)}[A-Z]`); 92 | // restore component name 93 | if (componentRegExp.test(componentName)) { 94 | componentName = componentName.replace(componentRegExp, (match) => match.slice(-1)); 95 | } 96 | // restore icon component name 97 | else if (iconComponentRegExp.test(componentName)) { 98 | componentName = `Icon${componentName.replace(iconComponentRegExp, (match) => 99 | match.slice(-1) 100 | )}`; 101 | } else { 102 | return; 103 | } 104 | } 105 | if ( 106 | !componentName || 107 | !['_resolveComponent', '_resolveDynamicComponent'].includes(funcName) 108 | ) { 109 | return; 110 | } 111 | const componentConfig = getComponentConfig(libraryName, componentName); 112 | if (componentConfig) { 113 | // the following import logic will be triggered here, so there is no need to import style 114 | importComponent({ 115 | path, 116 | componentDir: componentConfig.dir, 117 | componentName, 118 | }); 119 | } 120 | return; 121 | } 122 | 123 | // import { Button, InputNumber, TimeLine } from '@arco-design/web-vue' 124 | if (types.isImportDeclaration(node)) { 125 | const { value } = node.source; 126 | const dirs: string[] = []; 127 | if (value === libraryName) { 128 | node.specifiers.forEach((spec) => { 129 | if (types.isImportSpecifier(spec)) { 130 | const importedName = (spec as Specifier).imported.name; 131 | const componentConfig = getComponentConfig(libraryName, importedName); 132 | if (componentConfig?.styleDir) { 133 | dirs.push(componentConfig.styleDir); 134 | } 135 | } 136 | }); 137 | importStyle({ 138 | componentDirs: dirs, 139 | styleOptimization, 140 | path, 141 | style, 142 | theme, 143 | libraryName, 144 | }); 145 | } 146 | } 147 | }, 148 | }); 149 | 150 | return generate(ast, { sourceMaps, sourceFileName: id }); 151 | } 152 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/src/arco-design-plugin/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.json'; 2 | declare module '@babel/helper-module-imports'; 3 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/src/arco-design-plugin/utils.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, readdirSync } from 'fs'; 2 | import { dirname, extname, resolve, sep, win32, posix, join } from 'path'; 3 | import { addNamed, addSideEffect } from '@babel/helper-module-imports'; 4 | import traverse, { NodePath } from '@babel/traverse'; 5 | import { parse } from '@babel/parser'; 6 | import { isExportSpecifier, isIdentifier, isStringLiteral } from '@babel/types'; 7 | 8 | type Style = boolean | 'css'; 9 | 10 | // read file content 11 | export function readFileStrSync(path: string): false | string { 12 | try { 13 | const resolvedPath = require.resolve(path); 14 | return readFileSync(resolvedPath).toString(); 15 | } catch (error) { 16 | return false; 17 | } 18 | } 19 | 20 | // the theme package's component list 21 | const componentsListObj: Record = {}; 22 | export function getThemeComponentList(theme: string) { 23 | if (!theme) return []; 24 | if (!componentsListObj[theme]) { 25 | try { 26 | const packageRootDir = dirname(require.resolve(`${theme}/package.json`)); 27 | const dirPath = `${packageRootDir}/components`; 28 | componentsListObj[theme] = readdirSync(dirPath) || []; 29 | } catch (error) { 30 | componentsListObj[theme] = []; 31 | } 32 | } 33 | return componentsListObj[theme]; 34 | } 35 | 36 | export const parse2PosixPath = (path: string) => 37 | sep === win32.sep ? path.replaceAll(win32.sep, posix.sep) : path; 38 | 39 | // filePath match 40 | export function pathMatch(path: string, conf: [string | RegExp, number?]): false | string { 41 | const [regStr, order = 0] = conf; 42 | const reg = new RegExp(regStr); 43 | const posixPath = parse2PosixPath(path); 44 | const matches = posixPath.match(reg); 45 | if (!matches) return false; 46 | return matches[order]; 47 | } 48 | 49 | export function parseInclude2RegExp(include: (string | RegExp)[] = [], context?: string) { 50 | if (include.length === 0) return false; 51 | context = context || process.cwd(); 52 | const regStrList = []; 53 | const folders = include 54 | .map((el) => { 55 | if (el instanceof RegExp) { 56 | const regStr = el.toString(); 57 | if (regStr.slice(-1) === '/') { 58 | regStrList.push(`(${regStr.slice(1, -1)})`); 59 | } 60 | return false; 61 | } 62 | const absolutePath = parse2PosixPath(resolve(context, el)); 63 | const idx = absolutePath.indexOf('/node_modules/'); 64 | const len = '/node_modules/'.length; 65 | const isFolder = extname(absolutePath) === ''; 66 | if (idx > -1) { 67 | const prexPath = absolutePath.slice(0, idx + len); 68 | const packagePath = absolutePath.slice(idx + len); 69 | return `(${prexPath}(\\.pnpm/.+/)?${packagePath}${isFolder ? '/' : ''})`; 70 | } 71 | return `(${absolutePath}${isFolder ? '/' : ''})`; 72 | }) 73 | .filter((el) => el !== false); 74 | if (folders.length) { 75 | regStrList.push(`(^${folders.join('|')})`); 76 | } 77 | if (regStrList.length > 0) { 78 | return new RegExp(regStrList.join('|')); 79 | } 80 | return false; 81 | } 82 | 83 | export function isPascalCase(name: string) { 84 | return /^[A-Z][A-Za-z]*$/.test(name); 85 | } 86 | 87 | // kebab-case to PascalCase 88 | export function kebabCaseToPascalCase(name: string) { 89 | return name.replace(/(^|-)([A-Za-z])/g, (_match, _p1, p2) => p2.toUpperCase()); 90 | } 91 | 92 | // component config 93 | const componentConfigRecord: { 94 | [libraryName: string]: { 95 | [componentName: string]: { dir: string; styleDir?: string } | undefined; 96 | }; 97 | } = {}; 98 | export function getComponentConfig(libraryName: string, componentName: string) { 99 | if (!componentConfigRecord[libraryName]) { 100 | componentConfigRecord[libraryName] = {}; 101 | try { 102 | const packageRootDir = dirname(require.resolve(`${libraryName}/package.json`)); 103 | // generate component config 104 | const indexDeclaration = readFileSync(join(packageRootDir, 'es/index.d.ts'), 'utf8'); 105 | const indexDeclarationAst = parse(indexDeclaration, { 106 | sourceType: 'module', 107 | plugins: ['typescript'], 108 | }); 109 | traverse(indexDeclarationAst, { 110 | ExportNamedDeclaration: ({ node }) => { 111 | // when the exported item is a value (non type) 112 | if (node.exportKind === 'value' && isStringLiteral(node.source)) { 113 | const componentDir = join(libraryName, 'es', node.source.value).replace(/\\/g, '/'); 114 | node.specifiers.forEach((item) => { 115 | if (isExportSpecifier(item) && isIdentifier(item.exported)) { 116 | const _componentName = item.exported.name; 117 | // check whether it is a component 118 | const isComponent = isPascalCase(_componentName); 119 | if (isComponent) { 120 | componentConfigRecord[libraryName][_componentName] = { 121 | dir: libraryName, 122 | styleDir: `${componentDir}/style`, 123 | }; 124 | } 125 | } 126 | }); 127 | } 128 | }, 129 | }); 130 | // generate icon component config 131 | readdirSync(join(packageRootDir, 'es/icon'), { withFileTypes: true }) 132 | .filter((file) => file.isDirectory()) 133 | .map((file) => file.name) 134 | .forEach((fileName) => { 135 | // icon-github => IconGithub 136 | const _componentName = kebabCaseToPascalCase(fileName); 137 | componentConfigRecord[libraryName][_componentName] = { 138 | dir: `${libraryName}/es/icon`, 139 | }; 140 | }); 141 | // eslint-disable-next-line no-empty 142 | } catch (error) {} 143 | } 144 | return componentConfigRecord[libraryName][componentName]; 145 | } 146 | 147 | export function importComponent({ 148 | path, 149 | componentDir, 150 | componentName, 151 | }: { 152 | path: NodePath; 153 | componentDir: string; 154 | componentName: string; 155 | }) { 156 | const imported = addNamed(path, componentName, componentDir); 157 | path.replaceWith(imported); 158 | } 159 | 160 | export function importStyle({ 161 | componentDirs, 162 | styleOptimization, 163 | path, 164 | style, 165 | theme, 166 | libraryName, 167 | }: { 168 | componentDirs: string[]; 169 | styleOptimization: boolean; 170 | path: NodePath; 171 | style: Style; 172 | theme: string; 173 | libraryName: string; 174 | }) { 175 | if (componentDirs.length === 0) return; 176 | // lazy load (css files don't support lazy load with theme) 177 | if (styleOptimization && (style !== 'css' || !theme)) { 178 | componentDirs.forEach((dir) => { 179 | const stylePath = `${dir}/${style === 'css' ? 'css.js' : 'index.js'}`; 180 | addSideEffect(path, stylePath); 181 | }); 182 | } 183 | // import css bundle file 184 | else if (style === 'css') { 185 | addSideEffect(path, `${libraryName}/dist/arco.min.css`); 186 | } 187 | // import less bundle file 188 | else { 189 | addSideEffect(path, `${libraryName}/dist/arco.less`); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/src/index.ts: -------------------------------------------------------------------------------- 1 | import vitePluginForArco from './arco-design-plugin'; 2 | 3 | export default vitePluginForArco; 4 | 5 | export { vitePluginForArco }; 6 | -------------------------------------------------------------------------------- /packages/plugin-vite-vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "outDir": "./lib", 5 | "declarationDir": "./types", 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "experimentalDecorators": true, 9 | "allowSyntheticDefaultImports": true, 10 | "noUnusedParameters": true, 11 | "noUnusedLocals": true, 12 | "module": "commonjs", 13 | "target": "esnext", 14 | "declaration": true, 15 | "lib": ["esnext", "dom"], 16 | "types": ["node"], 17 | "resolveJsonModule": true 18 | }, 19 | "include": ["src"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.8 2 | 3 | `2023-02-08` 4 | 5 | 🐛 BugFix 6 | 7 | - Fix babel custom configuration issues 8 | 9 | 10 | ## 1.4.4 11 | 12 | `2022-10-19` 13 | 14 | 🐛 BugFix 15 | 16 | - Fix `!` recognition issue in style loading 17 | 18 | 19 | ## 1.4.2 20 | 21 | `2022-09-28` 22 | 23 | 💎 Enhancement 24 | 25 | - Optimization When the user already has babel-loader, modify the babel configuration directly instead of introducing a new loader 26 | 27 | 🐛 BugFix 28 | 29 | - Fixed the bug that the user-defined babel plugin could not get the source code path due to the introduction of the plugin 30 | 31 | 32 | ## 1.4.1 33 | 34 | `2022-06-22` 35 | 36 | 37 | 💎 Enhancement 38 | 39 | - Built-in adaptation to pnpm's path format 40 | 41 | ## 1.4.0 42 | 43 | `2022-06-22` 44 | 45 | 46 | 💎 Enhancement 47 | 48 | - `include` and `varsInjectScope` supports regular expressions. 49 | 50 | ## 1.3.1 51 | 52 | `2022-03-18` 53 | 54 | 55 | 🐛 BugFix 56 | 57 | - Import theme fails when using pnpm. 58 | 59 | ## 1.3.0 60 | 61 | `2022-03-17` 62 | 63 | ### 🆕 Feature 64 | 65 | - Replace property `themeOption` with `varsInjectScope` 66 | 67 | ## 1.2.0 68 | 69 | `2022-03-17` 70 | 71 | ### 🆕 Feature 72 | 73 | - Add property `themeOption` 74 | 75 | ## 1.1.2 76 | 77 | `2022-02-21` 78 | 79 | 80 | 🐛 BugFix 81 | 82 | - `transform-loader` should be load after `ts-loader` 83 | 84 | ## 1.1.1 85 | 86 | `2022-02-15` 87 | 88 | 89 | 🐛 BugFix 90 | 91 | - Fix the problem of getCompilationHooks is undefined 92 | - 93 | ## 1.1.0 94 | 95 | `2022-02-14` 96 | 97 | ### 🆕 Feature 98 | 99 | - Support specifying webpack implementation by `webpackImplementation` 100 | 101 | 🐛 BugFix 102 | 103 | - Fix the problem of cannot find @babel/preset-typescript 104 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/CHANGELOG.zh-CN.md: -------------------------------------------------------------------------------- 1 | ## 1.4.8 2 | 3 | `2023-02-08` 4 | 5 | 🐛 BugFix 6 | 7 | - 修复 babel 自定义配置的问题 8 | 9 | 10 | ## 1.4.4 11 | 12 | `2022-10-19` 13 | 14 | 🐛 BugFix 15 | 16 | - 修复样式加载中的 `!` 识别问题 17 | 18 | 19 | ## 1.4.2 20 | 21 | `2022-09-28` 22 | 23 | 💎 Enhancement 24 | 25 | - 优化在用户已存在 babel-loader 时,直接修改babel 配置,而非引入新的 loader 26 | 27 | 🐛 BugFix 28 | 29 | - 修复插件引入导致用户自定义 babel 插件无法获取到源码路径的 bug 30 | 31 | 32 | ## 1.4.1 33 | 34 | `2022-06-22` 35 | 36 | 37 | 💎 Enhancement 38 | 39 | - 内置对 `pnpm` 的路径格式的适配 40 | 41 | ## 1.4.0 42 | 43 | `2022-06-22` 44 | 45 | 46 | 💎 Enhancement 47 | 48 | - `include` 和 `varsInjectScope` 支持正则表达式。 49 | 50 | ## 1.3.1 51 | 52 | `2022-03-18` 53 | 54 | 55 | 🐛 BugFix 56 | 57 | - 使用 pnpm 时导入主题失败 58 | 59 | ## 1.3.0 60 | 61 | `2022-03-17` 62 | 63 | ### 🆕 新增功能 64 | 65 | - 属性 `themeOption` 替换为 `varsInjectScope` 66 | 67 | ## 1.2.0 68 | 69 | `2022-03-17` 70 | 71 | ### 🆕 新增功能 72 | 73 | - 新增属性 `themeOption` 74 | 75 | ## 1.1.2 76 | 77 | `2022-02-21` 78 | 79 | 80 | 🐛 BugFix 81 | 82 | - `transform-loader` 应该在 `ts-loader` 之后才加载 83 | 84 | ## 1.1.1 85 | 86 | `2022-02-15` 87 | 88 | 89 | 🐛 BugFix 90 | 91 | - 修复 getCompilationHooks 不存在的问题 92 | 93 | ## 1.1.0 94 | 95 | `2022-02-14` 96 | 97 | ### 🆕 新增功能 98 | 99 | - 支持通过 `webpackImplementation` 指定使用的 webpack 实例 100 | 101 | 🐛 问题修复 102 | 103 | - 修复找不到 `@babel/preset-typescript` 的问题 104 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/README.md: -------------------------------------------------------------------------------- 1 | # @arco-plugins/webpack-react 2 | 3 | `@arco-plugins/webpack-react` is a webpack plugin to help deal with arco usage issues. 4 | 5 | ## Feature 6 | 7 | 8 | 1. `Style lazy load`:Add babel-plugin-import to babel-loader to enable style lazy loading。*If there is a babel-plugin-import for arco-design/web-react, it will be replaced*。 9 | 2. `Theme import`:Specify the theme package name, the plugin will read the variable content of the theme package and inject it into modifyVars of less-loader。 10 | 3. `Remove the font package that comes with the component library`:Set `removeFontFace` to `true` to remove the font file that comes with the component library。 11 | 4. `Icon replacement`:Specify the package name of the icon library, the plug-in will read the icon in the package and replace the icon with the same name used in the component library.。 12 | 5. `Replace default language`:The default imported language pack of the component library is Chinese, which determines that Chinese will be included in the packaged product. If you don't want Chinese, you can use this parameter to replace it with the language you need. 13 | 6. `Get lazy load babel plugin configuration`:The implementation of on-demand loading is achieved by injecting babel-plugin-import configuration into babel. These configurations are open for everyone to use and can be obtained through `(new ArcoWebpackPlugin()).pluginForImport().getBabelPlugins()`. 14 | 15 | ## Install 16 | 17 | Use npm/yarn to install the plugin: 18 | 19 | ```shell 20 | # npm 21 | $ npm install -D @arco-plugins/webpack-react 22 | 23 | # yarn 24 | $ yarn add @arco-plugins/webpack-react 25 | ``` 26 | 27 | ## Usage 28 | 29 | To to the plugin, add the following code in webpack config: 30 | 31 | ```js 32 | const ArcoWebpackPlugin = require('@arco-plugins/webpack-react'); 33 | 34 | // webpack config 35 | { 36 | plugins: [ 37 | new ArcoWebpackPlugin(options) 38 | ] 39 | } 40 | ``` 41 | ## options 42 | 43 | The plugin supports the following parameters: 44 | 45 | |Params|Type|Default Value|Description| 46 | |:--:|:--:|:-----:|:----------| 47 | |**`include`**|`{(String\|RegExp)[]}`|`['src']`|File directory used by bebel-plugin-import| 48 | |**`extensions`**|`{String[]}`|`['js', 'jsx', 'ts', 'tsx']`| File suffix used by bebel-plugin-import | 49 | |**`theme`**|`{String}`|`-`|Theme package name| 50 | |**`iconBox`**|`{String}`|`-`|Icon library package name| 51 | |**`modifyVars`**|`{Object}`|`{}`|Less variables| 52 | |**`style`**|`{String\|Boolean}`|`true`| Style import method| 53 | |**`removeFontFace`**|`{Boolean}`|`false`| Whether to remove the font file that comes with the component library | 54 | |**`defaultLanguage`**|`{string}`|`-`| Replace the default language,[language list](https://arco.design/react/docs/i18n#%E6%94%AF%E6%8C%81%E7%9A%84%E8%AF%AD%E8%A8%80)| 55 | |**`webpackImplementation`**|`{webpack}`|`-`| Specifying which webpack implementation to use | 56 | |**`varsInjectScope`**|`{(String\|RegExp)[]}`|`-`| Scope of injection of less variables (modifyVars and the theme package's variables) | 57 | |**`modifyBabelLoader`**|`{Boolean}`|`true`| Whether to inject babel-plugin-import for babel-loader or adding a new loader | 58 | 59 | **Style import methods ** 60 | 61 | `style: true` will import less file 62 | 63 | ```js 64 | import '@arco-design/web-react/Affix/style' 65 | ``` 66 | 67 | `style: 'css'` will import css file 68 | 69 | ```js 70 | import '@arco-design/web-react/Affix/style/css' 71 | ``` 72 | 73 | `style: false` will not import any style file 74 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # @arco-plugins/webpack-react 2 | 3 | `@arco-plugins/webpack-react` 是协助处理 arco 使用问题的 webpack 插件。 4 | 5 | ## 特性 6 | 7 | 插件的功能如下: 8 | 9 | 1. `样式按需加载`:为 babel-loader 添加 babel-plugin-import 实现样式的按需加载。*如果原先就有针对 arco-design/web-react 的 babel-plugin-import 将会被替换*。 10 | 2. `主题引入`:指定主题包名,插件会读取主题包的变量内容注入到 less-loader 的 modifyVars。 11 | 3. `移除组件库自带的字体包`:指定 `removeFontFace` 为 `true` 可以去掉组件库自带的字体文件。 12 | 4. `图标替换`:指定图标库的包名,插件会读取包内图标对组件库内用到的同名的图标进行替换。 13 | 5. `替换默认语言`:组件库的默认导入的语言包是中文,这就决定了打包产物中一定会包含中文,如果不想要中文,就可以利用这个参数来将其替换为你需要的语言。 14 | 6. `获取按需加载的babel插件配置`:按需加载的实现是通过往 babel 注入 babel-plugin-import 配置来实现,这些配置开放出来供大家取用,可通过 `(new ArcoWebpackPlugin()).pluginForImport().getBabelPlugins()` 获取。 15 | 16 | ## 安装 17 | 18 | 通过 npm/yarn 安装这个工具: 19 | 20 | ```shell 21 | # npm 22 | $ npm install -D @arco-plugins/webpack-react 23 | 24 | # yarn 25 | $ yarn add @arco-plugins/webpack-react 26 | ``` 27 | 28 | ## 用法 29 | 30 | 使用方式是在 webpack config 文件中加入以下内容: 31 | 32 | ```js 33 | const ArcoWebpackPlugin = require('@arco-plugins/webpack-react'); 34 | 35 | // webpack config 36 | { 37 | plugins: [ 38 | new ArcoWebpackPlugin(options) 39 | ] 40 | } 41 | ``` 42 | ## options 43 | 44 | 插件支持以下参数: 45 | 46 | |参数名|类型|默认值|描述| 47 | |:--:|:--:|:-----:|:----------| 48 | |**`include`**|`{(String\|RegExp)[]}`|`['src']`|bebel-plugin-import 作用的文件目录| 49 | |**`extensions`**|`{String[]}`|`['js', 'jsx', 'ts', 'tsx']`| bebel-plugin-import 作用的文件后缀 | 50 | |**`theme`**|`{String}`|`-`|主题包名| 51 | |**`iconBox`**|`{String}`|`-`|图标库包名| 52 | |**`modifyVars`**|`{Object}`|`{}`|less 变量| 53 | |**`style`**|`{String\|Boolean}`|`true`| 样式引入方式| 54 | |**`removeFontFace`**|`{Boolean}`|`false`| 去掉组件库自带的字体文件 | 55 | |**`defaultLanguage`**|`{string}`|`-`| 替换默认的语言,[语言列表](https://arco.design/react/docs/i18n#%E6%94%AF%E6%8C%81%E7%9A%84%E8%AF%AD%E8%A8%80) | 56 | |**`webpackImplementation`**|`{webpack}`|`-`| 指定使用的 webpack 实例 | 57 | |**`varsInjectScope`**|`{(String\|RegExp)[]}`|`-`| less 变量(modifyVars 和主题包的变量)注入的范围 | 58 | |**`modifyBabelLoader`**|`{Boolean}`|`true`| 如果有 babel-loader 则添加 babel-plugin-import 插件,否则添加新的 loader | 59 | 60 | **样式引入方式** 61 | 62 | `style: true` 将引入 less 文件 63 | 64 | ```js 65 | import '@arco-design/web-react/Affix/style' 66 | ``` 67 | 68 | `style: 'css'` 将引入 css 文件 69 | 70 | ```js 71 | import '@arco-design/web-react/Affix/style/css' 72 | ``` 73 | 74 | `style: false` 不引入样式 75 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@arco-plugins/webpack-react", 3 | "version": "1.4.9-beta.2", 4 | "description": "arco webpack plugin", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "lib" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:arco-design/arco-plugins.git", 13 | "directory": "packages/plugin-webpack-react" 14 | }, 15 | "scripts": { 16 | "build": "rm -rf lib && tsc", 17 | "prepublishOnly": "npm run build" 18 | }, 19 | "keywords": [ 20 | "arco", 21 | "arco-design", 22 | "arco-plugin", 23 | "plugin", 24 | "webpack" 25 | ], 26 | "author": "arco-design", 27 | "license": "MIT", 28 | "dependencies": { 29 | "@arco-plugins/utils": "^1.2.1", 30 | "@babel/core": "^7.16.0", 31 | "@babel/generator": "^7.16.0", 32 | "@babel/parser": "^7.16.4", 33 | "@babel/preset-react": "^7.16.0", 34 | "@babel/preset-typescript": "^7.16.0", 35 | "@babel/traverse": "^7.16.3", 36 | "babel-plugin-import": "^1.13.3", 37 | "chalk": "^4.1.0", 38 | "loader-utils": "^2.0.2", 39 | "lodash": "^4.17.20" 40 | }, 41 | "devDependencies": { 42 | "@types/loader-utils": "^2.0.3", 43 | "@types/lodash": "^4.14.180", 44 | "@types/node": "^17.0.21", 45 | "typescript": "^4.5.2", 46 | "webpack": "^5.70.0" 47 | }, 48 | "peerDependencies": { 49 | "webpack": "^4.0.0 || ^5.0.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/config/babel.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | presets: [ 3 | [ 4 | require.resolve('@babel/preset-typescript'), 5 | { 6 | allExtensions: true, 7 | isTSX: true, 8 | }, 9 | ], 10 | [require.resolve('@babel/preset-react')], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/config/index.ts: -------------------------------------------------------------------------------- 1 | export const PLUGIN_NAME = 'ARCO_WEBPACK_PLUGIN'; 2 | export const ARCO_DESIGN_COMPONENT_NAME = '@arco-design/web-react'; 3 | export const ARCO_DESIGN_ICON_NAME = '@arco-design/web-react/icon'; 4 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/config/matchers.ts: -------------------------------------------------------------------------------- 1 | import { ARCO_DESIGN_COMPONENT_NAME } from '.'; 2 | 3 | export const lessMatchers = `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/**/*.less`; 4 | export const globalLessMatchers = `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/{es,lib}/style/index.less`; 5 | export const globalCssMatchers = `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/{es,lib}/style/index.css`; 6 | export const esLocalDefaultMatchers = `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/es/locale/default.js`; 7 | export const libLocalDefaultMatchers = `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/lib/locale/default.js`; 8 | export const componentLessMatchers = (componentName) => 9 | `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/{es,lib}/${componentName}/style/index.less`; 10 | export const componentCssMatchers = (componentName) => 11 | `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/{es,lib}/${componentName}/style/index.css`; 12 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/index.ts: -------------------------------------------------------------------------------- 1 | import type { WebpackPluginInstance } from 'webpack'; 2 | 3 | import { merge } from 'lodash'; 4 | import { ThemePlugin } from './plugin-for-theme'; 5 | import { ImportPlugin } from './plugin-for-import'; 6 | import { RemoveFontFacePlugin } from './plugin-for-remove-font-face'; 7 | import { ReplaceIconPlugin } from './plugin-for-replace-icon'; 8 | import { ReplaceDefaultLanguagePlugin } from './plugin-for-replace-default-language'; 9 | import { ArcoDesignPluginOptions } from './interface'; 10 | 11 | export class ArcoDesignPlugin { 12 | options: ArcoDesignPluginOptions; 13 | themePluginInstance: WebpackPluginInstance; 14 | importPluginInstance: WebpackPluginInstance; 15 | pluginForImport?: WebpackPluginInstance; 16 | removeFontFacePluginInstance?: WebpackPluginInstance; 17 | replaceIconPluginInstance?: WebpackPluginInstance; 18 | replaceDefaultLanguagePluginInstance?: WebpackPluginInstance; 19 | constructor(options: Partial = {}) { 20 | const resolveOptions = merge( 21 | { 22 | include: ['src'], 23 | extensions: ['js', 'jsx', 'ts', 'tsx'], 24 | style: true, 25 | libraryDirectory: 'es', 26 | modifyBabelLoader: true, 27 | }, 28 | options 29 | ); 30 | global.arcoDesignPlugin = { options: resolveOptions }; 31 | this.options = resolveOptions; 32 | this.themePluginInstance = new ThemePlugin(resolveOptions); 33 | this.importPluginInstance = new ImportPlugin(resolveOptions); 34 | // Compatible with `(new ArcoWebpackPlugin()).pluginForImport.getBabelPlugins()` usage 35 | this.pluginForImport = this.importPluginInstance; 36 | if (resolveOptions.removeFontFace) { 37 | this.removeFontFacePluginInstance = new RemoveFontFacePlugin(); 38 | } 39 | if (resolveOptions.iconBox) { 40 | this.replaceIconPluginInstance = new ReplaceIconPlugin(resolveOptions); 41 | } 42 | if (resolveOptions.defaultLanguage) { 43 | this.replaceDefaultLanguagePluginInstance = new ReplaceDefaultLanguagePlugin(resolveOptions); 44 | } 45 | } 46 | 47 | apply(compiler) { 48 | this.themePluginInstance.apply(compiler); 49 | this.importPluginInstance.apply(compiler); 50 | this.removeFontFacePluginInstance?.apply(compiler); 51 | this.replaceIconPluginInstance?.apply(compiler); 52 | this.replaceDefaultLanguagePluginInstance?.apply(compiler); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/interface.ts: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | 3 | export interface ArcoDesignPluginOptions { 4 | context?: string; 5 | include: (string | RegExp)[]; 6 | extensions: string[]; 7 | style: string | boolean; 8 | libraryDirectory: string; 9 | iconBox?: string; 10 | babelConfig?: object; 11 | removeFontFace?: boolean; 12 | defaultLanguage?: string; 13 | theme?: string; 14 | modifyVars?: Record; 15 | webpackImplementation?: typeof webpack; 16 | varsInjectScope?: (string | RegExp)[]; 17 | modifyBabelLoader?: boolean | 'merge' | 'override'; 18 | } 19 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/loaders/append.ts: -------------------------------------------------------------------------------- 1 | import { getOptions } from 'loader-utils'; 2 | import { LoaderDefinitionFunction } from 'webpack'; 3 | 4 | export const AppendLoader: LoaderDefinitionFunction = function (source) { 5 | const options = getOptions(this); 6 | const additionContent = `${options.additionContent}`; 7 | 8 | if (!additionContent) return source; 9 | 10 | return ` 11 | ${source}\n 12 | ${additionContent.replace(/__ARCO_PLACEHOLDER__/g, '!')}\n 13 | `; 14 | }; 15 | 16 | module.exports = AppendLoader; 17 | module.exports.default = AppendLoader; 18 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/loaders/remove-font-face.ts: -------------------------------------------------------------------------------- 1 | import { LoaderDefinitionFunction } from 'webpack'; 2 | 3 | const fontFaceRegex = /@font-face(.*)?{[^}]*?}/g; 4 | 5 | export const RemoveFontFaceLoader: LoaderDefinitionFunction = function (source) { 6 | const res = source.replace(fontFaceRegex, ''); 7 | return `${res}`; 8 | }; 9 | 10 | module.exports = RemoveFontFaceLoader; 11 | module.exports.default = RemoveFontFaceLoader; 12 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/loaders/replace-default-language.ts: -------------------------------------------------------------------------------- 1 | import { parse } from '@babel/parser'; 2 | import { getOptions } from 'loader-utils'; 3 | import traverse from '@babel/traverse'; 4 | import generate from '@babel/generator'; 5 | import { LoaderDefinitionFunction } from 'webpack'; 6 | 7 | interface ReplaceDefaultLanguageLoaderOptions { 8 | type: 'es' | 'lib'; 9 | defaultLanguage: string; 10 | } 11 | 12 | // 替换默认语言包 13 | const ReplaceDefaultLanguageLoader: LoaderDefinitionFunction = function (resource) { 14 | const options = getOptions(this); 15 | const { type = 'es', defaultLanguage } = 16 | options as unknown as ReplaceDefaultLanguageLoaderOptions; 17 | const ast = parse(resource, { 18 | sourceType: type === 'es' ? 'module' : 'script', 19 | }); 20 | 21 | const replaceLanguage = (source: string) => { 22 | return source.replace(/([a-zA-Z-]+)(.js|.ts)?$/, defaultLanguage); 23 | }; 24 | 25 | if (type === 'lib') { 26 | traverse(ast, { 27 | CallExpression(path) { 28 | const { node } = path; 29 | const { callee } = node; 30 | if (callee.name === 'require') { 31 | node.arguments[0].value = replaceLanguage(node.arguments[0].value); 32 | } 33 | }, 34 | }); 35 | } 36 | 37 | if (type === 'es') { 38 | traverse(ast, { 39 | ImportDeclaration(path) { 40 | const { node } = path; 41 | const { source } = node; 42 | source.value = replaceLanguage(source.value); 43 | }, 44 | }); 45 | } 46 | 47 | return generate(ast).code; 48 | }; 49 | 50 | module.exports = ReplaceDefaultLanguageLoader; 51 | module.exports.default = ReplaceDefaultLanguageLoader; 52 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/loaders/replace-icon.ts: -------------------------------------------------------------------------------- 1 | import { getOptions } from 'loader-utils'; 2 | import { LoaderDefinitionFunction } from 'webpack'; 3 | 4 | // 替换Arco默认图标 5 | const ReplaceIconLoader: LoaderDefinitionFunction = function (content) { 6 | const { iconBoxLib, iconBoxDirname } = getOptions(this) as unknown as { 7 | iconBoxLib: Record; 8 | iconBoxDirname: string; 9 | }; 10 | 11 | if (!iconBoxLib) { 12 | return content; 13 | } 14 | 15 | const matches = this.resourcePath.match( 16 | /@arco-design\/web-react\/icon\/react-icon\/([^/]+)\/index\.js$/ 17 | ); 18 | if (matches && iconBoxLib[matches[1]]) { 19 | return `export {default} from '${iconBoxDirname}/esm/${matches[1]}/index.js';`; 20 | } 21 | return content; 22 | }; 23 | 24 | module.exports = ReplaceIconLoader; 25 | module.exports.default = ReplaceIconLoader; 26 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/loaders/transform-import.ts: -------------------------------------------------------------------------------- 1 | import { getOptions } from 'loader-utils'; 2 | import { LoaderDefinitionFunction } from 'webpack'; 3 | import { transformImport } from '../utils/transform-import'; 4 | 5 | export const TransformImportLoader: LoaderDefinitionFunction = function (source) { 6 | const options = getOptions(this); 7 | 8 | const babelConfig = { 9 | filename: this.resourcePath, 10 | sourceFileName: this.resourcePath, 11 | }; 12 | if (typeof options.babelConfig === 'object') { 13 | Object.assign(babelConfig, options.babelConfig); 14 | } 15 | 16 | const code = transformImport(source, { 17 | ...options, 18 | babelConfig, 19 | }); 20 | 21 | return code; 22 | }; 23 | 24 | module.exports = TransformImportLoader; 25 | module.exports.default = TransformImportLoader; 26 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/plugin-for-import.ts: -------------------------------------------------------------------------------- 1 | import { arrify, pathUtils } from '@arco-plugins/utils'; 2 | import { Compiler } from 'webpack'; 3 | import { 4 | getBabelPlugins, 5 | modifyBabelLoaderMerge, 6 | modifyBabelLoaderOverride, 7 | } from './utils/transform-import'; 8 | import { getLoader, hookNormalModuleLoader, getLoaderIndex, getContext } from './utils'; 9 | import { PLUGIN_NAME } from './config'; 10 | import { ArcoDesignPluginOptions } from './interface'; 11 | /** 12 | * 给匹配的文件的 babel-loader 增加 babel-plugin-import 13 | */ 14 | export class ImportPlugin { 15 | options: ArcoDesignPluginOptions; 16 | 17 | babelPlugins: any[]; 18 | 19 | constructor(options: ArcoDesignPluginOptions) { 20 | this.options = options; 21 | this.babelPlugins = getBabelPlugins(this.options); 22 | } 23 | 24 | apply(compiler: Compiler) { 25 | const { include } = this.options; 26 | const context = getContext(compiler, this.options.context); 27 | const extensions = arrify(this.options.extensions); 28 | const isMatch = pathUtils.matcher(include, { extensions, cwd: context }); 29 | const loader = require.resolve('./loaders/transform-import'); 30 | hookNormalModuleLoader(compiler, PLUGIN_NAME, (_loaderContext, module, resource) => { 31 | if (isMatch(resource) && !getLoader(module.loaders, loader)) { 32 | const loaders = module.loaders; 33 | const babelLoaderIndex = getLoaderIndex(loaders, 'babel-loader'); 34 | const tsLoaderIndex = getLoaderIndex(loaders, 'ts-loader'); 35 | const shouldModifyBabelLoader = this.options.modifyBabelLoader && babelLoaderIndex > -1; 36 | const options = { 37 | style: this.options.style, 38 | libraryDirectory: this.options.libraryDirectory, 39 | iconBox: this.options.iconBox, 40 | babelConfig: this.options.babelConfig, 41 | }; 42 | if (shouldModifyBabelLoader) { 43 | const _loader = loaders[babelLoaderIndex]; 44 | if (this.options.modifyBabelLoader === 'merge') { 45 | modifyBabelLoaderMerge(_loader, options); 46 | } else { 47 | modifyBabelLoaderOverride(_loader, options); 48 | } 49 | return; 50 | } 51 | let insertIndex = loaders.length - 1; 52 | if (babelLoaderIndex > -1) { 53 | insertIndex = babelLoaderIndex + 1; 54 | } else if (tsLoaderIndex > -1) { 55 | insertIndex = (tsLoaderIndex || 1) - 1; 56 | } 57 | loaders.splice(insertIndex, 0, { 58 | loader, 59 | options, 60 | ident: null, 61 | type: null, 62 | }); 63 | module.loaders = loaders; 64 | } 65 | }); 66 | } 67 | 68 | getBabelPlugins() { 69 | return this.babelPlugins.slice(0); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/plugin-for-remove-font-face.ts: -------------------------------------------------------------------------------- 1 | import { cloneDeep } from 'lodash'; 2 | import { pathUtils } from '@arco-plugins/utils'; 3 | import { getLoader, hookNormalModuleLoader } from './utils'; 4 | import { PLUGIN_NAME } from './config'; 5 | import { globalLessMatchers } from './config/matchers'; 6 | 7 | export class RemoveFontFacePlugin { 8 | apply(compiler) { 9 | const loader = require.resolve('./loaders/remove-font-face'); 10 | hookNormalModuleLoader(compiler, PLUGIN_NAME, (_loaderContext, module, resource) => { 11 | if (pathUtils.isMatch(resource, globalLessMatchers) && !getLoader(module.loaders, loader)) { 12 | const loaders = cloneDeep(module.loaders || []); 13 | loaders.push({ 14 | loader, 15 | ident: null, 16 | type: null, 17 | options: {}, 18 | }); 19 | module.loaders = loaders; 20 | } 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/plugin-for-replace-default-language.ts: -------------------------------------------------------------------------------- 1 | import { cloneDeep } from 'lodash'; 2 | import { pathUtils } from '@arco-plugins/utils'; 3 | import { getLoader, hookNormalModuleLoader } from './utils'; 4 | import { PLUGIN_NAME } from './config'; 5 | import { esLocalDefaultMatchers, libLocalDefaultMatchers } from './config/matchers'; 6 | import { ArcoDesignPluginOptions } from './interface'; 7 | 8 | export class ReplaceDefaultLanguagePlugin { 9 | options: ArcoDesignPluginOptions; 10 | 11 | constructor(options: ArcoDesignPluginOptions) { 12 | this.options = options; 13 | } 14 | 15 | apply(compiler) { 16 | if (!this.options.defaultLanguage) return; 17 | 18 | const loader = require.resolve('./loaders/replace-default-language'); 19 | hookNormalModuleLoader(compiler, PLUGIN_NAME, (_loaderContext, module, resource) => { 20 | if ( 21 | pathUtils.isMatch(resource, esLocalDefaultMatchers) && 22 | !getLoader(module.loaders, loader) 23 | ) { 24 | const loaders = cloneDeep(module.loaders || []); 25 | loaders.push({ 26 | loader, 27 | ident: null, 28 | type: null, 29 | options: { 30 | defaultLanguage: this.options.defaultLanguage, 31 | type: 'es', 32 | }, 33 | }); 34 | module.loaders = loaders; 35 | } 36 | if ( 37 | pathUtils.isMatch(resource, libLocalDefaultMatchers) && 38 | !getLoader(module.loaders, loader) 39 | ) { 40 | const loaders = cloneDeep(module.loaders || []); 41 | loaders.push({ 42 | loader, 43 | ident: null, 44 | type: null, 45 | options: { 46 | defaultLanguage: this.options.defaultLanguage, 47 | type: 'lib', 48 | }, 49 | }); 50 | module.loaders = loaders; 51 | } 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/plugin-for-replace-icon.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { ArcoDesignPluginOptions } from './interface'; 3 | 4 | export class ReplaceIconPlugin { 5 | options: ArcoDesignPluginOptions; 6 | 7 | constructor(options: ArcoDesignPluginOptions) { 8 | this.options = options; 9 | } 10 | 11 | apply(compiler) { 12 | const { iconBox } = this.options; 13 | if (!iconBox) return; 14 | let iconBoxLib: Record; 15 | let iconBoxDirname: string; 16 | try { 17 | iconBoxDirname = path.dirname(require.resolve(`${iconBox}/package.json`)); 18 | iconBoxLib = require(iconBox); // eslint-disable-line 19 | } catch (e) { 20 | throw new Error(`IconBox ${iconBox} not existed`); 21 | } 22 | 23 | compiler.options.module.rules.unshift({ 24 | test: /\.js/, 25 | include: /node_modules\/@arco-design\/web-react\/icon\/react-icon\/([^/]+)\/index\.js$/, 26 | use: [ 27 | { 28 | loader: path.resolve(__dirname, './loaders/replace-icon.js'), 29 | options: { 30 | iconBoxLib, 31 | iconBoxDirname, 32 | }, 33 | }, 34 | ], 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/plugin-for-theme.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-empty */ 2 | import path from 'path'; 3 | import { readFileSync, readdirSync } from 'fs'; 4 | import { cloneDeep, isString, isEmpty } from 'lodash'; 5 | import { print, getFileSource, pathUtils } from '@arco-plugins/utils'; 6 | import { Compiler } from 'webpack'; 7 | import { getLoader, hookNormalModuleLoader, rewriteLessLoaderOptions, getContext } from './utils'; 8 | import { PLUGIN_NAME } from './config'; 9 | import { 10 | globalLessMatchers, 11 | globalCssMatchers, 12 | componentCssMatchers, 13 | componentLessMatchers, 14 | lessMatchers, 15 | } from './config/matchers'; 16 | import { ArcoDesignPluginOptions } from './interface'; 17 | 18 | export class ThemePlugin { 19 | options: ArcoDesignPluginOptions; 20 | compiler: Compiler; 21 | 22 | constructor(options: ArcoDesignPluginOptions) { 23 | this.options = options; 24 | } 25 | 26 | apply(compiler) { 27 | this.compiler = compiler; 28 | this.hookForGlobalStyle(); 29 | this.hookForVariables(); 30 | this.hookForComponentStyle(); 31 | } 32 | 33 | /** 34 | * 读取主题变量,添加到 less-loader 35 | */ 36 | hookForVariables() { 37 | const themeVars = this.getThemeVars() || {}; 38 | const modifyVars = this.options.modifyVars || {}; 39 | const vars = { ...themeVars, ...modifyVars }; 40 | if (isEmpty(vars)) return; 41 | 42 | let noLessLoaderWarning = ''; 43 | const context = getContext(this.compiler, this.options.context); 44 | const include = this.options.varsInjectScope || []; 45 | const isMatch = pathUtils.matcher(include, { 46 | extraGlobPattern: lessMatchers, 47 | extensions: ['.less'], 48 | cwd: context, 49 | }); 50 | 51 | hookNormalModuleLoader(this.compiler, PLUGIN_NAME, (_loaderContext, module, resource) => { 52 | if (isMatch(resource)) { 53 | const loaders = cloneDeep(module.loaders); 54 | const lessLoader = getLoader(loaders, 'less-loader'); 55 | 56 | if (!lessLoader) { 57 | noLessLoaderWarning = 58 | 'less-loader not found! The theme and modifyVars has no effective, please check if less-loader is added.'; 59 | return; 60 | } 61 | 62 | rewriteLessLoaderOptions(lessLoader, { 63 | modifyVars: vars, 64 | }); 65 | 66 | module.loaders = loaders; 67 | } 68 | }); 69 | 70 | this.compiler.hooks.afterCompile.tap(PLUGIN_NAME, () => { 71 | if (noLessLoaderWarning) { 72 | print.warn(`[arco-design/webpack-plugin]: ${noLessLoaderWarning}`); 73 | } 74 | }); 75 | } 76 | 77 | /** 78 | * 读取主题全局样式文件,利用 loader 添加到 style/index.less 79 | */ 80 | hookForGlobalStyle() { 81 | if (!this.options.theme) return; 82 | this.addAppendLoader(globalLessMatchers, `${this.options.theme}/theme.less`, { 83 | importLessPath: true, 84 | }); 85 | this.addAppendLoader(globalCssMatchers, `${this.options.theme}/theme.css`); 86 | } 87 | 88 | /** 89 | * 读取组件的样式文件,附加到各组件的 index.less 和 index.css 90 | */ 91 | hookForComponentStyle() { 92 | if (!this.options.theme) return; 93 | 94 | const componentList = this.getComponentList(); 95 | componentList.forEach((componentName) => { 96 | this.addAppendLoader( 97 | componentLessMatchers(componentName), 98 | `${this.options.theme}/components/${componentName}/index.less`, 99 | { 100 | importLessPath: true, 101 | } 102 | ); 103 | this.addAppendLoader( 104 | componentCssMatchers(componentName), 105 | `${this.options.theme}/components/${componentName}/index.css` 106 | ); 107 | }); 108 | } 109 | 110 | // 将 filePath 中的内容添加到 match 的文件中 111 | addAppendLoader(matcher, filePath, options?: { importLessPath: boolean }) { 112 | try { 113 | let source = getFileSource(filePath); 114 | if (!source) return; 115 | 116 | if (options && options.importLessPath) { 117 | source = `;\n@import '~${filePath}';`; 118 | } 119 | 120 | const appendLoader = require.resolve('./loaders/append'); 121 | hookNormalModuleLoader(this.compiler, PLUGIN_NAME, (_loaderContext, module, resource) => { 122 | if (pathUtils.isMatch(resource, matcher) && !getLoader(module.loaders, appendLoader)) { 123 | const loaders = cloneDeep(module.loaders || []); 124 | loaders.push({ 125 | loader: appendLoader, 126 | options: { 127 | additionContent: source.replace(/!/g, '__ARCO_PLACEHOLDER__'), 128 | }, 129 | ident: null, 130 | type: null, 131 | }); 132 | module.loaders = loaders; 133 | } 134 | }); 135 | } catch (error) {} 136 | } 137 | 138 | /** 读取组件配置目录 */ 139 | getComponentList() { 140 | if (!this.options.theme) return []; 141 | try { 142 | const packageRootDir = path.dirname(require.resolve(`${this.options.theme}/package.json`)); 143 | const themeComponentDirPath = `${packageRootDir}/components`; 144 | const componentList = readdirSync(themeComponentDirPath); 145 | return componentList || []; 146 | } catch (error) { 147 | return []; 148 | } 149 | } 150 | 151 | /** 152 | * 读取主题变量 153 | */ 154 | getThemeVars() { 155 | if (!this.options.theme) return {}; 156 | try { 157 | const variableLessPath = require.resolve(`${this.options.theme}/tokens.less`); 158 | const source = readFileSync(variableLessPath); 159 | return this.transformSourceToObject(source.toString()); 160 | } catch (error) { 161 | return {}; 162 | } 163 | } 164 | 165 | /** 166 | * 将文件内容转为对象 167 | * @param {less file content}} source 168 | */ 169 | transformSourceToObject(source = '') { 170 | // 去掉注释 171 | const str = 172 | isString(source) && 173 | source 174 | .replace(/\/\/.*/g, '') // ‘//’ 之后的所有内容(以一行为结束) 175 | .replace(/\/\*[\s\S]*?\*\//g, ''); // ‘/**/’ 之间的所有内容 176 | if (!str.length) return; 177 | const cssVarsPrefix = this.options.modifyVars?.['arco-cssvars-prefix']; 178 | const obj = {}; 179 | str 180 | .match(/(?=@)([\s\S]+?)(?=;)/g) // 匹配变量定义,结果为 ‘@变量名: 变量值’ 181 | .map((item) => item && item.split(':')) 182 | .filter((item) => item && item.length === 2) 183 | .forEach((item) => { 184 | const key = item[0].replace(/^@/, '').trim(); 185 | let value = item[1].trim(); 186 | if (key && value) { 187 | if (cssVarsPrefix && value.includes('--')) { 188 | value = value.replace('--', `${cssVarsPrefix}-`); 189 | } 190 | obj[key] = value; 191 | } 192 | }); 193 | 194 | return obj; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/utils/index.ts: -------------------------------------------------------------------------------- 1 | import webpack, { Compiler, NormalModule } from 'webpack'; 2 | import { isObject, get, set } from 'lodash'; 3 | import { print } from '@arco-plugins/utils'; 4 | import { isAbsolute, join } from 'path'; 5 | 6 | /** 7 | * 获取指定路径的值,没有的时候将其设置为指定默认值 8 | * @param {object} src 9 | * @param {string} path 10 | * @param {function} checkFn 11 | * @param {any} defaultValue 12 | * @return {any} 13 | */ 14 | function getAndSetWhen( 15 | src: object, 16 | path: string, 17 | checkFn: (val: any) => boolean, 18 | defaultValue: any 19 | ) { 20 | if (!isObject(src)) return; 21 | const res = get(src, path); 22 | 23 | if (!checkFn(res)) { 24 | set(src, path, defaultValue); 25 | return get(src, path); 26 | } 27 | 28 | return res; 29 | } 30 | 31 | /** 32 | * 勾入 normalModuleLoader 33 | * @param {Compiler} compiler 34 | * @param {string} pluginName 35 | * @param {Function} callback 36 | */ 37 | export function hookNormalModuleLoader( 38 | compiler: Compiler, 39 | pluginName: string, 40 | callback: (context: any, module: NormalModule, resource: string) => void 41 | ) { 42 | const hookHandler = (context, module) => { 43 | const { resource } = module; 44 | if (!resource) return; 45 | const i = resource.indexOf('?'); 46 | callback(context, module, i < 0 ? resource : resource.substr(0, i)); 47 | }; 48 | const webpackImplementation = 49 | (global.arcoDesignPlugin.options.webpackImplementation as typeof webpack | undefined) || 50 | webpack; 51 | compiler.hooks.compilation.tap(pluginName, (compilation) => { 52 | if ( 53 | webpackImplementation.NormalModule && 54 | webpackImplementation.NormalModule.getCompilationHooks 55 | ) { 56 | // for webpack 5 57 | webpackImplementation.NormalModule.getCompilationHooks(compilation).loader.tap( 58 | pluginName, 59 | hookHandler 60 | ); 61 | } else if (compilation.hooks) { 62 | // for webpack 4 63 | compilation.hooks.normalModuleLoader.tap(pluginName, hookHandler); 64 | } else if ((compilation as any).plugin) { 65 | // for webpack 3 66 | (compilation as any).plugin('normal-module-loader', hookHandler); 67 | } 68 | }); 69 | } 70 | 71 | /** 72 | * 返回指定的名称的loader配置 73 | * @param {loader[]} loaders 74 | * @param {string} loaderName 75 | * @returns {loader} 76 | */ 77 | export function getLoader(loaders, loaderName) { 78 | if (!Array.isArray(loaders) || !loaders.length) return; 79 | return loaders.find((item) => item.loader.indexOf(loaderName) > -1); 80 | } 81 | 82 | /** 83 | * 返回指定的名称的loader的位置 84 | * @param {loader[]} loaders 85 | * @param {string} loaderName 86 | * @returns {number} 87 | */ 88 | export function getLoaderIndex(loaders, loaderName) { 89 | if (!Array.isArray(loaders) || !loaders.length) return -1; 90 | return loaders.findIndex((item) => item.loader.indexOf(loaderName) > -1); 91 | } 92 | 93 | /** 94 | * 删除一个 babel 插件 95 | * @param {LoaderConfig} babelLoader 96 | * @param {Function} filterMethod 97 | */ 98 | export function deleteBabelPlugin(babelLoader, match) { 99 | if (!babelLoader) return; 100 | 101 | const plugins = get(babelLoader, 'options.plugins'); 102 | if (!plugins) return; 103 | 104 | for (let i = 0; i < plugins.length; i++) { 105 | const plugin = plugins[i]; 106 | if (match(plugin)) { 107 | plugins.splice(i, 1); 108 | i--; 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * 插入一个 babel 插件 115 | * @param {LoaderConfig} babelLoader 116 | * @param {loader plugin options} pluginOptions 117 | */ 118 | export function injectBabelPlugin(babelLoader, pluginOptions) { 119 | if (!babelLoader) return; 120 | 121 | const plugins = getAndSetWhen(babelLoader, 'options.plugins', Array.isArray, []); 122 | const pluginOptionsName = pluginOptions[2]; 123 | 124 | if (plugins.every((plugin) => plugin[2] !== pluginOptionsName)) { 125 | plugins.push(pluginOptions); 126 | } 127 | } 128 | 129 | /** 130 | * 给 less-loader 加上主题变量 131 | * @param {loaderConfig} lessLoader 132 | * @param {lessLoader.options} options 133 | */ 134 | export function rewriteLessLoaderOptions(lessLoader, options) { 135 | if (!lessLoader) return; 136 | 137 | // less-loader 6.0 之后 less 的配置放到了 options.lessOptions。这边简单的通过判断原本的配置中是否存在 lessOptions 来决定用谁 138 | const useLessOptions = !!get(lessLoader, 'options.lessOptions'); 139 | const lessOptions = getAndSetWhen( 140 | lessLoader, 141 | useLessOptions ? 'options.lessOptions' : 'options', 142 | isObject, 143 | {} 144 | ); 145 | Object.assign(lessOptions, options); 146 | } 147 | 148 | /** 149 | * 打印错误信息 150 | */ 151 | export function printError(error) { 152 | if (error) { 153 | print.error(`[arco-design/webpack-plugin]: ${error}`); 154 | } 155 | } 156 | 157 | /** 158 | * 获取上下文的决对路径,默认为 compiler.context 即 webpackConfig.context 159 | * @param {Compiler} compiler 160 | * @returns {string} 161 | */ 162 | export function getContext(compiler: Compiler, context?: string) { 163 | if (!context) { 164 | return String(compiler.options.context); 165 | } 166 | 167 | if (!isAbsolute(context)) { 168 | return join(String(compiler.options.context), context); 169 | } 170 | 171 | return context; 172 | } 173 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/arco-design-plugin/utils/transform-import.ts: -------------------------------------------------------------------------------- 1 | import { TransformOptions, transformSync } from '@babel/core'; 2 | import { merge } from 'lodash'; 3 | import babelConfig from '../config/babel.config'; 4 | 5 | const { ARCO_DESIGN_COMPONENT_NAME, ARCO_DESIGN_ICON_NAME } = require('../config'); 6 | 7 | const babelPluginImport = require.resolve('babel-plugin-import'); 8 | const babelPluginFlagPrefix = '__arco_babel_plugin__'; 9 | 10 | function getBabelPluginFlag(suffix) { 11 | return `${babelPluginFlagPrefix}${suffix}`; 12 | } 13 | 14 | // babel plugins for component library 15 | function getArcoImportPlugins(options) { 16 | return [ 17 | [ 18 | babelPluginImport, 19 | { 20 | libraryDirectory: 'es', 21 | style: true, 22 | ...options, 23 | libraryName: ARCO_DESIGN_COMPONENT_NAME, 24 | camel2DashComponentName: false, 25 | }, 26 | getBabelPluginFlag(`import_${ARCO_DESIGN_COMPONENT_NAME}`), 27 | ], 28 | [ 29 | babelPluginImport, 30 | { 31 | libraryName: ARCO_DESIGN_ICON_NAME, 32 | libraryDirectory: 'react-icon', 33 | camel2DashComponentName: false, 34 | }, 35 | getBabelPluginFlag(`import_${ARCO_DESIGN_ICON_NAME}`), 36 | ], 37 | ]; 38 | } 39 | 40 | // babel plugins for iconbox 41 | function getIconBoxImportPlugins(iconBoxLibName) { 42 | if (!iconBoxLibName) return []; 43 | return [ 44 | [ 45 | babelPluginImport, 46 | { 47 | libraryName: iconBoxLibName, 48 | libraryDirectory: 'esm', // 图标库默认文件夹 49 | camel2DashComponentName: false, 50 | }, 51 | getBabelPluginFlag(`import_${iconBoxLibName}`), 52 | ], 53 | ]; 54 | } 55 | 56 | function getTransformOptions(options) { 57 | return merge( 58 | { 59 | libraryDirectory: 'es', 60 | style: true, 61 | iconBox: '', 62 | babelConfig: {}, 63 | }, 64 | options || {} 65 | ); 66 | } 67 | 68 | export function getBabelPlugins(options) { 69 | const _options = getTransformOptions(options); 70 | 71 | return [ 72 | ...(_options.babelConfig.plugins || []), 73 | ...getArcoImportPlugins({ 74 | libraryDirectory: _options.libraryDirectory, 75 | style: _options.style, 76 | }), 77 | ...getIconBoxImportPlugins(_options.iconBox), 78 | ]; 79 | } 80 | 81 | function mergeBabelPresets(defaults, userPresets) { 82 | const finalPresets = [...defaults]; 83 | 84 | userPresets.forEach((p) => { 85 | const [presetName, presetOptions] = Array.isArray(p) ? p : [p]; 86 | 87 | const existedPresetIndex = finalPresets.findIndex((i) => { 88 | const _presetName = Array.isArray(i) ? i[0] : i; 89 | // Preset name can be passed in a file path or the name of an npm package, 90 | // so we use resolved paths to determine whether two presets are the same. 91 | if (require.resolve(presetName) === require.resolve(_presetName)) { 92 | return true; 93 | } 94 | return false; 95 | }); 96 | 97 | if (existedPresetIndex === -1) { 98 | finalPresets.push(p); 99 | } else { 100 | let mergedPreset = finalPresets[existedPresetIndex]; 101 | mergedPreset = Array.isArray(mergedPreset) ? mergedPreset : [mergedPreset]; 102 | // merge preset options 103 | mergedPreset[1] = { ...mergedPreset[1], ...presetOptions }; 104 | finalPresets.splice(existedPresetIndex, 1, mergedPreset); 105 | } 106 | }); 107 | 108 | return finalPresets; 109 | } 110 | 111 | export function transformImport(source, options) { 112 | const _options = getTransformOptions(options); 113 | 114 | const babelPlugins = getBabelPlugins(options); 115 | 116 | const babelPresets = mergeBabelPresets(babelConfig.presets, _options.babelConfig.presets || []); 117 | 118 | const transformResult = transformSync( 119 | source, 120 | merge({}, _options.babelConfig, { 121 | presets: babelPresets, 122 | plugins: babelPlugins, 123 | }) 124 | ); 125 | 126 | return transformResult.code; 127 | } 128 | 129 | export interface ModifyBabelLoaderOptions { 130 | style: string | boolean; 131 | libraryDirectory: string; 132 | iconBox?: string; 133 | babelConfig?: TransformOptions; 134 | } 135 | 136 | export function modifyBabelLoaderOverride(loader, options: ModifyBabelLoaderOptions) { 137 | const { options: loaderOptions } = loader; 138 | if (loaderOptions?.plugins?.some((item) => item[2]?.startsWith(babelPluginFlagPrefix))) { 139 | return; 140 | } 141 | const { babelConfig: config } = options; 142 | const plugins = getBabelPlugins(options); 143 | loader.options = { 144 | ...loaderOptions, 145 | ...config, 146 | presets: [...(loaderOptions.presets || []), ...(config?.presets || [])], 147 | plugins, 148 | }; 149 | } 150 | 151 | export function modifyBabelLoaderMerge(loader, options: ModifyBabelLoaderOptions) { 152 | const { options: loaderOptions } = loader; 153 | if (loaderOptions?.plugins?.some((item) => item[2]?.startsWith(babelPluginFlagPrefix))) { 154 | return; 155 | } 156 | const { babelConfig: config } = options; 157 | const plugins = getBabelPlugins(options); 158 | loader.options = { 159 | ...loaderOptions, 160 | ...config, 161 | presets: [...(loaderOptions.presets || []), ...(config?.presets || [])], 162 | plugins: [...(loaderOptions.plugins || []), ...(plugins || [])], 163 | }; 164 | } 165 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/global.d.ts: -------------------------------------------------------------------------------- 1 | import { ArcoDesignPluginOptions } from './arco-design-plugin/interface'; 2 | 3 | declare global { 4 | interface arcoDesignPlugin { 5 | options: ArcoDesignPluginOptions; 6 | } 7 | } 8 | 9 | export {}; 10 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/src/index.ts: -------------------------------------------------------------------------------- 1 | import { ArcoDesignPlugin } from './arco-design-plugin'; 2 | import type { ArcoDesignPluginOptions } from './arco-design-plugin/interface'; 3 | 4 | export type { ArcoDesignPluginOptions }; 5 | export default ArcoDesignPlugin; 6 | 7 | module.exports = ArcoDesignPlugin; 8 | module.exports.default = ArcoDesignPlugin; 9 | -------------------------------------------------------------------------------- /packages/plugin-webpack-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "outDir": "./lib", 5 | "declaration": true, 6 | "moduleResolution": "node", 7 | "module": "commonjs", 8 | "target": "es5", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "skipLibCheck": true, 12 | "types": ["node"], 13 | "lib": ["esnext"], 14 | }, 15 | "include": ["src"], 16 | "exclude": ["node_modules"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/unplugin-react/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/eslintrc", 3 | "rules": { 4 | "no-restricted-syntax": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/unplugin-react/.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | dist 3 | -------------------------------------------------------------------------------- /packages/unplugin-react/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | `2023-04-25` 4 | 5 | - Release first version. 6 | -------------------------------------------------------------------------------- /packages/unplugin-react/CHANGELOG.zh-CN.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | `2023-04-25` 4 | 5 | - 发布初版 6 | -------------------------------------------------------------------------------- /packages/unplugin-react/README.md: -------------------------------------------------------------------------------- 1 | # @arco-plugins/unplugin-react 2 | 3 | `@arco-plugins/unplugin-react` is a build plugin that helps with arco problems. 4 | 5 | It implements cross-bundler build capabilities based on unplugin. 6 | 7 | ## Features 8 | 9 | The features of the plugin are as follows: 10 | 11 | 1. `On-demand loading of styles`: Automatically configure import conversion plugins to implement on-demand loading of styles. 12 | 2. `Removal of font packages included in component libraries`: Specify `removeFontFace` as `true` to remove the font files included in the component library. 13 | 3. `Icon replacement`: Specify the package name of the icon library, and the plugin will read the icons in the package and replace them with the same-named icons used in the component library. 14 | 4. `Default language replacement`: The default imported language package of the component library is Chinese, which means that the packaging will definitely include Chinese. If you don't want Chinese, you can use this parameter to replace it with the language you need. 15 | 16 | ## Support 17 | 18 | | Configuration item | Webpack | Rspack | Vite | 19 | |:---------------------:|:-------:|:------:|:----:| 20 | | style | ⚪ | 🟢 | ⚪ | 21 | | libraryDirectory | ⚪ | 🟢 | ⚪ | 22 | | iconBox | ⚪ | 🟢 | ⚪ | 23 | | removeFontFace | ⚪ | 🟢 | ⚪ | 24 | | defaultLanguage | ⚪ | 🟢 | ⚪ | 25 | | theme | ⚪ | 🟢 | ⚪ | 26 | | context | ⚪ | ⚪ | ⚪ | 27 | | include | ⚪ | ⚪ | ⚪ | 28 | | extensions | ⚪ | ⚪ | ⚪ | 29 | | babelConfig | ⚪ | ⚪ | ⚪ | 30 | | modifyVars | ⚪ | ⚪ | ⚪ | 31 | | webpackImplementation | ⚪ | ⚪ | ⚪ | 32 | | varsInjectScope | ⚪ | ⚪ | ⚪ | 33 | | modifyBabelLoader | ⚪ | ⚪ | ⚪ | 34 | 35 | ### Rspack 36 | 37 | Compared to `@arco-plugins/webpack-react`, there are some differences when using it in conjunction with Rspack. This is due to the underlying differences between Rspack and webpack. 38 | 39 | ```diff 40 | export interface ArcoDesignPluginOptions { 41 | style?: string | boolean; 42 | libraryDirectory?: string; 43 | iconBox?: string; 44 | removeFontFace?: boolean; 45 | defaultLanguage?: string; 46 | theme?: string; 47 | - context?: string; 48 | - include: (string | RegExp)[]; 49 | - extensions: string[]; 50 | - babelConfig?: object; 51 | - modifyVars?: Record; 52 | - webpackImplementation?: typeof webpack; 53 | - varsInjectScope?: (string | RegExp)[]; 54 | - modifyBabelLoader?: boolean | 'merge' | 'override'; 55 | } 56 | ``` 57 | 58 | Unlike webpack, Rspack no longer uses Babel for limited-range code conversion, but instead uses SWC to process all code, including third-party dependencies. Therefore, support for `include`, `extensions`, `babelConfig`, and `modifyBabelLoader` configurations has been removed. 59 | 60 | In addition, because support for webpack@4 has been abandoned and internal implementation has been improved, `context` and `webpackImplementation` configuration is no longer required. 61 | 62 | For maintainability reasons, `@arco-plugins/unplugin-react` no longer supports the `modifyVars` and `varsInjectScope` configuration items. You can achieve the same function by manually configuring the `less-loader`. 63 | 64 | ## Install 65 | 66 | Install this tool via package manager: 67 | 68 | ```shell 69 | # npm 70 | $ npm install -D @arco-plugins/unplugin-react 71 | 72 | # yarn 73 | $ yarn add -D @arco-plugins/unplugin-react 74 | 75 | # pnpm 76 | $ pnpm add -D @arco-plugins/unplugin-react 77 | ``` 78 | 79 | ## Usage 80 | 81 | Take Rspack for example, the usage is to add the following content to the `rspack.config.js` file: 82 | 83 | ```js 84 | const { ArcoDesignPlugin } = require('@arco-plugins/unplugin-react'); 85 | 86 | module.exports = { 87 | module: { 88 | rules: [ 89 | { 90 | type: 'css', 91 | test: /\.less$/, 92 | use: [{ loader: 'less-loader' }], 93 | }, 94 | ], 95 | }, 96 | plugins: [ 97 | new ArcoDesignPlugin({ 98 | theme: '@arco-themes/react-asuka', 99 | iconBox: '@arco-iconbox/react-partial-bits', 100 | removeFontFace: true, 101 | }), 102 | ], 103 | }; 104 | ``` 105 | 106 | You can also find an actual available example project in [example-rspack-react](../../examples/rspack-react/). 107 | 108 | ## options 109 | 110 | The plugin supports the following parameters: 111 | 112 | |Parameter |Type|Default|Description| 113 | |:--:|:--:|:-----:|:----------| 114 | |**`theme`**|`{string}`|`-`|Theme package name| 115 | |**`iconBox`**|`{string}`|`-`|Icon library package name| 116 | |**`libraryDirectory`**|`{'es'\|'lib'}`|`'es'`|Export format| 117 | |**`style`**|`{string\|boolean}`|`true`|Style import method| 118 | |**`removeFontFace`**|`{boolean}`|`false`|Removes the font file included in the component library| 119 | |**`defaultLanguage`**|`{string}`|`-`|Replace default language, [language list](https://arco.design/react/docs/i18n#%E6%94%AF%E6%8C%81%E7%9A%84%E8%AF%AD%E8%A8%80)| 120 | -------------------------------------------------------------------------------- /packages/unplugin-react/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # @arco-plugins/unplugin-react 2 | 3 | `@arco-plugins/unplugin-react` 是协助处理 arco 使用问题的构建插件。 4 | 5 | 基于 unplugin 实现了跨 bundler 通用的构建能力支持。 6 | 7 | ## 特性 8 | 9 | 插件的功能如下: 10 | 11 | 1. `样式按需加载`:自动配置 import 转换插件实现样式的按需加载。 12 | 2. `移除组件库自带的字体包`:指定 `removeFontFace` 为 `true` 可以去掉组件库自带的字体文件。 13 | 3. `图标替换`:指定图标库的包名,插件会读取包内图标对组件库内用到的同名的图标进行替换。 14 | 4. `替换默认语言`:组件库的默认导入的语言包是中文,这就决定了打包产物中一定会包含中文,如果不想要中文,就可以利用这个参数来将其替换为你需要的语言。 15 | 16 | ## 支持情况 17 | 18 | | 配置项 | Webpack | Rspack | Vite | 19 | |:---------------------:|:-------:|:------:|:----:| 20 | | style | ⚪ | 🟢 | ⚪ | 21 | | libraryDirectory | ⚪ | 🟢 | ⚪ | 22 | | iconBox | ⚪ | 🟢 | ⚪ | 23 | | removeFontFace | ⚪ | 🟢 | ⚪ | 24 | | defaultLanguage | ⚪ | 🟢 | ⚪ | 25 | | theme | ⚪ | 🟢 | ⚪ | 26 | | context | ⚪ | ⚪ | ⚪ | 27 | | include | ⚪ | ⚪ | ⚪ | 28 | | extensions | ⚪ | ⚪ | ⚪ | 29 | | babelConfig | ⚪ | ⚪ | ⚪ | 30 | | modifyVars | ⚪ | ⚪ | ⚪ | 31 | | webpackImplementation | ⚪ | ⚪ | ⚪ | 32 | | varsInjectScope | ⚪ | ⚪ | ⚪ | 33 | | modifyBabelLoader | ⚪ | ⚪ | ⚪ | 34 | 35 | ### Rspack 36 | 37 | 与 Rspack 集成使用时相比于 `@arco-plugins/webpack-react` 存在一些不同,这是由 Rspack 与 webpack 的底层差异决定的。 38 | 39 | ```diff 40 | export interface ArcoDesignPluginOptions { 41 | style?: string | boolean; 42 | libraryDirectory?: string; 43 | iconBox?: string; 44 | removeFontFace?: boolean; 45 | defaultLanguage?: string; 46 | theme?: string; 47 | - context?: string; 48 | - include: (string | RegExp)[]; 49 | - extensions: string[]; 50 | - babelConfig?: object; 51 | - modifyVars?: Record; 52 | - webpackImplementation?: typeof webpack; 53 | - varsInjectScope?: (string | RegExp)[]; 54 | - modifyBabelLoader?: boolean | 'merge' | 'override'; 55 | } 56 | ``` 57 | 58 | 相比 webpack 来说 Rspack 不再使用 Babel 来进行有限范围的代码转译,转而使用 SWC 处理包括第三方依赖在内的所有代码,所以去除了对 `include` `extenstions` `babelConfig` `modifyBabelLoader` 配置的支持。 59 | 60 | 另外由于放弃了对 webpack@4 的支持并对内部实现做了改进,所以不再需要配置 `context` `webpackImplementation`。 61 | 62 | 出于可维护性的考虑,`@arco-plugins/unplugin-react` 不再支持 `modifyVars` `varsInjectScope` 配置项,你可以通过手动配置 `less-loader` 的配置来实现相同的功能。 63 | 64 | ## 安装 65 | 66 | 通过包管理器安装这个工具: 67 | 68 | ```shell 69 | # npm 70 | $ npm install -D @arco-plugins/unplugin-react 71 | 72 | # yarn 73 | $ yarn add -D @arco-plugins/unplugin-react 74 | 75 | # pnpm 76 | $ pnpm add -D @arco-plugins/unplugin-react 77 | ``` 78 | 79 | ## 用法 80 | 81 | 以 Rspack 为例,使用方式是在 `rspack.config.js` 文件中加入以下内容: 82 | 83 | ```js 84 | const { ArcoDesignPlugin } = require('@arco-plugins/unplugin-react'); 85 | 86 | module.exports = { 87 | module: { 88 | rules: [ 89 | { 90 | type: 'css', 91 | test: /\.less$/, 92 | use: [{ loader: 'less-loader' }], 93 | }, 94 | ], 95 | }, 96 | plugins: [ 97 | new ArcoDesignPlugin({ 98 | theme: '@arco-themes/react-asuka', 99 | iconBox: '@arco-iconbox/react-partial-bits', 100 | removeFontFace: true, 101 | }), 102 | ], 103 | }; 104 | ``` 105 | 106 | 你也可以在 [example-rspack-react](../../examples/rspack-react/) 中找到一个实际可用的示例项目。 107 | 108 | ## options 109 | 110 | 插件支持以下参数: 111 | 112 | |参数名|类型|默认值|描述| 113 | |:--:|:--:|:-----:|:----------| 114 | |**`theme`**|`{String}`|`-`|主题包名| 115 | |**`iconBox`**|`{String}`|`-`|图标库包名| 116 | |**`libraryDirectory`**|`{'es'\|'lib'}`|`'es'`|导出格式| 117 | |**`style`**|`{String\|Boolean}`|`true`| 样式引入方式| 118 | |**`removeFontFace`**|`{Boolean}`|`false`| 去掉组件库自带的字体文件 | 119 | |**`defaultLanguage`**|`{string}`|`-`| 替换默认的语言,[语言列表](https://arco.design/react/docs/i18n#%E6%94%AF%E6%8C%81%E7%9A%84%E8%AF%AD%E8%A8%80) | 120 | -------------------------------------------------------------------------------- /packages/unplugin-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@arco-plugins/unplugin-react", 3 | "version": "1.0.1", 4 | "description": "arco plugin", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "lib" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:arco-design/arco-plugins.git", 13 | "directory": "packages/plugin-unplugin-react" 14 | }, 15 | "scripts": { 16 | "build": "rm -rf lib && tsc", 17 | "dev": "tsc -w --sourceMap", 18 | "prepublishOnly": "npm run build" 19 | }, 20 | "keywords": [ 21 | "arco", 22 | "arco-design", 23 | "arco-plugin", 24 | "plugin", 25 | "webpack", 26 | "rspack", 27 | "unplugin" 28 | ], 29 | "author": "arco-design", 30 | "license": "MIT", 31 | "dependencies": { 32 | "@arco-plugins/utils": "^1.2.1", 33 | "@babel/core": "7.21.4", 34 | "minimatch": "^9.0.0" 35 | }, 36 | "devDependencies": { 37 | "@rspack/core": "^0.1.8", 38 | "@types/node": "^14", 39 | "typescript": "^5.0.4" 40 | }, 41 | "peerDependencies": { 42 | "@rspack/core": "*" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/config/index.ts: -------------------------------------------------------------------------------- 1 | export const PLUGIN_NAME = 'ARCO_WEBPACK_PLUGIN'; 2 | export const ARCO_DESIGN_COMPONENT_NAME = '@arco-design/web-react'; 3 | export const ARCO_DESIGN_ICON_NAME = '@arco-design/web-react/icon'; 4 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Compiler } from '@rspack/core'; 2 | import { ImportPlugin } from './plugins/import'; 3 | import { RemoveFontFacePlugin } from './plugins/remove-font-face'; 4 | import { ReplaceDefaultLanguagePlugin } from './plugins/replace-default-language'; 5 | import { ReplaceIconPlugin } from './plugins/replace-icon'; 6 | import { ThemePlugin } from './plugins/theme'; 7 | import { ArcoDesignPluginOptions } from './types'; 8 | 9 | export type { ArcoDesignPluginOptions }; 10 | 11 | export class ArcoDesignPlugin { 12 | options: ArcoDesignPluginOptions; 13 | 14 | constructor(options: Partial = {}) { 15 | this.options = { 16 | style: true, 17 | libraryDirectory: 'es', 18 | removeFontFace: false, 19 | ...options, 20 | }; 21 | } 22 | 23 | async apply(compiler: Compiler) { 24 | new ThemePlugin(this.options).apply(compiler); 25 | new ImportPlugin(this.options).apply(compiler); 26 | if (this.options.removeFontFace) { 27 | new RemoveFontFacePlugin().apply(compiler); 28 | } 29 | if (this.options.iconBox) { 30 | new ReplaceIconPlugin(this.options).apply(compiler); 31 | } 32 | if (this.options.defaultLanguage) { 33 | new ReplaceDefaultLanguagePlugin(this.options).apply(compiler); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/loaders/append.ts: -------------------------------------------------------------------------------- 1 | import { LoaderDefinition } from '../types'; 2 | 3 | export interface AppendLoaderOptions { 4 | additionContent: string; 5 | } 6 | 7 | export const AppendLoader: LoaderDefinition = function (source) { 8 | const options = this.getOptions(); 9 | const additionContent = `${options.additionContent}`; 10 | 11 | if (!additionContent) return source; 12 | 13 | return [source, additionContent.replace(/__ARCO_PLACEHOLDER__/g, '!')].join('\n'); 14 | }; 15 | 16 | module.exports = AppendLoader; 17 | module.exports.default = AppendLoader; 18 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/loaders/remove-font-face.ts: -------------------------------------------------------------------------------- 1 | import type { LoaderDefinition } from '../types'; 2 | 3 | const fontFaceRegex = /@font-face(.*)?{[^}]*?}/g; 4 | 5 | export const RemoveFontFaceLoader: LoaderDefinition = function (source) { 6 | return source.replace(fontFaceRegex, ''); 7 | }; 8 | 9 | module.exports = RemoveFontFaceLoader; 10 | module.exports.default = RemoveFontFaceLoader; 11 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/loaders/replace-default-language.ts: -------------------------------------------------------------------------------- 1 | import { Visitor, transformSync, types } from '@babel/core'; 2 | import { LoaderDefinition } from '../types'; 3 | 4 | export interface ReplaceDefaultLanguageLoaderOptions { 5 | type: 'es' | 'lib'; 6 | defaultLanguage: string; 7 | } 8 | 9 | // 替换默认语言包 10 | export const ReplaceDefaultLanguageLoader: LoaderDefinition = 11 | function (resource) { 12 | const { type = 'es', defaultLanguage } = this.getOptions(); 13 | 14 | const replaceLanguage = (source: string) => { 15 | return source.replace(/([a-zA-Z-]+)(.js|.ts)?$/, defaultLanguage); 16 | }; 17 | const visitor: Visitor = { 18 | CallExpression(path) { 19 | const { node } = path; 20 | const { callee } = node; 21 | if (!types.isIdentifier(callee)) return; 22 | if (callee.name !== 'require') return; 23 | const source = node.arguments[0]; 24 | if (!types.isStringLiteral(source)) return; 25 | source.value = replaceLanguage(source.value); 26 | }, 27 | ImportDeclaration(path) { 28 | const { node } = path; 29 | const { source } = node; 30 | source.value = replaceLanguage(source.value); 31 | }, 32 | }; 33 | const transformed = transformSync(resource, { 34 | plugins: [{ visitor }], 35 | sourceType: type === 'es' ? 'module' : 'script', 36 | }); 37 | 38 | this.callback(null, transformed.code, transformed.map); 39 | }; 40 | 41 | module.exports = ReplaceDefaultLanguageLoader; 42 | module.exports.default = ReplaceDefaultLanguageLoader; 43 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/loaders/replace-icon.ts: -------------------------------------------------------------------------------- 1 | import type { LoaderDefinition } from '../types'; 2 | 3 | export interface ReplaceIconLoaderOptions { 4 | iconBoxLib: Record; 5 | iconBoxDirname: string; 6 | } 7 | 8 | // 替换Arco默认图标 9 | export const ReplaceIconLoader: LoaderDefinition = function (content) { 10 | const { iconBoxLib, iconBoxDirname } = this.getOptions(); 11 | 12 | if (!iconBoxLib) return content; 13 | 14 | const matches = this.resourcePath.match( 15 | /@arco-design\/web-react\/icon\/react-icon\/([^/]+)\/index\.js$/ 16 | ); 17 | if (matches && iconBoxLib[matches[1]]) { 18 | return `export { default } from '${iconBoxDirname}/esm/${matches[1]}/index.js';`; 19 | } 20 | return content; 21 | }; 22 | 23 | module.exports = ReplaceIconLoader; 24 | module.exports.default = ReplaceIconLoader; 25 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/plugins/import.ts: -------------------------------------------------------------------------------- 1 | import type { Compiler } from '@rspack/core'; 2 | import { ARCO_DESIGN_COMPONENT_NAME, ARCO_DESIGN_ICON_NAME } from '../config'; 3 | import { ArcoDesignPluginOptions } from '../types'; 4 | 5 | export class ImportPlugin { 6 | options: ArcoDesignPluginOptions; 7 | 8 | constructor(options: ArcoDesignPluginOptions) { 9 | this.options = options; 10 | } 11 | 12 | apply(compiler: Compiler) { 13 | /** 14 | * Compatible with the new rspack version 15 | * due to since 0.63 removed options.builtins of compiler 16 | * https://github.com/web-infra-dev/rspack/releases/tag/v0.6.3 17 | */ 18 | if (compiler.options.builtins) { 19 | compiler.options.builtins.pluginImport ||= []; 20 | 21 | compiler.options.builtins.pluginImport.push({ 22 | libraryDirectory: this.options.libraryDirectory || 'es', 23 | style: this.options.style ?? true, 24 | libraryName: ARCO_DESIGN_COMPONENT_NAME, 25 | camelToDashComponentName: false, 26 | }); 27 | 28 | compiler.options.builtins.pluginImport.push({ 29 | libraryName: ARCO_DESIGN_ICON_NAME, 30 | libraryDirectory: 'react-icon', 31 | camelToDashComponentName: false, 32 | }); 33 | 34 | if (this.options.iconBox) { 35 | compiler.options.builtins.pluginImport.push({ 36 | libraryName: this.options.iconBox, 37 | libraryDirectory: 'esm', 38 | camelToDashComponentName: false, 39 | }); 40 | } 41 | } else { 42 | compiler.options.module.rules ||= []; 43 | 44 | const rule = { 45 | test: /(jsx?|tsx?)$/, 46 | loader: 'builtin:swc-loader', 47 | options: { 48 | rspackExperiments: { 49 | import: [ 50 | { 51 | customName: `${ARCO_DESIGN_COMPONENT_NAME}/${ 52 | this.options.libraryDirectory || 'es' 53 | }/{{member}}`, 54 | style: this.options.style ?? true, 55 | libraryName: ARCO_DESIGN_COMPONENT_NAME, 56 | }, 57 | { 58 | libraryName: ARCO_DESIGN_ICON_NAME, 59 | customName: `${ARCO_DESIGN_ICON_NAME}/react-icon/{{member}}`, 60 | }, 61 | { 62 | libraryName: this.options.iconBox, 63 | customName: `${this.options.iconBox}/esm/{{member}}`, 64 | }, 65 | ], 66 | }, 67 | }, 68 | }; 69 | 70 | compiler.options.module.rules.push(rule); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/plugins/remove-font-face.ts: -------------------------------------------------------------------------------- 1 | import type { Compiler, RuleSetUseItem } from '@rspack/core'; 2 | import { ARCO_DESIGN_COMPONENT_NAME } from '../config'; 3 | import { compileGlob } from '../utils'; 4 | 5 | export class RemoveFontFacePlugin { 6 | apply(compiler: Compiler) { 7 | const use: RuleSetUseItem = { 8 | loader: require.resolve('../loaders/remove-font-face'), 9 | options: {}, 10 | }; 11 | compiler.options.module.rules.push({ 12 | test: compileGlob(`**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/{es,lib}/style/index.less`), 13 | type: undefined, 14 | use: [use], 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/plugins/replace-default-language.ts: -------------------------------------------------------------------------------- 1 | import type { Compiler, RuleSetUseItem } from '@rspack/core'; 2 | import { ARCO_DESIGN_COMPONENT_NAME } from '../config'; 3 | import { compileGlob } from '../utils'; 4 | import { ArcoDesignPluginOptions } from '../types'; 5 | 6 | export class ReplaceDefaultLanguagePlugin { 7 | options: ArcoDesignPluginOptions; 8 | 9 | constructor(options: ArcoDesignPluginOptions) { 10 | this.options = options; 11 | } 12 | 13 | apply(compiler: Compiler) { 14 | for (const type of ['es', 'lib']) { 15 | const use: RuleSetUseItem = { 16 | loader: require.resolve('../loaders/replace-default-language'), 17 | options: { 18 | defaultLanguage: this.options.defaultLanguage, 19 | type, 20 | }, 21 | }; 22 | compiler.options.module.rules.push({ 23 | test: compileGlob( 24 | `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/${type}/locale/default.js` 25 | ), 26 | type: undefined, 27 | use: [use], 28 | }); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/plugins/replace-icon.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import type { Compiler, RuleSetUseItem } from '@rspack/core'; 3 | import { compileGlob } from '../utils'; 4 | import { ArcoDesignPluginOptions } from '../types'; 5 | 6 | export class ReplaceIconPlugin { 7 | options: ArcoDesignPluginOptions; 8 | 9 | constructor(options: ArcoDesignPluginOptions) { 10 | this.options = options; 11 | } 12 | 13 | apply(compiler: Compiler) { 14 | const { iconBox } = this.options; 15 | if (!iconBox) return; 16 | let iconBoxLib: Record; 17 | let iconBoxDirname: string; 18 | try { 19 | iconBoxDirname = path.dirname(require.resolve(`${iconBox}/package.json`)); 20 | // eslint-disable-next-line import/no-dynamic-require, global-require 21 | iconBoxLib = require(iconBox); 22 | } catch (e) { 23 | const error = new Error(`IconBox ${iconBox} not existed`); 24 | error.cause = e; 25 | throw error; 26 | } 27 | const use: RuleSetUseItem = { 28 | loader: path.resolve(__dirname, '../loaders/replace-icon.js'), 29 | options: { 30 | iconBoxLib, 31 | iconBoxDirname, 32 | }, 33 | }; 34 | compiler.options.module.rules.unshift({ 35 | test: compileGlob('**/node_modules/@arco-design/web-react/icon/react-icon/*/index.js'), 36 | use: [use], 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/plugins/theme.ts: -------------------------------------------------------------------------------- 1 | import { getFileSource } from '@arco-plugins/utils'; 2 | import type { Compiler, RuleSetUseItem } from '@rspack/core'; 3 | import path from 'path'; 4 | import { ARCO_DESIGN_COMPONENT_NAME } from '../config'; 5 | import { ArcoDesignPluginOptions } from '../types'; 6 | import { compileGlob } from '../utils'; 7 | import { getThemeComponents } from '../utils/theme'; 8 | 9 | export class ThemePlugin { 10 | options: ArcoDesignPluginOptions; 11 | 12 | constructor(options: ArcoDesignPluginOptions) { 13 | this.options = options; 14 | } 15 | 16 | apply(compiler: Compiler) { 17 | this.handleGlobalStyle(compiler); 18 | this.handleComponentStyle(compiler); 19 | } 20 | 21 | handleGlobalStyle(compiler: Compiler) { 22 | if (!this.options.theme) return; 23 | this._addAppendLoader( 24 | compiler, 25 | `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/{es,lib}/style/index.less`, 26 | `${this.options.theme}/theme.less` 27 | ); 28 | this._addAppendLoader( 29 | compiler, 30 | `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/{es,lib}/style/index.css`, 31 | `${this.options.theme}/theme.css` 32 | ); 33 | } 34 | 35 | handleComponentStyle(compiler: Compiler) { 36 | if (!this.options.theme) return; 37 | const componentList = getThemeComponents(this.options.theme); 38 | componentList.forEach((name) => { 39 | this._addAppendLoader( 40 | compiler, 41 | `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/{es,lib}/${name}/style/index.less`, 42 | `${this.options.theme}/components/${name}/index.less` 43 | ); 44 | this._addAppendLoader( 45 | compiler, 46 | `**/node_modules/${ARCO_DESIGN_COMPONENT_NAME}/{es,lib}/${name}/style/index.css`, 47 | `${this.options.theme}/components/${name}/index.css` 48 | ); 49 | }); 50 | } 51 | 52 | _addAppendLoader(compiler: Compiler, glob: string, request: string) { 53 | const ext = path.extname(glob); 54 | let source = ''; 55 | if (ext === '.css') { 56 | source = getFileSource(request); 57 | } else if (ext === '.less') { 58 | source = `;\n@import '~${request}';`; 59 | } else { 60 | throw new Error('Only accept to match css or less files.'); 61 | } 62 | if (!source) return; 63 | const use: RuleSetUseItem = { 64 | loader: require.resolve('../loaders/append'), 65 | options: { 66 | additionContent: source.replace(/!/g, '__ARCO_PLACEHOLDER__'), 67 | }, 68 | }; 69 | compiler.options.module.rules.push({ 70 | test: compileGlob(glob), 71 | type: undefined, 72 | use: [use], 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | import type { LoaderContext as WebpackLoaderContext } from 'webpack'; 3 | import type { LoaderContext as RspackLoaderContext } from '@rspack/core'; 4 | 5 | export interface ArcoDesignPluginOptions { 6 | style?: string | boolean; 7 | libraryDirectory?: string; 8 | iconBox?: string; 9 | removeFontFace?: boolean; 10 | defaultLanguage?: string; 11 | theme?: string; 12 | } 13 | 14 | export type LoaderContext = 15 | | WebpackLoaderContext 16 | | (Omit & { getOptions(schema?: any): T }); 17 | 18 | export interface LoaderDefinitionFunction { 19 | ( 20 | this: LoaderContext & ContextAdditions, 21 | content: string, 22 | sourceMap?: unknown, 23 | additionalData?: Record 24 | ): string | void | Buffer | Promise; 25 | } 26 | 27 | export type LoaderDefinition = LoaderDefinitionFunction; 28 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { makeRe } from 'minimatch'; 3 | 4 | export const compileGlob = (expr: string) => { 5 | const regex = makeRe(expr, { dot: true }); 6 | assert(regex); 7 | return { 8 | and: [(resource: string) => regex.test(resource)], 9 | toString() { 10 | return `glob(${expr})`; 11 | }, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/unplugin-react/src/utils/theme.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | /** 5 | * 将文件内容转为对象 6 | * @param {less file content}} source 7 | */ 8 | export function transformSourceToObject(source = '') { 9 | // 去掉注释 10 | const str = 11 | typeof source === 'string' && 12 | source 13 | .replace(/\/\/.*/g, '') // ‘//’ 之后的所有内容(以一行为结束) 14 | .replace(/\/\*[\s\S]*?\*\//g, ''); // ‘/**/’ 之间的所有内容 15 | if (!str.length) return; 16 | 17 | const ret: Record = {}; 18 | str 19 | .match(/(?=@)([\s\S]+?)(?=;)/g) // 匹配变量定义,结果为 ‘@变量名: 变量值’ 20 | .map((item) => item && item.split(':')) 21 | .filter((item) => item && item.length === 2) 22 | .forEach((item) => { 23 | const key = item[0].replace(/^@/, '').trim(); 24 | const value = item[1].trim(); 25 | if (key && value) { 26 | ret[key] = value; 27 | } 28 | }); 29 | 30 | return ret; 31 | } 32 | 33 | export function getThemeVars(theme: string) { 34 | try { 35 | const variableLessPath = require.resolve(`${theme}/tokens.less`); 36 | const source = fs.readFileSync(variableLessPath); 37 | return transformSourceToObject(source.toString()); 38 | } catch (error) { 39 | return {}; 40 | } 41 | } 42 | 43 | export function getThemeComponents(theme: string): string[] { 44 | try { 45 | const packageRootDir = path.dirname(require.resolve(`${theme}/package.json`)); 46 | const themeComponentDirPath = `${packageRootDir}/components`; 47 | const ret = fs.readdirSync(themeComponentDirPath); 48 | return ret || []; 49 | } catch { 50 | return []; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/unplugin-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "outDir": "./lib", 5 | "declaration": true, 6 | "moduleResolution": "node", 7 | "module": "commonjs", 8 | "target": "es2017", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "skipLibCheck": true, 12 | "types": ["node"], 13 | "lib": ["esnext"], 14 | }, 15 | "include": ["src"], 16 | "exclude": ["node_modules"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/utils/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | -------------------------------------------------------------------------------- /packages/utils/README.md: -------------------------------------------------------------------------------- 1 | # Utils for plugins 2 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@arco-plugins/utils", 3 | "version": "1.2.1", 4 | "description": "utils for arco design plugins", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "lib" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:arco-design/arco-plugins.git", 13 | "directory": "packages/utils" 14 | }, 15 | "scripts": { 16 | "build": "rm -rf lib && tsc", 17 | "prepublishOnly": "npm run build" 18 | }, 19 | "keywords": [ 20 | "arco", 21 | "arco-design", 22 | "arco-plugin", 23 | "plugin", 24 | "utils" 25 | ], 26 | "author": "arco-design", 27 | "license": "MIT", 28 | "dependencies": { 29 | "chalk": "^4.1.2", 30 | "lodash": "^4.17.20", 31 | "micromatch": "^4.0.2" 32 | }, 33 | "devDependencies": { 34 | "@types/lodash": "^4.14.180", 35 | "@types/micromatch": "^4.0.2", 36 | "@types/node": "^17.0.21", 37 | "typescript": "^4.5.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/utils/src/arrify.ts: -------------------------------------------------------------------------------- 1 | function isIterator(val: any): val is { [Symbol.iterator]() } { 2 | return typeof val[Symbol.iterator] === 'function'; 3 | } 4 | 5 | /** 6 | * 将传入的内容转化为数组 7 | * @param {any} value 8 | * @returns {array} 9 | */ 10 | export function arrify(value: T | T[]): T[] { 11 | if (value === null || value === undefined) { 12 | return []; 13 | } 14 | 15 | if (Array.isArray(value)) { 16 | return value; 17 | } 18 | 19 | if (typeof value === 'string') { 20 | return [value]; 21 | } 22 | 23 | if (isIterator(value)) { 24 | return [...value]; 25 | } 26 | 27 | return [value]; 28 | } 29 | -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | 3 | export { arrify } from './arrify'; 4 | export * as pathUtils from './pathUtils'; 5 | export { log as print } from './print'; 6 | 7 | export function getFileSource(filePath) { 8 | try { 9 | const fileAbsolutePath = require.resolve(filePath); 10 | const source = readFileSync(fileAbsolutePath); 11 | return source.toString(); 12 | } catch (error) { 13 | return ''; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/utils/src/pathUtils.ts: -------------------------------------------------------------------------------- 1 | import { extname, resolve } from 'path'; 2 | import { isString, isRegExp } from 'lodash'; 3 | import micromatch from 'micromatch'; 4 | 5 | import { arrify } from './arrify'; 6 | 7 | const UNESCAPED_GLOB_SYMBOLS_RE = /(\\?)([()*?[\]{|}]|^!|[!+@](?=\())/g; 8 | const NODE_MODULES_REGEXP = /(\/node_modules\/)/; 9 | 10 | /** 11 | * 替换反斜杠 12 | * @param {string} str 13 | * @returns {string} 14 | */ 15 | function replaceBackslashes(str: string) { 16 | return str.replace(/\\/g, '/'); 17 | } 18 | 19 | /** 20 | * 将后缀转为 glob 格式 21 | * @param {string|string[]} patterns 22 | * @param {string|string[]} extensions 23 | * @returns {string[]} 24 | */ 25 | function getExtensionsGlob(extensions: string[]) { 26 | const extensionsGlob = arrify(extensions).map((extension) => extension.replace(/^\./u, '')); 27 | const ext = extensionsGlob.length === 1 ? extensionsGlob[0] : `{${extensionsGlob.join(',')}}`; 28 | return `**/*.${ext}`; 29 | } 30 | 31 | /** 32 | * 将路径转为绝对路径 33 | * @param {string|string[]} paths 34 | * @param {string} context 35 | * @returns {string[]} 36 | */ 37 | function absoluteGlobPatterns(patterns: string | string[], context: string) { 38 | const validContext = `${replaceBackslashes(context).replace(UNESCAPED_GLOB_SYMBOLS_RE, '\\$2')}`; 39 | return arrify(patterns).map((p) => resolve(validContext, replaceBackslashes(p))); 40 | } 41 | 42 | /** 43 | * 转为 glob 格式 44 | * @param {string|string[]} patterns 45 | * @returns {string[]} 46 | */ 47 | function parseToGlobPatterns(patterns: string | string[], context: string) { 48 | return absoluteGlobPatterns(patterns, context).reduce((result, p) => { 49 | const isFolder = !extname(p); 50 | const g = isFolder ? p.replace(/[/\\]?$/u, `/**`) : p; 51 | result.push(g); 52 | if (NODE_MODULES_REGEXP.test(g)) { 53 | result.push(g.replace(NODE_MODULES_REGEXP, (_, $1) => `${$1}.pnpm/**${$1}`)); 54 | } 55 | return result; 56 | }, [] as string[]); 57 | } 58 | 59 | function splitStrAndRegExp(pattern: string | RegExp | (string | RegExp)[]) { 60 | const patterns = arrify(pattern); 61 | const strings: string[] = []; 62 | const regExps: RegExp[] = []; 63 | patterns.forEach((p) => { 64 | if (isRegExp(p)) { 65 | regExps.push(p); 66 | } else if (p && isString(p)) { 67 | strings.push(p); 68 | } 69 | }); 70 | return { 71 | strings, 72 | regExps, 73 | }; 74 | } 75 | 76 | interface PathMatchOptions { 77 | extensions?: string[]; // 后缀 78 | cwd?: string; // 上下文,如果有传递,将用于生成绝对路径 79 | extraGlobPattern?: string | string[]; // 额外的 glob 匹配路径 80 | } 81 | 82 | /** 83 | * 生成文件匹配函数 84 | * @param {string | RegExp | (string | RegExp)[]} pattern 85 | * @returns (resource: string) => boolean 86 | */ 87 | export function matcher( 88 | pattern: string | RegExp | (string | RegExp)[], 89 | options: PathMatchOptions = {} 90 | ) { 91 | const { extensions = [], cwd, extraGlobPattern = [] } = options; 92 | const { strings, regExps } = splitStrAndRegExp(pattern); 93 | const patternsForGlob = [ 94 | ...arrify(extraGlobPattern), 95 | ...(cwd 96 | ? strings.reduce((res, p) => { 97 | res.push(...parseToGlobPatterns(p, cwd)); 98 | return res; 99 | }, [] as string[]) 100 | : strings), 101 | ]; 102 | // 因为 resource中含有 . 符号时候默认会忽略匹配,所以设置 dot: true 103 | const globMatchOptions: micromatch.Options = { 104 | dot: true, 105 | }; 106 | const isMatchGlob = patternsForGlob.length 107 | ? (resource: string) => micromatch.isMatch(resource, patternsForGlob, globMatchOptions) 108 | : () => false; 109 | const isMatchRegExp = regExps.length 110 | ? (resource: string) => regExps.some((r) => r.test(resource)) 111 | : () => false; 112 | const isMatchExt = extensions.length 113 | ? micromatch.matcher(getExtensionsGlob(extensions), globMatchOptions) 114 | : () => true; 115 | return (resource: string) => { 116 | if (!isMatchExt(resource)) { 117 | return false; 118 | } 119 | return isMatchGlob(resource) || isMatchRegExp(resource); 120 | }; 121 | } 122 | 123 | /** 124 | * 返回是否文件路径匹配 125 | * @param {string} resource 126 | * @param {string | RegExp | (string | RegExp)[]} pattern 127 | * @returns boolean 128 | */ 129 | export function isMatch( 130 | resource: string, 131 | pattern: string | RegExp | (string | RegExp)[], 132 | options: PathMatchOptions = {} 133 | ) { 134 | return matcher(pattern, options)(resource); 135 | } 136 | -------------------------------------------------------------------------------- /packages/utils/src/print.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export function log(...args) { 4 | console.log(...args); 5 | } 6 | 7 | function print(color, ...args) { 8 | if (args.length > 1) { 9 | log( 10 | chalk[`bg${color.replace(/^\w/, (w) => w.toUpperCase())}`](` ${args[0]} `), 11 | chalk[color](args.slice(1)) 12 | ); 13 | } else { 14 | log(chalk[color](...args)); 15 | } 16 | } 17 | 18 | log.info = print.bind(null, 'gray'); 19 | log.warn = print.bind(null, 'yellow'); 20 | log.error = print.bind(null, 'red'); 21 | log.success = print.bind(null, 'green'); 22 | log.chalk = chalk; 23 | 24 | /** 25 | * 打印分割线 26 | * @param {'info' | 'warn' | 'success' | 'error'} level 27 | */ 28 | log.divider = (level = 'info') => { 29 | const logger = log[level] || log.info; 30 | logger('---------------------------------------------------------------------------------------'); 31 | }; 32 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "outDir": "./lib", 5 | "declaration": true, 6 | "moduleResolution": "node", 7 | "module": "CommonJS", 8 | "target": "esnext", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "skipLibCheck": true, 12 | "types": ["node"] 13 | }, 14 | "include": ["src"], 15 | "exclude": ["node_modules"] 16 | } 17 | --------------------------------------------------------------------------------