Webpack 5 + React
intro: 尝试使用Webpack 打包React项目 并添加到HTML中
开发环境
前序知识
Webpack
什么是Webpack?
本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
布署
创建文件夹
创建一个空文件夹
使用npm命令初始化, 生成一个package.json文件
npm init -y
安装webpack
运行命令
npm install --save-dev webpack
安装完成后项目结构如下
|- node_modules
|- package-lock.json
|- package.json
创建config配置文件
在根目录下新建一个config
文件夹, 并创建以下三个js文件
- webpack.common.config.js
- webpack.prod.config.js
- webpack.dev.config.js
在根目录下创建一个src
文件夹, 并新建一个app.js
文件
+ |- config
+ |- webpack.common.config.js
+ |- webpack.dev.config.js
+ |- webpack.prod.config.js
|- node_modules
+ |- src
+ |- app.js
|- package-lock.json
|- package.json
在webpack.common.config.js
中添加如下代码
const path = require('path');
module.exports = {
entry: {
app: './src/app.js',
},
output: {
filename: 'js/bundle.js',
path: path.resolve(__dirname, '../dist'),
},
};
这断代码的作用是指明了入口文件以及打包后的文件路径
在package.json
配置文件中添加如下属性定义打包指令
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
+ "start": "webpack --config ./config/webpack.common.config.js"
},
运行一下试试
npm run start
可以看到生成了一个dist
文件夹
my-project
|- config
+ |- dist
+ |- js
+ |- bundle.js
|- node_modules
|- src
|- app.js
|- package.json
此时bundle.js
文件夹里还是空的, 因为app.js
中还没有添加内容
安装 webpack-merge
npm install --save-dev webpack-merge
配置使用webpack-merge以生成更小的bundle, 更轻量的source map, 更优化的资源, 并改善加载时间, 以满足生产(production)的需求
修改webpack.prod.config.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.config.js');
module.exports = merge(common, {
mode: 'production',
});
在根目录下新建一个public文件夹, 并在其中新建一个index.html
文件
|- config
|- node_modules
+ |- public
+ |- index.html
|- src
|- app.js
|- package-lock.json
|- package.json
向index.html
中添加一点内容
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>my-project(webpack + react)</title>
</head>
<body>
<div id="root"></div>
</body>
<script src="../dist/js/bundle.js"></script>
</html>
向app.js
中添加代码
const root = getElementById("root");
root.innerHTML = "<h1>Hello World</h1>";
添加指令到package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack --config ./config/webpack.common.config.js",
+ "build": "webpack --config ./config/webpack.prod.config.js"
},
执行命令
npm run build
然后用浏览器打开public/index.html
, 可以看到app.js
中的内容已经成功添加到网页中
安装 React
运行以下命令安装React
npm install react react-dom
安装成功后package.json
中会多出项目依赖
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ }
接下来我们把app.js
里的代码替换成React的JSX语法
-
在src文件夹中创建一个
index.js
做为入口文件, 并向文件中添加如下代码\// index.js import React from "react"; import ReactDOM from "react-dom"; import App from "./app"; ReactDOM.render(<App />, document.getElementById("root"));
-
修改
src/app.js
中的代码- const root = document.getElementById("root"); - root.innerHTML = "<h1>Hello World</h1>"; + import React from "react"; + + export default function App() { + return <h1>Hello World</h1>; + }
-
替换
webpack.common.confid.js
中的入口文件
const path = require('path');
module.exports = {
entry: {
- app: './src/app.js',
+ index: './src/index.js',
},
output: {
filename: 'js/bundle.js',
path: path.resolve(__dirname, '../dist')
}
}
现在运行以下npm run build
, 会发现报错了, 原因是缺少讲jsx语法转译为js语言的预处理插件, 所以下面配置Babel
安装 Babel
运行以下命令安装Babel及其依赖
npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties @babel/plugin-transform-runtime
这些依赖各自的作用如下
- babel-loader:使用 Babel 和 webpack 来转译 JavaScript 文件。
- @babel/core:babel 的核心模块
- @babel/preset-env:转译 ES2015+的语法
- @babel/preset-react:转译 react 的 JSX
- @babel/plugin-proposal-class-properties:用来编译类(class)
- @babel/plugin-transform-runtime:防止污染全局,代码复用和减少打包体积
修改webpack.common.config.js
如下以引入这些Babel
const path = require("path");
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|bower_components)/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties",
],
},
},
},
],
},
};
现在再运行npm run build
就可以成功打包了
安装 Html-Webpack-Plugin
之前我们是手动添加的public/index.html
文件, 但是在生产中可能会生成多个HTML文件, 所以需要安装html-webpack-plugin
插件, 让Webpack在打包时自动创建一个HTML文件
运行以下命令安装html-webpack-plugin
npm install --save-dev html-webpack-plugin
修改webpack.prod.config.js
以调用html-webpack-plugin
const { merge } = require('webpack-merge');
const common = require('./webpack.common.config.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html',
filename: 'index.html',
inject: 'body',
minify: {
removeComments: true,
},
}),
],
});
解释以下这里的属性
- template:基于我们自己定义的 html 文件为模板生成 html 文件
- filename:打包之后的 html 文件名字
- inject:将 js 文件注入到 body 最底部
- minify:压缩 html 文件时的配置
- removeComments:去除注释
还有其他一些属性可以参考 HTML Webpack Plugin 项目文档
修改一下public/index.js
, 删去引入../dist/js/bundle.js
的这行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial- scale=1.0" />
<title>react_webpack</title>
</head>
<body>
<div id="root"></div>
</body>
- <script src="../dist/js/bundle.js"></script>
</html>
现在再运行npm run build
, dist文件夹中就会多出一个index.html
文件
|- dist
|- js
|- index.html
<!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>react_webpack</title>
</head>
<body>
<div id="root"></div>
<script defer="defer" src="js/bundle.js"></script></body>
<script src="../dist/js/bundle.js"></script>
</html>
修改配置文件以生成不同的HTML文件
如果每次都生成一样的JS文件, 浏览器可能不会发现网页已经更新, 从而直接读取本地的缓存, 所以需要通过修改output中的属性使每次打包后生成不同的JS文件名
只需在webpack.prod.config.js
中添加这段代码
module.exports = merge(common, {
+ output: {
+ filename: "js/[name]-bundle-[hash:6].js",
+ },
mode: "production",
...
- name: 打包入口的名称
- hash:6: 生成6位hash码
此时再运行npm run build
, 可以发现dist/js
文件夹中生成了新的js文件
|- dist
|- js
|- bundle.js
+ |- index-bundle-2c81f8.js
|- index.html
安装 Clean Webpack Plugin
经过上面的配置后每次打包都会生成新的文件, 但是我们发现原有的文件却没有被删除, 这样随着打包次数的增加, 文件也会越来越多, 所以需要安装另一个clean-webpack-plugin
来在每次打包编译前清理dist目录
运行以下命令安装clean-webpack-plugin
npm install --save-dev clean-webpack-plugin
修改webpack.prod.config.js
, 引入clean-webpack-plugin
const HtmlWebpackPlugin = require("html-webpack-plugin");
+ const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = merge(common, {
output: {
filename: "js/[name]-bundle-[hash:6].js",
},
mode: "production",
plugins: [
+ new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "public/index.html",
...
现在在打包编译后旧的文件就会被清除了
安装 Webpack Dev Server
经过上面的配置, 我们已经可以使用npm run build
命令来构建并打包React文件到HTML文件中, 但是每次构建完都要手动打开dist/index.html
文件来查看效果, 所以需要安装webpack-dev-server
, 使项目webpack打包后能创建一个进程运行生成的文件
运行以下命令安装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.common.config.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(common, {
mode: 'development',
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
port: 8080,
compress: true,
},
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html',
inject: 'body',
hash: false,
}),
],
});
- static.directory: 指定启动文件的目录
- port: 进程运行的端口号
- compress: 是否开启gzip压缩
修改package.json
中的指令以调用webpack-dev-server
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
- "start": "webpack --config ./config/webpack.common.config.js",
+ "start": "webpack serve --open --config ./config/webpack.dev.config.js",
"build": "webpack --config ./config/webpack.prod.config.js"
},
现在运行npm run start
, 就可以打包编译并且在本地的8080端口访问生成的页面啦