webpack4

webpack4

webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。

使用

  • 初始化 package.json
    npm init
  • 下载并安装 webpack
    npm install webpack webpack-cli -g
    npm install webpack webpack-cli -D
    
    开发环境指令:webpack src/js/index.js -o build/js/built.js --mode=development
    功能:webpack 能够编译打包 js 和 json 文件,并且能将 es6 的模块化语法转换成浏览器能识别的语法。
    生产环境指令:webpack src/js/index.js -o build/js/built.js --mode=production
    功能:在开发配置功能上多一个功能,压缩代码。
  • 问题
    不能编译打包 css、img 等文件。
    不能将 js 的 es6 基本语法转化为 es5 以下语法。

开发环境的基本配置

打包样式资源

  • 下载安装 loader 包
    npm i css-loader style-loader less-loader less -D
  • 修改配置文件
    module: {
      rules: [
        // 详细 loader 配置
        // 不同文件必须配置不同 loader 处理
        {
          // 匹配哪些文件
          test: /\.css$/,
          // 使用哪些 loader 进行处理
          use: [
            // use 数组中 loader 执行顺序:从右到左,从下到上 依次执行
            // 创建 style 标签,将 js 中的样式资源插入进行,添加到 head 中生效
            'style-loader',
            // 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串
            'css-loader'
          ]
        }
      ]
    },
    

打包 HTML 资源

  • 下载安装 plugin 包
    npm install --save-dev html-webpack-plugin
  • 修改配置文件
    plugins: [
      // plugins 的配置
      // html-webpack-plugin
      // 功能:默认会创建一个空的 HTML,自动引入打包输出的所有资源(JS/CSS)
      // 需求:需要有结构的 HTML 文件
      new HtmlWebpackPlugin({
        // 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
        template: './src/index.html'
      })
    ],
    

打包图片资源

  • 下载安装 loader 包
    npm install --save-dev html-loader url-loader file-loader
  • 修改配置文件
    module: {
      rules: [
        {
          // 问题:默认处理不了 html 中 img 图片
          // 处理图片资源
          test: /\.(jpg|png|gif)$/,
          // 使用一个 loader
          // 下载 url-loader file-loader
          loader: 'url-loader',
          options: {
            // 图片大小小于 8kb,就会被 base64 处理
            // 优点: 减少请求数量(减轻服务器压力)
            // 缺点:图片体积会更大(文件请求速度更慢)
            limit: 8 * 1024,
            // 问题:因为 url-loader 默认使用 es6 模块化解析,而 html-loader 引入图片是 commonjs
            // 解析时会出问题:[object Module]
            // 解决:关闭 url-loader 的 es6 模块化,使用 commonjs 解析
            esModule: false,
            // 给图片进行重命名
            // [hash:10]取图片的 hash 的前 10 位
            // [ext]取文件原来扩展名
            name: '[hash:10].[ext]'
          }
        },
        {
          test: /\.html$/,
          // 处理 html 文件的 img 图片(负责引入 img,从而能被 url-loader 进行处理)
          loader: 'html-loader'
        }
      ]
    },
    

打包其他资源

module: {
  rules: [
    // 打包其他资源(除了 html/js/css 资源以外的资源)
    {
      // 排除 css/js/html 资源
      exclude: /\.(css|js|html|less)$/,
      loader: 'file-loader',
      options: {
        name: '[hash:10].[ext]'
      }
    }
  ]
}

devserver

devServer: {
  // 项目构建后路径
  contentBase: resolve(__dirname, 'build'),
  // 启动 gzip 压缩
  compress: true,
  // 端口号
  port: 3000,
  // 自动打开浏览器
  open: true
}

运行指令: npx webpack-dev-server

生产环境的基本配置

