首页 > 解决方案 > 将 4.x 更新到 5.x 时的 Webpack 问题

问题描述

当我在我的项目中将 webpack 包更新到 V5.x 时,我遇到了问题。这是错误:webpack-hot-client:entry对象值必须是数组或函数。请检查您的 webpack 配置。

这是我的 package.json :

    "*.css",
    "*.scss",
    "*.sass"   ],   "main": "index.js",   "scripts": {
    "analyze": "bnr clean:build && bnr analyze",
    "build:all": "bnr clean:build && bnr build:client && bnr build:server",
    "build:client": "bnr clean:build && bnr build:client",
    "build:server": "bnr clean:build  && bnr build:server",
    "clean": "bnr clean:build",
    "dev": "bnr dev",
    "docker": "yarn docker:build && yarn docker:start && yarn docker:status",
    "docker:build": "docker-compose build --no-cache",
    "docker:start": "docker-compose up -d",
    "docker:status": "docker-compose logs -f -t",
    "flow": "bnr flow",
    "flow:stop": "bnr flow:stop",
    "lint": "npm-run-all lint:js lint:style lint:json",
    "lint:js": "bnr lint:js",
    "lint:style": "bnr lint:style",
    "lint:json": "bnr lint:json",
    "prod": "bnr build:client && bnr build:server && bnr start",
    "start": "bnr start",
    "sitemap": "node sitemap-generator.js"   },   "betterScripts": {
    "analyze": {
      "command": "npx webpack -p --progress  --hide-modules --config ./tools/webpack/production.client.babel.js",
      "env": {
        "NODE_ENV": "analyze"
      }
    },
    "build:client": {
      "command": "npx webpack --hide-modules --config ./tools/webpack/production.client.babel.js && npx gulp --gulpfile
tools/gulpfile.js",
      "env": {
        "NODE_ENV": "production"
      }
    },
    "build:server": {
      "command": "npx babel ./src -d ./dist --copy-files && npx webpack --hide-modules --config
./tools/webpack/production.server.babel.js",
      "env": {
        "NODE_ENV": "production"
      }
    },
    "clean:build": {
      "command": "rimraf ./public/assets && rimraf ./public/webpack-assets.json"
    },
    "dev": {
      "command": "nodemon ./index.js",
      "env": {
        "NODE_PATH": "./src",
        "NODE_ENV": "development",
        "PORT": 3000
      }
    },
    "flow": {
      "command": "npx flow"
    },
    "flow:stop": {
      "command": "npx flow stop"
    },
    "lint:js": {
      "command": "npx eslint --fix ./src ./tools ./index.js"
    },
    "lint:json": {
      "command": "npx prettier --write src/**/**/*.json"
    },
    "lint:style": {
      "command": "npx stylelint --fix ./src/**/*.css, ./src/**/*.scss"
    },
    "start": {
      "command": "node ./index.js",
      "env": {
        "NODE_PATH": "./dist",
        "NODE_HOST": "0.0.0.0",
        "NODE_ENV": "production",
        "PORT": 8080
      }
    }   },   "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }   },   "lint-staged": {
    "*.{js,jsx}": "eslint --fix",
    "*.{json,md}": "prettier --write",
    "*.{scss,sass}": "stylelint --syntax=scss"   },   "nodemonConfig": {
    "watch": [
      "src/server.js",
      "src/handler.js",
      "src/utils/renderHtml.js",
      "src/theme/variables.scss"
    ]   },   "browserslist": [
    "> 1%",
    "last 3 versions"   ],   "eslintIgnore": [
    "tools/flow",
    "public/assets"   ],   "dependencies": {
    "@babel/cli": "^7.14.5",
    "@babel/plugin-proposal-export-namespace-from": "^7.5.2",
    "@fortawesome/fontawesome-free": "^5.15.1",
    "@hapi/address": "^4.1.0",
    "@koa/router": "^10.1.1",
    "@loadable/component": "^5.10.2",
    "@loadable/server": "^5.10.2",
    "@tweenjs/tween.js": "^18.3.1",
    "ansi-regex": "^6.0.1",
    "axios": "^0.23.0",
    "bootstrap": "^5.1.3",
    "chalk": "^4.1.2",
    "classnames": "^2.2.6",
    "d3-ease": "^3.0.1",
    "del": "^6.0.0",
    "esm": "^3.2.25",
    "glob-parent": "^6.0.2",
    "gulp": "^4.0.2",
    "gulp-cache": "^1.1.3",
    "gulp-imagemin": "^8.0.0",
    "gulp-postcss": "^9.0.0",
    "gulp-rename": "^2.0.0",
    "i18next": "^21.3.2",
    "i18next-browser-languagedetector": "^6.1.2",
    "i18next-node-fs-backend": "^2.1.3",
    "i18next-resource-store-loader": "^0.1.2",
    "i18next-xhr-backend": "^3.1.2",
    "imagemin-mozjpeg": "^9.0.0",
    "imagemin-pngquant": "^9.0.2",
    "koa": "^2.8.1",
    "koa-bodyparser": "^4.2.1",
    "koa-compress": "^5.1.0",
    "koa-favicon": "^2.0.1",
    "koa-helmet": "^6.1.0",
    "koa-i18next-detector": "^0.7.2",
    "koa-i18next-middleware": "^1.1.12",
    "koa-i18next-middleware-fixed": "^1.1.10-b3",
    "koa-morgan": "^1.0.1",
    "koa-mount": "^4.0.0",
    "koa-router": "^10.1.1",
    "koa-static": "^5.0.0",
    "koa-webpack": "^6.0.0",
    "koa-webpack-server": "^0.2.4",
    "micro-dash": "^8.1.0",
    "moment": "^2.24.0",
    "p-min-delay": "^4.0.1",
    "pm2": "^5.1.2",
    "qs": "^6.8.0",
    "rc-animate": "^3.1.1",
    "rc-queue-anim": "^2.0.0",
    "rc-scroll-anim": "^2.6.1",
    "rc-tween-one": "^2.6.3",
    "react": "^17.0.2",
    "react-bootstrap": "^1.0.0-beta.8",
    "react-dom": "npm:@hot-loader/react-dom@^16.8.6",
    "react-global-configuration": "^1.4.1",
    "react-gtm-module": "^2.0.11",
    "react-helmet": "^6.1.0",
    "react-i18next": "^11.12.0",
    "react-icons": "^4.3.1",
    "react-motion": "^0.5.2",
    "react-player": "^2.9.0",
    "react-pose": "^4.0.8",
    "react-router": "^5.0.1",
    "react-router-config": "^5.0.1",
    "react-router-dom": "^5.0.1",
    "react-router-last-location": "^2.0.1",
    "react-router-sitemap": "^1.2.0",
    "react-spring": "^9.3.0",
    "react-tilt": "^0.1.4",
    "react-typist": "^2.0.5",
    "react-youtube": "^7.9.0",
    "sass-resources-loader": "^2.0.1",
    "serialize-javascript": "^6.0.0",
    "styled-components": "^5.3.1",
    "terser-webpack-plugin": "^5.2.4"   },   "devDependencies": {
    "@babel/core": "^7.6.0",
    "@babel/node": "^7.14.5",
    "@babel/plugin-proposal-class-properties": "^7.5.5",
    "@babel/plugin-proposal-export-default-from": "^7.5.2",
    "@babel/plugin-proposal-optional-chaining": "^7.6.0",
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
    "@babel/preset-env": "^7.14.5",
    "@babel/preset-flow": "^7.0.0",
    "@babel/preset-react": "^7.14.5",
    "@babel/register": "^7.6.0",
    "@babel/runtime": "^7.6.0",
    "@loadable/babel-plugin": "^5.10.0",
    "@loadable/webpack-plugin": "^5.7.1",
    "asset-require-hook": "^1.2.0",
    "babel-eslint": "^10.0.3",
    "babel-loader": "^8.0.6",
    "babel-plugin-dynamic-import-node": "^2.3.0",
    "babel-plugin-istanbul": "^6.1.1",
    "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
    "babel-plugin-transform-remove-console": "^6.9.4",
    "better-npm-run": "^0.1.1",
    "compression-webpack-plugin": "^9.0.0",
    "core-js": "3",
    "css-loader": "^6.4.0",
    "css-modules-require-hook": "^4.2.3",
    "cssnano": "^5.0.8",
    "eslint": "^8.0.1",
    "eslint-config-airbnb": "^18.0.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-import-resolver-webpack": "^0.13.1",
    "eslint-plugin-flowtype": "^6.1.1",
    "eslint-plugin-import": "^2.18.2",
    "eslint-plugin-jsx-a11y": "^6.2.3",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.14.3",
    "eslint-plugin-react-hooks": "^4.2.0",
    "file-loader": "^6.2.0",
    "flow-bin": "^0.162.0",
    "friendly-errors-webpack-plugin": "^1.7.0",
    "html-minifier": "^4.0.0",
    "husky": "^7.0.2",
    "imagemin-webpack-plugin": "^2.4.2",
    "lint-staged": "^11.2.3",
    "mini-css-extract-plugin": "^2.4.2",
    "nodemon": "^2.0.13",
    "normalize.css": "^8.0.1",
    "npm-run-all": "^4.1.5",
    "optimize-css-assets-webpack-plugin": "^6.0.1",
    "postcss": "^8.3.9",
    "postcss-loader": "^6.2.0",
    "postcss-preset-env": "^6.7.0",
    "prettier": "^2.4.1",
    "react-dev-utils": "^11.0.4",
    "react-hot-loader": "^4.12.13",
    "react-router-sitemap-generator": "^0.0.8",
    "react-test-renderer": "^17.0.2",
    "rimraf": "^3.0.0",
    "sass": "^1.43.2",
    "sass-loader": "^12.2.0",
    "static-site-generator-webpack-plugin": "^3.4.2",
    "stylelint": "^13.13.1",
    "stylelint-config-prettier": "^9.0.3",
    "stylelint-config-recommended-scss": "^4.3.0",
    "stylelint-config-standard": "^22.0.0",
    "stylelint-scss": "^3.10.1",
    "url-loader": "^4.1.1",
    "webpack": "^5.58.2",
    "webpack-bundle-analyzer": "^4.5.0",
    "webpack-cli": "4.9.1",
    "webpack-dev-middleware": "^5.2.1",
    "webpack-hot-middleware": "^2.25.0",
    "webpack-manifest-plugin": "^4.0.2",
    "webpack-merge": "^5.8.0",
    "webpack-node-externals": "^3.0.0"   },   "engines": {
    "node": ">=8",
    "npm": ">=5"   } }```

这是我的 webpack 的 base.config.js :

import webpack from 'webpack'
import { WebpackManifestPlugin } from 'webpack-manifest-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
import LoadablePlugin from '@loadable/webpack-plugin'
import config from './config'

const nodeEnv = process.env.NODE_ENV || 'development'
const isDev = nodeEnv === 'development'

const getPlugins = () => {
  const plugins = [
    new WebpackManifestPlugin({
      fileName: path.resolve(process.cwd(), 'public/webpack-assets.json'),
      filter: file => file.isInitial
    }),
    new MiniCssExtractPlugin({
      filename: `${config.cssFileName}.css`,
      chunkFilename: `${config.cssChunkFileName}.css`,
      ignoreOrder: true
    }),
    // Setup environment variables for client
    new webpack.EnvironmentPlugin({ NODE_ENV: JSON.stringify(nodeEnv) }),
    // Setup global variables for client
    new webpack.DefinePlugin({
      __CLIENT__: true,
      __SERVER__: false,
      __DEV__: isDev
    }),
    new LoadablePlugin({ filename: '../loadable-stats.json', writeToDisk: true })
  ]

  if (isDev) {
    plugins.push(new FriendlyErrorsWebpackPlugin())
  }
  return plugins
}

// Webpack configuration
module.exports = {
  mode: isDev ? 'development' : 'production',
  devtool: isDev ? 'eval-source-map' : 'hidden-source-map',
  context: path.resolve(process.cwd()),
  entry: ['webpack-hot-middleware/client','./src/index.js'],
  optimization: {
    splitChunks: {
      chunks: 'async'
    }
  },
  output: {
    path: path.resolve(process.cwd(), 'public/assets'),
    publicPath: '/assets/',
    filename: `${config.fileName}.js`,
    chunkFilename: `${config.chunkFileName}.js`,
    pathinfo: isDev
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel',
        options: { cacheDirectory: isDev }
      },
      {
        test: /\.css$/,
        include: /node_modules/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css',
            options: {
              importLoaders: 1,
              modules: false,
              sourceMap: true
            }
          },
          { loader: 'postcss', options: { sourceMap: true } }
        ]
      },
      {
        test: /\.(scss|sass)$/,
        exclude: path.resolve(__dirname, '..', '..', 'src/theme/'),
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: isDev,
              reloadAll: true
            }
          },
          {
            loader: 'css',
            options: {
              importLoaders: 2,
              modules: {
                localIdentName: config.cssModulesIdentifier,
                context: path.resolve(process.cwd(), 'src')
              },
              sourceMap: true
            }
          },
          { loader: 'postcss', options: { sourceMap: true } },
          {
            loader: 'sass',
            options: {
              sassOptions: {
                outputStyle: 'expanded'
              },
              sourceMap: true
            }
          },
          {
            loader: 'sass-resources',
            options: {
              resources: path.resolve(process.cwd(), 'src/theme/_include.scss')
            }
          }
        ]
      },
      {
        test: /\.(scss|sass)$/,
        include: path.resolve(__dirname, '..', '..', 'src/theme/'),
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: isDev,
              reloadAll: true
            }
          },
          {
            loader: 'css',
            options: {
              importLoaders: 2,
              modules: false,
              sourceMap: true
            }
          },
          { loader: 'postcss', options: { sourceMap: true } },
          {
            loader: 'sass',
            options: {
              sassOptions: {
                outputStyle: 'expanded'
              },
              sourceMap: true
            }
          }
        ]
      },
      {
        test: path.resolve(process.cwd(), 'src/locales'),
        loader: 'i18next-resource-store-loader'
      },
      {
        test: /\.(woff2?|ttf|eot|svg|otf)$/,
        loader: 'url',
        options: { limit: 10240, name: config.staticFilesName }
      },
      {
        test: /\.(gif|png|jpe?g|webp)$/,
        // Any image below or equal to 10Kb will be converted to base64
        loader: 'url',
        options: { limit: 10240, name: config.staticFilesName }
      },
      {
        test: /\.(mp3|mp4|ogv)$/,
        loader: 'file',
        options: { name: config.staticFilesName }
      }
    ]
  },
  plugins: getPlugins(),
  resolveLoader: {
    mainFields: ['loader']
  },
  resolve: {
    modules: ['src', 'node_modules'],
    descriptionFiles: ['package.json'],
    extensions: ['.js', '.jsx', '.json'],
    fallback:{
      fs: false,
      vm: false,
      net: false,
      tls: false
    }
  },
  cache: isDev,
  stats: { children: false }

我尝试了很多东西并修复了很多问题,但对我来说,条目是一个数组,所以我不知道为什么会出现这个错误。

这是我的 server.js(他启动所有程序):

import logger from 'koa-morgan'
import Koa from 'koa'
import bodyParser from 'koa-bodyparser'
import compression from 'koa-compress'
import helmet from 'koa-helmet'
import querystring from 'qs'
import Router from '@koa/router'
import favicon from 'koa-favicon'
import axios from 'axios'
import chalk from 'chalk'
import mount from 'koa-mount'
import serve from 'koa-static'
import Backend from 'i18next-node-fs-backend'
import i18n from 'i18next'
import i18nextMiddleware from 'koa-i18next-middleware-fixed'
import LanguageDetector from 'koa-i18next-detector'
import c from './config'

process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0
const app = new Koa()
const router = new Router()
const sendMessage = body =>
  new Promise((resolve, reject) => {
    const config = {
      maxRedirects: 0,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    }

    axios
      .post(
        'https://go.pardot.com/l/138941/2019-07-22/2h6dgb',
        querystring.stringify({ ...body, privacy: true }),
        config
      )
      .then(result => {
        resolve(result.status === 200)
      })
      .catch(e => {
        reject(new Error(e))
      })
  })

const lngDetector = new LanguageDetector()

i18n
  .use(Backend)
  .use(lngDetector)
  .init(
    {
      debug: false,
      fallbackLng: 'fr',
      saveMissing: false,
      react: {
        useSuspense: false
      },
      detection: {
        order: ['path', 'navigator']
      },
      interpolation: {
        escapeValue: false,
        formatSeparator: ',',
        format: (value, format) => {
          if (format === 'uppercase') return value.toUpperCase()
          return value
        }
      },
      preload: ['fr'],
      load: 'languageOnly',
      ns: [
        'global',
        'landing_references',
        'landing_expertises',
        'home',
        'references',
        'expertises',
        'contact',
        'about',
        'partners',
        'team'
      ],
      defaultNS: 'global',
      backend: {
        loadPath: `${path.resolve(process.cwd(), 'src')}/locales/{{lng}}/{{ns}}.json`
      }
    },
    async () => {
      // Use helmet to secure Express with various HTTP headers
      app.use(helmet())
      app.use(bodyParser())
      // Compress all requests
      app.use(compression())
      // Use for http request debug (show errors only)
      app.use(logger('dev', { skip: ctx => ctx.status < 400 }))
      app.use(favicon(path.resolve(process.cwd(), 'public/favicon.ico')))
      app.use(i18nextMiddleware.getHandler(i18n, { locals: 'lng' }))

      // Docker serve static trough nginx for better performance
      if (!__DOCKER__) {
        console.log(chalk.magenta('==>   Serve statics with koa'))
        app.use(mount('/locales', serve(path.resolve(process.cwd(), 'src/locales'))))
        app.use(serve(path.resolve(process.cwd(), 'public')))
      }
      if (__DEV__) {
        app.use(mount('/images', serve(path.resolve(process.cwd(), 'src/images'))))
        /* Run express as webpack dev server */
        const webpack = require('webpack')
        const webpackConfig = require('../tools/webpack/base.config')
        const compiler = webpack(webpackConfig)
          const koaWebpack = require('koa-webpack')
        new webpack.ProgressPlugin().apply(compiler)

        const options = {
            compiler,
          devMiddleware: {
            publicPath: webpackConfig.output.publicPath,
            headers: { 'Access-Control-Allow-Origin': '*' },
            hot: true,
            writeToDisk: false,
            quiet: true, // Turn it on for friendly-errors-webpack-plugin
            noInfo: true,
            stats: 'minimal',
            serverSideRender: true
          }
        }

        const middleware = await koaWebpack(options)
        app.use(middleware)
      }

      router
        .post('/contact', async ctx => {
          try {
            await sendMessage(ctx.request.body)
            ctx.body = {
              message: 'Votre message a bien été envoyé'
            }
            ctx.status = 200
          } catch (e) {
            ctx.body = {
              message: 'Une erreur est survenue'
            }
            ctx.status = 500
          }
        })
        .get('/', async ctx => {
          ctx.status = 302
          return ctx.redirect(`/${ctx.request.language}`)
        })

        .get('*', async (ctx, next) => {
          return require('./render')(ctx, next)
        })

      app.use(router.routes()).use(router.allowedMethods())
      if (c.port) {
        app.listen(c.port, c.host, err => {
          const url = `http://${c.host}:${c.port}`

          if (err) console.error(chalk.red(`\n==> Error happen ${err}`))
          console.info(chalk.green(`\n==>   Listening at ${url}`))
        })
      } else {
        console.error(chalk.red('\n==> Error : No PORT environment variable has been specified'))
      }
    }
  )

