Webpack之搭建React项目
1. 概述
之前都是使用React脚手架来搭建React项目的,这次想尝试放弃脚手架直接使用Webpack来搭建一个完整的React项目,学习一下Wepack的使用,在记录一下搭建过程,之前每次学习Wepack,过段时间都会忘记,这次记录一下,不求能够永远记住,但求能记得时间更久一点。
2. 创建项目目录
- 创建项目
mkdir webpack-react
- 创建package.json文件
- 使用默认方式创建:
npm init -y
- 使用自定义方式创建
npm init
- 创建项目目录
- 创建config,webpack等配置文件
- 创建scripts,start.js、build.js 项目启动和打包文件
- 创建public/index.html,html模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
- 创建src,源代码目录
- 创建src/index.js,源代码入口
- 创建src/components,源代码组件目录
3. 下载依赖文件
--save-dev:(简写-D)将依赖写入package.js文件devDependencies模块,为开发环境所需依赖。
--save: (简写-S)生产环境所需依赖,为产品发布后不可少的依赖,将依赖写入package.js文件devDependencies模块。
- React 依赖文件
npm install react react-dom --save
- Webpack 依赖文件
npm install webpack webpack-cli webpack-dev-server --save-dev
- Babel 依赖
- 核心: @babel/core babel 核心包,提供转换的API
npm install @babel/core --save-dev
- 预设:
@babel/preset-env:对es2015, es2016. es2017的支持
@babel/preset-react:Babel可以转换JSX语法
npm install @babel/preset-env @babel/preset-react -D
- 插件:
@babel/plugin-proposal-class-properties:解析类的属性
@babel/plugin-proposal-decorators:解析装饰器
@babel/plugin-transform-runtime:将 helper 和 polyfill 都改为从一个统一的地方引入,并且引入的对象和全局变量是完全隔离的,可以提高代码重用性,缩小编译后的代码体积
@babel/runtime-corejs3:和@babel/plugin-transform-runtime搭配使用,@babel/plugin-transform-runtime 的作用是转译代码,转译后的代码中可能会引入 @babel/runtime-corejs3 里面的模块。所以前者运行在编译时,后者运行在运行时,类似于 polyfill。后者需要被打包到最终产物里在浏览器中运行。
npm install @babel/plugin-proposal-class-properties -D
npm install @babel/plugin-proposal-decorators -D
npm install @babel/plugin-transform-runtime -D
npm install @babel/runtime-corejs3 -D
4. 配置文件
4.1 babel配置
在项目根目录下创建 .babelrc 文件。
babel 在转译的时候,会将源代码分成 syntax 和 api 两部分来处理:
- syntax:类似于展开对象、optional chain、let、const 等语法
- api:类似于 [1,2,3].includes 等函数、方法,使用polyfill来处理api
{
"presets": [
"@babel/preset-react",
[
"@babel/preset-env",
{
// 告诉 babel 如何处理api
// false, 默认值,不处理api
// entry, 需要手动引入 @babel/polyfill, 并且babel会将所有的 polyfill 全部引入,导致包非常大
// usage, babel 就可以按需加载 polyfill,并且不需要手动引入 @babel/polyfill
"useBuiltIns": "usage",
"debug": true
}
]
],
"plugins": [
[
// 编译类属性
"@babel/plugin-proposal-class-properties",
{
// 确定类属性的编译方式,赋值方式还是定义凡是
// true,赋值方式
// false, 定义方式,即使用Object.defineProperty()定义属性
"loose": true
}
],
// 解决 polyfill 的解析结果全局变量污染问题
[
"@babel/plugin-transform-runtime",
{
"corejs": "3",
}
]
]
}
4.2 webpack配置
4.2.1 配置devServer 、解析js和jsx
// 解析 js jsx
npm i babel-loader -D
// 解析 html
npm i html-webpack-plugin -D
// 删除上次打包生成的文件
npm i clean-webpack-plugin -D
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const srcPath = path.resolve(__dirname, '../src');
module.exports = {
entry: `${srcPath}/index.js`,
output: {
path: path.resolve(__dirname, '../build'),
filename: 'js/[name].[hash].bundle.js',
publicPath: '/'
},
devServer: {
port: 9999,
contentBase: path.join(__dirname, '../public'),
compress: true,
open: true,
hot: true
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'webpack-react',
template: path.resolve(__dirname, '../public/index.html'),
filename: 'index.html',
inject: 'body',
minify: false
})
]
}
4.2.2 解析 css、scss
4.2.2.1 下载文件
npm install node-sass -D
npm install style-loader css-loader sass-loader -D
// 自动补充css样式前缀
npm install postcss-loader postcss-preset-env -D
// 将样式提取为单独的文件
npm install mini-css-extract-plugin -D
4.2.2.2 配置
......
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
......
module.exports = {
......
module: {
rules: [
......
{
test: /\.(c|sc)ss$/,
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('postcss-preset-env')
]
}
}
},
'sass-loader'
]
}
]
},
plugins: [
......
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].css'
})
]
}
4.2.2.3 备注:配置postcss-loader,必须添加浏览器配置
package.json添加浏览器配置:
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.01%",
"not dead",
"npt op_mini all"
]
}
4.2.2 解析图片文件,字体图标
4.2.2.1 下载文件
npm install file-loader file-loader -D
4.2.2.2 配置
......
module.exports = {
......,
module: {
rules: [
......,
// 图片解析
{
test: /\.(jpg|png|gif)$/,
use: [
{
loader: 'url-loader',
options: {
// 图片大小超过8kb,将图片转化成base64
limit: 8 * 1024,
name: '[name].[hash].[ext]',
// 将解析后的图片放在image文件夹下
outputPath: 'image'
}
}
]
},
// 字体图标解析
{
test: /.(ttf|woff|woff2)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts'
}
}
}
]
},
......
}
4.2.2 复制静态文件
如果在index.html中直接引入了静态文件,需要将静态文件复制到输出文件夹中。
npm install copy-webpack-plugin -D
......
const copyWebpackPlugin = require('copy-webpack-plugin');
......
module.exports = {
......,
plugins: [
......,
// 将public下的除了index.html以外的静态文件复制到build文件夹内
new copyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../build'),
globOptions: {
dot: true,
gitignore: true,
ignore: ["**/index.html"],
}
}
]
})
]
}
5. webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const copyWebpackPlugin = require('copy-webpack-plugin');
const srcPath = path.resolve(__dirname, '../src');
module.exports = ({mode}) => {
return {
entry: `${srcPath}/index.js`,
// 出口
output: {
path: path.resolve(__dirname, '../build'),
filename: 'js/[name].[hash].bundle.js',
publicPath: '/'
},
// webpack-server 配置
devServer: {
contentBase: path.join(__dirname, '../public')
},
// 模式, development, production
mode: mode,
// 生成资源映射文件
devtool: mode === 'development' ? 'inline-source-map' : false,
resolve: {
// 文件扩展名
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
}
},
{
test: /\.(c|sc)ss$/,
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '/'
}
},
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('postcss-preset-env')
]
}
}
},
'sass-loader'
]
},
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[name].[hash].[ext]',
outputPath: 'image'
}
}
},
{
test: /\.(ttf|woff|woff2)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts'
}
}
}
]
},
plugins: [
// 删除上次打包生成的文件
new CleanWebpackPlugin(),
// 生成html
new HtmlWebpackPlugin({
title: 'webpack-react',
template: path.resolve(__dirname, '../public/index.html'),
filename: 'index.html',
inject: 'body',
minify: false
}),
// 提取css到单独的文件中
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].css'
}),
// 复制public中的静态文件
new copyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../build'),
globOptions: {
dot: true,
gitignore: true,
ignore: ["**/index.html"],
}
}
]
})
]
}
}
6. 使用node.js启动项目,并解决端口占用问题
如果使用node.js来启动项目,需要删除webpack配置文件中的devServer 配置项。
// 控制台打印样式
npm install chalk -D
// 获取本机的 IP, MAC and DNS servers
npm install address -D
6.1 start.js 文件
const webpack = require('webpack');
const WebpackServer = require('webpack-dev-server');
const webpackConfig = require('../config/webpack.config');
const net = require('net');
const chalk = require('chalk');
const address = require('address');
const compiler = webpack(webpackConfig({ mode: 'development' }));
// 默认端口号
let port = 8081;
function listenPort() {
const server = net.createServer().listen(port);
// 服务是否在监听连接,如果在监听表示端口没有被占用
// 关闭服务,释放端口,启动webpack服务
server.on('listening', function() {
server.close();
startWebpackServer();
});
// 服务出现错误
server.on('error', function(err) {
if (err.code === 'EADDRINUSE') {
console.log(chalk.yellow(`端口:${port},被占用,修改端口为:${++port}`));
listenPort();
}
})
}
function startWebpackServer() {
const serverConfig = Object.assign({}, webpackConfig.devServer || {}, {
host: address.ip(),
//启用 gzip 压缩。
compress: true,
//启动索引html文件,默认index.html
index: 'index.html',
//是否启用热替换
hot: true,
//启用内联模式(inline mode),会在控制台打印消息,用none阻止。
clientLogLevel: 'none',
//dev-server 的两种不同模式之间切换:true内联模式(inline mode)、 false: iframe 模式,默认true。
inline: true,
//自动打开浏览器
open: true,
});
const webpackServer = new WebpackServer(compiler, serverConfig);
webpackServer.listen(port);
}
listenPort();
7. node.js 调用 webpack 打包
7.1 build.js 文件
const webpack = require('webpack');
const webpackConfig = require('../config/webpack.config');
const chalk = require('chalk');
const ora = require('ora');
const spinner = ora('[building]').start();
const compiler = webpack(webpackConfig({ mode: 'production' }));
compiler.run(function(err, stats) {
if (err || stats.hasErrors()) {
console.log(chalk.red('build fail:' + stats.hasErrors()));
return;
}
spinner.stop();
console.log(chalk.green('build success'));
})
小贴士
1. Error: Cannot find module ‘webpack-cli/bin/config-yargs‘
原因:webpack更新至5.0,但是webpack-dev-server没有更新导致的。
解决方法:调整webpack-cli版本至3.3.12
2. devtool配置
// 不生成资源映射文件
devtool: false
Babel配置:https://zhuanlan.zhihu.com/p/147083132
webpack配置:https://blog.csdn.net/qq_41526942/article/details/102590921