提取 css 成单独文件

  • 下载插件
    npm install --save-dev mini-css-extract-plugin
  • 修改配置文件
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            // 创建 style 标签,将样式放入
            // 'style-loader',
            // 这个 loader 取代 style-loader。作用:提取 js 中的 css 成单独文件
            MiniCssExtractPlugin.loader,
            // 将 css 文件整合到 js 文件中
            'css-loader'
          ]
        }
      ]
    },
    plugins: [
      new MiniCssExtractPlugin({
        // 对输出的 css 文件进行重命名
        filename: 'css/built.css'
      })
    ]
    

css 兼容性处理

  • 下载 loader
    npm install --save-dev postcss-loader postcss-preset-env
  • 修改配置文件
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            MiniCssExtractPlugin.loader,
            'css-loader',
            {
              loader: 'postcss-loader',
              options: {
                ident: 'postcss',
                plugins: () => [
                    // postcss 的插件
                    require('postcss-preset-env')()
                  ]
              }
            }
          ] 
        }
      ]
    },
    
  • 修改 package.json
    "browserslist": {
      "development": [
        "last 1 chrome version",
        "last 1 firefox version",
        "last 1 safari version"
      ],
      "production": [
        ">0.2%",
        "not dead",
        "not op_mini all"
      ]
    }
    

压缩 css

  • 下载安装包
    npm install --save-dev optimize-css-assets-webpack-plugin
  • 修改配置文件
    plugins: [
      // 压缩 css
      new OptimizeCssAssetsWebpackPlugin()
    ]
    

js 语法检查

  • 下载安装包
    npm install --save-dev eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import
  • 修改配置文件
    module: {
      rules: [
        /*
        注意:只检查自己写的源代码,第三方的库是不用检查的
        airbnb --> eslint-config-airbnb-base eslint-plugin-import eslint
        */
        {
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'eslint-loader',
          options: {
            // 自动修复 eslint 的错误
            fix: true
          }
        }
      ]
    }
    
  • 配置 package.json
    "eslintConfig": {
      "extends": "airbnb-base",
      "env": {
        // eslint不认识window navigator全局变量
        "browser": true  //支持浏览器端全局变量
      }
    }
    

js 兼容性处理

  • 下载安装包
    npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/polyfill core-js
  • 修改配置文件
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'babel-loader',
          options: {
            // 预设:指示 babel 做怎么样的兼容性处理
            presets: [
              [
                '@babel/preset-env',
                {
                  // 按需加载
                  useBuiltIns: 'usage',
                  // 指定 core-js 版本
                  corejs: {
                    version: 3
                  },
                  // 指定兼容性做到哪个版本浏览器
                  targets: {
                    chrome: '60',
                    firefox: '60',
                    ie: '9',
                    safari: '10',
                    edge: '17'
                  }
                }
              ]
            ]
          }
        }
      ]
    }
    

js 压缩

  • 修改配置文件
    // 生产环境下会自动压缩 js 代码
    mode: 'production'
    

HTML 压缩

  • 修改配置文件
    plugins: [
      new HtmlWebpackPlugin({
        template: './src/index.html',
        // 压缩 html 代码
        minify: {
          // 移除空格
          collapseWhitespace: true,
          // 移除注释
          removeComments: true
        }
      })
    ]
    

优化配置

  • 开发环境性能优化
    • 优化打包构建速度 HMR
    • 优化代码调试 source-map
  • 生产环境性能优化
    • 优化打包构建速度 oneOf babel缓存 多进程打包 externals dll
    • 优化代码运行的性能 缓存(hash-chunkhash-contenthash) tree shaking code split 懒加载/预加载 pwa

HMR

