手把手教你使用webpack从零搭建自己的React项目
前言
很多同学在使用react库时都是使用create-react-app官方脚手架去生成React项目, 这样有一个缺点就是我们的项目配置不够灵活,虽然可以使用eject命令去暴露一些配置,但是配置毕竟不是我们自己写的,所以改起来也会很头疼,但是如果是我们自己搭建的项目,那么我们改起配置就会很容易,并且这个过程会使我们巩固很多知识, 对webpack的使用会有一个基础的了解
本文需要有对webpack一定的基础知识,如果你的基础不是很好,请先去查阅webpack官网
版本号一致很重要!!!
webpack@5.51.1
webpack-cli@4.8.0
webpack-dev-server@4.2.1
结构目录
MyReact/
|-- config/
| |-- webpack.base.js
| |-- webpack.dev.js
| |-- webpack.pro.js
|-- dist/
|-- images/
|-- pages/
| |-- Home
|-- public/
| |-- index.html
|-- App.js
|-- index.js
|-- index.less
|-- package.json
|-- postcss.config.js
|-- yarn.lock
|-- README
1.生成包管理文件
执行 npm init
包管理文件,俗话说就是说用来管理node_modules的,下面是一些常用配置
{
"name": "project",
//如果项目要发布npm,这就是npm包的名称,如果未设置,就不能发布和下载
"version": "1.0.0",
//包的版本号,发布时版本不能相同
"description": "",
//包的描述信息
"main": "dist/main.js",
//包的入口文件
"scripts": {
//npm脚本命令,可以封装一些功能,执行快捷操作 如 npm run test
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
//包的关键词信息
"author": "",
//作者
"license": "ISC",
//开源协议名称
"files": [
//要发布的文件或文件夹
"dist",
"package.json"
],
"dependencies": {
//生产依赖,指上线后也需要的包,会打包到dist中
},
"devDependencies": {
//开发依赖,只是开发时会用到的包,不会打包到dist
},
"browserslist": {
//要兼容到的浏览器,它主要被以下工具使用:
//Autoprefixer
//Babel
//post-preset-env
//eslint-plugin-compat
//stylelint-unsupported-browser-features
//postcss-normalize
"production": [], //生产环境支持
"development": [] //开发环境支持
}
}
2.下载所需依赖
推荐使用yarn或者npm(npm如果很慢的话可以切一下镜像源)
npm install react react-dom -S
npm install webpack webpack-cli webpack-dev-server -D
下载完依赖后在public文件夹下创建index.html,这个html文件会作为我们的模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
接着新建入口文件index.js和App.js
//index.js
import ReactDom from 'react-dom';
import App from './App';
//渲染组件
ReactDom.render(<App/>, document.querySelector('#root'));
//App.js
import React from 'react';
const App = () => {
return <div>
<h1>App</h1>
</div>
}
export default App;
3.配置webpack开发文件
因为我们写react时使用的是jsx语法,但是浏览器它并不认识这个语法,所以我们要对这些文件进行转换,这里用到了babel相关的插件, 这些插件我们智慧在编译文件时用到,所以我们将它们下载到开发依赖
npm install babel-loader @babel/core @babel/preset-env @babel/preset-react html-webpack-plugin -D
在config文件夹下新建webpack.dev.js
//config/webpack.dev.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const resolve = p => path.resolve(__dirname, p);
module.exports = {
//模式
mode: "development",
//入口,告诉webpack起点是哪里,它会找到所有依赖并处理
entry: resolve('../index.js'),
//出口
output: {
//最终输出的的目录名称
path: resolve('dist'),
//最终输出的文件名称
filename: "main.js"
},
module: {
rules: [
{
//匹配js或jsx类型的文件
test: /\.jsx?$/,
//排除node_modules下的文件,因为node_module下的文件不用我们去处理,人家已经处理过了
exclude: /node_modules/,
//使用的loader
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
}
]
},
//所使用的插件
plugins: [
//生成html文件
new HtmlWebpackPlugin({
//使用html模板的目录
template: resolve('../public/index.html'),
//自动引入打包后的文件,默认值true
inject: true
})
],
//开发服务器配置
devServer: {
static: {
//目录
directory: resolve('dist')
},
//压缩
compress: true,
//热更新
hot: true,
//是否自动打开浏览器
open: true,
//端口号
port: 8888
}
}
在package.json的scripts中配置
"scripts": {
"dev": "webpack-dev-server --config=config/webpack.dev.js"
}
然后执行 npm run start
配置路径别名和忽略后缀名
resolve: {
extensions: ['.js', '.json'],
alias: {
"@": path.resolve(__dirname, 'pages/')
}
}
我们继续更改配置让它支持css文件,同样把它们下载到开发依赖
npm i style-loader css-loader postcss-loader postcss-preset-env -D
在根目录下新建postcss.config.js
//postcss.config.js
module.exports = {
plugins: [
// 自动将有兼容性的样式加浏览器前缀
require('postcss-preset-env')
]
}
package.json中增加配置
{
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
然后在根目录下新建index.css,然后在index.js中引入
/*
* index.css
*/
html,body{
height: 100%;
background: #f5f5f5;
}
h1{
color: red;
}
在webpack.dev.js中的rules增加新的配置
{
test: /\.css$/,
exclude: /node_modules/,
/*
* 这里有一点需要注意,当我们使用多个loader去处理文件的时候,它的处理顺序
* 是从右到左侧,从下往上,loader的执行顺序很重要
*/
//style-loader 帮我们生成style标签并插入到html中
//css-loader 处理css文件的loader
//postcss-loader 也使用来处理css文件的,需结合第三方库使用,比如postcss-preset-env autofixer
use:['style-loader','css-loader','postcss-loader']
}
接着把我们的css文件引入一下,发现样式生效了。
当然,我们的项目一般不会直接使用css文件,都会只用less或scss等这些css预处理器, 那么我们也配置一下less预处理器,让我们的项目支持less文件吧
npm i less less-loader -D
更改webpack rules配置,这样的话这个less和css文件就能被同时处理了
{
test: /\.(less|css)$/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
}
然后把index.css改成index.less,并写一些嵌套的语法,发现它同样生效。
如需使用其他预处理原理也是一样的,可以根据不同的需求去配置不同的配置
接着配置静态资源,webpack5已经不需要我们使用file-loader或url-loader去处理的图片了,它内部已经支持静态资源的处理了,并且可以配置limit将小图片输出成base64格式从而减少请求次数
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[hash][ext][query]'
}
}
然后我们找一张图片引入一下发现生效了
3.配置webpack打包环境
其实production环境和development环境的配置大同小异,只是会做一些文件优化和构建速度的优化, 比如压缩css文件,压缩html文件,开启多进程打包和使用缓存等
新建webpack.pro.js
// config/webpack.pro.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require("clean-webpack-plugin"); //用来清除上一次打包的文件目录的插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; //打包分析工具
const MiniCssExtractPlugin = require('mini-css-extract-plugin');//将css提取成单独的文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); //压缩css文件
const resolve = p => path.resolve(__dirname, p);
module.exports = {
//模式
mode: "production",
//入口,告诉webpack起点是哪里,它会找到所有依赖并处理
entry: resolve('../index.js'),
//出口
output: {
//最终输出的的目录名称
path: resolve('../dist'),
//最终输出的文件名称
filename: "./js/[name].js"
},
module: {
rules: [
{
//一种类型的文件只匹配一个loader,提高性能
oneOf: [
{
//匹配js或jsx类型的文件
test: /\.jsx?$/,
//排除node_modules下的文件,因为node_module下的文件不用我们去处理,作者已经处理过了
exclude: /node_modules/,
//使用的loader
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
{
test: /\.(less|css)$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[hash][ext][query]'
}
}
]
}
]
},
//用于提高性能,可以进行代码分割,
optimization: {
//告知webpack使用 TerserPlugin 或其它在 optimization.minimizer 定义的插件压缩 bundle。
minimize: true,
//分割模块
splitChunks: {
chunks: 'all'
},
//允许你通过提供一个或多个定制过的 TerserPlugin 实例, 覆盖默认压缩工具(minimizer)。
minimizer: [
new CssMinimizerPlugin()
]
},
//所使用的插件
plugins: [
new CleanWebpackPlugin(),
new BundleAnalyzerPlugin(),
new MiniCssExtractPlugin({
//输出的目录
filename: 'css/[hash].css'
}),
//生成html文件
new HtmlWebpackPlugin({
//使用html模板的目录
template: resolve('../public/index.html'),
//自动引入打包后的文件,默认值true
inject: true
})
]
}
增加build快捷命令
"scripts": {
"build": "webpack --config=config/webpack.pro.js"
}
接下来我们运行npm run build,会发现根目录下多了一个dist文件夹,这个dist文件夹就是最终打包出来的效果,同事webpack-bundle-analyzer也会帮我打开一个页面,让我们有助于观察摸个bundle的体积大小,从而让我们更清楚的看到优化空间
4.配置文件优化
其实不难看出,我们的dev文件和pro文件有好多的冗余代码,这样我们想改个配置两个文件要各改一遍,还会有一定的维护成本,所以我们可以将公共部分提取出来
在config下新建webpack.base.js并提取公共配置,
//config/webpack.base.js
const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require('webpack');
const resolve = p => path.resolve(__dirname, p);
module.exports = {
//入口,告诉webpack起点是哪里,它会找到所有依赖并处理
entry: resolve('../index.js'),
//出口
output: {
//最终输出的的目录名称
path: resolve('../dist'),
//最终输出的文件名称
filename:'./js/[name].js'
},
resolve: {
extensions: ['.js', '.json', '.wasm'],
alias: {
"@": path.resolve(__dirname, 'components/')
}
},
module: {
rules: [
{
//匹配js或jsx类型的文件
test: /.jsx?$/,
//排除node_modules下的文件,因为node_module下的文件不用我们去处理,人家已经处理过了
exclude: /node_modules/,
//使用的loader
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
{
test: /.(le|c)ss$/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[hash][ext][query]'
}
}
]
},
//所使用的插件
plugins: [
//生成html文件
new HtmlWebpackPlugin({
//使用html模板的目录
template: resolve('../public/index.html'),
//自动引入打包后的文件,默认值true
inject: true
}),
//使用插件定义全局变量DEV
new webpack.DefinePlugin({
'process':JSON.stringify({
env:'development'
})
})
],
node: {
global: true
},
}
修改webpack.dev.js
//config/webpack.dev.js
const path = require("path");
const resolve = p => path.resolve(__dirname, p);
const webpackBaseConfig = require('./webpack.base');//webpack公用配置
const {merge} = require('webpack-merge'); //用于合并webpack配置
const webpackDevConfig = {
devtool: 'source-map',
//模式
mode: "development",
//开发服务器配置
devServer: {
static: {
//目录
directory: resolve('dist')
},
//压缩
compress: true,
//热更新
hot: true,
//是否自动打开浏览器
open: true,
//端口号
port: 8888
}
}
const result = merge(webpackBaseConfig, webpackDevConfig)
module.exports = result;
修改webpack.pro.js
//config/webpack.pro.js
const {CleanWebpackPlugin} = require("clean-webpack-plugin"); //用来清除上一次打包的文件目录的插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; //打包分析工具
const MiniCssExtractPlugin = require('mini-css-extract-plugin');//将css提取成单独的文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); //压缩css文件
const webpackBaseConfig = require('./webpack.base');//webpack公用配置
const {merge} = require('webpack-merge'); //用于合并webpack配置
const webpack = require('webpack');
const webpackProConfig = {
//模式
mode: "production",
//用于提高性能,可以进行代码分割,
optimization: {
//告知webpack使用 TerserPlugin 或其它在 optimization.minimizer 定义的插件压缩 bundle。
minimize: true,
//分割模块
splitChunks: {
chunks: 'all'
},
//允许你通过提供一个或多个定制过的 TerserPlugin 实例, 覆盖默认压缩工具(minimizer)。
minimizer: [
new CssMinimizerPlugin()
]
},
//所使用的插件
plugins: [
new CleanWebpackPlugin(),
new BundleAnalyzerPlugin(),
new MiniCssExtractPlugin({
//输出的目录
filename: 'css/[hash].css'
}),
//使用插件定义全局变量DEV
new webpack.DefinePlugin({
'process':JSON.stringify({
env:'production'
})
})
],
module: {
rules: [
{
test: /.(le|c)ss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'less-loader'
]
}
]
},
//不编译进dist的包,用于优化dist体积
externals: {}
}
module.exports = merge(webpackBaseConfig, webpackProConfig);
5.结尾
这样我们的一个最基础的配置就搭建好了,基本上可以满足日常的使用,和官方脚手架相比配置少了很多,但是是我们自己一点点搭建起来的,所以说灵活性比较高,如果你的项目不是很复杂可以考虑用自己搭建的项目,但是如果项目很庞大还是用官方的比较好,官方配置更成熟,更能满足我们的需要
最后附上git仓库地址地址,感兴趣的朋友一定要自己去敲一遍!!!
转载自:https://juejin.cn/post/7022503679752142862