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

前言

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

一、简单聊下 Webpack

1.1 Webpack 的好处

试想在不使用任何打包工具的情况下,我们很难在项目去使用 es6+ 新语法,TypeScript即使是新的浏览器,也不支持,更别说在项目中使用 ReactVue 了。打包工具能帮我们解决这些问题,打包工具有很多,比如 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

引入 reactreact-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 />)

// App.tsx
import * as React from 'react'

const App: React.FC = () => {
  return <div>hello 小柒</div>
}

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 目录下创建三个配置文件。

image.png

修改下 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 文件。

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

<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
	<script defer src="main.7a68ecf3.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

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

image.png

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

<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1, maximum-scale=1, minimal-ui, viewport-fit=cover"
    />
    <title></title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

安装 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 文件。

image.png

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 <div className="wrapper">hello 小柒</div>
}

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 解析之后的内容是什么。

image.png

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

image.png

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

image.png

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

image.png

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

image.png

通过上述,可以来配置我们的 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 下
    }),
  ],
})

打包后的效果如下:

image.png

(5) 图片&字体

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

image.png

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

image.png

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

image.png

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


注意💡

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

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

image.png

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

image.png

当我们配置了 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 来定义字体名。

image.png

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

// webpack.base.js
module: {
	rules: [
    // ...
		{
       test: /\.(eot|ttf|woff|woff2)$/i,
       use: [
          {
            loader: 'url-loader',
          },
        ],
      },
		}
	]
}

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

image.png

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

image.png

这里我们使用 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下了。

image.png

三、小结

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


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

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
redux、reactwebpack是目前非常流行的前端技术,它们可以帮助我们快速构建复杂的单页面应用。而热加载是一种非常方便的开发工具,能够在我们修改代码后自动重新编译并刷新页面,极大地提高了开发效率。 然而,IE8是一个老旧的浏览器,在支持新技术的同时也存在很多兼容性问题。要使我们的应用在IE8上运行,我们需要进行一些特殊的处理。 下面是一些可能有用的技巧: 1. 使用babel进行ES6代码转换:redux和react都使用了一些ES6语法,而IE8并不支持这些语法。我们可以使用babel将ES6代码转换为ES5代码,从而在IE8上运行。同时,我们还需要使用babel-polyfill来提供一些ES6新特性的支持。 2. 使用es5-shim来提供一些ES5新特性的支持:IE8对ES5的支持也不完整,我们可以使用es5-shim来提供一些ES5新特性的支持。 3. 使用es6-shim来提供一些ES6新特性的支持:IE8对ES6的支持更加有限,我们可以使用es6-shim来提供一些ES6新特性的支持。 4. 使用webpack的兼容性插件:webpack提供了一些兼容性插件,可以自动处理一些兼容性问题,比如babel-loader、es3ify-webpack-plugin等。 5. 使用react-ie8来提供react在IE8上的支持:react-ie8是一个为react提供在IE8上支持的库,可以很方便地解决一些兼容性问题。 6. 使用redux-ie8来提供redux在IE8上的支持:redux-ie8是一个为redux提供在IE8上支持的库,可以很方便地解决一些兼容性问题。 总之,要使我们的应用在IE8上运行,需要进行一些特殊的处理,包括使用babel进行ES6代码转换、使用es5-shim和es6-shim来提供一些新特性的支持、使用webpack的兼容性插件、使用react-ie8和redux-ie8来提供在IE8上的支持等。这些技巧需要不断地更新和完善,以适应不断变化的前端技术。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值