跟風微前端,看了一圈源碼,以爲微前端並不適合公司目前的項目,例如css會有屢次加載的問題等,並且咱們也不會有juqery的庫,因此將html解析成字符串動態插入並執行的路子在我這並不是最優解,如今花點時間作一個共用的項目javascript
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
複製代碼
{
"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"
}
}
複製代碼
// 這個會有命令提示,本身選就行了,最後會提示你缺的依賴,最後提示我缺的依賴我沒用自動安裝,手動用yarn安裝的,由於自動安裝調用的npm命令。注意,eslint有一些和ts兼容不是很好
npx eslint --init
// 添加官方推薦的eslint
yarn add eslint-plugin-react-hooks --dev
複製代碼
我不熟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 }]
複製代碼
npx typescript --init
複製代碼
{
"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. */
}
}
複製代碼
項目結構未必合理,僅僅是一個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
複製代碼
這裏我項目和其餘的不同 antd 再也不使用按需加載,由於已有的項目使用了antd的所有組件react
跟目錄下新建webpack文件夾 webpack文件夾下 新建 main react vue三個文件夾webpack
三個文件夾下分別新建webpack.common.ts webpack.dev.ts webpack.prod.tsios
main下額外新建一個 template.html
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;
複製代碼
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;
複製代碼
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;
複製代碼
// 採起約定式,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裏有
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: '',
},
};
複製代碼
// 簡單的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'));
複製代碼
數據類型不是通用的,此數據類型僅爲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'
}
複製代碼
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;
複製代碼
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;
複製代碼
其他的看源碼
// 模擬權限
export const getWebAuthority:()=>Promise<{[key:string]:boolean}> = async ()=>{
return {
menu1:true,
submenu1:true,
menu11:true,
submenu2:true,
menu21:true
}
}
複製代碼
複製代碼