webpack搭建dva项目

188 阅读3分钟

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的三种方式

  1. createBrowserHistory 现代浏览器使用
        createBrowserHistory({
            basename: '', // 基链接
            forceRefresh: false, // 是否强制刷新整个页面
            keyLength: 6, // location.key的长度
            getUserConfirmation: (message,callback) => callback(window.confirm(message)) // 跳转拦截函数
        })
  1. createMemoryHistory 手机端使用
        createMemoryHistory({
            initialEntries: ['/'], // 初始载入路径,和MemoryRouter中的initialEntries是一样的
            initialIndex: 0, // initialEntries初始载入索引
            keyLength: 6, // location.key的长度
            getUserConfirmation: null // 路由跳转拦截函数
        })
  1. createHashHistory (老版本浏览器使用,由于太过古老,这里不做记录)

源码地址