Manually build a React project from scratch using webpack

Since I came into contact with the front end, every time I create a new project, I almost use create-react-appscaffolding for convenience. When I got to work, I found that the company's projects were all built and configured by the bosses, and I didn't need to care about how the project was built and some internal configuration details. Although, some popular packaging tools (such as webpack, vite) on the market have been seen, but they are always forgotten after reading. Without personal practice, it is impossible to understand the true meaning. So this article uses online materials to manually build a React project.

1. Initialize the project

First create a project folder, which is named zero-react-app in this article, to store project files, as shown below:

image.png

Then enter the cmd terminal interface and switch to the path of the folder:

image.png

Then enter the command in cmd npm initto initialize a project:

npm init

The interface after running is as follows:

image.png

Some project-related information needs to be entered:

  • package name: project name
  • version: project version
  • description: item description
  • entry point: entry file
  • test command: test command
  • git repository: Git warehouse address
  • keywords: keywords
  • license: version information

These information can be entered directly to use the default values, or you can customize the settings.

npm initAfter the command runs successfully, a file will be created in the project folder package.json, which stores the basic information specified when creating the project:

image.png

What is the package.json file?

package.json is located in the root directory of the project and is the configuration file of the project, such as configuring project startup, packaging commands, declaring dependent packages, etc.
For specific configuration items, see npm doc or the introduction on Github .

2. Install webpack

First install webpack

npm install webpack webpack-cli -D

After installation, the project folder is as follows, with the addition of node_modules folder and package-lock.json file.

image.png

node_modules文件夹中存放着所有安装的依赖包。

package.json 和 package-lock.json的区别

package.json:运行 npm init 时生成,主要作用是描述项目以及记录项目依赖的模块信息(模块名称、大版本信息等)

package-lock.json:运行 npm install 时生成,用于记录所有模块的详细信息,包括版本信息、模块来源以及依赖的小版本信息等。

之所以引入 package.json 文件,主要用途在于:

  • 描述依赖关系树的单一表示,以便保证团队成员、部署和持续集成安装完全相同的依赖关系。(即锁定所有依赖的版本号)
  • npm install 重新安装全部依赖时,可以通过 package-lock.json 文件指定的下载地址和相关依赖,相对加快下载速度。

有关package-lock.json文件中具体配置,可以见官方说明

那为什么要安装 webpack 呢?

按官方定义,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 >webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图(dependency graph),然后>将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

安装好webpack后,需要在根目录下创建一个webpack.config.js文件,这是webpack的配置文件。该文件配置主要包含以下几部分:

  • 入口(entry):指示 webpack 应该使用哪个模块来作为构建其内部依赖图的开始。默认值是 ./src/index.js。具体配置可见官方文档
module.exports = {
  entry: './path/to/my/entry/file.js',
};
  • 输出(output):告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。
const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    // bundle生成位置
    path: path.resolve(__dirname, 'dist'),
    // bundle文件名
    filename: 'my-first-webpack.bundle.js',
  },
};
  • loader:webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。loader主要用于转换源代码,可以使你在import 或 "load(加载)" 模块时预处理文件。通常有两个属性:
    • test 属性,识别出哪些文件会被转换。
    • use 属性,定义出在进行转换时,应该使用哪个 loader

module.rules 允许你在 webpack 配置中指定多个 loader。
loader 支持链式调用。链中的每个 loader 会将转换应用在已处理过的资源上。一组链式的 loader 将按照相反的顺序执行。链中的第一个 loader 将其结果传递给下一个 loader,依此类推。最后,链中的最后一个 loader,返回 webpack 所期望的 JavaScript。

module.exports = {
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          // [style-loader](/loaders/style-loader)
          { loader: 'style-loader' },
          // [css-loader](/loaders/css-loader)
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          },
          // [sass-loader](/loaders/sass-loader)
          { loader: 'sass-loader' }
        ]
      }
    ]
  }
};
  • 插件(plugin):loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。 webpack 插件是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且在 整个 编译生命周期都可以访问 compiler 对象。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 访问内置的插件
const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    filename: 'my-first-webpack.bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,
        use: 'babel-loader',
      },
    ],
  },
  plugins: [
    new webpack.ProgressPlugin(),
    new HtmlWebpackPlugin({ template: './src/index.html' }),
  ],
};
  • 模式(mode):主要有开发模式(development)和生产模式(production

三、创建项目目录结构

  • 在项目根目录下创建 src文件夹。src文件夹用于存放项目代码文件.
    • 在其中创建一个index.js文件作为项目的入口文件。
  • 在项目根目录下创建public文件夹。
    • public文件夹下创建一个index.html文件,作为生成HTML文件的模板。

初始项目目录结构如下图:

image.png

紧接着向 webpack.config.js 中写入一些基本的webpack配置:

const path = require('path');

module.exports = {
    mode: 'development',  // 开发模式
    entry: './src/index.js', // 入口文件
    output: {
        // 必须是绝对路径
        path: path.resolve(__dirname, 'dist'), //打包后文件的输出位置
        filename: 'main.js',  // 打包后的文件名
        clean: true  // 打包之前清理dist文件夹
    },
}

我们在入口文件 ./src/index.js 文件中写入一段 JS 代码,测试是否能够打包成功:

image.png

然后运行 npx webpack --config webpack.config.js进行打包,可以看到打包后输出到了./dist/main.js

image.png

注意:

npx webpack --config webpack.config.js 可以简写为 npx webpack,因为当存在webpack.config.js文件时webpack会默认选择该文件,--config参数表明可以传递任何名称的配置文件。

四、安装 HtmlWebpackPlugin 插件

该插件将自动生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle。

npm install -D html-webpack-plugin

安装好后,我们可以在webpack.config.js文件中进行如下配置。

const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    ...
    plugins: [
        new htmlWebpackPlugin({
            template: './public/index.html',  // 生成HTML文件的模板文件
            filename: 'index.html',  // 生成的HTML文件名
            inject: 'body'  // <script>标签插入的地方
        })
    ],
    ...
}

然后我们每次使用 webpack 进行打包时,都会自动在dist文件夹中自动生成一个 index.html文件。

五、加载样式文件

项目中存在各种各样的样式文件,我们可以通过loader来加载。通常我们需要安装以下loader

以加载less文件为例;

首先安装相关loader 以及 less:

npm install -D style-loader css-loader less-loader less

然后在webpack.config.js文件中添加如下配置:

module.exports = {
    ...
    module: {
        rules: [
            {
                test: /\.(css|less)$/,
                use: ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    } 
    ...
}

当 webpack 打包 less 文件时,会依次调用'less-loader', 'css-loader', 'style-loader'对less文件进行解析。['style-loader', 'css-loader', 'less-loader']的书写顺序和调用顺序(从后往前)相反。

抽离和压缩CSS

MiniCssExtractPlugin:会将CSS提取到单独的文件中,为每个包含CSS的JS文件(打包后的)创建一个CSS文件,并且支持CSS和SourceMaps的按需加载

npm install -D mini-css-extract-plugin

webpack.config.js中添加如下配置:

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
    ...
    plugins: [
        new MiniCssExtractPlugin({
            filename: './styles/[contenthash].css'
        })
    ],
    module: {
        rules: [
            {
                test: /\.(css|less)$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
            }
        ]
    } 
    ...
}

CssMinimizerWebpackPlugin:这个插件使用cssnano优化和压缩CSS

npm install -D css-minimizer-webpack-plugin
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
    ...
    mode: 'production',
    
   optimization: {
       minimizer: new CssMinimizerWebpackPlugin()
   }
    ...
}

六、静态资源打包

资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。

在 webpack 5 之前,通常使用:

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
  • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。

例如打包 .png 格式的图片:

module.exports = {
    ...
    module: {
        rules: [
            {
                test: /\.png$/,
                type: 'asset/resource',
                generator: {
                    filename: 'images/[contenthash][ext]'
                }
            },
        ]
    } 
    ...
}

此外 webpack 也可以打包 .csv .xml 格式的文件,不过需要借助以下 loader:

  • csv-loader:将 csv 文件内容转换成数组
  • xml-loder:将 xml 文件内容转换成对象

这类静态资源如何打包,可根据官方文档以及具体项目需求进行配置。

七、支持 ES6 语法(babel-loader)

对于某些浏览器而言,并不支持 ES6 语法,所以需要使用babel-loader将代码转换成 ES5 语法。假设我们在index.js文件写入ES6的箭头函数:

image.png

然后我们看webpack打包后的文件:

image.png

可以看到,webpack 并未对其做任何转换。然后我们安装 babel-loader

npm install -D babel-loader @babel/core @babel/preset-env
  • babel-loader :webpack中用babel解析ES6的桥梁
  • @babel/core:babel的核心模块
  • @babel/preset-env:babel预设,一组babel插件的集合 然后在webpack.config.js文件中添加如下配置:
module.exports = {
    ...
    module: {
        rules: [
            {
                test: /\.m?js$/,
                exclude: /(node_modules)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
           }
        ]
    } 
    ...
}

然后再进行打包,可以看到箭头函数转换成了ES中的函数声明:

image.png

此外还需要安装regeneratorRuntime插件,这是webpack打包生成的全局辅助函数,由babel生成,用于兼容async/await语法。所以需要安装和配置插件:

// 该包中含有 regeneratorRuntime
npm install --save @babel/runtime

// 该插件会在需要regeneratorRuntime的地方自动require导入
npm install --save-dev @babel/plugin-transform-runtime

然后将配置文件修改如下:

module.exports = {
    ...
    module: {
        rules: [
            {
                test: /\.m?js$/,
                exclude: /(node_modules)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                        plugins: [
                            ["@babel/plugin-transform-runtime"]
                        ]
                    }
                }
           }
        ]
    } 
    ...
}

至此,babel相关配置已经完成。当然,实际项目中会根据实际要求进行更复杂的配置,可参考Babel官网

八、集成React

本文目的在于搭建一个 React 项目,上述已经完成了基本配置,紧接着我们将引入React。首先安装React:

npm install -D react react-dom

此外,我们仍然需要安装@babel/preset-react用于将 jsx 转换成 ES5.

npm install -D @babel/preset-react

然后在 webpack.config.js中添加如下配置:

{
    test: /\.(js|jsx)$/,
    exclude: /(node_modules)/,
    use: {
      loader: 'babel-loader',
      options: {
        presets: [
            '@babel/preset-env',
            '@babel/preset-react'
        ],
        plugins: [
            // 主要改变在此处
            ["@babel/plugin-transform-runtime"]
        ]
      }
    }
}

然后我们在index.js中写入如下代码:

image.png

HTML 模板文件也需要进行如下修改:

image.png

然后执行打包命令,打包后的文件在浏览器中显示如下:

image.png

至此,一个简单的React项目就搭建完成了。

九、模块热更新

在实际开发过程中,如果每次修改后都执行一次npx webpack 打包命令,操作过于繁琐。对此,我们引入webpack-dev-server,提供一个本地的web服务,同时具有live reloading(实时重新加载)功能。

npm install -D webpack-dev-server

然后在 webpack.config.js中添加如下配置:

module.exports = {
    ...
    devServer: {
        hot: true,
        static: './dist'
    },
    ...
}

然后在 package.json 文件的script中添加启动命令:
"start": "webpack-dev-server --open-app-name chrome"

然后我们就可以在命令行中执行npm start 启动服务了。

十、引入 TypeScript

TypeScript is currently used in most front-end project development. TypeScript  is a superset of JavaScript, which adds a type system and can be compiled into ordinary JavaScript code. If you want to use TypeScript for coding in your project, first we need to install TypeScript compiler and loader:

npm install -D typescript ts-loader

Then add a file in the root directory tsconfig.jsonand enter the basic configuration:

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true,
    "moduleResolution": "node"
  }
}

For more detailed configuration of TypeScript, you can check  the official documentation of TypeScript  to learn more about  tsconfig.json configuration options.

So far, a simple React project has been built. Of course, there are more complex configurations in actual development. You can refer to the official webpack documentation for configuration.

This article is a record of the process of learning to manually build a React project. There are errors or unreasonable places in it. Welcome to correct me.

reference:

Manually build react projects without scaffolding (webpack5 + Antd4 + React18)

Guess you like

Origin juejin.im/post/7255955134131404860