定製一個能夠react和vue共存的ts項目

前言

跟風微前端,看了一圈源碼,以爲微前端並不適合公司目前的項目,例如css會有屢次加載的問題等,並且咱們也不會有juqery的庫,因此將html解析成字符串動態插入並執行的路子在我這並不是最優解,如今花點時間作一個共用的項目javascript

github

https://github.com/757566833/react-vue
複製代碼

庫的結構

以react爲主,vue輔助,layout也是react寫的css

預覽

項目指望

支持先後端分離,自帶前端prod服務器,方便集成websocket等html

關鍵字

react vue antd webpack umd前端

整個項目須要的庫

解析ts用的babel而不是ts-loader/awesome-typescript-loadervue

// 這裏不會細說webpack 若是用開源腳手架例如umi等 須要瞭解下webpack內容

// 前兩個是核心 第三個是熱更新服務器 第四個是區分webpack mode 用的merge工具,具體內容請看webpack官方文檔(不要看中文版本)
yarn add webpack webpack-cli webpack-dev-server webpack-merge --dev

//  babel全家桶和react等,具體查看babel官網,還有不少插件可用
yarn add  @babel/core  @babel/plugin-proposal-class-properties  @babel/plugin-proposal-decorators  @babel/preset-env  @babel/preset-react  @babel/preset-typescript  --dev

// webpack插件和loader 有些插件不是必須安裝 例如clean能夠用rm命令代替 cross-env在非多人合做下也沒什麼用 error-overlay 在此項目也會失效 本身酌情處理 在筆者寫這個文檔的時候 vue-loader恰好更新到16 改版有點大
yarn add assets-webpack-plugin  babel-loader  clean-webpack-plugin  cross-env  css-loader  error-overlay-webpack-plugin  file-loader  fork-ts-checker-webpack-plugin  html-webpack-plugin  less less-loader  mini-css-extract-plugin  node-sass   sass-loader  vue-loader@15 webpack-bundle-analyzer url-loader  --dev

// react 熱更新插件
yarn add @hot-loader/react-dom react-hot-loader --dev

// eslint 本身酌情安裝
yarn add eslint --dev

// 最主要的庫
yarn add typescript  react immutable  react-dom react-redux react-router react-router-dom styled-components  redux  vue  vue-class-component  vue-property-decorator  vue-template-compiler --dev

// ui庫 vue的省略了
yarn add antd react-resizable --dev

// prod 下服務器的庫(koa)
yarn add koa  koa-router  koa-send  koa2-cors 

// 補一下types
yarn add @types/assets-webpack-plugin  @types/html-webpack-plugin  @types/koa  @types/koa-router  @types/koa-send  @types/koa2-cors  @types/mini-css-extract-plugin  @types/react  @types/react-dom  @types/react-hot-loader @types/react-redux  @types/react-resizable  @types/react-router-dom  @types/styled-components  @types/webpack-bundle-analyzer @types/webpack-dev-server  @types/webpack-merge   --dev
// ts 運行環境
yarn add ts-node --dev
複製代碼

最終個人package.json

{
  "devDependencies": {
    "@babel/core": "^7.10.3",
    "@babel/plugin-proposal-class-properties": "^7.10.1",
    "@babel/plugin-proposal-decorators": "^7.10.3",
    "@babel/preset-env": "^7.10.3",
    "@babel/preset-react": "^7.10.1",
    "@babel/preset-typescript": "^7.10.1",
    "@hot-loader/react-dom": "^16.13.0",
    "@types/assets-webpack-plugin": "^3.9.0",
    "@types/html-webpack-plugin": "^3.2.3",
    "@types/koa": "^2.11.3",
    "@types/koa-router": "^7.4.1",
    "@types/koa-send": "^4.1.2",
    "@types/koa2-cors": "^2.0.1",
    "@types/mini-css-extract-plugin": "^0.9.1",
    "@types/react": "^16.9.41",
    "@types/react-dom": "^16.9.8",
    "@types/react-hot-loader": "^4.1.1",
    "@types/react-redux": "^7.1.9",
    "@types/react-resizable": "^1.7.2",
    "@types/react-router-dom": "^5.1.5",
    "@types/styled-components": "^5.1.0",
    "@types/webpack-bundle-analyzer": "^3.8.0",
    "@types/webpack-dev-server": "^3.11.0",
    "@types/webpack-merge": "^4.1.5",
    "@typescript-eslint/eslint-plugin": "^3.4.0",
    "@typescript-eslint/parser": "^3.4.0",
    "antd": "^4.3.5",
    "assets-webpack-plugin": "^5.0.2",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "cross-env": "^7.0.2",
    "css-loader": "^3.6.0",
    "error-overlay-webpack-plugin": "^0.4.1",
    "eslint": "^7.3.1",
    "eslint-config-google": "^0.14.0",
    "eslint-plugin-react": "^7.20.0",
    "eslint-plugin-vue": "^6.2.2",
    "file-loader": "^6.0.0",
    "fork-ts-checker-webpack-plugin": "^5.0.5",
    "html-webpack-plugin": "^4.3.0",
    "immutable": "^4.0.0-rc.12",
    "less": "^3.11.3",
    "less-loader": "^6.1.2",
    "mini-css-extract-plugin": "^0.9.0",
    "node-sass": "^4.14.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-hot-loader": "^4.12.21",
    "react-redux": "^7.2.0",
    "react-resizable": "^1.10.1",
    "react-router": "^5.2.0",
    "react-router-dom": "^5.2.0",
    "redux": "^4.0.5",
    "sass-loader": "^8.0.2",
    "styled-components": "^5.1.1",
    "ts-node": "^8.10.2",
    "typescript": "^3.9.5",
    "vue": "^2.6.11",
    "vue-class-component": "^7.2.3",
    "vue-loader": "15",
    "vue-property-decorator": "^9.0.0",
    "vue-template-compiler": "^2.6.11",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0",
    "webpack-merge": "^4.2.2"
  },
  "dependencies": {
    "koa": "^2.13.0",
    "koa-router": "^9.0.1",
    "koa-send": "^5.0.0",
    "koa2-cors": "^2.0.6"
  }
}


