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

原文地址:https://www.cnblogs.com/wuqilang/p/13962179.html