webpack4.0笔记

359 阅读11分钟

Ⅰ. 开发环境配置

1. 安装依赖
// js的构建工具都是基于nodejs的 , 安装的依赖都是生产模式

npm i webpack webpack-cli --save-dev
2. module的use数组里面loader执行顺序

​ 下 ==> 上 ( 右 ==> 左 )

3. 处理 css文件
css-loader : 将css文件变成commonjs模块加载到js中, 里面内容是样式字符串
style-loader : 创建style标签 , 将js中的样式资源插入进行, 添加到head标签中
4. 处理scss/less文件
// webpack里面用sass/less要安装相应依赖 
npm install --save-dev node-sass sass-loader

npm install --save-dev less less-loader
 {
        test: /\.less$/,
        use: [{ loader: "style-loader" },{ loader: "css-loader" }, { loader: "less-loader" }],
 },
5. 处理html资源
html-webpack-plugin插件 (注意 : 不是html-loader)

作用 : 默认会创建一个空的html文件 , 自动引入打包输出的所有资源 (js/css文件等)
// 安装 npm i html-webpack-plugin -D

const HtmlWebpackPlugin = require("html-webpack-plugin")
 plugins: [
      // 创建一个实例
     new HtmlWebpackPlugin({
         // 模版 : 将路径中的html文件作为模版复制 , 并添加引入打包输出的所有资源 css/js
         template: "./src/index.html",
         minify: {
             removeComments: true, //清理注释
             collapseWhitespace: true, //清理空格
         },
     })
  ],
6. 打包图片资源
// 默认使用url-loader , 但仅仅能处理样式中引入的图片资源
url-loader , file-loader (url-loader依赖于file-loader) 

// 处理html中的img引入图片 (负责img引入, 从而能被url-loader处理)
html-loader 

注意 : url-loader默认使用es6模块化解析, 而html-loader引入图片是commonjs规范 , 解析时候会报错

解决 : 关闭url-loader的es6模块化 , 使用commonjs解析 esModule : false

// loader相关配置
	{
        test: /\.(jpg|png|gif)$/,
        use: [
          {
            loader: "url-loader", //url-loader依赖于file-loader
            options: {
              limit: 8 * 1024,
                /*    图片小于8kb就是用base64的方式
                      优点: 减少请求数量(减轻服务器压力) 缺点: 图片体积会更大(文件请求速度变慢)
                      所以一般不对大图片进行base64处理 , 对8-12kb以下的小图片进行base64处理 
                      比如 : 有个9kb的图片 , 就可以设置 10*1024
          		*/
             esModule: false,
             name: "[hash:8].[ext]" // 重命名 取到hash值前8位  .[ext] 保留原来的后缀名
            }
          }
        ]
    },
    {
        test: /\.html$/,
        use: [
          {
         	// 处理html中的img标签图片 (负责img引入, 从而能被url-loader处理)
            loader: "html-loader"
          }
        ]
    }

// 如果打包后的的html出现 // module 开头的乱码 , 可能是配置了两次 html-loader

// plugin相关配置

 const HtmlWebpackPlugin = require("html-webpack-plugin");

 plugins: [
    // 创建一个实例
    new HtmlWebpackPlugin({
      // 模版 : 将路径中的html文件作为模版复制 , 并添加引入打包输出的所有资源 css/js
      template: "./src/index.html",
      // minify: {
      //   removeComments: true, // 清理注释
      //   collapseWhitespace: true // 清理空格
      // }
    })
  ],

7. 打包其他资源

除了html / js / css其他的资源

      {
        /* 打包其他资源(排除资源,原样输出) */
        exclude: /\.(css|js|html)$/,
        use:[
                {
                    loader: "file-loader",
                    options: {
                      name: "[hash:8].[ext]"
                    }
                }
        	]  
      }


8. 开发服务器devServer
webpack-cli4的启动命令 webpack serve 
webpack-cli3的启动命令 npx webpack-dev-server

// 作用 : 自动编译 , 自动打开浏览器 , 自动刷新
// 特点 : 只会在内存中编译打包 , 不会有任何输出

// npm i webpack-dev-server -D  , 卸载安装webpack-cli指定版本,避免兼容性踩坑
// npm uninstall webpack-cli -D
// npm install  webpack-cli@3.3 -D

 devServer: {
    contentBase: path.resolve(__dirname, "build"),  // 打包输出的文件夹
    open: true,     // 自动打开浏览器
    port: 3003,     // 端口
    compress: true,  // gzip压缩代码体积
 	inline:true       // 实时刷新 
 }

