├── .changeset ├── README.md └── config.json ├── .commitlintrc.js ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── CONTRIBUTING.md ├── GIT_COMMIT_SPECIFIC.md ├── ISSUE_TEMPLATE.md └── workflows │ ├── ci.yml │ ├── pr.yml │ ├── release.yml │ └── version.yml ├── .gitignore ├── .npmrc ├── .prettierrc.js ├── LICENSE ├── README.md ├── examples ├── application │ ├── index.html │ ├── index.js │ ├── package.json │ └── webpack.config.js ├── plugin │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── rax-component │ ├── README.md │ ├── __mocks__ │ │ └── styleMock.js │ ├── babel.config.json │ ├── build.config.mts │ ├── docs │ │ ├── index.md │ │ ├── usage.md │ │ └── usage.module.css │ ├── enzyme-setup.ts │ ├── jest.config.mjs │ ├── package.json │ ├── src │ │ ├── components │ │ │ └── Header │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ ├── index.module.css │ │ ├── index.tsx │ │ └── typings.d.ts │ ├── tests │ │ ├── Header.spec.tsx │ │ └── types.d.ts │ └── tsconfig.json ├── react-component │ ├── README.md │ ├── __mocks__ │ │ └── styleMock.js │ ├── build.config.mts │ ├── docs │ │ ├── button.md │ │ ├── index.md │ │ ├── input.md │ │ └── test.md │ ├── jest-setup.ts │ ├── jest.config.mjs │ ├── package.json │ ├── pages │ │ ├── index.less │ │ └── index.tsx │ ├── src │ │ ├── a.cts │ │ ├── b.cjs │ │ ├── c.mts │ │ ├── components │ │ │ ├── Button │ │ │ │ ├── index.scss │ │ │ │ └── index.tsx │ │ │ ├── Input │ │ │ │ └── index.tsx │ │ │ └── Test │ │ │ │ ├── index.css │ │ │ │ └── index.jsx │ │ ├── d.mjs │ │ ├── index.ts │ │ └── typings.d.ts │ ├── tests │ │ ├── Button.spec.tsx │ │ └── Index.spec.tsx │ ├── tsconfig.json │ ├── vitest-setup.ts │ └── vitest.config.mts └── react-multi-components │ ├── README.md │ ├── build.config.mts │ ├── docs │ ├── Avatar.md │ ├── Button.md │ └── index.md │ ├── package.json │ ├── src │ ├── Avatar │ │ ├── default.png │ │ ├── index.css │ │ └── index.tsx │ ├── Button │ │ ├── index.css │ │ └── index.tsx │ ├── styles │ │ └── common.css │ └── typings.d.ts │ └── tsconfig.json ├── package.json ├── packages ├── create-pkg │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── checkEmpty.ts │ │ ├── index.mts │ │ ├── inquirePackageName.ts │ │ ├── inquireTemplateNpmName.ts │ │ ├── langs │ │ │ ├── en-US.ts │ │ │ ├── index.ts │ │ │ └── zh-CN.ts │ │ └── removeFilesAndContent.ts │ └── tsconfig.json ├── ice-npm-utils │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── index.test.ts │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── pkg │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ │ └── cli.mjs │ ├── package.json │ ├── src │ │ ├── commands │ │ │ ├── build.ts │ │ │ ├── start.ts │ │ │ └── test.ts │ │ ├── config │ │ │ ├── cliOptions.ts │ │ │ ├── index.ts │ │ │ └── userConfig.ts │ │ ├── constants.ts │ │ ├── defineConfig.ts │ │ ├── helpers │ │ │ ├── __tests__ │ │ │ │ └── getTaskIO.test.ts │ │ │ ├── builtinModules.ts │ │ │ ├── defaultSwcConfig.ts │ │ │ ├── dts.ts │ │ │ ├── formatAliasToTSPathsConfig.ts │ │ │ ├── getBabelOptions.ts │ │ │ ├── getBuildTasks.ts │ │ │ ├── getDefaultDefineValues.ts │ │ │ ├── getRollupOptions.ts │ │ │ ├── getTaskIO.ts │ │ │ ├── load.ts │ │ │ ├── logger.ts │ │ │ ├── pluginContainer.ts │ │ │ ├── reportSize.ts │ │ │ ├── suffix.ts │ │ │ └── watcher.ts │ │ ├── index.ts │ │ ├── plugins │ │ │ └── component.ts │ │ ├── rollupPlugins │ │ │ ├── alias.ts │ │ │ ├── babel.ts │ │ │ ├── dts.ts │ │ │ ├── minify.ts │ │ │ └── swc.ts │ │ ├── tasks │ │ │ ├── bundle.ts │ │ │ └── transform.ts │ │ ├── test │ │ │ ├── defineJestConfig.ts │ │ │ ├── defineVitestConfig.ts │ │ │ ├── getTaskConfig.ts │ │ │ └── index.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── tests │ │ ├── aliasPlugin.test.ts │ │ ├── babelPlugin.test.ts │ │ ├── createScriptsFilter.test.ts │ │ ├── fixtures │ │ │ ├── alias │ │ │ │ ├── build.config.default.mts │ │ │ │ ├── package.json │ │ │ │ ├── src │ │ │ │ │ ├── alias.ts │ │ │ │ │ └── index.ts │ │ │ │ └── tsconfig.json │ │ │ ├── bundle-browser │ │ │ │ ├── package.json │ │ │ │ └── src │ │ │ │ │ └── index.ts │ │ │ ├── default │ │ │ │ ├── package.json │ │ │ │ └── src │ │ │ │ │ └── index.ts │ │ │ ├── mock-entry-package │ │ │ │ ├── browser.js │ │ │ │ ├── index.d.ts │ │ │ │ ├── main.js │ │ │ │ ├── module.js │ │ │ │ └── package.json │ │ │ └── tsconfig.common.json │ │ ├── formatCnpmDepFilepath.test.ts │ │ └── projects │ │ │ ├── __snapshots__ │ │ │ ├── alias.test.ts.snap │ │ │ ├── bundle-browser.test.ts.snap │ │ │ └── default.test.ts.snap │ │ │ ├── alias.test.ts │ │ │ ├── bundle-browser.test.ts │ │ │ ├── default.test.ts │ │ │ └── helper.ts │ ├── tsconfig.json │ └── types.d.ts ├── plugin-docusaurus │ ├── CHANGELOG.md │ ├── README.md │ ├── build.config.mts │ ├── package.json │ ├── src │ │ ├── Previewer │ │ │ ├── Mobile.tsx │ │ │ ├── PC.tsx │ │ │ ├── index.tsx │ │ │ ├── iphoneX.png │ │ │ ├── styles.css │ │ │ └── styles.module.css │ │ ├── configureDocusaurus.mts │ │ ├── constants.mts │ │ ├── css │ │ │ └── custom.css │ │ ├── doc.mts │ │ ├── formatWinPath.cjs │ │ ├── genDemoPages │ │ │ ├── extractCodePlugin.mts │ │ │ └── index.mts │ │ ├── index.mts │ │ ├── plugin.js │ │ ├── remark │ │ │ ├── checkCodeLang.js │ │ │ ├── extractCode.js │ │ │ ├── fixedFilename.js │ │ │ ├── genDemoPages.js │ │ │ ├── getFileInfo.js │ │ │ ├── resolveImports.js │ │ │ └── uniqueFilename.js │ │ ├── template │ │ │ └── docusaurus.hbs │ │ ├── types.mts │ │ └── typings.d.ts │ └── tsconfig.json ├── plugin-jsx-plus │ ├── CHANGELOG.md │ ├── README.md │ ├── build.config.mts │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── plugin-rax-component │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json └── remark-react-docgen-docusaurus │ ├── CHANGELOG.md │ ├── README.md │ ├── build.config.mts │ ├── package.json │ ├── src │ ├── generateComponentInfo.ts │ ├── index.ts │ └── renderers │ │ └── renderComponentPropsToTable.ts │ ├── tsconfig.json │ └── typings.d.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts └── publish.ts ├── tsconfig.json ├── vitest.config.ts └── website ├── .gitignore ├── babel.config.js ├── docs ├── faq.md ├── guide │ ├── Button.tsx │ ├── abilities.md │ ├── build.md │ ├── jsx-plus.md │ ├── monorepo.md │ ├── preview.md │ ├── publish.md │ ├── scenarios.md │ └── test.md ├── index.md ├── quick-start.md └── reference │ ├── cli.md │ ├── config.md │ └── plugins-development.md ├── docusaurus.config.js ├── my-button ├── index.css ├── index.d.ts ├── index.js └── package.json ├── package.json ├── sidebars.js └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | 10 | ## Add A Changeset 11 | 12 | After you have completed a feature or fixed a bug, you need to do three things: 13 | 14 | - Select which packages should be released 15 | - Bump released packages version 16 | - Write Changelog for the released packages 17 | 18 | You can follow the steps: 19 | 20 | - Run the command line script `npm run changeset` 21 | - Select the packages you want to include in the changeset using `↑` and `↓` to navigate to packages, and `space` to select a package. Hit enter when all desired packages are selected. 22 | - You will be prompted to select a bump type for each selected package. Select an appropriate bump type for the changes made. See here for information on semver versioning 23 | - Your final prompt will be to provide a message to go alongside the changeset. This will be written into the changelog when the next release occurs. 24 | 25 | After that, you should commit changes to the remote repository. 26 | 27 | ```bash 28 | $ git status 29 | On branch test-3 30 | Untracked files: 31 | (use "git add ..." to include in what will be committed) 32 | .changeset/curvy-jobs-fly.md 33 | 34 | $ git commit -am "chore: add changeset" 35 | 36 | $ git push 37 | ``` 38 | 39 | For more detail, please see [this documentation](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md). 40 | 41 | ## Publish Beta Version 42 | 43 | > NOTE: You must add a changeset first before publishing beta version. 44 | 45 | Run the following commands to publish the beta version 46 | 47 | ```bash 48 | $ pnpm release:beta 49 | ``` 50 | 51 | Then, we need to commit changes to the remote repository. 52 | 53 | ```bash 54 | $ git status 55 | Changes not staged for commit: 56 | (use "git add ..." to update what will be committed) 57 | (use "git restore ..." to discard changes in working directory) 58 | modified: packages/a/CHANGELOG.md 59 | modified: packages/a/package.json 60 | modified: pnpm-workspace.yaml 61 | 62 | Untracked files: 63 | (use "git add ..." to include in what will be committed) 64 | .changeset/pre.json 65 | 66 | $ git commit -am "chore: beta version" 67 | 68 | $ git push 69 | ``` 70 | 71 | For more detail, please see this [documentation](https://github.com/changesets/changesets/blob/main/docs/prereleases.md). 72 | 73 | ## Publish Latest Version 74 | 75 | GitHub bot will automatically create a PR to update the latest versions for the released package. 76 | 77 | image 78 | 79 | What we need to do is merge the PR to the `release*` branch. 80 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [ 11 | "@ice/pkg-tests-*", 12 | "example-*" 13 | ], 14 | "snapshot": { 15 | "useCalculatedVersion": true, 16 | "prereleaseTemplate": "{tag}-{commit}-{datetime}" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | const { getCommitlintConfig } = require('@iceworks/spec'); 2 | 3 | module.exports = getCommitlintConfig('react'); 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 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 | # 忽略目录 2 | build/ 3 | tests/ 4 | demo/ 5 | .ice/ 6 | 7 | # node 覆盖率文件 8 | coverage/ 9 | node_modules 10 | extensions/*/out 11 | .vscode-test 12 | packages/**/lib/ 13 | packages/**/es/ 14 | packages/**/esnext/ 15 | packages/**/esm/ 16 | packages/**/es2017/ 17 | packages/**/cjs/ 18 | packages/**/build/ 19 | packages/build-plugin-component/*/template 20 | app/main_dist/ 21 | app/build/ 22 | tmp 23 | .tmp 24 | __mocks__ 25 | __tests__ 26 | 27 | # 忽略文件 28 | **/*-min.js 29 | **/*.min.js 30 | 31 | lib 32 | node_modules 33 | __tests__ 34 | examples/**/esm 35 | examples/**/es 36 | examples/**/es2017 37 | examples/**/cjs 38 | examples/**/lib 39 | examples/**/dist 40 | examples/**/build 41 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('@iceworks/spec'); 2 | 3 | module.exports = getESLintConfig('common-ts', { 4 | rules: { 5 | 'no-nested-ternary': 'off', 6 | 'no-await-in-loop': 'off', 7 | 'no-multi-assign': 'off', 8 | 'max-len': [ 9 | 'warn', 10 | 120, 11 | 2, 12 | { 13 | ignoreUrls: true, 14 | ignoreComments: false, 15 | ignoreRegExpLiterals: true, 16 | ignoreStrings: true, 17 | ignoreTemplateLiterals: true, 18 | }, 19 | ], 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Hi! I’m really excited that you are interested in contributing to ICE. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines. 4 | 5 | ## 历史分支 6 | 7 | ### [stable/0.x](https://github.com/ice-lab/icepkg/tree/stable/0.x/) 8 | 9 | - build-plugin-component@1.x 历史版本,以及相关包(已发布新方案) 10 | - ice-npm-utils@2.x 历史版本(已发布 3.x) 11 | - iceworks CLI 历史版本(已迁移 @appworks/cli) 12 | 13 | ## Setup Environment 14 | 15 | clone repo and initialize the setup environment: 16 | 17 | ```bash 18 | $ git clone git@github.com:ice-lab/iceworks.git 19 | ``` 20 | 21 | ## Pull Request Guidelines 22 | 23 | - Only code that's ready for release should be committed to the master branch. All development should be done in dedicated branches. 24 | - Checkout a **new** topic branch from master branch, and merge back against master branch. 25 | - Make sure `npm test` passes. 26 | - If adding new feature: 27 | - Add accompanying test case. 28 | - Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it greenlighted before working on it. 29 | - If fixing a bug: 30 | - If you are resolving a special issue, add `(fix #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `update entities encoding/decoding (fix #3899)`. 31 | - Provide detailed description of the bug in the PR. Live demo preferred. 32 | - Add appropriate test coverage if applicable. 33 | - Auto Publish 34 | - Add "publisher": "iceworks-team" into your extension package.json: 35 | ```json 36 | { 37 | "publisher": "iceworks-team" 38 | } 39 | ``` 40 | - When your PR has been merged into `master`, changed packages and VS Code Extensions will be auto published. 41 | - When your PR has been merged into `release/*`, changed packages will be auto publish its beta version. 42 | 43 | ## Issue Reporting Guidelines 44 | 45 | - The issue list of this repo is **exclusively** for bug reports and feature requests. Non-conforming issues will be closed immediately. 46 | - For simple beginner questions, you can get quick answers from 47 | - For more complicated questions, you can use Google or StackOverflow. Make sure to provide enough information when asking your questions - this makes it easier for others to help you! 48 | - Try to search for your issue, it may have already been answered or even fixed in the development branch. 49 | - It is **required** that you clearly describe the steps necessary to reproduce the issue you are running into. Issues with no clear repro steps will not be triaged. If an issue labeled "need repro" receives no further input from the issue author for more than 5 days, it will be closed. 50 | - For bugs that involves build setups, you can create a reproduction repository with steps in the README. 51 | - If your issue is resolved but still open, don’t hesitate to close it. In case you found a solution by yourself, it could be helpful to explain how you fixed it. 52 | 53 | ## Git Commit Specific 54 | 55 | - Your commits message must follow our [git commit specific](./GIT_COMMIT_SPECIFIC.md). 56 | - We will check your commit message, if it does not conform to the specification, the commit will be automatically refused, make sure you have read the specification above. 57 | - You could use `git cz` with a CLI interface to replace `git commit` command, it will help you to build a proper commit-message, see [commitizen](https://github.com/commitizen/cz-cli). 58 | - It's OK to have multiple small commits as you work on your branch - we will let GitHub automatically squash it before merging. 59 | -------------------------------------------------------------------------------- /.github/GIT_COMMIT_SPECIFIC.md: -------------------------------------------------------------------------------- 1 | # GIT COMMIT MESSAGE CHEAT SHEET 2 | 3 | **Proposed format of the commit message** 4 | 5 | ``` 6 | : 7 | 8 | 9 | ``` 10 | 11 | All lines are wrapped at 100 characters ! 12 | 13 | **Allowed ``** 14 | 15 | - feat (A new feature) 16 | - fix (A bug fix) 17 | - docs (Documentation only changes) 18 | - style (Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)) 19 | - perf (A code change that improves performance) 20 | - refactor (A code change that neither fixes a bug nor adds a feature) 21 | - test (Adding missing tests or correcting existing tests) 22 | - build (Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)) 23 | - ci (Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)) 24 | - chore (Other changes that don't modify src or test files) 25 | - revert (Reverts a previous commit) 26 | - release (Relase version) 27 | 28 | 29 | **Breaking changes** 30 | 31 | All breaking changes have to be mentioned in message body, on separated line: 32 | 33 | ​ _Breaks removed $browser.setUrl() method (use $browser.url(newUrl))_ 34 | ​ _Breaks ng: repeat option is no longer supported on selects (use ng:options)_ 35 | 36 | **Message body** 37 | 38 | - uses the imperative, present tense: “change” not “changed” nor “changes” 39 | - includes motivation for the change and contrasts with previous behavior 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | **Do you want to request a *feature* or report a *bug*?** 10 | 11 | **What is the current behavior?** 12 | 13 | If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. 14 | 15 | * Iceworks CLI version: 16 | * Node verson: 17 | * Platform: 18 | 19 | **What is the expected behavior?** 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push] 4 | 5 | jobs: 6 | ci: 7 | name: CI 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [18] 13 | 14 | steps: 15 | - name: Set branch name 16 | uses: actions/checkout@v3 17 | 18 | - name: Echo branch name 19 | run: echo ${BRANCH_NAME} 20 | 21 | - name: Install pnpm 22 | uses: pnpm/action-setup@v2 23 | 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'pnpm' 29 | 30 | - run: pnpm run setup 31 | - run: pnpm run lint 32 | - run: pnpm run test 33 | - run: pnpm run coverage 34 | 35 | - run: pnpm run build:doc 36 | - name: Deploy 37 | uses: JamesIves/github-pages-deploy-action@4.1.0 38 | if: github.ref == 'refs/heads/main' 39 | with: 40 | BRANCH: gh-pages 41 | FOLDER: website/build 42 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Check 2 | on: [pull_request] 3 | 4 | jobs: 5 | release: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - name: Checkout Branch 10 | uses: actions/checkout@v3 11 | 12 | - name: Install pnpm 13 | uses: pnpm/action-setup@v2 14 | 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 18 19 | cache: 'pnpm' 20 | 21 | - name: Setup 22 | run: pnpm run setup 23 | 24 | - run: pnpx pkg-pr-new publish './packages/*' --template './examples/*' 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout Branch 15 | uses: actions/checkout@v3 16 | 17 | - name: Install pnpm 18 | uses: pnpm/action-setup@v2 19 | 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 18 24 | cache: 'pnpm' 25 | 26 | - name: Setup 27 | run: pnpm run setup 28 | 29 | - name: Publish to npm 30 | id: changesets 31 | uses: changesets/action@v1 32 | with: 33 | publish: pnpm release 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/version.yml: -------------------------------------------------------------------------------- 1 | name: Version 2 | 3 | on: 4 | push: 5 | branches: 6 | - release-next 7 | 8 | jobs: 9 | version: 10 | name: Version 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout Branch 15 | uses: actions/checkout@v3 16 | 17 | - name: Install pnpm 18 | uses: pnpm/action-setup@v2 19 | 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 18 24 | cache: 'pnpm' 25 | 26 | - name: Setup 27 | run: pnpm run setup 28 | 29 | - name: Create Release Pull Request 30 | uses: changesets/action@v1 31 | with: 32 | version: pnpm run version 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | node_modules 10 | .DS_Store 11 | .eslintcache 12 | .happypack 13 | coverage/ 14 | tmp/ 15 | .tmp/ 16 | 17 | packages/*/lib/ 18 | packages/*/es/ 19 | .vscode-test 20 | 21 | **/.ice/ 22 | .idea/ 23 | .docusaurus 24 | **/package-lock.json 25 | **/build 26 | .history 27 | /.cache 28 | /node_modules 29 | /lib 30 | /packages/**/lib 31 | /packages/**/es 32 | /packages/**/esnext 33 | /packages/**/es2017 34 | /packages/**/cjs 35 | /packages/**/esm 36 | /packages/**/dist 37 | /packages/pkg/tests/fixtures/**/build.config.for-test.mts 38 | **/node_modules 39 | /.pnpm-debug.log 40 | **/pnpm-global 41 | examples/**/esm 42 | examples/**/es 43 | examples/**/es2017 44 | examples/**/cjs 45 | examples/**/lib 46 | examples/**/dist 47 | examples/**/.vscode 48 | examples/**/.DS_Store 49 | examples/**/.docusaurus 50 | examples/**/build 51 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const { getPrettierConfig } = require('@iceworks/spec'); 2 | 3 | module.exports = getPrettierConfig('react'); 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2018-present Alibaba Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @ice/pkg 2 | 3 | A fast builder for React components、Node modules and web libraries. 4 | 5 |
8 | benchmark 9 | 10 |
Above: this benchmark approximates a large TypeScript codebase by using ICE fusion pro template.
11 |
12 | 13 | ## Features 14 | 15 | - **Fast**:Code compiled and minified by [swc](https://swc.rs/docs/configuration/swcrc). 16 | - **Dual Mode**:Bundle mode to bundle everything up and transform mode to compile files one by one. 17 | - **Zero Configuration**:Zero Configuration with Typescript and JSX support. 18 | - **Modern Mode**:Outputs es2017 JavaScript specially designed to work in all modern browsers. 19 | - **Doc Preview**:Enhanced doc preview, powered by [Docusaurus](https://docusaurus.io/). 20 | 21 | 22 | ## Quick Start 23 | 24 | ```bash 25 | npm init @ice/pkg react-component 26 | 27 | # Or pnpm 28 | # pnpm create @ice/pkg react-component 29 | 30 | cd react-component 31 | npm run start 32 | ``` 33 | 34 | That's it. Start editing `src/index.tsx` and go! 35 | 36 | ## Documentation 37 | 38 | For complete usages, please dive into the [docs](https://pkg.ice.work/). 39 | 40 | ## Contributing 41 | 42 | Please see our [CONTRIBUTING.md](/.github/CONTRIBUTING.md) 43 | 44 | ## License 45 | 46 | [MIT](https://oss.ninja/mit/developit/) 47 | -------------------------------------------------------------------------------- /examples/application/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /examples/application/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from 'example-pkg-react-component'; 4 | 5 | ReactDOM.render(React.createElement(App, null, 'one'), document.getElementById('ice-container')); 6 | -------------------------------------------------------------------------------- /examples/application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-application", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "webpack --config webpack.config.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "example-pkg-react-component": "workspace:*", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0" 16 | }, 17 | "devDependencies": { 18 | "css-loader": "^6.6.0", 19 | "style-loader": "^3.3.1", 20 | "webpack": "^5.69.1", 21 | "webpack-cli": "^4.9.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/application/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'production', 3 | optimization: { 4 | minimize: false, 5 | }, 6 | resolve: { 7 | conditionNames: ['esnext'], 8 | }, 9 | entry: './index.js', 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.css$/i, 14 | use: ['style-loader', 'css-loader'], 15 | }, 16 | ], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /examples/plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-pkg-plugin", 3 | "private": true, 4 | "version": "0.0.0", 5 | "main": "lib/index.js", 6 | "type": "module", 7 | "exports": "./lib/index.js", 8 | "files": [ 9 | "lib" 10 | ], 11 | "scripts": { 12 | "build": "tsc", 13 | "watch": "tsc -w" 14 | }, 15 | "devDependencies": { 16 | "@ice/pkg": "workspace:*", 17 | "typescript": "catalog:" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import { BundleTaskConfig, TaskName } from '@ice/pkg'; 2 | import type { Plugin } from '@ice/pkg'; 3 | 4 | const plugin: Plugin = (api) => { 5 | const { onGetConfig } = api; 6 | 7 | const bundleTaskCallback: Parameters[0] = async (config: BundleTaskConfig) => { 8 | // if (config.type === 'transform') { 9 | // return; 10 | // } 11 | config.extensions = [ 12 | '.js', 13 | '.json', 14 | '.jsx', 15 | '.ts', 16 | '.tsx', 17 | '.html', 18 | ]; 19 | config.entry = { 20 | avatar: './src/Avatar/index', 21 | button: './src/Button/index', 22 | }; 23 | // config.sourcemap = true; 24 | config.alias = { ...config.alias }; 25 | config.externals = { react: 'React', 'react-dom': 'ReactDOM' }; 26 | 27 | config.modifyStylesOptions ??= []; 28 | config.modifyStylesOptions.push((options) => { 29 | return options; 30 | }); 31 | config.modifySwcCompileOptions = (originOptions) => { 32 | return originOptions; 33 | }; 34 | }; 35 | 36 | onGetConfig(TaskName.BUNDLE_ES2017, bundleTaskCallback); 37 | onGetConfig(TaskName.BUNDLE_ES5, bundleTaskCallback); 38 | }; 39 | 40 | export default plugin; 41 | -------------------------------------------------------------------------------- /examples/plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ESNext", 5 | "jsx": "react", 6 | "moduleResolution": "node", 7 | "lib": ["ESNext"], 8 | "outDir": "lib", 9 | "skipLibCheck": true 10 | }, 11 | "include": ["src"], 12 | } 13 | -------------------------------------------------------------------------------- /examples/rax-component/README.md: -------------------------------------------------------------------------------- 1 | # example-rax-component 2 | 3 | 组件功能描述 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ npm i example-rax-component --save 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```jsx 14 | import ExampleRaxComponent from 'example-rax-component'; 15 | ``` 16 | -------------------------------------------------------------------------------- /examples/rax-component/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | 3 | -------------------------------------------------------------------------------- /examples/rax-component/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-typescript", 5 | { 6 | "onlyRemoveTypeImports": true 7 | } 8 | ], 9 | ["@babel/preset-env", { 10 | "loose": true 11 | }], 12 | ["@babel/preset-react", { 13 | "pragma": "createElement" 14 | }] 15 | ] 16 | } -------------------------------------------------------------------------------- /examples/rax-component/build.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@ice/pkg'; 2 | 3 | // https://pkg.ice.work/reference/config-list/ 4 | export default defineConfig({ 5 | plugins: [ 6 | '@ice/pkg-plugin-docusaurus', 7 | '@ice/pkg-plugin-rax-component', 8 | ['@ice/pkg-plugin-jsx-plus', { moduleName: 'rax' }], 9 | ], 10 | }); 11 | -------------------------------------------------------------------------------- /examples/rax-component/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 首页 3 | sidebar_position: 0 4 | --- 5 | 6 | import Readme from '../README.md'; 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/rax-component/docs/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 用法 3 | --- 4 | 5 | 本 Demo 演示一行文字的用法。 6 | 7 | ```tsx preview 8 | import { createElement } from 'rax'; 9 | import ExampleRaxComponent from 'example-rax-component'; 10 | import styles from './usage.module.css'; 11 | 12 | export default function App () { 13 | return ( 14 |
15 | 16 |
17 | ) 18 | } 19 | ``` 20 | -------------------------------------------------------------------------------- /examples/rax-component/docs/usage.module.css: -------------------------------------------------------------------------------- 1 | /* Example for CSS use in usage.md in case of need. */ 2 | .usageContainer {} 3 | -------------------------------------------------------------------------------- /examples/rax-component/enzyme-setup.ts: -------------------------------------------------------------------------------- 1 | import * as enzyme from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-rax'; 3 | 4 | // Setup Enzyme 5 | enzyme.configure({ adapter: new Adapter() }); 6 | -------------------------------------------------------------------------------- /examples/rax-component/jest.config.mjs: -------------------------------------------------------------------------------- 1 | import pkgService, { defineJestConfig } from '@ice/pkg'; 2 | 3 | export default defineJestConfig(pkgService, { 4 | setupFilesAfterEnv: ['/enzyme-setup.ts'], 5 | moduleNameMapper: { 6 | '\\.(css|less|scss)$': '/__mocks__/styleMock.js', 7 | }, 8 | transformIgnorePatterns: ['/node_modules/rax-*'], 9 | }); 10 | -------------------------------------------------------------------------------- /examples/rax-component/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-rax-component", 3 | "version": "0.1.0", 4 | "description": "组件功能描述", 5 | "private": true, 6 | "files": [ 7 | "esm", 8 | "es2017", 9 | "cjs", 10 | "dist" 11 | ], 12 | "main": "esm/index.js", 13 | "module": "esm/index.js", 14 | "types": "esm/index.d.ts", 15 | "exports": { 16 | ".": { 17 | "es2017": { 18 | "types": "./es2017/index.d.ts", 19 | "default": "./es2017/index.js" 20 | }, 21 | "default": { 22 | "types": "./esm/index.d.ts", 23 | "default": "./esm/index.js" 24 | } 25 | }, 26 | "./*": "./*" 27 | }, 28 | "sideEffects": [ 29 | "dist/*", 30 | "*.scss", 31 | "*.less", 32 | "*.css" 33 | ], 34 | "scripts": { 35 | "start": "ice-pkg start", 36 | "build": "ice-pkg build", 37 | "prepublishOnly": "npm run build", 38 | "eslint": "eslint --cache --ext .js,.jsx,.ts,.tsx ./", 39 | "eslint:fix": "npm run eslint -- --fix", 40 | "stylelint": "stylelint \"**/*.{css,scss,less}\"", 41 | "lint": "npm run eslint && npm run stylelint", 42 | "test": "jest" 43 | }, 44 | "keywords": [ 45 | "ice", 46 | "react", 47 | "component" 48 | ], 49 | "dependencies": { 50 | "@swc/helpers": "^0.5.1", 51 | "babel-runtime-jsx-plus": "^0.1.5", 52 | "rax-view": "^2.3.0" 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.17.5", 56 | "@babel/preset-env": "^7.20.2", 57 | "@babel/preset-react": "^7.18.6", 58 | "@babel/preset-typescript": "^7.18.6", 59 | "@ice/pkg": "workspace:*", 60 | "@ice/pkg-plugin-docusaurus": "workspace:*", 61 | "@ice/pkg-plugin-jsx-plus": "workspace:*", 62 | "@ice/pkg-plugin-rax-component": "workspace:*", 63 | "@testing-library/jest-dom": "^5.16.5", 64 | "@types/enzyme": "^3.10.12", 65 | "@types/rax": "^1.0.0", 66 | "@types/testing-library__jest-dom": "^5.14.5", 67 | "enzyme": "^3.11.0", 68 | "enzyme-adapter-rax": "^1.0.3", 69 | "jest": "^28.0.0", 70 | "rax": "^1.2.2", 71 | "rax-test-renderer": "^1.1.0", 72 | "react": "^18.0.0", 73 | "react-dom": "^18.0.0" 74 | }, 75 | "peerDependencies": { 76 | "rax": "^1.2.2" 77 | }, 78 | "publishConfig": { 79 | "access": "public" 80 | }, 81 | "license": "MIT" 82 | } -------------------------------------------------------------------------------- /examples/rax-component/src/components/Header/index.css: -------------------------------------------------------------------------------- 1 | div { 2 | color: red 3 | } -------------------------------------------------------------------------------- /examples/rax-component/src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import { createElement } from 'rax'; 2 | import View from 'rax-view'; 3 | import './index.css'; 4 | 5 | export default function Header() { 6 | return ( 7 | Header 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /examples/rax-component/src/index.module.css: -------------------------------------------------------------------------------- 1 | /* Write your style here. */ 2 | .ExampleRaxComponent { 3 | 4 | } 5 | -------------------------------------------------------------------------------- /examples/rax-component/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createElement, Fragment } from 'rax'; 2 | import styles from './index.module.css'; 3 | import Header from './components/Header'; 4 | 5 | interface ComponentProps { 6 | /** Title for ExampleRaxComponent. */ 7 | title: string; 8 | } 9 | 10 | export default function ExampleRaxComponent(props: ComponentProps) { 11 | const { title = 'Hello World!' } = props; 12 | console.log(__DEV__); 13 | const role = 'admin'; 14 | return ( 15 |
16 |
17 | {title} 18 | <>xxxxxxxx 19 |
admin
20 |
guest
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /examples/rax-component/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/rax-component/tests/Header.spec.tsx: -------------------------------------------------------------------------------- 1 | import { createElement } from 'rax'; 2 | import renderer from 'rax-test-renderer'; 3 | import Header from '../src/components/Header'; 4 | 5 | test('test 21 | 22 | 23 | ) 24 | } 25 | ``` 26 | 27 | ### 添加事件 28 | 29 | 可以通过 `onClick` 设置点击按钮后的事件回调函数。 30 | 31 | ```tsx preview 32 | import * as React from 'react'; 33 | import { Button } from 'example-pkg-react-component'; 34 | 35 | export default function App () { 36 | return ( 37 |
38 | 39 |
40 | ) 41 | } 42 | ``` 43 | 44 | ## API 45 | 46 | 47 | -------------------------------------------------------------------------------- /examples/react-component/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 快速开始 3 | sidebar_position: 0 4 | title: 快速开始 5 | --- 6 | 7 | import Readme from '../README.md'; 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/react-component/docs/input.md: -------------------------------------------------------------------------------- 1 | # Input 组件 2 | 3 | ## 何时使用 4 | 5 | 这是一段 Input 组件使用描述 6 | 7 | ## 代码演示 8 | 9 | ### 基本使用 10 | 11 | 12 | ```tsx preview 13 | import * as React from 'react'; 14 | import { Input } from 'example-pkg-react-component'; 15 | 16 | export default function App () { 17 | return ( 18 |
19 | 20 |
21 | ) 22 | } 23 | ``` 24 | 25 | ## API 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/react-component/docs/test.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: Test 组件 3 | title: Test 4 | --- 5 | 6 | 本 Demo 演示一行文字的用法。 7 | 8 | ```tsx preview 9 | import * as React from 'react'; 10 | import { Test } from 'example-pkg-react-component'; 11 | 12 | export default function App () { 13 | return ( 14 | 15 | ) 16 | } 17 | ``` 18 | ## API 19 | 20 | -------------------------------------------------------------------------------- /examples/react-component/jest-setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /examples/react-component/jest.config.mjs: -------------------------------------------------------------------------------- 1 | import pkgService, { defineJestConfig } from '@ice/pkg'; 2 | 3 | export default defineJestConfig(pkgService, { 4 | preset: 'ts-jest', 5 | setupFilesAfterEnv: ['/jest-setup.ts'], 6 | testEnvironment: 'jest-environment-jsdom', 7 | moduleNameMapper: { 8 | '\\.module\\.(css|scss|less)$': 'identity-obj-proxy', 9 | '\\.(css|less|scss)$': '/__mocks__/styleMock.js', 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /examples/react-component/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-pkg-react-component", 3 | "version": "0.0.0", 4 | "private": true, 5 | "files": [ 6 | "esm", 7 | "cjs", 8 | "es2017", 9 | "dist", 10 | "build" 11 | ], 12 | "exports": { 13 | ".": { 14 | "es2017": { 15 | "types": "./es2017/index.d.ts", 16 | "default": "./es2017/index.js" 17 | }, 18 | "default": { 19 | "types": "./esm/index.d.ts", 20 | "default": "./esm/index.js" 21 | } 22 | }, 23 | "./*": "./*" 24 | }, 25 | "sideEffects": [ 26 | "dist/*", 27 | "*.scss", 28 | "*.less", 29 | "*.css" 30 | ], 31 | "scripts": { 32 | "start": "ice-pkg start", 33 | "build": "ice-pkg build", 34 | "prepublishOnly": "npm run build", 35 | "vitest": "vitest", 36 | "jest": "jest" 37 | }, 38 | "dependencies": { 39 | "@ice/jsx-runtime": "^0.2.0", 40 | "@swc/helpers": "^0.5.1", 41 | "babel-runtime-jsx-plus": "^0.1.5" 42 | }, 43 | "devDependencies": { 44 | "@ice/pkg": "workspace:*", 45 | "@ice/pkg-plugin-docusaurus": "workspace:*", 46 | "@ice/pkg-plugin-jsx-plus": "workspace:*", 47 | "@ice/remark-react-docgen-docusaurus": "workspace:*", 48 | "@testing-library/jest-dom": "^5.16.5", 49 | "@testing-library/react": "^14.0.0", 50 | "@types/react": "^18.0.0", 51 | "@types/react-dom": "^18.0.0", 52 | "@types/testing-library__jest-dom": "^5.14.5", 53 | "identity-obj-proxy": "^3.0.0", 54 | "jest": "^29.0.0", 55 | "jest-environment-jsdom": "^29.0.0", 56 | "jsdom": "^21.1.0", 57 | "prop-types": "^15.8.1", 58 | "react": "^18.2.0", 59 | "react-dom": "^18.2.0", 60 | "sass": "catalog:", 61 | "sass-loader": "catalog:", 62 | "style-unit": "^3.0.4", 63 | "ts-jest": "^29.0.0", 64 | "vitest": "catalog:" 65 | }, 66 | "peerDependencies": { 67 | "react": "^17 || ^18" 68 | }, 69 | "publishConfig": { 70 | "access": "public" 71 | }, 72 | "license": "MIT" 73 | } 74 | -------------------------------------------------------------------------------- /examples/react-component/pages/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | font-size: 14px; 3 | } 4 | -------------------------------------------------------------------------------- /examples/react-component/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './index.less'; 3 | 4 | export default function () { 5 | return ( 6 |
Index
7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /examples/react-component/src/a.cts: -------------------------------------------------------------------------------- 1 | export interface Person { 2 | age: number; 3 | name: string; 4 | } 5 | 6 | export const p: Person = { age: 3, name: 'ice' }; 7 | 8 | const { ...rest } = p; 9 | 10 | console.log(rest); 11 | -------------------------------------------------------------------------------- /examples/react-component/src/b.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | console.log(123); 3 | }; 4 | -------------------------------------------------------------------------------- /examples/react-component/src/c.mts: -------------------------------------------------------------------------------- 1 | export default function () { 2 | console.log('c'); 3 | } 4 | -------------------------------------------------------------------------------- /examples/react-component/src/components/Button/index.scss: -------------------------------------------------------------------------------- 1 | .pkg-btn { 2 | border-radius: 6px; 3 | height: 32px; 4 | padding: 4px 15px; 5 | font-size: 14px; 6 | text-align: center; 7 | border: 1px solid transparent; 8 | cursor: pointer; 9 | font-weight: 400; 10 | 11 | } 12 | 13 | .pkg-btn-primary { 14 | color: #fff; 15 | background-color: #1677ff; 16 | box-shadow: 0 2px 0 rgb(5 145 255 / 10%); 17 | } 18 | 19 | .pkg-btn-default { 20 | background-color: #fff; 21 | border-color: #d9d9d9; 22 | box-shadow: 0 2px 0 rgb(0 0 0 / 2%); 23 | } -------------------------------------------------------------------------------- /examples/react-component/src/components/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './index.scss'; 3 | import Input from '@/components/Input'; 4 | 5 | console.log('input', Input); 6 | 7 | interface ButtonProps { 8 | /** 9 | * 设置按钮类型 10 | */ 11 | type?: 'primary' | 'default'; 12 | /** 13 | * 点击跳转的地址,指定此属性 button 的行为和 a 链接一致 14 | */ 15 | href?: string; 16 | /** 17 | * 显式加载状态 18 | */ 19 | loading: boolean; 20 | /** 21 | * 点击按钮时的回调 22 | */ 23 | onClick?: React.MouseEventHandler; 24 | } 25 | 26 | export const app: Application = { add: () => Promise.resolve(1) }; 27 | 28 | const Button: React.FunctionComponent> = (props: ButtonProps) => { 29 | const { 30 | type = 'default', 31 | } = props; 32 | const typeCssSelector = { 33 | primary: 'pkg-btn-primary', 34 | default: 'pkg-btn-default', 35 | }; 36 | return ( 37 | 44 | ); 45 | }; 46 | 47 | Button.defaultProps = { 48 | type: 'default', 49 | onClick: () => { }, 50 | href: undefined, 51 | }; 52 | 53 | export default Button; 54 | -------------------------------------------------------------------------------- /examples/react-component/src/components/Input/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class Input extends React.Component { 5 | render() { 6 | return ( 7 | <> 8 | 9 | 10 | ); 11 | } 12 | } 13 | 14 | Input.propTypes = { 15 | /** 16 | * 输入框的 id 17 | */ 18 | id: PropTypes.string, 19 | /** 20 | * 设置校验状态 21 | */ 22 | status: PropTypes.string, 23 | }; 24 | -------------------------------------------------------------------------------- /examples/react-component/src/components/Test/index.css: -------------------------------------------------------------------------------- 1 | /* This is comment */ 2 | .title { 3 | color: red; 4 | } 5 | 6 | .btn { 7 | width: 50rpx; 8 | } 9 | -------------------------------------------------------------------------------- /examples/react-component/src/components/Test/index.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import * as React from 'react'; 3 | import './index.css'; 4 | /** 5 | * Test component 6 | */ 7 | const Test = ({ title }) => { 8 | // eslint-disable-next-line 9 | console.log(__DEV__); 10 | console.log(process.env.NODE_ENV); 11 | 12 | const [visible, setVisible] = React.useState(false); 13 | return ( 14 |
15 |

{title}

16 | 17 | 18 |
19 |
Hello
20 |
World
21 |
22 |
23 | ); 24 | }; 25 | 26 | Test.propTypes = { 27 | /** 28 | * 29 | */ 30 | title: PropTypes.string, 31 | /** 32 | * 33 | */ 34 | baz: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 35 | 36 | /** 37 | * 38 | */ 39 | bar(props, propName, componentName) { 40 | // ... 41 | }, 42 | }; 43 | 44 | Test.defaultProps = { 45 | title: 'Hello World', 46 | bar: () => {}, 47 | baz: 'baz', 48 | }; 49 | 50 | export default Test; 51 | -------------------------------------------------------------------------------- /examples/react-component/src/d.mjs: -------------------------------------------------------------------------------- 1 | export default function () { 2 | console.log('d'); 3 | return 'd'; 4 | } 5 | -------------------------------------------------------------------------------- /examples/react-component/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Button } from '@/components/Button'; 2 | export { default as Test } from '@/components/Test'; 3 | export { default as Input } from '@/components/Input'; 4 | -------------------------------------------------------------------------------- /examples/react-component/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface Application { 4 | add: () => Promise; 5 | } 6 | -------------------------------------------------------------------------------- /examples/react-component/tests/Button.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import Button from '../src/components/Button'; 4 | 5 | test('test ); 7 | expect(screen.getByTestId('normal-button')).toHaveTextContent('PKG'); 8 | }); 9 | -------------------------------------------------------------------------------- /examples/react-component/tests/Index.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import Test from '../src/components/Test'; 4 | 5 | test('test 13 | 14 | ) 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /examples/react-multi-components/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 首页 3 | sidebar_position: 0 4 | --- 5 | 6 | import Readme from '../README.md'; 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/react-multi-components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-pkg-react-multi-components", 3 | "version": "0.0.0", 4 | "private": true, 5 | "files": [ 6 | "esm", 7 | "cjs", 8 | "es2017", 9 | "dist", 10 | "build" 11 | ], 12 | "exports": { 13 | "./Button": { 14 | "es2017": { 15 | "types": "./es2017/Button/index.d.ts", 16 | "default": "./es2017/Button/index.js" 17 | }, 18 | "import": { 19 | "types": "./esm/Button/index.d.ts", 20 | "default": "./esm/Button/index.js" 21 | } 22 | }, 23 | "./Avatar": { 24 | "es2017": { 25 | "types": "./es2017/Avatar/index.d.ts", 26 | "default": "./es2017/Avatar/index.js" 27 | }, 28 | "import": { 29 | "types": "./esm/Avatar/index.d.ts", 30 | "default": "./esm/Avatar/index.js" 31 | } 32 | } 33 | }, 34 | "sideEffects": [ 35 | "dist/*", 36 | "*.scss", 37 | "*.less", 38 | "*.css" 39 | ], 40 | "scripts": { 41 | "start": "ice-pkg start", 42 | "build": "ice-pkg build", 43 | "prepublishOnly": "npm run build" 44 | }, 45 | "dependencies": { 46 | "@ice/jsx-runtime": "^0.2.0", 47 | "@swc/helpers": "^0.5.1" 48 | }, 49 | "devDependencies": { 50 | "@ice/pkg": "workspace:*", 51 | "@ice/pkg-plugin-docusaurus": "workspace:*", 52 | "example-pkg-plugin": "workspace:*", 53 | "react": "^18.2.0", 54 | "react-dom": "^18.2.0", 55 | "@types/react": "^18.0.0", 56 | "@types/react-dom": "^18.0.0", 57 | "style-unit": "^3.0.4" 58 | }, 59 | "peerDependencies": { 60 | "react": "^17 || ^18" 61 | }, 62 | "publishConfig": { 63 | "access": "public" 64 | }, 65 | "license": "MIT" 66 | } -------------------------------------------------------------------------------- /examples/react-multi-components/src/Avatar/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-lab/icepkg/3c3f7e65bbc3980b79611eea7a11555b463e1834/examples/react-multi-components/src/Avatar/default.png -------------------------------------------------------------------------------- /examples/react-multi-components/src/Avatar/index.css: -------------------------------------------------------------------------------- 1 | .pkg-avatar { 2 | width: 32px; 3 | height: 32px; 4 | border-radius: 50%; 5 | border: 1px solid transparent; 6 | background-color: rgb(135, 208, 104); 7 | } 8 | -------------------------------------------------------------------------------- /examples/react-multi-components/src/Avatar/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import defaultAvatar from './default.png'; 3 | import './index.css'; 4 | import '../styles/common.css'; 5 | 6 | interface AvatarProps { 7 | 8 | } 9 | 10 | const Avatar: React.FunctionComponent> = (props) => { 11 | return ( 12 | avatar 13 | ); 14 | }; 15 | 16 | export default Avatar; 17 | -------------------------------------------------------------------------------- /examples/react-multi-components/src/Button/index.css: -------------------------------------------------------------------------------- 1 | .pkg-btn { 2 | border-radius: 6px; 3 | height: 32px; 4 | padding: 4px 15px; 5 | font-size: 14px; 6 | text-align: center; 7 | border: 1px solid transparent; 8 | cursor: pointer; 9 | font-weight: 400; 10 | } 11 | 12 | .pkg-btn-primary { 13 | color: #fff; 14 | background-color: #1677ff; 15 | box-shadow: 0 2px 0 rgb(5 145 255 / 10%); 16 | } 17 | 18 | .pkg-btn-default { 19 | background-color: #fff; 20 | border-color: #d9d9d9; 21 | box-shadow: 0 2px 0 rgb(0 0 0 / 2%); 22 | } -------------------------------------------------------------------------------- /examples/react-multi-components/src/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './index.css'; 3 | import '../styles/common.css'; 4 | 5 | interface ButtonProps { 6 | type?: 'primary' | 'default'; 7 | } 8 | 9 | const Button: React.FunctionComponent> = (props) => { 10 | const { 11 | type = 'default', 12 | } = props; 13 | const typeCssSelector = { 14 | primary: 'pkg-btn-primary', 15 | default: 'pkg-btn-default', 16 | }; 17 | return ( 18 | 21 | ); 22 | }; 23 | 24 | export default Button; 25 | -------------------------------------------------------------------------------- /examples/react-multi-components/src/styles/common.css: -------------------------------------------------------------------------------- 1 | a { 2 | color: red; 3 | } -------------------------------------------------------------------------------- /examples/react-multi-components/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/react-multi-components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ESNext", 5 | "jsx": "react", 6 | "moduleResolution": "nodenext", 7 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "paths": { 11 | "example-pkg-react-multi-components": ["./src"], 12 | "example-pkg-react-multi-components/*": ["./src/*"] 13 | } 14 | }, 15 | "include": ["src"], 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "icepkg", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "The next generation of npm package development kit.", 6 | "scripts": { 7 | "preinstall": "npx only-allow pnpm", 8 | "setup": "pnpm i && pnpm run build", 9 | "build": "pnpm run clean && pnpm -r --filter=./packages/* run build", 10 | "watch": "pnpm --parallel --filter=./packages/* run watch", 11 | "clean": "rm -rf packages/*/lib", 12 | "test": "vitest run", 13 | "coverage": "vitest run --coverage", 14 | "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx,.mts,.mjs ./", 15 | "lint:fix": "pnpm run lint --fix", 16 | "build:doc": "cd website && pnpm run build", 17 | "changeset": "changeset", 18 | "install:frozen": "pnpm install --frozen-lockfile false", 19 | "version": "changeset version && pnpm install:frozen", 20 | "release": "changeset publish", 21 | "release:beta": "changeset pre enter beta && pnpm run version && pnpm build && pnpm release && changeset pre exit", 22 | "release:snapshot": "changeset version --snapshot canary && pnpm install:frozen && pnpm build && pnpm release --tag canary --no-git-tag --snapshot" 23 | }, 24 | "author": "ICE Team", 25 | "devDependencies": { 26 | "@changesets/cli": "^2.26.0", 27 | "@commitlint/cli": "^15.0.0", 28 | "@iceworks/spec": "^1.6.0", 29 | "@types/fs-extra": "^9.0.13", 30 | "@types/node": "^17.0.45", 31 | "@vitest/coverage-c8": "catalog:", 32 | "axios": "^0.23.0", 33 | "c8": "^7.11.3", 34 | "eslint": "^8.19.0", 35 | "fs-extra": "^10.1.0", 36 | "ice-npm-utils": "workspace:^3.0.2", 37 | "npm-run-all": "^4.1.5", 38 | "rimraf": "^3.0.2", 39 | "stylelint": "^13.13.1", 40 | "ts-node": "^10.8.2", 41 | "typescript": "catalog:", 42 | "vitest": "catalog:" 43 | }, 44 | "packageManager": "pnpm@9.6.0" 45 | } 46 | -------------------------------------------------------------------------------- /packages/create-pkg/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.3.5 4 | 5 | - fix: remove docs directory 6 | 7 | ## 1.3.4 8 | 9 | - chore: rename internal -> isAliInternal 10 | 11 | ## 1.3.3 12 | 13 | - feat: support handle pkg-plugin-dev 14 | 15 | ## 1.3.2 16 | 17 | ### Patch Changes 18 | 19 | - e8e0c08: fix: show monorepo templates when creating a sub package 20 | - 1b0cad7: feat: add workspace template option 21 | 22 | ## 1.3.1 23 | 24 | ### Patch Changes 25 | 26 | - 458fa1f: fix: npm name is empty when create template 27 | 28 | ## 1.3.0 29 | 30 | ### Minor Changes 31 | 32 | - b48cb6c: feat: support `-w` option to generate sub package in workspace 33 | 34 | ### Patch Changes 35 | 36 | - e9a8a4b: chore: update fields in package.json 37 | 38 | ## 1.2.1 39 | 40 | ### Patch Changes 41 | 42 | - 081fb8d: chore: optimize project type list items order 43 | 44 | ## 1.2.1-beta.1 45 | 46 | ### Patch Changes 47 | 48 | - chore: release beta version 49 | 50 | ## 1.2.1-beta.0 51 | 52 | ### Patch Changes 53 | 54 | - 081fb8d: chore: optimize project type list items order 55 | 56 | ## 1.2.0 57 | 58 | - [feat] support `--template` and `--npmName` cli option 59 | - [chore] remove `@ice/pkg-cli` package 60 | 61 | ## 1.1.2 62 | 63 | - [feat] update builder 64 | 65 | ## 1.1.1 66 | 67 | - [fix] can't check ali Intranet correctly 68 | - [feat] remove prompt of scope choose if in ali Intranet 69 | 70 | ## 1.1.0 71 | 72 | - [feat] support rax component/web library/node module project type 73 | - [feat] support language locale 74 | 75 | ## 1.0.1 76 | 77 | - [fix] remove `@@appworks/cli`. 78 | 79 | ## 1.0.0 80 | 81 | - init 82 | -------------------------------------------------------------------------------- /packages/create-pkg/README.md: -------------------------------------------------------------------------------- 1 | # @ice/create-pkg 2 | 3 | Create ice-pkg projects by `npm init`. 4 | 5 | ## Usage 6 | 7 | ```shell 8 | $ npm init @ice/pkg component-name 9 | 10 | # or with pnpm 11 | $ pnpm create @ice/pkg component-name 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /packages/create-pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ice/create-pkg", 3 | "version": "1.3.5", 4 | "description": "npm init @ice/pkg", 5 | "type": "module", 6 | "exports": "./lib/index.mjs", 7 | "bin": { 8 | "create-pkg": "./lib/index.mjs" 9 | }, 10 | "files": [ 11 | "lib", 12 | "!lib/**/*.map" 13 | ], 14 | "engines": { 15 | "node": ">=16.14.0" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/ice-lab/icepkg.git", 20 | "directory": "packages/create-pkg" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/ice-lab/icepkg/issues" 24 | }, 25 | "homepage": "https://pkg.ice.work", 26 | "license": "MIT", 27 | "scripts": { 28 | "watch": "tsc -w", 29 | "build": "rm -rf lib && tsc" 30 | }, 31 | "devDependencies": { 32 | "typescript": "catalog:" 33 | }, 34 | "dependencies": { 35 | "@iceworks/generate-material": "^1.1.0", 36 | "cac": "^6.7.12", 37 | "consola": "^2.15.3", 38 | "fs-extra": "^10.0.0", 39 | "globby": "^14.0.1", 40 | "ice-npm-utils": "^3.0.2", 41 | "inquirer": "^8.2.1", 42 | "os-locale": "^6.0.2", 43 | "validate-npm-package-name": "^3.0.0" 44 | }, 45 | "publishConfig": { 46 | "access": "public", 47 | "registry": "https://registry.npmjs.org/" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/create-pkg/src/checkEmpty.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | 3 | export async function checkEmpty(dir: string): Promise { 4 | let files: string[] = fs.readdirSync(dir); 5 | files = files.filter((filename) => { 6 | return ['node_modules', '.git', '.DS_Store', '.iceworks-tmp', 'build', '.bzbconfig'].indexOf(filename) === -1; 7 | }); 8 | if (files.length && files.length > 0) { 9 | return false; 10 | } else { 11 | return true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/create-pkg/src/inquirePackageName.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import { checkAliInternal } from 'ice-npm-utils'; 3 | import validateName from 'validate-npm-package-name'; 4 | import getInfo from './langs/index.js'; 5 | 6 | export async function inquirePackageName() { 7 | const info = await getInfo(); 8 | const isInternal = await checkAliInternal(); 9 | 10 | const { npmName } = await inquirer.prompt([ 11 | { 12 | type: 'input', 13 | name: 'npmName', 14 | message: info.packageName, 15 | default: `${isInternal ? '@ali/' : ''}example-component`, 16 | validate: (value) => { 17 | if (!validateName(value).validForNewPackages) { 18 | return info.packageNameValidateError(value); 19 | } 20 | return true; 21 | }, 22 | }, 23 | ]); 24 | 25 | return npmName; 26 | } 27 | -------------------------------------------------------------------------------- /packages/create-pkg/src/inquireTemplateNpmName.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import getInfo from './langs/index.js'; 3 | 4 | 5 | export default async function inquireTemplateNpmName(workspace?: boolean) { 6 | const info = await getInfo(); 7 | const baseTemplateChoices = [ 8 | { 9 | name: info.reactComponent, 10 | value: '@ice/template-pkg-react', 11 | }, 12 | { 13 | name: info.nodeModule, 14 | value: '@ice/template-pkg-node', 15 | }, 16 | { 17 | name: info.webLibrary, 18 | value: '@ice/template-pkg-web', 19 | }, 20 | { 21 | name: info.raxComponent, 22 | value: '@ice/template-pkg-rax', 23 | }, 24 | ]; 25 | const monorepoTemplateChoices = [ 26 | { 27 | name: info.reactMonorepo, 28 | value: '@ice/template-pkg-monorepo-react', 29 | }, 30 | { 31 | name: info.nodeMonorepo, 32 | value: '@ice/template-pkg-monorepo-node', 33 | }, 34 | ]; 35 | // If create a sub package(the cli flag is `-w`), don't show the monorepo templates. 36 | const choices = baseTemplateChoices.concat(!workspace ? monorepoTemplateChoices : []); 37 | const { templateNpmName } = await inquirer.prompt([ 38 | { 39 | type: 'list', 40 | message: info.selectProjectType, 41 | name: 'templateNpmName', 42 | default: '@ice/template-pkg-react', 43 | choices, 44 | }, 45 | ]); 46 | return templateNpmName; 47 | } 48 | -------------------------------------------------------------------------------- /packages/create-pkg/src/langs/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | targetDir: 'Target dirname to generate', 3 | version: '@ice/create-pkg version: ', 4 | dirExistFiles: 'Files exist in the current directory already. Are you sure to continue ?', 5 | initSuccess: 'Initialize project successfully.', 6 | selectNpmScope: 'Please select npm scope', 7 | packageName: 'Package name', 8 | packageNameValidateError: (name: string) => `npm package name ${name} not validate, please retry`, 9 | selectProjectType: 'Please select project type', 10 | webLibrary: 'Web Library', 11 | nodeModule: 'Node Module', 12 | reactComponent: 'React Component', 13 | raxComponent: 'Rax Component', 14 | reactMonorepo: 'Monorepo React Components', 15 | nodeMonorepo: 'Monorepo Node Libs', 16 | }; 17 | -------------------------------------------------------------------------------- /packages/create-pkg/src/langs/index.ts: -------------------------------------------------------------------------------- 1 | import { osLocale } from 'os-locale'; 2 | import zhCN from './zh-CN.js'; 3 | import enUS from './en-US.js'; 4 | 5 | 6 | let lang: string; 7 | 8 | async function getLocale() { 9 | if (!lang) { 10 | lang = await osLocale(); 11 | return lang; 12 | } 13 | return lang; 14 | } 15 | 16 | 17 | export default async function getInfo() { 18 | const currentLang = await getLocale(); 19 | if (currentLang.startsWith('zh')) { 20 | return zhCN; 21 | } else { 22 | return enUS; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/create-pkg/src/langs/zh-CN.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | targetDir: '指定目录名以初始化项目', 3 | version: '@ice/create-pkg 版本: ', 4 | dirExistFiles: '当前目录已存在文件,是否继续?', 5 | initSuccess: '项目初始化成功', 6 | selectNpmScope: '请选择 npm scope', 7 | packageName: '包名', 8 | packageNameValidateError: (name: string) => `npm 包名 ${name} 校验不通过,请重试`, 9 | selectProjectType: '请选择项目类型', 10 | webLibrary: '前端类库', 11 | nodeModule: 'Node 模块', 12 | reactComponent: 'React 组件', 13 | raxComponent: 'Rax 组件', 14 | reactMonorepo: 'Monorepo React 组件', 15 | nodeMonorepo: 'Monorepo Node 模块', 16 | }; 17 | -------------------------------------------------------------------------------- /packages/create-pkg/src/removeFilesAndContent.ts: -------------------------------------------------------------------------------- 1 | import fse from 'fs-extra'; 2 | import * as path from 'path'; 3 | 4 | /** 5 | * If we create a sub package in monorepo workspace, we need to delete some files and content which don't need. 6 | */ 7 | export default async function removeFilesAndContent(dir: string) { 8 | await removeFiles(dir); 9 | await deleteFieldsInPkgJSON(dir); 10 | } 11 | 12 | const uselessFiles: string[] = [ 13 | '.gitignore', 14 | 15 | '.eslintrc.cjs', 16 | '.eslintrc.js', 17 | '.eslintrc', 18 | '.eslintignore', 19 | 20 | '.stylelintrc.js', 21 | '.stylelintrc.cjs', 22 | '.stylelintrc', 23 | '.stylelintignore', 24 | 25 | 'abc.json', 26 | ]; 27 | 28 | async function removeFiles(dir: string) { 29 | for (const file of uselessFiles) { 30 | const filePath = path.join(dir, file); 31 | if (await fse.pathExists(filePath)) { 32 | await fse.remove(filePath); 33 | } 34 | } 35 | } 36 | 37 | const uselessFields: Record = { 38 | scripts: ['eslint', 'eslint:fix', 'stylelint', 'stylelint:fix', 'lint'], 39 | devDependencies: ['stylelint', 'eslint', '@applint/spec'], 40 | }; 41 | 42 | async function deleteFieldsInPkgJSON(dir: string) { 43 | const pkgJSONPath = path.join(dir, 'package.json'); 44 | const pkgJSON = await fse.readJSON(pkgJSONPath); 45 | Object 46 | .entries(uselessFields) 47 | .forEach(([field, properties]) => { 48 | properties.forEach((property) => { 49 | delete pkgJSON[field][property]; 50 | }); 51 | }); 52 | await fse.writeJSON(pkgJSONPath, pkgJSON, { spaces: 2 }); 53 | } 54 | -------------------------------------------------------------------------------- /packages/create-pkg/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "src", 6 | "outDir": "lib" 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/ice-npm-utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.0.3 4 | 5 | - [fix] Using `fs.createWriteStream` will change all file permissions to `0o666`. However, there are cases where files need to be executable, such as files within the `.husky` directory. Therefore, `fs.createWriteStream` should maintain the original file permissions instead of overwriting them. 6 | 7 | ## 3.0.2 8 | 9 | - [fix] compareFunction of Array.sort should not returns boolean value. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description 10 | 11 | ## 3.0.1 12 | 13 | - [feat] optimize checkAliInternal 14 | 15 | ## 3.0.0 16 | 17 | - [chore] change cnpm registry to `registry.npmmirror.com` 18 | - [chore] migrate request to axios, notice `error.statusCode` -> `error.response && error.response.status === 404` 19 | - [chore] upgrade deps: 20 | - fs-extra 8 -> 10, Only support Node.js 12+ 21 | - mkdirp 0.5.x -> 1.x 22 | - semver 6.x -> 7.x 23 | - tar 0.4.x -> 0.6.x 24 | 25 | ## 2.1.2 26 | 27 | - [chore] remove `@iceworks/constant`, use `@appworks/constant` 28 | 29 | ## 2.1.1 30 | 31 | - [chore] better error message 32 | 33 | ## 2.1.0 34 | 35 | - [feat] add `getVersions` and `getSatisfiesVersions` method 36 | 37 | ## 1.4.1 38 | 39 | - [chore] remove cacheData for server use 40 | 41 | ## 1.3.1 42 | 43 | - [feat] isAliNpm add `@kaola` 44 | 45 | ## 1.3.0 46 | 47 | - [feat] getNpmRegistry remove npmconf and default `https://registry.npm.taobao.org` 48 | 49 | ## 1.2.0 50 | 51 | - [feat] getNpmRegistry 优先读取 npm config,否则返回 `https://registry.npm.com` 52 | - [fix] extract tarball consider directory 53 | 54 | ## 1.1.2 55 | 56 | - [fix] 修复getAndExtractTarball写空文件时卡死的问题 57 | 58 | ## 1.1.1 59 | 60 | - [fix] 用 request-promise 替换 axios 之后的参数变化 61 | 62 | ## 1.1.0 63 | 64 | - [feat] 新增 getAndExtractTarball&getNpmTarball 两个 API 65 | - [chore] 请求库从 axios 统一为 request 66 | 67 | ## 1.0.3 68 | 69 | - [chore] 增加 log 70 | - [fix] checkInternal timeout 延长到 3s 71 | 72 | ## 1.0.2 73 | 74 | - init 75 | -------------------------------------------------------------------------------- /packages/ice-npm-utils/README.md: -------------------------------------------------------------------------------- 1 | # ice-npm-utils 2 | 3 | some utils for ice. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | npm install ice-npm-utils --save-dev 9 | ``` 10 | 11 | ## Basic Usage 12 | 13 | ```js 14 | const { getNpmLatestSemverVersion } = require('ice-npm-utils'); 15 | ``` 16 | 17 | ## API 18 | 19 | ### getNpmRegistry(npmName) 20 | 21 | Default return `https://registry.npm.taobao.org` 22 | 23 | ### getUnpkgHost(npmName) 24 | 25 | Default return `https://unpkg.com` 26 | 27 | ### getNpmLatestSemverVersion(npmName, baseVersion) 28 | 29 | Return `Promise.resolve(version)` 30 | 31 | ### getLatestVersion(npmName) 32 | 33 | Return `Promise.resolve(version)` 34 | 35 | ### getVersions(npmName) 36 | 37 | Return `Promise.resolve(versions)` 38 | 39 | ### getSatisfiesVersions(npmName, semverRange) 40 | 41 | Return `Promise.resolve(versions)` 42 | 43 | ### getNpmInfo(npmName) 44 | 45 | Return `Promise.resolve(response.data)` 46 | 47 | ### getNpmClient(npmName) 48 | 49 | Default return `npm` 50 | 51 | ### checkAliInternal() 52 | 53 | Return `Promise.resolve(isInternal)` 54 | 55 | ### getNpmTarball(name, version) 56 | 57 | Return `Promise.resolve(tarball)` 58 | 59 | ### getAndExtractTarball(destDir, tarball, progressFunc: () => {}) 60 | 61 | Return `Promise.resolve(allFiles: string[])` 62 | 63 | ## Custom 64 | 65 | ### Custom Npm Registry 66 | 67 | ``` 68 | process.env.REGISTRY=https://registry.npmjs.org 69 | ``` 70 | 71 | ### Custom Unpkg Host 72 | 73 | ``` 74 | process.env.UNPKG=https://unpkg.com 75 | ``` 76 | 77 | ### Custom Npm Client 78 | 79 | ``` 80 | process.env.NPM_CLIENT=cnpm 81 | ``` -------------------------------------------------------------------------------- /packages/ice-npm-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ice-npm-utils", 3 | "version": "3.0.3", 4 | "description": "npm utils for ice", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib", 8 | "!lib/**/*.map" 9 | ], 10 | "engines": { 11 | "node": ">=12" 12 | }, 13 | "scripts": { 14 | "watch": "tsc -w", 15 | "build": "rm -rf lib && tsc", 16 | "prepublishOnly": "npm run build" 17 | }, 18 | "dependencies": { 19 | "@appworks/constant": "^0.1.2", 20 | "axios": "^0.23.0", 21 | "fs-extra": "^10.0.0", 22 | "mkdirp": "^1.0.0", 23 | "semver": "^7.0.0", 24 | "tar": "^6.0.0", 25 | "url-join": "^4.0.1" 26 | }, 27 | "publishConfig": { 28 | "access": "public" 29 | }, 30 | "license": "MIT", 31 | "devDependencies": { 32 | "typescript": "catalog:", 33 | "vitest": "catalog:" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/ice-npm-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "src", 6 | "outDir": "lib", 7 | "target": "ES2019", 8 | "module": "commonjs", 9 | "lib": ["ES2019"] 10 | }, 11 | "exclude": ["__tests__", "lib"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/pkg/README.md: -------------------------------------------------------------------------------- 1 | # @ice/pkg 2 | 3 | A fast builder for React components, Node modules and web libraries. 4 | 5 |
8 | benchmark 9 | 10 |
Above: this benchmark approximates a large TypeScript codebase by using ICE fusion pro template.
11 |
12 | 13 | ## Features 14 | 15 | - **Fast**:Code compiled and minified by [swc](https://swc.rs/docs/configuration/swcrc). 16 | - **Dual Mode**:Bundle mode to bundle everything up and transform mode to compile files one by one. 17 | - **Zero Configuration**:Zero Configuration with Typescript and JSX support. 18 | - **Modern Mode**:Outputs es2017 JavaScript specially designed to work in all modern browsers. 19 | - **Doc Preview**:Enhanced doc preview, powered by [Docusaurus](https://docusaurus.io/). 20 | 21 | 22 | ## Quick Start 23 | 24 | ```bash 25 | npm init @ice/pkg react-component 26 | 27 | # Or pnpm 28 | # pnpm create @ice/pkg react-component 29 | 30 | cd react-component 31 | npm run start 32 | ``` 33 | 34 | That's it. Start editing `src/index.tsx` and go! 35 | 36 | ## Documentation 37 | 38 | For complete usages, please dive into the [docs](https://pkg.ice.work/). 39 | 40 | ## Contributing 41 | 42 | Please see our [CONTRIBUTING.md](/.github/CONTRIBUTING.md) 43 | 44 | ## License 45 | 46 | [MIT](https://oss.ninja/mit/developit/) 47 | -------------------------------------------------------------------------------- /packages/pkg/bin/cli.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { fileURLToPath } from 'url'; 4 | import consola from 'consola'; 5 | import { cac } from 'cac'; 6 | import { readFileSync } from 'fs'; 7 | import { join, dirname } from 'path'; 8 | import pkgService, { getBuiltInPlugins } from '../lib/index.js'; 9 | 10 | const __dirname = dirname(fileURLToPath(import.meta.url)); 11 | 12 | const cli = cac('ice-pkg'); 13 | 14 | (async () => { 15 | cli 16 | .command('build', 'Bundle files', { 17 | allowUnknownOptions: false, 18 | }) 19 | .option('--config ', 'specify custom config path') 20 | .option('--analyzer', "visualize size of output files(it's only valid in bundle mode)", { 21 | default: false, 22 | }) 23 | .option('--rootDir ', 'specify root directory', { 24 | default: process.cwd(), 25 | }) 26 | .action(async (options) => { 27 | delete options['--']; 28 | const { rootDir, ...commandArgs } = options; 29 | 30 | await pkgService.run({ 31 | command: 'build', 32 | commandArgs, 33 | getBuiltInPlugins, 34 | rootDir: options.rootDir, 35 | }); 36 | }); 37 | 38 | cli 39 | .command('start', 'Watch files', { 40 | allowUnknownOptions: false, 41 | }) 42 | .option('--config ', 'specify custom config path') 43 | .option('--analyzer', "visualize size of output files(it's only valid in bundle mode)", { 44 | default: false, 45 | }) 46 | .option('--rootDir ', 'specify root directory', { 47 | default: process.cwd(), 48 | }) 49 | .action(async (options) => { 50 | delete options['--']; 51 | const { rootDir, ...commandArgs } = options; 52 | 53 | await pkgService.run({ 54 | command: 'start', 55 | commandArgs, 56 | getBuiltInPlugins, 57 | rootDir: options.rootDir, 58 | }); 59 | }); 60 | 61 | cli.help(); 62 | 63 | const pkgPath = join(__dirname, '../package.json'); 64 | cli.version(JSON.parse(readFileSync(pkgPath, 'utf-8')).version); 65 | 66 | cli.parse(process.argv, { run: true }); 67 | })() 68 | .catch((err) => { 69 | consola.error(err); 70 | process.exit(1); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ice/pkg", 3 | "version": "1.6.2", 4 | "description": "A fast builder for React components, Node modules and web libraries.", 5 | "type": "module", 6 | "main": "./lib/index.js", 7 | "exports": "./lib/index.js", 8 | "bin": { 9 | "ice-pkg": "./bin/cli.mjs" 10 | }, 11 | "files": [ 12 | "lib", 13 | "!lib/**/*.map", 14 | "types.d.ts" 15 | ], 16 | "scripts": { 17 | "build": "rm -rf lib && tsc", 18 | "watch": "tsc -w" 19 | }, 20 | "license": "MIT", 21 | "engines": { 22 | "node": ">=16.14.0" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/ice-lab/icepkg.git", 27 | "directory": "packages/pkg" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/ice-lab/icepkg/issues" 31 | }, 32 | "homepage": "https://pkg.ice.work", 33 | "dependencies": { 34 | "@ampproject/remapping": "^2.0.3", 35 | "@babel/core": "^7.17.5", 36 | "@babel/parser": "^7.17.3", 37 | "@babel/preset-react": "^7.18.6", 38 | "@babel/preset-typescript": "^7.18.6", 39 | "@rollup/plugin-alias": "^5.0.1", 40 | "@rollup/plugin-commonjs": "^25.0.0", 41 | "@rollup/plugin-image": "^3.0.1", 42 | "@rollup/plugin-json": "^4.1.0", 43 | "@rollup/plugin-node-resolve": "^15.0.2", 44 | "@rollup/plugin-replace": "^5.0.1", 45 | "@rollup/pluginutils": "^4.1.2", 46 | "@swc/core": "1.3.80", 47 | "acorn": "^8.7.0", 48 | "autoprefixer": "^10.4.2", 49 | "build-scripts": "^2.0.0", 50 | "cac": "^6.7.12", 51 | "chokidar": "^3.5.3", 52 | "consola": "^2.15.3", 53 | "debug": "^4.3.3", 54 | "deepmerge": "^4.2.2", 55 | "es-module-lexer": "^1.3.1", 56 | "escape-string-regexp": "^5.0.0", 57 | "fs-extra": "^10.0.0", 58 | "globby": "^11.0.4", 59 | "gzip-size": "^7.0.0", 60 | "lodash.merge": "^4.6.2", 61 | "magic-string": "^0.25.7", 62 | "picocolors": "^1.0.0", 63 | "postcss": "^8.4.6", 64 | "postcss-plugin-rpx2vw": "^1.0.0", 65 | "rollup": "^2.79.1", 66 | "rollup-plugin-styles": "^4.0.0", 67 | "rollup-plugin-visualizer": "^5.8.3", 68 | "semver": "^7.0.0", 69 | "tsc-alias": "1.8.13", 70 | "typescript": "^4.9.4" 71 | }, 72 | "devDependencies": { 73 | "@types/babel__core": "^7.1.20", 74 | "@types/fs-extra": "^9.0.13", 75 | "@types/lodash.merge": "^4.6.7", 76 | "@types/node": "^17.0.2", 77 | "cssnano": "^5.1.15", 78 | "jest": "^29.4.3", 79 | "sass": "catalog:", 80 | "source-map": "0.6.1", 81 | "vitest": "catalog:" 82 | }, 83 | "publishConfig": { 84 | "access": "public", 85 | "registry": "https://registry.npmjs.org/" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/pkg/src/commands/build.ts: -------------------------------------------------------------------------------- 1 | import fse from 'fs-extra'; 2 | import { RollupOptions } from 'rollup'; 3 | import { getBuildTasks } from '../helpers/getBuildTasks.js'; 4 | import { getRollupOptions } from '../helpers/getRollupOptions.js'; 5 | import { buildBundleTasks } from '../tasks/bundle.js'; 6 | import { buildTransformTasks } from '../tasks/transform.js'; 7 | 8 | import type { Context, OutputResult, TaskRunnerContext } from '../types.js'; 9 | 10 | export default async function build(context: Context) { 11 | const { applyHook, commandArgs } = context; 12 | 13 | const buildTasks = getBuildTasks(context); 14 | const taskConfigs = buildTasks.map(({ config }) => config); 15 | 16 | await applyHook('before.build.load', { 17 | args: commandArgs, 18 | config: taskConfigs, 19 | }); 20 | 21 | if (!taskConfigs.length) { 22 | throw new Error('Could not Find any pending tasks when executing \'build\' command.'); 23 | } 24 | 25 | await applyHook('before.build.run', { 26 | args: commandArgs, 27 | config: taskConfigs, 28 | }); 29 | 30 | // Empty outputDir before run the task. 31 | const outputDirs = taskConfigs.map((config) => config.outputDir).filter(Boolean); 32 | outputDirs.forEach((outputDir) => fse.emptyDirSync(outputDir)); 33 | 34 | const transformOptions = buildTasks 35 | .filter(({ config }) => config.type === 'transform') 36 | .map((buildTask) => { 37 | const { config: { modes } } = buildTask; 38 | return modes.map((mode) => { 39 | const taskRunnerContext: TaskRunnerContext = { mode, buildTask }; 40 | const rollupOptions = getRollupOptions(context, taskRunnerContext); 41 | return [rollupOptions, taskRunnerContext] as [RollupOptions, TaskRunnerContext]; 42 | }); 43 | }) 44 | .flat(1); 45 | 46 | const bundleOptions = buildTasks 47 | .filter(({ config }) => config.type === 'bundle') 48 | .map((buildTask) => { 49 | const { config: { modes } } = buildTask; 50 | return modes.map((mode) => { 51 | const taskRunnerContext: TaskRunnerContext = { mode, buildTask }; 52 | const rollupOptions = getRollupOptions(context, taskRunnerContext); 53 | return [rollupOptions, taskRunnerContext] as [RollupOptions, TaskRunnerContext]; 54 | }); 55 | }) 56 | .flat(1); 57 | 58 | try { 59 | const outputResults: OutputResult[] = []; 60 | const { outputResults: transformOutputResults } = await buildTransformTasks( 61 | transformOptions, 62 | context, 63 | ); 64 | const { outputResults: bundleOutputResults } = await buildBundleTasks( 65 | bundleOptions, 66 | context, 67 | ); 68 | 69 | outputResults.push( 70 | ...bundleOutputResults, 71 | ...transformOutputResults, 72 | ); 73 | 74 | await applyHook('after.build.compile', outputResults); 75 | } catch (err) { 76 | await applyHook('error', { 77 | errCode: 'COMPILE_ERROR', 78 | err, 79 | }); 80 | 81 | throw err; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/pkg/src/commands/start.ts: -------------------------------------------------------------------------------- 1 | import consola from 'consola'; 2 | import { RollupOptions } from 'rollup'; 3 | import { getBuildTasks } from '../helpers/getBuildTasks.js'; 4 | import { getRollupOptions } from '../helpers/getRollupOptions.js'; 5 | import { createBatchChangeHandler, createWatcher } from '../helpers/watcher.js'; 6 | import { watchBundleTasks } from '../tasks/bundle.js'; 7 | import { watchTransformTasks } from '../tasks/transform.js'; 8 | 9 | import type { 10 | OutputResult, 11 | Context, 12 | TaskRunnerContext, WatchChangedFile, 13 | } from '../types.js'; 14 | 15 | export default async function start(context: Context) { 16 | const { applyHook, commandArgs } = context; 17 | 18 | const buildTasks = getBuildTasks(context); 19 | const taskConfigs = buildTasks.map(({ config }) => config); 20 | 21 | await applyHook('before.start.load', { 22 | args: commandArgs, 23 | config: taskConfigs, 24 | }); 25 | 26 | if (!taskConfigs.length) { 27 | throw new Error('Could not Find any pending tasks when excuting \'start\' command.'); 28 | } 29 | 30 | await applyHook('before.start.run', { 31 | args: commandArgs, 32 | config: taskConfigs, 33 | }); 34 | 35 | const watcher = createWatcher(taskConfigs); 36 | const batchHandler = createBatchChangeHandler(runChangedCompile); 37 | batchHandler.beginBlock(); 38 | 39 | watcher.on('add', (id) => batchHandler.onChange(id, 'create')); 40 | watcher.on('change', (id) => batchHandler.onChange(id, 'update')); 41 | watcher.on('unlink', (id) => batchHandler.onChange(id, 'delete')); 42 | watcher.on('error', (error) => consola.error(error)); 43 | 44 | const transformOptions = buildTasks 45 | .filter(({ config }) => config.type === 'transform') 46 | .map((buildTask) => { 47 | const { config: { modes } } = buildTask; 48 | return modes.map((mode) => { 49 | const taskRunnerContext: TaskRunnerContext = { mode, buildTask }; 50 | const rollupOptions = getRollupOptions(context, taskRunnerContext); 51 | return [rollupOptions, taskRunnerContext] as [RollupOptions, TaskRunnerContext]; 52 | }); 53 | }) 54 | .flat(1); 55 | 56 | const bundleOptions = buildTasks 57 | .filter(({ config }) => config.type === 'bundle') 58 | .map((buildTask) => { 59 | const { config: { modes } } = buildTask; 60 | return modes.map((mode) => { 61 | const taskRunnerContext: TaskRunnerContext = { mode, buildTask }; 62 | const rollupOptions = getRollupOptions(context, taskRunnerContext); 63 | return [rollupOptions, taskRunnerContext] as [RollupOptions, TaskRunnerContext]; 64 | }); 65 | }) 66 | .flat(1); 67 | 68 | const outputResults: OutputResult[] = []; 69 | 70 | const transformWatchResult = await watchTransformTasks( 71 | transformOptions, 72 | context, 73 | watcher, 74 | ); 75 | const bundleWatchResult = await watchBundleTasks( 76 | bundleOptions, 77 | context, 78 | watcher, 79 | ); 80 | 81 | outputResults.push( 82 | ...(transformWatchResult.outputResults), 83 | ...(bundleWatchResult.outputResults), 84 | ); 85 | 86 | await applyHook('after.start.compile', outputResults); 87 | 88 | batchHandler.endBlock(); 89 | 90 | async function runChangedCompile(changedFiles: WatchChangedFile[]) { 91 | const newOutputResults = []; 92 | try { 93 | const newTransformOutputResults = transformWatchResult.handleChange ? 94 | await transformWatchResult.handleChange(changedFiles) : 95 | []; 96 | const newBundleOutputResults = bundleWatchResult.handleChange ? 97 | await bundleWatchResult.handleChange(changedFiles) : 98 | []; 99 | newOutputResults.push( 100 | ...newTransformOutputResults, 101 | ...newBundleOutputResults, 102 | ); 103 | 104 | await applyHook('after.start.compile', newOutputResults); 105 | } catch (error) { 106 | consola.error(error); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /packages/pkg/src/commands/test.ts: -------------------------------------------------------------------------------- 1 | import { getBuildTasks } from '../helpers/getBuildTasks.js'; 2 | 3 | import type { Context } from '../types.js'; 4 | 5 | export default async function test(context: Context) { 6 | const buildTasks = getBuildTasks(context); 7 | const taskConfigs = buildTasks.map(({ config }) => config); 8 | 9 | return { 10 | taskConfigs, 11 | context, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /packages/pkg/src/config/cliOptions.ts: -------------------------------------------------------------------------------- 1 | import { mergeValueToTaskConfig } from '../utils.js'; 2 | import { TaskConfig } from '../types'; 3 | 4 | function getCliOptions() { 5 | const cliOptions = [ 6 | { 7 | name: 'analyzer', 8 | commands: ['start', 'build'], 9 | setConfig: (config: TaskConfig, analyzer: boolean) => { 10 | return mergeValueToTaskConfig(config, 'analyzer', analyzer); 11 | }, 12 | }, 13 | ]; 14 | return cliOptions; 15 | } 16 | 17 | export default getCliOptions; 18 | -------------------------------------------------------------------------------- /packages/pkg/src/config/index.ts: -------------------------------------------------------------------------------- 1 | export { default as getUserConfig } from './userConfig.js'; 2 | export { default as getCliOptions } from './cliOptions.js'; 3 | -------------------------------------------------------------------------------- /packages/pkg/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const JSX_RUNTIME_SOURCE = '@ice/jsx-runtime'; 2 | -------------------------------------------------------------------------------- /packages/pkg/src/defineConfig.ts: -------------------------------------------------------------------------------- 1 | import { UserConfig } from './types'; 2 | 3 | /** 4 | * Provide intellisense of user config. 5 | */ 6 | export const defineConfig = (options: UserConfig | (() => UserConfig)): UserConfig => { 7 | if (typeof options === 'function') { 8 | return options(); 9 | } else { 10 | return options; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/__tests__/getTaskIO.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { formatEntry } from '../getTaskIO.js'; 3 | 4 | test('formatEntry', () => { 5 | expect(formatEntry('src/index')).toEqual({ 6 | index: 'src/index', 7 | }); 8 | 9 | expect(formatEntry(['src/index', 'src/client'])).toEqual({ 10 | index: 'src/index', 11 | client: 'src/client', 12 | }); 13 | 14 | expect(formatEntry({ 15 | index: 'src/index', 16 | client: 'src/client', 17 | })).toEqual({ 18 | index: 'src/index', 19 | client: 'src/client', 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/builtinModules.ts: -------------------------------------------------------------------------------- 1 | // Copy from https://github.com/sindresorhus/builtin-modules#readme 2 | export const builtinNodeModules = [ 3 | 'assert', 4 | 'async_hooks', 5 | 'buffer', 6 | 'child_process', 7 | 'cluster', 8 | 'console', 9 | 'constants', 10 | 'crypto', 11 | 'dgram', 12 | 'dns', 13 | 'domain', 14 | 'events', 15 | 'fs', 16 | 'http', 17 | 'http2', 18 | 'https', 19 | 'inspector', 20 | 'module', 21 | 'net', 22 | 'os', 23 | 'path', 24 | 'perf_hooks', 25 | 'process', 26 | 'punycode', 27 | 'querystring', 28 | 'readline', 29 | 'repl', 30 | 'stream', 31 | 'string_decoder', 32 | 'timers', 33 | 'tls', 34 | 'trace_events', 35 | 'tty', 36 | 'url', 37 | 'util', 38 | 'v8', 39 | 'vm', 40 | 'wasi', 41 | 'worker_threads', 42 | 'zlib', 43 | ]; 44 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/defaultSwcConfig.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BundleTaskConfig, 3 | Context, 4 | TaskName, 5 | TaskValue, 6 | TransformTaskConfig, 7 | NodeEnvMode, 8 | } from '../types.js'; 9 | import type { Config, ModuleConfig } from '@swc/core'; 10 | import getDefaultDefineValues from './getDefaultDefineValues.js'; 11 | 12 | // https://github.com/ice-lab/ice-next/issues/54#issuecomment-1083263523 13 | const LEGACY_BROWSER_TARGETS = { 14 | chrome: 49, 15 | ie: 11, 16 | }; 17 | const MODERN_BROWSER_TARGETS = { 18 | chrome: 61, 19 | safari: 11, 20 | firefox: 60, 21 | edge: 16, 22 | ios: 11, 23 | }; 24 | 25 | export const getDefaultBundleSwcConfig = ( 26 | bundleTaskConfig: BundleTaskConfig, 27 | ctx: Context, 28 | taskName: TaskValue, 29 | ): Config => { 30 | const browserTargets = taskName === TaskName.BUNDLE_ES2017 ? MODERN_BROWSER_TARGETS : LEGACY_BROWSER_TARGETS; 31 | return { 32 | jsc: { 33 | externalHelpers: true, 34 | }, 35 | minify: false, 36 | // Always generate map in bundle mode, 37 | // and leave minify-plugin to tackle with it. 38 | sourceMaps: true, 39 | // 由 env 字段统一处理 syntax & polyfills 40 | env: { 41 | targets: browserTargets, 42 | coreJs: '3.29', 43 | mode: bundleTaskConfig.polyfill === false ? undefined : bundleTaskConfig.polyfill, 44 | }, 45 | }; 46 | }; 47 | 48 | export const getDefaultTransformSwcConfig = ( 49 | transformTaskConfig: TransformTaskConfig, 50 | ctx: Context, 51 | taskName: TaskValue, 52 | mode: NodeEnvMode, 53 | ): Config => { 54 | const module: ModuleConfig = taskName === TaskName.TRANSFORM_CJS 55 | ? { type: 'commonjs' } 56 | : undefined; 57 | 58 | const target = taskName === TaskName.TRANSFORM_ES2017 ? 'es2017' : 'es5'; 59 | 60 | return { 61 | jsc: { 62 | target, 63 | transform: { 64 | optimizer: { 65 | globals: { 66 | vars: { 67 | ...getDefaultDefineValues(mode), 68 | ...transformTaskConfig.define, 69 | }, 70 | }, 71 | }, 72 | }, 73 | // Helpers function will not be inlined into the output files for sake of optimizing. 74 | // Get more info https://github.com/ice-lab/ice-next/issues/95 75 | externalHelpers: true, 76 | }, 77 | minify: false, 78 | module, 79 | sourceMaps: transformTaskConfig.sourcemap, 80 | }; 81 | }; 82 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/formatAliasToTSPathsConfig.ts: -------------------------------------------------------------------------------- 1 | import { TaskConfig } from '../types.js'; 2 | 3 | export default function formatAliasToTSPathsConfig(alias: TaskConfig['alias']) { 4 | const paths: { [from: string]: [string] } = {}; 5 | 6 | Object.entries(alias || {}).forEach(([key, value]) => { 7 | const [pathKey, pathValue] = formatPath(key, value); 8 | paths[pathKey] = [pathValue]; 9 | }); 10 | 11 | return paths; 12 | } 13 | 14 | function formatPath(key: string, value: string) { 15 | if (key.endsWith('$')) { 16 | return [key.replace(/\$$/, ''), value]; 17 | } 18 | // abc -> abc/* 19 | // abc/ -> abc/* 20 | return [addWildcard(key), addWildcard(value)]; 21 | } 22 | 23 | function addWildcard(str: string) { 24 | return `${str.endsWith('/') ? str : `${str}/`}*`; 25 | } 26 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/getBabelOptions.ts: -------------------------------------------------------------------------------- 1 | import { TransformOptions } from '@babel/core'; 2 | import { BabelPluginOptions } from 'src/rollupPlugins/babel.js'; 3 | import { JSX_RUNTIME_SOURCE } from '../constants.js'; 4 | 5 | function getBabelOptions( 6 | plugins: babel.PluginItem[], 7 | options: BabelPluginOptions, 8 | modifyBabelOptions?: (babelCompileOptions: TransformOptions) => TransformOptions, 9 | ) { 10 | const { pragma = 'React.createElement', pragmaFrag = 'React.Fragment', jsxRuntime = 'automatic' } = options; 11 | const baseBabelOptions: TransformOptions = { 12 | babelrc: false, 13 | configFile: false, 14 | generatorOpts: { 15 | decoratorsBeforeExport: true, 16 | }, 17 | plugins, 18 | presets: [ 19 | [ 20 | '@babel/preset-typescript', 21 | { 22 | jsxPragma: pragma, 23 | jsxPragmaFrag: pragmaFrag, 24 | }, 25 | ], 26 | [ 27 | '@babel/preset-react', 28 | jsxRuntime === 'automatic' 29 | ? { 30 | runtime: jsxRuntime, 31 | importSource: JSX_RUNTIME_SOURCE, 32 | } 33 | : { 34 | pragma, 35 | pragmaFrag, 36 | throwIfNamespace: false, 37 | }, 38 | ], 39 | ], 40 | }; 41 | return typeof modifyBabelOptions === 'function' 42 | ? modifyBabelOptions(baseBabelOptions) 43 | : baseBabelOptions; 44 | } 45 | export default getBabelOptions; 46 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/getBuildTasks.ts: -------------------------------------------------------------------------------- 1 | import deepmerge from 'deepmerge'; 2 | import { formatEntry, getTransformDefaultOutputDir } from './getTaskIO.js'; 3 | import { getDefaultBundleSwcConfig, getDefaultTransformSwcConfig } from './defaultSwcConfig.js'; 4 | import { stringifyObject } from '../utils.js'; 5 | import getDefaultDefineValues from './getDefaultDefineValues.js'; 6 | 7 | import type { Context, BuildTask } from '../types.js'; 8 | 9 | export function getBuildTasks(context: Context): BuildTask[] { 10 | const { getTaskConfig } = context; 11 | const buildTasks = getTaskConfig() as BuildTask[]; 12 | return buildTasks.map((buildTask) => getBuildTask(buildTask, context)); 13 | } 14 | 15 | function getBuildTask(buildTask: BuildTask, context: Context): BuildTask { 16 | const { rootDir, command } = context; 17 | const { config, name: taskName } = buildTask; 18 | 19 | config.entry = formatEntry(config.entry); 20 | // Configure define 21 | config.define = Object.assign( 22 | // Note: The define values in bundle mode will be defined (according to the `modes` value) 23 | // in generating rollup options. But when the command is test, we don't need to get the rollup options. 24 | // So in test, we assume the mode is 'development'. 25 | command === 'test' ? getDefaultDefineValues('development') : {}, 26 | stringifyObject(config.define || {}), 27 | ); 28 | 29 | config.sourcemap = config.sourcemap ?? command === 'start'; 30 | 31 | const mode = command === 'build' ? 'production' : 'development'; 32 | 33 | if (config.type === 'bundle') { 34 | const defaultBundleSwcConfig = getDefaultBundleSwcConfig(config, context, taskName); 35 | config.swcCompileOptions = typeof config.modifySwcCompileOptions === 'function' ? 36 | config.modifySwcCompileOptions(defaultBundleSwcConfig) : 37 | deepmerge( 38 | defaultBundleSwcConfig, 39 | config.swcCompileOptions || {}, 40 | ); 41 | config.modes = config.modes ?? [mode]; 42 | } else if (config.type === 'transform') { 43 | config.outputDir = getTransformDefaultOutputDir(rootDir, taskName); 44 | config.modes = [mode]; 45 | const defaultTransformSwcConfig = getDefaultTransformSwcConfig(config, context, taskName, mode); 46 | config.swcCompileOptions = typeof config.modifySwcCompileOptions === 'function' ? 47 | config.modifySwcCompileOptions(defaultTransformSwcConfig) : 48 | deepmerge( 49 | defaultTransformSwcConfig, 50 | config.swcCompileOptions || {}, 51 | ); 52 | } else { 53 | throw new Error('Invalid task type.'); 54 | } 55 | 56 | return buildTask; 57 | } 58 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/getDefaultDefineValues.ts: -------------------------------------------------------------------------------- 1 | import { NodeEnvMode } from '../types'; 2 | 3 | export default function getDefaultDefineValues(mode: NodeEnvMode) { 4 | return { 5 | __DEV__: JSON.stringify(mode === 'development'), 6 | 'process.env.NODE_ENV': JSON.stringify(mode), 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/getTaskIO.ts: -------------------------------------------------------------------------------- 1 | import { isAbsolute, resolve, join } from 'path'; 2 | import type { TaskValue } from '../types.js'; 3 | 4 | export function formatEntry(inputEntry: string | string[] | Record): Record { 5 | const entry = {}; 6 | if (typeof inputEntry === 'string') { 7 | entry[getEntryId(inputEntry)] = inputEntry; 8 | } else if (Array.isArray(inputEntry)) { 9 | inputEntry.forEach((item) => { 10 | entry[getEntryId(item)] = item; 11 | }); 12 | } else if (typeof inputEntry === 'object') { 13 | Object.keys(inputEntry).forEach((key) => { 14 | entry[key] = inputEntry[key]; 15 | }); 16 | } 17 | return entry; 18 | } 19 | 20 | // Eg. src/index.js => index 21 | function getEntryId(entry: string): string { 22 | return entry.split('/').pop().split('.').shift(); 23 | } 24 | 25 | export const getTransformEntryDirs = (rootDir: string, entry: Record) => { 26 | const entries = Object.values(entry); 27 | const transformEntryDirs: string[] = []; 28 | 29 | entries.forEach((entryItem) => { 30 | const absoluteEntry = isAbsolute(entryItem) ? entryItem : resolve(rootDir, entryItem); 31 | transformEntryDirs.push(join(absoluteEntry, '..')); 32 | }); 33 | 34 | return transformEntryDirs; 35 | }; 36 | 37 | export const getTransformDefaultOutputDir = (rootDir: string, taskName: TaskValue) => { 38 | return resolve(rootDir, taskName.split('-')[1]); 39 | }; 40 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/load.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as glob from 'globby'; 3 | import { join } from 'path'; 4 | import fs from 'fs-extra'; 5 | import { safeRequire, toArray } from '../utils.js'; 6 | 7 | /** 8 | * load package.json 9 | * @param cwd 10 | * @returns 11 | */ 12 | export function loadPkg(cwd: string) { 13 | return safeRequire(join(cwd, 'package.json')); 14 | } 15 | 16 | /** 17 | * load entry files 18 | * @param entry 19 | * @returns 20 | */ 21 | export function loadEntryFiles(entry: string, excludes: string | string[]) { 22 | return glob.sync('**/*.*', { 23 | cwd: entry, 24 | ignore: ['node_modules/**', ...toArray(excludes ?? [])], 25 | onlyFiles: true, 26 | }); 27 | } 28 | 29 | export const INCLUDES_UTF8_FILE_TYPE = /\.(js|mjs|mts|ts|jsx|tsx|cjs|cts|css|sass|less|json|html)$/; 30 | 31 | export function loadSource(path: string): string { 32 | try { 33 | return fs.readFileSync(path, 'utf-8'); 34 | } catch (err) { 35 | if (err.code !== 'ENOENT') { 36 | throw err; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/logger.ts: -------------------------------------------------------------------------------- 1 | import consola from 'consola'; 2 | import picocolors from 'picocolors'; 3 | 4 | // copy from consola 5 | enum LogLevel { 6 | Fatal= 0, 7 | Error= 0, 8 | Warn= 1, 9 | Log= 2, 10 | Info= 3, 11 | Success= 3, 12 | Debug= 4, 13 | Trace= 5, 14 | Silent= -Infinity, 15 | Verbose= Infinity, 16 | } 17 | 18 | const colorize = (type: LogLevel) => (msg: string) => { 19 | const color = 20 | type === LogLevel.Info 21 | ? 'green' 22 | : type === LogLevel.Error 23 | ? 'red' 24 | : type === LogLevel.Warn 25 | ? 'yellow' 26 | : 'white'; 27 | return picocolors[color](msg); 28 | }; 29 | 30 | function colorizeNamespace( 31 | name: string, 32 | type: LogLevel, 33 | ) { 34 | return `${picocolors.dim('[')}${colorize(type)(name.toUpperCase())}${picocolors.dim(']')} `; 35 | } 36 | 37 | /** 38 | * create logger 39 | * @param name 40 | * @returns 41 | */ 42 | export function createLogger(namespace?: string) { 43 | return { 44 | info(...args) { 45 | consola.info( 46 | colorizeNamespace(namespace, LogLevel.Info), 47 | ...args.map((item) => colorize(LogLevel.Info)(item)), 48 | ); 49 | }, 50 | 51 | error(...args) { 52 | consola.error( 53 | colorizeNamespace(namespace, LogLevel.Error), 54 | ...args.map((item) => colorize(LogLevel.Error)(item)), 55 | ); 56 | }, 57 | 58 | warn(...args) { 59 | consola.warn( 60 | colorizeNamespace(namespace, LogLevel.Warn), 61 | ...args.map((item) => colorize(LogLevel.Warn)(item)), 62 | ); 63 | }, 64 | 65 | debug(...args) { 66 | consola.debug( 67 | colorizeNamespace(namespace, LogLevel.Debug), 68 | ...args.map((item) => colorize(LogLevel.Debug)(item)), 69 | ); 70 | }, 71 | }; 72 | } 73 | 74 | export type CreateLoggerReturns = ReturnType; 75 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/reportSize.ts: -------------------------------------------------------------------------------- 1 | import consola from 'consola'; 2 | import picocolors from 'picocolors'; 3 | import { gzipSizeSync } from 'gzip-size'; 4 | 5 | const UNIT = ['B', 'KiB', 'MiB', 'GiB', 'TiB']; 6 | 7 | const prettifySize = (bytes: number): string => { 8 | if (bytes === 0) return '0 B'; 9 | 10 | const exp = Math.floor(Math.log2(bytes) / 10); 11 | return `${(`${(bytes / Math.pow(1024, exp)).toFixed(2)}`).replace(/\.00/, '')} ${UNIT[exp]}`; 12 | }; 13 | 14 | const findMaxLength = (names: string[]) => { 15 | return names.map((name) => name.length).sort((a, b) => b - a)[0]; 16 | }; 17 | 18 | export const reportSize = ( 19 | files: { 20 | [name: string]: string; 21 | }, 22 | ) => { 23 | const names = Object.keys(files); 24 | const maxLen = findMaxLength(names); 25 | const padLength = maxLen > 35 ? (maxLen + 2) : 35; 26 | 27 | names.forEach((name) => { 28 | const rawSize = prettifySize(files[name].length); 29 | const gzipSize = prettifySize(gzipSizeSync(files[name])); 30 | consola.info(`${name.padStart(padLength, ' ')}: ${rawSize}, ${picocolors.cyan('Gzipped')} ${gzipSize}`); 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/suffix.ts: -------------------------------------------------------------------------------- 1 | export const jsx = ['.jsx', '.tsx']; 2 | export const typescript = ['.ts', '.mts', '.cts', '.tsx']; 3 | export const declaration = ['.d.ts', '.d.mts', '.d.cjs']; 4 | export const ecmascript = ['.js', '.mjs', '.cjs', '.jsx']; 5 | 6 | /** 7 | * Wether is declaration file, checked by filepath 8 | * @param filePath 9 | * @returns 10 | */ 11 | export const isDeclaration = (filePath: string) => declaration.some((dec) => filePath.endsWith(dec)); 12 | 13 | /** 14 | * Wether is typescript only file, checked by filepath and suffix 15 | * @param suffix 16 | * @returns 17 | */ 18 | export const isTypescriptOnly = ( 19 | suffix: string, filePath: string, 20 | ) => typescript.includes(suffix) && !isDeclaration(filePath); 21 | 22 | /** 23 | * Wether is ecmascript only file, checked by filepath and suffix 24 | * @param suffix 25 | * @param filePath 26 | * @returns 27 | */ 28 | export const isEcmascriptOnly = ( 29 | suffix: string, filePath: string, 30 | ) => ecmascript.includes(suffix) && !isDeclaration(filePath); 31 | 32 | 33 | /** 34 | * Wether is [j|t]SX,only need suffix 35 | * @param suffix 36 | * @returns 37 | */ 38 | export const isJsx = (suffix: string) => jsx.includes(suffix); 39 | -------------------------------------------------------------------------------- /packages/pkg/src/helpers/watcher.ts: -------------------------------------------------------------------------------- 1 | import * as chokidar from 'chokidar'; 2 | import { unique } from '../utils.js'; 3 | import type { TaskConfig, WatchChangedFile, WatchEvent } from '../types.js'; 4 | 5 | const WATCH_INTERVAL = 250; 6 | 7 | type WatchCallback = (changedFiles: WatchChangedFile[]) => (Promise); 8 | 9 | export const createWatcher = (taskConfigs: TaskConfig[]) => { 10 | const outputs = unique(taskConfigs.map((taskConfig) => taskConfig.outputDir)); 11 | 12 | const watcher = chokidar.watch([], { 13 | ignored: [ 14 | '**/node_modules/**', 15 | '**/.git/**', 16 | ...outputs, 17 | ], 18 | ignoreInitial: true, 19 | ignorePermissionErrors: true, // Prevent permission errors 20 | }); 21 | 22 | return watcher; 23 | }; 24 | 25 | export function createBatchChangeHandler(changeCallback: WatchCallback) { 26 | let nextChangedFiles: WatchChangedFile[] = []; 27 | let runningTask: Promise | null = null; 28 | let enableBatch = false; 29 | let timer: any = 0; 30 | 31 | async function onChange(id: string, event: WatchEvent) { 32 | nextChangedFiles.push({ path: id, event }); 33 | if (enableBatch) { 34 | return; 35 | } 36 | tryRunTask(); 37 | } 38 | 39 | function tryRunTask() { 40 | if (timer) { 41 | clearTimeout(timer); 42 | } 43 | timer = setTimeout(() => { 44 | runTask(); 45 | timer = null; 46 | }, WATCH_INTERVAL); 47 | } 48 | 49 | function runTask() { 50 | if (!nextChangedFiles.length) { 51 | return; 52 | } 53 | 54 | if (!runningTask) { 55 | const changedFiles = nextChangedFiles; 56 | nextChangedFiles = []; 57 | const task = changeCallback(changedFiles); 58 | runningTask = task.finally(() => { 59 | runningTask = null; 60 | tryRunTask(); 61 | }); 62 | } 63 | } 64 | 65 | 66 | return { 67 | /** 68 | * Block and cache file changes event, do not trigger change handler 69 | */ 70 | beginBlock() { 71 | enableBatch = true; 72 | }, 73 | /** 74 | * Trigger change handler since beginBlock 75 | */ 76 | endBlock() { 77 | enableBatch = false; 78 | tryRunTask(); 79 | }, 80 | onChange, 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /packages/pkg/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'build-scripts'; 2 | import build from './commands/build.js'; 3 | import start from './commands/start.js'; 4 | import test from './commands/test.js'; 5 | 6 | import type { TaskConfig, UserConfig } from './types.js'; 7 | 8 | const pkgService = new Service({ 9 | name: 'pkgService', 10 | command: { 11 | build, 12 | start, 13 | test, 14 | }, 15 | }); 16 | 17 | export default pkgService; 18 | 19 | export * from './test/index.js'; 20 | 21 | export * from './types.js'; 22 | 23 | export { getBuiltInPlugins } from './utils.js'; 24 | 25 | export { defineConfig } from './defineConfig.js'; 26 | -------------------------------------------------------------------------------- /packages/pkg/src/plugins/component.ts: -------------------------------------------------------------------------------- 1 | import * as config from '../config/index.js'; 2 | import { TaskName, UserConfig } from '../types.js'; 3 | import type { Plugin } from '../types.js'; 4 | 5 | const plugin: Plugin = (api) => { 6 | const { 7 | registerUserConfig, 8 | registerCliOption, 9 | registerTask, 10 | context, 11 | } = api; 12 | 13 | const userConfig = context.userConfig as UserConfig; 14 | 15 | registerUserConfig(config.getUserConfig()); 16 | registerCliOption(config.getCliOptions()); 17 | // TODO: Move default value to userConfig defaultValue 18 | (userConfig.transform?.formats || ['esm', 'es2017']).forEach((format) => { 19 | registerTask(`transform-${format}`, { 20 | type: 'transform', 21 | }); 22 | }); 23 | 24 | if (userConfig.bundle) { 25 | const bundleTasks = (userConfig.bundle?.formats || ['esm', 'es2017']); 26 | if (bundleTasks.includes('umd') || bundleTasks.includes('esm') || bundleTasks.includes('cjs')) { 27 | registerTask(TaskName.BUNDLE_ES5, { 28 | type: 'bundle', 29 | }); 30 | } 31 | 32 | if (bundleTasks.includes('es2017')) { 33 | registerTask(TaskName.BUNDLE_ES2017, { 34 | type: 'bundle', 35 | }); 36 | } 37 | } 38 | }; 39 | 40 | export default plugin; 41 | -------------------------------------------------------------------------------- /packages/pkg/src/rollupPlugins/alias.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { init, parse } from 'es-module-lexer'; 3 | import consola from 'consola'; 4 | import MagicString from 'magic-string'; 5 | import { createScriptsFilter } from '../utils.js'; 6 | import type { ImportSpecifier } from 'es-module-lexer'; 7 | import type { Plugin } from 'rollup'; 8 | 9 | // aliasPlugin only available for transform task and ES Module 10 | const aliasPlugin = (rootDir: string, originalAlias: Record): Plugin => { 11 | const scriptFilter = createScriptsFilter(); 12 | return { 13 | name: 'ice-pkg:transform-alias', 14 | 15 | async transform(code: string, id: string) { 16 | // only transform source code; 17 | if (!code || !scriptFilter(id)) { 18 | return null; 19 | } 20 | await init; 21 | let imports: readonly ImportSpecifier[] = []; 22 | try { 23 | imports = parse(code)[0]; 24 | } catch (e) { 25 | consola.error('[parse error]', e); 26 | } 27 | if (!imports.length) { 28 | return { 29 | code, 30 | map: null, 31 | }; 32 | } 33 | 34 | const alias = resolveAliasConfig(originalAlias, rootDir, id); 35 | const str: MagicString = new MagicString(code); 36 | imports.forEach(({ n, s, e }) => { 37 | const matchedEntry = Object.keys(alias).find((pattern) => matches(pattern, n)); 38 | if (matchedEntry) { 39 | const updatedId = n.replace(matchedEntry, alias[matchedEntry]); 40 | str.overwrite(s, e, updatedId); 41 | } 42 | }); 43 | 44 | return { 45 | code: str.toString(), 46 | map: str.generateMap({ 47 | hires: true, 48 | includeContent: true, 49 | }), 50 | }; 51 | }, 52 | }; 53 | }; 54 | 55 | export function matches(pattern: string, importee: string) { 56 | // empty importee or pattern just return false 57 | if (!importee || !pattern) { 58 | return false; 59 | } 60 | if (importee.length < pattern.length) { 61 | return false; 62 | } 63 | if (importee === pattern) { 64 | return true; 65 | } 66 | // eslint-disable-next-line prefer-template 67 | return importee.startsWith(pattern + '/'); 68 | } 69 | 70 | export function resolveAliasConfig(alias: Record, rootDir: string, filePath: string) { 71 | const newAlias = {}; 72 | Object.keys(alias).forEach((pattern) => { 73 | const target = alias[pattern]; 74 | newAlias[pattern] = target[0] === '.' ? 75 | // transform alias relative target to relative to the rootDir 76 | path.relative(path.dirname(filePath), path.resolve(rootDir, target)).split(path.sep).join('/') || '.' : 77 | target; 78 | }); 79 | 80 | return newAlias; 81 | } 82 | 83 | export default aliasPlugin; 84 | -------------------------------------------------------------------------------- /packages/pkg/src/rollupPlugins/babel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This plugin is used to be as a supplement for swc. 3 | */ 4 | import * as babel from '@babel/core'; 5 | import type { ParserPlugin } from '@babel/parser'; 6 | import { Plugin } from 'rollup'; 7 | import { createScriptsFilter, formatCnpmDepFilepath, getIncludeNodeModuleScripts } from '../utils.js'; 8 | import type { BundleTaskConfig } from '../types.js'; 9 | import { TransformOptions } from '@babel/core'; 10 | import getBabelOptions from '../helpers/getBabelOptions.js'; 11 | 12 | const getParserPlugins = (isTS?: boolean): ParserPlugin[] => { 13 | const commonPlugins: ParserPlugin[] = [ 14 | 'jsx', 15 | 'importMeta', 16 | 'topLevelAwait', 17 | 'classProperties', 18 | 'classPrivateMethods', 19 | ]; 20 | 21 | if (isTS) { 22 | return [ 23 | ...commonPlugins, 24 | 'typescript', 25 | 'decorators-legacy', 26 | ]; 27 | } 28 | 29 | return commonPlugins; 30 | }; 31 | export interface BabelPluginOptions { 32 | pragma?: string; 33 | /** @default automatic */ 34 | jsxRuntime?: 'classic' | 'automatic'; 35 | pragmaFrag?: string; 36 | } 37 | 38 | const babelPlugin = ( 39 | plugins: babel.PluginItem[], 40 | options: BabelPluginOptions, 41 | compileDependencies?: BundleTaskConfig['compileDependencies'], 42 | modifyBabelOptions?: (babelCompileOptions: TransformOptions) => TransformOptions, 43 | ): Plugin => { 44 | // https://babeljs.io/docs/en/babel-preset-react#usage 45 | const babelOptions = getBabelOptions(plugins, options, modifyBabelOptions); 46 | const scriptsFilter = createScriptsFilter(getIncludeNodeModuleScripts(compileDependencies)); 47 | return { 48 | name: 'ice-pkg:babel', 49 | 50 | transform(source, id) { 51 | if (!scriptsFilter(formatCnpmDepFilepath(id))) { 52 | return null; 53 | } 54 | 55 | const parserPlugins = getParserPlugins(/\.tsx?$/.test(id)); 56 | 57 | const { code, map } = babel.transformSync(source, { 58 | ...babelOptions, 59 | babelrc: false, 60 | configFile: false, 61 | filename: id, 62 | parserOpts: { 63 | sourceType: 'module', 64 | plugins: parserPlugins, 65 | }, 66 | sourceFileName: id, 67 | }); 68 | return { 69 | code, 70 | map, 71 | }; 72 | }, 73 | }; 74 | }; 75 | 76 | export default babelPlugin; 77 | -------------------------------------------------------------------------------- /packages/pkg/src/rollupPlugins/dts.ts: -------------------------------------------------------------------------------- 1 | import { extname } from 'path'; 2 | import { createFilter } from '@rollup/pluginutils'; 3 | import { dtsCompile, type File } from '../helpers/dts.js'; 4 | 5 | import type { Plugin } from 'rollup'; 6 | import type { TaskConfig, UserConfig } from '../types.js'; 7 | import type { DtsInputFile, FileExt } from '../helpers/dts.js'; 8 | 9 | interface CachedContent extends DtsInputFile { 10 | updated: boolean; 11 | } 12 | 13 | interface DtsPluginOptions { 14 | rootDir: string; 15 | entry: Record; 16 | alias: TaskConfig['alias']; 17 | outputDir: string; 18 | generateTypesForJs?: UserConfig['generateTypesForJs']; 19 | } 20 | 21 | // dtsPlugin is used to generate declaration file when transforming 22 | function dtsPlugin({ 23 | rootDir, 24 | alias, 25 | generateTypesForJs, 26 | outputDir, 27 | }: DtsPluginOptions): Plugin { 28 | const includeFileRegexps = [/\.(?:[cm]?ts|tsx)$/]; 29 | if (generateTypesForJs) { 30 | includeFileRegexps.push(/\.(?:[cm]?js|jsx)$/); 31 | } 32 | const dtsFilter = createFilter( 33 | includeFileRegexps, // include 34 | [/node_modules/, /\.d\.[cm]?ts$/], // exclude 35 | ); 36 | // Actually, it's useful in dev. 37 | const cachedContents: Record = {}; 38 | 39 | return { 40 | name: 'ice-pkg:dts', 41 | transform(code, id) { 42 | if (dtsFilter(id)) { 43 | if (!cachedContents[id]) { 44 | cachedContents[id] = { 45 | srcCode: code, 46 | updated: true, 47 | ext: extname(id) as FileExt, 48 | filePath: id, 49 | }; 50 | } else if (cachedContents[id].srcCode !== code) { 51 | cachedContents[id].srcCode = code; 52 | cachedContents[id].updated = true; 53 | } 54 | } 55 | // Always return null to escape transforming 56 | return null; 57 | }, 58 | 59 | async buildEnd() { 60 | // should re-run typescript programs 61 | const updatedIds = Object.keys(cachedContents).filter((id) => cachedContents[id].updated); 62 | 63 | let dtsFiles: DtsInputFile[]; 64 | if (updatedIds.length) { 65 | const files: File[] = updatedIds.map((id) => ({ 66 | ext: cachedContents[id].ext, 67 | filePath: id, 68 | srcCode: cachedContents[id].srcCode, 69 | })); 70 | dtsFiles = await dtsCompile({ files, alias, rootDir, outputDir }); 71 | } else { 72 | dtsFiles = Object.keys(cachedContents).map((id) => { 73 | const { updated, ...rest } = cachedContents[id]; 74 | return { ...rest }; 75 | }); 76 | } 77 | dtsFiles.forEach((file) => { 78 | this.emitFile({ 79 | type: 'asset', 80 | fileName: file.dtsPath, 81 | source: file.dtsContent, 82 | }); 83 | 84 | cachedContents[file.filePath] = { 85 | ...cachedContents[file.filePath], 86 | ...file, 87 | }; 88 | }); 89 | 90 | updatedIds.forEach((updateId) => { cachedContents[updateId].updated = false; }); 91 | }, 92 | }; 93 | } 94 | 95 | export default dtsPlugin; 96 | -------------------------------------------------------------------------------- /packages/pkg/src/rollupPlugins/minify.ts: -------------------------------------------------------------------------------- 1 | import * as swc from '@swc/core'; 2 | 3 | import type { Plugin } from 'rollup'; 4 | import type { TaskConfig } from '../types'; 5 | 6 | /** 7 | * plugin-minify use minimize bundle outputs using swc 8 | */ 9 | const minifyPlugin = (sourcemap: TaskConfig['sourcemap'], minifyOptions: swc.JsMinifyOptions): Plugin => { 10 | return { 11 | name: 'ice-pkg:minify', 12 | renderChunk(code) { 13 | return swc.minify(code, { 14 | // Minify amd module will cause an error(`module` reserved Words will be declared in the top level). 15 | module: true, 16 | sourceMap: !!sourcemap, 17 | ...minifyOptions, 18 | }); 19 | }, 20 | }; 21 | }; 22 | 23 | export default minifyPlugin; 24 | -------------------------------------------------------------------------------- /packages/pkg/src/test/defineJestConfig.ts: -------------------------------------------------------------------------------- 1 | import merge from 'lodash.merge'; 2 | import * as path from 'path'; 3 | import fse from 'fs-extra'; 4 | import getTaskConfig from './getTaskConfig.js'; 5 | 6 | import type { Config as JestConfig } from 'jest'; 7 | import type { Service } from 'build-scripts'; 8 | import type { TaskConfig, UserConfig } from '../types'; 9 | 10 | export default function defineJestConfig( 11 | service: Service, 12 | userJestConfig: JestConfig | (() => Promise), 13 | ): () => Promise { 14 | return async () => { 15 | // Support jest configuration (object or function) Ref: https://jestjs.io/docs/configuration 16 | let customJestConfig: JestConfig; 17 | if (typeof userJestConfig === 'function') { 18 | customJestConfig = await userJestConfig(); 19 | } else { 20 | customJestConfig = userJestConfig; 21 | } 22 | 23 | const defaultConfig = await getDefaultConfig(service); 24 | 25 | return merge(defaultConfig, customJestConfig); 26 | }; 27 | } 28 | 29 | async function getDefaultConfig(service: Service): Promise { 30 | const { taskConfig, context: { rootDir } } = await getTaskConfig(service); 31 | const { alias = {}, define = {} } = taskConfig; 32 | 33 | const moduleNameMapper = generateModuleNameMapper(rootDir, alias); 34 | 35 | return { 36 | moduleNameMapper, 37 | globals: define, 38 | }; 39 | } 40 | 41 | function generateModuleNameMapper(rootDir: string, alias: TaskConfig['alias']) { 42 | const moduleNameMapper = {}; 43 | for (const key in alias) { 44 | const aliasPath = alias[key]; 45 | const absoluteAliasPath = path.isAbsolute(aliasPath) ? aliasPath : path.join(rootDir, aliasPath); 46 | const isDir = fse.lstatSync(absoluteAliasPath).isDirectory(); 47 | moduleNameMapper[`^${key}${isDir ? '/(.*)' : ''}`] = `${absoluteAliasPath}${isDir ? '/$1' : ''}`; 48 | } 49 | 50 | return moduleNameMapper; 51 | } 52 | -------------------------------------------------------------------------------- /packages/pkg/src/test/defineVitestConfig.ts: -------------------------------------------------------------------------------- 1 | import merge from 'lodash.merge'; 2 | import getTaskConfig from './getTaskConfig.js'; 3 | 4 | import type { Service } from 'build-scripts'; 5 | import type { UserConfigExport, ConfigEnv, UserConfig as VitestUserConfig, UserConfigFn as VitestUserConfigFn } from 'vitest/config'; 6 | import type { TaskConfig, UserConfig } from '../types'; 7 | 8 | export default function defineVitestConfig( 9 | service: Service, 10 | userConfig: UserConfigExport, 11 | ): VitestUserConfigFn { 12 | return async (env: ConfigEnv) => { 13 | // Support vitest configuration (object or function) Ref: https://github.com/vitest-dev/vitest/blob/e5c40cff0925c3c12d8cdfa59f5649d3562668ce/packages/vitest/src/config.ts#L3 14 | let customConfig: VitestUserConfig; 15 | if (typeof userConfig === 'function') { 16 | customConfig = await userConfig(env); 17 | } else { 18 | customConfig = await userConfig; 19 | } 20 | 21 | const defaultConfig = await getDefaultConfig(service); 22 | 23 | return merge(defaultConfig, customConfig); 24 | }; 25 | } 26 | 27 | async function getDefaultConfig(service: Service): Promise { 28 | const { taskConfig } = await getTaskConfig(service); 29 | const { alias = {}, define = {} } = taskConfig; 30 | return { 31 | resolve: { 32 | alias, 33 | }, 34 | // FIXME: pass the custom define config 35 | define, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/pkg/src/test/getTaskConfig.ts: -------------------------------------------------------------------------------- 1 | import type { Service } from 'build-scripts'; 2 | import type { TaskConfig, UserConfig } from '../types'; 3 | import { getBuiltInPlugins } from '../utils.js'; 4 | 5 | export default async function getTaskConfig(service: Service) { 6 | const { taskConfigs, context } = await service.run({ 7 | command: 'test', 8 | commandArgs: {}, 9 | getBuiltInPlugins, 10 | }) as any; 11 | 12 | if (taskConfigs.length === 0) { 13 | throw new Error('No task config was found.'); 14 | } 15 | 16 | return { 17 | taskConfig: taskConfigs[0] as TaskConfig, 18 | context, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /packages/pkg/src/test/index.ts: -------------------------------------------------------------------------------- 1 | export { default as defineJestConfig } from './defineJestConfig.js'; 2 | export { default as defineVitestConfig } from './defineVitestConfig.js'; 3 | -------------------------------------------------------------------------------- /packages/pkg/tests/aliasPlugin.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it, describe } from 'vitest'; 2 | import aliasPlugin, { matches, resolveAliasConfig } from '../src/rollupPlugins/alias'; 3 | 4 | describe('aliasPlugin', () => { 5 | it('matches alias id', () => { 6 | // length not match 7 | expect(matches('foo', 'fo')).toBe(false); 8 | expect(matches('@', '')).toBe(false); 9 | // match 10 | expect(matches('foo', 'foo')).toBe(true); 11 | expect(matches('foo', 'foo/bar')).toBe(true); 12 | expect(matches('foo', 'foo/bar/baz')).toBe(true); 13 | expect(matches('@', '@/bar')).toBe(true); 14 | expect(matches('@', '@/bar/baz')).toBe(true); 15 | // start string not match 16 | expect(matches('foo', 'foo-bar')).toBe(false); 17 | expect(matches('foo', 'foo-bar/baz')).toBe(false); 18 | expect(matches('foo', 'foo-bar/baz/qux')).toBe(false); 19 | }) 20 | 21 | it('resolve alias config', () => { 22 | // module 23 | expect(resolveAliasConfig({ 24 | 'react': 'react-dom', 25 | }, '/workspace', '/workspace/src/index.tsx')).toEqual({ 26 | 'react': 'react-dom', 27 | }) 28 | // relative path 29 | expect(resolveAliasConfig({ 30 | '@': './src', 31 | }, '/workspace', '/workspace/src/components/index.tsx')).toEqual({ 32 | '@': '..', 33 | }) 34 | expect(resolveAliasConfig({ 35 | '@': './src', 36 | }, '/workspace', '/workspace/src/index.tsx')).toEqual({ 37 | '@': '.', 38 | }) 39 | expect(resolveAliasConfig({ 40 | '@': './src', 41 | }, '/workspace', '/workspace/src/components/Button/index.tsx')).toEqual({ 42 | '@': '../..', 43 | }) 44 | }) 45 | 46 | it('alias plugin transform', async () => { 47 | const plugin = aliasPlugin( 48 | '/workspace', 49 | { 50 | '@': './src', 51 | 'react': 'react-dom', 52 | } 53 | ); 54 | const result1 = await (plugin as any).transform( 55 | 'console.log()', 56 | '/workspace/src/index.tsx', 57 | ) 58 | expect(result1.code).toBe('console.log()'); 59 | 60 | const result2 = await (plugin as any).transform( 61 | 'import react from "react";', 62 | '/workspace/src/index.tsx', 63 | ) 64 | expect(result2.code).toBe('import react from "react-dom";'); 65 | 66 | const result3 = await (plugin as any).transform( 67 | 'import Button from "@/components/Button";', 68 | '/workspace/src/index.tsx', 69 | ) 70 | expect(result3.code).toBe('import Button from "./components/Button";'); 71 | 72 | const result4 = await (plugin as any).transform( 73 | 'import Button from "@/components/Button";', 74 | '/workspace/src/pages/index.tsx', 75 | ) 76 | expect(result4.code).toBe('import Button from "../components/Button";'); 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /packages/pkg/tests/babelPlugin.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it, describe } from 'vitest'; 2 | import { TaskConfig } from '../src'; 3 | import { default as makeBabelPlugin } from '../src/rollupPlugins/babel'; 4 | 5 | const babelPlugins = [ 6 | 'babel-plugin-transform-jsx-list', 7 | 'babel-plugin-transform-jsx-condition', 8 | 'babel-plugin-transform-jsx-memo', 9 | 'babel-plugin-transform-jsx-slot', 10 | [ 11 | 'babel-plugin-transform-jsx-fragment', 12 | { 13 | moduleName: 'react', 14 | }, 15 | ], 16 | 'babel-plugin-transform-jsx-class', 17 | ]; 18 | 19 | function cleanCode(str: string) { 20 | return str.replace(/\s+/g, ''); 21 | } 22 | 23 | describe('transform', () => { 24 | it('w/ automatic jsx runtime', () => { 25 | const transformTaskConfig: TaskConfig = { 26 | babelPlugins, 27 | jsxRuntime: 'automatic', 28 | type: 'transform', 29 | }; 30 | const babelPlugin = makeBabelPlugin(transformTaskConfig.babelPlugins!, { 31 | jsxRuntime: transformTaskConfig.jsxRuntime, 32 | pragma: transformTaskConfig.swcCompileOptions?.jsc?.transform?.react?.pragma, 33 | pragmaFrag: transformTaskConfig.swcCompileOptions?.jsc?.transform?.react?.pragmaFrag, 34 | }); 35 | expect(babelPlugin.name).toBe('ice-pkg:babel'); 36 | // @ts-ignore it's callable 37 | const ret = babelPlugin.transform('
', 'src/test.tsx'); 38 | expect(cleanCode(ret.code)).toBe( 39 | cleanCode(`import { createCondition as __create_condition__ } from "babel-runtime-jsx-plus"; 40 | import { jsx as _jsx } from "@ice/jsx-runtime/jsx-runtime"; 41 | __create_condition__([[() => false, () => _jsx("div", {})]]);`), 42 | ); 43 | }); 44 | 45 | it('w/ classic jsx runtime', () => { 46 | const transformTaskConfig: TaskConfig = { 47 | babelPlugins, 48 | jsxRuntime: 'classic', 49 | type: 'transform', 50 | }; 51 | const babelPlugin = makeBabelPlugin(transformTaskConfig.babelPlugins!, { 52 | jsxRuntime: transformTaskConfig.jsxRuntime, 53 | pragma: transformTaskConfig.swcCompileOptions?.jsc?.transform?.react?.pragma, 54 | pragmaFrag: transformTaskConfig.swcCompileOptions?.jsc?.transform?.react?.pragmaFrag, 55 | }); 56 | expect(babelPlugin.name).toBe('ice-pkg:babel'); 57 | // @ts-ignore it's callable 58 | const ret = babelPlugin.transform('
', 'src/test.tsx'); 59 | expect(cleanCode(ret.code)).toBe( 60 | cleanCode(`import { createCondition as __create_condition__ } from "babel-runtime-jsx-plus"; 61 | __create_condition__([[() => false, () => /*#__PURE__*/React.createElement("div", null)]]);`), 62 | ); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/alias/build.config.default.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@ice/pkg'; 2 | 3 | // https://pkg.ice.work/reference/config-list 4 | export default defineConfig({ 5 | transform: { 6 | formats: ['cjs', 'esm', 'es2017'] 7 | }, 8 | alias: { 9 | '@': './src' 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/alias/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ice/pkg-tests-fixtures-alias", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ice/pkg": "workspace:*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/alias/src/alias.ts: -------------------------------------------------------------------------------- 1 | export const bar = 2 2 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/alias/src/index.ts: -------------------------------------------------------------------------------- 1 | import { bar } from '@/alias.js' 2 | 3 | export const foo = 1 4 | export { bar } 5 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/alias/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.common.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@": ["./src"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/bundle-browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ice/pkg-tests-fixtures-bundle-browser", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ice/pkg": "workspace:*", 7 | "@ice/pkg-tests-fixtures-mock-entry-package": "workspace:*" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/bundle-browser/src/index.ts: -------------------------------------------------------------------------------- 1 | import { id } from '@ice/pkg-tests-fixtures-mock-entry-package' 2 | console.log(id) 3 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/default/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ice/pkg-tests-fixtures-default", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ice/pkg": "workspace:*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/default/src/index.ts: -------------------------------------------------------------------------------- 1 | export const foo = 1 2 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/mock-entry-package/browser.js: -------------------------------------------------------------------------------- 1 | export const id = 'browser' 2 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/mock-entry-package/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare const id: string 2 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/mock-entry-package/main.js: -------------------------------------------------------------------------------- 1 | export const id = 'main' 2 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/mock-entry-package/module.js: -------------------------------------------------------------------------------- 1 | export const id = 'module' 2 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/mock-entry-package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ice/pkg-tests-fixtures-mock-entry-package", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./main.js", 6 | "module": "./module.js", 7 | "browser": "./browser.js", 8 | "types": "./index.d.ts", 9 | "description": "模块入口测试", 10 | "dependencies": { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/pkg/tests/fixtures/tsconfig.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/pkg/tests/formatCnpmDepFilepath.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it, describe } from 'vitest'; 2 | import { formatCnpmDepFilepath } from '../src/utils'; 3 | 4 | describe('formatCnpmDepFilepath function', () => { 5 | it('pnpm path', () => { 6 | expect(formatCnpmDepFilepath('/workspace/node_modules/.pnpm/classnames@2.3.2/node_modules/classnames/index.js')).toBe('/workspace/node_modules/.pnpm/classnames@2.3.2/node_modules/classnames/index.js'); 7 | }) 8 | it('pnpm path with scope', () => { 9 | expect(formatCnpmDepFilepath('/workspace/node_modules/.pnpm/@actions+exec@1.1.1/node_modules/@actions/exec/lib/exec.js')).toBe('/workspace/node_modules/.pnpm/@actions+exec@1.1.1/node_modules/@actions/exec/lib/exec.js'); 10 | }) 11 | it('cnpm path', () => { 12 | expect(formatCnpmDepFilepath('/workspace/node_modules/_idb@7.1.1@idb/build/index.js')).toBe('/workspace/node_modules/idb/build/index.js'); 13 | }) 14 | it('cnpm path with npm scope', () => { 15 | expect(formatCnpmDepFilepath('/workspace/node_modules/_@swc_helpers@0.5.3@@swc/helpers/esm/_extends.js')).toBe('/workspace/node_modules/@swc/helpers/esm/_extends.js'); 16 | }) 17 | it('npm path', () => { 18 | expect(formatCnpmDepFilepath('/workspace/node_modules/idb/build/index.js')).toBe('/workspace/node_modules/idb/build/index.js'); 19 | }) 20 | it('npm path with npm scope', () => { 21 | expect(formatCnpmDepFilepath('/workspace/node_modules/@ice/idb/build/index.js')).toBe('/workspace/node_modules/@ice/idb/build/index.js'); 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /packages/pkg/tests/projects/__snapshots__/alias.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`Run config default > cjs structure 1`] = ` 4 | { 5 | "files": [ 6 | { 7 | "name": "alias.d.ts", 8 | }, 9 | { 10 | "name": "alias.js", 11 | }, 12 | { 13 | "name": "index.d.ts", 14 | }, 15 | { 16 | "name": "index.js", 17 | }, 18 | ], 19 | "name": "cjs", 20 | } 21 | `; 22 | 23 | exports[`Run config default > dist structure 1`] = `null`; 24 | 25 | exports[`Run config default > es2017 structure 1`] = ` 26 | { 27 | "files": [ 28 | { 29 | "name": "alias.d.ts", 30 | }, 31 | { 32 | "name": "alias.js", 33 | }, 34 | { 35 | "name": "index.d.ts", 36 | }, 37 | { 38 | "name": "index.js", 39 | }, 40 | ], 41 | "name": "es2017", 42 | } 43 | `; 44 | 45 | exports[`Run config default > esm structure 1`] = ` 46 | { 47 | "files": [ 48 | { 49 | "name": "alias.d.ts", 50 | }, 51 | { 52 | "name": "alias.js", 53 | }, 54 | { 55 | "name": "index.d.ts", 56 | }, 57 | { 58 | "name": "index.js", 59 | }, 60 | ], 61 | "name": "esm", 62 | } 63 | `; 64 | 65 | exports[`Run config default > file content cjs/alias.d.ts 1`] = ` 66 | "export declare const bar = 2; 67 | " 68 | `; 69 | 70 | exports[`Run config default > file content cjs/alias.js 1`] = ` 71 | "\\"use strict\\"; 72 | Object.defineProperty(exports, \\"__esModule\\", { 73 | value: true 74 | }); 75 | Object.defineProperty(exports, \\"bar\\", { 76 | enumerable: true, 77 | get: function() { 78 | return bar; 79 | } 80 | }); 81 | var bar = 2; 82 | " 83 | `; 84 | 85 | exports[`Run config default > file content cjs/index.d.ts 1`] = ` 86 | "import { bar } from './alias.js'; 87 | export declare const foo = 1; 88 | export { bar }; 89 | " 90 | `; 91 | 92 | exports[`Run config default > file content cjs/index.js 1`] = ` 93 | "\\"use strict\\"; 94 | Object.defineProperty(exports, \\"__esModule\\", { 95 | value: true 96 | }); 97 | function _export(target, all) { 98 | for(var name in all)Object.defineProperty(target, name, { 99 | enumerable: true, 100 | get: all[name] 101 | }); 102 | } 103 | _export(exports, { 104 | foo: function() { 105 | return foo; 106 | }, 107 | bar: function() { 108 | return _alias.bar; 109 | } 110 | }); 111 | var _alias = require(\\"@/alias.js\\"); 112 | var foo = 1; 113 | " 114 | `; 115 | 116 | exports[`Run config default > file content es2017/alias.d.ts 1`] = ` 117 | "export declare const bar = 2; 118 | " 119 | `; 120 | 121 | exports[`Run config default > file content es2017/alias.js 1`] = ` 122 | "export const bar = 2; 123 | " 124 | `; 125 | 126 | exports[`Run config default > file content es2017/index.d.ts 1`] = ` 127 | "import { bar } from './alias.js'; 128 | export declare const foo = 1; 129 | export { bar }; 130 | " 131 | `; 132 | 133 | exports[`Run config default > file content es2017/index.js 1`] = ` 134 | "import { bar } from './alias.js'; 135 | export const foo = 1; 136 | export { bar }; 137 | " 138 | `; 139 | 140 | exports[`Run config default > file content esm/alias.d.ts 1`] = ` 141 | "export declare const bar = 2; 142 | " 143 | `; 144 | 145 | exports[`Run config default > file content esm/alias.js 1`] = ` 146 | "export var bar = 2; 147 | " 148 | `; 149 | 150 | exports[`Run config default > file content esm/index.d.ts 1`] = ` 151 | "import { bar } from './alias.js'; 152 | export declare const foo = 1; 153 | export { bar }; 154 | " 155 | `; 156 | 157 | exports[`Run config default > file content esm/index.js 1`] = ` 158 | "import { bar } from \\"./alias.js\\"; 159 | export var foo = 1; 160 | export { bar }; 161 | " 162 | `; 163 | -------------------------------------------------------------------------------- /packages/pkg/tests/projects/__snapshots__/bundle-browser.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`Run config default > cjs structure 1`] = `null`; 4 | 5 | exports[`Run config default > dist structure 1`] = ` 6 | { 7 | "files": [ 8 | { 9 | "name": "index.cjs.es5.production.js", 10 | }, 11 | { 12 | "name": "index.esm.es5.production.js", 13 | }, 14 | ], 15 | "name": "dist", 16 | } 17 | `; 18 | 19 | exports[`Run config default > es2017 structure 1`] = `null`; 20 | 21 | exports[`Run config default > esm structure 1`] = `null`; 22 | 23 | exports[`Run config default > file content dist/index.cjs.es5.production.js 1`] = ` 24 | "\\"use strict\\";console.log(\\"module\\"); 25 | " 26 | `; 27 | 28 | exports[`Run config default > file content dist/index.esm.es5.production.js 1`] = ` 29 | "console.log(\\"module\\"); 30 | " 31 | `; 32 | 33 | exports[`Run config enable-browser > cjs structure 1`] = `null`; 34 | 35 | exports[`Run config enable-browser > dist structure 1`] = ` 36 | { 37 | "files": [ 38 | { 39 | "name": "index.cjs.es5.production.js", 40 | }, 41 | { 42 | "name": "index.esm.es5.production.js", 43 | }, 44 | ], 45 | "name": "dist", 46 | } 47 | `; 48 | 49 | exports[`Run config enable-browser > es2017 structure 1`] = `null`; 50 | 51 | exports[`Run config enable-browser > esm structure 1`] = `null`; 52 | 53 | exports[`Run config enable-browser > file content dist/index.cjs.es5.production.js 1`] = ` 54 | "\\"use strict\\";console.log(\\"browser\\"); 55 | " 56 | `; 57 | 58 | exports[`Run config enable-browser > file content dist/index.esm.es5.production.js 1`] = ` 59 | "console.log(\\"browser\\"); 60 | " 61 | `; 62 | -------------------------------------------------------------------------------- /packages/pkg/tests/projects/alias.test.ts: -------------------------------------------------------------------------------- 1 | import {runProjectTest} from "./helper"; 2 | 3 | runProjectTest('alias', [{ 4 | name: 'default', 5 | config: 'build.config.default.mts' 6 | }]) 7 | -------------------------------------------------------------------------------- /packages/pkg/tests/projects/bundle-browser.test.ts: -------------------------------------------------------------------------------- 1 | import { runProjectTest } from "./helper"; 2 | 3 | runProjectTest('bundle-browser', [{ 4 | name: 'default', 5 | config: { 6 | transform: { formats: [] }, 7 | bundle: { 8 | formats: ['esm', 'cjs'] 9 | } 10 | } 11 | }, { 12 | name: 'enable-browser', 13 | config: { 14 | transform: { formats: [] }, 15 | bundle: { 16 | formats: ['esm', 'cjs'], 17 | browser: true 18 | } 19 | } 20 | }]) 21 | -------------------------------------------------------------------------------- /packages/pkg/tests/projects/default.test.ts: -------------------------------------------------------------------------------- 1 | import {runProjectTest} from "./helper"; 2 | 3 | runProjectTest('default', [ 4 | { 5 | name: 'default', 6 | config: {}, 7 | snapshot: 'structure' 8 | }, 9 | { 10 | name: 'bundle', 11 | snapshot: 'structure', 12 | config: { 13 | transform: { formats: [] }, 14 | bundle: {} 15 | } 16 | }, 17 | { 18 | name: 'bundle-full', 19 | snapshot: 'structure', 20 | config: { 21 | transform: { formats: [] }, 22 | bundle: { 23 | formats: ['cjs', 'es2017', 'esm', 'umd'] 24 | } 25 | } 26 | }, 27 | { 28 | name: 'bundle-with-full-modes', 29 | snapshot: 'structure', 30 | config: { 31 | transform: { formats: [] }, 32 | bundle: { 33 | modes: ['development', 'production'] 34 | } 35 | } 36 | }, 37 | { 38 | name: 'bundle-with-dev-mode', 39 | snapshot: 'structure', 40 | config: { 41 | transform: { formats: [] }, 42 | bundle: { 43 | modes: ['development'] 44 | } 45 | } 46 | }, 47 | { 48 | name: 'bundle-with-empty-mode', 49 | snapshot: 'structure', 50 | config: { 51 | transform: { formats: [] }, 52 | bundle: { 53 | modes: [] 54 | } 55 | } 56 | }, 57 | { 58 | name: 'sourcemap-enable', 59 | snapshot: 'structure', 60 | config: { 61 | sourceMaps: true 62 | } 63 | } 64 | ]) 65 | -------------------------------------------------------------------------------- /packages/pkg/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "src", 6 | "outDir": "lib" 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/pkg/types.d.ts: -------------------------------------------------------------------------------- 1 | // CSS modules 2 | declare module '*.module.less' { 3 | const classes: { [key: string]: string }; 4 | export default classes; 5 | } 6 | 7 | declare module '*.module.css' { 8 | const classes: { [key: string]: string }; 9 | export default classes; 10 | } 11 | 12 | declare module '*.module.scss' { 13 | const classes: { [key: string]: string }; 14 | export default classes; 15 | } 16 | 17 | // images 18 | declare module '*.jpg' { 19 | const src: string; 20 | export default src; 21 | } 22 | declare module '*.jpeg' { 23 | const src: string; 24 | export default src; 25 | } 26 | declare module '*.png' { 27 | const src: string; 28 | export default src; 29 | } 30 | declare module '*.gif' { 31 | const src: string; 32 | export default src; 33 | } 34 | declare module '*.svg' { 35 | const src: string; 36 | export default src; 37 | } 38 | declare module '*.webp' { 39 | const src: string; 40 | export default src; 41 | } 42 | 43 | // global env 44 | declare let __DEV__: boolean; 45 | 46 | // process.env 47 | declare namespace NodeJS { 48 | export interface ProcessEnv { 49 | NODE_ENV: 'development' | 'production'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/README.md: -------------------------------------------------------------------------------- 1 | # @ice/pkg-plugin-docusaurus 2 | 3 | This plugin supports component and docs preview for ICE PKG. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | $ npm i @ice/pkg-plugin-docusaurus --save-dev 9 | ``` 10 | 11 | ```ts 12 | // build.config.mts 13 | import { defineConfig } from '@ice/pkg'; 14 | 15 | export default defineConfig({ 16 | plugins: [ 17 | ['@ice/pkg-plugin-docusaurus'], 18 | ], 19 | }) 20 | ``` 21 | 22 | For more detail, please see https://pkg.ice.work/guide/preview 23 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/build.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@ice/pkg'; 2 | 3 | export default defineConfig({ 4 | transform: { 5 | formats: ['es2017'], 6 | }, 7 | generateTypesForJs: true, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ice/pkg-plugin-docusaurus", 3 | "version": "1.4.18", 4 | "description": "@ice/pkg plugin for component and docs preview.", 5 | "main": "es2017/index.mjs", 6 | "exports": { 7 | ".": { 8 | "type": "./es2017/index.d.mts", 9 | "import": "./es2017/index.mjs", 10 | "default": "./es2017/index.mjs" 11 | }, 12 | "./remark/extractCode": { 13 | "type": "./es2017/extractCode.d.ts", 14 | "import": "./es2017/remark/extractCode.js", 15 | "default": "./es2017/remark/extractCode.js" 16 | }, 17 | "./plugin.js": { 18 | "type": "./es2017/plugin.d.ts", 19 | "import": "./es2017/plugin.js", 20 | "default": "./es2017/plugin.js" 21 | }, 22 | "./css/custom.css": "./es2017/css/custom.css" 23 | }, 24 | "files": [ 25 | "es2017", 26 | "!esm/**/*.map", 27 | "!es2017/**/*.map" 28 | ], 29 | "engines": { 30 | "node": ">=16.14.0" 31 | }, 32 | "license": "MIT", 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/ice-lab/icepkg.git", 36 | "directory": "packages/plugin-docusaurus" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/ice-lab/icepkg/issues" 40 | }, 41 | "homepage": "https://pkg.ice.work", 42 | "scripts": { 43 | "watch": "ice-pkg start", 44 | "build": "ice-pkg build" 45 | }, 46 | "dependencies": { 47 | "@docusaurus/core": "^2.4.0", 48 | "@docusaurus/preset-classic": "^2.4.0", 49 | "@docusaurus/plugin-content-pages": "^2.4.0", 50 | "@mdx-js/react": "^1.6.22", 51 | "@swc/helpers": "^0.5.1", 52 | "@ice/jsx-runtime": "^0.2.0", 53 | "address": "^1.2.1", 54 | "consola": "^2.15.3", 55 | "copy-text-to-clipboard": "^3.0.1", 56 | "detect-port": "^1.3.0", 57 | "directory-tree": "^3.3.1", 58 | "es-module-lexer": "^0.10.0", 59 | "fs-extra": "^10.0.0", 60 | "handlebars": "^4.7.7", 61 | "hast-util-find-and-replace": "3", 62 | "less": "^4.1.3", 63 | "less-loader": "^11.0.0", 64 | "postcss-plugin-rpx2vw": "^0.0.3", 65 | "prism-react-renderer": "^1.3.1", 66 | "qrcode.react": "^3.1.0", 67 | "react-tooltip": "^4.2.21", 68 | "remark-parse": "^10.0.1", 69 | "remark-stringify": "^10.0.2", 70 | "sass": "^1.3.0", 71 | "sass-loader": "^12.6.0", 72 | "style-unit": "^3.0.4", 73 | "unified": "^10.1.2", 74 | "unist-util-visit": "2.0.3" 75 | }, 76 | "devDependencies": { 77 | "@algolia/client-search": "^4.9.1", 78 | "@types/react": "^17.0.0", 79 | "@ice/pkg": "^1.5.12", 80 | "react": "^18.2.0", 81 | "react-dom": "^18.2.0", 82 | "typescript": "catalog:", 83 | "webpack": "^5.76.3" 84 | }, 85 | "publishConfig": { 86 | "access": "public", 87 | "registry": "https://registry.npmjs.org/" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/Previewer/PC.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import CodeBlock from '@theme-original/CodeBlock'; 3 | import ReactTooltip from 'react-tooltip'; 4 | import copy from 'copy-text-to-clipboard'; 5 | import styles from './styles.module.css'; 6 | 7 | function UnfoldSvg() { 8 | return ( 9 | 15 | 16 | 17 | ); 18 | } 19 | 20 | function CopySvg() { 21 | return ( 22 | 25 | ); 26 | } 27 | 28 | function PcPreview({ children, code }) { 29 | const [unfold, triggerFold] = useState(false); 30 | 31 | const doCopy = () => { 32 | copy(code); 33 | }; 34 | 35 | return ( 36 |
37 |
38 | {/* Preview Demo Content */} 39 |
40 | {children} 41 |
42 | 43 | 46 | 47 |
48 |
doCopy()} 51 | data-tip="复制" 52 | > 53 | 54 |
55 | 56 |
triggerFold((fold) => !fold)} 59 | data-tip={unfold ? '收起' : '展开'} 60 | > 61 | 62 |
63 |
64 |
65 | 66 |
67 | {unfold && ( 68 | 75 | )} 76 |
77 |
78 | ); 79 | } 80 | 81 | export default PcPreview; 82 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/Previewer/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PCPreview from './PC'; 3 | import MobilePreview from './Mobile'; 4 | 5 | interface PreviewerProps { 6 | mobilePreview: boolean; 7 | url: string; 8 | code: string; 9 | } 10 | 11 | const Previewer: React.FunctionComponent> = ({ 12 | mobilePreview, 13 | children, 14 | code, 15 | url, 16 | }) => { 17 | const deserializedCode = (code || '') 18 | .replace(/`/g, '`') 19 | .replace(/$/g, '$'); 20 | 21 | if (mobilePreview) { 22 | return ; 23 | } else { 24 | return ( 25 | 26 | {children} 27 | 28 | ); 29 | } 30 | }; 31 | 32 | export default Previewer; 33 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/Previewer/iphoneX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-lab/icepkg/3c3f7e65bbc3980b79611eea7a11555b463e1834/packages/plugin-docusaurus/src/Previewer/iphoneX.png -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/Previewer/styles.css: -------------------------------------------------------------------------------- 1 | .mobile-previewer pre { 2 | height: 720px; 3 | } 4 | 5 | .mobile-previewer > div:nth-child(1) { 6 | width: calc(100% - 375px); 7 | } 8 | 9 | iframe body::-webkit-scrollbar { 10 | display: none; /* Chrome Safari */ 11 | } 12 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/Previewer/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-top: 20px; 3 | border: 1px solid #eee; 4 | } 5 | 6 | .unfoldContainer { 7 | margin-bottom: 20px; 8 | } 9 | 10 | .preview { 11 | padding: 24px; 12 | } 13 | 14 | .operations { 15 | margin-top: 24px; 16 | display: flex; 17 | align-items: center; 18 | justify-content: flex-end; 19 | height: 40px; 20 | border-top: 1px solid #eee; 21 | } 22 | 23 | .item { 24 | cursor: pointer; 25 | padding: 0 12px; 26 | } 27 | 28 | .codeWrapper { 29 | border-width: 0 1px 0px 1px; 30 | border-color: #eee; 31 | border-style: solid; 32 | } 33 | 34 | .mobileWrapper { 35 | display: flex; 36 | justify-content: space-between; 37 | } 38 | 39 | .iframeWrapper { 40 | padding-top: 75px; 41 | width: 375px; 42 | height: 730px; 43 | overflow: hidden; 44 | display: flex; 45 | justify-content: center; 46 | background: url(./iphoneX.png) no-repeat; 47 | background-size: 100%; 48 | } 49 | 50 | .mobilePreviewWrapper { 51 | display: flex; 52 | } 53 | 54 | .mobilePreviewArea { 55 | margin-left: 20px; 56 | position: relative; 57 | } 58 | 59 | .mobileOperations { 60 | transform: translateX(-50%); 61 | left: 50%; 62 | display: flex; 63 | justify-content: space-around; 64 | align-items: center; 65 | position: absolute; 66 | height: 40px; 67 | width: 325px; 68 | bottom: 20px; 69 | border-top: 2px solid #eee; 70 | } 71 | 72 | .mobileCodeWrapper { 73 | flex: 1; 74 | } 75 | 76 | .operationItem { 77 | display: flex; 78 | align-items: center; 79 | font-size: 14px; 80 | color: #666; 81 | cursor: pointer; 82 | } 83 | .dialog{ 84 | background-color: #666; 85 | opacity: 0.6; 86 | position: absolute; 87 | top: 0; 88 | left: 0; 89 | right: 0; 90 | bottom: 0; 91 | display: flex; 92 | align-items: center; 93 | justify-content: center; 94 | } 95 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/constants.mts: -------------------------------------------------------------------------------- 1 | export const DOCUSAURUS_DIR = '.docusaurus'; 2 | export const DOCUSAURUS_CONFIG_FILE = 'docusaurus.config.cjs'; 3 | export const DOCUSAURUS_BABEL_CONFIG_FILE = 'babel.config.js'; 4 | export const DEFAULT_DEV_SERVER_PORT = 4000; 5 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | :root { 7 | --ifm-global-radius: 2px; 8 | } 9 | 10 | /* Make menu link more smaller */ 11 | .menu__link--sublist:after { 12 | background: var(--ifm-menu-link-sublist-icon) 50% / 1.25rem 1.25rem 13 | } 14 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/doc.mts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { fork } from 'child_process'; 4 | import { createRequire } from 'module'; 5 | import consola from 'consola'; 6 | import detect from 'detect-port'; 7 | import address from 'address'; 8 | import { DOCUSAURUS_DIR, DOCUSAURUS_CONFIG_FILE, DOCUSAURUS_BABEL_CONFIG_FILE } from './constants.mjs'; 9 | 10 | import type { PluginDocusaurusOptions } from './types.mjs'; 11 | import type { PluginAPI } from '@ice/pkg'; 12 | 13 | const require = createRequire(import.meta.url); 14 | 15 | export const doc = async (api: PluginAPI, options: PluginDocusaurusOptions) => { 16 | const { context } = api; 17 | const { rootDir, command } = context; 18 | 19 | const maybeCustomPath = path.join(rootDir, 'docusaurus.config.js'); 20 | const docusaurusConfigFileExist = fs.pathExistsSync(maybeCustomPath); 21 | 22 | if (docusaurusConfigFileExist) { 23 | consola.warn('PLUGIN-DOCUSAURUS', 'Found docusaurus.config.js in current project. And you should configure docusaurus by yourself.'); 24 | } 25 | 26 | const ip = options.host || address.ip(); 27 | const port = await detect(options.port); 28 | 29 | const binPath = require.resolve('@docusaurus/core/bin/docusaurus.mjs'); 30 | 31 | const child = fork( 32 | binPath, 33 | [ 34 | command, 35 | !docusaurusConfigFileExist && `--config=${rootDir}/${DOCUSAURUS_DIR}/${DOCUSAURUS_CONFIG_FILE}`, 36 | command === 'start' && `--port=${port}`, 37 | command === 'start' && `--host=${ip}`, 38 | command === 'build' && options.outputDir && `--out-dir=${options.outputDir}`, 39 | ].filter(Boolean), 40 | { 41 | cwd: rootDir, 42 | env: { 43 | ...process.env, 44 | DOCUSAURUS_BABEL_CONFIG_FILE_NAME: `${DOCUSAURUS_DIR}/${DOCUSAURUS_BABEL_CONFIG_FILE}`, 45 | }, 46 | }, 47 | ); 48 | 49 | child.on('exit', (code) => { 50 | if (code === 1) { 51 | throw new Error('Doc build failed!'); 52 | } 53 | }); 54 | 55 | // If transform task failed and main process exit, 56 | // then doc process should be killed too 57 | process.on('exit', () => { 58 | child.kill(); 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/formatWinPath.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function formatWinPath(path) { 2 | return path.replace(/\\/g, '\\\\'); 3 | }; 4 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/genDemoPages/extractCodePlugin.mts: -------------------------------------------------------------------------------- 1 | import visit from 'unist-util-visit'; 2 | import { getDemoFileInfo, getPageFileInfo } from '../remark/getFileInfo.js'; 3 | import checkCodeLang from '../remark/checkCodeLang.js'; 4 | import genDemoPages from '../remark/genDemoPages.js'; 5 | 6 | interface MdNode { 7 | type: string; 8 | meta: string; 9 | lang: string; 10 | value: string; 11 | } 12 | 13 | export default function getExtractCodePlugin(filepath: string, rootDir: string) { 14 | return function extractCodePlugin() { 15 | return async (ast) => { 16 | let demoIndex = 0; 17 | await visit(ast, 'code', (node) => { 18 | if (node.meta === 'preview') { 19 | const { lang } = node; 20 | checkCodeLang(lang); 21 | const { demoFilename, demoFilepath } = getDemoFileInfo({ 22 | rootDir, 23 | code: node.value, 24 | lang, 25 | filepath, 26 | index: demoIndex, 27 | }); 28 | const { pageFilename, pageFileCode } = getPageFileInfo({ rootDir, demoFilepath, demoFilename }); 29 | genDemoPages({ 30 | filepath, 31 | code: node.value, 32 | demoFilename, 33 | demoFilepath, 34 | pageFilename, 35 | pageFileCode, 36 | }); 37 | demoIndex += 1; 38 | } 39 | }); 40 | }; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/genDemoPages/index.mts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import * as directoryTree from 'directory-tree'; 3 | import type { DirectoryTree } from 'directory-tree'; 4 | import fse from 'fs-extra'; 5 | import { unified } from 'unified'; 6 | import getExtractCodePlugin from './extractCodePlugin.mjs'; 7 | import parse from 'remark-parse'; 8 | import stringify from 'remark-stringify'; 9 | 10 | function scanDocsDirectory(rootDir: string, docsPath: string): DirectoryTree | null { 11 | const docsDir = path.join(rootDir, docsPath); 12 | const tree = directoryTree.default(docsDir, { extensions: /\.mdx?$/, exclude: /node_modules/ }); 13 | return tree; 14 | } 15 | 16 | function extractCodeFromDocs(docsTree: DirectoryTree, rootDir: string): void { 17 | docsTree.children?.forEach((sub) => { 18 | if (sub.children) { 19 | extractCodeFromDocs(sub, rootDir); 20 | } else { 21 | const content = fse.readFileSync(sub.path, 'utf-8'); 22 | unified() 23 | .use(parse) 24 | .use(stringify) 25 | .use(getExtractCodePlugin(sub.path, rootDir)) 26 | .process(content); 27 | } 28 | }); 29 | } 30 | 31 | export default function genDemoPages(rootDir: string, docsPath: string): void { 32 | const docsTree = scanDocsDirectory(rootDir, docsPath); 33 | if (docsTree) { 34 | extractCodeFromDocs(docsTree, rootDir); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/index.mts: -------------------------------------------------------------------------------- 1 | import fse from 'fs-extra'; 2 | import { doc } from './doc.mjs'; 3 | import { configureDocusaurus } from './configureDocusaurus.mjs'; 4 | import genDemoPages from './genDemoPages/index.mjs'; 5 | import { DEFAULT_DEV_SERVER_PORT, DOCUSAURUS_DIR } from './constants.mjs'; 6 | import type { EnableConfig, PluginDocusaurusOptions } from './types.mjs'; 7 | import type { Plugin } from '@ice/pkg'; 8 | 9 | const defaultOptions: PluginDocusaurusOptions = { 10 | title: 'ICE PKG', 11 | url: 'https://your-docusaurus-test-site.com', 12 | baseUrl: '/', 13 | path: 'docs', 14 | favicon: 'https://img.alicdn.com/imgextra/i2/O1CN01jUf9ZP1aKwVvEc58W_!!6000000003312-73-tps-160-160.ico', 15 | navBarLogo: 'https://img.alicdn.com/imgextra/i1/O1CN01lZTSIX1j7xpjIQ3fJ_!!6000000004502-2-tps-160-160.png', 16 | navBarTitle: 'ICE PKG', 17 | port: undefined, 18 | defaultLocale: 'zh-Hans', 19 | locales: ['zh-Hans'], 20 | docsRouteBasePath: '/', 21 | pagePath: 'pages', 22 | pageRouteBasePath: '/pages', 23 | }; 24 | 25 | const plugin: Plugin = (api, options: PluginDocusaurusOptions = {}) => { 26 | const { onHook, context, getAllPlugin } = api; 27 | const { command, rootDir } = context; 28 | const { enable = true } = options; 29 | if (!checkPluginEnable(enable, command)) { 30 | return; 31 | } 32 | 33 | fse.removeSync(DOCUSAURUS_DIR); 34 | 35 | const configuredPlugins = getAllPlugin(); 36 | const pluginOptions = { 37 | ...defaultOptions, 38 | ...options, 39 | configuredPlugins, 40 | }; 41 | 42 | configureDevServerPort(pluginOptions); 43 | 44 | configureDocusaurus(rootDir, pluginOptions); 45 | 46 | onHook(`before.${command}.run`, async () => { 47 | if (command === 'build') { 48 | // Pages must be generated before build 49 | // because remark plugin of docusaurus-plugin-content-docs works after docusaurus-plugin-content-pages reads the pages dir 50 | genDemoPages(rootDir, pluginOptions.path); 51 | } 52 | await doc(api, pluginOptions); 53 | }); 54 | }; 55 | 56 | const checkPluginEnable = (enable: boolean | EnableConfig, command: string): boolean => { 57 | if (typeof enable === 'boolean') { 58 | if (!enable) { 59 | return false; 60 | } 61 | } else if (!enable[command]) { 62 | return false; 63 | } 64 | return true; 65 | }; 66 | 67 | function configureDevServerPort(options: PluginDocusaurusOptions) { 68 | // Port from environment variable is preferred. 69 | if (process.env.PORT) { 70 | const envPort = parseInt(process.env.PORT, 10); 71 | if (typeof envPort === 'number' && !isNaN(envPort) && envPort > 0) { 72 | options.port = envPort; 73 | return; 74 | } 75 | } 76 | if (!options.port) { 77 | options.port = DEFAULT_DEV_SERVER_PORT; // The default port. 78 | } 79 | } 80 | 81 | export default plugin; 82 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/remark/checkCodeLang.js: -------------------------------------------------------------------------------- 1 | function checkCodeLang(lang) { 2 | if (!['tsx', 'jsx'].includes(lang)) { 3 | throw new Error(` 4 | Found code block with lang ${lang}.\n\ 5 | ${lang} is not supported in code preview. 6 | `); 7 | } 8 | } 9 | 10 | module.exports = checkCodeLang; 11 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/remark/extractCode.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const visit = require('unist-util-visit'); 3 | const checkCodeLang = require('./checkCodeLang.js'); 4 | const { getDemoFileInfo, getPageFileInfo } = require('./getFileInfo.js'); 5 | const genDemoPages = require('./genDemoPages.js'); 6 | const formatWinPath = require('../formatWinPath.cjs'); 7 | 8 | const rootDir = process.cwd(); 9 | const previewerComponentPath = formatWinPath(path.join(__dirname, '../Previewer/index.js')); 10 | 11 | const escapeCode = (code) => { 12 | return (code || '').replace(/`/g, '`').replace(/\$/g, '$'); 13 | }; 14 | 15 | /** 16 | * Remark Plugin to extract codeBlock & rendered as component 17 | * @type {import('unified').Plugin} 18 | * @param {options: { mobilePreview: boolean; baseUrl: string; mobilePreviewUrlParams: string; }} 19 | */ 20 | const extractCodePlugin = (options) => { 21 | const { mobilePreview = false, baseUrl = '/', mobilePreviewUrlParams = '' } = options; 22 | 23 | const transformer = (ast, vfile) => { 24 | const demosMeta = []; 25 | let demoIndex = 0; 26 | visit(ast, 'code', (node, index) => { 27 | if (node.meta === 'preview') { 28 | const { lang } = node; 29 | checkCodeLang(lang); 30 | const { demoFilename, demoFilepath } = getDemoFileInfo({ 31 | rootDir, 32 | filepath: vfile.path, 33 | lang, 34 | code: node.value, 35 | index: demoIndex, 36 | }); 37 | const { pageFilename, pageFileCode } = getPageFileInfo({ 38 | rootDir, 39 | demoFilepath, 40 | demoFilename, 41 | }); 42 | genDemoPages({ 43 | filepath: vfile.path, 44 | code: node.value, 45 | demoFilename, 46 | demoFilepath, 47 | pageFilename, 48 | pageFileCode, 49 | }); 50 | 51 | demosMeta.push({ 52 | code: node.value, 53 | idx: index, 54 | demoFilename, 55 | demoFilepath, 56 | url: `${path.join(baseUrl.startsWith('/') ? '' : '/', baseUrl, 'demos', demoFilename, '/')}?${mobilePreviewUrlParams}`, 57 | }); 58 | demoIndex += 1; 59 | } 60 | }); 61 | 62 | if (demosMeta.length) { 63 | // Remove original component and insert custom component 64 | for (let m = 0; m < demosMeta.length; ++m) { 65 | const { idx, code, demoFilepath, demoFilename, url } = demosMeta[m]; 66 | ast.children.splice(idx, 1, { 67 | type: 'jsx', 68 | value: ` 69 | 70 | 71 | {() => { 72 | const ${demoFilename} = require('${formatWinPath(demoFilepath)}').default; 73 | return <${demoFilename} />; 74 | }} 75 | 76 | `, 77 | }); 78 | } 79 | 80 | // Import ahead. 81 | ast.children.unshift({ 82 | type: 'import', 83 | value: `import Previewer from '${previewerComponentPath}';`, 84 | }); 85 | 86 | // Import ahead. 87 | ast.children.unshift({ 88 | type: 'import', 89 | value: "import BrowserOnly from '@docusaurus/BrowserOnly';", 90 | }); 91 | } 92 | }; 93 | 94 | return transformer; 95 | }; 96 | 97 | module.exports = extractCodePlugin; 98 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/remark/fixedFilename.js: -------------------------------------------------------------------------------- 1 | const DEMO_PREFIX = 'IcePkgDemo'; 2 | 3 | /** Use docPath */ 4 | const fixedFilename = (filepath, rootDir, index) => { 5 | try { 6 | const componentName = filepath.replace(rootDir, '').replace(/\.mdx?$/, ''); 7 | return `${DEMO_PREFIX}${componentName}_${index}`.replace(/[/\\-]/g, '_'); 8 | } catch (e) { 9 | return ''; 10 | } 11 | }; 12 | 13 | module.exports = fixedFilename; 14 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/remark/genDemoPages.js: -------------------------------------------------------------------------------- 1 | const fse = require('fs-extra'); 2 | const resolveImports = require('./resolveImports'); 3 | 4 | const genDemoPages = ({ filepath, code, demoFilepath, pageFilename, pageFileCode }) => { 5 | const resolvedCode = resolveImports(code, filepath); 6 | fse.writeFileSync(demoFilepath, resolvedCode, 'utf-8'); 7 | fse.writeFileSync(pageFilename, pageFileCode, 'utf-8'); 8 | }; 9 | 10 | module.exports = genDemoPages; 11 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/remark/getFileInfo.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fse = require('fs-extra'); 3 | const uniqueFilename = require('./uniqueFilename.js'); 4 | const fixedFilename = require('./fixedFilename.js'); 5 | const formatWinPath = require('../formatWinPath.cjs'); 6 | 7 | const DOCUSAURUS_DIR = '.docusaurus'; 8 | 9 | const getDemoFileInfo = ({ rootDir, code, lang, filepath, index }) => { 10 | const demoDir = path.join(rootDir, DOCUSAURUS_DIR, 'demos'); 11 | fse.ensureDirSync(demoDir); 12 | const demoFilename = fixedFilename(filepath, rootDir, index) || uniqueFilename(code); 13 | const demoFilepath = formatWinPath(path.join(demoDir, `${demoFilename}.${lang}`)); 14 | return { demoFilename, demoFilepath }; 15 | }; 16 | 17 | const getPageFileInfo = ({ rootDir, demoFilepath, demoFilename }) => { 18 | const pagesDir = path.join(rootDir, DOCUSAURUS_DIR, 'demo-pages'); 19 | fse.ensureDirSync(pagesDir); 20 | const pageFilename = formatWinPath(path.join(pagesDir, `${demoFilename}.jsx`)); 21 | const pageFileCode = ` 22 | import BrowserOnly from '@docusaurus/BrowserOnly'; 23 | export default () => { 24 | return ( 25 | 26 | {() => { 27 | const Demo = require('${formatWinPath(demoFilepath)}').default; 28 | return 29 | }} 30 | 31 | ) 32 | } 33 | `; 34 | return { pageFilename, pageFileCode }; 35 | }; 36 | 37 | module.exports = { 38 | getDemoFileInfo, 39 | getPageFileInfo, 40 | }; 41 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/remark/resolveImports.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const formatWinPath = require('../formatWinPath.cjs'); 3 | 4 | const importRegex = /import\s+?(?:(?:(?:[\w*\s{},]*)\s+from\s+?)|)(?:(?:"(.*?)")|(?:'(.*?)'))[\s]*?(?:;|$|)/; 5 | 6 | const resolveImports = (code, filePath) => { 7 | let _code = code; 8 | const matches = code.replace(/\n$/, '').match(new RegExp(importRegex, 'g')); 9 | let importedBrowserOnly = false; 10 | 11 | if (matches) { 12 | const imports = matches 13 | .map((matchStr) => { 14 | const [, singleQuoteImporter, doubleQuoteImporter] = matchStr.match(importRegex); 15 | const importer = singleQuoteImporter || doubleQuoteImporter; 16 | 17 | // If `import xx from '.'` 18 | return importer === '.' ? './src' : importer; 19 | }) 20 | .filter(Boolean); 21 | 22 | const fileDirname = path.dirname(filePath); 23 | 24 | let importedReact = false; 25 | imports.forEach((i) => { 26 | if (i[0] === '.') { 27 | _code = _code.replace(i, formatWinPath(path.resolve(fileDirname, i))); 28 | } 29 | 30 | if (i === 'react') { 31 | importedReact = true; 32 | } 33 | 34 | if (i === '@docusaurus/BrowserOnly') { 35 | importedBrowserOnly = true; 36 | } 37 | }); 38 | 39 | // If import React already 40 | if (!importedReact) { 41 | _code = `import React from 'react'; \n${_code}`; 42 | } 43 | } 44 | 45 | if (_code.includes('') && !importedBrowserOnly) { 46 | _code = `import BrowserOnly from '@docusaurus/BrowserOnly'; \n${_code}`; 47 | } 48 | 49 | return _code; 50 | }; 51 | 52 | module.exports = resolveImports; 53 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/remark/uniqueFilename.js: -------------------------------------------------------------------------------- 1 | const { createHash } = require('crypto'); 2 | 3 | const DEMO_PREFIX = 'IcePkgDemo'; 4 | 5 | /** Use the md5 value of docPath */ 6 | const uniqueFilename = (code) => { 7 | const hash = createHash('md5'); 8 | hash.update(code); 9 | const hashValue = hash.digest('hex'); 10 | 11 | return `${DEMO_PREFIX}_${hashValue.slice(0, 6)}`; 12 | }; 13 | 14 | module.exports = uniqueFilename; 15 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/template/docusaurus.hbs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | const extractCode = require('@ice/pkg-plugin-docusaurus/remark/extractCode'); 4 | 5 | /** @type {import('@docusaurus/types').Config} */ 6 | const config = { 7 | title: '{{title}}', 8 | tagline: 'ICE Component Cool', 9 | url: '{{url}}', 10 | baseUrl: '{{baseUrl}}', 11 | i18n: { 12 | defaultLocale: '{{defaultLocale}}', 13 | locales: [{{#each locales}}'{{this}}',{{/each}}], 14 | }, 15 | {{#if onBrokenLinks}} 16 | onBrokenLinks: '{{{onBrokenLinks}}}', 17 | {{/if}} 18 | onBrokenMarkdownLinks: 'warn', 19 | favicon: '{{favicon}}', 20 | {{#unless haveStaticFiles}} 21 | staticDirectories: [], 22 | {{/unless}} 23 | 24 | plugins: [ 25 | require.resolve('@ice/pkg-plugin-docusaurus/plugin.js'), 26 | [ 27 | '{{docusaurusPluginContentPagesPath}}', 28 | { 29 | path: '{{pagePath}}', 30 | routeBasePath: '{{pageRouteBasePath}}', 31 | } 32 | ], 33 | {{#each plugins}}require.resolve('{{this}}'),{{/each}} 34 | ], 35 | 36 | presets: [ 37 | [ 38 | '{{docusaurusClassPresetPath}}', 39 | /** @type {import('@docusaurus/preset-classic').Options} */ 40 | ({ 41 | docs: { 42 | {{#if path}} 43 | path: '{{{path}}}', 44 | {{/if}} 45 | {{#if sidebarItemsGenerator}} 46 | sidebarItemsGenerator: {{{sidebarItemsGenerator}}}, 47 | {{/if}} 48 | remarkPlugins: [ 49 | [extractCode, { mobilePreview: {{mobilePreview}}, baseUrl: '{{baseUrl}}', mobilePreviewUrlParams: '{{mobilePreviewUrlParams}}' }], 50 | {{#if remarkPlugins}} 51 | {{#each remarkPlugins}} 52 | {{{this}}}, 53 | {{/each}} 54 | {{/if}} 55 | ], 56 | exclude: [ 57 | '**/_*.{js,jsx,ts,tsx,md,mdx}', 58 | '**/_*/**', 59 | '**/*.test.{js,jsx,ts,tsx}', 60 | '**/__tests__/**', 61 | {{#if exclude}} 62 | {{#each exclude}} 63 | '{{this}}' 64 | {{/each}} 65 | {{/if}} 66 | ], 67 | routeBasePath: '{{docsRouteBasePath}}', 68 | }, 69 | 70 | theme: { 71 | customCss: require.resolve('@ice/pkg-plugin-docusaurus/css/custom.css'), 72 | }, 73 | 74 | // For demo preview in mobile mode. 75 | pages: { 76 | path: '.docusaurus/demo-pages', 77 | routeBasePath: '/demos', 78 | id: 'demo-pages' 79 | } 80 | }), 81 | ], 82 | ], 83 | 84 | themeConfig: 85 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 86 | ({ 87 | navbar: { 88 | title: '{{navBarTitle}}', 89 | logo: { 90 | src: '{{navBarLogo}}', 91 | }, 92 | {{#if navBarItems}} 93 | items: {{{navBarItems}}} 94 | {{/if}} 95 | }, 96 | prism: { 97 | theme: require('{{prismReactRendererPath}}/themes/github'), 98 | darkTheme: require('{{prismReactRendererPath}}/themes/dracula'), 99 | }, 100 | docs: { 101 | sidebar: { 102 | autoCollapseCategories: true, 103 | }, 104 | }, 105 | }), 106 | }; 107 | 108 | module.exports = config; 109 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/types.mts: -------------------------------------------------------------------------------- 1 | export interface EnableConfig { 2 | start?: boolean; 3 | build?: boolean; 4 | } 5 | 6 | export interface PluginDocusaurusOptions { 7 | /** 8 | * Enable doc build 9 | */ 10 | enable?: boolean | EnableConfig; 11 | /** 12 | * Docs path. 13 | */ 14 | path?: string; 15 | /** 16 | * Exclude the md file with glob patterns. For example: ['node_modules/**'] 17 | */ 18 | exclude?: string[]; 19 | /** 20 | * The behavior of Docusaurus when it detects any broken link. 21 | * @default 'throw' 22 | */ 23 | onBrokenLinks?: 'ignore' | 'log' | 'warn' | 'throw'; 24 | /** 25 | * Title for your doc. 26 | */ 27 | title?: string; 28 | /** 29 | * URL for your website. This can also be considered the top-level hostname. 30 | */ 31 | url?: string; 32 | /** 33 | * Base URL for your site. 34 | */ 35 | baseUrl?: string; 36 | /** 37 | * Path to your site favicon. 38 | */ 39 | favicon?: string; 40 | /** 41 | * Path to your sidebar logo. 42 | */ 43 | navBarLogo?: string; 44 | /** 45 | * Path to your sidebar title. 46 | */ 47 | navBarTitle?: string; 48 | /** 49 | * A list of navbar items. 50 | */ 51 | navBarItems?: string | Array<{[key: string]: string}>; 52 | /** 53 | * Specify a host to use. 54 | */ 55 | host?: string; 56 | /** 57 | * DevServer port for your dev server. 58 | */ 59 | port?: number; 60 | 61 | /** 62 | * Function used to replace the sidebar items. 63 | */ 64 | sidebarItemsGenerator?: Function; 65 | /** 66 | * Whether preview components of mobile styles 67 | */ 68 | mobilePreview?: boolean; 69 | /** 70 | * When mobilePreview is true, the url parameters of the mobile preview page 71 | */ 72 | mobilePreviewUrlParams?: string; 73 | 74 | /** 75 | * Default locale that does not have its name in the base URL 76 | */ 77 | defaultLocale?: string; 78 | 79 | /** 80 | * List of locales deployed on your site. Must contain defaultLocale. 81 | */ 82 | locales?: string[]; 83 | 84 | /** 85 | * Docusaurus output dir. 86 | */ 87 | outputDir?: string; 88 | /** 89 | * Docusaurus docs page base route path. 90 | * @default '/' 91 | */ 92 | docsRouteBasePath?: string; 93 | 94 | /** 95 | * Docusaurus page local path. 96 | * @default 'pages' 97 | */ 98 | pagePath?: string; 99 | 100 | /** 101 | * Docusaurus page base route path. 102 | * @default '/pages' 103 | */ 104 | pageRouteBasePath?: string; 105 | 106 | /** 107 | * Plugins of Docusaurus. 108 | */ 109 | plugins?: Array; 110 | } 111 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.module.css' { 2 | const classes: { [key: string]: string }; 3 | export default classes; 4 | } 5 | // Docusaurus inner component, use `@theme-original` to alias to pre-swizzled components 6 | declare module '@theme-original/CodeBlock'; 7 | -------------------------------------------------------------------------------- /packages/plugin-docusaurus/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "src", 6 | "outDir": "lib" 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/plugin-jsx-plus/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.3 4 | 5 | ### Patch Changes 6 | 7 | - 86b56d4: add missing babel-runtime-jsx-plus 8 | 9 | ## 1.0.2 10 | 11 | ### Patch Changes 12 | 13 | - e9a8a4b: chore: update fields in package.json 14 | 15 | ## 1.0.1 16 | 17 | ### Patch Changes 18 | 19 | - 061db4f: fix: jsx-plus syntax can't be used in rax components 20 | 21 | ## 1.0.1-beta.1 22 | 23 | ### Patch Changes 24 | 25 | - chore: release beta version 26 | 27 | ## 1.0.1-beta.0 28 | 29 | ### Patch Changes 30 | 31 | - 061db4f: fix: jsx-plus syntax can't be used in rax components 32 | 33 | ## 1.0.0 34 | 35 | ### Major Changes 36 | 37 | - 11a2dd9: feat: init plugin 38 | 39 | ## 1.0.0-beta.0 40 | 41 | ### Major Changes 42 | 43 | - 11a2dd9: feat: init plugin 44 | -------------------------------------------------------------------------------- /packages/plugin-jsx-plus/README.md: -------------------------------------------------------------------------------- 1 | # @ice/pkg-plugin-jsx-plus 2 | 3 | This plugin adds support for JSX+ syntax for ICE PKG. 4 | 5 | ## Definition of JSX Plus 6 | 7 | https://github.com/jsx-plus/jsx-plus 8 | 9 | ## Usage 10 | 11 | ```bash 12 | $ npm i babel-runtime-jsx-plus --save 13 | $ npm i @ice/pkg-plugin-jsx-plus --save-dev 14 | ``` 15 | 16 | ```ts 17 | // build.config.mts 18 | import { defineConfig } from '@ice/pkg'; 19 | 20 | export default defineConfig({ 21 | plugins: [ 22 | ['@ice/pkg-plugin-jsx-plus'], 23 | ], 24 | }) 25 | ``` 26 | 27 | For more detail, please see https://pkg.ice.work/jsx-plus 28 | -------------------------------------------------------------------------------- /packages/plugin-jsx-plus/build.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@ice/pkg'; 2 | 3 | export default defineConfig({ 4 | transform: { 5 | formats: ['es2017'], 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/plugin-jsx-plus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ice/pkg-plugin-jsx-plus", 3 | "version": "1.0.3", 4 | "description": "ICE PKG plugin for jsx-plus", 5 | "main": "es2017/index.js", 6 | "type": "module", 7 | "exports": { 8 | ".": { 9 | "type": "./es2017/index.d.ts", 10 | "import": "./es2017/index.js", 11 | "default": "./es2017/index.js" 12 | } 13 | }, 14 | "files": [ 15 | "es2017", 16 | "!es2017/**/*.map" 17 | ], 18 | "engines": { 19 | "node": ">=16.14.0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/ice-lab/icepkg.git", 24 | "directory": "packages/plugin-jsx-plus" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/ice-lab/icepkg/issues" 28 | }, 29 | "homepage": "https://pkg.ice.work", 30 | "license": "MIT", 31 | "scripts": { 32 | "watch": "ice-pkg start", 33 | "build": "ice-pkg build" 34 | }, 35 | "dependencies": { 36 | "babel-plugin-transform-jsx-class": "^0.1.3", 37 | "babel-plugin-transform-jsx-condition": "^0.1.2", 38 | "babel-plugin-transform-jsx-fragment": "^0.1.4", 39 | "babel-plugin-transform-jsx-list": "^0.1.2", 40 | "babel-plugin-transform-jsx-memo": "^0.1.4", 41 | "babel-plugin-transform-jsx-slot": "^0.1.2", 42 | "babel-runtime-jsx-plus": "^0.1.5" 43 | }, 44 | "devDependencies": { 45 | "@ice/pkg": "^1.5.14" 46 | }, 47 | "publishConfig": { 48 | "access": "public", 49 | "registry": "https://registry.npmjs.org/" 50 | } 51 | } -------------------------------------------------------------------------------- /packages/plugin-jsx-plus/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from '@ice/pkg'; 2 | 3 | interface PluginOptions { 4 | moduleName?: 'react' | 'rax'; 5 | } 6 | 7 | const defaultPluginOptions: PluginOptions = { 8 | moduleName: 'react', 9 | }; 10 | 11 | const plugin: Plugin = (api, rawOptions?: PluginOptions) => { 12 | const { onGetConfig } = api; 13 | const pluginOptions = Object.assign({}, defaultPluginOptions, rawOptions); 14 | const babelPlugins = [ 15 | 'babel-plugin-transform-jsx-list', 16 | 'babel-plugin-transform-jsx-condition', 17 | 'babel-plugin-transform-jsx-memo', 18 | 'babel-plugin-transform-jsx-slot', 19 | ['babel-plugin-transform-jsx-fragment', { moduleName: pluginOptions.moduleName }], 20 | 'babel-plugin-transform-jsx-class', 21 | ]; 22 | 23 | onGetConfig((config) => { 24 | config.babelPlugins ??= []; 25 | config.babelPlugins.push(...babelPlugins); 26 | }); 27 | }; 28 | 29 | export default plugin; 30 | -------------------------------------------------------------------------------- /packages/plugin-jsx-plus/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "src", 6 | "outDir": "lib" 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/plugin-rax-component/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.2 4 | 5 | ### Patch Changes 6 | 7 | - 8cd4c98: chore: use Plugin type instead of deprecated PkgPlugin type 8 | 9 | ## 1.1.1 10 | 11 | ### Patch Changes 12 | 13 | - e9a8a4b: chore: update fields in package.json 14 | 15 | ## 1.1.0 16 | 17 | - [feat] external helpers 18 | 19 | ## 1.0.0 20 | 21 | - init 22 | -------------------------------------------------------------------------------- /packages/plugin-rax-component/README.md: -------------------------------------------------------------------------------- 1 | # @ice/pkg-plugin-rax-component 2 | 3 | This plugin adds support for Rax component for ICE PKG. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | $ npm i @ice/pkg-plugin-rax-component --save-dev 9 | ``` 10 | 11 | ```ts 12 | // build.config.mts 13 | import { defineConfig } from '@ice/pkg'; 14 | 15 | export default defineConfig({ 16 | plugins: [ 17 | ['@ice/pkg-plugin-rax-component'], 18 | ], 19 | }) 20 | ``` 21 | 22 | For more detail, please see https://pkg.ice.work 23 | -------------------------------------------------------------------------------- /packages/plugin-rax-component/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ice/pkg-plugin-rax-component", 3 | "version": "1.1.2", 4 | "description": "Rax component plugin for @ice/pkg", 5 | "main": "lib/index.js", 6 | "type": "module", 7 | "exports": "./lib/index.js", 8 | "files": [ 9 | "lib", 10 | "!lib/**/*.map" 11 | ], 12 | "engines": { 13 | "node": ">=16.14.0" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/ice-lab/icepkg.git", 18 | "directory": "packages/plugin-rax-component" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/ice-lab/icepkg/issues" 22 | }, 23 | "homepage": "https://pkg.ice.work", 24 | "license": "MIT", 25 | "scripts": { 26 | "build": "rm -rf lib && tsc", 27 | "watch": "tsc -w" 28 | }, 29 | "dependencies": { 30 | "rax-compat": "^0.1.5" 31 | }, 32 | "devDependencies": { 33 | "@ice/pkg": "^1.5.5", 34 | "react": "^18.2.0", 35 | "react-dom": "^18.2.0", 36 | "typescript": "catalog:" 37 | }, 38 | "publishConfig": { 39 | "access": "public", 40 | "registry": "https://registry.npmjs.org/" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/plugin-rax-component/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from '@ice/pkg'; 2 | 3 | const plugin: Plugin = (api) => { 4 | const { onGetConfig } = api; 5 | 6 | onGetConfig((config) => { 7 | return { 8 | ...config, 9 | swcCompileOptions: { 10 | jsc: { 11 | transform: { 12 | react: { 13 | // Use classic jsx transform, see https://swc.rs/docs/configuration/compilation#jsctransformreactruntime 14 | runtime: 'classic', 15 | pragma: 'createElement', 16 | pragmaFrag: 'Fragment', 17 | }, 18 | legacyDecorator: true, 19 | }, 20 | externalHelpers: true, 21 | loose: false, // No recommend 22 | }, 23 | }, 24 | }; 25 | }); 26 | }; 27 | 28 | export default plugin; 29 | -------------------------------------------------------------------------------- /packages/plugin-rax-component/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "src", 6 | "outDir": "lib" 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/remark-react-docgen-docusaurus/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @ice/remark-react-docgen-docusaurus 2 | 3 | ## 1.0.1 4 | 5 | ### Patch Changes 6 | 7 | - 7f37d7f: chore: upgrade @swc/helpers version to ^0.5.1 8 | 9 | ## 1.0.0 10 | 11 | ### Major Changes 12 | 13 | - 2b01e99: feat: init plugin 14 | -------------------------------------------------------------------------------- /packages/remark-react-docgen-docusaurus/README.md: -------------------------------------------------------------------------------- 1 | # @ice/remark-react-docgen-docusaurus 2 | 3 | A remark plugin(based on [react-docgen](https://github.com/reactjs/react-docgen/tree/5.x)) to automatic generate react component docs in [Docusaurus](https://docusaurus.io/) or [ICE PKG](http://pkg.ice.work/) 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ npm i @ice/remark-react-docgen-docusaurus --save-dev 9 | ``` 10 | 11 | ## Usage 12 | 13 | First, we need to add the plugin to the config: 14 | 15 | If you use it in Docusaurus, add the plugin to the `docusaurus.config.js`: 16 | 17 | ```js 18 | // docusaurus.config.js 19 | const remarkReactDocgen = require('@ice/remark-react-docgen-docusaurus'); 20 | 21 | module.exports = { 22 | presets: [ 23 | [ 24 | '@docusaurus/preset-classic', 25 | { 26 | docs: { 27 | remarkPlugins: [remarkReactDocgen], 28 | }, 29 | }, 30 | ], 31 | ], 32 | } 33 | ``` 34 | 35 | If you use it in ICE PKG, add the plugin to the `build.config.mts`: 36 | 37 | ```ts 38 | // build.config.mts 39 | import { defineConfig } from '@ice/pkg'; 40 | 41 | export default defineConfig({ 42 | plugins: [ 43 | [ 44 | '@ice/pkg-plugin-docusaurus', 45 | { 46 | remarkPlugins: [ 47 | "require('@ice/remark-react-docgen-docusaurus')", 48 | ], 49 | }, 50 | ], 51 | ], 52 | }); 53 | ``` 54 | 55 | Add the `` component to the markdown: 56 | 57 | ```md 58 | ## API 59 | 60 | 61 | ``` 62 | 63 | > the path is the path of the React component 64 | 65 | Finally, you can run the command `npm run start`, you can see the following: 66 | 67 | image 68 | -------------------------------------------------------------------------------- /packages/remark-react-docgen-docusaurus/build.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@ice/pkg'; 2 | 3 | export default defineConfig({ 4 | transform: { 5 | formats: ['cjs'], 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/remark-react-docgen-docusaurus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ice/remark-react-docgen-docusaurus", 3 | "version": "1.0.1", 4 | "description": "A remark plugin to generate react components doc with docusaurus.", 5 | "files": [ 6 | "cjs" 7 | ], 8 | "main": "cjs/index.js", 9 | "types": "cjs/index.d.ts", 10 | "sideEffects": false, 11 | "scripts": { 12 | "watch": "ice-pkg start", 13 | "build": "ice-pkg build" 14 | }, 15 | "keywords": [ 16 | "remark", 17 | "react-docgen" 18 | ], 19 | "dependencies": { 20 | "@swc/helpers": "^0.5.1", 21 | "fs-extra": "^10.1.0", 22 | "react-docgen": "^5.2.3", 23 | "mdast-builder": "^1.1.1", 24 | "rehype-stringify": "^8.0.0", 25 | "remark-rehype": "^8.1.0", 26 | "remark-parse": "^9.0.0", 27 | "remark-gfm": "^1.0.0", 28 | "remark-stringify": "^8.0.0", 29 | "unified": "^9.2.2", 30 | "unist-util-visit": "^2.0.3" 31 | }, 32 | "devDependencies": { 33 | "@ice/pkg": "^1.5.6", 34 | "@types/unist": "^2.0.6", 35 | "vfile": "^4.0.0" 36 | }, 37 | "publishConfig": { 38 | "access": "public" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/ice-lab/icepkg.git", 43 | "directory": "packages/remark-react-docgen" 44 | }, 45 | "bugs": { 46 | "url": "https://github.com/ice-lab/icepkg/issues" 47 | }, 48 | "homepage": "https://pkg.ice.work", 49 | "license": "MIT" 50 | } -------------------------------------------------------------------------------- /packages/remark-react-docgen-docusaurus/src/generateComponentInfo.ts: -------------------------------------------------------------------------------- 1 | import * as docgen from 'react-docgen'; 2 | import fse = require('fs-extra'); 3 | 4 | export default function generateComponentInfo(componentPath: string) { 5 | return docgen.parse(fse.readFileSync(componentPath, 'utf-8')); 6 | } 7 | -------------------------------------------------------------------------------- /packages/remark-react-docgen-docusaurus/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports */ 2 | import visit = require('unist-util-visit'); 3 | import renderComponentPropsToTable from './renderers/renderComponentPropsToTable'; 4 | 5 | import type { Plugin } from 'unified'; 6 | 7 | const remarkReactDocgenPlugin: Plugin = () => { 8 | return (tree, vfile) => { 9 | visit(tree, (node, index, parent) => { 10 | if (node.type === 'jsx' && (node as any).value) { 11 | const componentPropsTable = renderComponentPropsToTable(node, vfile); 12 | if (componentPropsTable) { 13 | parent.children.splice(index, 1, { 14 | type: 'html', 15 | value: componentPropsTable, 16 | } as any); 17 | } 18 | } 19 | }); 20 | }; 21 | }; 22 | 23 | module.exports = remarkReactDocgenPlugin; 24 | -------------------------------------------------------------------------------- /packages/remark-react-docgen-docusaurus/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "target": "ESNext", 5 | "moduleResolution": "node", 6 | "lib": ["ESNext", "DOM"], 7 | "sourceMap": true, 8 | "allowJs": true, 9 | "noUnusedLocals": true, 10 | "noImplicitReturns": true, 11 | "noImplicitThis": true, 12 | "noImplicitAny": true, 13 | "skipLibCheck": true, 14 | "resolveJsonModule": true 15 | }, 16 | "include": ["src", "typings.d.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/remark-react-docgen-docusaurus/typings.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module 'react-docgen'; 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - examples/* 4 | - website 5 | - packages/pkg/tests/fixtures/* 6 | 7 | catalog: 8 | vitest: ^0.28.5 9 | '@vitest/coverage-c8': ^0.28.5 10 | typescript: ^4.9.4 11 | sass: ^1.58.3 12 | sass-loader: ^13.2.0 13 | -------------------------------------------------------------------------------- /scripts/publish.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import { existsSync, readdirSync } from 'fs'; 3 | import { join } from 'path'; 4 | import * as fse from 'fs-extra'; 5 | import * as axios from 'axios'; 6 | import { getVersions } from 'ice-npm-utils'; 7 | 8 | // Set by github actions 9 | const branchName = process.env.BRANCH_NAME; 10 | const rootDir = join(__dirname, '../'); 11 | const REGISTRY = 'https://registry.npmjs.org/'; 12 | 13 | // 依赖关系影响发布顺序 14 | const orderedPackages = [ 15 | 'ice-npm-utils', 16 | '@ice/pkg', 17 | '@ice/pkg-plugin-component', 18 | '@ice/pkg-plugin-rax-component', 19 | '@ice/pkg-plugin-docusaurus', 20 | ]; 21 | 22 | if (!branchName) { 23 | throw new Error('Only support publish in GitHub Actions env'); 24 | } 25 | 26 | (async () => { 27 | const packageDirs = getPackagesPaths(join(rootDir, 'packages')); 28 | const packages = packageDirs.map((pkgDir) => { 29 | const pkgData = fse.readJSONSync(join(pkgDir, 'package.json')); 30 | return { pkgDir, pkgData }; 31 | }).sort((a, b) => { 32 | const aIndex = orderedPackages.indexOf(a.pkgData.name); 33 | const bIndex = orderedPackages.indexOf(b.pkgData.name); 34 | return aIndex - bIndex; 35 | }); 36 | 37 | for (const pkgInfo of packages) { 38 | // eslint-disable-next-line no-await-in-loop 39 | await publishPackage(pkgInfo); 40 | } 41 | })().catch((err) => { 42 | console.error(err); 43 | process.exit(1); 44 | }); 45 | 46 | async function publishPackage({ pkgDir, pkgData }) { 47 | const { version, name } = pkgData; 48 | const npmTag = branchName === 'master' ? 'latest' : 'beta'; 49 | 50 | const versionExist = await checkVersionExist(name, version, REGISTRY); 51 | if (versionExist) { 52 | console.log(`${name}@${version} 已存在,无需发布。`); 53 | return; 54 | } 55 | 56 | const isProdVersion = /^\d+\.\d+\.\d+$/.test(version); 57 | if (branchName === 'master' && !isProdVersion) { 58 | throw new Error(`禁止在 master 分支发布非正式版本 ${version}`); 59 | } 60 | 61 | if (branchName !== 'master' && isProdVersion) { 62 | console.log(`非 master 分支 ${branchName},不发布正式版本 ${version}`); 63 | return; 64 | } 65 | 66 | console.log('start publish', version, npmTag); 67 | execSync('npm install', { 68 | cwd: pkgDir, 69 | stdio: 'inherit', 70 | }); 71 | execSync(`npm publish --tag ${npmTag}`, { 72 | cwd: pkgDir, 73 | stdio: 'inherit', 74 | }); 75 | 76 | console.log('start notify'); 77 | const response = await axios.default({ 78 | url: process.env.DING_WEBHOOK, 79 | method: 'post', 80 | headers: { 81 | 'Content-Type': 'application/json;charset=utf-8', 82 | }, 83 | data: { 84 | msgtype: 'markdown', 85 | markdown: { 86 | title: `${name}@${version} 发布成功`, 87 | text: `${name}@${version} 发布成功`, 88 | }, 89 | }, 90 | }); 91 | console.log('notify success', response.data); 92 | } 93 | 94 | async function checkVersionExist( 95 | name: string, 96 | version: string, 97 | registry?: string, 98 | ): Promise { 99 | try { 100 | const versions = await getVersions(name, registry); 101 | return versions.indexOf(version) !== -1; 102 | } catch (err) { 103 | console.error('checkVersionExist error', err); 104 | return false; 105 | } 106 | } 107 | 108 | function getPackagesPaths(dir) { 109 | const packagesPaths: string[] = readdirSync(dir).map((dirname) => { 110 | return join(dir, dirname); 111 | }).filter((dirpath) => { 112 | return existsSync(join(dirpath, 'package.json')); 113 | }); 114 | 115 | return packagesPaths; 116 | } 117 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "jsx": "react", 7 | "experimentalDecorators": true, 8 | "declaration": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "allowSyntheticDefaultImports": true, 13 | "esModuleInterop": true, 14 | "paths": { 15 | "example-pkg-react-component": ["./examples/react-component/src"], 16 | "example-pkg-react-component/*": ["./examples/react-component/src/*"], 17 | "example-pkg-react-multi-components": ["./examples/react-multi-components/src"], 18 | "example-pkg-react-multi-components/*": ["./examples/react-multi-components/src/*"], 19 | "example-rax-component": ["./examples/rax-component/src"], 20 | "example-rax-component/*": ["./examples/rax-component/src/*"] 21 | } 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { configDefaults, defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | exclude: [...configDefaults.exclude, 'examples/**'], 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/docs/faq.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | ## 为什么需要依赖 `@ice/jsx-runtime` 4 | 5 | `@ice/pkg` 在 v1.5.0 版本以后,为支持能在内联样式中使用 rpx 单位,组件需要依赖 `@ice/jsx-runtime` 才能正常渲染。 6 | 7 | ```jsx 8 | const Home = () => { 9 | return ( 10 |
Home
11 | ) 12 | } 13 | ``` 14 | 15 | 因此,Transform 模式下,需要 `@ice/jsx-runtime` 作为项目的 `dependencies`;Bundle 模式下需要把 `@ice/jsx-runtime` 作为项目的 `devDependencies` 16 | 17 | ```bash 18 | # Transform 模式 19 | $ npm i @ice/jsx-runtime --save 20 | # Bundle 模式 21 | $ npm i @ice/jsx-runtime --save-dev 22 | ``` 23 | 24 | ## 为什么需要依赖 `@swc/helpers` 25 | 26 | :::caution 27 | `@ice/pkg` 1.5.6 版本升级 `@swc/core` 到 1.3.55 版本,需将项目中的 `@swc/helpers` 同时升级到 ^0.5.0 版本 28 | ::: 29 | 30 | Transform 模式的产物代码中可能依赖一些 helper 函数用以支持目标环境。ICE PKG 默认将这些 helper 函数统一从 `@swc/helpers` 中导出使用,以减小产物代码体积。因此,当你的产物代码中引用了 `@swc/helpers` 时,请务必将 `@swc/helpers` 作为项目的 `dependencies`。 31 | 32 | ```bash 33 | $ npm i @swc/helpers --save 34 | ``` 35 | 36 | ## `Error: Can't resolve 'sass-loader' in ...` 37 | 38 | 出现这个报错的原因是在使用 docusaurus 插件时,docusaurus 无法正常 resolve 到 `sass-loader` 依赖。请更新 `@ice/pkg-plugin-docusaurus` 到最新版本。 39 | -------------------------------------------------------------------------------- /website/docs/guide/Button.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | function Button() { 4 | const [count, setCount] = useState(0); 5 | return ( 6 |
7 | 8 |

