Gowhich

Durban's Blog

继续之前的分享【Webpack4生产环境构建相关配置 - 代码压缩

下面让我们用我们之前文章的项目来做下实践

1
2
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git react-webpack-demo && cd react-webpack-demo
npm install

许多 library 将通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容。例如,当不处于生产环境中时,某些 library 为了使调试变得容易,可能会添加额外的日志记录(log)和测试(test)。其实,当使用 process.env.NODE_ENV === ‘production’ 时,一些 library 可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码。我们可以使用 webpack 内置的 DefinePlugin 为所有的依赖定义这个变量:

大概设置配置如下
修改webpack.prod.js,在plugins里面加入如下

1
2
3
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),

最终plugins如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'React + ReactRouter',
filename: './index.html', // 调用的文件
template: './index.html', // 模板文件
}),
new UglifyJsPlugin({
sourceMap: true,
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
],

官方还有一个说明

技术上讲,NODE_ENV 是一个由 Node.js 暴露给执行脚本的系统环境变量。通常用于决定在开发环境与生产环境(dev-vs-prod)下,服务器工具、构建脚本和客户端 library 的行为。然而,与预期不同的是,无法在构建脚本 webpack.config.js 中,将 process.env.NODE_ENV 设置为 “production”,请查看 #2537[https://github.com/webpack/webpack/issues/2537]。因此,例如 process.env.NODE_ENV === ‘production’ ? ‘[name].[hash].bundle.js’ : ‘[name].bundle.js’ 这样的条件语句,在 webpack 配置文件中,无法按照预期运行。

我记得以前是可以按照预期执行的,但是现在说不行,我们就认为不行吧,以防做了其他的改动,也为了跟官方一致,我们按照官方的说明来配置

如果有印象的话,之前文章有一个环节是配置redux-logger的,在src/index.jsx里面有个

1
2
3
if (typeof __DEV__ !== 'undefined') {
middleware.push(createLogger());
}

虽然这种方式也能实现,但是感觉不专业,毕竟像react库自己或者其他大型的库都是通过使用process.env.NODE_ENV这样的方式来判断的,为了保持自己库也能跟国际挂钩,咱也按照标准来,修改如下

1
2
3
if (process.env.NODE_ENV !== 'production') {
middleware.push(createLogger());
}
1
npm run build

打包完之后我们打开index.html,这里会个问题,就是需要一个真实的server去运行咱们的项目根目录就设置在dist下面,
推荐一个http-server
如果没有的话按照我的步骤安装下,安装如下

1
npm install http-server -g

安装为之后通过终端进入到本项目的dist目录下,然后执行

1
http-server ./ -p 8084

如果有如下输出表示已经启动成功

1
2
3
4
5
Starting up http-server, serving ./
Available on:
http://127.0.0.1:8084
http://172.18.0.70:8084
Hit CTRL-C to stop the server

用Chrome浏览器打开http://127.0.0.1:8084,然后调出DevTools,然后进入到计数器,点击加减号,右侧的console中是没有任何输出,如下图,表示我们的webpack.pord.js的配置是起作用的。

我们运行下

1
npm run start

效果如下图


console中会有对应的输出,这是因为我们没有在webpack.dev.js做任何配置

以后在生产环境和开发环境有任何的需要根据环境变量来做判断处理,这个就是一个很好的例子

项目地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.8

继续之前的分享【Webpack4生产环境构建相关配置 - 基础配置

下面让我们用我们之前文章的项目来做下实践

1
2
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git react-webpack-demo && cd react-webpack-demo
npm install

1、安装代码压缩需要的插件uglifyjs-webpack-plugin

1
npm i -D uglifyjs-webpack-plugin

2、修改webpack配置
修改webpack.prod.js

为什么改webpack.prod.js而不是webpack.dev.js,因为在生产环境为了减少文件的大小以加快文件的加载速度,以此来提高用户的体验度

在plugins加入如下代码

1
new UglifyJsPlugin()

结果如下

1
2
3
4
5
6
7
8
9
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'React + ReactRouter',
filename: './index.html', // 调用的文件
template: './index.html', // 模板文件
}),
new UglifyJsPlugin(),
],

