webpack配置详解

webpack 基础配置总结

配置文件基本结构

module.exports = {
 entry: "./src/index.js", //打包⼊⼝⽂件
 output: "./dist", //输出结构
 mode: "production", //打包环境 值有 production development none
 module: {
 rules: [
 //loader模块处理
   {
     test: /\.css$/,
     use: "style-loader"
    }
  ]
 },
 plugins: [new HtmlWebpackPlugin()] //插件配置
};

entry

指定webpack打包入口文件: webpack执行构建的第一步将从entry开始。

// 单入口文件 spa应用 本质是个字符串
entry: {
  main: './src/index.js'
}
// 上面这段相当于 entry: './src/index.js'

// 多入口 entry是个对象
entry: {
  index: './src/index.js',
  login: './src/login.js'
}

output

打包转换后的文件输出到磁盘位置: 输出的结果,在webpack经过一系列处理并得出最终想要的代码输出结果。

output: {
  filename: 'bundle.js',
  path: path.resolve(__dirname, 'dist') // 输出文件到磁盘的目录,必须是绝对路径,需要借助node path模块来输出到绝对路径
}
// 多入口处理
output: {
  filename: '[name][chunkhash:8].js', // 利用[name]占位符,动态获取文件名来原名输出chunkhash:8 获取hash值的前8位拼接在文件名后
  path: path.resolve(__dirname, 'dist') // 输出文件
}

mode

mode 用来指定当前的构建环境

  • production
  • development
  • none
    设置mode可以自动触发webpack内置的函数,达到优化的效果
    webpack配置详解

loader

webpack默认只支持.json和.js模块 不支持 不认识其他格式的模块
项目里面包含其他模块文件 (.css,,jsx) 会需要用到loader来做转换。
loader 模块解析器 转换器,用于吧模块原内容按照需求转换成新的内容
webpack 是模块打包工具,而模块不仅仅是js,还可以是css 图片 或者其他格式,但是webpack处理不了其他的,这时候就只能借用loader来处理。loader执行顺序 从右到左,从下到上,⼀个loader只处理⼀件事情
常见的loader

  • style-loader
  • css-loader
  • less-loader
  • sass-loader
  • ts-loader
  • babel-loader
  • file-loader
  • eslint-loader

module

在webpack里 一切皆模块,一个模块对应着一个文件,webpack会从配置的entry开始递归找出所有依赖的模块。
当webpack处理到不认识的模块时,需要在webpack中的module对象里进行配置,当检测到是什么格式的模块,需要使用到什么loader来处理。

module: {
  rules: [
    {
      test: '/\.jsx$/', // 指定匹配规则
      use: 'xxx-loder', // 用什么loader来处理
    },
    {
      test: '/\.css$/', // 指定匹配规则
      use: 'xxx-loder', // 用什么loader来处理
    }
  ]
}

使用场景:当我们需要把源代码里面的某些文件打包挪移到根目录里面,可以使用file-loader来处理,txt,svg,excel,图片文件等
先安装loader npm install file-loader -D

module: {
 rules: [
 {
   test: /\.(png|jpe?g|gif)$/,
   //use使⽤⼀个loader可以⽤对象,字符串,两个loader需要⽤数组
   use: {
     loader: "file-loader",
     // options额外的配置,⽐如资源名称
     options: {
     // placeholder 占位符 [name]⽼资源模块的名称
     // [ext]⽼资源模块的后缀
     // https://webpack.js.org/loaders/fileloader#placeholders
     name: "[name]_[hash].[ext]",
     //打包后的存放位置
     outputPath: "images/"
    }
   }
  }
 ]
},

url-loader file-loader的加强版本

url-loader内部使⽤了file-loader,所以可以处理file-loader所有的事情,但是遇到jpg格式的模块,会把该图⽚转换成base64格式字符串,并打包到js⾥。对⼩体积的图⽚⽐较合适,⼤图⽚不合适。

// npm install url-loader -D

