webpack作为新一代前端打包工具,为前端工程化提供了一些很方便的解决方式,在经过好几天的踩坑之后,一路蹒跚、跌跌撞撞的终于使用webpack-dev-middleware 插件+express搭建出了一个dva的项目
webpack-dev-middleware 插件
首先介绍一下这个webpack-dev-middleware 插件,webpack-dev-middleware,作用就是,生成一个与webpack的compiler绑定的中间件,然后在express启动的服务app中调用这个中间件。
这个中间件的作用有三点:
- 1.通过监听代码的变更,实现自动打包
- 2.快速编译并且将打包后的文件缓存在内存之中。(这就是webpack明明可以用watch mode,可以实现一样的效果,但是为什么还需要这个中间件的原因)
- 3.返回的是一个中间件,支持express的use格式
ps: 这个中间件打包后的产物直接走内存,相比于webpack每次打包完之后将新的文件放在本地指定位置来说效率更快,对于开发来讲更加方便
项目搭建
1.创建一个文件夹,在文件夹目录运行npm init生成package.json 安装相关依赖
生成的package.json文件如下,可直接复制之后 yarn。
{
"name": "reactwebpackexc",
"version": "0.1.0",
"private": true,
"dependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^8.1.0",
"babel-preset-env": "^1.7.0",
"commitizen": "^4.0.3",
"cz-conventional-changelog": "^3.1.0",
"dva": "^2.4.1",
"express": "^4.17.1",
"react": "^16.13.1",
"react-dev-utils": "^10.2.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
},
"devDependencies": {
"@babel/preset-react": "^7.9.4",
"clean-webpack-plugin": "^3.0.0",
"eslint": "^6.8.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^3.0.3",
"html-webpack-plugin": "^4.0.3",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"webpack-dev-middleware": "^3.7.2",
"webpackbar": "^4.0.0"
},
"scripts": {
"start": "node script/start.js",
"build:dll": "webpack --progress --config config/webpack.dll.config.js",
"build:dll-dev": "webpack --progress --config config/webpack.dll-dev.config.js",
"commit": "git-cz"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}
项目文件目录如下:
config (项目所需的配置文件都在该文件夹下)
|__webpack.config.js (webpack基础配置文件)
|__webpack.dll-dev.config.js (webpack dll 插件配置文件)
|__routes.js (路由配置文件)
public (静态文件,html模板相关的文件)
|__index.html (spa应用入口页面模板)
script (项目启动文件)
|__start.js (启动本地开发服务器文件)
src
|__Layout (整体布局相关文件夹)
|__model (dva model相关文件夹)
|__routes (dva 路由组件相关文件夹)
|__service (请求接口相关文件夹)
|__index.js (项目入口文件)
|__router.js (项目路由组件入口文件)
重要文件代码
webpack.config.js (webpack基础配置文件):
const path = require('path');
const WebpackBar = require('webpackbar');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const plugins = require('./plugins');
module.exports = function (mode) {
const isDev = mode === 'development';
return {
mode: mode,
devtool: isDev
? 'source-map'
: false,
entry: {
main: path.resolve(__dirname, '../src/index.js'),
},
output: {
filename: '[name].js',
path: !isDev ? path.resolve(__dirname, '../dist/[hash]') : path.resolve(__dirname, '../static/dist'),
publicPath: '/app'
},
plugins: Array.from(new Set([
...plugins,
new HtmlWebpackPlugin({
filename: !isDev ? path.resolve(__dirname, '../dist/[hash]/index.html') : path.resolve(__dirname, '../static/dist/index.html'),
template: path.resolve(__dirname, '../public/index.html')
}),
new webpack.DllReferencePlugin({ // 这个插件时配合DllPligin插件使用的,指定library的映射文件
context:'/app',
manifest: !isDev ? require('../dist/vendor-manifest.json') : require('../static/vendor-manifest.json')
}),
new WebpackBar({ name: '✈ 打包中。。。', color: 'red' }),
new webpack.NamedModulesPlugin(), // 用于启动 HMR 时可以显示模块的相对路径
new webpack.HotModuleReplacementPlugin(), // Hot Module Replacement 的插件
])),
module: {
rules: [{ test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }, { test: /\.jsx$/, use: 'babel-loader', exclude: /node_modules/ }]
},
}
}
这里就和正常webpack配置一样
webpack.dll-dev.config.js(DllPlugin插件配置文件):
const path = require('path');
const webpack = require('webpack');
const WebpackBar = require('webpackbar');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: {
vendor: ['react',
'react-dom',
'dva',
'dva/router',
'dva/saga',
'dva/fetch',],
},
output: {
path: path.resolve(__dirname, '../static/dll'),
filename: '[name].dll.js',
library: '[name]_library',
publicPath: '/app'
},
plugins: [
new webpack.DllPlugin({
path: path.resolve('./static', '[name]-manifest.json'),
name: '[name]_library',
context: 'app'
}),
new CleanWebpackPlugin({
root: path.resolve(__dirname,'../') , // 根目录
verbose: true, // 开启在控制台输出信息
dry: false, // 启用删除文件
}),
new WebpackBar({ name: '✈ 生成DLL文件', color: 'blue' }),
],
module: {
rules: [{ test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }]
}
};
这里使用DllPlugin插件是将除了业务代码之外的框架代码提前打包,提高业务代码打包的效率,所以再yarn start 之前需要运行yarn build:dll-dev生成library文件
start.js(开发服务器启动文件):
'use strict';
const webpack = require('webpack');
const express = require('express');
const path = require('path');
const webpackMiddleWare = require('webpack-dev-middleware');
const webpackConfigFac = require('../config/webpack.config');
const openBrowser = require('react-dev-utils/openBrowser');
const app = new express();
const compiler = webpack(webpackConfigFac('development'));
app.use(express.static(path.resolve(__dirname, '../static'))); // 指定静态文件目录
app.use(webpackMiddleWare(compiler, {
publicPath: '/app'
}));
app.get('*', function (request, response) { response.sendFile(path.resolve(__dirname, '../public', 'index.html')) }) // createBrowserHistory 的服务端配置
app.listen(3000, () => {
console.log(`server is running in port 3000`);
console.log(`http://localhost:3000/app`);
openBrowser('http://localhost:3000/app');
})
index.js (项目入口文件):
import dva from 'dva';
import { createBrowserHistory } from 'history';
import routes from './router';
const app = dva({
history: createBrowserHistory({
basename:'/app'
})
});
app.router(routes);
app.start('#root');
router.js(路由处理文件):
import React from 'react';
import { routerRedux, Switch, Route,Redirect } from 'dva/router';
import routes from '../config/routes';
const { ConnectedRouter } = routerRedux;
function getRoutes(routes) {
if (routes.components) {
getRoutes(routes.components);
} else {
return routes.map(item => {
return <Route path={item.path} key={item.path} component={require(`./routes/${item.component}`).default} />;
})
}
}
export default function ({ history }) {
return (<ConnectedRouter history={history}>
<Switch>
{
getRoutes(routes)
}
<Redirect form='/' to='home' />
</Switch>
</ConnectedRouter>)
}
.babelrc(babel配置文件):
{
"presets": ["@babel/preset-env","@babel/preset-react"],
"plugins": ["@babel/plugin-transform-runtime","@babel/plugin-proposal-class-properties"]
}
这里记录一下管理react路由的history使用
history 插件的使用
cnpm install history --save
import { createBrowserHistory } from 'history';
使用history的三种方式
- createBrowserHistory 现代浏览器使用
createBrowserHistory({
basename: '', // 基链接
forceRefresh: false, // 是否强制刷新整个页面
keyLength: 6, // location.key的长度
getUserConfirmation: (message,callback) => callback(window.confirm(message)) // 跳转拦截函数
})
- createMemoryHistory 手机端使用
createMemoryHistory({
initialEntries: ['/'], // 初始载入路径,和MemoryRouter中的initialEntries是一样的
initialIndex: 0, // initialEntries初始载入索引
keyLength: 6, // location.key的长度
getUserConfirmation: null // 路由跳转拦截函数
})
- createHashHistory (老版本浏览器使用,由于太过古老,这里不做记录)