前言

在平时工作中,为减少开发成本,一般都会使用脚手架来进行开发,比如 create-react-app。脚手架都会帮我们配置好了 webpack,但如果想自己搭建 webpack 项目要怎么做呢?这边文章将介绍如何使用 webpack 5 来搭建 react 项目,项目地址在文末。

一、简单聊下 Webpack

1.1 Webpack 的好处

试想在不使用任何打包工具的情况下,我们很难在项目去使用 es6+ 新语法,TypeScript即使是新的浏览器,也不支持,更别说在项目中使用 React、Vue 了。打包工具能帮我们解决这些问题,打包工具有很多,比如 Webpack、Vite、Snowpack、Rspack 等,这里介绍 Webapck,毕竟生态圈大。

Webpack 是一个 JavaScript 应用程序的静态模块打包工具。 它可以帮我们分析项目结构,将模块打包,最终得以在浏览器中直接使用。 Webpack 有哪些好处呢?

开发环境:

新特性&新语法: 像 ESNext 新特性,.less、.ts、tsx/jsx、.vue 等浏览器无法识别的格式文件d都能在开发中使用。Webpack 的 Loader 机制能帮助进行转换。模块化: 在 Webpack 中,一切皆为模块, 我们可以使用模块化编程,把复杂的程序细化为小的模块文件。模块热替换(HMR): 提供模块热替换功能, 在修改代码后,不需要重新加载整个页面,只需要替换修改的模块,从而提高开发效率。Source Map: 提供了 Source Map 功能,可以将编译后的代码映射回原始源代码,从而方便我们进行调试

生产环境:

性能优化:可以压缩代码,合并文件,从而减少网络请求。代码分割:可以进行代码分割,实现按需加载或者并行加载,从而减少页面加载时间,提高用户体验。缓存优化:可以根据文件内容生成 hash 值,从而实现缓存优化,减少网络请求和服务器负载。

1.2 Webpack 的基本概念

这里我们先简单熟悉下 Webpack 基本概念,下面搭建项目时都要用到。

entry: 使用哪个模块来作为构建的起始入口。output: 最终打包后的文件放在哪里,以及如何命名这些文件。loader: 是处理文件的转换器,用于对模块源码进行转换,webpack 只能识别 js、json 文件,像 css 、ts 、jsx等文件都需要通过 loader 进行转换。plugin: 是一种可扩展的机制,可以打包过程中添加额外的功能。比如打包优化,资源管理,注入环境变量等。mode: 对于不同的环境,我们往往需要不同的配置,通过设置 mode 参数来选择环境。

二、搭建 React 项目

上面简单介绍了 webpack,接下来开始搭建我们的项目。

2.1 项目初始化

我们使用 pnpm 来初始化一个项目(8.x 版本需要 node 在 16 + ),为什么选用 pnpm ,可以看下包管理工具 —— 更推荐的 pnpm。

mkdir create-react

cd create-react

pnpm init --y

git init

2.2 安装配置 react & TypeScript

引入 react 、react-dom 和对应的类型包 @types/react 、@types/react-dom。这里使用的版本是18.2.0。

pnpm add react react-dom

pnpm add -D @types/react @types/react-dom

然后配置 TypeScript

pnpm add typescript -D

有了 TypeScript,就可以直接通过 tsc 命令生成一个 tsconfig.json 的配置文件

tsc --init

可以按照所需手动修改 ts 的配置文件。

{

"compilerOptions": {

"target": "ESNext",

"jsx": "preserve",

"module": "ESNext",

"moduleResolution": "node",

"rootDir": "./src",

"baseUrl": ".",

"paths": {

"common/*": [

"src/common/*"

],

"@/*": [

"src/*"

]

},

"strict": true,

"sourceMap": true,

"forceConsistentCasingInFileNames": true,

"noImplicitReturns": true,

"importHelpers": true,

"noUnusedLocals": true,

"noFallthroughCasesInSwitch": true,

"noUnusedParameters": true,

"noEmit": true,

"skipLibCheck": true

},

"include": ["src"]

}

接着我们创建 src 目录,在根目录创建 index.tsx,在 src 下创建 App.tsx 。

// index.tsx

import * as React from 'react'

import * as ReactDOM from 'react-dom/client'

import App from './src/App'

const root = ReactDOM.createRoot(document.getElementById('app')!)

// v18 的新方法

root.render()

// App.tsx

import * as React from 'react'

const App: React.FC = () => {

return

hello 小柒

}

export default App

前置准备已经做好, 接下来我们来一步一步的使用 webpack 打包 React 项目。

2.3 Webpack 相关

(1) 安装 Webpack

webpack 、webpack-cli :打包必备。webpack-dev-server: 一个提供热更新的开发服务器,对开发阶段友好。webpack-merge: 用来合并配置文件。