我们做下对比,使用压缩工具,跟不使用压缩工具的区别

上图是使用压缩扩展的

上图是未使用压缩扩展的
奇怪好像没有什么区别,也许webpack在生产环境下应该默认调用了压缩扩展

source map
这里引用下官方的话


我们鼓励你在生产环境中启用 source map,因为它们对调试源码(debug)和运行基准测试(benchmark tests)很有帮助。虽然有如此强大的功能,然而还是应该针对生成环境用途,选择一个构建快速的推荐配置(具体细节请查看 devtool)。对于本指南,我们将在生产环境中使用 source-map 选项,而不是我们在开发环境中用到的 inline-source-map

因人而异吧,我们来看下这个如果我们加了会有什么变化

修改配置webpack.prod.js
添加

1
devtool: 'source-map',

并修改

1
new UglifyJSPlugin()

为下面的代码

1
2
3
new UglifyJSPlugin({
sourceMap: true
})

webpack.prod.js结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
entry: {
app: [
'./src/index.jsx',
],
},
output: {
filename: '[name].[chunkhash].bundle.js',
chunkFilename: '[name].[chunkhash].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'React + ReactRouter',
filename: './index.html', // 调用的文件
template: './index.html', // 模板文件
}),
new UglifyJsPlugin({
sourceMap: true,
}),
],
});

运行下

1
npm run build

如下图

可以看出文件稍微变大了。

开发环境不建议使用代码压缩,这样不方便调试,生产环境可加可不加,因人而异,因需求而异。

项目地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.7

这里为什么要实践下这个,引用下官方的原文

开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。

虽然,以上我们将生产环境和开发环境做了略微区分,但是,请注意,我们还是会遵循不重复原则(Don’t repeat yourself - DRY),保留一个“通用”配置。为了将这些配置合并在一起,我们将使用一个名为 webpack-merge 的工具。通过“通用”配置,我们不必在环境特定(environment-specific)的配置中重复代码。

下面让我们用我们之前文章的项目来做下实践

1
2
3
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git react-webpack-demo && cd react-webpack-demo
npm install
npm install --save-dev webpack-merge

创建文件webpack.common.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'React + ReactRouter Demo',
filename: './index.html', // 调用的文件
template: './index.html', // 模板文件
}),
],
output: {
filename: '[name].bundle.js',
chunkFilename: '[chunkhash].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
module: {
rules: [{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
exclude: [
path.resolve(__dirname, 'node_modules'),
],
options: {
plugins: ['transform-async-to-generator', 'transform-strict-mode', 'transform-object-assign', 'transform-decorators-legacy', 'react-hot-loader/babel'],
presets: ['es2015', 'react', 'stage-0'],
},
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
],
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader',
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader',
],
},
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader',
],
},
{
test: /\.xml$/,
use: [
'xml-loader',
],
},
],
},
resolve: {
extensions: ['.js', '.jsx'], // 这里是必须要加的,不然默认的值加载['.js','.json']为后缀的文件
alias: {
Actions: path.resolve(__dirname, 'src/actions'),
},
},
};

创建文件webpack.dev.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const merge = require('webpack-merge');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');
const common = require('./webpack.common');

module.exports = merge(common, {
mode: 'development',
devtool: 'eval',
entry: {
app: [
'webpack/hot/only-dev-server',
'react-hot-loader/patch',
'./src/index.jsx',
],
},
devServer: {
hot: true,
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 8083,
historyApiFallback: {
rewrites: [{
from: /^\/$/,
to: './index.html',
}],
},
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'React + ReactRouter Demo',
filename: './index.html', // 调用的文件
template: './index.html', // 模板文件
}),
new webpack.DefinePlugin({
'global.GENTLY': false,
__DEV__: true,
}),
],
});

创建文件webpack.prod.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const merge = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const common = require('./webpack.common');