複製代碼

eslint

// 這個會有命令提示,本身選就行了,最後會提示你缺的依賴,最後提示我缺的依賴我沒用自動安裝,手動用yarn安裝的,由於自動安裝調用的npm命令。注意,eslint有一些和ts兼容不是很好
npx eslint --init
// 添加官方推薦的eslint
 yarn add eslint-plugin-react-hooks  --dev
複製代碼

最終個人eslint

我不熟vue 須要本身加java

env:
  browser: true
  es2020: true
  node: true
extends:
  - "eslint:recommended"
  - "plugin:react/recommended"
  - google
parser: "@typescript-eslint/parser"
parserOptions:
  ecmaFeatures:
    jsx: true
  ecmaVersion: 11
  sourceType: module
plugins:
  - react
  - "@typescript-eslint"
  - "react-hooks"
rules:
  no-unused-vars: "off"
  no-prototype-builtins: "off"
  react-hooks/rules-of-hooks: "error"
  react-hooks/exhaustive-deps: "warn"
  react/jsx-uses-react: "error"
  react/jsx-uses-vars: "error"
  no-undef: "off"
  object-curly-spacing: ["error", "always"]
  react/prop-types: 0
  max-len: ["error", { "code": 120 }]

複製代碼

typescript

npx typescript --init
複製代碼

最終個人tsconfig

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Basic Options */
    // "incremental": true,                   /* Enable incremental compilation */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    // "lib": [],                             /* Specify library files to be included in the compilation. */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    "jsx": "react",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    "noUnusedLocals": true,                /* Report errors on unused locals. */
    "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    "paths": {
      "@/*":["./src/*"],
    },                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */

    /* Advanced Options */
    "skipLibCheck": true,                     /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  }
}

複製代碼

代碼組成

layout 爲main項目,頁面自己爲r和v項目
main 和r 用react
v用vue

項目結構

項目結構未必合理,僅僅是一個demo 根目錄下新建srcnode

|-- src
    |-- asset      靜態文件
    |-- global     全局文件例如方便moment全局設置等
    |-- components 項目通用組件
    |-- config     項目的一些配置
    |-- http       封裝http請求,例如fetch axios等
    |-- layouts    main的具體內容
    |-- menu       layout 左側的menu
    |-- pages
        |--react   react的內容
        |--vue     vue的內容
    |--redux      實際上沒什麼用 在這裏僅僅main用到了
    |--services   各類接口
    |--util       工具
        |--react.tsx
        |--vue.ts
複製代碼

webpack

這裏我項目和其餘的不同 antd 再也不使用按需加載,由於已有的項目使用了antd的所有組件react

跟目錄下新建webpack文件夾 webpack文件夾下 新建 main react vue三個文件夾webpack

三個文件夾下分別新建webpack.common.ts webpack.dev.ts webpack.prod.tsios

main下額外新建一個 template.html

main 的common

import path from 'path';
// 生成html的插件
import HtmlWebpackPlugin from 'html-webpack-plugin';
// 把css拆出來的插件
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import webpack from 'webpack';