entry: ['./src/js/index.js', './src/index.html'],
devServer: {
  contentBase: resolve(__dirname, 'build'),
  compress: true,
  port: 3000,
  open: true,
  /*  
    HMR:hot module replacement 热模块替换/模块热替换
    作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块)
      极大提升构建速度

      样式文件: 可以使用HMR功能 因为style-loader内部实现了
      js文件: 默认不能使用HMR功能 -- 需要修改js代码,添加支持HMR功能的代码
        注意:HMR功能对js的处理,只能处理非入口文件的其他文件
      html文件: 默认不能使用HMR功能,同时会导致问题:html文件不能热更新了(不用做HMR功能)
        解决: 修改entry入口,将html文件引入
  */
  /*  
    开启HMR功能
    当修改了webpack配置,新配置要想生效,必须重新启动webpack服务
  */
  hot: true
}

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: 外部
    错误代码的准确信息 和源代码的错误位置

  内联和外部的区别:1.外部生成了文件,内联没有 2.内联构建速度更快

  开发环境:速度快,调试更友好 -- eval-source-map
    速度(eval-inline-cheap-..)
      eval-cheap-source-map
      eval-source-map
    调试更友好
      source-map
      cheap-module-source-map
      cheap-source-map
  
  生产环境:源代码要不要隐藏?调试要不要更友好 -- source-map/cheap-module-source-map
    内联会让代码体积变大,所以在生产环境中不用内联
    nosource-source-map 全部隐藏
    hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
*/
devtool: 'cheap-module-source-map'

one-of

module: {
  rules: [
    {
      //在package.json中 eslintConfig
      test: /\.js$/,
      exclude: /node_modules/,
      enforce: "pre",
      loader: "eslint-loader",
      options: {
        fix: true,
      },
    },
    {
      // 一下loader只会匹配一个
      //注意: 不能有两个配置处理同一种类型文件
      //优化打包速度
      oneOf: [
        {
          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",
                  },
                },
              ],
            ],
          },
        },
      ],
    },
  ],
},

缓存

/*  
  缓存:
    babel缓存 --让第二次打包构建速度更快
      cacheDirectory: true
    文件资源缓存 --让代码上线运行缓存更好使用
      hash:每次wepack构建时会生成一个唯一的hash值
        问题:因为js和css同时使用一个hash值,如果重新打包,会导致所有缓存失效(可能我只改动了一个文件)
      chunkhash:根据chunk生成的hash值,如果打包来源于同一个chunk,那么hash值就一样
        问题:js和css的hash值还是一样的 因为css和js中被引入的,所以同属于一个chunk
      contenthash:根据文件的内容生成hash值,不同文件hash值一定不一样
*/
output: {
  filename: "js/built.[contenthash:8].js",
  path: resolve(__dirname, "build")
},
module: {
  rules: [
    {
      oneOf: [
        {
          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",
                  },
                },
              ],
            ],
            //开启babel缓存
            //第二次构建时,会读取之前的缓存
            cacheDirectory: true
          }
      ],
    },
  ],
},
plugins: [
  new MiniCssExtractPlugin({
    filename: "css/built.[contenthash:8].css",
  })
]

tree shaking

/*  
tree shaking: 去除无用代码
  前提: 1.必须使用ES6模块化 2.开启production环境
  作用: 减少代码体积

在package.json中配置
  "sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)
    问题:可能会把css/ @babel/polyfill(副作用)文件干掉
  "sideEffects": ["*.css", "*.less"]
*/

code split

  • 多入口

    //多入口: 有一个入口,最终输出就有一个bundle
    entry: {
      index: './src/js/index.js',
      test: './src/js/test.js'
    },
    /*  
      1.可以将node_modules中代码单独打包一个chunk最终输出
      2.自动分析多入口chunk中 有没有公共的文件,如果有会打包成单独一个chunk
    */  
    optimization: {
      splitChunks: {
        chunks: 'all'
      }
    },
    
  • 单入口

    entry: './src/js/index.js',
    /*  
      1.可以将node_modules中代码单独打包一个chunk最终输出
    */  
    optimization: {
      splitChunks: {
        chunks: 'all'
      }
    }
    
    /*  
      通过js代码,让某个文件被单独打包成一个chunk
      import动态导入语法:能将某个文件单独打包
    */
    import(/* webpackChunkName: 'test' */"./test")
      .then((result) => {
        //文件加载成功
        // eslint-disable-next-line
        console.log(mul);
      })
      .catch(() => {
        //eslint-disable-next-line
        console.log("文件加载失败");
      });
    

懒加载和预加载