module: {
  rules: [
   {
     test: /\.(png|jpe?g|gif)$/,
     use: {
     loader: "url-loader",
     options: {
     name: "[name]_[hash].[ext]",
     outputPath: "images/",
     //⼩于2048,才转换成base64
     limit: 2048
    }
   }
  }
 ]
},

css样式处理
css-loader 分析css模块之间的关系,并合成一个css, style-loader会把css-loader生成的内容,以style标签包裹的形式然后挂载到页面的header标签里面

// npm install style-loader css-loader -D

// 写法一
  {
    test: /\.css$/,
    use: ["style-loader", "css-loader"]
  }
// 写法二
  {
   test:/\.css$/,
   use:[{
   loader:"style-loader",
   options: {
    injectType: "singletonStyleTag" // 将所有的style标签合并成⼀个
   }
 },"css-loader"]
}

less样式处理
需要使用less-laoder把less语法转换成css

{
  test: /\.scss$/,
  use: ["style-loader", "css-loader", "less-loader"]
}

样式⾃动添加前缀(-webkit(前缀)-font-smoothing):

// npm i postcss-loader autoprefixer -D
// 新建postcss.config.js 文件

// webpack.config.js
{
 test: /\.css$/,
 use: ["style-loader", "css-loader", "postcss-loader"]
},

//postcss.config.js
module.exports = {
 plugins: [
   require("autoprefixer")({
   overrideBrowserslist: ["last 2 versions", ">1%"]
   })
 ]
};

Plugins

作用于webpack打包整个过程,webpack的打包过程是有(生命周期概念)钩子的。plugin可以便在webpack运行到某个阶段的时候,帮你做一些事情,类似生命周期的概念。
plugin 扩展插件,在webpack构建流程中的特定时机注入拓展逻辑来改变构建结果或者你想要做的事情。作用于整个构建过程。
HtmlWebpackPlugin
HtmlWebpackPlugin会在打包结束后,自动生成一个html文件,并把大版生成的js模块引入到该html中。

// npm install --save-dev html-webpack-plugin

// 可配置信息
title: ⽤来⽣成⻚⾯的 title 元素

filename: 输出的 HTML ⽂件名,默认是 index.html, 也可以直接配置带有⼦⽬录。

template: 模板⽂件路径,⽀持加载器,⽐如html!./index.html

inject: true | 'head' | 'body' | false ,注⼊所有的资源到特定的 template 或者 templateContent 中,如果设置为
true 或者 body,所有的 javascript 资源将被放置到 body 元素的底部,'head' 将放置到 head 元素中。

favicon: 添加特定的 favicon 路径到输出的 HTML ⽂件中。

minify: {} | false , 传递 html-minifier 选项给 minify输出

hash: true | false, 如果为 true, 将添加⼀个唯⼀的

webpack 编译 hash 到所有包含的脚本和 CSS ⽂件,对于解除cache 很有⽤。

cache: true | false,如果为 true, 这是默认值,仅仅在⽂件修改之后才会发布⽂件。

showErrors: true | false, 如果为 true, 这是默认值,错误信息会写⼊到 HTML ⻚⾯中

chunks: 允许只添加某些块 (⽐如,仅仅 unit test 块)

chunksSortMode: 允许控制块在添加到⻚⾯之前的排序⽅式,⽀持的值:'none' | 'default' | {function}-default:'auto'

excludeChunks: 允许跳过某些块,(⽐如,跳过单元测试的块)

// 使用

const path = require("path");
const htmlWebpackPlugin = require("html-webpackplugin");

module.exports = {
  ...
  plugins: [
  new htmlWebpackPlugin({
  title: "My App",
  filename: "app.html",
  template: "./src/index.html"
  })
 ]
};

//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=devicewidth, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
 <title><%= htmlWebpackPlugin.options.title %>
</title>
  </head>
  <body>
   <div id="root"></div>
  </body>
</html>

clean-webpack-plugin
每次打包都会清除上一次打包的产出