module.exports = merge(common, {
mode: 'production',
entry: {
app: [
'./src/index.jsx',
],
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'React + ReactRouter',
filename: './index.html', // 调用的文件
template: './index.html', // 模板文件
}),
],
});

然后修改package.json

1
2
"build": "npx webpack --config webpack.prod.js",
"start": "npx webpack-dev-server --open --hot --config webpack.dev.js"

指定具体的配置文件

接下来分别执行

1
2
npm run start // 开发环境执行
npm run build // 生产环境执行

项目地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.6

为什么要实现按需加载或懒加载,引用下官网的话

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

1、现在clone原始项目如下

1
2
3
4
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git webpack-react-demo
cd webpack-react-demo
npm install
npm start

2、安装代码分割懒加载需要的库

1
2
npm install babel-plugin-syntax-dynamic-import --save-dev
npm install react-loadable

3、代码配置
webpack.config.js

1
2
3
4
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},

添加如下代码

1
chunkFilename: '[name].bundle.js',

结果如下

1
2
3
4
5
output: {
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},

.babelrc

1
2
3
4
5
6
7
"plugins": [
"transform-async-to-generator",
"transform-strict-mode",
"transform-object-assign",
"transform-decorators-legacy",
"react-hot-loader/babel"
],

plugins中添加如下代码

1
"syntax-dynamic-import"

结果如下

1
2
3
4
5
6
7
8
"plugins": [
"transform-async-to-generator",
"transform-strict-mode",
"transform-object-assign",
"transform-decorators-legacy",
"react-hot-loader/babel",
"syntax-dynamic-import"
],

4、修改代码调用
修改src/routes.jsx
添加

1
import Loadable from 'react-loadable';

将路由配置中的

1
<Route path="/about" component={AboutComponent} />

改为

1
2
3
4
5
6
7
<Route
path="/about"
component={Loadable({
loader: () => import('./components/AboutComponent'),
loading: LoadingComponent,
})}
/>

从webpack-dev-server控制台可以看出原来的如下图


更新代码后通过webpack-dev-server控制台可以看出多了一个类似0.bundle.js的文件,如下图

同样的逻辑我们更改 /topics对应的组件和/counter对应的组件
从webpack-dev-server控制台可以看出,多了几个类似的文件

如果我重新启动webpack-dev-server的话可以从webpack-dev-server控制台日志看出类似下图的输出

小知识点

1
chunkFilename: '[name].bundle.js',

改为

1
chunkFilename: '[chunkhash].bundle.js',

重新

1
npm start

看看日志输出,会看到类似如下的输出

这个特点有助于项目的版本发布需求

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag: v_1.0.4

之前的几篇文章写的很多,里面在开发环境下都用到了

1
2
3
4
5
6
7
historyApiFallback: {
rewrites: [{
from: /^\/$/,
to: './index.html',
},
],
},

但是在我们开发也许会遇到一个很奇怪的问题就是,当我们访问到二级以上的路由的情况下,在刷新页面会发现页面加载不出需要的js,此时会显示一个404,说文件不存在,但是仔细看的话发现加载的js路径不对呀,正常来说js的加载是在/下面的,如、xxx.js,但是当我们访问到比如/topics/1的时候,js请求的路径就变成了/topics/xxx.js,这个问题根据经验来说应该是html-webpack-plugin的原因,因为它在帮我们加入js文件的时候应该给我们一个base的参数,但是我也没有找到类似这样的参数,或者其他配置可以解决这个问题,最终让我追溯到了webpack的配置中output的选项中,其中有个publicPath这个选项

文档中的意思是

该选项的值是以 runtime(运行时) 或 loader(载入时) 所创建的每个 URL 为前缀。因此,在多数情况下,此选项的值都会以/结束。

那么我们就来设置下

1
2
3
4
5
output: {
filename: '[name].bundle.js',
chunkFilename: '[chunkhash].bundle.js',
path: path.resolve(__dirname, 'dist'),
},

中加入

1
publicPath: '/',

结果如下

