Webpack@5.0之搭建React项目

1. 概述

之前都是使用React脚手架来搭建React项目的,这次想尝试放弃脚手架直接使用Webpack来搭建一个完整的React项目,学习一下Wepack的使用,在记录一下搭建过程,之前每次学习Wepack,过段时间都会忘记,这次记录一下,不求能够永远记住,但求能记得时间更久一点。

2. 创建项目目录

  1. 创建项目
mkdir webpack-react
  1. 创建package.json文件
  • 使用默认方式创建:
npm init -y 
  • 使用自定义方式创建
npm init 
  1. 创建项目目录
  • 创建config,webpack等配置文件
  • 创建scripts,start.js、build.js 项目启动和打包文件
  • 创建public/index.html,html模板
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Title</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
  • 创建src,源代码目录
  • 创建src/index.js,源代码入口
  • 创建src/components,源代码组件目录

3. 下载依赖文件

--save-dev:(简写-D)将依赖写入package.js文件devDependencies模块,为开发环境所需依赖。
--save: (简写-S)生产环境所需依赖,为产品发布后不可少的依赖,将依赖写入package.js文件devDependencies模块。

  1. React 依赖文件
npm install react react-dom --save
  1. Webpack 依赖文件
npm install webpack webpack-cli webpack-dev-server --save-dev
  1. Babel 依赖
  • 核心: @babel/core babel 核心包,提供转换的API
npm install @babel/core --save-dev
  • 预设:
    @babel/preset-env:对es2015, es2016. es2017的支持
    @babel/preset-react:Babel可以转换JSX语法
npm install @babel/preset-env @babel/preset-react -D
  • 插件:
    @babel/plugin-proposal-class-properties:解析类的属性
    @babel/plugin-proposal-decorators:解析装饰器
    @babel/plugin-transform-runtime:将 helper 和 polyfill 都改为从一个统一的地方引入,并且引入的对象和全局变量是完全隔离的,可以提高代码重用性,缩小编译后的代码体积
    @babel/runtime-corejs3:和@babel/plugin-transform-runtime搭配使用,@babel/plugin-transform-runtime 的作用是转译代码,转译后的代码中可能会引入 @babel/runtime-corejs3 里面的模块。所以前者运行在编译时,后者运行在运行时,类似于 polyfill。后者需要被打包到最终产物里在浏览器中运行。
npm install @babel/plugin-proposal-class-properties -D
npm install @babel/plugin-proposal-decorators -D
npm install @babel/plugin-transform-runtime -D
npm install @babel/runtime-corejs3 -D

4. 配置文件

4.1 babel配置

在项目根目录下创建 .babelrc 文件。

babel 在转译的时候,会将源代码分成 syntax 和 api 两部分来处理:

  • syntax:类似于展开对象、optional chain、let、const 等语法
  • api:类似于 [1,2,3].includes 等函数、方法,使用polyfill来处理api
{
  "presets": [
    "@babel/preset-react",
    [
      "@babel/preset-env",
      {
        // 告诉 babel 如何处理api
        // false, 默认值,不处理api
        // entry, 需要手动引入 @babel/polyfill, 并且babel会将所有的 polyfill 全部引入,导致包非常大
        // usage, babel 就可以按需加载 polyfill,并且不需要手动引入 @babel/polyfill
        "useBuiltIns": "usage",
        "debug": true
      }
    ]
  ],
  "plugins": [
    [
      // 编译类属性
      "@babel/plugin-proposal-class-properties", 
      {
      	// 确定类属性的编译方式,赋值方式还是定义凡是
      	// true,赋值方式
      	// false, 定义方式,即使用Object.defineProperty()定义属性
        "loose": true
      }
    ],
    // 解决 polyfill 的解析结果全局变量污染问题
    [
    	"@babel/plugin-transform-runtime",
    	{
			"corejs": "3",
		}
    ]
  ]
}

4.2 webpack配置

4.2.1 配置devServer 、解析js和jsx

// 解析 js jsx
npm i babel-loader -D
// 解析 html
npm i html-webpack-plugin -D
// 删除上次打包生成的文件
npm i clean-webpack-plugin -D
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