// clean-webpack-plugin

const { CleanWebpackPlugin } = require("cleanwebpack-plugin");

plugins: [
 new CleanWebpackPlugin()
]

mini-css-extract-plugin
生成单独的css文件

const MiniCssExtractPlugin = require("mini-cssextract-plugin");
{
  test: /\.css$/,
  use: [MiniCssExtractPlugin.loader, "css-loader"]
}
new MiniCssExtractPlugin({
 filename: "[name][chunkhash:8].css"
})

sourceMap

源代码与打包后的代码的映射关系,通过sourceMap定位到源代码,在dev模式中,默认开启。关闭的话 可以在配置文件里 devtool:"none"

// devtool 可配置的值有

eval:速度最快,使⽤eval包裹模块代码,
source-map: 产⽣.map⽂件
cheap:较快,不包含列信息
Module:第三⽅模块,包含loader的sourcemap(⽐如jsx to js ,babel的sourcemap)
inline: 将.map作为DataURI嵌⼊,不单独⽣成.map⽂件

配置推荐:

devtool:"cheap-module-eval-source-map",// 开发环境配置
//线上不推荐开启
devtool:"cheap-module-source-map", // 线上⽣成配置

WebpackDevServer

热更新

// npm install webpack-dev-server -D

// 修改package.json

"scripts": {
  "start": "webpack-dev-server"
 },
 
// 在webpack.config.js配置:
devServer: {
   contentBase: "./dist",
   open: true,
   port: 8081
 },
 
// 启动 npm run start

启动服务后,会发现不会生成dist目录,这是因为devServer把打包后的模块不会再放到dist目录下,二是放到了内存中,从而提升速度。

mock数据及跨域问题
启动服务器 mock接口

// npm i express -D

// 创建⼀个server.js

//server.js

const express = require('express')
const app = express()
app.get('/api/info', (req,res)=>{
 res.json({
 name: '张三',
 age: 22,
 msg: 'hello world'
 })
})
app.listen('8081')

// 启动 node server.js

// 访问 http:// localhost:8081/api/info

使用axios发起请求

// npm i axios -D

//index.js
import axios from 'axios'
axios.get('http://localhost:9092/api/info').then(res=>{
 console.log(res)
})
// 此时会有跨域的问题, 在webpack.config.js 文件中进行处理

proxy: {
  "/api": {
     target: "http://localhost:9092"
   }
}

// 修改index.js

axios.get("/api/info").then(res => {
  console.log(res);
});

Babel处理ES6

Babel是JavaScript编译器,能将ES6代码转换成ES5代码,让我们开发过程中放⼼使⽤JS新特性⽽不⽤担⼼兼容性问题。并且还可以通过插件机制根据需求灵活的扩展。

Babel在执⾏编译的过程中,会从项⽬根⽬录下的.babelrc JSON⽂件中读取配置。没有该⽂件会从loader的options地⽅读取配置。

//index.js

const arr = [new Promise(() => {}), new Promise(() => {})];
  arr.map(item => {
    console.log(item);
});

// npm i babel-loader @babel/core @babel/preset-env -D
1.babel-loader是webpack 与 babel的通信桥梁,不会做把es6转成es5的⼯作,转换这部分⼯作需要⽤到@babel/preset-env来做
2.@babel/preset-env⾥包含了es,6,7,8转es5的转换规则

// Webpack.config.js
{
   test: /\.js$/,
   exclude: /node_modules/, // exclude 排除不编译部分 include 包含
   use: {
     loader: "babel-loader",
     options: {
       presets: ["@babel/preset-env"]
     }
   }
}

通过上⾯的⼏步 还不够,默认的Babel只⽀持let等⼀些基础的特性转换,Promise等⼀些还有转换过来,这时候需要借助@babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性。
@babel/polyfill
以全局变量的⽅式注⼊进来的。windows.Promise,它会造成全局对象的污染。

// npm install --save @babel/polyfill