1
2
3
4
5
6
output: {
filename: '[name].bundle.js',
chunkFilename: '[chunkhash].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},

保存后重启webpack-dev-server

1
npm start

然后再去刷新,发现问题得到了解决。在任何页面进行刷新就能保证正常的加载到js文件。

项目地址

1
2
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag: v_1.0.5

安装 zsh

1
sudo yum install zsh

或者

1
sudo apt install zsh

安装 oh-my-zsh

wget https://gitee.com/mirrors/oh-my-zsh/raw/master/tools/install.sh

进入编辑状态:

1
vim install.sh

找到以下部分:

1
2
3
4
5
# Default settings
ZSH=${ZSH:-~/.oh-my-zsh}
REPO=${REPO:-ohmyzsh/ohmyzsh}
REMOTE=${REMOTE:-https://github.com/${REPO}.git}
BRANCH=${BRANCH:-master}

然后将中间两行改为:

1
2
REPO=${REPO:-mirrors/oh-my-zsh}
REMOTE=${REMOTE:-https://gitee.com/${REPO}.git}

然后保存退出::wq

然后给install.sh添加权限:

1
chmod +x install.sh

然后执行install.sh:

1
./install.sh

执行即可

之前的几篇文章继承了redux,这里不得不说下与redux开发相关的一些配置redux-logger,其实有很多,也有另外一种方式,这里我觉得这个是比较好的,这里做下记录,跟大家分享下。

首先clone代码

1
2
3
4
5
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git redux-devtools
cd redux-devtools
npm install

npm start

然后安装redux-logger

1
npm install redux-logger

打开src/index.jsx
引入redux-logger

1
import { createLogger } from 'redux-logger';

更新修改代码结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const middleware = [];
if (typeof __DEV__ !== 'undefined') {
middleware.push(createLogger()); // 创建日志
}
// 为了添加多个中间件 我们重新改造middleware
middleware.push(routerMiddleware(history));

// 这行是DevTools的配置 后面做详细说明
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
connectRouter(history)(rootReducer),
initialState,
composeEnhancer(applyMiddleware(...middleware)), // 中间件引入
);

日志一般是在开发环境中使用,我们需要加个变量来做下控制,这里通过修改webpack.config.js中的plugins加入

1
2
3
4
new webpack.DefinePlugin({
'global.GENTLY': false,
__DEV__: true,
})

配置完结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

let config = {
entry: {
app: [
'webpack/hot/only-dev-server',
'react-hot-loader/patch',
'./src/index.jsx',
],
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'React + ReactRouter Demo',
filename: './index.html', // 调用的文件
template: './index.html', // 模板文件
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
exclude: [
path.resolve(__dirname, 'node_modules'),
],
options: {
plugins: ['transform-async-to-generator', 'transform-strict-mode', 'transform-object-assign', 'transform-decorators-legacy', 'react-hot-loader/babel'],
presets: ['es2015', 'react', 'stage-0'],
},
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
],
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader',
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader',
],
},
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader',
],
},
{
test: /\.xml$/,
use: [
'xml-loader',
],
},
],
},
resolve: {
extensions: ['.js', '.jsx'], // 这里是必须要加的,不然默认的值加载['.js','.json']为后缀的文件
alias: {
Actions: path.resolve(__dirname, 'src/actions'),
},
},
};

if (process.env.NODE_ENV === 'production') {
config = Object.assign({}, config, {
mode: 'production',
});
} else {
const {
plugins,
} = config;
plugins.push(new webpack.DefinePlugin({
'global.GENTLY': false,
__DEV__: true,
}));
config = Object.assign({}, config, {
mode: 'development',
devtool: 'eval',
devServer: {
hot: true,
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 8083,
historyApiFallback: {
rewrites: [{
from: /^\/$/,
to: './index.html',
},
],
},
},
plugins,
});
}

module.exports = config;

重新执行

1
npm start

打开计数器,通过chrome的devtools可以看到类似如下图的日志,这样我们在做数据交互的时候就能清晰的了解到具体的数据调用情况