pnpm add webpack webpack-cli webpack-dev-server webpack-merge -D

(2) 配置 Webpack 文件

Webpack 默认读取的是 webpack.config.js 文件,但在实际开发中我们需要将生产环境和开发环境分开。我们先来整理下配置文件的目录结构,在 scripts 目录下创建三个配置文件。

修改下 package.json 中的 scripts 配置,用来简化命令。

"scripts": {

"dev": "cross-env NODE_ENV=development webpack serve -c scripts/webpack.dev.js",

"build": "cross-env NODE_ENV=production webpack -c scripts/webpack.prod.js"

},

我们使用 NODE_ENV = production 来设置环境变量,为了在不同的平台上都能使用,我们使用 cross-env 来兼容,这样在不同环境下也能正确获取环境变量。

pnpm add cross-env -D

我们将一些通用的配置写在 webpack.base.js 文件里。

const path = require('path')

module.exports = {

entry: path.resolve(__dirname, '../src/index.tsx'),

output: {

path: path.resolve(__dirname, '../dist'), // 打包后的代码放在dist目录下

filename: '[name].[hash:8].js', // 打包的文件名

},

}

在 webpack.dev.js 文件和 webpack.prod.js 中引入通用配置。

// webpack.dev.js

const { merge } = require('webpack-merge')

const base = require('./webpack.base.js')

module.exports = merge(base, {

mode: 'development', // 开发模式

devServer: {

open: true, // 编译完自动打开浏览器

port: 8080,

},

})

// webpack.prod.js

const { merge } = require('webpack-merge')

const base = require('./webpack.base.js')

module.exports = merge(base, {

mode: 'producton', // 生产模式

})

到这里环境基本搭建好了,接下来我们就一步一步的来完善配置。

(3) 配置 babel

由于 webpack 只能识别js、json 文件, 无法识别 jsx/tsx 文件,此时如果我们尝试启动项目肯定会报错。如何让 webpack 能识别呢?此时我们就需要使用 babel-loader 来转换代码,babel-loader 可以让 webpack 在构建的时候借助 Babel 对JS代码进行转译。

注意: Babel 是一个 JavaScript 编译器。主要用于将高版本的JavaScript代码转为向后兼容的JS代码,从而能让我们的代码运行在更低版本的浏览器或者其他的环境中。

babel-loader 的转码功能依赖 Babel 的核心转码包 @babel/core,如果要转义 React 文件还需要引入 @babel/preset-react 这个预设; 对于 ts 我们除了可以使用 ts-loader 外,也可以使用 @babel-preset-typescript 来编译 ts 代码;在实际项目中,考虑到浏览器的兼容性问题,我们都会设置目标浏览器来转换我们的代码,这时候就需要使用到 @babel/preset-env 这个预设。这里就不过多的介绍 babel 配置 ,接下来我们来安装上述提到的关于 babel 的依赖包并进行 webpack 的配置。

pnpm add -D babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript

我们在 webpack.base.js 文件添加下面配置,我们将 js|ts、jsx|tsx 文件都交给 babel-loader 来处理,并配置对应的 presets,这些 presets 会从右向左执行。

{

...

resolve: {

// 配置 extensions 来告诉 webpack 在没有书写后缀时,以什么样的顺序去寻找文件

extensions: ['.mjs','.js', '.json', '.jsx', '.ts', '.tsx'], // 如果项目中只有 tsx 或 ts 可以将其写在最前面

},

module: {

rules: [

{

test: /.(jsx?)|(tsx?)$/,

exclude: /node_modules/,

use: {

loader: 'babel-loader',

options: {

presets: [

[

'@babel/preset-env',

{

targets: 'iOS 9, Android 4.4, last 2 versions, > 0.2%, not dead', // 根据项目去配置

useBuiltIns: 'usage', // 会根据配置的目标环境找出需要的polyfill进行部分引入

corejs: 3, // 使用 core-js@3 版本

},

],

['@babel/preset-typescript'],

['@babel/preset-react'],

],

},

},

},

],

},

...

}

运行 pnpm run build,打包后会生成 dist 目录,可以看到打包后的 js 文件。

此时如果想要在浏览器中访问,我们需要手动在 dist 目录下添加 html 文件,并引入打包好的 js 文件:

Document

在浏览器中打开 html 文件,即可访问。

如果想项目启动或打包时自动生成 html 文件 ,要怎么做呢?我们可以借助 html-webpack-plugin 插件来帮忙自动生成 html文件,先在根目录创建一个模板 index.html 文件。

name="viewport"

content="width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1, maximum-scale=1, minimal-ui, viewport-fit=cover"

/>

安装 html-webpack-plugin:

pnpm add -D html-webpack-plugin