按需引入
修改Webpack.config.js

options: {
   presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          edge: "17",
          firefox: "60",
          chrome: "67",
          safari: "11.1"
       },
       corejs: 2,//新版本需要指定核⼼库版本
       useBuiltIns: "usage"//按需注⼊
      }
    ]
  ]
}

useBuiltIns选项是babel 7的新功能,这个选项告诉babel如何配置@babel/polyfill。 它有三个参数可以使⽤: ①entry: 需要在webpack的⼊⼝⽂件⾥import “@babel/polyfill"⼀次。babel会根据你的使⽤情况导⼊垫⽚,没有使⽤的功能不会被导⼊相应的垫⽚。 ②usage: 不需要import,全⾃动检测,但是要安装@babel/polyfill。(试验阶段) ③false: 如果你import”@babel/polyfill",它不会排除掉没有使⽤的垫⽚,程序体积会庞⼤(不推荐)。

babelrc⽂件: 新建.babelrc⽂件,把options部分移⼊到该⽂件中,就可以了

//.babelrc
{
   presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          edge: "17",
          firefox: "60",
          chrome: "67",
          safari: "11.1"
       },
       corejs: 2,//新版本需要指定核⼼库版本
       useBuiltIns: "usage"//按需注⼊
      }
    ]
  ]
}

//webpack.config.js

{
  test: /\.js$/,
  exclude: /node_modules/,
  loader: "babel-loader"
}

配置React打包环境

// npm install react react-dom --save
编写react代码:

//index.js
import React, { Component } from "react";
import ReactDom from "react-dom";
class App extends Component {
 render() {
 return <div>hello world</div>;
 }
}
ReactDom.render(<App />,
document.getElementById("app"));

安装babel与react转换的插件:
// npm install --save-dev @babel/preset-react

在babelrc⽂件⾥添加:

//.babelrc
{
   presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          edge: "17",
          firefox: "60",
          chrome: "67",
          safari: "11.1"
       },
       corejs: 2,//新版本需要指定核⼼库版本
       useBuiltIns: "usage"//按需注⼊
      }
    ],
    "@babel/preset-react" // react 编译
  ]
}

多⻚⾯打包通⽤⽅案

entry:{
 index:"./src/index",
 list:"./src/list",
 detail:"./src/detail"
}
new htmlWebpackPlugins({
 title: "index.html",
 template: path.join(__dirname, "./src/index/index.html"),
 filename:"index.html",
 chunks:[index]
})

⽂件监听

轮询判断⽂件的最后编辑时间是否变化,某个⽂件发⽣了变化,并不会⽴刻告诉监听者,先缓存起来

webpack开启监听模式,有两种
1.启动webpack命令式 带上--watch 参数,启动监听后,需要⼿动刷新浏览器

scripts:{
 "watch":"webpack --watch"
}

2.在配置⽂件⾥设置 watch:true

 watch: true, //默认false,不开启
  //配合watch,只有开启才有作⽤
  watchOptions: {
  //默认为空,不监听的⽂件或者⽬录,⽀持正则
  ignored: /node_modules/,
  //监听到⽂件变化后,等300ms再去执⾏,默认300ms,
  aggregateTimeout: 300,
  //判断⽂件是否发⽣变化是通过不停的询问系统指定⽂件有没有变化,默认每秒问1次
  poll: 1000 //ms
 }

配置别名 resolve.alias

alias: {
 "@": path.join(__dirname, "./pages"),
 "react": path.resolve(__dirname, "./node_modules/react/umd/react.production.min.js"),
 "react-dom": path.resolve(__dirname, "./node_modules/react-dom/umd/react-dom.production.min.js")
}

resolve.extensions配置

resolve.extensions在导⼊语句没带⽂件后缀时,webpack会⾃动带上后缀后,去尝试查找⽂件是否存在。

默认值: extensions:['.js','.json','.jsx','.ts']

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