这里前提说下,Chrome浏览器最好是安装下redux的Chrome扩展,不知道的可自行百度或google下,后面的文章有需要的话,在详细说下。

实践项目地址

https://github.com/durban89/webpack4-react16-reactrouter-demo.git

tag:v_1.0.3

继续上篇文章【Webpack4+React16+ReactRouter4+Redux整合开发】继续分享,这里我们针对细节的部分做下优化,主要是能更加高效的提升我们的开发效率

1、clone 实例代码并将代码运行起来

1
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git react-hot-loader-demo && cd react-hot-loader-demo
1
npm install

2、运行项目

1
npm start

3、修改webpack.config.js

修改webpack-dev-server的配置,添加

1
hot: true

结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
devServer: {
hot: true,
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 8083,
historyApiFallback: {
rewrites: [{
from: /^\/$/,
to: './index.html',
},
],
},
},

修改entry

1
2
3
4
5
6
7
entry: {
app: [
'webpack/hot/only-dev-server', // 这里新加
'react-hot-loader/patch', // 这里新加
'./src/index.jsx',
],
},

修改module中rule部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
test: /\.(js|jsx)$/,
loader: [
'babel-loader',
'react-hot-loader/webpack',
],
exclude: [
path.resolve(__dirname, 'node_modules'),
],
options: {
plugins: ['transform-async-to-generator', 'transform-strict-mode', 'transform-object-assign', 'transform-decorators-legacy'],
presets: ['es2015', 'react', 'stage-0'],
},
}

修改src/index.jsx
将原来的

1
2
3
4
5
6
7
8
9
10
11
12
ReactDOM.render(
(
<AppContainer>
<Provider store={store}>
<ConnectedRouter history={history}>
{routes}
</ConnectedRouter>
</Provider>
</AppContainer>
),
document.getElementById('root'),
);

替换为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const render = () => {
ReactDOM.render(
(
<AppContainer>
<Provider store={store}>
<App history={history} />
</Provider>
</AppContainer>
),
document.getElementById('root'),
);
};

render();

if (module.hot) {
module.hot.addStatusHandler((status) => {
console.log('status = ', status);
});

module.hot.accept('./App', () => {
// 这里是当前版本很重要的环节,不然的话react-hot-reload不起作用
require('./App').default;
render();
});

module.hot.accept('./reducers', () => {
store.replaceReducer(connectRouter(history)(rootReducer));
render();
});
}

添加src/App.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import PropTypes from 'prop-types';
import { ConnectedRouter } from 'connected-react-router';
import routes from './routes';

const App = ({ history }) => (
<ConnectedRouter history={history}>
{routes}
</ConnectedRouter>
);

App.propTypes = {
history: PropTypes.objectOf(PropTypes.any).isRequired,
};

export default App;

最后一个package.json中start命令修改

1
"start": "npx webpack-dev-server --open --hot"

最后重启项目

1
npm start

试着改改UI,会有神奇的效果

实践环境具体版本以项目的package.json为准

os: mac
node: v8.9.4

项目地址
https://github.com/durban89/webpack4-react16-reactrouter-demo.git
TAG: v_1.0.2

续接上文【Webpack4+React16+ReactRouter4整合开发

拉取github代码
https://github.com/durban89/webpack4-react16-reactrouter-demo.git

1
2
3
4
git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git webpack4-react16-reactrouter-redux
cd webpack4-react16-reactrouter-redux
npm install
npm start

如果没有异常就可以正常写代码了

首先看下react-router-redux,从react-router官网的redux介绍部分可以看出写的是React Router Redux,但是点击后会告诉你Deprecated。我的天!废弃了,好吧,然后下面告诉你connected-react-router
我的天,又造了一个轮子,看来要重头学起了,没关系,咱们就是不怕折腾,人活着就是折腾嘛。
我们链接到connected-react-router,各种的炫耀夸,太多了,各种优点,我们还是来点实际的,操作开始

