# skyl

[![version][version-tag]][npm-url]
[![install size][size-tag]][size-url]
[![download][download-tag]][npm-url]
[![Build][cd-tag]][cd-url]
[![Build][automerge-tag]][automerge-url]

## 安装

[![skyl][install-tag]][npm-url]

[npm-url]: https://npmjs.org/package/skyl
[install-tag]: https://nodei.co/npm/skyl.png
[version-tag]: https://img.shields.io/npm/v/skyl/latest.svg?logo=npm
[size-tag]: https://packagephobia.com/badge?p=skyl@latest
[size-url]: https://packagephobia.com/result?p=skyl@latest
[download-tag]: https://img.shields.io/npm/dm/skyl.svg?logo=docusign
[cd-tag]: https://github.com/monako97/neko-cli/actions/workflows/cd.yml/badge.svg
[cd-url]: https://github.com/monako97/neko-cli/actions/workflows/cd.yml
[automerge-tag]: https://github.com/monako97/neko-cli/actions/workflows/automerge.yml/badge.svg
[automerge-url]: https://github.com/monako97/neko-cli/actions/workflows/automerge.yml

### 特点

1. **快速刷新（Fast Refresh）**
2. **约定式路由（支持动态路由、路由懒加载）**
3. **Mock 数据（支持文件上传）**
4. **包按需引入（支持swc）**
5. **模块联邦**
6. **微前端（qiankun）**
7. **自动装卸的 redux 模版**
8. **自动装卸的国际化模版**
9. **移动端**
10. **编译 apk、ipa**
11. **支持切换 swc ｜ tsc 构建**

#### 快速开始

```shell
# 安装脚手架工具
npm i skyl -g
```

#### 创建一个新项目

```shell
skyl create demo-project # 项目名 demo-project
? 请输入开发者名称? skyl
? 您想创建一个什么类型的项目呢？ 移动端 H5应用（mobile）
? 请选择需要开启的开发辅助功能 (Press <space> to select, <a> to toggle all, <i> to invert selection, and <ente? 请选择需要开启的开发辅助功能 css代码规范, javascript代码规范
fetching plugin-runtime: ^1.0.30
fetching stylelint-config-neko: ^1.0.0
fetching eslint-config-neko: ^1.0.1

# 创建完成
cd ./demo-project
npm install
npm start
```

#### 开始开发

```shell
cd 项目名称
# 安装依赖
npm i
# 启动项目
npm start
# 编译项目 (编译前跳过 lint: skyl build site no-verify)
npm run build
```

#### 项目结构

```bash
./
├── config                  脚手架自定义配置
├── mock                    自定义mock脚本
├── src                     源码
   ├── components           组件目录
   ├── locales              国际化配置
   ├── models               redux 模块化配置
   ├── pages                页面路由组件
   ├── router               自定义路由配置
   ├── services             数据请求脚本
   ├── styles               公共样式表
      ├── mixins            less：全局mixins（不需要引入）
      ├── variables         less：全局变量（不需要引入）
      ├── index.global.less 全局样式表（不需要引入）
      ├── reset.less        重置样式表（不需要引入）
   ├── utils                工具脚本
   └── index.ts             初始化时会执行的脚本
├── typings                 全局类型定义
├── .gitignore              git过滤项
├── .prettierignore         prettier过滤项
├── .stylelintrc.yaml       stylelint配置项
├── .eslintrc.yaml          eslint配置项
├── .prettierrc.yaml        prettier配置项
├── package.json            包依赖配置
├── README.md               项目说明文档
└── tsconfig.json           typescript配置项

```

#### 配置

````javascript
// config/index.ts
import type { PartialConfigType } from 'plugin-runtime';
import { packageJson } from 'plugin-runtime/build/common';