document.getElementById('btn').onclick = function() {
  //懒加载: 当文件需要使用时才加载
  //预加载 prefetch:会在使用之前,提前加载js文件
  //正常加载可以认为是并行加载(同一时间加载多个文件) 预加载:等其他资源加载完毕,浏览器空闲了再偷偷加载资源
  import(/* webpackChunkName: 'test, webpackPrefetch: true */'./test').then(({ mul } )=> {
    console.log(mul(4, 5))
  }) 
}

PWA

  • 下载插件
    npm install --save-dev workbox-webpack-plugin

  • 修改配置文件

    /*  
      PWA:渐进式网络开发应用程序(离线可访问)
      workbox -- workbox-webpack-plugin
    */
    plugins: [
      new WorkboxWebpackPlugin.GenerateSW({
        /*  
          1.帮助serviceworker快速启动
          2.删除旧的serviceworker
    
          生成一个serviceworker配置文件
        */
        clientsClaim: true,
        skipWaiting: true
      })
    ],
    
    sw代码必须运行在服务器上
        --nodejs
        --
          npm i serve -g
          serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
    */
    //注册serviceWorker
    //处理兼容性问题
    if ("serviceWorker" in navigator) {
      window.addEventListener("load", () => {
        navigator.serviceWorker
          .register("./service-worker.js")
          .then(() => {
            console.log("sw注册成功");
          })
          .catch(() => {
            console.log("sw注册失败了");
          });
      });
    }
    

多进程打包

  • 下载插件
    npm install --save-dev thread-loader
  • 修改配置文件
    module: {
      rules: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: [
              /*  
                开启多进程打包
                进程启动大概为600ms,进程通信也有开销
                只有工作消耗时间比较长,才需要多进程打包
              */
              {
                loader: "thread-loader",
                options: {
                  workers: 2 //进程为2
                }
              },
              {
                loader: "babel-loader",
              },
            ],
          },
        ],
      },
    

externals

externals: {
  //拒绝jQuery被打包进来 用cdn链接
  //忽略库名 --npm包名
  jquery: 'jQuery'
}

dll

  • 新增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: {
        //最终打包生成的[name] --jquery
        //['jquery'] --要打包的库是jquery
        jquery: ['jquery']
      },
      output: {
        filename: '[name].js',
        path: resolve(__dirname, 'dll'),
        library: '[name]_[hash:8]' //打包的库里面向外暴露出去的内容叫什么名字
      },
      plugins: [
        //打包生成一个manifest.json --提供对jquery映射
        new webpack.DllPlugin({
          name: '[name]_[hash]', //映射库的暴露的内容名称
          path: resolve(__dirname, 'dll/manifest.json') //输出文件路径
        })
      ],
      mode: 'production'
    }
    
  • 修改webpack.config.js

    plugins: [
      //告诉webpack哪些库不参与打包,同时使用时的名称也得变
      new webpack.DllReferencePlugin({
        manifest: resolve(__dirname, 'dll/manifest.json')
      }),
      //将某个文件打包输出出去,并在html中自动引入该资源
      new AddAssetHtmlWebpackPlugin({
        filepath: resolve(__dirname, 'dll/jquery.js')
      })
    ],
    

webpack 配置详情

entry

/*  
  entry: 入口起点
    1.string  './src/index.js'
      打包形成一个chunk 输出一个bundle文件 此时chunk的名称默认是main
    2.array  ['./src/index.js', './src/add.js']
      多入口
      所有入口文件最终只会形成一个chunk,输出出去只有一个bundle文件
        只有在HMR功能中让html热更新生效
    3.object 
      entry: {
        index: './src/index.js',
        add: './src/add.js'
      }
      多入口
      有几个入口文件就形成几个chunk,输出几个bundle文件 此时chunk的名称是key
    4.特殊用法
      entry: {
        //所有入口文件最终只会形成一个chunk,输出出去只有一个bundle文件
        index: ['./src/index.js', './src/count.js'],
        //形成一个chunk,输出一个bundle文件
        add: './src/add.js'
      } 
*/

output