1
npm install redux react-redux history react-hot-loader connected-react-router --save

如何使用
修改src/index.jsx

1、第一步
添加第一部分 - 导入使用的库函数

1
2
3
4
5
import { AppContainer } from 'react-hot-loader';
import { createBrowserHistory } from 'history';
import { applyMiddleware, compose, createStore } from 'redux';
import { Provider } from 'react-redux';
import { connectRouter, ConnectedRouter, routerMiddleware } from 'connected-react-router';

添加第二部分 - 需要自己去定义

1
2
import rootReducer from './reducers'; // 这里的文件是新加的 - 具体是如何创建的可以看下项目代码
import routes from './routes'; // 这里的文件是新加的 - 具体是如何创建的可以看下项目代码

添加第三部分 - 配置redux相关的逻辑

1
2
3
4
5
6
7
8
const history = createBrowserHistory();
const initialState = {};
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
connectRouter(history)(rootReducer),
initialState,
composeEnhancer(applyMiddleware(routerMiddleware(history))),
);

添加第四部分 - 整合react-router

1
2
3
4
5
6
7
8
9
10
11
12
ReactDOM.render(
(
<AppContainer>
<Provider store={store}>
<ConnectedRouter history={history}>
{routes}
</ConnectedRouter>
</Provider>
</AppContainer>
),
document.getElementById('root'),
);

最终src/index.jsx内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React from 'react';
import { AppContainer } from 'react-hot-loader';
import { createBrowserHistory } from 'history';
import { applyMiddleware, compose, createStore } from 'redux';
import { Provider } from 'react-redux';
import { connectRouter, ConnectedRouter, routerMiddleware } from 'connected-react-router';
import ReactDOM from 'react-dom';
import rootReducer from './reducers';
import routes from './routes';

const history = createBrowserHistory();
const initialState = {};
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
connectRouter(history)(rootReducer),
initialState,
composeEnhancer(applyMiddleware(routerMiddleware(history))),
);

ReactDOM.render(
(
<AppContainer>
<Provider store={store}>
<ConnectedRouter history={history}>
{routes}
</ConnectedRouter>
</Provider>
</AppContainer>
),
document.getElementById('root'),
);

src/routes.jsx 主要配置路由跟组件的关系 -> 上面这行import routes from ‘./routes’;代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React from 'react';
import {
BrowserRouter as Router,
Route,
Link as ALink,
} from 'react-router-dom';

import AppComponent from './components/AppComponent';
import HomeComponent from './components/HomeComponent';
import AboutComponent from './components/AboutComponent';
import TopicsComponent from './components/TopicsComponent';

const routes = (
<div>
<Router>
<AppComponent>
<ul>
<li><ALink to="/">首页</ALink></li>
<li><ALink to="/about">关于</ALink></li>
<li><ALink to="/topics">论题</ALink></li>
</ul>
<hr />

<Route exact path="/" component={HomeComponent} />
<Route path="/about" component={AboutComponent} />
<Route path="/topics" component={TopicsComponent} />
</AppComponent>
</Router>
</div>
);

export default routes;

2、第二步
配置好了,就要开始使用了
1】、创建reducers
src/reducers/counter.js

1
2
3
4
5
6
7
8
9
10
11
12
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};

export default counterReducer;

2】、将counter.js加入到主reducers里面

1
2
3
4
5
6
7
src/reducers/index.js // 这里的文件就是对应这段代码 import rootReducer from './reducers';
import { combineReducers } from 'redux';
import counterReducer from './counter';

export default combineReducers({
count: counterReducer,
});

3】、创建actions

src/actions/counter.js
1
2
3
4
5
6
7
8

export const increment = () => ({
type: 'INCREMENT',
});

export const decrement = () => ({
type: 'DECREMENT',
});

4】、添加一个组件src/components/CounterComponent.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { counter } from 'Actions';

const {
increment, decrement,
} = counter;