{count}

9 |
10 | ); 11 | } 12 | 13 | export default Button; 14 | -------------------------------------------------------------------------------- /website/docs/guide/jsx-plus.md: -------------------------------------------------------------------------------- 1 | # JSX+ 2 | 3 | 该插件支持了一种 JSX 扩展语法 JSX+,它能帮助业务开发者更爽更快地书写 JSX。JSX+ 不是一种新的概念,它是 JSX 基础上的扩展指令概念。 4 | 5 | ## 为什么需要 JSX+ 6 | 7 | - JSX 虽然语法灵活,但是大量的花括号 + JS 语法导致了上下文切换和代码可读性的下降,JSX+ 的指令很好的解决了这个问题 8 | - JSX 本质是 JS 表达式,在运行时阶段才可以计算出真实的 DOM 结构,JSX+ 引入了一部分静态模板特性可以满足编译优化 9 | - 不新创造实体,指令在社区中是已经被广泛接受的概念,对开发者更友好,语法糖的表达更简单 10 | - 统一一套 JSX+ 类似概念的语法规范,减少已存在和潜在的重复建设 11 | 12 | ## 安装使用 13 | 14 | 首先需要安装依赖: 15 | 16 | ```bash 17 | $ npm i babel-runtime-jsx-plus --save 18 | $ npm i @ice/pkg-plugin-jsx-plus --save-dev 19 | ``` 20 | 21 | 然后把 jsx+ 插件加入到配置文件中: 22 | 23 | ```ts title="build.config.mts" 24 | import { defineConfig } from '@ice/pkg'; 25 | 26 | export default defineConfig({ 27 | plugins: [ 28 | ['@ice/pkg-plugin-jsx-plus'], 29 | ], 30 | }) 31 | ``` 32 | 33 | 然后你就可以使用更强大的 JSX+ 语法了: 34 | 35 | ```jsx 36 | function ExampleComponent(props) { 37 | const { isAdmin, dataSource } = props; 38 | 39 | return ( 40 |
41 |
admin
42 |
guest
43 | 44 |
45 | {item.name} 46 |
47 |
48 | ); 49 | } 50 | ``` 51 | 52 | ## 指令语法 53 | 54 | ### 条件判断 55 | 56 | ```jsx 57 |
Hello
58 |
World
59 |
NothingElse
60 | ``` 61 | 62 | :::tip 63 | `x-elseif` 可以多次出现,但是顺序必须是 x-if -> x-elseif -> x-else,且这些节点是兄弟节点关系,如顺序错误则指令被忽略。 64 | ::: 65 | 66 | ### 循环列表 67 | 68 | ```jsx 69 | {/* Array or Plain Object*/} 70 | {item} 71 | 72 | {key}: {item} 73 | ``` 74 | 75 | 说明: 76 | 77 | 1. 若循环对象为数组,key 表示循环索引,其类型为 Number。 78 | 2. 当 `x-for` 与 `x-if` 同时作用在同一节点上时,循环优先级大于条件,即循环的 `item` 和 `index` 可以在子条件判断中使用。 79 | 80 | ### 单次渲染 81 | 82 | 仅在首次渲染时会触发 `createElement` 并将其引用缓存,重新渲染时直接复用缓存,用于提高不带绑定节点渲染效率和 Diff 性能。 83 | 84 | ```jsx 85 |