天坑 : 默认启动指令: npx webpack-dev-server 可以在package.json中修改 , 默认指令可能在启动后不能自动刷新 , 还是修改比较好 ( 傻b配置坑过我一晚上)

// 在package.json中配置 

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --mode development"
  }
// 之后 用npm run dev就能启动devServer

8.1 报错: Cannot find module ‘webpack-cli/bin/config-yargs‘
// webpack最新版和webpack-cli4冲突 
// 卸载全局和本地的最新webpack-cli , 安装指定版本
npm uninstall webpack-cli -g
npm uninstall webpack-cli -D

npm install  webpack-cli@3.3 -D
npm install  webpack-cl3.3 -g

8.2 devServer 不能自动刷新
// 绝壁是在output里配置了publicPath:'./'
// 要么不写 , 要么写成下面

publicPath:"/dist/"    // 虚拟路径 , devServer运行时 webpack的虚拟路径

// 这里写的是什么 , 打包后的html文件引入的main.js路径前面就是什么
<script src="/dist/js/main.js"></script></body>

9. 各种打包报错
报错 1 : Cannot find module 'webpack-cli/bin/config-yargs'
webpack-cli版本冲突了 

先把webpack-cli卸载,再安装执行npm install webpack-cli@3 -D

报错 2 : 打包后控制台报错Refused to apply style from 'http://localhost:7099/iconfont/iconfont.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
// 在devServer的配置中加入 publicPath:'./'
  devServer: {
    contentBase: path.join(__dirname, "build"), // 项目构建打包输出的路径
    compress: true, // 启动gzip压缩 , 代码体积更小
    open: true, // 自动打开浏览器
    port: 7099,
    publicPath:'./'
  }

报错 3 : document is not defined
 webpack://guigu_webpack/./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js?:93  
    var style = document.createElement('style');

解决方案: src中的index.html文件引入了css样式 , 或者main.js入口文件 , 删掉即可

image-20201019142952699

报错 4. Automatic publicPath is not supported in this browser
// 输出配置output里面加上
	
publicPath:'./'

报错5. 打包后背景图片不显示
// 使用 mini-css-extract-plugin插件单独提取css后背景图不显示, 在options里加 publicPath: '../'
{ 
    loader: MiniCssExtractPlugin.loader,
     options:{
          publicPath: '../'
     	}
}
// 加在处理css/scss/less的配置中 , 不是加在处理img的配置中 , 我是傻逼

报错6. export default webpack_public_path + "others/e86c5939.less";
打包其他文件时 , 没有把对应文件的格式排除

10. 配置文件打包输出的路径
//  loader配置outputPath输出到文件夹 

1. css/less/scss的输出路径不需要配置文件夹路径 , 因为能打包进 build.js文件 ,不然报错
2. html-loader处理html中img标签引入的图片资源时 , 不用写outputPath , 不然报错

10.1 傻逼报错 configuration.output has an unknown property 'outputPath'
 Webpack has been initialized using a configuration object that does not match the API schema.
 - configuration.output has an unknown property 'outputPath'. These properties are valid:

要么是出口output中'path'写成了"outputPath
要么是打包css/scss/less的时候 options里写了outputPath

11. 每次打包前清理build文件夹的目录
// 注意: 尽量别用 ,devServer的时候 , 会把build里的文件都删了 , 但是又不打包输出

// 依赖 clean-webpack-plugin 插件
npm i --save-dev clean-webpack-plugin

// 解构 , 再plugins配置里调用
// 注意是解构 , 不是直接引用
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); 

 // 每次打包前清除一遍build目录下的文件夹
 // 在最新版的webpack中 ( )中不需要写里面的目标路径,会自动清除生成的文件夹,比如是build文件夹
 new CleanWebpackPlugin()

Ⅱ. 处理兼容和压缩

1. css相关处理
1.1 提取css到单独文件
// mini-css-extract-plugin 插件
// 将CSS提取为独立的文件的插件,对每个包含css的js文件都会创建一个CSS文件,支持按需加载css和sourceMap
npm i mini-css-extract-plugin -D

MiniCssExtractPlugin.loader 取代 style-loader , 提取 js中打包的各个css文件整合成单独文件 , 就不需要插入style标签中了