class CounterComponent extends Component {
constructor(props, context) {
super(props, context);

this.state = {};
}

render() {
const {
count,
} = this.props;

return (
<div>
<p>数值: {count}</p>
<button onClick={this.props.doIncrement}>+</button>
<button onClick={this.props.doDecrement}>-</button>
</div>
);
}
}

CounterComponent.propTypes = {
count: PropTypes.number,
doIncrement: PropTypes.func.isRequired,
doDecrement: PropTypes.func.isRequired,
};

CounterComponent.defaultProps = {
count: 0,
};

const mapStateToProps = state => ({
count: state.count,
});

const mapDispatchToProps = dispatch => ({
doIncrement: () => dispatch(increment()),
doDecrement: () => dispatch(decrement()),
});

export default connect(mapStateToProps, mapDispatchToProps)(CounterComponent);

5】、将组件加到路由里面

src/routes.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react';
import {
BrowserRouter as Router,
Route,
Link as ALink,
} from 'react-router-dom';

import AppComponent from './components/AppComponent';
import HomeComponent from './components/HomeComponent';
import AboutComponent from './components/AboutComponent';
import TopicsComponent from './components/TopicsComponent';
import CounterComponent from './components/CounterComponent';

const routes = (
<div>
<Router>
<AppComponent>
<ul>
<li><ALink to="/">首页</ALink></li>
<li><ALink to="/about">关于</ALink></li>
<li><ALink to="/topics">论题</ALink></li>
<li><ALink to="/counter">计数器</ALink></li>
</ul>
<hr />

<Route exact path="/" component={HomeComponent} />
<Route path="/about" component={AboutComponent} />
<Route path="/topics" component={TopicsComponent} />
<Route path="/counter" component={CounterComponent} />
</AppComponent>
</Router>
</div>
);

export default routes;

6】、为了引入方便再加一个src/actions/index.js

1
2
3
4
5
6
7
8
import counter from './counter';

const actions = {
counter,
};

export default actions;
export { counter };

到这里基本上就完工了,应该可以在页面看到计数器,并进行操作了

1
import { counter } from 'Actions';

这里是比较特殊的
这个要归功于webpack

1
2
3
4
5
6
resolve: {
extensions: ['.js', '.jsx'], // 这里是必须要加的,不然默认的值加载['.js','.json']为后缀的文件
alias: {
Actions: path.resolve(__dirname, 'src/actions'),
},
},

这里我们加了这个配置,于是在引入的时候就会正常引入需要的代码了。

好了,整个配置就完成了,项目我已经上传到github了,需要的同学可以自行去下载查看

实例项目地址:

https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag: v_1.0.1

这次分享的内容有部分是跟之前的文章【webpack4手动配置安装react开发】大概类似,不同的是这次我们要结合reactr-router进行开发

1、 创建项目并安装

1
mkdir webpack4_react16_reactrouter && cd webpack4_react16_reactrouter
1
npm init -y
1
npm install react react-dom  prop-types react-router-dom
1
npm install webpack webpack-cli html-webpack-plugin clean-webpack-plugin webpack-dev-server eslint eslint-plugin-html eslint-plugin-react babel-eslint eslint-config-airbnb eslint-plugin-jsx-a11y eslint-plugin-import babel-core babel-loader babel-plugin-transform-strict-mode babel-plugin-transform-object-assign babel-plugin-transform-decorators-legacy babel-preset-es2015 babel-preset-react babel-preset-stage-0 style-loader css-loader url-loader --save-dev
  • react开发需要用到的
  • babel相关的是用来做es5/es6语法解析的
  • eslint相关的是用来做语言检查的

2、eslint和webpack相关配置
.eslintrc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"env": {
"browser": true,
"node": true,
"es6": true,
"jquery": true
},
"parser": "babel-eslint",
"plugins": [
"react",
"html"
],
"extends": [
"airbnb"
],
"rules": {
"no-underscore-dangle": 0
}
}

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

