webpack逐步构建React项目详细解析版

351 阅读14分钟

一、前言

构建React项目一般分为脚手架构建的方式和webpack逐步构建的方式,本期就是对webpack一步一步构建项目进行逐步详细讲解。

二、新建文件夹并进入文件夹

mkdir react-demo
cd react-demo

三、初始化项目

npm init

此步骤会生成 package.json 文件;

四、安装webpack及插件

npm i webpack webpack-cli webpack-dev-server webpack-merge -D
npm i html-webpack-plugin clean-webpack-plugin  mini-css-extract-plugin file-loader less-loader css-loader style-loader cross-env -D

1、webpack

Webpack 的主要作用是将各种模块(如 JavaScript、CSS、图片等进行打包和处理,使得项目能够在浏览器中高效运行。它可以实现以下功能:

  • 模块打包:将多个模块成一个或多个包,减少网络请求。
  • 代码转换:例如将高版本的 ES 语法转换为低版本的 ES 语法,以便在更多浏览器中运行。
  • 资源处理:处理各种类型的资源,如图片、字体等。
  • 开发服务器:提供开发服务器,支持热更新等功能,方便开发调试。

2、Webpack-cli

Webpack-cli 是在命令行中运行 Webpack 的工具,通过命令行指令来启动和配置 Webpack 的构建过程。

3、Webpack-merge

Webpack-merge 用于将分离的配置进行合并,方便对不同环境(如开发环境和生产环境)的 Webpack 配置进行管理和组织。

4、webpack-dev-server

webpack-server 是 Webpack 提供的一个开发服务器,具有以下主要作用:

  • 支持热更新:在开发过程中,代码发生更改时能够自动编译并实时刷新页面,无需手动刷新浏览器。
  • 提供开发环境的配置选项,如设置端口、等。
  • 可以在浏览器中百分比显示编译进度。
  • 支持压缩和不压缩的模式。
  • 可以自动打开指定的浏览器,并支持无痕和新窗口等选项。
文件或功能解析器或插件备注
htmlhtml-webpack-pluginhtml解析和打包插件
csscss-loadercss解析器
lessless-loaderless解析器
sasssass-loadersass解析器
filefile-loader文件解析器
threadthread-loader多线程打包
mini-css-extract-pluginmini-css-extract-plugincss文件提取插件
postcsspostcsscss浏览器兼容处理
babel-loaderbabel-loaderwebpack使用babel

五、安装React 和 React-Dom

npm i --save react  react-dom

六、Babel 的安装配置

Babel 主要用于将高版本的 ES 语法转换为低版本的 ES 语法,以便在更多浏览器中运行。它可以将新的 JavaScript 特性和语法转换为向后兼容的形式,确保代码能够在不支持最新特性的环境中正常工作。

npm i @babel/core core-js @babel/preset-env babel-loader @babel/preset-react @babel/preset-typescript babel-plugin-dynamic-import-node @babel/plugin-transform-runtime @babel/plugin-transform-runtime @babel/plugin-proposal-decorators -D
解析器或插件功能备注
@babel/corebabel核心Babel 的核心模块,例如对代码进行语法分析、转换和生成新的代码。在整个 Babel 转码过程中起到关键的支撑作用。
babel-loaderwebpack中使用babel用于在 webpack中使用Babel代码转换的加载器。
@babel/preset-env语法转化将现代 JavaScript 语法转换为目标浏览器或环境能够理解的语法
core-jsJavaScript的模块化标准库在 Babel 配置中用于提供 JavaScript 标准库的功能
@babel/preset-reactreact转码插件将 React 的特定语法(如 JSX)转换为普通的 JavaScript。
@babel/preset-typescripttypescript转码插件用于编译 TypeScript 代码
babel-plugin-dynamic-import-node将import() 转为 require()如果代码中有使用动态导入(import())语法,这个插件可以将其转换为 Node.js 环境中的 require() 函数调用,以便在服务器端(如 Node.js)中使用。
@babel/plugin-transform-runtimeJavaScript标准库注入向 JavaScript 标准库注入兼容性方法。
@babel/plugin-proposal-decoratorsES7修饰器转码器ES7 修饰器(Decorators)是一种实验性的特性,它允许你修改类及其成员的行为。修饰器是一个函数,它可以用来修改类的行为,或者类的属性和方法。修饰器可以在类定义的时候被应用,也可以在类的实例化的时候被应用。

1、babel.config.json 配置

/*babel.config.json*/
////Babel 的主要作用是将高版本的语法转换为低版本的ES语法,以便在不支持最新语法的环境中能够正常代码。
{
	"comments": false,    //注释状态- //表示在编译过程中,Babel 将忽略掉代码中的注释,这可以减少最终输出文件的大小。
	"presets": [//定义了一系列的预设
		[
			"@babel/preset-env", //语法转化-//语法转化-将现代 JavaScript 语法转换为目标浏览器或环境能够理解的语法
			{
				"targets": {//通过配置"targets"字段,可以指定需要支持的浏览器版本。
					"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
				},
				"useBuiltIns": "usage",//让 Babel 在转换时自动使用需要的 polyfill。这样能在保证兼容性的同时,减少不必要的模块引入。
				"corejs": 3,//模块化标准库-CoreJS 的版本为 3
				"loose": true//表示启用宽松模式。在这种模式下,一些语法转换可能会采用更宽松的规则。
			}
		],
		"@babel/preset-react",    //将 React 的特定语法(如 JSX)转换为普通的 JavaScript。
		"@babel/preset-typescript"   //用于编译 TypeScript 代码。
	],
	"plugins": [//列出了一些额外的插件,用于提供特定的编译功能。
		"dynamic-import-node",   //将import() 转为 require()-如果代码中有使用动态导入(import())语法,这个插件可以将其转换为 Node.js 环境中的 require() 函数调用,以便在服务器端(如 Node.js)中使用。
		"@babel/plugin-transform-runtime",     //向 JavaScript 标准库注入兼容性方法。
		[
			"@babel/plugin-proposal-decorators",    //ES7修饰器转化- 用于处理 ES7 中的装饰器(decorators)语法。
			{
				"legacy": true//使用传统的装饰器实现。
			}
		]
	]
}

2、 import() 和 require() 的区别

  1. 动态性

    • import() 是动态的,可以在代码的任何位置使用,并且可以根据条件来决定是否导入模块。它返回一个 Promise,当模块加载完成后,Promise 会被解决。
    • require() 是静态的,通常在文件的开头使用,并且在运行时就会加载并执行模块。
  2. 模块类型

    • import() 用于导入 ES6 模块。
    • require() 用于导入 CommonJS 模块。
  3. 浏览器支持

    • import() 需要通过构建工具(如 Webpack、Rollup 等)将 ES6 模块转换为浏览器可以理解的格式。
    • require() 在 Node.js 环境中广泛使用,并且可以直接在浏览器中通过使用 Browserify 或 RequireJS 等工具来支持。
  4. 使用场景

    • import() 通常用于现代 JavaScript 项目,特别是在使用构建工具的前端项目中。
    • require() 通常用于 Node.js 项目,以及一些需要在浏览器中运行的旧项目。

七、安装Typescript(TS配置)

安装

npm i typescript -D

配置

1、初始化

tsc --init

执行命令会生成一个tsconfig.json文件

2、tsconfig.json文件配置

{
	"compilerOptions": {
		"module": "commonjs", //设置程序的模块系统。
		"target": "es5", //编译目标
		"jsx": "react", //控制 JSX 在 JavaScript 文件中的输出方式。
		"sourceMap": true, //调试显示原始的 TypeScript 代码。
		"removeComments": true, //移除注释
		/**
		 * TypeScript 开启严格模式 ,开启 "noImplicitAny": true,"strictNullChecks": true, "strictFunctionTypes": true,
		 * "strictBindCallApply": true,"strictPropertyInitialization": true,"noImplicitThis": true,"alwaysStrict": true
		 */
		"strict": true,
		"noImplicitAny": true, //ypeScript无法推断变量的类型时,TypeScript将返回到变量的any。
		"strictNullChecks": true, //null和undefined有各自不同的类型
		"moduleResolution": "node", //指定模块解析策略
		"baseUrl": "./", //基准目录
		"typeRoots": ["node_modules/@types"], //类型根路径
		"allowSyntheticDefaultImports": true, //允许合成默认导入
		"esModuleInterop": true, //ES 模块互操作性
		"experimentalDecorators": true //ES7类修饰器允许
	},
	"exclude": ["node_modules", "config/*"],  //忽略文件
	"include": ["src/**/*"] //包含文件
}


八、文件组装

1、新建index.html文件

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta http-equiv="pragma" content="no-cache" />
		<meta http-equiv="Cache-Control" content="no-store, must-revalidate" />
		<meta http-equiv="expires" content="Wed, 26 Feb 1997 08:21:57 GMT" />
		<meta
			name="viewport"
			content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"
		/>
		<title><%= htmlWebpackPlugin.options.title %></title>
	</head>
	<body>
		<div id="app_root"></div>
	</body>
</html>


2、新建源码文件夹和入口

(1)根目录下新建文件夹src
(2)src下新建index.tsx
(3)src下新建APP.tsx

index.tsx

import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.querySelector('#app_root') as Element).render(<App />);

