react + webpack 项目搭建

1,082 阅读4分钟

常用配置说明

  • entry

    • 可以接受字符串,数组,对象等多种格式
    • 项目打包入口文件,可以是绝对路径也可以是相对路径
    • webpack 打包默认入口文件是 ./src/index.js 约定,默认输出是 项目根目录下 /dist/main.js
  • output 打包后的内容输出相关配置,path打包后文件路径, 只能使用绝对路径

  • 常用loader相关理解

    • 默认情况下,webpack 值支持.js,.json文件,通过 loader,可以让他解析其他类型的文件(.css, .less, .ts, .vue ....),充当翻译官的角色。理论上只要有响应的 loader,就可以处理任何文件。一个 loader 只干一件事,webpack 的约定
    • 同一后缀文件使用多个 loader 时,loader 有执行顺序,从右向左依次执行
    • css-loader只是把 css 代码打包进 js 文件而已,并不做任何操作
    • style-loader 提取css-loader打包后的css代码,并自动生成标签,然后插入 html 中
    • less-loader 只是把 less 语法编译为 css 语法,然后 css-loader 把编译好的 css 内容插入 js 文件中,最后 style-loader 把 css 添加到标签并动态插入到 html 文件标签中
    • postcss-loader使用顺序,放在能够拿到 css 内容的地方就可以,拿到 css 内容,通过一些 js 插件处理 css 达到增强 css 的效果
  • loader 和 plugin 区别:loader 的主要职责是让 webpack 认识更多的文件类型,而 plugin 的职责是让其可以控制构建流程,从而执行一些特殊的任务。相当于 webpack 的功能补充。

hash,chunkhash,contenthash

如果不指定 hash 类型,webpack 默认是用 hash。

文件使用 hash 名的目的是为了利用浏览器的缓存,当我们做下次功能迭代的时候,只修改某一个文件的时候,hash 发生变化, 但是其他文件没有变化,hash 不变则会走缓存,变化的就会重新请求,这样可以减少请求

  1. hash 是随整个工程内容的变化而变化
  2. chunkhash 只影响一个 chunk 下的模块,一个文件所依赖的代码块会打包进一个 chunk
  3. contenthash 只根据自身的内容变化 而变化, 但是如果 contenthash 发生变化,他所属的 chunkhash 也会变化

项目目录结构

.  
|-- src                             项目源代码  
  |-- components                    项目通用组件
  |-- pages                         项目功能模块
    |-- index.js                    项目入口文件
|-- static                          项目静态资源,图片,字体文件  
|-- webpack                         webpack 打包相关的配置  
  |-- config.js                     通用配置变量抽离
  |-- webpack.config.common.js      webpack 公共配置
  |-- webpack.config.dev.js         本地开发环境打包配置
  |-- webpack.config.production.js  正式环境打包配置
  |-- config.js
|-- index.html                      打包模板
|-- .babelrc                        babel 相关配置文件
|-- postcss.config.js               postcss 相关配置文件
|-- .npmrc                          npm 安装源设置
|-- package.json         

完整的项目打包方案

webpack.config.common.js

const path = require('path');
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");

module.exports = {
  entry: {
    index: './src/pages/index.js',
  },
  resolve: {
    // 项目路径别名
    alias: {
      Utils: path.resolve(__dirname, "../src/utils"),
      Components: path.resolve(__dirname, "../src/components"),
    },
  },
  performance: {
    // false | "error" | "warning" // 不显示性能提示 | 以错误形式提示 | 以警告...
    hints: false,
    // 开发环境设置较大防止警告
    // 根据入口起点的最大体积,控制webpack何时生成性能提示,整数类型,以字节为单位
    maxEntrypointSize: 5000000,
    // 最大单个资源体积,默认250000 (bytes)
    maxAssetSize: 3000000,
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader',
        include: path.resolve(__dirname, "../src"),
        exclude: /node_modules/,
      },
    ]
  },
  plugins: [
    new CleanWebpackPlugin(), // 每次打包前先清除之前的打包内容
    new HtmlWebpackPlugin({
      template: "./index.html", // 打包 html 模板
      filename: "index.html", // 打包后生成的文件名
    }),
    new webpack.DefinePlugin({ // 自定义项目环境变量,此处用于二级目录部署
      "process.env": {
        SECONDARY_PATH: JSON.stringify(process.env.SECONDARY_PATH),
      },
    }),
  ]
}

