在之前有一篇使用react搭建vue项目的文章,现在来说一下用webpack搭建react项目,按照我个人来说,搭建react项目比vue项目坑多一点。下面的文章会一一说明,废话不多说,直接开干
react官方有脚手架,为什么还要用webpack搭建一个?这不是找麻烦嘛,自己搭建一个对于提高自己的技术,了解一些底层的技术也很有帮助,没准以后面试的时候正好问到了呢!,刚开始搭建的时候也踩坑不少
第一步老样子还是要在文件夹里面npm init -y初始化项目
一般工具安装都是-D;-S是我们从开发到上线一直都要用到的
1、工程目录
我的工程目录结构是这样的,按照个人开发习惯
下面的例子用到的都是main.js,我个人真正开发用到的是app.js,哈哈哈不要纠结那么多
dist:打包输出文件夹
pubilc中的index.html:入口html文件
assets:存放图片和icon的
components:存放封装的公共组件
utils:存放全局封装的js文件
views:页面相关文件
app.js:全局js文件,配置相关
2、新建工程目录和相关文件
- 根目录下新建src文件夹用来存放项目文件;新建public文件夹并在里面新建index.html作为入口html文件
- 根目录下新建main.js作为webpack打包的入口文件
3、安装webpack相关
- 安装webpack:
npm i webpack -D - 安装webpack-cli:
npm i webpack-cli -D - 根目录下新建webpack.config.js,然后的简单的配置一下
如果我们不配置webpack.config.js直接在终端输入webpack,这时候会报错的,如下图所示
然后我们来简单的配置一下。这里我们看见用的是module.exports来向外暴漏一个对象。那么用export default可以吗,当然是不可以的,因为webpack是基于node开发的,第二种是ES6的语法,node识别不了,那么webpack也识别不了
注意:在webpack4.x以后,用pink老师的话说就是:约定大于配置。目的是为了减少配置文件的体积
这时候我们在终端输入webpack会看见就不报错了,dist文件夹里面也有一个main.js
在这里提醒一下大家,不建议安装webpack5.x版本的,原因是:配置的时候及容易出现不兼容报错的问题,坑太多;而且现在4.x稳的一批
- 安装webpack-dev-serve:npm install webpack-dev-server --save-dev
现在安装下来的是最新的5.x的版本,在run跑起来的时候会报错,是因为版本不兼容的导致的,把webpack的版本安装为4.x的即可,非常不建议安装到5.x的版本。更换为4.x的版本后,重新run一下脚本,哇靠,运行成功了,仔细一看怎么和我想象中的不一样,而且我在main.js中打印的数据也不一样
右键查看源代码的时候,看到main.js是在根目录下的一个dist文件夹里面的,这是为啥?因为webpack-dev-serve打包好的main.js是托管在内存中的,所以在根目录中看不到
在index.html中我们引用的main.js路径是写死的,所以一直都是以前的文件,改变一下路径即可,或者直接不用引用,交给一个plugin插件处理下面会说 到,看一下修改后的
- 配置运行后自动打开浏览器,一般我都是不配置,因为太懒了!!!只在后面加上运行脚本只需要在
package.json的script部分的运行脚本中加入如下代码即可
--open:是自动打开浏览器,后面加上相应的浏览器名字就能打开不同的浏览器,比如:--open firefox
--port 3000:代表运行在3000的端口
--hot:实现热重载
--progress:代表进度
--compress:代表走网络的时候压缩(数据)
--host:指定端口号,一般默认的都是localhost的端口,–host+空格+后面加上端口号就可以改变端口号:--host 129.0.0.1
4、安装html-webpack--plugin
现在运行成功后浏览器上显示的不是我们想要的内容,这时候需要借助一个插件:html-webpack-plugin,首先用npm安装一下,安装的时候注意思 webpack4.x和5.x安装的命令是不一样,可以去npm网站看一下,我的webpack是4.x的,所以用:npm i --save-dev html-webpack-plugin,他能帮助我们把HTML页面生成到内存中
使用方法也很简单,可以去看webpack的官方文档,首先在webpack配置文件创建一个plugins节点,在节点中new一下那个插件,可以传递两个参数:template、filename
webpack官方文档---tml-webpack-plugin:webpack.js.org/plugins/htm…
npm官方文档---tml-webpack-plugin:www.npmjs.com/package/htm…
- template:是根据谁生成模板,源文件
- filename:生成的目标文件的名称
重新跑一下项目看看效果,ok完美
这时候我们右键查看源代码看到多了一个main.js的引用,这个是插件帮我们生成的,所以在上面index.html的引用去掉就可以了,毕竟有时候我自己写着写着难免不出现问题
5、安装react、react-dom
1、安装命令:npm i react react-dom -S
2、main.js导入
先来渲染一个最基本的div看看
6、安装babel相关
使用过react的都知道平时项目中可不是这样一个个创建的,那这样就能累死人啊,假设我们现在用class的方式创建一个组件会怎么样呢??
他告诉我们应该有一个loader来处理这种type,就要用到babel来处理这种语法,webpack只能打包处理js文件
下面就来安装一下:npm install babel-loader@8 @babel/core @babel/preset-env @babel/runtime @babel/plugin-transform-runtime @babel/plugin-proposal-class-properties @babel/preset-react -D,这里具体的就不解释了,可以去百度,反正就是转换成webpack可以识别的,看个人情况也不用全部都安装
配置babel
在webpack.config.js中添加module节点并进行配置,也可以新建.babelrc文件来配置,我更喜欢第一种,因为就不用新建文件了(懒!!!)(文章底部有完整配置)
//模块配置规则
module: {
//第三方匹配规则(多个loader的话要写成数组)
rules: [
{ test: /\.js|jsx$/, exclude: /node_modules/, use: 'babel-loader' }//在配置babel-loader的时候一定要加上exclude,否则项目会报错跑不起来
]
}
根目录新建.babelrc文件(考虑到更多人,这里还是说一下新建文件配置的方式)
这时候重新run一下看看,ok完美
7、配置路径、别名
作为一个懒人,这是我必须配置,就能少些一些代码了
在webpack.config.js中新增resolve节点,写入如下代码
resolve: {
//省略后缀名
extensions: ['*', '.js', '.jsx', '.json'],
// 配置路径别名
alias: {
'@assets': path.resolve('./src/assets'),
'@views': path.resolve('./src/views'),
'@utils': path.resolve('./src/utils'),
'@components': path.resolve('./src/components')
},
}
现在我们引入文件只需要这样写就行了
8、react中使用css、scss;css模块化
如果你样式写的都是内联的话,这一步可以不看,应该很少有人吧~~~~~~~~~~~
完整的css配置及模块化请看最底部的附录,那里的很完整,这部分介绍如何使用和部分的配置
sass的配置下面会讲到
webpack css处理流程:
-
当webpack打包到某个文件的时候看到有后缀名为.css的文件,这时候webapck识别不了他会拿着该文件到rules里面找第三方的loader
-
当找到有这样的loader的时候会从最后一个loader开始处理,也就是css-loader,当css-loader处理完后会看前面还有没有其他的loader 这时候前面还有一个style-loader,会给他做第二次处理这时候再去前面找有没有其他的loader,有的话再交给前面的处理
-
当最后一个loader处理完的时候就会交给webpack做打包合并
安装相关loader
npm i style-loader css-loader -D
配置css-loader
css模块化
我们也可以不使用sass,直接将css模块化,万一到时候用的第三方的UI框架怎么办,直接在rules里面加一个exclude就可以了
在这里使用css模块化的时候,首先安装一个插件:mini-css-extract-plugin:npm install --save-dev mini-css-extract-plugin这个插件主要是其他样式格式转成css ,抽离css用的 然后我们实例化一下这个插件;filename是我们要输出的css文件地址
const MiniCssPlugin = new MiniCssExtractPlugin({
filename: './css/common.css'
})
在webpack.config.js文件的plugins节点导入
plugins: [
HtmlPlugin, MiniCssPlugin
],
使用的时候看下图 ,将module设置为true就可以开启模块化
**注意:这个插件不能和style-loader一起使用、不能为sass和less文件使用,主要就是给css文件使用的;最好放在首位**
rules: [
{ test: /\.js|jsx$/, exclude: /node_modules/, use: 'babel-loader' },//在配置babel-loader的时候一定要加上exclude,否则项目会报错跑不起来
{ test: /\.css$/, exclude: /node_modules/, use: [MiniCssExtractPlugin.loader, {loader: 'css-loader',
options: {
modules: true,
importLoaders: 1
}
}]},
//打包处理字体、图片文件的loader
{ test: /\.ttf|woff|woff2|eot|svg|png|jpg|gif$/, use: 'url-loader' },
]
9、安装url-loader、file-loader
安装这两个loader可以帮助我们处理字体文件和图片文件
安装: npm i url-loader file-loader -D
在moudle------>rules节点里面进行配置
//打包处理字体、图片文件的loader
{ test: /\.ttf|woff|woff2|eot|svg|png|jpg|gif$/, use: 'url-loader' }
10、安装、配置sass
sass-loader内部是依赖node-sass的,安装命令建议去使用npm官网的 先安装node-sass:npm install node-sass; 然后安装sass-loader:npm install sass-loader sass webpack --save-dev 现在运行一下:npm run start,这时候发现终端报错了(这里选用的是less报错,scss报错的图片找不到了,不过都一样
报错信息告诉我们getOptions这么个东西不是一个方法,出现这个的原因就是由于sass-loader版本过高导致的,换一下版本号,建议安装8.x以下的版本,还有node-sass也是,因为默认安装最新版本的,刚开始默认安装了一个5.x,终端也是报错并提示最好安装4.x的
下面的配置我没有对css进行模块化,因为以后项目使用UI框架地时候引入的都是css文件,为了以后不出什么问题我还是搞一下sass吧,我们自己的文件就是.scss结尾的,UI框架的就是.css 下方postcss-loader去webpack官方看一下文档吧,文档写的很详细,这里就不说了
webpack文档:webpack.js.org/loaders/pos…
npm文档:www.npmjs.com/package/pos…
rules: [
{
test: /\.scss$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
}
}, 'sass-loader']
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
importLoaders: 1,
}
},
{
loader: 'postcss-loader',
options: {
//配置选项和插件
postcssOptions: {
plugins: [
autoprefixer(["last 2 version", "> 1%", "not ie < 11"]),
pxtorem({
rootValue: 16,
unitPrecision: 5,
propList: ["font", "font-size", "line-height", "letter-spacing", "width", "height", "margin", "padding"],
selectorBlackList: [],
replace: true,
mediaQuery: false,
minPixelValue: 0,
exclude: /node_modules/i
})
]
}
}
}
]
}
]
10、打包测试
现在我们需要的都已经配置完了,打个包试一下,走起~~~
我项目中已经有一个dist文件夹,虽然我不知道是干啥的,没事,将最后的打包文件换一个
在package.json中写上打包的命令
"build": "webpack --progress --config webpack.config.js"
给我飞起来!!!!!~~~~~~
一般情况下我们的打包文件都是dist文件夹,可以看一下本篇最后面的目录,里面有非常完整的配置,我已经将outPutDist改为dist文件夹了,打包出来也是一样,里面也都是一些我们打包后的html、js、css文件文件
static文件夹里面放的是我们的css和一些图片文件,我们借助了mini-css-extract-plugin这个插件将css合成了一个
至此,打包也已经完成了
11、附录(一定要看!!!)
- 有时候安装的依赖太多了,一个个安装也是费死劲,将全部用到的依赖和部分的webpack配置放在这里;到时候直接复制过去,然后直接npm install就可以,方便大家使用
- 里面安装了ts相关的loader,不用ts的可以不用安装,直接用js就可以
- @babel/plugin-proposal-class-properties"和@babel/plugin-transform-runtime不用安装也可以
- 配置babel的时候我是没有使用创建.babelrc文件来配置的,太麻烦了直接在webpack配置文件里面就可以
package.json完整代码
{
"name": "react-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --colors --progress --profile",
"start": "webpack-dev-server"
},
"devDependencies": {
"@babel/core": "^7.12.13",
"@babel/plugin-proposal-class-properties": "^7.12.13",
"@babel/plugin-transform-runtime": "^7.12.15",
"@babel/preset-env": "^7.12.13",
"@babel/preset-react": "^7.12.13",
"@babel/runtime": "^7.12.13",
"autoprefixer": "^10.0.2",
"babel-loader": "^8.2.2",
"terser-webpack-plugin": "^2.2.1",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^5.0.1",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^1.3.1",
"node-sass": "^4.14.1",
"postcss": "^8.1.9",
"postcss-loader": "^4.1.0",
"postcss-pxtorem": "^5.1.1",
"sass": "^1.32.8",
"ts-loader": "^8.0.12",
"typescript": "^4.1.3",
"sass-loader": "^7.3.1",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"webpack": "^4.46.0",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14"
},
"dependencies": {
"antd": "^4.12.3",
"axios": "^0.21.0",
"core-js": "^3.7.0",
"qs": "^6.9.4",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0"
},
"keywords": [],
"author": "",
"license": "ISC"
}
webpack.config.js完整代码
clean-webpack-plugin:是用来清空dist文件的
postcss-pxtorem:是px自动转rem的一个插件,推荐和postcss-loader一起使用
autoprefixer:一个自动管理浏览器前缀的插件
terser-webpack-plugin:打包自动去除console.log、打包优化
const path = require('path')
const HtmlWebpackPlugin = require("html-webpack-plugin")
const autoprefixer = require("autoprefixer")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const { CleanWebpackPlugin} = require("clean-webpack-plugin")
const pxtorem = require("postcss-pxtorem")
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
mode: 'production',
entry: path.join(__dirname, './app.js'),
output: {
path: path.join(__dirname, 'dist'),
filename: "static/js/bundle.js",
},
//loader配置相关
module: {
rules: [{
// js文件、babel配置
test: /\.js|jsx$/,
exclude: [
path.resolve(__dirname, 'node_modules')
],
use: [{
loader: 'babel-loader',
options: {
babelrc: false,
presets: [
require.resolve('@babel/preset-react'),
[require.resolve('@babel/preset-env'), {
"useBuiltIns": "usage",
"corejs": 3,
modules: false
}]
],
cacheDirectory: true
}
}]
},
//css、scss和模块化相关配置
{
test: /\.scss$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
}
}, 'sass-loader']
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
importLoaders: 1
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
autoprefixer(["last 2 version", "> 1%", "not ie < 11"]),
pxtorem({
rootValue: 16,
unitPrecision: 5,
propList: ["font", "font-size", "line-height", "letter-spacing", "width", "height", "margin", "padding"],
selectorBlackList: [],
replace: true,
mediaQuery: false,
minPixelValue: 0,
exclude: /node_modules/i
})
]
}
}
}
]
},
// 文件配置
{
test: /\.(png|svg|jpg|gif|ttf|woff|woff2|eot)$/,
use: {
loader: "url-loader",
options: {
esModule: false,
name: "static/images/[hash:8].[name].[ext]",
limit: 3 * 1024
}
}
}
]
},
// 路径别名配置
resolve: {
extensions: ['.json', '.ts', '.tsx','.js', '.jsx'],
alias: {
"@images": path.resolve("./src/assets"),
"@css": path.resolve("./src/css"),
"@views": path.resolve("./src/views"),
"@components": path.resolve("./src/components"),
"@utils": path.resolve("./src/utils")
}
},
//插件配置
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: "static/css/[name].css",
chunkFilename: "static/css/[id].css"
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
filename: "index.html"
})
],
//打包优化配置
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
sourceMap: true,
terserOptions: {
comments: false,
compress: {
unused: true,
drop_console: true,
drop_debugger: true,
dead_code: true
}
},
parallel: true,
})
]
}
//服务器代理配置
// devServer: {
// host: "192.168.15.28",
// port: "3001",
// historyApiFallback: true,
//proxy: { //代理
//'http://106.53.151.120:8888/user-info/
//'/user-info' : {
//target: 'http://132.232.223.165:8888/',
// changeOrigin:true,
//secure:false,
//pathRewrite: { '^/user-info': '' }
//}
//}
// }
}
App.js(也就是main.js)代码
这里代码可以很简洁也可以直接写antd的组件,我就不新建文件然后引入了,直接在这里面写入
import React from 'react'
import ReactDOM from 'react-dom'
import { HashRouter, Route, Link } from 'react-router-dom'
import { Layout, Menu } from 'antd'
//导入组件
import Home from './src/views/Home'
import About from './src/views/About'
import 'antd/dist/antd.css'
const { Header, Content, Footer } = Layout
ReactDOM.render(
<HashRouter>
<Layout style={{width:'100vw',height:'100%',flex:1}}>
<Header className="header" style={{display:'flex',alignItems:'center'}}>
<Menu style={{flex:1}} theme="dark" mode="horizontal">
<Menu.Item key="Home">
<Link to="/Home">首页</Link>
</Menu.Item>
<Menu.Item key="About">
<Link to="/About">关于</Link>
</Menu.Item>
</Menu>
</Header>
<Layout>
<Layout style={{ padding: '0 24px 24px' }}>
<Content className="site-layout-background" style={{ margin: 0, minHeight: 280, }}>
<Route path="/Home" component={Home}></Route>
<Route path="/About" component={About}></Route>
</Content>
<Footer style={{ textAlign: 'center' }}>webpack—react-cli</Footer>
</Layout>
</Layout>
</Layout>
</HashRouter>,
document.getElementById('app')
)