APP.tsx

import React, { FC } from 'react';
const App: FC<any> = (props) => {
	return <>app</>;
};
export default App;

3、新建webpack配置文件夹和配置文件

  1. 在根目录下创建Webpack文件
  2. 在Webpack文件夹下创建webpack.base.js、webpack.dev.js、webpack.prod.js
  3. 编写webpack配置文件

说明:

  • webpack.base.js 公共部分配置
  • webpack.dev.js 开发模式配置
  • webpack.prod.js 生产模式配置

生产模式和开发模式的区别

  1. 功能

    • 生产模式:通常只包含最终用户需要的功能,去除了开发和调试过程中使用的工具和功能,以提高性能和安全性。
    • 开发模式:包含了所有的开发工具和功能,如源代码映射、热更新、错误报告等,以便开发者能够更高效地进行开发和调试。
  2. 性能

    • 生产模式:优化了性能,减少了不必要的代码和资源加载,提高了应用的运行速度和响应时间。
    • 开发模式:为了方便调试,可能会包含一些额外的代码和资源,这可能会影响应用的性能。
  3. 安全性

    • 生产模式:更加注重安全性,可能会对敏感信息进行加密处理,并且会限制一些可能存在安全风险的操作。
    • 开发模式:为了方便开发,可能会降低一些安全限制,以便开发者能够更自由地进行测试和调试。
  4. 调试便利性

    • 生产模式:通常不提供详细的错误信息和调试工具,因为这些信息可能会被攻击者利用。
    • 开发模式:提供了丰富的错误信息和调试工具,以便开发者能够快速定位和解决问题。