let config = {
entry: {
app: ['./src/index.jsx'],
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'React + ReactRouter Demo',
filename: './index.html', // 调用的文件
template: './index.html', // 模板文件
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
exclude: [
path.resolve(__dirname, 'node_modules'),
],
options: {
plugins: ['transform-async-to-generator', 'transform-strict-mode', 'transform-object-assign', 'transform-decorators-legacy'],
presets: ['es2015', 'react', 'stage-0'],
},
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
],
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader',
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader',
],
},
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader',
],
},
{
test: /\.xml$/,
use: [
'xml-loader',
],
},
],
},
resolve: {
extensions: ['.js', '.jsx'], // 这里是必须要加的,不然默认的值加载['.js','.json']为后缀的文件
},
};

if (process.env.NODE_ENV === 'production') {
config = Object.assign({}, config, {
mode: 'production',
});
} else {
config = Object.assign({}, config, {
mode: 'development',
devtool: 'eval',
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 8083,
},
});
}

module.exports = config;

index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>React + ReactRouter Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="root"></div>
</body>
</html>

基本上整个目录结构就搭建完了。还是蛮繁琐的,但是自己了解了其中整个逻辑及流程,对自己是有意的,不过说不好未来就成体系了,直接调用就好了,比如现在的create-react-app,可以直接帮你创建一个项目,很不错的一个选择。
但是搭建一个符合自己需求的更有价值。
基本的都弄完了,接下来如何使用ReactRouter呢,先从入口文件开始
src/index.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Route,
Link as ALink,
} from 'react-router-dom';

import AppComponent from './components/AppComponent';
import HomeComponent from './components/HomeComponent';
import AboutComponent from './components/AboutComponent';
import TopicsComponent from './components/TopicsComponent';

ReactDOM.render(
(
<Router>
<AppComponent>
<ul>
<li><ALink to="/">首页</ALink></li>
<li><ALink to="/about">关于</ALink></li>
<li><ALink to="/topics">论题</ALink></li>
</ul>
<hr />

<Route exact path="/" component={HomeComponent} />
<Route path="/about" component={AboutComponent} />
<Route path="/topics" component={TopicsComponent} />
</AppComponent>
</Router>
),
document.getElementById('root'),
);

基本上整个实例是参考了官网的实例,只是针对自己的需求做局部的调整,对于需要使用React开发的同学完全可以入手了,关于数据相关的后面再继续讨论。

这里有个很重要的点,就是在使用webpack-dev-server的使用,当你跳转到某个路由的时候,再刷新会发现页面提示找不到,这个问题这里介绍一个解决方案
historyApiFallback
只需要在webpack.config.js中配置下

1
2
3
4
5
6
7
historyApiFallback: {
rewrites: [{
from: /^\/$/,
to: './index.html',
},
],
},

最终的文件结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

let config = {
entry: {
app: ['./src/index.jsx'],
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'React + ReactRouter Demo',
filename: './index.html', // 调用的文件
template: './index.html', // 模板文件
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
exclude: [
path.resolve(__dirname, 'node_modules'),
],
options: {
plugins: ['transform-async-to-generator', 'transform-strict-mode', 'transform-object-assign', 'transform-decorators-legacy'],
presets: ['es2015', 'react', 'stage-0'],
},
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
],
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader',
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader',
],
},
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader',
],
},
{
test: /\.xml$/,
use: [
'xml-loader',
],
},
],
},
resolve: {
extensions: ['.js', '.jsx'], // 这里是必须要加的,不然默认的值加载['.js','.json']为后缀的文件
},
};

if (process.env.NODE_ENV === 'production') {
config = Object.assign({}, config, {
mode: 'production',
});
} else {
config = Object.assign({}, config, {
mode: 'development',
devtool: 'eval',
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 8083,
historyApiFallback: {
rewrites: [{
from: /^\/$/,
to: './index.html',
},
],
},
},
});
}

module.exports = config;

在运行npm start,修改代码试试,这里强调下版本,如果你的版本比我的新的话,要自己去看下官网的api是否有调整

实例环境,拉取github代码,看package.json,一切尽在你掌握之中
实例项目地址:

https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag: v_1.0.0

0%