在 webpack.base.js 文件中添加以下配置:

const path = require('path')

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {

// ...

plugins: [

new HtmlWebpackPlugin({

template: path.resolve(__dirname, '../index.html'), // 使用自定义模板

}),

],

// ...

}

pnpm run build一下,dist 目录下已经自动生成了 index.html 文件,并引入了打包的 js 文件。

在 pnpm run dev 启动开发时,我们也能在 localhost:8080 中看到页面了。

注意:在使用 dev-server 启动时,它会读取 Webpack 的配置文件(默认是 webpack.config.js),然后将文件打包到内存中(我们看不到 dist 文件夹的生成,Webpack 会打包到硬盘上),用默认地址打开时默认显示index.html 的内容,如果没有index.html 文件,则显示目录。

(4) css 配置

项目中样式的引用那是必不可少的,这里使用 less 来语法来举例。在 src下创建 index.less 文件,在 App.tsx 中引入。此时启动项目,控制台一定会报错。

// index.less

@color: red;

.wrapper {

display: flex;

color: @color;

}

// App.tsx

import * as React from 'react'

import './index.less'

const App: React.FC = () => {

return

hello 小柒

}

export default App

less 文件可以使用 less-loader 将 less 编译为 css,一般情况下我们还会使用 Postcss 来处理 CSS,在 Webpack 中我们可以使用 postcss-loader 来处理 css。

注意: PostCSS 本身是一个工具,有了它我们可以使用 JavaScript 代码来处理 CSS。它将 CSS 解析成抽象语法树 AST, 将 AST 交给插件来处理并得到结果。PostCSS 的插件体系很强大,提供很多插件,比如:autoprefixer 用来添加浏览器前缀、 cssnano 用来压缩 CSS、 postcss-preset-env 用来根据目标浏览器生成 CSS的 polyfill等等。当然我们也可以实现自己的 PostCSS 插件。

处理过的 css 可以使用 css-loader 来解析成 js ,我们来看看 css-loader 解析之后的内容是什么。

打印出来是一个数组,第二个元素是我们想要的 css 样式。

css-loader 只能帮我们将 css 解析成 js,但不能挂载到元素上。如果想让 css 生效,我们要手动挂载。

这样就达到了我们想要的效果,不过这么写未免有点憨憨。

想要自动挂载样式,style-loader 可以帮我们实现,它负责将 css 样式通过 style 标签插入到 DOM 中。下面是通过 style-loader 实现样式挂载,自动添加 style 标签到head 中。

通过上述,可以来配置我们的 webpack.dev.js 文件了。第一步还是先安装所需要的依赖包。

// less-loader 默认是11版本过高会报错。

// 两种方法:1、要么指定低版本的 less-loader@^6.2.0 2、同时安装 less 和 postcss

pnpm add -D style-loader css-loader postcss-loader less-loader@^6.2.0 postcss-preset-env

// 或者

pnpm add -D style-loader css-loader postcss postcss-loader less less-loader postcss-preset-env

第二步配置 webpack.dev.js 文件。这样我们就可以在代码中正常使用 less 啦~。

// webpack.dev.js

module: {

rules: [

// ...

{

test: /\.less$/,

use: [

'style-loader',

'css-loader',

{

loader: 'postcss-loader',

options: {

postcssOptions: {

[['postcss-preset-env', {}]]

},

},

},

'less-loader',

],

// 排除 node_modules 目录

exclude: /node_modules/,

},

],

},

我们可以发现,css 样式都打包到最终中的 js 文件了,如果项目比较复杂,css 都打包在js文件,js的体积就会越来越大。在生产环境下,我们肯定希望打包出来的文件体积越小越好,在生产环境下,我们一般是用 MiniCssExtractPlugin 代替 style-loader,来将打包后的 js 文件的css提取出来,单独创建一个 css 文件,使用 link 的方式引入。除了分离 css文件减小 js 体积, 还可以使用 CssMinimizerWebpackPlugin 优化、压缩来 CSS 体积。 看看 webpack.prod.js 文件中的配置:

// webpack.prod.js

// ...

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = merge(base, {

// ...

module: {

rules: [

{

test: /\.(css|less)$/,

use: [

MiniCssExtractPlugin.loader, // 使用 MiniCssExtractPlugin.loader 代替 style-loader

'css-loader',

{

loader: 'postcss-loader',

options: {

// 它可以帮助我们将一些现代的 CSS 特性,转成大多数浏览器认识的 CSS,并且会根据目标浏览器或运行时环境添加所需的 polyfill;

// 也包括会自动帮助我们添加 autoprefixer

postcssOptions: {

plugins: [['postcss-preset-env', {}]],

},

},

},

'less-loader',

],

// 排除 node_modules 目录

exclude: /node_modules/,

},

],

},

optimization: {

minimizer: [

// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释

// `...`,

new CssMinimizerPlugin({

// 默认开启

// parallel true: // 多进程并发执行,提升构建速度 。 运行时默认的并发数:os.cpus().length - 1

}),

],

},

plugins: [

new MiniCssExtractPlugin({

filename: 'assets/css/[hash:8].css', // 将css单独提测出来放在assets/css 下

}),

],

})

