本应用程序为webpack5.0 + React17 + antd4.x

搭建环境

初始化项目

初始化包

我们使用npm先创建一个package.json, 安装webpack    webpack-cli    react     react-dom     react-router-dom

1
2
3
4
5
npm init -y

yarn add webpack webpack-cli --dev

yarn add react react-dom react-router-dom

根目录创建webpack.config.js(暂时只考虑开发环境)

根目录下创建app文件夹作为我们的应用程序主入口,入口文件为app.js

项目结构如下

1
2
3
4
5
6
├─ app
|-app.js
├─ node_modules
|- ......
├─ webpack.config.js
└─ package.json

配置基本webpack

这里我们配置一些基本的webpack打包必须的配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const path = require("path")

module.exports = {
entry: {
index: {
import: '/app/app.js',
},
},
mode: "development",
output: {
filename: '[name].[contenthash].bundle.js',
chunkFilename: '[name].[id].js',
publicPath: '/',
path: path.resolve(__dirname, 'dist'),
},
devtool: 'inline-source-map',
}

管理资源

我们需要解析模块, 生成bundle, 这里我们使用babel-loader来转换js, less-loader     css-loader     style-loader来加载样式

1
2
3
yarn add less less-loader --dev
yarn add babel-loader --dev
yarn add css-loader style-loader --dev
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
const path = require("path")

module.exports = {
entry: {
index: {
import: '/app/app.js',
},
},
mode: "development",
output: {
filename: '[name].[contenthash].bundle.js',
chunkFilename: '[name].[id].js',
publicPath: '/',
path: path.resolve(__dirname, 'dist'),
},
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
include: path.resolve(__dirname, 'app'),
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader'],
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
}
}

由于用到了babel转换js,我们需要装一些babel的核心模块

1
yarn add @babel/core @babel/cli @babel/preset-env --dev

添加调试基础插件

接下来装一些便于我们调试的基础插件html-webpack-plugin    clean-webpack-plugin    webpack-manifest-plugin

1
yarn add html-webpack-plugin clean-webpack-plugin webpack-manifest-plugin --dev

创建模板index.html, 并添加插件

1
2
3
4
5
6
7
8
9
<html>
<head>
<meta charset="utf-8" />
<title>起步</title>
</head>
<body>
<div id="app"></div>
</body>
</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
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');

module.exports = {
entry: {
index: {
import: '/app/app.js',
},
},
mode: "development",
output: {
filename: '[name].[contenthash].bundle.js',
chunkFilename: '[name].[id].js',
publicPath: '/',
path: path.resolve(__dirname, 'dist'),
},
devtool: 'eval-source-map',
devServer: {
contentBase: '/dist',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
include: path.resolve(__dirname, 'app'),
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader'],
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
inject: true,
template: '/app/index.html',
}),
new WebpackManifestPlugin()
],
}

创建服务

新建本地服务, 添加模块热更新, 这里我们使用Express启动服务

我们在根目录创建一个server的文件夹, 创建一个index.js文件, 编写我们的启动脚本

这里我们需要安装webpack-dev-server     webpack-dev-middleware

1
2
3
yarn add express
yarn add webpack-dev-server --dev
yarn add webpack-dev-middleware --dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// /server/index.js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);

// 告知 express 使用 webpack-dev-middleware,
// 以及将 webpack.config.js 配置文件作为基础配置。
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
);

// 将文件 serve 到 port 3000。
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});

webpack.config.js文件添加contentBase,表明从哪里去寻找文件

1
2
3
devServer: {
contentBase: './dist', // dist为我们的输出目录
},

编写启动脚本

我们在package.json中编写一些脚本,便于我们启动服务和打包

1
2
3
4
5
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config webpack.config.js",
"start": "node server/index.js"
},

这时候基本的需要已经搭建完成, 我们在app.js中随便写点东西

1
console.log(123)


解析jsx

到此为止我们已经可以正确打包js文件, 正确启动脚本, 安装了应用程序一些必要的依赖, 但是我们还无法解析jsx语法

如果我们在app.js文件中写如下代码, 启动或编译

1
2
3
4
5
6
// app.js

import React from 'react'
import ReactDOM from 'react-dom'

ReactDOM.render(<div>111</div>, document.getElementById("app"))

我们会看到如下报错

因此我们需要安装babel转换jsx相关的插件

1
yarn add @babel/preset-react --dev

按照babel官网的教程,在根目录下创建babel.config.json

此时项目结构如下

1
2
3
4
5
6
7
8
9
10
├─ app
|-app.js
├─ node_modules
|- ......
├─ server
├─ index.js
├─ webpack.config.js
├─ babel.config.json
└─ package.json
└─ yarn.lock

在这里我们配一些基础的 js 转换的预设, 这里包括一个解析 jsxbabel预设,一个js转换的预设, 这里 module 设置为 false 是根据当前的调用者判断其已经支持ES6模块语法,则默认的auto将自动选择false,否则将选择 commonjs, 这里手动设置为 false 将不支持将 ES6 模块语法转换为其他模块类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// babel.config.json
{
"presets": [
[
"@babel/env",
{
"useBuiltIns": "usage",
"module": false
}
],
["@babel/preset-react",
{
"development": true
}
]
],
}

这里注意,babelpreset顺序是从后往前执行,plugin的顺序是从后往前执行

再次打包后成功

我们启动下试试看

同样, 使用class的写法也能正确解析, 这里有个注意点, 针对类, 我们需要使用@babel/plugin-proposal-class-properties,否则如下代码无法编译

1
2
3
4
5
6
7
8
class A {
say = () => {
console.log('我说话了')
}
}

let a = new A()
a.say()
1
yarn add @babel/plugin-proposal-class-properties --dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// babel.config.json
{
"presets": [
[
"@babel/env",
{
"useBuiltIns": "usage",
"module": false
}
],
["@babel/preset-react",
{
"development": true
}
]
],
"plugins": [
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}

安装antd

1
yarn add antd

我们修改app.js,导入antdButton看看效果如何

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// app.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Button } from 'antd'

class MyDemo extends React.Component{
constructor(props){
super(props)
this.state = {
buttonText: "我是按钮"
}
}

render(){
return <Button>{this.state.buttonText}</Button>
}
}

ReactDOM.render(<MyDemo />, document.getElementById("app"))

结果是确实引入了antd的组件,但是并未引入antd的样式

我们选择使用babel动态导入的方法引入样式

1
yarn add babel-plugin-import --dev

然后修改babel.config.json配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// babel.config.json
{
"presets": [
[
"@babel/env",
{
"useBuiltIns": "usage",
"modules": false
}
],
["@babel/preset-react",
{
"development": true
}
]
],
"plugins": [
["@babel/plugin-proposal-class-properties", { "loose": true }],
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
]
}

结果如下

浏览器兼容

为了确保项目需要, 我们保持ie10及以上的兼容, babel会根据配置文件的target, package.json, 根目录browserslist依次去寻找浏览器版本.这里我们在 package.json 中配置 browerslist

1
2
3
4
5
"browserslist": [
"last 1 version",
"> 1%",
"IE 10"
]

运行 npx browerslist得到如下图