const config: webpack.Configuration = {
  entry: {
    main: './src/layouts/index.tsx',
  },
  module: {
    rules: [
      {
        test: /\.(tsx|ts)?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: [
                '@babel/preset-env',
                '@babel/preset-typescript',
                '@babel/preset-react',
              ],
              plugins: [
                ['@babel/plugin-proposal-decorators', { legacy: true }],
                ['@babel/plugin-proposal-class-properties', { loose: true }],
              ],
            },
          },
        ],
      },
      {
        test: /\.css$/,
        use: [{
          loader: MiniCssExtractPlugin.loader,
        },
        {
          loader: 'css-loader',
        },
        ],
        exclude: /node_modules/,
      },
      {
        test: /\.less$/,
        use: [{
          loader: MiniCssExtractPlugin.loader,
        },
        {
          loader: 'css-loader',
        },
        {
          loader: 'less-loader',
        },
        ],
      },
      {
        test: /\.scss$/,
        use: [{
          loader: MiniCssExtractPlugin.loader,
        }, {
          loader: 'css-loader',
        }, {
          loader: 'sass-loader',
        }],
        exclude: /node_modules/,
      },
      {
        test: /\.(jpg|jpeg|png|svg|gif|woff|woff2|otf|ttf)?$/,
        loader: 'url-loader',
        options: {
          limit: 8192,
          publicPath: '/',
          name: 'img/[name].[hash:7].[ext]',
        },

      },
    ],
  },
  resolve: {
    // 自動後綴
    extensions: ['.tsx', '.ts'],
    // 軟鏈接
    alias: {
      '@': path.resolve('src'),
    },
  },
  plugins: [
    // 生成html
    new HtmlWebpackPlugin({
      title: 'test',
      template: path.resolve(__dirname, 'template.html'),
    }),
    // 拆css
    new MiniCssExtractPlugin({
      filename: 'main/[name].[contenthash].css',
    }),
    // 檢查類型
    new ForkTsCheckerWebpackPlugin(),
  ],

};
export default config;

複製代碼

main 的dev

import path from 'path';
import webpack from 'webpack';
import merge from 'webpack-merge';
import common from './webpack.common';
// 由於是babel轉譯的ts 如今須要個插件檢查類型
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
const config: webpack.Configuration = merge(common, {
  mode: 'development',
  devtool: 'eval-source-map',
  output: {
    path: path.resolve(__dirname, '..', '..', 'dist'),
    filename: 'main/app.js',
    publicPath: '/',
  },
  devServer: {
    // spa必備
    historyApiFallback: { index: '/' },
    contentBase: path.join(__dirname, '..', '..', 'dist'),
    host: '127.0.0.1',
    hot: true,
    port: 7000,
    // 這個的做用是讓webpack安靜點
    stats: 'errors-warnings',
    publicPath: '/',
  },
  plugins: [
    // 熱更插件
    new webpack.HotModuleReplacementPlugin(),
    // 命名空間 也是熱更用的
    new webpack.NamedModulesPlugin(),
    // 檢查類型
    new ForkTsCheckerWebpackPlugin(),
    // 全局變量 區分環境
    new webpack.DefinePlugin({
      ENV_MODE: JSON.stringify('development'),
    }),
  ],
  // 熱更必備
  resolve: {
    alias: {
      'react-dom': '@hot-loader/react-dom',
    },
  },
});
// 覆蓋掉common的配置,加入熱更的babel
const config2 = merge.smart(config, {
  module: {
    rules: [{
      test: /\.(tsx|ts)?$/,
      exclude: /node_modules/,
      use: [
        {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            babelrc: false,
            presets: [
              '@babel/preset-env',
              '@babel/preset-typescript',
              '@babel/preset-react',
            ],
            plugins: [
              ['@babel/plugin-proposal-decorators', { legacy: true }],
              ['@babel/plugin-proposal-class-properties', { loose: true }],
              'react-hot-loader/babel',
            ],
          },
        },
      ],
    }],
  },
});
export default config2;

複製代碼

main 的prod

import path from 'path';
import webpack from 'webpack';
// 分析打包
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import merge from 'webpack-merge';
import common from './webpack.common';
import { CleanWebpackPlugin } from 'clean-webpack-plugin';
const config: webpack.Configuration = merge(common, {
  mode: 'production',
  devtool: 'source-map',
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM',
    'react-router': 'ReactRouter',
    'react-router-dom': 'ReactRouterDOM',
    'antd': 'antd',
  },
  output: {
    // 改爲了chunk命名,避免出現0123這種
    filename: 'main/[name].[chunkhash].js',
    path: path.resolve(__dirname, '..', '..', 'dist'),
  },
  plugins: [
    new BundleAnalyzerPlugin(),
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: ['main/**/*'],
    }),
    new webpack.DefinePlugin({
      ENV_MODE: JSON.stringify('production'),

    }),
  ],
});

export default config;

複製代碼

react的webpack common