this paragragh {mesasge} content will not change.

86 | ``` 87 | 88 | ### 插槽指令 89 | 90 | 类似 WebComponents 的 slot 概念,并提供插槽作用域。 91 | 92 | 语法: 93 | 94 | ```html 95 | 96 | ``` 97 | 98 | 示例: 99 | 100 | ```jsx 101 | // Example 102 | 103 | header 104 | {props.index}: {props.item} 105 | footer 106 | 107 | // 槽位 108 | ``` 109 | 110 | 对比传统 JSX: 111 | 112 | ```jsx 113 | (header)} 115 | renderFooter={() => (footer)} 116 | renderItem={(item, index) => ({index}: {item}} 117 | /> 118 | ``` 119 | 120 | 对比小程序: 121 | 122 | ```jsx 123 | 124 | header 125 | {props.index}: {props.item} 126 | footer 127 | 128 | ``` 129 | 130 | ### Fragment 组件 131 | 132 | 提供空组件,不产生 UI,提供绑定 `x-if` `x-for` `x-slot` 指令。 133 | 134 | ```jsx 135 | 136 | ``` 137 | 138 | ### 类名绑定 139 | 140 | 语法: 141 | 142 | ```jsx 143 |
144 | ``` 145 | 146 | 参考实现: 147 | 148 | ```jsx 149 |
150 | ``` 151 | 152 | `classnames` 方法能力参考[同名 npm 包](https://npmjs.com/classnames)。 153 | 154 | > 更多请参考 [jsx-plus](https://github.com/jsx-plus/jsx-plus) 155 | 156 | ## 插件选项 157 | 158 | ### moduleName 159 | 160 | - 类型:`'react' | 'rax'` 161 | - 默认值:`'react'` 162 | 163 | 指定当前从哪个模块中导入 `jsx.Fragment` 组件,以在转换 JSX+ 语法的时候从对应的模块中引入组件。比如如果使用 `rax`: 164 | 165 | ```ts title="build.config.mts" 166 | import { defineConfig } from '@ice/pkg'; 167 | 168 | export default defineConfig({ 169 | plugins: [ 170 | ['@ice/pkg-plugin-jsx-plus', { moduleName: 'rax' }], 171 | ], 172 | }) 173 | ``` 174 | -------------------------------------------------------------------------------- /website/docs/guide/publish.md: -------------------------------------------------------------------------------- 1 | # 发布 2 | 3 | import Tabs from '@theme/Tabs'; 4 | import TabItem from '@theme/TabItem'; 5 | 6 | ## 配置产物信息 7 | 8 | ### files 9 | 10 | 发布之前需要确认需要发布哪些文件到 npm 上。比如,现在通过 build 后生成了 `esm` 和 `es2017` 产物,我们需要把它们加到 `package.json` 中: 11 | 12 | ```diff 13 | { 14 | "files": [ 15 | ... 16 | + "esm", 17 | + "es2017" 18 | ] 19 | } 20 | ``` 21 | 22 | 默认情况下,`package.json`、`README`、`LICENSE` 等文件是默认被发布上去的;一些临时目录和 `node_modules` 目录在发布时默认是不带上去的。更多详情可参考 [NPM 文档](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#files)。 23 | 24 | ### main & exports 25 | 26 | 推荐在 `package.json` 中配置 `exports` 字段来声明 npm 包的入口: 27 | 28 | ```json 29 | { 30 | "exports": { 31 | ".": { 32 | "import": "./esm/index.js", 33 | "require": "./cjs/index.js", 34 | "es2017": "./es2017/index.js", 35 | "default": "./cjs/index.js" 36 | }, 37 | "./feature": { 38 | "import": "./esm/feature.js", 39 | "require": "./cjs/feature.js", 40 | "es2017": "./es2017/feature.js", 41 | "default": "./cjs/feature.js" 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | 这样我们就可以通过以下的方式分别导入对应的模块了: 48 | 49 | ```js 50 | import Module1 from 'your-package-name'; 51 | import feature from 'your-package-name/feature'; 52 | ``` 53 | 54 | :::tip 55 | 关于更多 `package.exports` 导出规则,可以查看 [Node.js 文档](https://nodejs.org/dist/latest-v18.x/docs/api/packages.html#package-entry-points)。 56 | ::: 57 | 58 | 如果需要兼容 v12.20.0 或者 v14.13.0 以下的 Node.js,那就需要在 `package.json` 里的 `main` 字段指定主入口: 59 | 60 | ```json 61 | { 62 | "main": "./esm/index.js" 63 | } 64 | ``` 65 | 66 | > 注意:exports 优先级比 main 优先级高,所以如果需要兼容低版本的 Node.js,可以同时配置 exports 和 main 字段。 67 | 68 | ## 标识产物是否有副作用 69 | 70 | `sideEffects` 是用于标识我们的 ES 模块是否有副作用,从而提供更大的压缩空间。目前大部分的打包工具(比如 Webpack)都识别某个模块的 `package.json` 中的 `sideEffects` 字段来确定是否需要把有副作用的代码打包进去。`sideEffects` 默认值是 `true`。 71 | 72 | 模块副作用是指在模块执行的时候除了导出成员,是否还做其他的事情(比如 `console.log()`、`IIFE` 等)。 73 | 74 | 举个例子,比如现在的一个组件库在 `src/index.ts` 中导出两个模块: 75 | 76 | 77 | 78 | 79 | ```tsx 80 | export { default as Button } from './Button'; 81 | export { default as Input } from './Input'; 82 | ``` 83 | 84 | 85 | 86 | 87 | ```tsx 88 | console.log(123); 89 | 90 | export default function Button() { 91 | return () 92 | } 93 | ``` 94 | 95 | 96 | 97 | 98 | ```tsx 99 | console.log(123); 100 | 101 | export default function Input() { 102 | return () 103 | } 104 | ``` 105 | 106 | 107 | 108 | 109 | 然后我们在使用组件库的时候导入: 110 | 111 | ```ts 112 | import { Button } from 'your-ui-lib'; 113 | 114 | .console.log(Button); 115 | ``` 116 | 117 | 这时候,虽然没有使用到 Input 模块,但是 Input 模块包含副作用的代码,Tree Shaking 也不会直接移除掉 Input 模块。 118 | 119 | 如果你明确知道 Input 模块确实没有副作用,你可以在 package.json 中配置 `sideEffects` 来标识我们的代码没有副作用: 120 | 121 | ```json 122 | { 123 | "sideEffects": false 124 | } 125 | ``` 126 | 127 | 如果确实有些模块是有副作用的,比如你在源码导入全局样式: 128 | 129 | ```tsx 130 | import 'index.css'; 131 | import 'index.scss'; 132 | ``` 133 | 134 | 你可以在 `sideEffects` 中指定对应的文件: 135 | 136 | ```json 137 | { 138 | "sideEffects": [ 139 | "./esm/Input/index.js", 140 | "*.css", 141 | "*.less" 142 | ] 143 | } 144 | ``` 145 | 146 | ## 修改版本 147 | 148 | 在发布前需要更新 `package.json` 中的版本号: 149 | 150 | ```diff 151 | { 152 | - "version": "1.0.1", 153 | + "version": "1.1.0" 154 | } 155 | ``` 156 | 157 | 或者可以使用以下命令更新版本号: 158 | 159 | ```bash 160 | $ npm version minor 161 | ``` 162 | 163 | 关于 `npm-version` 的更多用法可以参考 [NPM 文档](https://docs.npmjs.com/cli/v9/commands/npm-version?v=true)。 164 | 165 | 166 | ## 构建和发布 167 | 168 | ICE PKG 的脚手架默认把构建命令配置在 `prepublishOnly`: 169 | 170 | ```json 171 | { 172 | "scripts": { 173 | "prepublishOnly": "npm run build" 174 | } 175 | } 176 | ``` 177 | 178 | 执行下面的命令即可把发布到 npm: 179 | 180 | ```bash 181 | # NPM 会先自动执行 prepublishOnly 脚本然后再发布 182 | $ npm publish 183 | ``` 184 | -------------------------------------------------------------------------------- /website/docs/guide/scenarios.md: -------------------------------------------------------------------------------- 1 | # 构建场景 2 | 3 | import Tabs from '@theme/Tabs'; 4 | import TabItem from '@theme/TabItem'; 5 | 6 | ICE PKG 默认支持 React 组件、Rax 组件、Node 模块、前端类库研发场景。你可以执行下面的命令: 7 | 8 | ```bash 9 | $ npm init @ice/pkg my-lib 10 | ``` 11 | 12 | 根据实际研发需求,选择对应的脚手架: 13 | 14 | ```bash 15 | ? 请选择项目类型 (Use arrow keys) 16 | ❯ React 组件 17 | Node 模块 18 | 前端类库 19 | Rax 组件 20 | ``` 21 | 22 | ## React 组件 23 | 24 | 如果你在多个不同的项目中共同使用了一个或多个 React 组件,那么你可以考虑把这些公共的 React 组件抽成一个 npm 包,这样你就可以在不同的项目中复用组件了。 25 | 26 | 假设一个 npm 包仅导出一个 React 组件,推荐使用以下目录结构和写法: 27 | 28 | ```md 29 | src 30 | ├── Header # 子组件 Header 31 | | ├── index.css 32 | | └── index.tsx 33 | └── index.tsx 34 | ``` 35 | 36 | 37 | 38 | 39 | ```tsx 40 | import Header from './Header'; 41 | 42 | // 通过 export default 方式导出 43 | export default function Component() { 44 | return ( 45 |
46 |
47 | ... 48 |
49 | ) 50 | } 51 | ``` 52 | 53 |
54 | 55 | 56 | 57 | ```tsx 58 | import './index.css'; 59 | 60 | export default function Header() { 61 | return (
Header
) 62 | } 63 | ``` 64 | 65 |
66 |
67 | 68 | 这样在消费处可以通过 `import Component from 'your-component-name'` 的方式导入组件了。 69 | 70 | 假如一个 npm 包要导出多个不同的组件,也就是类似我们常说的组件库,推荐使用以下的目录组织结构和写法: 71 | 72 | ```md 73 | src 74 | ├── Button 75 | | ├── index.css 76 | | └── index.tsx 77 | ├── Input 78 | | ├── index.css 79 | | └── index.tsx 80 | └── index.ts 81 | ``` 82 | 83 | 84 | 85 | ```ts 86 | export * from './Button'; 87 | export * from './Input'; 88 | ``` 89 | 90 | 91 | 92 | 93 | 94 | ```tsx 95 | import * as React from 'react'; 96 | 97 | export function Button() { 98 | return ( 99 | 100 | ) 101 | } 102 | ``` 103 | 104 | 105 | 106 | 107 | :::tip 108 | 有关样式的说明和写法请参考[构建能力 - CSS](./abilities#css) 文档。 109 | ::: 110 | 111 | `src/index.ts` 作为组件库的入口文件,然后统一导出不同的 React 组件,这样就可以通过 `import { Button, Input } from 'your-component-name';` 导入组件了。 112 | 113 | ## Node 模块 114 | 115 | 如果现在有相同的工具函数在多个 Node 应用被消费,可以把这些公共的函数抽成一个 npm 包,供多个 Node 应用使用。支持经过 Transform 模式生成 CommonJS 产物和 ES Module 产物。 116 | 117 | ```ts title="src/index.ts" 118 | export function writeLicenseToFileHeader(absFilePath: string) { 119 | const newFileContent = '/* LICENSE */' + fs.readFileSync(absFilePath, 'utf-8'); 120 | fs.writeFileSync(absFilePath, newFileContent); 121 | } 122 | ``` 123 | 124 | ## 前端类库 125 | 126 | 前端类库指的是运行在浏览器环境中的 JavaScript 模块,并且所有的依赖都会打包到这个模块里面。使用的场景有: 127 | 128 | 1. 类似 [React](https://unpkg.com/browse/react@18.2.0/umd/)、[moment](https://unpkg.com/browse/moment@2.29.4/min/) 等类库,用户的项目中把这些依赖 external 掉,需要在 HTML 中通过 ` 133 | 134 | 135 | 138 | 139 | 140 | ``` 141 | 142 | 2. 在 ` 150 | 151 | 152 | ``` 153 | 154 | ## Rax 组件 155 | 156 | 与 React 组件场景类似,你可以把公共的 Rax 组件抽成一个 npm 包,然后在其他项目中使用。 157 | 158 | ```tsx title="src/index.tsx" 159 | import { createElement } from 'rax'; 160 | import styles from './index.module.css'; 161 | 162 | export default function Component() { 163 | 164 | return ( 165 |
Hello
166 | ); 167 | } 168 | ``` 169 | 170 | :::caution 171 | 注意:Rax 组件必须要显式引入 `createElement` 和 `Fragment`(如果使用了 `<>` 语法),否则在运行时会报错。 172 | ::: 173 | 174 | 组件的目录结构组织和源码写法可参考[React 组件](#react-组件)。 175 | -------------------------------------------------------------------------------- /website/docs/index.md: -------------------------------------------------------------------------------- 1 | # ICE PKG 2 | 3 | ICE PKG 是飞冰开源的 NPM 包开发解决方案,默认支持 React 组件、Rax 组件、Node 模块、前端类库等多场景 NPM 包的研发。 4 | 5 | ## 特性 6 | 7 | - **📈 更快**:使用 [SWC](https://swc.rs/docs/configuration/swcrc) 编译和压缩,提升数十倍编译速度 8 | - **🎊 双模式**:同时提供 Transform + Bundle 两种构建模式 9 | - **🅾️ 零配置**:无需任何配置,提供内建的 TypeScript、JSX 等构建支持 10 | - **☄️ 面向未来**:提供 ES2017 产物,打包出面向现代浏览器支持的产物 11 | - **☘️ 文档预览**:基于 [Docusaurus](https://docusaurus.io/) 提供预览文档、生成静态文档能力 12 | 13 | ### 更快 14 | 15 | 使用 SWC 与 [tsc](https://www.typescriptlang.org/)、[Babel](https://babeljs.io/) 编译同一个项目之间耗时对比: 16 | 17 |
22 | benchmark 23 | 24 |
Above: benchmark 使用 飞冰 fusion pro 模板
25 |
26 | 27 | ### 双模式 28 | 29 | 社区的众多方案如 [Microbundle](https://github.com/developit/microbundle)、[tsup](https://github.com/egoist/tsup) 均只支持打包模式 (将所有依赖文件打包成一个文件输出,下称 Bundle 模式)。但 Bundle 模式[并非总是最佳选择](https://github.com/ice-lab/icepkg/issues/301)。其中最为**显著的问题**在于:**对 Tree-Shaking 不友好**,无用的依赖总是会被打包到最终的输出产物中,继而影响应用的体积。 30 | 31 | ICE PKG 除支持 Bundle 模式外,也默认支持了 Transform 模式(将文件挨个编译到输出目录)。更多内容请参考[构建能力 — 双模式构建](./guide/abilities#双模式构建)。 32 | 33 | ### ES2017 产物 34 | 35 | 为现代浏览器提供 ES2017 产物,可以减少产物体积,亦可加快执行速度。更多内容参考 [构建能力 — es2017 产物](./guide/abilities#es2017-产物)。 36 | 37 | ### 多场景 38 | 39 | 依赖 ICE PKG 强大的[双模式](#双模式)能力,支持多类场景的开发需求。包括但不限定于以下场景: 40 | 41 | + React 组件 42 | + Rax 组件 43 | + Node 模块 44 | + 前端类库 45 | 46 | ### 文档预览 47 | 48 | 结合 [Docusaurus](https://docusaurus.io/),ICE PKG 升级了文档预览的能力。更多内容参考 [指南 - 文档预览](./guide/preview)。 49 | 50 | ## 社区 51 | 52 | 如果你有疑问或者需要帮助,可以通过 [GitHub Issues](https://github.com/ice-lab/icepkg/issues) 来寻求帮助。 53 | -------------------------------------------------------------------------------- /website/docs/quick-start.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | ## 环境准备 4 | 5 | ### 1. Node.js 6 | 7 | 使用 ICE PKG 开发前需要安装 [Node.js](https://nodejs.org),并确保 node 版本是 16.14 或以上。 8 | 9 | ### 2. 包管理工具 10 | 11 | 安装 Node.js 后,默认会包含 npm。在国内使用 npm 安装依赖可能会比较慢。建议使用 [cnpm](https://www.npmjs.com/package/cnpm) 的国内镜像源进行加速: 12 | 13 | ```bash 14 | $ npm install -g cnpm --registry=https://registry.npmmirror.com 15 | # 验证 cnpm 安装是否成功 16 | $ cnpm -v 17 | ``` 18 | 19 | 除此之外,你还可以使用 [pnpm](https://pnpm.io/)、[yarn](https://yarnpkg.com/) 等其他包管理工具。本文档仍以 npm 作为示例。 20 | 21 | ## 初始化 22 | 23 | 以 React 组件类型为例,通过以下命令,可以快速初始化一个项目: 24 | 25 | ```bash 26 | $ npm init @ice/pkg@latest react-component 27 | ``` 28 | 29 | 选择 React 组件项目类型: 30 | ```bash 31 | ? 请选择项目类型 (Use arrow keys) 32 | ❯ React 组件 33 | Node 模块 34 | 前端类库 35 | Rax 组件 36 | ``` 37 | 38 | 你还可以通过附加的命令行选项的方式指定你想要的模板和 npm 包名,比如: 39 | 40 | ```bash 41 | $ npm init @ice/pkg@latest react-component --template @ice/template-pkg-react --npmName my-react-component 42 | ``` 43 | 44 | ## 启动项目 45 | 46 | ```bash 47 | $ cd react-component 48 | $ npm start 49 | ``` 50 | 51 | 现在,访问 `http://localhost:4000`,即可查看组件 README 文档: 52 | 53 | ![demo-readme](https://img.alicdn.com/imgextra/i2/O1CN01OctOw81JXuHCC6FhP_!!6000000001039-2-tps-1110-720.png) 54 | 55 | 访问 `http://localhost:4000/usage`,即可预览组件: 56 | 57 | ![component-preview](https://img.alicdn.com/imgextra/i3/O1CN01uEHuWp1DtXHv6uwax_!!6000000000274-2-tps-1160-540.png) 58 | 59 | ## 生成构建产物 60 | 61 | ```shell 62 | $ npm run build 63 | ``` 64 | 65 | ## 发布产物 66 | 67 | 1. 在 `package.json` 中修改包名 68 | 69 | 2. 执行发布命令: 70 | 71 | ```bash 72 | $ npm publish 73 | ``` 74 | -------------------------------------------------------------------------------- /website/docs/reference/cli.md: -------------------------------------------------------------------------------- 1 | # CLI 2 | 3 | ## start 4 | 5 | 启动本地调试服务。 6 | 7 | ```bash 8 | $ ice-pkg start [options] 9 | ``` 10 | | 选项 | 类型 | 说明 | 11 | | :----------: | :-------: | ----------------------------- | 12 | | `--config ` | `string` | 指定配置文件路径 | 13 | | `--rootDir ` | `string` | 指定应用运行的根目录 | 14 | | `--analyzer` | `boolean` | Bundle 模式下开启体积构建分析 | 15 | 16 | ## build 17 | 18 | 执行编译或者打包构建,输出构建产物。 19 | 20 | 21 | ```bash 22 | $ ice-pkg build [options] 23 | ``` 24 | 25 | | 选项 | 类型 | 说明 | 26 | | :----------: | :-------: | ----------------------------- | 27 | | `--config ` | `string` | 指定配置文件路径 | 28 | | `--rootDir ` | `string` | 指定应用运行的根目录 | 29 | | `--analyzer` | `boolean` | Bundle 模式下开启体积构建分析 | 30 | -------------------------------------------------------------------------------- /website/my-button/index.css: -------------------------------------------------------------------------------- 1 | .pkg-btn { 2 | border-radius: 6px; 3 | height: 32px; 4 | padding: 4px 15px; 5 | font-size: 14px; 6 | text-align: center; 7 | border: 1px solid transparent; 8 | cursor: pointer; 9 | font-weight: 400; 10 | } 11 | 12 | .pkg-btn-primary { 13 | color: #fff; 14 | background-color: #1677ff; 15 | box-shadow: 0 2px 0 rgb(5 145 255 / 10%); 16 | } 17 | 18 | .pkg-btn-default { 19 | background-color: #fff; 20 | border-color: #d9d9d9; 21 | box-shadow: 0 2px 0 rgb(0 0 0 / 2%); 22 | } -------------------------------------------------------------------------------- /website/my-button/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import * as React from 'react'; 4 | import './index.css'; 5 | 6 | interface ButtonProps { 7 | type?: 'primary' | 'default'; 8 | } 9 | declare const Button: React.FunctionComponent>; 10 | export default Button; 11 | -------------------------------------------------------------------------------- /website/my-button/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import { jsx as _jsx } from 'react/jsx-runtime'; 4 | import './index.css'; 5 | 6 | const Button = (props) => { 7 | const { type = 'default' } = props; 8 | const typeCssSelector = { 9 | primary: 'pkg-btn-primary', 10 | default: 'pkg-btn-default', 11 | }; 12 | return /* #__PURE__ */ _jsx('button', { 13 | className: `pkg-btn ${typeCssSelector[type] || ''}`, 14 | children: props.children, 15 | }); 16 | }; 17 | export default Button; 18 | -------------------------------------------------------------------------------- /website/my-button/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-button", 3 | "module": "./index.js", 4 | "types": "./index.d.ts", 5 | "exports": { 6 | ".": { 7 | "import": { 8 | "types": "./index.d.ts", 9 | "default": "./index.js" 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "icepkg-site", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "^2.3.1", 19 | "@docusaurus/preset-classic": "^2.3.1", 20 | "@easyops-cn/docusaurus-search-local": "^0.33.6", 21 | "@mdx-js/react": "^1.6.22", 22 | "clsx": "^1.1.1", 23 | "prism-react-renderer": "^1.3.3", 24 | "react": "^18.0.0", 25 | "react-dom": "^18.0.0" 26 | }, 27 | "devDependencies": { 28 | "@algolia/client-search": "^4.9.1", 29 | "@docusaurus/module-type-aliases": "2.3.1", 30 | "@docusaurus/theme-common": "^2.3.1", 31 | "@ice/pkg-plugin-docusaurus": "^1.4.2", 32 | "@tsconfig/docusaurus": "^1.0.5", 33 | "@types/react": "^18.0.0", 34 | "my-button": "file:./my-button", 35 | "typescript": "catalog:" 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.5%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tutorialSidebar: [ 3 | { type: 'autogenerated', dirName: '.' }, 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | } 7 | } 8 | --------------------------------------------------------------------------------