const srcPath = path.resolve(__dirname, '../src');

module.exports = {
  entry: `${srcPath}/index.js`,
  output: {
    path: path.resolve(__dirname, '../build'),
    filename: 'js/[name].[hash].bundle.js',
    publicPath: '/'
  },
  devServer: {
    port: 9999,
    contentBase: path.join(__dirname, '../public'),
    compress: true,
    open: true,
    hot: true
  },
  resolve: {
    extensions: ['.js', '.jsx']
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        }
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'webpack-react',
      template: path.resolve(__dirname, '../public/index.html'),
      filename: 'index.html',
      inject: 'body',
      minify: false
    })
  ]
}

4.2.2 解析 css、scss

4.2.2.1 下载文件
npm install node-sass -D
npm install style-loader css-loader sass-loader -D
// 自动补充css样式前缀
npm install postcss-loader postcss-preset-env -D
// 将样式提取为单独的文件
npm install mini-css-extract-plugin -D
4.2.2.2 配置
......
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
......

module.exports = {
  ......
  module: {
    rules: [
      ......
      {
        test: /\.(c|sc)ss$/,
        exclude: /node_modules/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  require('postcss-preset-env')
                ]
              }
            }
          },
          'sass-loader'
        ]
      }
    ]
  },
  plugins: [
    ......
    new MiniCssExtractPlugin({
      filename: 'css/[name].[hash].css',
      chunkFilename: 'css/[id].css'
    })
  ]
}
4.2.2.3 备注:配置postcss-loader,必须添加浏览器配置

package.json添加浏览器配置:

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

4.2.2 解析图片文件,字体图标

4.2.2.1 下载文件
npm install file-loader file-loader -D
4.2.2.2 配置
......

module.exports = {
  ......,
  module: {
    rules: [
      ......,
      // 图片解析
      {
        test: /\.(jpg|png|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              // 图片大小超过8kb,将图片转化成base64
              limit: 8 * 1024,
              name: '[name].[hash].[ext]',
              // 将解析后的图片放在image文件夹下
              outputPath: 'image'
            }
          }
        ]
      },
      // 字体图标解析
      {
        test: /.(ttf|woff|woff2)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: 'fonts'
          }
        }
      }
    ]
  },
  ......
}

4.2.2 复制静态文件

如果在index.html中直接引入了静态文件,需要将静态文件复制到输出文件夹中。

npm install copy-webpack-plugin -D
......

const copyWebpackPlugin = require('copy-webpack-plugin');

......

module.exports = {
  ......,
  
  plugins: [
    ......,
    // 将public下的除了index.html以外的静态文件复制到build文件夹内
    new copyWebpackPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, '../public'),
          to: path.resolve(__dirname, '../build'),
          globOptions: {
            dot: true,
            gitignore: true,
            ignore: ["**/index.html"],
          }
        }
      ]
    })
  ]
}

5. webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const copyWebpackPlugin = require('copy-webpack-plugin');

const srcPath = path.resolve(__dirname, '../src');