// 如果less/scss/css 样式都有的话 , 就得每个loader配置里面都替换

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

 use: [
          // { loader: "style-loader" },
          { loader: MiniCssExtractPlugin.loader }, // 注意不要写成了字符串
          { loader: "css-loader" },
          { loader: "sass-loader" }
      ]

// 在plugins中
 new MiniCssExtractPlugin({
      filename: "css/build.css" // 配置单独提取出来的css文件的路径以及名字
    })

1.2 css兼容处理及压缩
// 需要安装 postcss-loader  postcss-preset-env
/*
            css兼容性处理:postcss --> postcss-loader postcss-preset-env
                    帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式

        "browserslist": {
          // 开发环境 --> 设置node环境变量:process.env.NODE_ENV = development
          "development": [
            "last 1 chrome version",
            "last 1 firefox version",
            "last 1 safari version"
          ],
          // 生产环境:默认是看生产环境
          "production": [
            ">0.2%",
            "not dead",
            "not op_mini all"
          ]
        }
      */

- 新建postcss.config.js 配置文件 

  ```js
  module.exports = {
    plugins: [
      // 使用postcss插件
      require("postcss-preset-env")
    ]
  };

  • 在package.json中写browserslist的配置

    "browserslist": {
            
                  "development": [
                    "last 1 chrome version",
                    "last 1 firefox version",
                    "last 1 safari version"
                  	],
                 
                  "production": [
                    ">0.2%",
                    "not dead",
                    "not op_mini all"
                  ]
    }
    
    
  • 默认是看生产模式的browserslist配置 , 如果要改成开发模式 , 要设置nodejs环境变量

    // 设置nodejs环境变量
    process.env.NODE_ENV = 'development';
    
    
  • 完整代码

    {
        test: /\.css$/,
            use: [
              MiniCssExtractPlugin.loader,
              'css-loader', 'postcss-loader'
            ]
    }
    
    
    
  • 压缩css及其简单

    // 安装 optimize-css-assets-webpack-plugin , 然后直接在plugins里配置调用即可
    
    npm i optimize-css-assets-webpack-plugin -D 
    
    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
    
    // plugins里配置调用即可
      new OptimizeCssAssetsWebpackPlugin();
    
    

    项目上线前记得要压缩css代码

1.3 less/sass的兼容处理
/* 	
	postcss-loader 不能处理less/sass代码
	和css处理相同 , 只是要把postcss-loader , 放在 less-loader 和 css-loader中间 , 执行顺序		less-loader先转换成css , 再处理兼容
*/

{
    test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader', 'postcss-loader','less-loader'
        ]
}

1.4 复用 loader 处理css/sass/less兼容
const commonCssLoader =  [
          MiniCssExtractPlugin.loader,
          'css-loader', 'postcss-loader'
        ]

// 代替前面重复代码 

2. js语法检查 -- eslint
// 1. 注意 eslint只检查自己写的代码 ,第三方的库是不用检查的 , 所以要排除node_modules里的模块

// 2. 设置检查规则 , 在package.json中写eslintConfig 配置 , 继承airbnb-base的配置
/*
	"eslintConfig": {
						"root":true,
    					"extends": "airbnb-base"
  					}
*/

npm i eslint eslint-loader eslint-config-airbnb-base eslint-plugin-import -D
// eslint-config-airbnb-base 不包含react的插件

  • 代码 :

    // 开发调式时候忽略下一行的eslint检查 , 上线时候注意删掉
    // eslint-disable-next-line
    
    // loader如下配置
          {
            test: /\.js$/,
            exclude: /node_modules/, // 一定要排除, 不检查node_modules里的模块
            loader: "eslint-loader",
            options: {
              // 自动修复eslint的错误
              fix: true
            }
          }
    
    // package.json中如下配置 
    
        "eslintConfig": {
            "root":true,
            "extends": "airbnb-base"
          }
    
    
  
  ###### 2.1 报错 : Failed to load config "airbnb-base" to extend from.
  
  ![image-20201021202722429](C:\Users\Jiraiya\AppData\Roaming\Typora\typora-user-images\image-20201021202722429.png)
  
  ```js
  // 解决方法 
  
  在上面eslintConfig的配置中忘加 "root":true

image-20201021202722429

3. js兼容性处理 -- babel

推荐第三种方法 : 按需解决兼容

