从零开始搭建antd4.x + react16 + redux4 + webpack4 + react-router5基础框架解析
这个框架,使用了几个月,也丰富了很多内容。回头看,重点是:
- 搭建框架顺序,先干什么后干什么,这个弄反了,各种报错都来了
- 依赖的平衡,不可能所有都用最新的依赖,因为依赖之间版本有关联,需要不断调整从中间找平衡。
- webpack配置,根据文档,按需配置
- 业务层面,layout,service拦截器,路由,store,换肤,组件复用等
一、webpack配置重点
Entry
入口起点(entry point)指示webpack应该使用哪个模块,来作为构建其内部依赖图的开始。
进入入口起点后,webpack会找出有哪些模块和库是入口起点(直接和间接)依赖的。
每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。
Output
output属性告诉webpack在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。
基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。
Module
模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
Chunk
代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
Loader
loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript),模块转换器,用于把模块原内容按照需求转换成新内容。
loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。
Plugin
loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。
插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。
webpack启动流程
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程 :
- 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。
- 确定入口:根据配置中的 entry 找出所有的入口文件。
- 编译模块:从入口文件出发,调用所有配置的 Loader 。
- 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
- 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
二、配置webpack
1、安装create-react-app
装npm,用npm init可以初始化生成package.json
我这里用的creact-react-app生成基本模块
create-react-app 中文文档:https://www.html.cn/create-react-app/docs/getting-started/
2、配置webpack.config.js
const path = require('path'); const webpack = require('webpack'); const merge = require('webpack-merge'); const dev = require('./webpack.dev'); const prod = require('./webpack.prod'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const isDev = process.env.NODE_ENV === 'development'; const base = { entry: path.resolve(__dirname, '../src/index.js'), // 入口 module: { rules: [{ // 对.js、.jsx的处理 test: /\.(js|jsx)$/, exclude: /node_modules/, use: 'babel-loader' }, { // 对.css的处理 test: /\.css$/, use: [ !isDev && MiniCssExtractPlugin.loader, // 生产环境下样式抽离 isDev && 'style-loader', { loader: 'css-loader', options: { importLoaders: 1 // 引入的文件调用后面的loader处理 } }, { // 智能添加样式前缀 loader: "postcss-loader", options:{ plugins:[require('autoprefixer')] } }, ].filter(Boolean) }, { test: /\.scss$/, // css预处理器scss的处理 use: [ !isDev && MiniCssExtractPlugin.loader, // 生产环境下抽离样式 isDev && 'style-loader', "css-loader", "sass-loader" ].filter(Boolean) }, { test: /\.less$/, // css预处理器less的处理 use: "less-loader", }, { test: /\.stylus$/, // css预处理器stylus的处理 use: "stylus-loader", }, { test: /\.(jpe?g|png|svg|gif)$/, // 对图片的处理 loader: "file-loader", options: { name: "image/[contentHash].[ext]" }, }, { test: /\.(woff|ttf|eot|otf|ico)$/, // 对字体图标的处理 loader: "file-loader", options: { name: "image/[name].[ext]" }, }] }, output: { // 出口 filename: 'scripts/[name].bundle.js', path: path.resolve(__dirname, '../dist') }, resolve: { // 引入js、jsx文件时,无需添加后缀 extensions: ['.js', '.jsx'], }, plugins: [ !isDev && new MiniCssExtractPlugin({ // css样式抽离 filename: 'css/[name].[contentHash].css' }), new HtmlWebpackPlugin({ // 配置入口html filename: 'index.html', template: path.resolve(__dirname, '../public/index.html'), hash: true, inject: true, favicon: path.resolve(__dirname, '../public/favicon.ico'), minify: !isDev && { removeAttributeQuotes: true, // 去掉属性双引号 collapseWhitespace: true, // 将html文件折叠成一行 } }), new webpack.HotModuleReplacementPlugin(), ].filter(Boolean), devServer: { // 配置服务 hot:true, // 热更新 port: 3000, // 端口号 compress: true, // 提升页面返回速度 open: true, // 启动服务后自动启动浏览器 contentBase: path.resolve(__dirname, '../dist'), // webpack启动服务会在dist目录下 } } module.exports = () => { // 根据环境合并webpack if (isDev) { return merge(base, dev); } else { return merge(base, prod); } }
3.proxy代理
我使用了webpack-dev-server来启动服务,script/start.js,以下是介绍:
webpack-dev-server是webpack官方提供的一个小型Express服务器。使用它可以为webpack打包生成的资源文件提供web服务。
webpack-dev-server 主要提供两个功能:
1.为静态文件提供web服务
2.自动刷新和热替换(HMR)
自动刷新指当修改代码时webpack会进行自动编译,更新网页内容
热替换指运行时更新各种模块,即局部刷新
具体配置方法,见官方文档:https://www.webpackjs.com/configuration/dev-server/#devserver
4.路由配置
app.js里面进行配置和测试,这里要注意,用的是react-router-dom,而不是react-router
开始我用的react-router,调试了很久,才弄好。另外还需要注意hashrouter和browserrouter的区别
react-router-dom官方文档:https://reactrouter.com/web/guides/quick-start
5.layout和redux同步进行
国际化用antd的:https://ant.design/docs/react/i18n-cn
redux主要是configureStore的配置,以下代码里面分别说明
//app.js import React, { Component } from 'react'; import { Switch, Route, HashRouter as Router,Redirect} from 'react-router-dom'; import { Provider } from 'react-redux' import {PersistGate} from 'redux-persist/lib/integration/react'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/lib/locale-provider/zh_CN';//直接用antd的国际化就可以了,后来同组的人改成之前的了 import Error from '@/components/Exception/error'; //错误页面,用react自带getDerivedStateFromError判断,如果错误就调404页面 import { addLocaleData, IntlProvider } from 'react-intl'; //import { fetchToken } from '@/service/xhr/fetch' import configureStore from "./stores/configureStore"; //redux 配置 import postcssFlexbugsFixes from 'postcss-flexbugs-fixes'; import BasicLayout from '@/layouts/BasicLayout' import Login from '@/pages/login';//login测试使用,正式开发可去除 const {store, persistor } = configureStore(); class App extends Component { constructor(props){ super(props) this.state = { isError: false }; // 只在开发环境获取 token // if(process.env.NODE_ENV === "development"){ // fetchToken() // } } static getDerivedStateFromError(error) { return { isError: true } } render() { // const routers = getLayoutData(); if(this.state.isError){ return <Error /> } return ( <Provider store={store} > <PersistGate loading={null} persistor={persistor}> <Router> <ConfigProvider locale={zhCN}> <IntlProvider locale={zhCN} messages={zhCN}> <Switch> <Route path='/portal/login' component={Login} /> <Route path='/portal' component={BasicLayout} /> <Redirect to='/portal/login' /> </Switch> </IntlProvider> </ConfigProvider> </Router> </PersistGate> </Provider> ); } } export default App;
//layout.js本来想放上来的,发现由于UI设计,写了很多样式判断 //关键点就是router循环渲染出menu,没有什么特别高深的 //redux配置 import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' //中间件 import createLogger from 'redux-logger' //开发环境,打印出dispatch action方便调试 import rootReducer from '@/models/rootReducer' import { persistStore, persistReducer } from 'redux-persist' import storage from 'redux-persist/lib/storage' import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2' export default function configureStore() { const persistConfig = { key: 'root', storage, stateReconciler: autoMergeLevel2, // whitelist: ['fetchDatacenter','getConfig','fetchVirtualDatacenterList',] whitelist: [] }; const persistedReducer = persistReducer(persistConfig, rootReducer) let middleware = [thunk]; if(process.env.NODE_ENV !== 'production'){ middleware.push(createLogger) } const store = createStore( persistedReducer, undefined, applyMiddleware(...middleware), ); let persistor = persistStore(store) return { store, persistor } }
6.公共方法
- table公共组件,白同学写的,直接调用可以生成table
- 换肤,将theme写在redux里面,根据获取的theme渲染不同的样式
- login,权限,角色等常规业务
以上就是全部内容了,框架在我们公司git上,也可以按照我之前的文件脚手架下载,看代码了解。
【完】
参考
webpack配置:https://www.runoob.com/w3cnote/webpack-tutorial.html
antd4.x与3.x的不同 https://ant.design/docs/react/migration-v4-cn
redux的store配置官方文档:http://cn.redux.js.org/docs/recipes/ConfiguringYourStore.html
redux-logger:https://github.com/LogRocket/redux-logger
webpack优化:https://juejin.cn/post/6844904093463347208