更新:我在编译器文件中放了一些 console.log :

      for (const key of Object.keys(entry)) {
        const value = entry[key];
        console.log(entry)
        console.log(value)
        console.log(Array.isArray(Object.values(value)))
        if (!Array.isArray(value)) {
          throw new TypeError(
            'webpack-hot-client: `entry` Object values must be an Array or Function. Please check your webpack config.'
          );

Console.log 返回:

  main: { import: [ 'webpack-hot-middleware/client', './src/index.js' ] }
}
{ import: [ 'webpack-hot-middleware/client', './src/index.js' ] }
true

所以我认为变量的声明有问题,因为我们必须做一个 Object.values() 来获取数组。所以我可以做些什么来解决这个问题,我不会更改依赖文件......谢谢你的时间和你的回应!

问候

标签: node.jswebpackwebpack-dev-serverwebpack-hot-middlewarewebpack-hot-client

解决方案


对于您的情况,这可能不是正确的示例,但他们对 webpack 结构进行了一些内部更改:

entry: {} allows an empty object now (to allow to use plugins to add entries)

这是我发现的较新示例之一:

module.exports = {
  entry: {
    about: { import: './about.js', filename: 'pages/[name][ext]' },
  },
};

来源:Webpack v5 内部变更:Entry


推荐阅读