3.1 基础js兼容处理
// 使用 babel-loader @babel/preset-env @babel/core
npm i babel-loader @babel/preset-env @babel/core -D

// js兼容性处理
      {
        test: /\.js$/,
        exclude: /node_modules/, // 一定要排除, 不检查node_modules里的模块
        use: [
          {
            loader: "babel-loader",
            options: {
              // 预设: 指示babel做怎么样的兼容处理
              presets: ["@babel/preset-env"]
            }
          }
        ]
      }
      ```

缺点 : 只能处理基础的 js 语法 , 像promise等高级语法就不能转换了

3.2 全部 js兼容处理
// 极其简单 安装 @babel/polyfill 并引入即可
npm i @babel/polyfill -D 

import '@babel/polyfill'

缺点 : 只需解决部分兼容性问题 , 但是将所有兼容性代码全部引入 , 代码体积太大 , 一般不考虑

3.3 按需解决兼容
 npm i core-js babel-loader @babel/core  @babel/preset-env @babel/plugin-transform-runtime  -D 

// 使用此方案 , 就不能再用第二种方案的引入了 

 {
        test: /\.js$/,
        exclude: /node_modules/, // 一定要排除, 不检查node_modules里的模块
        use: [
          {
            loader: "babel-loader",
            options: {
              // 预设: 指示babel做怎么样的兼容处理
              presets: [
                [
                  "@babel/preset-env",
                  {
                    // 按需加载
                    useBuiltIns: "entry",
                    // 指定corejs版本
                    corejs: {
                      version: 3
                    },
                    // 指定兼容性做到哪个版本浏览器
                    targets: {
                      chrome: "60",
                      firefox: "60",
                      ie: "9",
                      safari: "10",
                      edge: "17"
                    }
                  }
                ]
              ],
              plugins: ["@babel/plugin-transform-runtime"]
            }
          }
        ]
 }

4. eslint-loader和babel-loader执行顺序
/*
	正常来讲, 一个文件只能被一个loader来处理.
	当一个文件要被多个loader处理的话 , 那么一定要指定loader的执行顺序: 先执行eslint , 后babel
	给eslint-loader加上 enforce: 'pre' 属性
*/
 {
        test: /\.js$/,
        exclude: /node_modules/, // 一定要排除, 不检查node_modules里的模块
        enforce: 'pre' ,         // 优先执行, 其他loader都往后稍稍
        loader: "eslint-loader",
        options: {
          // 自动修复eslint的错误
          fix: true
        }
      }

5. js和html的压缩
// js 
 改成production模式就能自动压缩js 

// html
 new HtmlWebpackPlugin({
      // 模版 : 将路径中的html文件作为模版复制 , 并添加引入打包输出的所有资源 css/js
      template: "./src/index.html",
      minify: {
          removeComments: true, // 清理注释
          collapseWhitespace: true // 清理空格
     	}
    })

Ⅲ. Webpack性能优化 (重点)

1. 开发环境性能优化
优化点 : 1.打包构建速度   2.代码调试
1.1 热模块替换 (HMR)
hot: true

  • 样式文件 : 默认可以使用HMR功能 , 因为style-loader内部实现了 , 所以开发环境可以用style-loader方便调式 , 生产环境就得把css文件单独提取出来再上线 , 不能使用style-loader

  • js 文件 : 默认不能使用HMR功能 --> 需要添加支持HMR的代码

    ```js
    

    // 首先要知道入口js文件是没办法做HMR优化的 , 入口文件js代码一旦改变,所有文件都会重新加载 // 添加代码如下 : if(module.hot){ // 一旦module.hot为true, 说明开启了HMR功能 ==> 让HMR功能代码生效 module.hot.accept("./print.js",function(){ // 方法会监听print.js文件的变化 , 一旦变化,其他模块不会重新打包构建 // 会执行callback print(); }) } ```

  • html 文件 : 默认不能使用HMR功能 , 同时会导致问题 , html文件不能热更新了~ (不用做HMR功能)

    解决:修改entry入口, 将html文件引入

1.2. source-map
devtool:'source-map'