打包后的效果如下:

(5) 图片&字体

先来看看图片的配置,创建文件夹 assets/images,引入图片 coffee.jpg。

我们在 App.tsx 中引入张图,这里使用 @ 符号来声明路径,需要在 base 文件中进行alias 属性的配置,同时还要对 ts 类型声明进行声明,否则 ts 类型会报错。

此时再编译,会报错,webpack 无法识别图片,需要使用loader 去解析图片资源。

对于图片的处理,在 webpack 5 之前,我们可以使用 file-loader和 url-loader ,这两个 loader 都可以帮我们解析图片资源。

注意

file-loader :不仅仅可以处理图片资源,本质是处理文件导入地址并替换成其访问地址,并把文件输出到相应位置,音视频等资源也可以使用它。url-loader:file-loader 的升级版,包含 file-loader 的全部功能,并且能够根据配置将符合配置的文件转换成 Base64 方式引入,将小体积的图片 Base64 引入项目可以减少 http 请求,也是一个前端常用的优化方式。

我们先来看看 webpack 5 之前如何配置。先使用 file-loader 来处理图片资源,可以看到图片是一个可访问的地址。

再使用url-loader 看看效果,在不设置 limit 限制时,会转换成 Base64 格式引入。

当我们配置了 limit 时(这里设置比较小,项目中应合理配置),图片超过这个限制就会使用file-loader去处理图片,表现如上。一般情况下我们使用url-loader来处理。在webpack.base.js中配置:

// webpack.base.js

resolve: {

//...

alias: {

'@': path.resolve(__dirname, '../src'),

},

},

module: {

rules: [

//...

{

test: /\.(png|jpe?g|gif|svg|webp)$/i,

use: [

// {

// loader: 'file-loader',

// },

{

loader: 'url-loader',

options: {

limit: 2000,

// //限制打包图片的大小:

// //如果大于或等于2000Byte,则按照相应的文件名和路径打包图片;如果小于2000Byte,则将图片转成base64格式的字符串。

// name: 'img/[name].[hash:8].[ext]',

// //img:图片打包的文件夹;

// //[name].[ext]:设定图片按照本来的文件名和扩展名打包,不用进行额外编码

// //[hash:8]:一个项目中如果两个文件夹中的图片重名,打包图片就会被覆盖,加上hash值的前八位作为图片名,可以避免重名。

},

},

],

},

]

}

了解图片的配置方式后,我们同样可以使用 url-loader 去处理字体。一般数字类型都用din,这里我们引入一个ttf字体,使用@font-face 来定义字体名。

接着在 webpack.base.js 文件中配置下。

// webpack.base.js

module: {

rules: [

// ...

{

test: /\.(eot|ttf|woff|woff2)$/i,

use: [

{

loader: 'url-loader',

},

],

},

}

]

}

这样我们就能使用 din 字体了,编译运行,字体生效~。

Webpack 5 中的 asset module 其实已经帮我们处理了,可以直接使用。资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。

这里我们使用 asset 类型,并配置 parser.dataUrlCondition.maxSize 属性,小于 maxSize 的会被打包成 base64,否则会被打包到目录,以url的形式引入。话不多说直接上配置。

module: {

rules: [

// ...

{

test: /\.(png|jpe?g|gif|svg|webp)$/i,

type: 'asset',

parser: {

dataUrlCondition: {

maxSize: 25 * 1024, // 25kb

},

},

generator: {

filename: 'assets/imgs/[name].[hash:8][ext]',

},

},

{

test: /\.(eot|ttf|woff|woff2)$/i,

type: 'asset',

parser: {

dataUrlCondition: {

maxSize: 25 * 1024, // 25kb

},

},

generator: {

filename: 'assets/fonts/[name].[hash:8][ext]',

},

},

]

}

build 一下,图片和字体都被分别打包再 imgs 和 fonts下了。

三、小结

项目上需要用到的基本上都配置完了,当然这只是一个简单的项目配置,随着项目的复杂度变高,webpack 的配置也不同。webpack 5 其实已经内置了很多新的特性,比如内置的 tree-shaking、内置静态资源构建能力、持久化缓存等,让开发体验更友好了。感兴趣的小伙伴也可以试着尝试下哦~。

项目地址: 手把手教你搭建 Webpack 5 + React 项目

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: