likes
comments
collection
share

手把手教你使用webpack从零搭建自己的React项目

作者站长头像
站长
· 阅读数 58

前言

很多同学在使用react库时都是使用create-react-app官方脚手架去生成React项目, 这样有一个缺点就是我们的项目配置不够灵活,虽然可以使用eject命令去暴露一些配置,但是配置毕竟不是我们自己写的,所以改起来也会很头疼,但是如果是我们自己搭建的项目,那么我们改起配置就会很容易,并且这个过程会使我们巩固很多知识, 对webpack的使用会有一个基础的了解

本文需要有对webpack一定的基础知识,如果你的基础不是很好,请先去查阅webpack官网

版本号一致很重要!!!

webpack@5.51.1

webpack-cli@4.8.0

webpack-dev-server@4.2.1

结构目录

MyReact/
|-- config/
|   |-- webpack.base.js
|   |-- webpack.dev.js
|   |-- webpack.pro.js
|-- dist/
|-- images/
|-- pages/
|   |-- Home
|-- public/
|   |-- index.html
|-- App.js
|-- index.js
|-- index.less
|-- package.json
|-- postcss.config.js 
|-- yarn.lock
|-- README

1.生成包管理文件

执行 npm init

包管理文件,俗话说就是说用来管理node_modules的,下面是一些常用配置

{
  "name": "project",
  //如果项目要发布npm,这就是npm包的名称,如果未设置,就不能发布和下载
  "version": "1.0.0",
  //包的版本号,发布时版本不能相同
  "description": "",
  //包的描述信息
  "main": "dist/main.js",
  //包的入口文件
  "scripts": {
    //npm脚本命令,可以封装一些功能,执行快捷操作 如 npm run test
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  //包的关键词信息
  "author": "",
  //作者
  "license": "ISC",
  //开源协议名称
  "files": [
    //要发布的文件或文件夹
    "dist",
    "package.json"
  ],
  "dependencies": {
    //生产依赖,指上线后也需要的包,会打包到dist中
  },
  "devDependencies": {
    //开发依赖,只是开发时会用到的包,不会打包到dist
  },
  "browserslist": {
    //要兼容到的浏览器,它主要被以下工具使用:
    //Autoprefixer
    //Babel
    //post-preset-env
    //eslint-plugin-compat
    //stylelint-unsupported-browser-features
    //postcss-normalize
    "production": [], //生产环境支持
    "development": [] //开发环境支持
  }
}

2.下载所需依赖

推荐使用yarn或者npm(npm如果很慢的话可以切一下镜像源)

npm install react react-dom -S

npm install webpack webpack-cli webpack-dev-server -D

下载完依赖后在public文件夹下创建index.html,这个html文件会作为我们的模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>React</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

接着新建入口文件index.jsApp.js

//index.js
import ReactDom from 'react-dom';
import App from './App';

//渲染组件
ReactDom.render(<App/>, document.querySelector('#root'));
//App.js
import React from 'react';

const App = () => {
    return <div>
        <h1>App</h1>
    </div>
}

export default App;

3.配置webpack开发文件

因为我们写react时使用的是jsx语法,但是浏览器它并不认识这个语法,所以我们要对这些文件进行转换,这里用到了babel相关的插件, 这些插件我们智慧在编译文件时用到,所以我们将它们下载到开发依赖

npm install babel-loader @babel/core @babel/preset-env @babel/preset-react html-webpack-plugin -D

在config文件夹下新建webpack.dev.js

//config/webpack.dev.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const resolve = p => path.resolve(__dirname, p);


module.exports = {
    //模式
    mode: "development",
    //入口,告诉webpack起点是哪里,它会找到所有依赖并处理
    entry: resolve('../index.js'),
    //出口
    output: {
        //最终输出的的目录名称
        path: resolve('dist'),
        //最终输出的文件名称
        filename: "main.js"
    },
    module: {
        rules: [
            {
                //匹配js或jsx类型的文件
                test: /\.jsx?$/,
                //排除node_modules下的文件,因为node_module下的文件不用我们去处理,人家已经处理过了
                exclude: /node_modules/,
                //使用的loader
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', '@babel/preset-react']
                    }
                }
            }
        ]
    },
    //所使用的插件
    plugins: [
        //生成html文件
        new HtmlWebpackPlugin({
            //使用html模板的目录
            template: resolve('../public/index.html'),
            //自动引入打包后的文件,默认值true
            inject: true
        })
    ],
    //开发服务器配置
    devServer: {
        static: {
            //目录
            directory: resolve('dist')
        },
        //压缩
        compress: true,
        //热更新
        hot: true,
        //是否自动打开浏览器
        open: true,
        //端口号
        port: 8888
    }
}

在package.json的scripts中配置

 "scripts": {
    "dev": "webpack-dev-server --config=config/webpack.dev.js"
  }

然后执行 npm run start

手把手教你使用webpack从零搭建自己的React项目

配置路径别名和忽略后缀名

 resolve: {
        extensions: ['.js', '.json'],
        alias: {
            "@": path.resolve(__dirname, 'pages/')
        }
    }

我们继续更改配置让它支持css文件,同样把它们下载到开发依赖

npm i style-loader css-loader postcss-loader postcss-preset-env -D

在根目录下新建postcss.config.js

//postcss.config.js
module.exports = {
    plugins: [
        // 自动将有兼容性的样式加浏览器前缀
        require('postcss-preset-env')
    ]
}

package.json中增加配置

{
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

然后在根目录下新建index.css,然后在index.js中引入

/*
 * index.css
 */
html,body{
  height: 100%;
  background: #f5f5f5;
}
h1{
    color: red;
  }

在webpack.dev.js中的rules增加新的配置

    {
        test: /\.css$/,
        exclude: /node_modules/,
        /*
         * 这里有一点需要注意,当我们使用多个loader去处理文件的时候,它的处理顺序
         * 是从右到左侧,从下往上,loader的执行顺序很重要
         */
         //style-loader 帮我们生成style标签并插入到html中
         //css-loader 处理css文件的loader
         //postcss-loader 也使用来处理css文件的,需结合第三方库使用,比如postcss-preset-env autofixer
        use:['style-loader','css-loader','postcss-loader']
    }

接着把我们的css文件引入一下,发现样式生效了。

手把手教你使用webpack从零搭建自己的React项目

当然,我们的项目一般不会直接使用css文件,都会只用less或scss等这些css预处理器, 那么我们也配置一下less预处理器,让我们的项目支持less文件吧

npm i less less-loader -D

更改webpack rules配置,这样的话这个less和css文件就能被同时处理了

 {
     test: /\.(less|css)$/,
     exclude: /node_modules/,
     use: [
            'style-loader',
            'css-loader',
            'postcss-loader',
            'less-loader'
         ]
 }

然后把index.css改成index.less,并写一些嵌套的语法,发现它同样生效。

如需使用其他预处理原理也是一样的,可以根据不同的需求去配置不同的配置

接着配置静态资源,webpack5已经不需要我们使用file-loader或url-loader去处理的图片了,它内部已经支持静态资源的处理了,并且可以配置limit将小图片输出成base64格式从而减少请求次数

{
    test: /\.(png|svg|jpg|jpeg|gif)$/i,
    type: 'asset/resource',
    generator: {
        filename: 'images/[hash][ext][query]'
    }
}

然后我们找一张图片引入一下发现生效了

手把手教你使用webpack从零搭建自己的React项目

3.配置webpack打包环境

其实production环境和development环境的配置大同小异,只是会做一些文件优化和构建速度的优化, 比如压缩css文件,压缩html文件,开启多进程打包和使用缓存等

新建webpack.pro.js

// config/webpack.pro.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require("clean-webpack-plugin"); //用来清除上一次打包的文件目录的插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; //打包分析工具
const MiniCssExtractPlugin = require('mini-css-extract-plugin');//将css提取成单独的文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); //压缩css文件
const resolve = p => path.resolve(__dirname, p);

module.exports = {
    //模式
    mode: "production",
    //入口,告诉webpack起点是哪里,它会找到所有依赖并处理
    entry: resolve('../index.js'),
    //出口
    output: {
        //最终输出的的目录名称
        path: resolve('../dist'),
        //最终输出的文件名称
        filename: "./js/[name].js"
    },
    module: {
        rules: [
            {
                //一种类型的文件只匹配一个loader,提高性能
                oneOf: [
                    {
                        //匹配js或jsx类型的文件
                        test: /\.jsx?$/,
                        //排除node_modules下的文件,因为node_module下的文件不用我们去处理,作者已经处理过了
                        exclude: /node_modules/,
                        //使用的loader
                        use: {
                            loader: 'babel-loader',
                            options: {
                                presets: ['@babel/preset-env', '@babel/preset-react']
                            }
                        }
                    },
                    {
                        test: /\.(less|css)$/,
                        exclude: /node_modules/,
                        use: [
                            MiniCssExtractPlugin.loader,
                            'css-loader',
                            'postcss-loader',
                            'less-loader'
                        ]
                    },
                    {
                        test: /\.(png|svg|jpg|jpeg|gif)$/i,
                        type: 'asset/resource',
                        generator: {
                            filename: 'images/[hash][ext][query]'
                        }
                    }
                ]
            }
        ]
    },
    //用于提高性能,可以进行代码分割,
    optimization: {
        //告知webpack使用 TerserPlugin 或其它在 optimization.minimizer 定义的插件压缩 bundle。
        minimize: true,
        //分割模块
        splitChunks: {
            chunks: 'all'
        },
        //允许你通过提供一个或多个定制过的 TerserPlugin 实例, 覆盖默认压缩工具(minimizer)。
        minimizer: [
            new CssMinimizerPlugin()
        ]
    },
    //所使用的插件
    plugins: [
        new CleanWebpackPlugin(),
        new BundleAnalyzerPlugin(),
        new MiniCssExtractPlugin({
            //输出的目录
            filename: 'css/[hash].css'
        }),
        //生成html文件
        new HtmlWebpackPlugin({
            //使用html模板的目录
            template: resolve('../public/index.html'),
            //自动引入打包后的文件,默认值true
            inject: true
        })
    ]
}

增加build快捷命令

 "scripts": {
    "build": "webpack --config=config/webpack.pro.js"
  }

接下来我们运行npm run build,会发现根目录下多了一个dist文件夹,这个dist文件夹就是最终打包出来的效果,同事webpack-bundle-analyzer也会帮我打开一个页面,让我们有助于观察摸个bundle的体积大小,从而让我们更清楚的看到优化空间

4.配置文件优化

其实不难看出,我们的dev文件和pro文件有好多的冗余代码,这样我们想改个配置两个文件要各改一遍,还会有一定的维护成本,所以我们可以将公共部分提取出来

在config下新建webpack.base.js并提取公共配置,

//config/webpack.base.js
const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require('webpack');
const resolve = p => path.resolve(__dirname, p);
module.exports = {
    //入口,告诉webpack起点是哪里,它会找到所有依赖并处理
    entry: resolve('../index.js'),
    //出口
    output: {
        //最终输出的的目录名称
        path: resolve('../dist'),
        //最终输出的文件名称
        filename:'./js/[name].js'
    },
    resolve: {
        extensions: ['.js', '.json', '.wasm'],
        alias: {
            "@": path.resolve(__dirname, 'components/')
        }
    },
    module: {
        rules: [
            {
                //匹配js或jsx类型的文件
                test: /.jsx?$/,
                //排除node_modules下的文件,因为node_module下的文件不用我们去处理,人家已经处理过了
                exclude: /node_modules/,
                //使用的loader
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', '@babel/preset-react']
                    }
                }
            },
            {
                test: /.(le|c)ss$/,
                exclude: /node_modules/,
                use: [
                    'style-loader',
                    'css-loader',
                    'postcss-loader',
                    'less-loader'
                ]
            },
            {
                test: /.(png|svg|jpg|jpeg|gif)$/i,
                type: 'asset/resource',
                generator: {
                    filename: 'images/[hash][ext][query]'
                }
            }
        ]
    },
    //所使用的插件
    plugins: [
        //生成html文件
        new HtmlWebpackPlugin({
            //使用html模板的目录
            template: resolve('../public/index.html'),
            //自动引入打包后的文件,默认值true
            inject: true
        }),
        //使用插件定义全局变量DEV
        new webpack.DefinePlugin({
            'process':JSON.stringify({
                env:'development'
            })
        })
    ],
    node: {
        global: true
    },
}

修改webpack.dev.js

//config/webpack.dev.js
const path = require("path");
const resolve = p => path.resolve(__dirname, p);
const webpackBaseConfig = require('./webpack.base');//webpack公用配置
const {merge} = require('webpack-merge'); //用于合并webpack配置
const webpackDevConfig = {
    devtool: 'source-map',
    //模式
    mode: "development",
    //开发服务器配置
    devServer: {
        static: {
            //目录
            directory: resolve('dist')
        },
        //压缩
        compress: true,
        //热更新
        hot: true,
        //是否自动打开浏览器
        open: true,
        //端口号
        port: 8888
    }
}


const result = merge(webpackBaseConfig, webpackDevConfig)
module.exports = result;

修改webpack.pro.js

//config/webpack.pro.js
const {CleanWebpackPlugin} = require("clean-webpack-plugin"); //用来清除上一次打包的文件目录的插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; //打包分析工具
const MiniCssExtractPlugin = require('mini-css-extract-plugin');//将css提取成单独的文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); //压缩css文件
const webpackBaseConfig = require('./webpack.base');//webpack公用配置
const {merge} = require('webpack-merge'); //用于合并webpack配置
const webpack = require('webpack');
const webpackProConfig = {
    //模式
    mode: "production",
    //用于提高性能,可以进行代码分割,
    optimization: {
        //告知webpack使用 TerserPlugin 或其它在 optimization.minimizer 定义的插件压缩 bundle。
        minimize: true,
        //分割模块
        splitChunks: {
            chunks: 'all'
        },
        //允许你通过提供一个或多个定制过的 TerserPlugin 实例, 覆盖默认压缩工具(minimizer)。
        minimizer: [
            new CssMinimizerPlugin()
        ]
    },
    //所使用的插件
    plugins: [
        new CleanWebpackPlugin(),
        new BundleAnalyzerPlugin(),
        new MiniCssExtractPlugin({
            //输出的目录
            filename: 'css/[hash].css'
        }),
        //使用插件定义全局变量DEV
        new webpack.DefinePlugin({
            'process':JSON.stringify({
                env:'production'
            })
        })
    ],
    module: {
        rules: [
            {
                test: /.(le|c)ss$/,
                exclude: /node_modules/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader',
                    'less-loader'
                ]
            }
        ]
    },
    //不编译进dist的包,用于优化dist体积
    externals: {}
}


module.exports = merge(webpackBaseConfig, webpackProConfig);

5.结尾

这样我们的一个最基础的配置就搭建好了,基本上可以满足日常的使用,和官方脚手架相比配置少了很多,但是是我们自己一点点搭建起来的,所以说灵活性比较高,如果你的项目不是很复杂可以考虑用自己搭建的项目,但是如果项目很庞大还是用官方的比较好,官方配置更成熟,更能满足我们的需要

最后附上git仓库地址地址,感兴趣的朋友一定要自己去敲一遍!!!

转载自:https://juejin.cn/post/7022503679752142862
评论
请登录