react+webpack5搭建环境
前言
工作中用到,练习搭建react + webpack5环境
计划是这样的:
-
创建一个基本项目结构
-
针对不同资源进行分别处理
-
对开发环境和生产环境有差异化的配置分开不同配置文件,公共部分放一块
准备一个基本npm项目
- 新建一个目录 react-webpack5-template,初始化并安装
webpack-cli
mkdir react-webpack5-template
cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
-
webpack-cli
: 在命令行中运行webpack
的工具 -
--save-dev
: 开发环境依赖,开发时会用到,自动把模块和版本号添加到devDependencies部分
- 按以下结构创建文件
react-webpack5-template
|- config
|- webpack.base.config.js // webpack 基本配置
|- webpack.dev.config.js // webpack 开发环境配置
|- webpack.prod.config.js // webpack 生产环境配置
|- node-modules //初始化时自动创建
|- public
|- index.html
|- /src
|- index.js //入口文件
|- app.js //第一个组件
|- app.css
|- package.json //初始化时自动创建
|- package-lock.json //初始化时自动创建
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>react-webpack5</title>
</head>
<body>
<!-- 根标签 -->
<div id="root"></div>
</body>
</html>
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
ReactDOM.render(<App />, document.getElementById('root'));
app.js
import React, { Component } from 'react';
import './app.css';
class App extends Component {
render() {
return (
<div className="app">
第一
</div>
);
}
}
export default App;
app.css
.app {
background-color: blue;
}
好的,项目初始结构就这样子。
基本配置
安装react
npm install --save react react-dom
-
可以使用类似
npm install --save react@16.14.0 react-dom@16.14.0
安装指定版本,其他包同理 -
--save
是项目依赖,项目运行时必须的依赖,自动把模块和版本号添加到dependencies部分
添加webpack.base.config.js配置
const path = require('path');
module.exports = {
entry: {
index: './src/index.js',
},
output: {
filename: 'js/bundle.js',
path: path.resolve(__dirname, '../dist')
}
}
-
entry配置入口文件
-
output 配置打包生成文件
到这里项目还不能跑,因为webpack不认识 react 的 jsx语法, 需要安装 babel 进行预处理
配置js打包
安装 Babel (处理js/jsx等文件)
Babel 是一个工具链,主要用于在当前和旧的浏览器或环境中,将 ECMAScript 2015+ 代码转换为 JavaScript 向后兼容版本的代码。以下是 Babel 可以做的主要事情:
转换语法为html文件中引入的外部资源如
script
、link
动态添加每次compile后的hash,防止引用缓存的外部文件问题Polyfill 目标环境中缺少的功能(通过如 core-js 的第三方
polyfill
)源代码转换(codemods)
以及更多!
可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个
html-webpack-plugin
可以生成N个页面入口
npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/plugin-transform-runtime
- babel-loader:通过babel 处理 JavaScript 文件。
- @babel/core:babel 的核心模块
- @babel/preset-env:默认转译 ES6语法到ES5
- @babel/preset-react:转换 JSX 为函数
- @babel/plugin-proposal-class-properties:支持类属性
- @babel/plugin-proposal-object-rest-spread:支持对象展开
- @babel/plugin-transform-runtime:不污染全局变量,代码复用和减少打包体积
在webpack.base.config.js 中添加对应配置:
const path = require("path");
module.exports = {
entry: {
index: "./src/index.js",
},
output: {
filename: "js/bundle.js",
path: path.resolve(__dirname, "../dist"),
},
module: {
rules: [
{ //babel 处理对应文件
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-class-properties",
],
},
},
},
],
},
};
配置html打包
安装 HtmlWebpackPlugin
HtmlWebpackPlugin
简化了HTML文件的创建,以便为你的webpack包提供服务。
为html文件中引入的外部资源如
script
、link
动态添加每次compile后的hash,防止引用缓存的外部文件问题可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个
html-webpack-plugin
可以生成N个页面入口
npm install --save-dev html-webpack-plugin
添加对应配置到 webpack.base.config.js
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: "./src/index.js",
},
output: {
filename: "js/bundle.js",
path: path.resolve(__dirname, "../dist"),
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-class-properties",
],
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
filename: 'index.html',
inject: "body",
hash: false,
}),
],
};
-
template: 基于给的html文件为模板生成html文件
-
filename:打包后生成的文件名称
-
inject:将 js 文件注入到 body 底部
-
hash:如果是true,则会在html中引入的css和script文件名后自动添加一串hash字符类似这样:
<script defer="defer" src="js/bundle.js?49ca999d5677fd86f6d1"></script>
配置样式资源打包
- 安装处理 css 样式文件的插件
style-loader
通过 标签把 CSS 插入到 DOM 中
css-loader
会对@import
和url()
进行处理,就像 js 解析import/require()
一样
npm install --save-dev style-loader css-loader
添加对应配置到 webpack.base.config.js
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: "./src/index.js",
},
output: {
filename: "js/bundle.js",
path: path.resolve(__dirname, "../dist"),
},
module: {
rules: [
{ //处理 css
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-class-properties",
],
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
inject: "body",
hash: false,
}),
],
};
-
style-loader能够在需要载入的html中创建一个
<style></style>
标签,标签里的内容就是CSS内容 -
css-loader是允许在js中
import
一个css文件,会将css文件当成一个模块引入到js文件中 -
需要先执行css-loader加载样式文件, 然后style-loader将样式文件注入html页面
-
由于webpack 里 loader 是从右往左执行的,所以 style-loader 须在前, css-loader 在后
至此,我们开始创建的项目文件类型都已经可以被处理,在 package.js
的 script
中添加如下命令:
"build": "webpack --config ./config/webpack.base.config.js",
然后运行 npm run build
便可以打包了, 以下再添加几项常用的配置:
- 添加处理less的loader
less-loader
将 Less 编译为 CSS
npm install less less-loader --save-dev
添加以下配置到 webpack 的 module 下的rules中
{ //处理 less
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
- 添加处理sass的loader
npm install sass sass-loader node-sass --save-dev
添加以下配置到 webpack 的 module 下的rules中
{ //处理 sass
test: /\.s[ac]ss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
- 配置postcss
安装postcss:
postcss 是一个用 JavaScript 工具和插件转换 CSS 代码的工具,常见的功能如:
自动补全浏览器前缀
将最新的 CSS 语法转换成大多数浏览器都能理解的语法,并根据你的目标浏览器或运行时环境来确定你需要的 polyfills
增强css书写体验
npm install postcss postcss-loader autoprefixer --save-dev
在根目录创建 postcss.config.js
,设置内容如下:
module.exports = {
plugins: [
require('autoprefixer')
],
};
在根目录创建 .browserslistrc
, 内容如下:
defaults
not ie < 11
last 2 versions
> 1%
iOS 7
last 3 iOS versions
或者在 package.json
中加入如下内容:
"browserslist": [
"defaults",
"not ie < 11",
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
- browserslist 的配置用于告诉
postcss
的浏览器作用范围
修改样式资源打包配置为如下内容:
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
],
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader',
],
},
{
test: /\.s[ac]ss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
],
},
配置图片资源打包
安装 html-loader
,用于解析 html
中的图片路径
html-loader
将html文档以字符串形式导出
npm install html-loader --save-dev
- 后面字体与音频和图片打包类似
添加以下配置到 webpack 的 module 下的rules中
{ // 解析html中的src路径并加载js中引入的html资源
test: /\.html$/,
use: "html-loader",
},
{ // 对图片资源文件进行处理
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: "asset",
exclude: [path.resolve(__dirname, "src/assets/imgs")],
generator: {
filename: "imgs/[name].[contenthash][ext]",
},
},
配置字体资源打包
添加以下配置到 webpack 的 module 下的rules中
{
// 对字体资源文件进行处理
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: "asset",
generator: {
filename: "fonts/[name].[contenthash][ext]",
},
},
配置音频资源打包
{ // 对音频资源文件进行处理
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: "asset",
exclude: [path.resolve(__dirname, "src/assets/medias")],
generator: {
filename: "medias/[name].[contenthash][ext]",
},
},
配置resolve
在 添加resolve配置:
module.exports = {
resolve: {
extensions: ['.js', '.jsx', '.json', '.less', '.scss'],
alias: {
@src: path.join(__dirname, '../src')
}
},
}
-
extensions
自动解析确定的扩展,能够使用户在引入模块时不带扩展 -
alias
创建import
或require
的别名,来确保模块引入变得更简单
但是到这里是远远不够的,我需要有开发环境和生产环境打包配置,才能既方便开发, 又能打包部署。
开发环境配置
- 安装使用
webpack-merge
webpack-merge
提供了一个合并函数,可以连接数组和合并对象,创建一个新对象。如果遇到函数,它将执行它们,通过算法运行结果,然后将返回的值再次包裹在一个函数内。简单来说就是可以合并webpack的配置
npm install --save-dev webpack-merge
添加以下内容到 webpack.dev.config.js
中
const path = require('path');
const { merge } = require('webpack-merge');
const common = require('./webpack.base.config.js');
module.exports = merge(common, {
mode: 'development',
});
- 配置source-map
source-map 能够方便开发环境调试代码,有多种类型,根据自己需要选择
添加以下内容到 webpack.dev.config.js
中
const path = require('path');
const { merge } = require('webpack-merge');
const common = require('./webpack.base.config.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
});
- 使用eval打包源文件模块,直接在源文件中写入干净完整的source-map,不影响构建速度,但影响执行速度和安全,建议开发环境中使用,生产阶段不要使用
- 配置代码热加载, 这里我们使用
webpack-dev-server
安装 webpack-dev-server
npm install --save-dev webpack-dev-server
添加以下内容到 webpack.dev.config.js
中
const path = require("path");
const { merge } = require("webpack-merge");
const common = require("./webpack.base.config.js");
module.exports = merge(common, {
mode: "development",
devtool: "eval-source-map",
devServer: {
host: "0.0.0.0",
port: 8081,
static: {
directory: path.join(__dirname, "dist"),
},
compress: true,
},
});
-
directory:告诉服务器从哪里提供内容
-
host:服务器host, 默认为localhost
-
port:访问端口号
-
compress:启用gzip压缩
在 package.js
的 script
中添加如下命令:
"dev": "webpack serve --open --config ./config/webpack.dev.config.js",
然后运行 npm run dev
便可以打开开发环境进行代码调试了。
生产环境配置
由于生产环境与开发环境要求有很大不同,所以接下来我会将有差异的部分分别放在 webpack.dev.config.js
和 webpack.prod.config.js
,而将公共部分放在 webpack.base.config.js
首先添加以下内容到 webpack.prod.config.js
中
const { merge } = require("webpack-merge");
const common = require("./webpack.base.config.js");
module.exports = merge(common, {
mode: "production",
});
- 调整输出,给打包文件加hash, 避免缓存问题
const { merge } = require("webpack-merge");
const common = require("./webpack.base.config.js");
module.exports = merge(common, {
mode: "production",
output: {
filename: 'js/[name]-bundle-[hash:6].js',
},
});
- 调整
HtmlWebpackPlugin
插件, 对prod环境移除注释, 这里移除webpack.base.config.js
文件中的HtmlWebpackPlugin
配置,将webpack.prod.config.js
改成如下内容:
const { merge } = require("webpack-merge");
const common = require("./webpack.base.config.js");
module.exports = merge(common, {
mode: "production",
output: {
filename: 'js/[name]-bundle-[hash:6].js',
},
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
filename: "index.html",
inject: "body",
minify: {
removeComments: true,
},
}),
],
});
在 webpack.dev.config.js
中同样位置加入如下内容:
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
inject: "body",
hash: false,
}),
],
- 将样式文件分离出来
安装 mini-css-extract-plugin
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
建议
mini-css-extract-plugin
与css-loader
一起使用。不要同时使用
style-loader
与mini-css-extract-plugin
npm install --save-dev mini-css-extract-plugin
这里移除 webpack.base.config.js
文件中 module
里面关于样式的配置,将 webpack.prod.config.js
改成如下内容:
const { merge } = require("webpack-merge");
const common = require("./webpack.base.config.js");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = merge(common, {
mode: "production",
output: {
filename: "js/[name]-bundle-[hash:6].js",
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"less-loader",
],
},
{
test: /\.(sass|scss)$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"sass-loader",
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
filename: "index.html",
inject: "body",
minify: {
removeComments: true,
},
}),
new MiniCssExtractPlugin({
filename: "style/[name].[hash:6].css",
}),
]
});
在 webpack.dev.config.js
中同样位置加入如下内容:
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
],
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader',
],
},
{
test: /\.s[ac]ss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
],
},
],
},
压缩打包出的样式文件,安装 css-minimizer-webpack-plugin
:
这个插件使用 cssnano 优化和压缩 CSS。
npm install css-minimizer-webpack-plugin --save-dev
在 webpack.prod.config.js
中导入插件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
在 plugins
配置下面(不是里面)添加:
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
],
},
- 这里的插件也可以放在
plugins
配置里, 区别是放在这里由minimize
决定是否生效,放在plugins
里一直生效
- 压缩js文件,安装
terser-webpack-plugin
:
该插件使用 terser 来压缩 JavaScript。
webpack v5 开箱即带有最新版本的
terser-webpack-plugin
。如果你使用的是 webpack v5 或更高版本,同时希望自定义配置,那么仍需要安装terser-webpack-plugin
。
npm install terser-webpack-plugin --save-dev
在 webpack.prod.config.js
中导入插件
const TerserWebpackPlugin = require("terser-webpack-plugin");
在 optimization
配置的 minimizer
里面添加:
new TerserWebpackPlugin({
terserOptions: {
compress: {
drop_console: true,
},
},
}),
- drop_console:压缩时是否删除console
- 打包编译前清理 dist 目录,安装
clean-webpack-plugin
:
clean-webpack-plugin
是一个清除文件的插件
npm install --save-dev clean-webpack-plugin
在 webpack.prod.config.js
中导入插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
在 plugins
配置里面添加:
new CleanWebpackPlugin(),
修改 package.js
里面的build命令为:
"build": "webpack --config ./config/webpack.prod.config.js",
运行 npm run build
就可以打包了。
文件汇总
package.json
{
"name": "react-webpack5",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack serve --open --config ./config/webpack.dev.config.js",
"build": "webpack --config ./config/webpack.prod.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.18.9",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-object-rest-spread": "^7.18.9",
"@babel/preset-env": "^7.18.9",
"@babel/preset-react": "^7.18.6",
"autoprefixer": "^10.4.8",
"babel-loader": "^8.2.5",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"html-loader": "^4.1.0",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"mini-css-extract-plugin": "^2.6.1",
"node-sass": "^7.0.1",
"postcss": "^8.4.14",
"postcss-loader": "^7.0.1",
"sass": "^1.54.0",
"sass-loader": "^13.0.2",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.3",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@babel/plugin-transform-runtime": "^7.18.9",
"react": "^16.14.0",
"react-dom": "^16.14.0"
},
"browserslist": [
"defaults",
"not ie < 11",
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
}
webpack.base.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
index: "./src/index.js",
},
output: {
filename: "js/bundle.js",
path: path.resolve(__dirname, "../dist"),
},
module: {
rules: [
{
// 解析html中的src路径并加载js中引入的html资源
test: /\.html$/,
use: "html-loader",
},
{
// 对图片资源文件进行处理
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: "asset",
exclude: [path.resolve(__dirname, "src/assets/imgs")],
generator: {
filename: "imgs/[name].[contenthash][ext]",
},
},
{
// 对字体资源文件进行处理
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: "asset",
generator: {
filename: "fonts/[name].[contenthash][ext]",
},
},
{ // 对音频资源文件进行处理
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: "asset",
exclude: [path.resolve(__dirname, "src/assets/medias")],
generator: {
filename: "medias/[name].[contenthash][ext]",
},
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-class-properties",
],
},
},
},
],
},
plugins: [
],
};
webpack.dev.config.js
const path = require("path");
const { merge } = require("webpack-merge");
const common = require("./webpack.base.config.js");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = merge(common, {
mode: "development",
devtool: "eval-cheap-module-source-map",
devServer: {
host: "0.0.0.0",
port: 8081,
static: {
directory: path.join(__dirname, "dist"),
},
compress: true,
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\.less$/,
use: ["style-loader", "css-loader", "postcss-loader", "less-loader"],
},
{
test: /\.s[ac]ss$/,
use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
inject: "body",
hash: false,
}),
],
});
webpack.prod.config.js
const path = require("path");
const { merge } = require("webpack-merge");
const common = require("./webpack.base.config.js");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
module.exports = merge(common, {
mode: "production",
output: {
filename: "js/[name]-bundle-[hash:6].js",
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"less-loader",
],
},
{
test: /\.(sass|scss)$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"sass-loader",
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
filename: "index.html",
inject: "body",
minify: {
removeComments: true,
},
}),
new MiniCssExtractPlugin({
filename: "style/[name].[hash:6].css",
}),
new CleanWebpackPlugin(),
],
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
new TerserWebpackPlugin({
terserOptions: {
compress: {
drop_console: true,
},
},
}),
],
},
});
总结
webpack 作为一个打包工具,功能挺丰富的,配置也是挺丰富的,这只是一个粗浅的配置,想要熟悉更多,还需多多在项目中实践。
配置方法有多种途径,个人可以按自己喜欢和理解的方式来,想要了解更多还是需要多查文档。
参考链接:
转载自:https://juejin.cn/post/7127657716524417031