output: {
  //文件名称(指定目录+名称)
  filename: '[name].js',
  //输出文件目录(将来所有资源输出的公共目录)
  path: resolve(__dirname, 'build'),
  //所有资源引入公共路径前缀 'imgs/a.jpg' '/imgs/a.jpg'
  publicPath: '/',
  //非入口chunk的名称
  chunkFilename: 'js/[name]_chunk.js',
  //整个库向外暴露的变量名
  library: '[name]',
  // 变量名添加到哪个上 (browser)
  libraryTarget: 'window'
  // libraryTarget: 'global' //nodejs
  // libraryTarget: 'commonjs' //commonjs
}

module

module: {
  rules: [
    //loader的配置
    {
      test: /\.css$/,
      //多个loader用use
      use: ['style-loader', 'css-loader']
    },
    {
      test: /\.js$/,
      //排除node_modules下的js文件
      exclude: /node_modules/,
      //只检查src文件夹下js文件
      include: resolve(__dirname, 'src'),
      //优先执行 延后执行(post)
      enforce: 'pre',
      //单个loader用loader
      loader: 'eslint-loader',
      options: {}
    },
    {
      //以下配置只会生效一个
      oneOf: []
    }
  ]
}

resolve

//解析模块的规则
resolve: {
  //配置解析模块路径别名 简写路径
  alias: {
    $css: resolve(__dirname, 'src/css')
  },
  //配置省略文件路径的后缀名
  extensions: ['.js', '.json', '.jsx', '.css'],
  //告诉webpack解析模块是去找哪个目录
  modules: [resolve(__dirname, '../../node_modules', 'node_modules')]
}

devserver

devServer: {
  //项目构建之后的路径
  contentBase: resolve(__dirname, 'build'),
  //监视 contentBase 目录下的所有文件,一旦文件变化就会reload
  watchContentBase: true,
  watchOptions: {
    //忽略文件
    ignored: /node_modules/
  },
  //启动gzip压缩
  compress: true,
  //域名
  host: 'localhost',
  //端口号
  port: 5000,
  open: true,
  //开启HMR功能
  hot: true,
  //不要显示启动服务器日志信息
  clientLogLevel: 'none',
  //除了一些基本启动信息以外,其他内容都不显示
  quiet: true,
  //如果出错了,不要全屏提示
  overlay: false,
  //服务器代理 解决开发环境跨域问题
  proxy: {
    //一旦devServer(5000)服务器接受到/api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
    '/api': {
      target: 'http://localhost:3000',
      //发送请求时,请求路径重写:将/api/xxx 变成 /xxx (去掉/api)
      pathRewrite: {
        '^/api': ''
      }
    }
  }
}

optimization

optimization: {
  splitChunks: {
    chunks: 'all',
    // 默认值,可以不写~
    minSize: 30 * 1024, // 分割的chunk最小为30kb
    maxSize: 0, // 最大没有限制
    minChunks: 1, // 要提取的chunk最少被引用1次
    maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
    maxInitialRequests: 3, // 入口js文件最大并行请求数量
    automaticNameDelimiter: '~', // 名称连接符
    name: true, // 可以使用命名规则
    cacheGroups: {
      // 分割chunk的组
      // node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js
      // 满足上面的公共规则,如:大小超过30kb,至少被引用一次。
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        // 优先级
        priority: -10
      },
      default: {
        // 要提取的chunk最少被引用2次
        minChunks: 2,
        // 优先级
        priority: -20,
        // 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
        reuseExistingChunk: true
      } 
    }
  },
  // 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
  // 解决:修改a文件导致b文件的contenthash变化
  runtimeChunk: {
    name: entrypoint => `runtime-${entrypoint.name}`
  },
  minimizer: [
    // 配置生产环境的压缩方案:js和css
    new TerserWebpackPlugin({
      // 开启缓存
      cache: true,
      // 开启多进程打包
      parallel: true,
      // 启动source-map
      sourceMap: true
    })
  ]
}
posted @   提莫一米五呀  阅读(227)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示