More React App

webpack 搭建 React 项目系列文章:

  1. Hello React App
  2. More React App 项目开发配置
  3. Optimize React App 打包优化
  4. Release React App 发布到npm
  5. Extra React App

本片文章需要完成的工作

  1. 支持SCSSCSSModule
  2. 支持图片、图标字体等资源的编译
  3. 启用本地服务以启动项目
  4. 实现组件级热更新
  5. 区分开发和生产环境,分离配置

Todo

1、样式处理

webpack是不识别scss,css等非js文件,这里就需要引入一系列的laoder来处理:

  1. scss-laoder 将scss文件处理为css
  2. css-loader 解析css文件(如url(...)),CSSModule
  3. style-loader 将css文件以style标签的形式插入到目标html文件中

1.1、CSS

安装

1
npm i style-loader css-loader -D

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module: {
rules: [
// ...
{
test: /\.css$/,
exclude: /(node_modules)/,
// 注意loader的前后顺序
use: [
{
loader: "style-loader",
},
{
loader: "css-loader",
},
],
},
],
},

实践

index.js同级建立index.css

1
2
3
4
/* index.css */
.wrap {
background-color: red;
}
1
2
3
4
5
6
7
8
9
10
/* index.js */
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";

function App() {
return <div className="wrap">Hello World With React!</div>;
}

ReactDOM.render(<App></App>, document.getElementById("root"));

结果

用webpack打包后,可以发现css文件中的内容以字符串的形式被打包整合到main.js中,样式在index.html的head中以style标签的形式被使用【style-loader完成的】

1.2、SCSS

举一反三,这里只要使用scss对应的处理loader就可以了 —— sass-loader

1
npm i sass-loader node-sass -D

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module: {
rules: [
// ...
{
test: /\.css$/,
exclude: /(node_modules)/,
// 注意loader的前后顺序
use: [
{
loader: "style-loader",
},
{
loader: "css-loader",
},
{
loader: "sass-loader",
},
],
},
],
},

实践

1
2
3
4
5
6
7
8
9
10
11
12
/* index.scss */
$fontcolor: pink;
.inner {
background-color: blue;
.inner_test {
background-color: $fontcolor;
}
}

.inner_test {
background-color: skyblue;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/* index.js */
function App() {
return (
<div>
<p className="wrap">Hello World With React!</p>
<p className="inner">
I am Inner
<span className="inner_test">I am inner_test</span>
</p>
<span className="inner_test">I am inner_test</span>
</div>
);
}

1.3、CSSModule

只需要在css-loader中简单设置一下就完事了,其他的就是test匹配的策略问题了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module: {
rules: [
// ...
{
test: /\.module\.scss$/,
exclude: /(node_modules)/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: {
// 重新生成的 css 类名 [当前样式文件名]__[类名]--[hash]
localIdentName: "[name]__[local]--[hash:base64:5]",
},
},
},
{
loader: "sass-loader",
},
],
},
],
},

实践

项目中需要用一个变量来接受所引入的样式文件,如下

1
2
3
4
5
6
/* index.js */
import STYLES from './index.module.scss';

function App(){
return <div className={STYLES.wrap}>I Am The App</div>
}
1
2
3
4
/* index.module.scss */
.wrap {
background-color: skyblue;
}

策略

参考社区优秀的脚手架对对于CSSModule的设计,以*.module.*结尾的则开启CSSModule,而其他文件不开启

1
2
3
4
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;

具体细节:请见github

1.4、分离css文件

直接用loader解析样式文件,都会将样式文件打包进js文件中,当样式文件过大时,不利于css文件和js文件的并行加载等,这里样式独立成css文件。这里需要用到plugin了,这里使用mini-css-extract-plugin

安装

1
npm i mini-css-extract-plugin -D

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

plugins: [
//...
new MiniCssExtractPlugin({
// 此处进行plugin的配置
filename: "./assets/css/[name].css"
})
]

module: {
rules: [
// 将所有 style-loader 替换为 MiniCssExtractPlugin.loader
]
}

此时需要在index.html中手动引入样式文件,才能生效,至于怎么自动引入,在第三节可以了解到。