// 

  • source-map : 1.内部 2.提示错误代码准确信息 和 源代码的错误位置
  • inline-source-map : 1.只生成一个内联的source-map 2.同上 hidden-source-map : 1.外部生成 2.不能追踪源代码错误 .只能提示到构建后代码的错误位置
  • eval--source-map : 1.内部 每一个文件都生成对应的source-map 2.提示错误代码准确信息 和 源代码的错误位置
  • nosource-source-map : 1.生成外部 2.错误代码准确信息,但是没有任何源代码信息
  • cheap-source-map : 1.生成外部 2.提示错误代码准确信息 和 源代码的错误位置 ,但只能精确到行, 不能精确到列
  • cheap-module--source-map : 1.生成外部 2. 同上 3. module会把loader的source map加进来
// 外部和内联的区别 : 1.外部生成了文件,内联没有  2.内联构建的速度更快

以上几种方案用法 :

  • 开发环境要求 : 速度快 , 调试更友好

    ---> eva-source-map / eval-cheap-module-source-map

  • 生产环境要求 : 源代码要不要隐藏 ? 调试要不要更友好 ?

    内联会让代码体积变大 , 所以一般不用内联

    ---> source-map / cheap-module--source-map

    如果需要隐藏源代码 , 就 nosource-source-map( 全部隐藏 ) / hidden-source-map( 只隐藏源代码, 会提示构建后代码错误信息 )

2. 生产环境性能优化
优化点 : 1.打包构建速度   2.代码运行性能   

2.1 oneOf
oneOf数组里的loader规则: 只会匹配一个 , 比如css文件匹配到css-loader以后 , 就不会在去查询匹配其他loader了 , 性能就更好

注意 : 数组里不能有两个loader处理同一种类型文件 , 比如eslint-loader , babel-loader不能同时放在里面 , 把eslint-loader提取到外面

2.2 缓存
  • babel缓存

    cacheDirectory: true
    
    
  • 文件资源缓存

    ​ hash 值命名后缀

    问题 : 因为 js和css构建时候生成唯一一个hash值 , 如果重新打包会使缓存失效

    ​ chunkhash: 根据chunk生成的hahs值 . 如果打包来源同一个chunk , 那么hash值就一样

    ​ contenthash:

Ⅳ. Webpack补充详解

1. entry 多入口
  • array : 只会形成一个chunk和一个bundle.js

    • 作用 : 只有在HMR功能中html热更新生效
  • object : 有几个入口文件就形成几个chunk , 输出几个bundle文件 ,此时的chunk名称就是 key

    entry: {
        a: "./src/test.js",          // 生成a.js
        b: "./src/index.js"          // 生成b.js
      },
    
    
    • 特殊用法 : 对象的value可以是一个数组 , 包含多个文件路径 , 将多个文件打包输出一个bundle
2. output
filename: 'js/[name].js'      // 指定名称 + 目录
publicPath:'/'                // 所有资源引入的公共路径前缀
chunkFilename: '[name]_chunk.js'  // 对非入口chunk进行重命名
library: '[name]'             // 将打包后的js文件代码赋值给一个变量并抛出(变量名就是[]中的name)
libraryTarget: 'window'     // 变量名添加到哪个上

3. loader
exclude: /node_modules/      // 排除
include: path.resolve(__dirname,'src')   // 只检查src文件夹
enforce: 'pre'               // 优先执行 , post -> 最后执行 , 其他就中间执行

4. resolve

不是 path.resolve , 和核心配置同级

resolve: {
    // 配置路径别名 , 简写路径 比如 src --> @
    alias: {
        $css: path.resolve(__dirname,'src/css')
    },
    // 配置省略文件路径的后缀名
    extensions: ['.js','.json'],
    // 直接告诉 webpack去哪个目录找解析模块
    modules: [path.resolve(__dirname,'../../node_modules'),"node_modules"]
}

5. devServer
watchContentBase: true,      // 监视contentBase下的文件 , 一旦变动就reload
watchOptions:{
    ignored: /node_modules/  // 忽略文件
},
clientLoginLevel: 'none',    // 不显示启动服务器日志信息
quiet: true ,                // 除了一些基本启动信息以外 , 其他内容都不要显示
overlay: false,               // 如果出错了不要全屏提示
proxy:{                       // 反向代理
    '/api':{
        // 一旦devServer接收到'/api'开头的请求,就把请求转发到如下服务器
        target: 'http://localhost:3000',
        // 发送请求时 , 请求路径重写 : 将'/api/xxx' --> '/xxx'
        pathRewrite: {
            '^/api':''
        }
    }
}

6. opnimization

必须在生产环境才有意义