webpack.config.dev.js

const path = require("path");
const config = require("./config");
const webpack = require("webpack");
const { merge } = require("webpack-merge");

const common = require("./webpack.config.common.js");

module.exports = merge(common, {
  mode: "development",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name]-[hash:6].js",
    publicPath: config.publicPath,
    chunkFilename: "[name].[chunkhash:4].chunk.js",
  },
  devServer: {
    port: 8089,
    host: "0.0.0.0",
    hot: true, // css 修改热更新,js 热更新需要配合 hotOnly和webpack.HotModuleReplacementPlugin()
    compress: true,
    // 当使用 HTML5 History API 时, 所有的 404 请求都会响应 index.html 的内容。 将 devServer.historyApiFallback 设为 true开启:
    historyApiFallback: true, 
    hotOnly: true, // 即便HMR不⽣效,浏览器也不⾃动刷新,就开启hotOnly
    /* proxy: {   // 每个 key 都是需要转发的前缀
      "/dataassets/api": {
        target: "后端接口服务地址",
      },
    }, */
    proxy: [
      // 多个前缀代理到同一个后端服务写法
      {
        context: ["/dataassets/api", "/sso"],
        target: "后端接口服务地址",
      },
    ]
  },
  devtool: "inline-source-map", // 开发环境配置
  module: {
    rules: [
      {
        test: /\.(less|css)$/,
        use: [
          "style-loader",
          "css-loader",
          {
            loader: "less-loader",
            options: {
              // less@3
              javascriptEnabled: true,
              // 覆盖antd样式的全局变量
              modifyVars: config.modifyVars,
              globalVars: {
                imgUri: `~"${config.publicPath}"`,
              },
            },
          },
        ],
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        use: [
          "file-loader?hash=sha512&digest=hex&name=[hash].[ext]",
        ],
      },
      {
        test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: "url-loader?limit=10000&mimetype=application/font-woff",
      },
      {
        test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: "file-loader",
      },
    ],
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
});

webpack.config.production.js

const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { merge } = require("webpack-merge");
const config = require("./config");
const common = require("./webpack.config.common.js");

module.exports = merge(common, {
  mode: "production",
  output: {
    path: path.resolve(__dirname, "../dist"),
    publicPath: config.publicPath, // 项目打包后的 css,js 都会添加统一访问路径前缀
    filename: "js/[name].[hash:6].js",
    chunkFilename: "js/[name].[hash:6].chunk.js",
  },
  module: {
    rules: [
      {
        test: /\.(less|css)$/,
        use: [
          {
            // MiniCssExtractPlugin 既有插件的配置也有 loader 的配置,具体配置项参考 github 文档
            // https://github.com/webpack-contrib/mini-css-extract-plugin
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: config.publicPath, // 配置打包后访问 css 文件的路径前缀。
            },
          },
          "css-loader",
          "postcss-loader",
          {
            loader: "less-loader",
            options: {
              // less@3
              javascriptEnabled: true,
              // 覆盖antd样式的全局变量
              modifyVars: config.modifyVars,
              globalVars: {
                imgUri: `~"${config.publicPath}"`, // 用于设置样式文件里引入的 image 图片地址前缀
              },
            },
          },
        ],
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: "[name]-[contenthash:6].[ext]",
              limit: 2 * 1024, // 小于2k的图片,直接使用Base64编码进行处理
              publicPath: config.publicPath
            }
          }
        ],
      },
      {
        test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              publicPath: config.publicPath
            }
          }
        ]
      },
      {
        test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              publicPath: config.publicPath
            }
          }
        ]
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "css/[contenthash:6]-[name].css",
    }),
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, "../static"),
        to: path.resolve(__dirname, "../dist/static"),
      },
    ]),
  ],
});