module.exports = ({mode}) => {
  return {
    entry: `${srcPath}/index.js`,
    // 出口
    output: {
      path: path.resolve(__dirname, '../build'),
      filename: 'js/[name].[hash].bundle.js',
      publicPath: '/'
    },
    // webpack-server 配置
    devServer: {
      contentBase: path.join(__dirname, '../public')
    },
    // 模式, development, production
    mode: mode,
    // 生成资源映射文件
    devtool: mode === 'development' ? 'inline-source-map' : false,
    resolve: {
      // 文件扩展名
      extensions: ['.js', '.jsx']
    },
    module: {
      rules: [
        {
          test: /\.(js|jsx)$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
          }
        },
        {
          test: /\.(c|sc)ss$/,
          exclude: /node_modules/,
          use: [
            {
              loader: MiniCssExtractPlugin.loader,
              options: {
                publicPath: '/'
              }
            },
            'css-loader',
            {
              loader: 'postcss-loader',
              options: {
                postcssOptions: {
                  plugins: [
                    require('postcss-preset-env')
                  ]
                }
              }
            },
            'sass-loader'
          ]
        },
        {
          test: /\.(jpg|png|gif)$/,
          use: {
            loader: 'url-loader',
            options: {
              limit: 8 * 1024,
              name: '[name].[hash].[ext]',
              outputPath: 'image'
            }
          }
        },
        {
          test: /\.(ttf|woff|woff2)$/,
          use: {
            loader: 'file-loader',
            options: {
              name: '[name].[ext]',
              outputPath: 'fonts'
            }
          }
        }
      ]
    },
    plugins: [
      // 删除上次打包生成的文件
      new CleanWebpackPlugin(),
      // 生成html
      new HtmlWebpackPlugin({
        title: 'webpack-react',
        template: path.resolve(__dirname, '../public/index.html'),
        filename: 'index.html',
        inject: 'body',
        minify: false
      }),
      // 提取css到单独的文件中
      new MiniCssExtractPlugin({
        filename: 'css/[name].[hash].css',
        chunkFilename: 'css/[id].css'
      }),
      // 复制public中的静态文件
      new copyWebpackPlugin({
        patterns: [
          {
            from: path.resolve(__dirname, '../public'),
            to: path.resolve(__dirname, '../build'),
            globOptions: {
              dot: true,
              gitignore: true,
              ignore: ["**/index.html"],
            }
          }
        ]
      })
    ]
  }
}

6. 使用node.js启动项目,并解决端口占用问题

如果使用node.js来启动项目,需要删除webpack配置文件中的devServer 配置项。

// 控制台打印样式
npm install chalk -D
// 获取本机的 IP, MAC and DNS servers
npm install address -D

6.1 start.js 文件

const webpack = require('webpack');
const WebpackServer = require('webpack-dev-server');
const webpackConfig = require('../config/webpack.config');
const net = require('net');
const chalk = require('chalk');
const address = require('address');

const compiler = webpack(webpackConfig({ mode: 'development' }));
// 默认端口号
let port = 8081;

function listenPort() {
  const server = net.createServer().listen(port);

  // 服务是否在监听连接,如果在监听表示端口没有被占用
  // 关闭服务,释放端口,启动webpack服务
  server.on('listening', function() {
    server.close();
    startWebpackServer();
  });

  // 服务出现错误
  server.on('error', function(err) {
    if (err.code === 'EADDRINUSE') {
      console.log(chalk.yellow(`端口:${port},被占用,修改端口为:${++port}`));
      listenPort();
    }
  })
}

function startWebpackServer() {
  const serverConfig = Object.assign({}, webpackConfig.devServer || {}, {
    host: address.ip(),
    //启用 gzip 压缩。
    compress: true,
    //启动索引html文件,默认index.html
    index: 'index.html',
    //是否启用热替换
    hot: true,
    //启用内联模式(inline mode),会在控制台打印消息,用none阻止。
    clientLogLevel: 'none',
    //dev-server 的两种不同模式之间切换:true内联模式(inline mode)、 false: iframe 模式,默认true。
    inline: true,
    //自动打开浏览器
    open: true,
  });

  const webpackServer = new WebpackServer(compiler, serverConfig);
  webpackServer.listen(port);
}

listenPort();

7. node.js 调用 webpack 打包

7.1 build.js 文件

const webpack = require('webpack');
const webpackConfig = require('../config/webpack.config');
const chalk = require('chalk');
const ora = require('ora');

const spinner = ora('[building]').start();

const compiler = webpack(webpackConfig({ mode: 'production' }));

compiler.run(function(err, stats) {
  if (err || stats.hasErrors()) {
    console.log(chalk.red('build fail:' + stats.hasErrors()));
    return;
  }
  spinner.stop();
  console.log(chalk.green('build success'));
})

小贴士

1. Error: Cannot find module ‘webpack-cli/bin/config-yargs‘

原因:webpack更新至5.0,但是webpack-dev-server没有更新导致的。
解决方法:调整webpack-cli版本至3.3.12

2. devtool配置

// 不生成资源映射文件
devtool: false

Babel配置:https://zhuanlan.zhihu.com/p/147083132
webpack配置:https://blog.csdn.net/qq_41526942/article/details/102590921

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值