const config: PartialConfigType<'swc'> = {
  /** 编译器 */
  compiler: 'swc',
  /** swc 配置项，仅当编译器为 swc 时有效 */
  swcrc: {},
  /**
   * 自定义entry
   * 当你需要添加一些polyfill函数时可在这里添加
   * 比如兼容es10的object.fromentries:
   * @example
   * ```
   * const config = {
   *   entry: {
   *     polyfillObjectFromentries: 'polyfill-object.fromentries'
   *   }
   * }
   * ```
   **/
  entry: {},
  /** 压缩配置 */
  minifier: {
    type: 'terser',
  },
  /** 环境变量, 可通过 process.env 获取 */
  env: {},
  /** mini-idc 数据持久化配置, 为 false 时关闭持久化 */
  miniIdc: {
     /** 持久化数据使用的key, 当key为 'none' 时, 关闭持久化 */
     programName: 'programName',
     /** 持久化数据是否加密 */
     encrypt: false,
     /** 自定义RSA加密密钥 */
     // encryptKey: ''
   },
  /** 路径别名映射 */
  alias: {},
  /** 设计图尺寸 默认: 1680 */
  designSize: 1680,
  /** 自定义降级组件 */
  fallbackCompPath: null,
  /** less 全局变量 */
  modifyVars: {},
  /** antd 主题配置 */
  antdThemeVariables: {
    compact: false,
    dark: false,
  };
  /** 类名前缀 */
  prefixCls: 'skl',
  /** 是否需要左侧菜单栏 仅 type: back-stage 时有效 */
  layoutHasSider: false,
  /** 自定义 webpack module rules */
  moduleRules: [],
  /** node_modules中, 需要开启 cssModules 的模块 */
  cssModules: [],
  /** 开发服务器代理 */
  proxy: {},
  /** 网站徽标 */
  favicon: 'public/favicon.ico',
  /** 自定义缓存目录，为 false 时，不使用缓存 */
  cacheDirectory: false,
  /** 开发服务器设置 */
  devServer: {
    port: 3000,
  };
  /** 自定义插入 html 的 js 和 css */
  assetHtml: [
    {
      // 自动加入 public 文件夹下 满足 *.entry.js 的文件
      glob: './public/*.entry.js',
    },
    {
      // antd 原始类名 兜底
      glob: './public/*.entry.css',
      typeOfAsset: 'css',
    },
  ],
  /** 路由模式 默认 browser */
  routerMode: 'hash',
  /** webpack 插件 */
  plugins: [],
  /** 通过Web字体加载加载的字体 */
  webFontLoader: [],
  /** 编译输出路径 */
  output: path.join(__dirname, `./dist/`),
  /** 模块联邦 */
  moduleFederation: [
    {
      name: 'pkg_name',
      // 接入远程 'shared_library' 模块
      remotes: [
        {
          name: 'shared_library',
          host: '[window.__MicroAppEntry__.shared_library]',
          // 声明从 shared_library 这个远程模块加载的依赖
          library: ['react', 'react-dom'],
        },
      ],
      // 将本项目的 Button 组件共享出去，其他项目接入 'pkg_name' 模块，可通过 import Button from 'pkg_name/Button'; 使用
      exposes: [
        {
          name: 'Button',
          path: './components/button/index.tsx',
        },
      ],
    },
  ],
  // swc 的按需导入配置如下，tsc按需导入参考 ts-import-plugin
  importOnDemand: {
    antd: ['[source]/es/[name:-]', '[source]/es/[name:-]/style'],
    lodash: '[source]/[name]',
    'skyline-ui': '[source]/es/[name:-]',
    'skyline-business': '[source]/es/[name:-]',
    '@ant-design/icons': {
      transform: ({ name, source }) => {
        if (name === 'createFromIconfontCN') {
          return `${source}/es/components/IconFont`;
        }
        return `${source}/es/icons/${name}`;
      },
    },
  },
};

export default config;

````

##### 自定义模块解析规则

```javascript
// config/index.ts
export default {
  moduleRules: [
    {
      test: /\.md$/,
      type: 'assets',
      exclude: [/node_modules/],
    },
  ],
};
```

##### 模块按需引入配置

```javascript
// config/index.ts
export default {
  // 模块按需引入配置 例如：
  importOnDemand: [
    {
      libraryName: '@antv/x6-react-components',
      libraryDirectory: 'es',
      style: true,
      transformToDefaultImport: false,
    },
  ],
};
```

##### 开发环境请求代理配置

```javascript
// config/index.ts
export default {
  proxy: [
    {
      context: ['/api/'],
      target: 'http://***.***.***.***:****/',
      changeOrigin: true,
      pathRewrite: { '^/api/': '/' },
      secure: false,
    },
  ],
};
```

##### 自定义 ts-loader 配置

> 在项目根目录下创建一个 tsloader.config.ts

```javascript
// tsloader.config.ts
export default = {};
```

##### 国际化配置

> 通过在 src/locales 目录下创建一个 ts 或 js 文件来描述

```typescript
// src/locales/zh-CN.ts
import type { LocalesConfType } from 'plugin-runtime';

const zhCN: LocalesConfType = {
  namespace: 'zh_CN',
  title: '简体中文',
  translation: {
    'route-home': '主页',
  },
};

export default zhCN;
```

如何使用？

```typescript
import React from 'react';
import { useLocale } from 'plugin-runtime';

const Test: React.FC = () => {
  const { getLanguage } = useLocale();

  return <div>{getLanguage('unknown')}</div>;
};

export default Test;
```

##### redux model

> 只需在 src/models 目录下新建 符合规则的 js 文件即可

如何使用？

1. 创建一个角色 model

   ```typescript
   import { loginByUserName } from '@/services/user';

   const model: ModelType<UserModelType> = {
     // model名称，view层用于提取state的key，需要保证唯一
     namespace: 'user',
     // 初始state状态
     state: {},
     effects: {
       // 用户名登录 --> 请求远程数据
       *fetchLoginByUserName({ payload }, { call, put, select }) {
         // 在 effects 中读取state？使用 select 获取
         const resp = yield call(() => loginByUserName(payload.data));

         yield put({
           type: 'user/saveUserInfo',
           payload: resp.result,
         });
       },
     },
     reducers: {
       // 通过 user/saveUserInfo 调用
       saveUserInfo(state, action) {
         return { ...state, info: action.payload };
       },
       logout() {
         return {};
       },
     },
   };

   export default model;
   ```

2. 在页面或组件中使用

```typescript
import React from 'react';
import { useDispatch, useSelector, shallowEqual } from 'plugin-runtime';

const Test: React.FC = () => {
  const userInfo = useSelector((state) => state.user.info, shallowEqual);
  const dispatch = useDispatch();

  return (
    <div onClick={() => dispatch({ type: 'user/logout' })}>
      登录
      {userInfo}
    </div>
  );
};

export default Test;
```

#### 编译 app

> 需安装 flutter 环境

```shell
skyl buildApp
? 请输入软件包名称? com.myoucai.bid ## 默认值
? 请选择需要打包的类型 打包为apk (release)
❯◉ 打包为apk (release) # 方向上下键选择条目，按空格键选中，回车键确认
 ◯ 打包为apk (debug)
 ◯ 打包为ipa (release)[未签名，需要 OSX 及 Xcode]
 ◯ 打包为ipa (debug)[未签名，需要 OSX 及 Xcode]

 # ...
 Running Gradle task 'assembleRelease'...                           27.7s
✓  Built build/app/outputs/flutter-apk/app-release.apk (15.8MB).
Done

```