CDN通过将资源部署到世界各地,使得⽤户可以就近访问资源,加快访问速度。要接⼊CDN,需要把⽹⻚的静态资源上传到CDN服务上,在访问这些资源时,使⽤CDN服务提供的URL。

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

压缩css

借助 optimize-css-assets-webpack-plugin
借助cssnano

npm install cssnano -D
npm i optimize-css-assets-webpack-plugin -D

// webpack.config.js

const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
new OptimizeCSSAssetsPlugin({
  cssProcessor: require("cssnano"), //引⼊cssnano配置压缩选项
  cssProcessorOptions: {
  discardComments: { removeAll: true }
 }
})

压缩HTML

借助html-webpack-plugin

new htmlWebpackPlugin({
   title: "web",
   template: "./index.html",
   filename: "index.html",
   minify: {
   // 压缩HTML⽂件
    removeComments: true, // 移除HTML中的注释
    collapseWhitespace: true, // 删除空⽩符与换⾏符
    minifyCSS: true // 压缩内联css
  }
}),

development vs Production模式区分打包

// npm install webpack-merge -D
const merge = require("webpack-merge")
const commonConfig = require("./webpack.common.js") //基本配置
const devConfig = {
 ...
}
module.exports = merge(commonConfig,devConfig)  // 合并配置文件

//package.js
"scripts":{
 "dev":"webpack-dev-server --config ./build/webpack.dev.js",
 "build":"webpack --config ./build/webpack.prod.js"
}

基于环境变量区分

// 借助cross-env    npm i cross-env -D

// package.json
"test": "cross-env NODE_ENV=test webpack --config ./webpack.config.test.js",


// 在webpack.config.js⾥拿到参数  process.env.NODE_ENV

//外部传⼊的全局变量
module.exports = (env)=>{
 if(env && env.production){
 return merge(commonConfig,prodConfig)
 }else{
 return merge(commonConfig,devConfig)
 }
}
//外部传⼊变量
scripts:" --env.production"

tree Shaking

webpack2.x开始⽀持 tree shaking概念,顾名思义,“摇树”,清除⽆⽤ css,js(Dead Code) Js tree shaking只⽀持ES module的引⼊⽅式, Dead Code ⼀般具有以下⼏个特征。

  • 代码不会被执⾏,不可到达
  • 代码执⾏的结果不会被⽤到
  • 代码只会影响死变量(只写不读)
// Css tree shaking
npm i glob-all purify-css purifycss-webpack --save-dev

const PurifyCSS = require('purifycss-webpack')
const glob = require('glob-all')
  plugins:[
  // 清除⽆⽤ css
    new PurifyCSS({
     paths: glob.sync([
     // 要做 CSS Tree Shaking 的路径⽂件
     path.resolve(__dirname, './src/*.html'), // 请注意,我们同样需要对 html ⽂件进⾏ tree shaking
     path.resolve(__dirname, './src/*.js')
    ])
  })
]

JS tree shaking

//expo.js
export const add = (a, b) => {
 return a + b;
};
export const minus = (a, b) => {
 return a - b;
};
//index.js
import { add } from "./expo";
add(1, 2);

//webpack.config.js
optimization: {
 usedExports: true // 哪些导出的模块被使⽤了,再做打包
}

只要mode是production就会⽣效,develpoment的tree shaking是不⽣效的,因为webpack为了⽅便你的调试可以查看打包后的代码注释以辨别是否⽣效。⽣产模式不需要配置,默认开启.

代码分割 code Splitting

单⻚⾯应⽤spa:打包完后,所有⻚⾯只⽣成了⼀个bundle.js, 代码体积变⼤,不利于下载, 没有合理利⽤浏览器资源。

多⻚⾯应⽤mpa:如果多个⻚⾯引⼊了⼀些公共模块,那么可以把这些公共的模块抽离出来,单独打包。公共代码只需要下载⼀次就缓存起来了,避免了重复下载。

