夜听城嚣 夜听城嚣
首页
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 前端基建与架构
  • 专题分享

    • Git入门与开发
    • 前端面试题汇总
    • HTML和CSS知识点
  • 项目实践
  • 抓包工具
  • 知识管理
  • 工程部署
  • 团队规范
bug知多少
  • 少年歌行
  • 青年随笔
  • 文海泛舟
  • 此事躬行

    • 项目各工种是如何协作的
    • TBA课程学习
收藏

dwfrost

前端界的小学生
首页
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 前端基建与架构
  • 专题分享

    • Git入门与开发
    • 前端面试题汇总
    • HTML和CSS知识点
  • 项目实践
  • 抓包工具
  • 知识管理
  • 工程部署
  • 团队规范
bug知多少
  • 少年歌行
  • 青年随笔
  • 文海泛舟
  • 此事躬行

    • 项目各工种是如何协作的
    • TBA课程学习
收藏
  • 抓包工具

  • 知识管理

  • 编辑器工具

  • 工程部署

  • 团队规范

    • VSCode使用Eslint
    • husky+lint-staged+commitlint规范代码提交
    • uniapp小程序模板搭建
    • webpack3老项目升级为vite实践
    • webpack3项目升级webpack4实践
    • webpack4项目升级webpack5实践
      • 提交代码时修改commit消息
    • 效率工具
    • 团队规范
    frost
    2021-12-06

    webpack4项目升级webpack5实践

    本文是继上篇webpack3项目升级webpack4实践的第二篇,本着循序渐进的原则,对老项目一步步升级。

    老规矩,先复制一份原始依赖。如下:

        "dependencies": {
            "@xkeshi/vue-qrcode": "^1.0.0",
            "axios": "^0.19.0",
            "blueimp-md5": "^2.12.0",
            "china-area-data": "^5.0.1",
            "easemob-websdk": "^3.6.3",
            "echarts": "^4.6.0",
            "el-tree-transfer": "^2.3.0",
            "element-china-area-data": "^5.0.0",
            "element-ui": "^2.12.0",
            "file-saver": "^2.0.2",
            "html2canvas": "^1.0.0-rc.5",
            "js-cookie": "^2.2.1",
            "jsbarcode": "^3.11.0",
            "jwt-js": "^0.5.0",
            "normalize.css": "^8.0.1",
            "nprogress": "^0.2.0",
            "throttle-debounce": "^2.1.0",
            "v-charts": "^1.19.0",
            "v-distpicker": "^1.2.2",
            "vant": "^2.12.19",
            "vue": "^2.5.2",
            "vue-amap": "^0.5.10",
            "vue-clipboard2": "^0.3.1",
            "vue-drag-resize": "^1.3.2",
            "vue-quill-editor": "^3.0.6",
            "vue-router": "^3.0.1",
            "vuedraggable": "^2.24.0",
            "vuex": "^3.1.1",
            "xlsx": "^0.15.6"
        },
        "devDependencies": {
            "@babel/core": "^7.7.5",
            "@babel/plugin-transform-runtime": "^7.7.6",
            "@babel/polyfill": "^7.7.0",
            "@babel/preset-env": "^7.7.6",
            "@babel/runtime": "^7.7.6",
            "autoprefixer": "^7.1.2",
            "babel-eslint": "^10.0.3",
            "babel-helper-vue-jsx-merge-props": "^2.0.3",
            "babel-loader": "^8.0.6",
            "babel-plugin-syntax-jsx": "^6.18.0",
            "babel-plugin-transform-vue-jsx": "^3.7.0",
            "body-parser": "^1.19.0",
            "chalk": "^2.0.1",
            "chokidar": "^3.2.2",
            "concurrently": "^5.0.0",
            "copy-webpack-plugin": "^6.4.1",
            "core-js": "^2.6.11",
            "css-loader": "^5.2.7",
            "el-tree-transfer": "^2.3.0",
            "eslint": "^4.15.0",
            "eslint-config-standard": "^10.2.1",
            "eslint-friendly-formatter": "^3.0.0",
            "eslint-loader": "^1.7.1",
            "eslint-plugin-import": "^2.7.0",
            "eslint-plugin-node": "^5.2.0",
            "eslint-plugin-promise": "^3.4.0",
            "eslint-plugin-standard": "^3.0.1",
            "eslint-plugin-vue": "^4.0.0",
            "execa": "^5.0.0",
            "express": "^4.17.1",
            "file-loader": "^1.1.4",
            "friendly-errors-webpack-plugin": "^1.6.1",
            "html-webpack-plugin": "^4.4.0",
            "http-proxy-middleware": "^0.20.0",
            "http-server": "^0.11.1",
            "inquirer": "^7.3.3",
            "mini-css-extract-plugin": "^1.6.2",
            "node-notifier": "^5.1.2",
            "node-sass": "^4.12.0",
            "optimize-css-assets-webpack-plugin": "^3.2.0",
            "ora": "^1.2.0",
            "os": "^0.1.1",
            "portfinder": "^1.0.13",
            "postcss-import": "^11.0.0",
            "postcss-loader": "^2.0.8",
            "postcss-url": "^7.2.1",
            "rimraf": "^2.6.0",
            "sass-loader": "^7.3.1",
            "semver": "^5.3.0",
            "shelljs": "^0.7.6",
            "socket.io": "^3.0.4",
            "terser-webpack-plugin": "^4.2.3",
            "thread-loader": "^2.1.3",
            "uglifyjs-webpack-plugin": "^1.1.1",
            "url-loader": "^4.1.1",
            "vue-loader": "^15.9.8",
            "vue-style-loader": "^4.1.3",
            "vue-template-compiler": "^2.6.14",
            "webpack": "^4.46.0",
            "webpack-bundle-analyzer": "^2.9.0",
            "webpack-cli": "^3.3.12",
            "webpack-dev-server": "^3.11.3",
            "webpack-merge": "^4.1.0"
        },
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96

    本次升级主要升级webpack,并附带升级相关webpack插件。

    # 过程

    先调试开发。

            "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --env.pageName",
    
    1

    # 升级webpack5

    npm i webpack@5 webpack-cli@4 -D
    
    1

    运行报错,如下

    internal/modules/cjs/loader.js:892
      throw err;
      ^
    
    Error: Cannot find module 'webpack-cli/bin/config-yargs'
    Require stack:
    - F:\h5\node_modules\webpack-dev-server\bin\webpack-dev-server.js
    
    1
    2
    3
    4
    5
    6
    7

    是webpack-dev-server版本偏低

    升级webpack-dev-server

     npm i webpack-dev-server@4 -D
    
    1

    报错,如下

    [webpack-cli] Error: Unknown option '--inline'
    [webpack-cli] Run 'webpack --help' to see available commands and options
    npm ERR! code ELIFECYCLE
    npm ERR! errno 2
    npm ERR! s2b2c@1.0.0 dev: `webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --env.pageName "goodsCenter"`
    npm ERR! Exit status 2
    
    1
    2
    3
    4
    5
    6

    是因为新版本的webpack-dev-server不支持--inline参数,去除。

    运行报错,如下

    [webpack-cli] Error: Unknown option '--env.pageName'
    [webpack-cli] Run 'webpack --help' to see available commands and options
    npm ERR! code ELIFECYCLE
    npm ERR! errno 2
    npm ERR! s2b2c@1.0.0 dev: `webpack-dev-server --progress --config build/webpack.dev.conf.js --env.pageName "goodsCenter"`
    npm ERR! Exit status 2
    
    1
    2
    3
    4
    5
    6

    参考github文档 (opens new window),发现webpack5不支持用户传入自定义参数了,不过依旧支持给env对象增加属性(见webpack文档 (opens new window))。这句话可能有点难理解,以当前项目为例:

    1. 项目开发命令为npm run dev goodsCenter,只编译goodsCenter页面。

    2. 原webpack config文件可以通过env.pageName来获取goodsCenter的值

    3. webpack5对于--env的写法有变,它支持的写法如下

      webpack serve --env pageName=goodsCenter
      
      1

      但这里有个问题,就是goodsCenter实际是开发者作为命令行参数输入的,而webpack这里必须固定设置。

    解决方案是使用node命令行参数,作为中转。

    1. 更改dev命令

              "dev": "node build/dev.js",
      
      1
    2. 中转dev.js

      // build/dev.js
      const chalk = require('chalk')
      const cp = require('child_process')
      
      async function run() {
      
          const cliParams = process.argv.slice(2)
      
          if (cliParams.length > 2) {
              console.log(chalk.yellow('仅支持传入一个页面参数,多余页面会被忽略'))
          }
      
          const page = cliParams[0] || ''
      
          const pageParam = page ? `--env pageName=${page}` : ''
          const command = `webpack serve --config build/webpack.dev.conf.js --color ${pageParam}`
      
          const child = cp.exec(command)
          child.stdout.pipe(process.stdout) // 输出子进程信息
          child.stderr.pipe(process.stderr)
      }
      
      run()
      
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24

    done,原有处理pageName的逻辑不需要改变。

    # 处理报错

    继续运行,报错。

    [webpack-cli] Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
     - configuration.devtool should match pattern "^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$".
       BREAKING CHANGE since webpack 5: The devtool option is more strict.
       Please strictly follow the order of the keywords in the pattern.
    
    1
    2
    3
    4

    之前的devtool如下

    devtool: 'cheap-module-eval-source-map',
    
    1

    改写为

    devtool: 'eval-cheap-module-source-map',
    
    1

    继续运行,报错。

    [webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
     - options has an unknown property 'watchOptions'. 
    
    1
    2

    提示watchOptions已废弃,去除。

    			// watchOptions: {
                //     poll: config.dev.poll
                // }
    
    1
    2
    3

    继续运行,报错。

    [webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
     - options has an unknown property 'quiet'.
    
    1
    2

    提示quiet属性已废弃,去除。

    // quiet: true // necessary for FriendlyErrorsPlugin
    
    1

    继续运行,报错。

    [webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
     - options has an unknown property 'publicPath'.
    
    1
    2

    提示publicPath已废弃,改写之,文档-publicPath (opens new window)。

    // 之前
            devServer: {
    			publicPath: config.dev.assetsPublicPath
            }
    
    // 之后
            devServer: {
                static: {
                    publicPath: config.dev.assetsPublicPath
                },			
            }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    继续运行,报错。

    [webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
     - options has an unknown property 'overlay'.
    
    1
    2

    提示overlay属性不存在,根据文档-overlay (opens new window)修改。

    // 之前
            devServer: {
    				overlay: {
                        warnings: false,
                        errors: true
                    }
            }
    
    // 之后
            devServer: {
                client: {
                    overlay: {
                        warnings: false,
                        errors: true
                    }
                },            
            }            
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    继续运行,报错。

    Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
     - options has an unknown property 'contentBase'.
    
    1
    2

    webpack5修改了字段。

    // 之前
            devServer: {
                contentBase: path.resolve(__dirname, '../client'),
            }
                
    // 之后
            devServer: {
                directory: path.resolve(__dirname, '../client'),
            } 
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    继续运行,报错。

    Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
     - options has an unknown property 'clientLogLevel'.
    
    1
    2

    webpack5已删除该属性,去除。

    继续运行,报错。

    [webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
     - options has an unknown property 'disableHostCheck'.
    
    1
    2

    webpack5修改了字段。

    // 之前
            devServer: {
                disableHostCheck: true,
            }
                
    // 之后
            devServer: {
                allowedHosts: 'all',
            }  
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    好的,没报错了,项目正常启动。

    # 打包

    开发过程正常,接下来是构建部署包过程。

    运行npm run build,报错

    h5\build\webpack.prod.conf.js:136
                new webpack.HashedModuleIdsPlugin(),
                ^
    
    TypeError: webpack.HashedModuleIdsPlugin is not a constructor
    
    1
    2
    3
    4
    5

    参考文档:hashed-module-ids-plugin (opens new window),改为

    new webpack.ids.HashedModuleIdsPlugin(),
    
    1

    插一句,官网不推荐使用这种方式,移除最好,见文档 (opens new window)。

    继续运行,又报错

    h5\build\build-all.js:35
            if (err) throw err
                     ^
    
    TypeError: compiler.plugin is not a function
        at LastCallWebpackPlugin.apply (F:\h5\node_modules\last-call-webpack-plugin\index.js:190:12)
        at OptimizeCssAssetsPlugin.apply (F:\h5\node_modules\optimize-css-assets-webpack-plugin\index.js:73:32)
        at createCompiler (F:\h5\node_modules\webpack\lib\webpack.js:73:12)
        at create (F:\h5\node_modules\webpack\lib\webpack.js:134:16)
        at webpack (F:\h5\node_modules\webpack\lib\webpack.js:142:47)
        at f (F:\h5\node_modules\webpack\lib\index.js:55:16)
        at buildPage (F:\h5\build\build-all.js:33:5)
        at F:\h5\build\build-all.js:22:9
        at Array.forEach (<anonymous>)
        at F:\h5\build\build-all.js:21:14
        at next (F:\h5\node_modules\rimraf\rimraf.js:83:7)
        at CB (F:\h5\node_modules\rimraf\rimraf.js:119:9)
        at F:\h5\node_modules\rimraf\rimraf.js:145:14
        at FSReqCallback.oncomplete (fs.js:192:21)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    因为optimize-css-assets-webpack-plugin插件已过时(参考文档 (opens new window)),webpack5使用css-minimizer-webpack-plugin替代。

    安装插件npm i ss -D,运行,报错。

    xx/css/776.0442ce6947f070cf7f1f.css from Css Minimizer
    Error: [object Object] is not a PostCSS plugin
        at Processor.normalize (F:\h5\node_modules\postcss\lib\processor.js:145:15)
        at new Processor (F:\h5\node_modules\postcss\lib\processor.js:51:25)
        at postcss (F:\h5\node_modules\postcss\lib\postcss.js:73:10)
        at cssnanoPlugin (F:\h5\node_modules\cssnano\src\index.js:156:10)
        at cssnanoMinify (eval at transform (F:\h5\node_modules\css-minimizer-webpack-plugin\dist\minify.js:34:28), <anonymous>:51:33)
        at minify (F:\h5\node_modules\css-minimizer-webpack-plugin\dist\minify.js:15:32)
        at Object.transform (F:\h5\node_modules\css-minimizer-webpack-plugin\dist\minify.js:35:24)
        at execFunction (F:\h5\node_modules\jest-worker\build\workers\threadChild.js:158:17)
        at execHelper (F:\h5\node_modules\jest-worker\build\workers\threadChild.js:137:5)
        at execMethod (F:\h5\node_modules\jest-worker\build\workers\threadChild.js:141:5)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    见issue (opens new window),需要重新安装postcss。

    npm i postcss -D
    
    1

    # 升级依赖

    • 升级html-webpack-plugin

      npm i html-webpack-plugin@5
      
      1
    • 升级mini-css-extract-plugin

      npm i mini-css-extract-plugin@2
      
      1
    • 升级thread-loader

      npm i thread-loader@3
      
      1
    • 升级copy-webpack-plugin

      npm i copy-webpack-plugin@10
      
      1
    • 升级css-loader

      npm i css-loader@6
      
      1
    • 升级file-loader

      npm i file-loader@6
      
      1

    # 其他问题

    # 端口占用

    F:\h5\node_modules\webpack-dev-server\lib\Server.js:1751
          throw error;
          ^
    
    Error: listen EADDRINUSE: address already in use 0.0.0.0:8088
        at Server.setupListenHandle [as _listen2] (net.js:1320:16)
        at listenInCluster (net.js:1368:12)
        at doListen (net.js:1505:7)
        at processTicksAndRejections (internal/process/task_queues.js:83:21) {
      errno: -4091,
      syscall: 'listen',
      address: '0.0.0.0',
      port: 8088
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    查找开启服务的端口

    netstat -ano|findstr 8088
    
    1

    发现的确存在

      TCP    0.0.0.0:8088           0.0.0.0:0              LISTENING       2968
      TCP    127.0.0.1:8088         127.0.0.1:52191        ESTABLISHED     2968
      TCP    127.0.0.1:52191        127.0.0.1:8088         ESTABLISHED     13156
      TCP    127.0.0.1:52222        127.0.0.1:8088         TIME_WAIT       0
    
    1
    2
    3
    4

    干掉它,即可。

    tskill 2968
    
    1

    # 字体文件损坏(css-loader@6)

    部署后,发现element-ui的字体图标失效,控制台报警告错误:

    Failed to decode downloaded font: https://example.com/d3cc9e4763d2a7c851ef.woff
    
    1

    查找对应的打包文件,发现这个woff文件里面是js代码。

    export default __webpack_public_path__ + "element-ui/fonts/element-icons.313f7da.woff";
    
    1

    明显是打包错了。

    查看提交记录,发现是升级了css-loader(v5.2.7->v6.6.0)导致,于是去github查询issue。

    相关issue有1338 (opens new window),1360 (opens new window),作者对于webpack5 做了 breaking change,建议开发者使用资源模块 (opens new window)来加载资源,并废弃raw-loader,url-loader,file-loader。前后对比如下:

    之前

    {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
            limit: 10000,
            name: path.posix.join('element-ui/fonts/[name].[hash:7].[ext]')
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    之后

    {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        type: 'asset',
        generator: {
            filename: path.posix.join('element-ui/fonts/[name].[hash:7].[ext]')
        }
    }
    
    1
    2
    3
    4
    5
    6
    7

    注:options选项是作为参数传入loader的,采用asset-modules后,就不需要了,webpack默认小于8kb就会使用Base64编码字符串注入,可以修改maxSize。

    # 整理webpack控制台信息

    正常情况下,webpack构建时会输出一大堆信息,如下:

    log

    多余的信息会让开发者变得焦躁,清理方式很简单:

    module.export = {
        infrastructureLogging: {
            // 禁用日志
            // https://webpack.docschina.org/configuration/other-options/#level
            level: 'none'
        },
        stats: 'errors-only' // 编译错误时输出
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    不过,顺利的话是没有任何输出的,开发者还以为“卡死”了,优化如下:

    module.export = {
            plugins: [
                compiler => {
                    console.log('Start compiling...')
                    compiler.hooks.done.tap({ name: 'myPlugin' }, stats => {
                        const hasErrors = stats.hasErrors()
                        if (!hasErrors) {
                            console.log()
                            console.log(`  App running at:`)
                            console.log(`  - Local:   ${chalk.cyan(`http://localhost:8088`)}`)
                        }
                    })
                }
            ],
    }            
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    log2

    另外,还可以使用成熟的解决方案,即friendly-errors-webpack-plugin (opens new window)。

    const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
    
    module.exports = env => {
        const devWebpackConfig = {
        }
        return new Promise((resolve, reject) => {
            console.log(`${chalk.bgBlue.black(' I ')} Start compiling...`)
            portfinder.basePort = config.dev.port || '8088'
            portfinder.getPort((err, port) => {
                if (err) {
                    reject(err)
                } else {
                    devWebpackConfig.devServer.port = port
    
                    // Add FriendlyErrorsPlugin
                    devWebpackConfig.plugins.push(
                        new FriendlyErrorsPlugin({
                            compilationSuccessInfo: {
                                messages: [`${new Date().toLocaleTimeString()} update`, `App running at: http://localhost:${port}`]
                            },
                            onErrors: config.dev.notifyOnErrors ? utils.createNotifierCallback() : undefined // 参考官方文档介绍
                        })
                    )
                    resolve(devWebpackConfig)
                }
            })
        })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    log3

    # 总结

    # 参考

    webpack5官方迁移文档 (opens new window)

    上次更新: 2022/03/30, 14:10:11
    webpack3项目升级webpack4实践
    提交代码时修改commit消息

    ← webpack3项目升级webpack4实践 提交代码时修改commit消息→

    最近更新
    01
    提交代码时修改commit消息
    04-09
    02
    如何快速定位bug
    02-20
    03
    云端web项目开发踩坑
    08-25
    更多文章>
    Theme by Vdoing | Copyright © 2021-2025 dwfrost | 粤ICP备2021118995号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式
    ×