2、支持图片、图标字体等资源的编译

安装

1
npm i url-loader file-loader -D

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
test: /\.(png|jpg)$/,
exclude: /^node_modules$/,
use: [
{
loader: "url-loader",
options: {
limit: 8192,
// 这是相对于 entry目录的路径
outputPath: "./assets",
// 为静态资源地址应用自动增加前置路径
// 如url(./image.jpg) ==> url(./assets/iamge.jpg)
publicPath: "./assets",
},
},
],
}

实践

1
2
3
4
5
6
/* index.js */
import IMG_my from "./my.jpg"

function App(){
return <img src={IMG_my}></img>
}

3、html-webpack-plugin

在安装此插件之前,需要在html中手动引入相应的js和css文件,这违背了webpack自动化打包初衷,而html-webpack-plugin可以帮助我们自动创建html文件,并引入相应资源

安装

1
npm i html-webpack-plugin -D

配置

1
2
3
4
5
6
7
8
9
10
11
12
const HtmlWebpackPlugin = require("html-webpack-plugin");

plugin: [
new HtmlWebpackPlugin({
// 需要在项目的根目录下的public新建一个模板文件
template: "./public/index.html",
minify:{
removeComments:true,//清除注释
collapseWhitespace:true//清理空格
}
})
]

配套

clean-webpack-plugin来清除上一次生成的文件

1
npm i clean-webpack-plugin -D

并在webpack.config.js中实例化该插件

4、区分开发环境和生产环境

项目打包

5、webpack-dev-server

使用webpack-dev-server启动,生成的“文件”只存在于内存中,并不会生成真正的存储文件

安装

1
npm i webpack-dev-server@3.11.0 -D

此处建议固定版本

配置

使用webpack-dev-server启动项目即可

1
2
3
4
5
6
// package.json
"scripts": {
"build": "webpack",
"start": "webpack-dev-server --inline",
"test": "echo \"Error: no test specified\" && exit 1"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// webpack.config.js
devServer: {
// 本地服务器启动源文件路径
contentBase: "./dist",
historyApiFallback: true,
inline: true,
// 热更新
hot: true,
// 本地服务器端口号
port: 8080,
// 自动打开设备默认浏览器
open: true,
},

6、热更新

本小节实现react组件级更新

主要借助webpack-dev-server来实现热更新

6.1、样式热更新

6.2、js热更新

  1. 沿用上一步中的devServer的配置

  2. webpack.config.js新增plugin

    1
    2
    3
    4
    5
    const webpack = require("webpack")

    plugins: [
    + new webpack.HotModuleReplacementPlugin()
    ]
  3. 开始热更新,/src/index.js中增加语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // src/index.js
    import React from "react";
    import ReactDOM from "react-dom";

    import App from "./App";

    + if (module.hot) {
    + module.hot.accept(() => {
    + ReactDOM.render(<App />, document.getElementById("root"));
    + });
    + }

    ReactDOM.render(<App />, document.getElementById("root"));

6.3、react组件级热更新

安装

1
npm i react-hot-loader -D

配置

1
2
3
4
5
6
// .babelrc
{
+ "plugins": [
+ "react-hot-loader/babel"
+ ]
}
1
2
3
4
// webpack.config.js
{
entry: ["react-hot-loader/patch", path.join(__dirname, "./src/index.js")]
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// App.js
import { hot } from "react-hot-loader/root"; // 必须要在react和react-dom之前require
import React from "react";
import FunctionComponent from "@components/FunctionComponent";

function App() {
return (
<div>
<h1>Hello React</h1>
<FunctionComponent name={"哈哈哈"} />
<input></input>
</div>
);
}
export default hot(App);

到此,便实现了组件级热更新,当我们在input中输入值后,再去修改FunctionComponent组件,页面变化也不会清楚input的状态,也就是说组件之间的热更新不会相互影响,这样便能方便开发过程中的调试

7、分离开发与生产环境配置

待续…

2020.07.05晚前完成

8、按需加载

以Antd为例实现按需加载

最后更新: 2020年07月03日 00:48

原始链接: https://HowlCN1997.github.io/2020/03/13/More React App/

× 请我吃糖~
打赏二维码