webpack学习总结

561 阅读6分钟

一基础概念和配置

1.1 Entry 入口

入口配置有如下四种情况:

(1) 单入口单文件,entry是string类型。entry: './src/index.js'打包形成一个chunk。 输出一个bundle文件。此时chunk的名称默认是 main

(2) 单入口多文件,entry是array类型。entry: ['./src/index.js', './src/count.js']所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。

(3)多入口单文件,entry是object类型。entry: { index: './src/index.js', add: './src/add.js'}有几个入口文件就形成几个chunk,输出几个bundle文件此时chunk的名称是 key

(4)多入口多文件,entry是object类型。

entry: {
    // 所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
    index: ['./src/index.js', './src/count.js'], 
    // 形成一个chunk,输出一个bundle文件。
    add: './src/add.js'
  },

1.2 output 出口

output: {
// 文件名称(目录/指定名称)
filename: 'js/[name].js',
// 输出文件目录(将来所有资源输出的公共目录)resolve指向根目录
path: resolve(__dirname, 'build'),
// 所有资源引入公共路径前缀 --> 'imgs/a.jpg' --> '/imgs/a.jpg',在文件打包后文件引入时会在前面添加“/”
publicPath: '/',
chunkFilename: 'js/[name]_chunk.js', // 非入口 chunk 的名称,在文件中用impout()方法引入
// library: '[name]', // 整个库向外暴露的变量名
// libraryTarget: 'window' // 变量名添加到哪个上 browser
// libraryTarget: 'global' // 变量名添加到哪个上 node
// libraryTarget: 'commonjs'
}

1.3 loader

单个loader

module: {
    rules: [
      {
        test: /\.js$/,
        // 排除node_modules下的js文件
        exclude: /node_modules/,
        // 只检查 src 下的js文件
        include: resolve(__dirname, 'src'),
        // 优先执行
        enforce: 'pre',
        // 延后执行
        // enforce: 'post',
        // 单个loader用loader
        loader: 'eslint-loader',
        //修改配置
        options: {}
      },
      {
        // 以下配置只会生效一个,当文件匹配成功后就不会匹配后边的loader,如果一个js文件要匹配两个配置,可以将一个写在如上方
        oneOf: []
      }
    ]
  }

多个loader,默认配置

module: {
    rules: [
      // loader的配置
      { test: /\.css$/,
        // 多个loader用use
        use: ['style-loader', 'css-loader']
      }
    ]
  }

多个loader,额外配置

{
	test: /\.css$/,
    //use是一个数组,其中每一项可以是字符串也可以是对象。
	use: [
		'css-loader',
		{loader: 'postcss-loader',
			options: {
				ident: 'postcss',
				plugins: () => [
					// postcss 的插件
					require('postcss-preset-env')()
				]
			}
		}
	]
}

1.4 resolve 解析模块的规则

// 解析模块的规则
resolve: {
// 配置解析模块路径别名: 优点简写路径 缺点路径没有提示
alias: {
$css: resolve(__dirname, 'src/css')
},
// 配置省略文件路径的后缀名,如果这样写在同一个路径下不能有同名的js和css文件。
extensions: ['.js', '.json', '.jsx', '.css'],
// 告诉 webpack 解析模块是去找哪个目录
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
}

1.5 使⽤externals优化cdn静态资源(例如jquery)

在HTML文件引入jquer文件

<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>

在webpack.config.js文件中

module.exports = {
    //...
    externals: {
        //jquery通过script引⼊之后,全局中即有了 jQuery 变量
        'jquery': 'jQuery'
    }

}

1.6 使⽤静态资源路径publicPath(CDN)

##webpack.config.js
output:{
    publicPath: '//cdnURL.com', //指定存放JS⽂件的CDN地址
}

1.7 devserver

devServer: {
    // 运行代码的目录
    contentBase: resolve(__dirname, 'build'),
    // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
    watchContentBase: true,
    watchOptions: {
      // 忽略文件
      ignored: /node_modules/
    },
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 5000,
    // 域名
    host: 'localhost',
    // 自动打开浏览器
    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': ''
        }
      }
    }
  }

二、 开发环境配置

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: [
      // loader的配置
      {
        // 处理less资源
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        // 处理css资源
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          // 关闭es6模块化
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
        // 处理html中img资源
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        // 处理其他资源
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true
  }
};

三 生产环境配置

3.1 css文件处理

(1) 提取 css 成单独文件,下载插件npm install --save-dev mini-css-extract-plugin

配置

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  //省略出入口和模式配置
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 创建style标签,将样式放入
          // 'style-loader', 
          // 这个loader取代style-loader。作用:提取js中的css成单独文件
          MiniCssExtractPlugin.loader,
          // 将css文件整合到js文件中
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      // 对输出的css文件进行重命名
      filename: 'css/built.css'
    })
  ],
};