3.1 webpack.base.js 公共部分配置

概括:入口文件、ts,js,图片,其他文件,less,html相同

//项目的公共配置部分
const HtmlWebpackPlugin = require('html-webpack-plugin');//处理 HTML 文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');//在生产环境提取 CSS
const path = require('path');//引入了 Node.js 中的 `path 模块,该模块了用于处理文件和目录路径的实用方法。
const devMode = process.env.NODE_ENV !== 'production';//process.env.NODE_ENV环境变量,用于标识当前应用运行的环境,常见的值有 development(开发环境)和 production(生产环境)。
const resolve = (dir) => path.resolve(__dirname, dir);//其作用是使用 path.resolve 方法当前脚本所在的目录(__dirname)和传入的 dir 参数组合起来,返回一个绝对路径。

module.exports = {
	target: 'web',//目标平台
	entry: resolve('../src/index.tsx'), //入口文件
	plugins: [
		/**
		 * html文件处理
		 */
		new HtmlWebpackPlugin({
			title: 'Wizard-RUI',//设置生成的 HTML 页面的标题
			filename: 'index.html',//指定生成的 HTML 文件名。
			template: resolve('../index.html'),//指定模板文件的路径。
			hash: true,//为生成的资源添加哈希值。
			cache: false,//控制缓存策略。
			inject: true,//决定资源(如 JavaScript 和 CSS)的注入方式。
			minify: {//用于对生成的 HTML 进行压缩,包含了一些具体的压缩选项
				removeComments: true,//移除注释
				removeAttributeQuotes: true,//移除属性引号
				collapseWhitespace: true,//压缩空白
				minifyJS: true, // 在脚本元素和事件属性中缩小JavaScript(使用UglifyJS)
				minifyCSS: true // 缩小CSS样式元素和样式属性
			},
			nodeModules: resolve('../node_modules')//资源的存放位置。
		}),
		/**
		 * MiniCss插件,在生产环境使用
		 */
		!devMode
			? new MiniCssExtractPlugin({
					filename: '[name].[contenthash].css',//指定生成的 CSS 文件的名称格式
					chunkFilename: 'css/[id].[contenthash].css',//指定分块的 CSS 文件的名称格式
					ignoreOrder: true//忽略模块加载的顺序
			  })
			: function(){}
	],
	module: {//定义了模块的规则module.rules,用于处理不同类型的文件,如css、ts、tsx、图片、字体等文件的加载和处理方式
		rules: [
			/**
			 * 处理less,css 为dev模式下使用style-loader 为pod模式下使用MIniCss
			 */
			{
				test: /\.(le|c)ss$/i,
				use: [devMode ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
			},
			/**
			 * ts,tsx,js,jsx解析
			 */
			{
				test: /\.(ts|tsx)$/,
				exclude: /(node_modules|bower_components)/,
				use: [
					{
						loader: 'babel-loader',
						options: {
							cacheDirectory: true
						}
					}
				]
			},
			/**
			 * 图片处理
			 */
			{
				test: /\.(png|svg|jpg|gif)$/, // 图片
				use: [
					{
						loader: 'file-loader',
						options: {
							name: 'assets/images/[name].[ext]' // 存放的位置: dist/assets/images/文件
						}
					}
				]
			},
			/**
			 * 字体文件处理
			 */
			{
				test: /\.(woff|woff2|eot|ttf|otf)$/, // 字体
				use: [
					{
						loader: 'file-loader',
						options: {
							name: 'assets/fonts/[name].[ext]' // 存放的位置: dist/assets/fonts/文件
						}
					}
				]
			}
		]
	}
};

3.2 webpack.dev.js开发环境配置

概括:dev模式 less 略微不同,devServer相关配置,代理,resove路径,优化模式

//开发环境配置
const base = require('./webpack.base'); //取出公共部分
const { merge } = require('webpack-merge');//从 webpack-merge 模块中导入 merge 函数。在这个 React 项目构建的配置中,使用 merge 来合并不同的 Webpack 配置,例如在开发环境配置 webpack.dev.js和生产环境配置webpack.prod.js中,通过merge(base, {…})` 的方式将公共的基础配置和特定环境的配置进行合并。
const path = require('path');
const resolve = (dir) => path.resolve(__dirname, dir);

module.exports = merge(base, {
	mode: 'development', //开发环境webpack内置优化
	devtool: 'inline-source-map', //控制台调试代码------用于在开发环境中为 Webpack 配置提供源代码映射功能。这在浏览器的控制台中调试代码时,可以更方便地查看的源代码,而不是经过打包处理后的代码,有助于更高效地定位解决代码中的问题。
	devServer: {
		client: {
			progress: true //在浏览器中以百分比显示编译进度。
		},
		compress: false, //gzip压缩
		hot: true, //热更新------指在开发过程,当代码发生修改时,无需重新启动整个应用程序,而是能够实时地将修改的部分应用正在运行的应用中,让开发者能够立即看到修改后的效果,提高开发效率。
		open: {//指定打开浏览器
			app: {
				name: 'goole-chrome', //指定打开chrome
				arguments: ['--incognito', '--new-window'] //无痕,新的窗口
			}
		},
		port: 8081, //监听端口
		proxy: {} //代理配置
	},
	optimization: {
		//优化模式---表示在开发环境中不进行代码的最小化压缩。在生产环境中,通常会将 minimize 设置为 true 以减小代码体积,提高加载性能。
		minimize: false
	},
	/**
	 * 路径别名
	 */
	resolve: {
		alias: {
			// "@": ["../src"],
			'@': resolve('../src/'),
			src: resolve('../src/'),
			components: resolve('../src/components'),
			config: resolve('../src/config'),
			hook: resolve('../src/hook'),
			apis: resolve('../src/apis'),
			router: resolve('../src/router'),
			store: resolve('../src/store'),
			theme: resolve('../src/theme'),
			util: resolve('../src/util'),
			i18n: resolve('../src/i18n'),
			assets: resolve('../src/assets'),
			views: resolve('../src/views')
		},
		extensions: ['.tsx', '.ts', '.wasm', '.mjs', '.js', '.json']//指定模块解析时可接受的文件扩展名。
	}
});

3.3 webpack.prod.js生产环境配置

概括:出口,优化模式, 公共部分配置

//生产环境配置
const base = require('./webpack.base'); //取出公共部分
const { merge } = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPlugin;//它的作用是在每次构建前清理输出目录,确保生成的新构建文件不会受到旧文件的干扰。
const path = require('path');
const resolve = (dir) => path.resolve(__dirname, dir);

module.exports = merge(base, {
	mode: 'production', // 环境 development 和 production 环境
	output: {
		filename: 'index.js', // 文件名
		path: resolve('../dist'), // 文件输出地址
		library: {//library 用于指定构建后的模块发布方式,这里设置为 umd ,以实现兼容多种运行环境。
			/**
			 * 发布运行环境
			 * umd——兼容浏览器
			 * commonjs,commonjs2,module——适用于node.js环境
			 * amd——适用于require.js环境
			 * cmd——适用sea.js环境
			 */
			type: 'umd'
		},
		clean: true//用于在构建生产时清理输出目录(通常是 dist 文件夹)中的旧文件,以确保每次构建的输出都是干净和最新的。
	},
	optimization: {
		//优化模式
		minimize: false
	},
	/**若通过CDN引入React和ReactDOM可以使用 */
	// externals: {
	// 	react: 'React',
	// 	'react-dom': 'ReactDOM'
	// },
	plugins: [new CleanWebpackPlugin()],
	resolve: {
		extensions: ['.js', '.jsx', '.ts', '.tsx']//指定模块解析时可接受的文件扩展名。
	}
});

3.4 配置运行命令及打包命令

package.json 的script加入

{
    "dev": "webpack-dev-server --config ./webpack/webpack.dev.js",
    "build": "webpack --config ./webpack/webpack.prod.js",
    "test": "echo \"Error: no test specified\" && exit 1"
}

测试npm run devnpm run build是否成功运行;


说明 devtool选项值及其作用:

  • eval:每个模块都使用eval()执行,并且source map被嵌入到eval()中。这是最快的选项,但它不提供列映射(column mapping),这意味着你只能看到行号,而不能看到具体的列信息。
  • cheap-eval-source-map:与eval类似,但它提供了列映射,并且只在开发环境中使用。这是一个不错的选择,因为它在提供调试信息的同时,保持了较快的构建速度。
  • cheap-module-eval-source-map:与cheap-eval-source-map类似,但它适用于使用importexport语句的模块系统。
  • inline-source-map:将生成的source map嵌入到打包后的文件中,而不是生成一个单独的文件。这可以在浏览器的开发者工具中直接看到原始的源代码,但会增加文件的大小。
  • cheap-source-map:生成一个不带列映射的source map文件。这比完整的source map文件小,但仍然提供了足够的调试信息。
  • cheap-module-source-map:生成一个带有列映射的source map文件,适用于使用importexport语句的模块系统。
  • source-map:生成一个完整的source map文件。这是最详细的选项,但它会生成一个单独的文件,并且可能会减慢构建速度。
  • hidden-source-map:生成一个source map文件,但不将其嵌入到打包后的文件中。这通常用于在生产环境中调试,但不希望用户看到source map文件。
  • nosources-source-map:生成一个source map文件,但不包含源代码。这可以用于在生产环境中调试,但不希望用户看到原始的源代码。