// 採起約定式,pages下面的react裏面全部的tsx文件(不包含某些關鍵字)所有爲入口文件
...
const getEntry = (url: string) => {
  if (url.includes('component') ||
    url.includes('hooks') ||
    url.includes('services') ||
    url.includes('http')
  ) {
    return;
  }
  const list = fs.readdirSync(url);
  for (const iterator of list) {
    if (iterator.includes('.tsx')) {
      entry[`/${url}/${iterator}`
          .replace('/index.tsx', '')
          .replace('.tsx', '')
          .replace('src/pages/react/', '')
          .replace(/\//g, '')] = `./${url}/${iterator}`;
    } else if (!iterator.includes('.')) {
      getEntry(`${url}/${iterator}`);
    }
  }
};
const rootpath = path.join('src', 'pages', 'react');
...
複製代碼

其他的再也不贅述 demo裏有

正式開始項目

1.靜態服務配置文件

src/config/index.tsx

後綴ts tsx均可以,這個文件是main調用

// react 在7001端口 vue在7002
export const cssPrefix = 'egu-layout-';
export const modulePath = {
  r: {
    development: '//127.0.0.1:7001',
    production: '',
  },
  v: {
    development: '//127.0.0.1:7002',
    production: '',
  },
};

複製代碼

2. main開始

1. src/layouts/index.tsx

// 簡單的react 入門
import React from 'react';
import ReactDOM from 'react-dom';
import {
  BrowserRouter as Router,
  Route,
} from 'react-router-dom';
import store from '@/redux/store';
import { Provider } from 'react-redux';
import Base from './base';
import '@/global/react/global.scss';
import '@/global/react/global';
// antd 限定了中文
import { ConfigProvider } from 'antd';
import zhCN from 'antd/es/locale/zh_CN';
const Layout: React.FC = () => {
    return <Provider store={store}>
      <ConfigProvider locale={zhCN}>
         {/* base模塊 */}
        <Base />
      </ConfigProvider>
    </Provider>;
};

const AppRouter: React.FC = () => {
  return (
    <Router>
      <Route path="*" component={Layout} />
    </Router>
  );
};

const App: React.FC = () => {
  return <>
    <AppRouter />
  </>;
};
ReactDOM.render(<App />, document.getElementById('root'));
複製代碼

2. src/menu/interface.ts

數據類型不是通用的,此數據類型僅爲demo

// menu的item
export interface IMenuBean {
  title: string;
  path: string;
  module?: 'r' | 'v'
  type?: EMenuType;
  authority?: string;
  children?: IMenuBean[];
}
// menu item的類型
export enum EMenuType {
  SubMenu = 'SubMenu',
  Item = 'Item',
  NoMenu = 'NoMenu',
  Header = 'Header'
}
// 這個模塊屬於react仍是vue
export enum module {
  react = 'r',
  vue = 'v'
}
複製代碼

3. src/menu/index.ts

import { IMenuBean } from './interface';
import basics from './basics';
import mall from './mall';
import assets from './assets';
import finance from './finance';
import config from './config';

const menu: IMenuBean[] = [
  basics, // 運營管理
  mall, // 業務管理
  finance, // 財務
  assets, // 資產
  config, // 設置

];
export default menu;
複製代碼

4.src/menu/basics.ts

import { IMenuBean, EMenuType, module } from './interface';
const basics: IMenuBean = {
  title: 'menu1',
  type: EMenuType.Header,
  path: '/basics/',
  authority: 'menu1',
  module: module.react,
  children: [
    {
      title: 'submenu1',
      type: EMenuType.SubMenu,
      path: '/basics/enterprise',
      authority: 'pc-op-enterprise',
      children: [
        {
          title: 'menu11',
          type: EMenuType.Item,
          path: '/basics/enterprise/authentication',
          authority: 'menu11',
        },
        {
          title: 'menu12',
          type: EMenuType.Item,
          path: '/basics/enterprise/menu',
          authority: 'menu12',
        },
      ],
    },
    {
      title: 'submenu2',
      type: EMenuType.SubMenu,
      path: '/basics/test',
      authority: 'pc-op-account',
      children: [
        {
          title: 'menu21',
          type: EMenuType.Item,
          path: '/basics/test/test1',
          module: module.vue,
          authority: 'menu21',
        },
      ],
    },
  ],
};
export default basics;
複製代碼

其他的看源碼

  1. src/services/index.ts
// 模擬權限
export const getWebAuthority:()=>Promise<{[key:string]:boolean}> = async ()=>{
    return {
        menu1:true,
        submenu1:true,
        menu11:true,
        submenu2:true,
        menu21:true
    }
}
複製代碼
  1. src/layouts/base/index.tsx
複製代碼
相關文章
相關標籤/搜索