(2) css 兼容性处理 ,下载 loader npm install --save-dev postcss-loader postcss-preset-env

配置

 //简化代码只写了css的loader其他配置不变。
{    
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          // 使用loader的默认配置
          // 'postcss-loader',
          // 修改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"
	]
}  

(3) 压缩 css ,下载 插件 npm install --save-dev optimize-css-assets-webpack-plugin

配置

//简化代码只写了改变的
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
	plugins: [
		// 压缩 css
		new OptimizeCssAssetsWebpackPlugin()
	],
};

3.2 js处理

(1)js语法检查 下载安装包npm install --save-dev eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import

配置

////简化代码只写了改变的js loader配置
/*
语法检查: eslint-loader eslint
注意: 只检查自己写的源代码, 第三方的库是不用检查的
设置检查规则:
package.json 中 eslintConfig 中设置~
"eslintConfig": {"extends": "airbnb-base"}
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": {
		"browser": true
	}
}  

(2)js 兼容性处理 下载安装包npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/polyfill core-js

配置

//简化代码只写了改变的js loader配置
{
	test: /\.js$/,
	exclude: /node_modules/,
	loader: 'eslint-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'
                }
              }
            ]
          ]
      }
}

3.3 html

//简化代码只写了改变的html plugins配置
plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      // 压缩html代码
      minify: {
        // 移除空格
        collapseWhitespace: true,
        // 移除注释
        removeComments: true
      }
    })
  ]

3.4 生产环境配置

const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    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: /\.css$/,
        use: [...commonCssLoader]
      },
      {
        test: /\.less$/,
        use: [...commonCssLoader, 'less-loader']
      },
      /*
        正常来讲,一个文件只能被一个loader处理。
        当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
          先执行eslint 在执行babel
      */
      {
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                useBuiltIns: 'usage',
                corejs: {version: 3},
                targets: {
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ]
        }
      },
      {
        test: /\.(jpg|png|gif)/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          outputPath: 'imgs',
          esModule: false
        }
      },
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        exclude: /\.(js|css|less|html|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

四、优化

4.1 HMR

在devServer中开启热更新hot: true

4.2 source-map 错误提示

module.exports = {
  mode: 'production',
  devtool: 'eval-source-map'//开发环境用:eval-source-map;生产环境用:source-map 
}

4.3 oneOf

一个文件只能被一个 loader 处理。

4.4 缓存

在loader的options配置中加入cacheDirectory: true

/*
  缓存:
    babel缓存
      cacheDirectory: true
      --> 让第二次打包构建速度更快
    文件资源缓存
      hash: 每次wepack构建时会生成一个唯一的hash值。
        问题: 因为js和css同时使用一个hash值。
          如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
      chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
        问题: js和css的hash值还是一样的
          因为css是在js中被引入的,所以同属于一个chunk
      contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样    
      --> 让代码上线运行缓存更好使用
*/
module.exports = {
  module: {
    rules: [
      {
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
             //省略 presets 配置
              // 开启babel缓存
              // 第二次构建时,会读取之前的缓存
              cacheDirectory: true
            }
          }
    ]
  },
 
};

4.5 tree shaking

树摇,去除无用代码

前提:1. 必须使用ES6模块化 2. 开启production环境; 作用: 减少代码体积

在package.json中配置 "sideEffects": false 所有代码都没有副作用(都可以进行tree shaking) 问题:可能会把css / @babel/polyfill (副作用)文件干掉,所以要是使用下面这种情况。 "sideEffects": [".css", ".less"] 意思是css文件和less文件不进行树摇。

module.exports = {mode: 'production'}

4.6 code split 代码分割

(1) 多入口

entry: {
	// 多入口: 有一个入口, 最终输出就有一个 bundle
	index: './src/js/index.js',
	test: './src/js/test.js'
},

(2) optimization 将 node_modules 中代码单独打包一个 chunk 最终输出

module.exports = {
  /*
    1. 可以将node_modules中代码单独打包一个chunk最终输出
    2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
  */
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  mode: 'production'
};

(3) js文件中使用import()方法引入模块,打包形成单独文件。

4.7 懒加载预加载

在我们的js文件中使用import()方法引入模块,使用时才加载。预加载:在懒加载的前提下加上webpackPrefetch: true

console.log('index.js文件被加载了~');

// import { mul } from './test';

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

4.8 pwa

渐进式网络开发应用程序(离线可访问)

4.9 多线程打包

4.10 externals 拒绝打包

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')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'production',
  externals: {
    // 拒绝jQuery被打包进来
    jquery: 'jQuery'
  }
};