zoukankan      html  css  js  c++  java
  • webpack03----HMR、source-map、oneOf、缓存、tree shaking、code split、lazy loading、pwa、多进程打包、externals、dll

    HMR:

    /*
      HMR:hot module replacement 热模块替换,模块热替换
      作用:一个模块发生变化,只会重新打包这一个模块,而不是重新打包所有模块,极大地提升了构建速度
    
      样式文件:可以使用HMR功能,style-loader内部实现了
      js文件:默认不能使用HMR功能,需要修改js代码,添加支持HMR功能的代码  注意:HMR功能对js的处理,只能处理非入口文件的其他文件
      html文件:默认不能使用HMR功能,同时会导致问题:html文件不能热更新了(只有一个html文件,不用做HMR功能)  解决:修改entry入口,将html文件引入
    
    */
    const { resolve } = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      // entry: './src/js/index.js',
      entry: ['./src/js/index.js', './src/index.html'],
      output: {
        filename: 'js/built.js',
        path: resolve(__dirname, 'build')
      },
      module: {
        rules: [
          {
            test: /.less$/,
            use: ['style-loader', 'css-loader', 'less-loader'] // 开发环境使用style-loader,内置了HMR可以使打包速度更快,生产环境提取成单个文件
          },
          {
            test: /.css$/,
            use: ['style-loader', 'css-loader']
          },
          {
            test: /.(jpg|png|jif)$/,
            loader: 'url-loader',
            options: {
              limit: 6 * 1024,
              name: '[hash:10].[ext]',
              exModule: false,
              outputPath: 'images'
            }
          },
          {
            test: /.html$/,
            loader: 'html-loader'
          },
          {
            exclude: /.(css|js|html|jpg|png|jif|less)$/,
            loader: 'file-loader',
            options: {
              name: '[hash:10].[ext]',
              outputPath: 'media'
            }
          }
        ]
      },
      plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
      mode: 'development',
      devServer: {
        contentBase: resolve(__dirname, 'build'),
        compress: true,
        open: true,
        port: 3000,
        hot: true // 开启HMR功能
      }
    }

    注意:

      1、devServer中设置 hot:true 即开启了HMR功能,修改webpack.config.js后要重启webpack服务:npx webpack-dev-server

      2、css文件使用style-loader,它的内部是默认开启了HMR功能的,开发环境使用style-loader,生产环境将css提取为单独文件

      3、html文件只有一个,它不用做HMR,但是在开启HMR后没有热更新了,解决:在entry中将html文件引入

      4、js文件需要添加支持HMR功能的代码:

        // accept() 第一个参数指示哪个文件需要HMR,第二个参数是一个回调函数
        if (module.hot) {
          module.hot.accept('./print.js', function () {
            print()
          })
    
          module.hot.accept('./test.js', function () {})
        }

      5、js文件的HMR功能针对除了入口文件以外的其他js文件,入口文件一旦修改,页面会整体刷新

    source-map:

      devtool: 'eval-source-map'
      source-map:一种提供源代码到构建后代码映射的技术,如果构建后代码出错了,用过映射可以追踪源代码错误
    
      [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
    
      source-map:外部            错误代码准确信息 源代码的错误位置
      inline-source-map:内联     只生成一个内联source-map 错误代码准确信息 源代码的错误位置  √只能开发用
      hidden-source-map:外部     错误代码错误原因,但是没有错误位置  不能追踪源代码错误,只能提示到构建后代码的错误位置  *隐藏源码
      eval-source-map:内联       每一个文件都生成对应的source-map,都在eval()函数中  错误代码准确信息 源代码的错误位置  √只能开发用
      nosources-source-map:外部   错误代码准确信息,但是没有任何源代码信息  *隐藏全部源码
      cheap-source-map:外部      错误代码准确信息  源代码的错误位置  只能精确到行
      cheap-module-source-map:外部  错误代码准确信息 源代码的错误位置  module会将loader的source map加入
    
      内联和外部的区别:
        1、外部生成了文件,内联没有
        2、内联构建速度更快
      
      开发环境:速度快,调试更友好
        速度快 eval>inline>cheap>...
          eval-cheap-soucre-map  cheap会把列去掉,只精确到行
          eval-source-map
        调试更友好
          source-map
          cheap-module-source-map
          cheap-source-map
    
        结论:eval-source-map 速度快,脚手架用的  eval-cheap-module-source-map  加上cheap调试变差,性能会变强
    
      生产环境:源代码要不要隐藏?调试要不要更友好
    
        内联会让代码体积变大,所以在生产环境不用内联
        nosources-source-map 全部隐藏
        hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
    
        结论:source-map 调试最友好  cheap-module-source-map 加了cheap速度快

    oneOf:

      oneOf表示以下loader只会匹配一个,优化生成环境打包构建速度 注意:不能有两个loader处理一种类型文件,将eslint-loader放到外面

    const { resolve } = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 提取css成单独文件
    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin') // 压缩css
    
    process.env.NODE_ENV = 'production' // browserslist默认使用生产环境配置,这里可以手动修改为开发环境配置
    
    // 复用loader
    const commonCssLoader = [
      MiniCssExtractPlugin.loader,
      'css-loader',
      {
        loader: 'postcss-loader', // 需要在package.json中定义browserslist
        options: {
          ident: 'postcss',
          plugins: () => [require('postcss-preset-env')()]
        }
      }
    ]
    
    module.exports = {
      entry: './src/js/index.js',
      output: {
        filename: 'js/built.js',
        path: resolve(__dirname, 'build')
      },
      module: {
        rules: [
          {
            test: /.js$/,
            exclude: /node_modules/,
            enforce: 'pre', // 一般情况下,一个文件只能被一个loader处理,当一个文件需要被多个loader处理,需要指定loader的先后顺序:先执行eslint再执行babel    原因:eslint语法检查错误后面的事再去做就没有意义;babel是将es6转为es5,如果先执行eslint,转化后的var会被eslint报错
            loader: 'eslint-loader', // 在package.json中定义eslintConfig
            options: { fix: true } // 自动修复eslint的错误
          },
          {
            // oneOf表示以下loader只会匹配一个,优化生成环境打包构建速度   注意:不能有两个loader处理一种类型文件,将eslint-loader放到外面
            oneOf: [
              {
                test: /.css$/,
                use: [...commonCssLoader]
              },
              {
                test: /.less$/,
                use: [...commonCssLoader, 'less-loader'] // 执行过程:less-loader将less文件编译成css文件,postcss-loader对css做兼容性处理,css-loader将css加载到js中,MiniCssExtractPlugin.loader提取css为单独文件
              },
              {
                test: /.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: {
                  presets: [
                    [
                      '@babel/preset-env',
                      {
                        useBuiltIns: 'usage', // 按需加载
                        corejs: { version: 3 },
                        targets: {
                          chrome: '60',
                          firefox: '60',
                          ie: '9',
                          safari: '10',
                          edge: '17'
                        }
                      }
                    ]
                  ]
                }
              },
              {
                test: /.(jpg|png|gif)$/,
                loader: 'url-loader',
                options: {
                  limit: 6 * 1024,
                  esModule: false,
                  outputPath: 'images', // 打包完后css引入的图片路径会有问题
                  // publicPath: '../images', // 加了这句,css的图片引入没有问题,但是html中的图片引入有问题
                  name: '[hash:10].[ext]'
                }
              },
              {
                test: /.html$/,
                loader: 'html-loader' // 处理html中的img,需要关闭ES6的模块化 esModule: false
              },
              {
                exclude: /.(js|css|less|html|jpg|png|jif)$/,
                loader: 'file-loader',
                options: { outputPath: 'media' }
              }
            ]
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',
          minify: {
            collapseWhitespace: true,
            removeComments: true
          }
        }),
        new MiniCssExtractPlugin({ filename: 'css/built.css' }), // 提取css成单独文件
        new OptimizeCssAssetsWebpackPlugin() // 压缩css
      ],
      mode: 'production', // 生成模式下js自动压缩devServer:
      devServer: {
        contentBase: resolve(__dirname, 'build'),
        compress: true,
        port: 3000,
        open: true
      }
    }

    缓存:

      babel缓存: cacheDirectory: true 作用:让第二次打包构建速度更快

      文件资源缓存:

        hash:每次webpack构建时会生成一个唯一个hash值 问题:因为js和css用时使用一个hash值,如果重新打包,会导致所有缓存失效,可能当前只修改了一个文件

        chunkhash:根据chunk生成的hash值,如果打包来源于同一个chunk,那么hash值就一样 问题:js和css的hash值还是一样的,因为css是在js中被引入的,所以同属于一个chunk

         contenthash:根据文件的内容生成hash值,不同文件的hash值一定不一样  作用:让代码上线运行缓存更好使用,推荐使用这种

    const { resolve } = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 提取css成单独文件
    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin') // 压缩css
    
    process.env.NODE_ENV = 'production' // browserslist默认使用生产环境配置,这里可以手动修改为开发环境配置
    
    // 复用loader
    const commonCssLoader = [
      MiniCssExtractPlugin.loader,
      'css-loader',
      {
        loader: 'postcss-loader', // 需要在package.json中定义browserslist
        options: {
          ident: 'postcss',
          plugins: () => [require('postcss-preset-env')()]
        }
      }
    ]
    
    module.exports = {
      entry: './src/js/index.js',
      output: {
        filename: 'js/built.[contenthash:10].js',
        path: resolve(__dirname, 'build')
      },
      module: {
        rules: [
          {
            test: /.js$/,
            exclude: /node_modules/,
            enforce: 'pre', // 一般情况下,一个文件只能被一个loader处理,当一个文件需要被多个loader处理,需要指定loader的先后顺序:先执行eslint再执行babel    原因:eslint语法检查错误后面的事再去做就没有意义;babel是将es6转为es5,如果先执行eslint,转化后的var会被eslint报错
            loader: 'eslint-loader', // 在package.json中定义eslintConfig
            options: { fix: true } // 自动修复eslint的错误
          },
          {
            // oneOf表示以下loader只会匹配一个,优化生成环境打包构建速度   注意:不能有两个loader处理一种类型文件,将eslint-loader放到外面
            oneOf: [
              {
                test: /.css$/,
                use: [...commonCssLoader]
              },
              {
                test: /.less$/,
                use: [...commonCssLoader, 'less-loader'] // 执行过程:less-loader将less文件编译成css文件,postcss-loader对css做兼容性处理,css-loader将css加载到js中,MiniCssExtractPlugin.loader提取css为单独文件
              },
              {
                test: /.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: {
                  presets: [
                    [
                      '@babel/preset-env',
                      {
                        useBuiltIns: 'usage', // 按需加载
                        corejs: { version: 3 },
                        targets: {
                          chrome: '60',
                          firefox: '60',
                          ie: '9',
                          safari: '10',
                          edge: '17'
                        }
                      }
                    ]
                  ],
                  cacheDirectory: true // 开启babel缓存,第二次构建时会读取之前的缓存
                }
              },
              {
                test: /.(jpg|png|gif)$/,
                loader: 'url-loader',
                options: {
                  limit: 6 * 1024,
                  esModule: false,
                  outputPath: 'images', // 打包完后css引入的图片路径会有问题
                  // publicPath: '../images', // 加了这句,css的图片引入没有问题,但是html中的图片引入有问题
                  name: '[hash:10].[ext]'
                }
              },
              {
                test: /.html$/,
                loader: 'html-loader' // 处理html中的img,需要关闭ES6的模块化 esModule: false
              },
              {
                exclude: /.(js|css|less|html|jpg|png|jif)$/,
                loader: 'file-loader',
                options: { outputPath: 'media' }
              }
            ]
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',
          minify: {
            collapseWhitespace: true,
            removeComments: true
          }
        }),
        new MiniCssExtractPlugin({ filename: 'css/built.[contenthash:10].css' }), // 提取css成单独文件
        new OptimizeCssAssetsWebpackPlugin() // 压缩css
      ],
      mode: 'production', // 生成模式下js自动压缩devServer:
      devServer: {
        contentBase: resolve(__dirname, 'build'),
        compress: true,
        port: 3000,
        open: true
      },
      devtool:'source-map'
    }

    需要启动本地服务器:server.js:

    /*
      服务器代码:
        启动服务器指令:
          npm i nodemon -g
          nodemon server.js
    
          node server.js
        
        访问服务器地址:
          http://localhost:3000
    */
    const express = require('express')
    const app = express()
    app.use(express.static('build', { maxAge: 1000 * 3600 })) // express.static() 向外暴露静态资源    maxAge:资源缓存的最大时间,单位ms
    app.listen(3000)

    tree shaking:

      tree shaking:去除无用代码,减少代码体积
        前提:必须使用ES6模块化,开启production环境

      在package.json中设置:
        "sideEffects": false    所有代码都没有副作用,都可以进行tree shaking    问题:可能会把 css @babel/polyfill 文件干掉
        "sideEffects": ["*.css", "*.less"]
     
      注意:在webpack4中,如果js模块进行嵌套使用,那么树摇功能会受限,无法彻底删除无用代码,尽量减少嵌套使用。而在webpack5中可以精确地删除无用代码。

    code split:

    第一种:

    const { resolve } = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      // entry: './src/js/index.js', // 单入口
      entry: {
        // 多入口:一个入口对应一个bundle
        index: './src/js/index.js',
    test: './src/js/test.js'
      },
      output: {
        filename: 'js/[name].[contenthash:10].js', // [name] 取文件名
        path: resolve(__dirname, 'build')
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',
          minify: {
            collapseWhitespace: true,
            removeComments: true
          }
        })
      ],
      mode: 'production'
    }

      打包后的文件有几个入口文件,就有几个bundle:

    第二种:

    const { resolve } = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      // entry: './src/js/index.js', // 单入口
      entry: {
        // 多入口:一个入口对应一个bundle
        index: './src/js/index.js',
        test: './src/js/test.js'
      },
      output: {
        filename: 'js/[name].[contenthash:10].js', // [name] 取文件名
        path: resolve(__dirname, 'build')
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',
          minify: {
            collapseWhitespace: true,
            removeComments: true
          }
        })
      ],
      // 1、对于单入口文件(单页面应用)自动将node_modules中代码单独打包一个chunk输出   2、对于多入口文件(多页面应用)自动分析多入口chunk中有没有公共的文件,如果有会打包成单独的一个chunk
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      },
      mode: 'production'
    }

    第三种:使用单入口,实际开发中以单页面为主,通过js代码让某个文件单独打包成一个chunk,不同js文件中引入同一个模块只会打包成一个js文件

      entry: './src/js/index.js', // 单入口
    
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      },
    /*
      通过js代码,让某个文件被单独打包成一个chunk   import动态导入语法:能将某个文件单独打包
    */
    import(/* webpackChunkName: 'test' */'./test')
      .then(({ add, count }) => {
        console.log(add(5, 6))
        console.log(count(5, 6))
      })
      .catch(() => {
        console.log('文件加载失败')
      })

    lazy loading:

    webpack.config.js:

    const { resolve } = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      entry: './src/js/index.js',
      output: {
        filename: 'js/[name].[contenthash:10].js',
        path: resolve(__dirname, 'build')
      },
      plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      },
      mode: 'production'
    }

    index.js中开启懒加载和预加载:

    console.log('index被加载了')
    
    // import { add } from './test'
    
    document.querySelector('.btn').onclick = function () {
      /*
        懒加载:当这个文件需要使用时才加载
        预加载:prefetch,在使用之前,提前加载js
        正常加载可以认为是并行加载,同一时间加载多个文件,而预加载是等其他资源加载完毕,浏览器空闲了再偷偷加载资源
        webpackChunkName: 'test'----给打包后的js文件命名   webpackPrefetch: true----开启预加载
      */
      import(/* webpackChunkName: 'test',webpackPrefetch: true */ './test').then(
        ({ add, count }) => {
          console.log(add(5, 6))
          console.log(count(5, 6))
        }
      )
    }

    pwa:

      1、渐进式网络开发应用程序,离线可访问。下载插件:npm i workbox-webpack-plugin -D

      2、webpack.config.js中引入插件和使用插件:

    const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
    
        new WorkboxWebpackPlugin.GenerateSW({
          // 生成一个serviceWorker配置文件
          clientsClaim: true, // 帮助serviceWorker快速启动
          skipWaiting: true // 删除旧的serviceWorker
        })

      3、src/index.js中注册serviceWorker:

    // 注册serviceWorker
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker
          .register('/service-worker.js')
          .then(() => {
            console.log('sw注册成功')
          })
          .catch(() => {
            console.log('sw注册失败')
          })
      })
    }

      4、eslint不支持window、navigate 等全局变量,需要在package中"eslintConfig"中加上:

        "env": {
          "browser": true
        }

      5、sw代码必须运行在服务器上,一种是通过nodejs,一种是通过serve,这里用serve的方式:

        npm i serve -g

        serve -s build  启动服务器,将build目录下所有资源作为静态资源暴露出去

    多进程打包:

      1、下载插件:npm i thread-loader -D

      2、使用:

              {
                test: /.js$/,
                exclude: /node_modules/,
                use: [
                  {
                    loader: 'thread-loader', // 开启多进程打包,多进程本身启动大概为600ms,进程通信也有开销,只有工作消耗时间较长,才需要多进程打包
                    options: { workers: 2 } // 进程2个
                  },
                  {
                    loader: 'babel-loader',
                    options: {
                      presets: [
                        [
                          '@babel/preset-env',
                          {
                            useBuiltIns: 'usage',
                            corejs: { version: 3 },
                            targets: {
                              chrome: '60',
                              firefox: '50'
                            }
                          }
                        ]
                      ],
                      cacheDirectory: true
                    }
                  }
                ]
              },

    externals:

    如果使用了外部cdn资源,则在打包的时候拒绝该资源被打包进来,提高打包速度----需要cdn引入的包,需要在externals中拒绝打包,在html中通过script标签引入cdn资源

    const { resolve } = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      entry: './src/js/index.js',
      output: {
        filename: 'js/built.js',
        path: resolve(__dirname, 'build')
      },
      module: {
        rules: []
      },
      plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
      mode: 'production',
      externals: { jquery: 'jQuery' } // 拒绝jQuery被打包进来
    }

    externals中拒绝某个包的前提是在html中引入了cdn资源:<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

    dll:

      1、下载插件:npm i add-asset-html-webpack-plugin -D

      2、定义webpack.dll.js:

    /*
      使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包
      当运行webpack时,默认查找 webpack.config.js 配置文件
      需求:需要运行 webpack.dll.js 文件  webpack --config webpack.dll.js
    */
    const { resolve } = require('path')
    const Webpack = require('webpack')
    
    module.exports = {
      entry: {
        jquery: ['jquery'] // 最终打包生成的[name] --> jquery    ['jquery'] --> 要打包的库是jquery
      },
      output: {
        filename: '[name].js',
        path: resolve(__dirname, 'dll'),
        library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
      },
      plugins: [
        // 打包生成一个 manifest.json 提供jquery映射
        new Webpack.DllPlugin({
          name: '[name]_[hash]', // 映射库的暴露的内容名称
          path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
        })
      ],
      mode: 'production'  
    }

      运行 webpack --config webpack.dll.js

      3、webpack.config.js:

    const { resolve } = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const Webpack = require('webpack')
    const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
    
    module.exports = {
      entry: './src/js/index.js',
      output: {
        filename: 'js/built.js',
        path: resolve(__dirname, 'build')
      },
      plugins: [
        new HtmlWebpackPlugin({ template: './src/index.html' }),
        new Webpack.DllReferencePlugin({
          manifest: resolve(__dirname, 'dll/manifest.json')
        }), // 告诉webpack哪些库不参与打包,同时使用的名称也得变
        new AddAssetHtmlWebpackPlugin({
          filepath: resolve(__dirname, 'dll/jquery.js')
        }) // 将某个文件打包输出去,并在html中自动引入该资源
      ],
      mode: 'production'
    }

    注意:

      如果用cdn引入第三方资源,用externals;

      如果需要将第三方库进行单独打包,不使用cdn连接,通过自己服务器向外暴露出去,用dll。

    x

  • 相关阅读:
    MYSQL索引
    列表里重复次数最多的元素
    python学习笔记
    Spark 扫描 HDFS lzo/gz/orc异常压缩文件
    Yarn RM写ZNode超数据量限制bug修复
    Spark HistoryServer日志解析&清理异常
    【Yarn源码分析】Container启动流程源码分析
    Yarn NodeManager总体架构
    【Yarn源码分析】ApplicationMaster源码分析
    【Yarn源码分析】FairScheduler资源调度
  • 原文地址:https://www.cnblogs.com/wuqilang/p/13962179.html
Copyright © 2011-2022 走看看