其实code Splitting概念 与 webpack并没有直接的关系,只不过webpack中提供了⼀种更加⽅便的⽅法供我们实现代码分割。

optimization: {
  splitChunks: {
    chunks: "all", // 所有的 chunks 代码公共的部分分离出来成为⼀个单独的⽂件
   },
},

其他可选配置项

optimization: {
 splitChunks: {
 chunks: 'async',//对同步 initial,异步 async,所有的模块有效 all
 minSize: 30000,//最⼩尺⼨,当模块⼤于30kb
 maxSize: 0,//对模块进⾏⼆次分割时使⽤,不推荐使⽤
 minChunks: 1,//打包⽣成的chunk⽂件最少有⼏个chunk引⽤了这个模块
 maxAsyncRequests: 5,//最⼤异步请求数,默认5
 maxInitialRequests: 3,//最⼤初始化请求书,⼊⼝⽂件同步请求,默认3
 automaticNameDelimiter: '-',//打包分割符号
 name: true,//打包后的名称,除了布尔值,还可以接收⼀个函数function
 cacheGroups: {//缓存组
 vendors: {
 test: /[\\/]node_modules[\\/]/,
 name:"vendor", // 要缓存的 分隔出来的 chunk 名称
 priority: -10//缓存组优先级 数字越⼤,优先级越⾼
 },
 other:{
 chunks: "initial", // 必须三选⼀: "initial" | "all" | "async"(默认就是async)
 test: /react|lodash/, // 正则规则验证,如果符合就提取 chunk,
 name:"other",
 minSize: 30000,
 minChunks: 1,
 },
 default: {
 minChunks: 2,
 priority: -20,
 reuseExistingChunk: true//可设置是否重⽤该chunk
 }
 }
 }
 }

Scope Hoisting

作⽤域提升(Scope Hoisting)是指 webpack 通过 ES6 语法的静态分析,分析出模块之间的依赖关系,尽可能地把模块放到同⼀个函数中。下⾯通过代码示例来理解

// hello.js
export default 'Hello, Webpack';
// index.js
import str from './hello.js';
console.log(str);

打包后, hello.js 的内容和 index.js 会分开

通过配置 optimization.concatenateModules=true`:开启 Scope Hoisting

// webpack.config.js
module.exports = {
  optimization: {
  concatenateModules: true
 }
};

我们发现hello.js内容和index.js的内容合并在⼀起了!所以通过 Scope Hoisting 的功能可以让Webpack 打包出来的代码⽂件更⼩、运⾏的更快。

使⽤happypack并发执⾏任务

运⾏在 Node.之上的Webpack是单线程模型的,也就是说Webpack需要⼀个⼀个地处理任务,不能同时处理多个任务。 Happy Pack 就能让Webpack做到这⼀点,它将任务分解给多个⼦进程去并发执⾏,⼦进程处理完后再将结果发送给主进程。从⽽发挥多核 CPU 电脑的威⼒。

// npm i -D happypack

var happyThreadPool = HappyPack.ThreadPool({ size: 5 });
 //const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
// webpack.config.js
rules: [
   {
     test: /\.jsx?$/,
     exclude: /node_modules/,
     use: [
       {
        // ⼀个loader对应⼀个id
        loader: "happypack/loader?id=babel"
       }
     ]
   },
   {
     test: /\.css$/,
     include: path.resolve(__dirname, "./src"),
     use: ["happypack/loader?id=css"]
   },
]

//在plugins中增加
plugins:[
   new HappyPack({
   // ⽤唯⼀的标识符id,来代表当前的HappyPack是⽤来处理⼀类特定的⽂件
   id:'babel',
   // 如何处理.js⽂件,⽤法和Loader配置中⼀样
   loaders:['babel-loader?cacheDirectory'],
   threadPool: happyThreadPool,
   }),
   new HappyPack({
   id: "css",
   loaders: ["style-loader", "css-loader"]
   }),
]
上一篇:前端非框架开发问题集


下一篇:2. vue脚手架生成的项目结构