webpack基础知识

1.webpack是什么?

  • 最开始是一个基于node的js打包工具。
  • 简单来说,webpack是一个(bundle)模块打包工具,处理模块之间的依赖同时生成对应模块的静态资源。

2.为什么使用webpack?

  • 项目中html文件可能会引用十几个js文件,而且得按照一定的顺序,js文件之间存在依赖关系,同时对于es6+等语法,less,sass等css预处理都不能很好的解决。此时需要借助一些工具处理这些问题

打包例子如下:

// 初始化
npm init
// 安装webpack,webpack-cil的作用是可以在命令行内使用webpack指令或者npx webpack指令
npm install webpack webpack-cli --save-dev
  • 文件目录如下:
1-1
  • 在index.html引入打包后的main.js文件
<script src="../dist/main.js"></script> <!--这是打包之后默认生成的main.js文件-->
  • header.js导出一个头部模块
function header() {
  const el = document.createElement('div')
  el.innerText = '这是导航模块'
  document.body.appendChild(el)
}
 module.exports = header
  • content.js导出内容模块
function content() {
  const el = document.createElement('div')
  el.innerText = '这是内容模块'
  document.body.appendChild(el)
}
 module.exports = content
  • 将这两个模块引入index.js
var Header = require('./header.js');
var Content = require('./content.js');

new Header()
new Content()
  • 执行打包命令
//  npx 是node 5.2版本以上自带的        并且查看局部版本号的
 npx webpack src/index.js
 // 直接使用webpack 需要全局安装webpack,且可以修改打包输出文件名
 webpack src/index.js --output dist/bundle.js  
  • 页面效果如下


    1-2

上述操作就相当于我们把header.js模块和content.js模块合并到了index.js模块,对这合并后的index.js模块打包成main.js,然后供index.html引用,这就是webpack的打包原理。

webpack打包原理

它会把项目当成一个整体,通过一个入口文件(index.js),从这个文件开始找到项目中所有依赖,然后在内部创建一个依赖图(dependencygraph),用于映射到项目需要的每个模块,最后打包成一个或多个浏览器可识别的js文件。

现在已经成功的使用webpack进行打包啦,但是在终端输入命令,感觉好烦哦,还好山人自有妙计,且往下看看。

3.通过配置文件使用webpack

  1. 在当前根目录下新建配置文件webpack.config.js,从入口和出口配置
// 输出路径,必须是绝对路径,这里需要使用node的核心模块path,调用模块的resolve方法
const path = require('path')
module.exports = {
    entry:'./src/index.js', // 入口文件
      // 输出文件
  output:{
    publicPath:'/',  // 打包输出根路径 
    // publicPath:'gttp://cdn.com.cn', 
    filename:'[name].bundle.js', 
    path:path.resolve(__dirname,'dist')
  },
}
  • publicPath:项目发布时,配置对应的域名参数
  • filename:打包输出文件名,可以是bundle.js默认生成的main.js,如果打包多个文件,可以改成占位符,这样打包出来的名字就是entry配置的
  • path:打包出的文件放在哪个文件下面
  • __dirname:node的变量,即webpck.config.js当前所在文件夹的目录,这个就是生成的bundle文件夹的路径,一般都是叫dist,生成的文件名字叫做bundle.js

  • 配置完成,运行命令时会自动引用webpack.config.js文件中的配置选项,运行如下:


    1-3

注意:如果配置的默认webpack.config.js名字修改为webpackconfig.js,此时运行命令

// 告诉webpack 按照webpackconfig.js里面的规则打包
npx webpack --config webpackconfig.js
  1. 通过pack.json 文件配置对应的命令

注意:package.json中的script会按你设置的命令名称来执行对应的命令

// 执行npm run build,会自动执行webpackj进行打包
"scripts": {
    "build":"webpack"
  },
1-4

总结(webpack三种使用方法)

  • 全局安装执行命令
// index.js 是入口文件
webpack index.js
  • 本地安装执行命令
npx webpack index.js
  • 在pack.json 中配置命令并且借助配置文件间接执行webpack指令
// 在script 目录下使用webpack,会在当前的工程目录下找webpack,这就是为什么不用加npx
npm run build => webpack

4.Loaders介绍

1,开始之前,先看看下面的图片是否能正常显示?

  • 在当前项目下新建img.js,引入一张下载并放到对应的目录文件下
import avater from '../assets/images/avater.jpg'

function Img(){
  var img = new Image();
  img.src=avater;
  img.classList.add('avater');
  document.body.append(img)
}
export default Img;
  • 在index.js中引入img模块,并创建一个该构造函数的实例
import Img from './img'
new Img();
  • 运行打包命令npm run build
  • 结果报错:

出错原因是webpack只知道怎么打包js文件,不知道怎么打包其他类似jpg文件。那么webpack打包除了js以外的文件都需要借助loaders。

4-1

loaders是webpack最强大的功能之一,通过不同的loader,webpack有能力调用外部的脚本或工具,实现对不同格式的文件(如图片),字体图标的处理,例如把less转为css,将ES66、ES7等语法转化为当前浏览器能识别的语法等功能

Loaders需要单独安装并且需要在webpack.config.js中的modules配置项下进行配置,Loaders的配置包括以下几方面:

  • test:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须)
  • loader:loader的名称(必须)
  • include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
  • options:为loaders提供额外的设置选项(可选)
图片打包loader
  1. file-loader
  • 安装对应的loader
npm install --save-dev file-loader
  • 配置参数
module:{
    rules:[{
      test: /\.(png|jpe?g|gif)$/i, // 匹配图片格式名
      use:[
        {
          loader:'file-loader', // 使用file-loader
          options: {
            name:'[name].[hash].[ext]' //配置打包名字和格式
          }
        }
      ]
        
    }]
  },
  • 运行npm run build 后显示如下:


    4-3
  1. url-loader
  • 安装
npm install --save-dev url-loader
  • 将上面的file-loader 替换成url-loader
  • 运行 npm run build
  • 结果如下:
    4-3

    url-loader
    它会将图片转化成base64 图片,将图片打包到js文件中
    虽然不用httpq请求图片,但是假如生成的js文件过大的情况下,会影响页面加载性能
url-loader的最佳使用方法:

给图片一个限制limit属性,当图片小于某一个值的时候,打包到js文件中,否则超出这个限制,则和file-loader一样,将图片打包成对应的图片,放到dist目录下

rules:[{
      test: /\.(png|jpe?g|gif)$/i,
      use:[
        {
          loader:'url-loader',
          options: {
            /**placeholder 占位符, 
             * [name] 名字是原来图片对应的名字
             * [ext] 图片对应的格式
             * outputPath:表示打包到dist对应的images下
             * limit:2048 指的是2kb 204800 即200kb,如果小于200kb则会使用url-loader 打包成base64 
             * 到js文件中,大于200kb的则不会打包到js文件中,而是按照设置的目录打包成图片
             *  */ 
            name:'[name]_[hash].[ext]',
            outputPath:'images/',
            limit:10240
          } 
      }
      ]

运行打包结果,生成单独的图片

4-4

注意:虽然我们只需使用url-loader,但url-loader是依赖于file-loader的,需要两个一起安装

样式打包loader

如果我们要加载一个css文件,需要安装配置style-loader和css-loader:

npm install style-loader css-loader --save-dev
  • 在项目里面配置一下规则
{
      test: /\.css$/,
      use: [ 'style-loader', 'css-loader' ]
    }
  • 我们在src文件夹下新建css文件夹,该文件夹内新建index.css文件:
// index.css
body{
  background: red;
}
  • 此时运行结果会发现背景变成了红色
配置项目中常用的less
// 安装less 及对应的less-loader
npm install less less-loader --save-dev
  • 增加less的rules
{
      test: /\.less$/,
      use: [
      'style-loader',
      {
        loader:'css-loader',
        options:{
          importLoaders:2// 作用于 @import的资源之前」有多少个 loader 0 => 无 loader(默认); 1 => postcss-loader; 2 => postcss-loader, less-loader
          // modules:true
        }
      },
      'less-loader',
      'postcss-loader'
    ], // compiles Less to CSS
    },
  • style-loader :会将对应的样式挂在在dom节点上
  • less-loader:解析less语法
  • postcss-loader:自动生成css3的浏览器前缀

loader 执行顺序:从下到上,从右到左,先执行less-loader文件进行翻译,然后给到css-loader,最后挂载到style-loader挂载到dom上

  • importLoaders:保证不管在js文件中importless文件还是less文件中导入子less文件,都能执行下面的是在一个less文件导入另外的less文件,postcss-loader,less-loader在进行,然后给到css-loader
  • modules:开启css模块化打包,类似与全局或者局部
  • postcss-loader与autoprefixer配合自动补全css前缀
npm install autoprefixer postcss-loader --save-dev
// postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer')]
}

结果会在加上前缀

4-5
  • 在css文件夹下创建avater.less
// avater.less
body{
  .avater{
    width: 200px;
    height: auto;
    border: 1px solid #ccc;
     transform: translate(100px,100px);
  }
}
  • 在img.js中引入avater.less:
import './css/avater.less'
  • 结果入下图
4-6

还有诸如字体图标loader、字体loader等就不一一列出来了,感兴趣的可前往webpack官网查看,都是一样的套路。

5.插件(Plugins)

插件(Plugins)是用来拓展Webpack功能的,它们会在整个构建过程中生效,执行相关的任务,类似与vue,react的生命周期概念,在构建过程中某些时刻,做些事情,让打包更加便捷。

插件的使用流程:

  1. 安装
  2. 引入
  3. 在plugins里面实列化
1.HtmlWebpackPlugin

功能:在打包结束后,会自动生成一个html文件,并把打包生成的js自动引入到这个html文件中

还可以通过配置模板属性,打包生成的html文件与src目录下的index.html文件一致

// 安装
npm install --save-dev html-webpack-plugin
  • webpack.config.js中我们引入了HtmlWebpackPlugin插件,并配置了引用了我们设置的模板,如下:
const path = require('path')
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode:'development',
  entry:'./src/index.js',
  module:{
    rules:[{
      test: /\.(png|jpe?g|gif)$/i,
      use:[
        {
          loader:'url-loader',
          options: {
            name:'[name].[hash].[ext]',
            limit:2048  /*:2048 指的是2kb 204800 即200kb,如果小于200kb则会使用url-loader 打包成base64 到js文件中,
            大于200kb的则不会打包到js文件中,而是按照设置的目录打包成图片*/
          }
        }
      ]
    },  {
      test: /\.css$/,
      use: [ 'style-loader', 'css-loader']
    },
    {
      test: /\.less$/,
      use: [ 'style-loader', 'css-loader','less-loader','postcss-loader']
    }
  ]
  },
  plugins:[
    new HtmlWebpackPlugin({
      template:'src/index.html'  //根据自己的指定的模板文件来生成特定的 html 文件。这里的模板类型可以是任意你喜欢的模板,可以是 html, jade, ejs, hbs, 等等,但是要注意的是,使用自定义的模板文件时,需要提前安装对应的 loader, 否则webpack不能正确解析
    })
  ],
  output:{
    filename:'[name].js',
    path:path.resolve(__dirname,'dist')
  }
}
  • 先将将目录下dist文件删除然后我们使用npm run build进行打包,你会发现,dist文件夹和html文件都会自动生成,如下:
5-1
CleanWebpackPlugin

在打包的之前,清除上一次的dist文件夹下的打包生成的js为文件,也可以配置清理/dist文件夹

// 安装
npm install clean-webpack-plugin  --save-dev
 //引入清除文件插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
//实例化,参数为目录,这里面无参数,默认删除的是output.path!
我的output配置项里的路径就是dist目录。 
ew CleanWebpackPlugin()

 output:{
    filename:'[name].js',
    path:path.resolve(__dirname,'dist')
  }

注意:webpack 4.32.2 以上的写法,
新版本的插件需要进行解构,默认删除的是index.html里面未引用的文件

const { CleanWebpackPlugin } = require('clean-webpack-plugin');

6.构建本地服务器

使用 webpack-dev-server能够根据代码的变化,自动打包并刷新页面效果,提升开发效率

webpack-dev-server配置本地服务器
npm i webpack-dev-server --save-dev

devServer作为webpack配置选项中的一项,以下是它的一些配置选项:

  • contentBase :设置服务器所读取文件的目录,当前我们设置为"./dist"
  • open:true 是否自动打开新的浏览器窗口
  • port :设置端口号,如果省略,默认为8080
  • inline :设置为true,当源文件改变时会自动刷新页面
  • historyApiFallback:设置为true,所有的跳转将指向index.html
  • 将这些配置加到webpack.config.js文件上,如下:
 devServer:{  // 
      contentBase: path.join(__dirname, 'dist'),
      open: true,  // 自动打开浏览器
      port: "8088",   // 设置端口号为8088,默认是8080
      hot:true,   // 热模块
      hotOnly:true,    //html 热加载,html 失效的时候,如果是false ,会重新帮你刷新页面,一般也买你失效,不需要去刷新,所以这里值为true
      proxy:{}  // 设置代理
  },
  • 在package.json文件中scripts添加启动命令:
"dev":"webpack-dev-server"
  • 启动项目
npm run dev
6-1
其他webpack配置本地服务器方法
  1. 在package.json 中 配置--watch 启动之后,监听打包的js文件发生变化就会自动执行打包,这个无法axios请求以及自动更新
"build": "webpack --watch",
  1. 利用node自己搭建一个服务器
  • 安装webpack 的中间件 middleware
npm install express webpack-dev-middleware -D
  • 在package.json 配置命令
"scripts": {
    "build": "webpack",
   "server": "node server.js"
  }
  • 在项目根目录下新建一个server.js文件
// server.js
const express = require('express');
const webpack= require('webpack');
const webpackDevMiddlewear = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
const complier = webpack(config);  // 进行编译
// 在node中使用webpack
const app = express();
app.use(webpackDevMiddlewear(complier,{
  publicPath:config.output.publicPath   // 打包输出的路径就是config
}))
app.listen(3000,(()=> {
  console.log('server is runnin')
}))
6-3

总结:会发现这个没有webpack-dev-server智能,每次修改页面需要手动刷新页面才能更新,所以项目中一般用webpack-dev-server

Source Maps调试配置

作为开发,代码调试当然少不了,那么问题来了,经过打包后的文件,你是不容易找到出错的地方的,Source Map就是用来解决这个问题的。

通过如下配置,我们会在打包时生成对应于打包文件的.map文件,使得编译后的代码可读性更高,更易于调试

module.exports = {
  mode:'production',  // 改成production 则是生产环境,会打包压缩
  devtool:'source-map',  // development 环境下,会默认是关闭de,线上cheap-module-source-map
  entry: {main:'./src/index.js'}
  // 热加载配置
  devServer:{
    contentBase:'./dist',
    open:false,
    port: "8088",   // 设置端口号为8088
    hot:true,   // 热模块
    hotOnly:true    //html 热加载,html 失效的时候,如果是false ,会重新帮你刷新页面,一般也买你失效,不需要去刷新,所以这里值为true
  },
  }
  • 比如在index.js中写入错误代码
consoel.log('1111')
  • 配置好后,我们再次运行npm run build进行打包,这时我们会发现在dist文件夹中多出了一个main.js.map文件如下:
6-4
  • 并且执行npm run dev,SourceMap 会建立打包代码与源代码的一个映射概关系,它是一个映射 关系,他知道dist目录下main.js文件报错代码对映的实际上对于的是src目录下SourceMap/index.js文件中的第8行代码报错,结果如下
6-5
6-6

如果我们的代码有bug,在浏览器的调试工具中会提示错误出现的位置,这就是devtool: 'source-map'配置项的作用。

source-map配置优化
  • source-map 参数,具体看官网,关闭用none
  • source-map:会降低打包速度,打包过程中会生成main.bundle.js.map映射文件,降低打包速度
  • inline-source-map:区别 对应的main.bundle.js.map文件会以base64格式打包到对应的js文件中,也就是不会单独生成.map文件
  • inline-cheap-source-map:只要告诉第几行出错了,打包速度会提高,只管业务代码,他的打包速度比较快
  • cheap-module-source-map:不止管业务代码里面的错误,还会管css,loader里面的错误,但是速度比较慢
  • eval-source-map:这个速度比较快,eval并且会将代码的映射概关系打包到main.js中,但是在大型中可能不全。且不会生产map.js文件
  • ==cheap-module-eval-source-map== 建议在开发环境下,打包速度比较快且比较全
  • production 线上代码:==cheap-module-source-map== 提示更全面点

具体参数配置在官网:
打开官网
浏览器调式插件:source-map

热更新(HotModuleReplacementPlugin)

可以在我们修改代码后自动刷新预览效果

  • 使用方法:
  1. devServer配置项中添加hot: true参数。
  2. 因为HotModuleReplacementPlugin是webpack模块自带的,所以引入webpack后,在plugins配置项中直接使用即可。

7.babel

Babel其实是一个编译JavaScript的平台,它可以编译代码帮你达到以下目的:

让你能使用最新的JavaScript代码(ES6,ES7...),而不用管新标准是否被当前使用的浏览器完全支持;
让你能使用基于JavaScript进行了拓展的语言,比如React的JSX;

7-1
// 安装es6语法
npm install --save-dev babel-loader @babel/core
npm install @babel/preset-env --save-dev
{ test: /\.js$/,
       exclude: /node_modules/, // 检测到js文件,排除node_modules下js文件,进行语法转化
       loader: "babel-loader" ,
       options:{
        "presets": ["@babel/preset-env"]
       }
      }

现在我们已经可以支持ES6语法转换ES5啦

// index.ja写下面代码
const str = '1223333'
let nameArr = ['violet','122','3333']
nameArr.map((el,idx) => {
  console.log(el,idx)
})
// 执行打包命令 在目录中生成dist
webpack

(因为执行npm run dev 会将打包的dist目录放放置在内存中,隐藏起来,此时我们打开打包后的main.js,最末尾会看到语法转化),如下:

7-2

但是map函数或者对象在低版本浏览器里面是没有的,还需要将这些低版本浏览器里没有的函数或者对象补充进去,这就需要使用@babel/polyfill

npm install --save @babel/polyfill
  • 在index.js中导入
import "@babel/polyfill";

执行webpack
你会发现这中全局注入的方式,会增大main.js的体积

注入@babel/polyfill前。main.js只有20.2kib

7-3

注入后。main.js 419kib

7-4

体积变大的原因是这种全局注入方式,会将所有的将函数或者变量在低版本的补充,包括promise, symbol 这类新增的内建对象的注入,因此如果能实现按需注入,就可以减小体积啦

@babel/polyfill优化方法:

可以设置useBuiltIns:'usage' 类似与根据你的业务代码进行替换,业务代码里面用到什么就注入@babel/polyfill,按需引入,优化打包体积
同时,使用这个属性webpack会在打包时自动引入@babel/polyfill,不需要手动引入

 { test: /\.js$/,
       exclude: /node_modules/, // 检测到js文件,排除node_modules下js文件,进行语法转化
       loader: "babel-loader" ,
       options:{
        "presets": [["@babel/preset-env",{
            targets: {
              edge: "17",
              firefox: "60",
              chrome: "67",  // 项目会打包运行在chorme 版本67 的上面,在这个版本号或者以上版本是否支持es6,如果支持,则是不必要进行注入转换
              safari: "11.1",
            },
          useBuiltIns:'usage' // 根据业务代码用到的函数,按需替换
        }]]
       }
      } 

打包后体积变成 main.js 45.9kib

7-5

此外还可以在设置targets的浏览器版本号,决定是否要转换代码,比如chorme 67 以上对Es6语法都支持所以打包后的体积,又精简成 20.3kib啦,如下图

7-6
@babel/runtime 配置

虽然polyfill是按需引入的,但是会污染全局命名空间,当你写的是公共库时,可能会与使用者本地的方法产生冲突。

当我们开发UI组件库或者在开发第三方库时就要需要使用@babel/runtime和@babel/plugin-transform-runtime,能够有效的避免,会以闭包的形式注入,不会污染全局的环境

npm install --save @babel/runtime-corejs2
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime

配置代码

options:{
          "plugins": [[
            "@babel/plugin-transform-runtime",
            {
              "absoluteRuntime": false,
              "corejs": 2, // 一般写2
              "helpers": true,
              "regenerator": true,
              "useESModules": false
            }
          ]]
        
        // "presets": [["@babel/preset-env",{
        //     targets: {
        //       edge: "17",
        //       firefox: "60",
        //       chrome: "67",  // 项目会打包运行在chorme 版本67 的上面,在这个版本号或者以上版本是否支持es6,如果支持,则是不必要进行注入转换
        //       safari: "11.1",
        //     },
        //   useBuiltIns:'usage' // 根据业务代码用到的函数,按需替换
        // }]]
       }
      }
babele配置提取
  1. 在项目根目录下新建一个.babelrc文件
    这里面的内容对应的就是babel-loader options里面的内容
// .babelrc 使用时把注释删掉,该文件不能添加注释
{
  "plugins": [[
    "@babel/plugin-transform-runtime",
    {
      "absoluteRuntime": false,
      "corejs": 2, // 一般写2
      "helpers": true,
      "regenerator": true,
      "useESModules": false
    }
  ]]
}

更详细babel7的可以看这篇文章的介绍
link

8.webpack项目优化及拓展

1.环境模式区分打包配置

  1. 修改目录结构如下:
8-1
  1. 使用webapxk-merge将公共配置提取出来
npm install webpack-merge --save-dev
// webpack.prod.js
// 引入合并插件
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base');
const prodConfig = {
    mode: 'production',  // 在development开发环境中,默认sourceMaps devtool默认是false的
    devtool: 'cheap-module-source-map',
}
module.exports = merge(baseConfig, prodConfig)
// webpack.dev.js
// 引入合并插件
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base');
const path = require('path')
const devConfig = {
    mode: 'development',  // 在development开发环境中,默认sourceMaps devtool默认是false的
    devtool: 'cheap-module-eval-source-map',
    devServer: {  // 
        contentBase: path.join(__dirname, 'dist'),
        open: false,  // 自动打开浏览器
        port: "8088",   // 设置端口号为8088
        hot: true,   // 热模块
        hotOnly: true,    //html 热加载,html 失效的时候,如果是false ,会重新帮你刷新页面,一般也买你失效,不需要去刷新,所以这里值为true
        proxy: {}  // 设置代理
    },
    optimization: {
        usedExports: true  // tree shaking
    }
}
module.exports = merge(baseConfig, devConfig);
// webapck.base.js
const path = require('path')
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 引入CleanWebpackPlugin插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  entry:'./src/index.js',
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'  //根据自己的指定的模板文件来生成特定的 html 文件。这里的模板类型可以是任意你喜欢的模板,可以是 html, jade, ejs, hbs, 等等,但是要注意的是,使用自定义的模板文件时,需要提前安装对应的 loader, 否则webpack不能正确解析
    }),
    new CleanWebpackPlugin(), //实例化,参数为目录
  ],
  module: {
    rules: [{
      test: /\.(png|jpe?g|gif)$/i,
      use: [
        {
          loader: 'url-loader',
          options: {
            name: '[name].[hash].[ext]',
            limit: 2048  /*:2048 指的是2kb 204800 即200kb,如果小于200kb则会使用url-loader 打包成base64 到js文件中,
            大于200kb的则不会打包到js文件中,而是按照设置的目录打包成图片*/
          }
        }
      ]
    }, {
      test: /\.css$/,
      use: ['style-loader', 'css-loader']
    },
    {
      test: /\.less$/,
      use: ['style-loader', 'css-loader', 'less-loader', 'postcss-loader']
    },
    {
      test: /\.js$/,
      exclude: /node_modules/, // 检测到js文件,排除node_modules下js文件,进行语法转化
      loader: "babel-loader"
    }
    ]
  },
  output:{
    publicPath:'/', 
    filename:'[name].js',
    path: path.resolve(process.cwd(), 'dist')  // __dirname: 当前模块的目录名,而 process.cwd() 当前Node.js进程执行时的工作目录,就是根目录,放到上一层dist目录下
  }
}

wepack.base.js中放置共有的配置,这里注意因为配置文件放在build里面,路径发生了变化,所以需要将输出地址修改,表示打包的dist文件放到项目根目录

path.resolve(process.cwd(), 'dist')
  1. 修改package.json,配置打包环境
"scripts": {
      "dev": "webpack-dev-server --config ./build/webpack.dev.js",
      "build": "webpack --config ./build/webpack.prod.js"
  },
  1. 执行命令,你会发现项目打包和启动都ok

2.tree shaking(摇树优化,剔除无用的代码)

  • 在根目录下新建一个untils.js
// untils.js
const a = () =>{
    console.log('a')
}
const b = () =>{
    console.log('b')
}
const c = () =>{
    console.log('c')
}
export {
    a,
    b,
    c
}
  • 在index.js中引入a
import {a} from './untils.js'
a();
  • 我们可以看到 在 utils.js中我们定义了三个方法, 在index.js中引入了方法a,也就是说方法b和方法c都是没有使用的。 我们看一下打包之后的结果
8-2

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。其实就是说把我们项目中没有使用到的代码在打包的时候丢弃掉,只保留我们引入了的JS代码和css代码

==注意:它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。不支持CommonJs的引入,因为这个是CommonJs动态加载模块==

开发环境配置tree shaking
  • 在webpack.config.js中增加一个optimization 属性 设置usedExports:true
mode:'development', 
optimization:{
    usedExports:true  
  },
  • 在在package.json 设置 "sideEffects",一般设置为false
{
  "name": "review",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "sideEffects":["@babel/pollyfill"],
  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server",
    "server": "node server.js"
  },

注意:如果项目中有使用babel/pollyfill 文件,这个文件没有导出任何的内容,three shaking

  • 打包的时候会忽略点,需要做特殊的设置sideEffects":["@babel/pollyfill","*.css"]这类文件都没有导出文件,这样设置就会忽略这个,一般设置为false
  • 打包结果如下,在开发环境下,方便代码调试,不会删除代码,但是会多出一个used属性
8-3
在production环境下
mode:'production',
  • 在在package.json 设置 "sideEffects",一般设置为false
{
  "name": "review",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "sideEffects":["@babel/pollyfill"],
  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server",
    "server": "node server.js"
  },
  • 去掉optimization
  • 打包会发现只有a被调用,其他的代码都被删除
8-4

3.codesplit(代码分割)

Webpack 文件分离包括两个部分,一个是 Bundle 的分离,一个是 Code 代码的分离:

Bundle splitting: 实际上就是创建多个更小的文件,并行加载,以获得更好的缓存效果;主要的作用就是使浏览器并行下载,提高下载速度。并且运用浏览器缓存,只有代码被修改,文件名中的哈希值改变了才会去再次加载。

Code splitting: 只加载用户最需要的部分,其余的代码都遵从懒加载的策略;主要的作用就是加快页面加载速度,不加载不必要加载的东西。

比如我们在项目中使用lodash这个库,未进行代码分割配置
打包结果:

8-5

每次打包全部依赖都被打包到 main.xxx.js 中去,大小是 1.36mib,在项目中业务代码可能会很大,每次打包生成的文件,影响页面打开速度

Bundle Split 的主要任务是将多个引用的包和模块进行分离,避免全部依赖打包到一个文件下
  • Webpack 4 中需要使用到 optimization.splitChunks 进行同步代码分割的配置:
// webpack.base.js
splitChunks:{  // 分片打包 ,其默认值是对异步代码打包
    chunks:'all', 
     minSize: 30000,  // 30kb
     maxSize: 0,  // 50kb  
    minChunks: 1, // chunks 在项目中引用的次数,满足条件则会进行分割,此时为2 ,但是只引用了一次,则不会进行分割
     maxAsyncRequests: 5, // 同时加载的模块数最多是5个
     maxInitialRequests: 3,  // 整个网页首页或者入口文件引入的库做代码分割,超过3个也就不会进行分割
    automaticNameDelimiter: '~',  // 打包生成的js 文件~链接符 vendors-lodash.js
    automaticNameMaxLength: 30,
     name: true,  // 生成的下面对应的fliename
     cacheGroups: {  // 缓存组:对应的分割生成所在组内,缓存组,将符合要求的文件打包生成一个文件
     vendors: {
        test: /[\\/]node_modules[\\/]/, // 匹配node_modules 里面的js,将其打包分割
        priority: -10,
       filename:'vendors.js'  
      }, 
      default: {  // 所有的文件都符合defaul 至于放到vendors 这个组内还是default 组内,取决于priority,-10 高于-20,则会打包到vebdors
       priority: -20, // 引入两个文件,但是a 文件使用过b ,在打包a的时候,a用到的b ,会使用之前打包的b 
        reuseExistingChunk: true, 如果一个模块已经被打包过,那么再使用这个模块则会被忽略,直接复用之前的代码
        filename:'common.js'   // 当引用的文件不在node_modules 中,会打包到defalt 中设置的common.js 中
       }
     }
  }

optimization.splitChunks 的意思是将所有来自 node_modules 中的依赖全部打包分离出来,这个时候我们再看打包的文件是个什么样子:

8-6

增加了 splitChunks 的配置,我们第三方模块都被打包到了 vendors~main.xxx.js 中去了,这个文件大小有 1.36mib,而入口文件 main.xxx.js 则只有9.24kib

  • splitChunks的主要有以下配置
8-7
webpack 会对异步代码自动分割

异步代码import语法,类似路由的按需加载,无需做任何配置,会自动代码分割

  • 安装@babel/plugin-syntax-dynamic-import
// 用以解析识别import()动态导入语法
npm install @babel/plugin-syntax-dynamic-import --save-dev
  • 在index.js中写入以下代码:
function getComponent () {
// 里面的注释打包生成的name,生成对应得 vendors~lodash.js ,默认生成vendors~main.js
  return import(/* webpackChunkName: 'loadsh' */'lodash').then(({default:_}) =>{
    var element = document.createElement('div');
    element.innerHTML = _.join(['vlolet','lee2'],'-');
    return element
  })
}
getComponent().then((res) => {
    document.body.appendChild(res)
})
  • 在bablerc中配置
{
  presets: [
    ["@babel/preset-env", {
      targets: {
        chrome:"67"
      },
      useBuiltIns: "usage"
    }],
    "@babel/preset-react"
  ],
  plugins: ["@babel/plugin-syntax-dynamic-import"]
}
  • 打包结果:
8-8
css代码分割
  • 在当前目录下。有一个css文件夹,引用了css,但是打包并没有生成独立的css文件,而是打包到js文件中啦
// index.css
body{
  background: plum;
}
div{
  font-size: 20px;
  color: blueviolet;
  margin: 0 aut0;
}
.avater{
  width: 100px;
}
  • css代码分割需要借助插件MiniCssExtractPlugin
  • 因为只能在生成环境使用,所以需要将css相关打包规则移到各自对应的环境中,并进行压缩
// 分割代码并压缩css代码
npm install mini-css-extract-plugin optimize-css-assets-webpack-plugin --save-dev
// 安装js压缩代码
npm install terser-webpack-plugin --save-dev
// webpack.prod.js中配置插件
// 分割插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 压缩css代码
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
// js压缩代码
const TerserJSPlugin = require('terser-webpack-plugin');

module: {  // 配置对应的css打包
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                    loader: MiniCssExtractPlugin.loader,  // 使用插件的loader 替换style-loader
                        options: {  // 配置对应的参数
                            publicPath: '../',
                            hmr: process.env.NODE_ENV === 'development',
                        },
                    },
                    'css-loader',
                ],
            },
            {
                test: /\.less$/,
                use: ['style-loader', 'css-loader', 'less-loader', 'postcss-loader']
            },
        ]
    },
     optimization: {
        runtimeChunk: {  //解决打包两次 contHash 值不一样的情况
            name: 'runtime'
        },
        minimize: true,
        minimizer: [ new OptimizeCSSAssetsPlugin({})],  // css 自动合并与压缩 TerserJSPlugin这个压缩js代码
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'css/[name].css', // dist 目录下index.html直接引用的会默认生成filename name.css ,间接的使用的文件会生成chunkFilename
            chunkFilename: 'css/[name].css',
            ignoreOrder: false,
        }),
    ],
  • 修改tree shaking package.json中
// 前面有提到过,css文件并没有输出模块,所以不进行tree shaking优化
 "sideEffects": ["*.css"],
  • 打包结果会生成一个压缩的main.css:
8-9

MiniCssExtractPlugin,这个插件不会热更新,不支持html,会降低开发效率,一般在线上环境打包使用这个,在配置css压缩时会覆盖掉webpack默认的优化设置,导致JS代码无法压缩,所以还需要把JS代码压缩插件导入进来 terser-webpack-plugin

4.preload和prefetch及懒加载

  • webpackPrefetch: 等页面核心业务加载完成后,空闲时间去加载一些懒加载的代码
  • webpackPreload: 通常用于本页面要用到的关键资源,包括关键js、字体、css文件。preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度。

通过import 这个语法,会提升加载速度,类似于vue 及react路由的概念,懒加载实际上并不是webpack的概念,而是es的语法,webpack只不过是能够识别这种语法进行代码分割而已

  • 例如在index.js中写入以下代码
// preloading 
document.addEventListener('click', () => {
    const element = document.createElement('div');
    element.innerText = '123'
    document.body.appendChild(element);

})
  • npm run build 打开dist目录下html文件
  • 在浏览器控制台
  • 看代码使用率 Ctrl+ shift+ p show Coverage 点击录制按钮,变红,刷新页面,查看main.js代码使用率
  • 效果如下图:


    8-10

main.js使用率15.2%

  • 将这代点击才加载的代码放到异步组件,做出下面的修改
  • 在src下,新建一个click.js
// click.js
function getComponent () {
  const element = document.createElement('div');
  element.innerText = 'dell'
  document.body.appendChild(element);
}
export default getComponent
  • 将index.js代码修改
//改成异步组件
document.addEventListener('click', () => {
  import('./click.js').then(({default:func}) => {
     func()
  })
})
  • 打包,查看代码使用率,改异步组件,使用率变成46.9%
8-11

Ctrl+ shift+ p show Coverage 看代码使用率
,代码使用率高,页面速度快,webpack希望你多写异步组件,多写异步代码,放到异步组件里面去,
但是异步加载或者懒加载可能影响用户体检,可以使用webpackPrefetch等提升体验
同步会有缓存

  • 常用场景:页面弹窗,如果能在在网络空闲时间,加载登录页面,这样既不会在主业务逻辑加载的时候,影响页面速度,又能在点击的时候,快速打开
  • 使用方法:
    在import 语法中添加一下语法
import(/* webpackPrefetch: true */ 'LoginModal');
import(/* webpackPreload: true */ 'ChartingLibrary');

5.shamming

shimming简单翻译是垫片。在开发中,经常在一个文件中引入很多第三方库或者自己写的库,每个js文件用到的库都要引入,让人很繁琐,但又不能不引入。webpack内置插件ProvidePlugin能帮助我们解决上面遇到的问题。
举个列子:比如在下面文件使用lodash.js

npm install lodash --save-dev
  • src目录下新建一个test.js
// test.js
const nameCount = () => {
    let b = _.join(['123', 'abc', 'violet2'],'------------')
    console.log(b)
}
export default nameCount

注意虽然index.js中导入了lodash,但是webpack 打包 是模块化打包,通过import 引入的只能在当前模块里面使用,这样可以降低模块的耦合性

// index.js 
import nameCount from './test'
nameCount()
  • npm start


    8-12
  • 在webpack.base.js

const webpack = require('webpack'); // webpack内置插件,需引入webpack,
new webpack.ProvidePlugin({  // // 帮助第三方模块或者js文件结合自身需要,使用_ 的组件自动引用lodash模块
  _: 'lodash',
    }),
8-13

ProvidePlugin插件作用是自动加载模块,而不必到处 import 或 require。只要在ProvidePlugin插件中配置了变量。凡是在源码中用到_变量,在webpack打包时,就会文件最前面引入import _ from 'loadsh'就不要我们自己手动引入了。

6. webpack 与浏览器缓存(Caching)

项目开发完成,我们会将生成的dist 文件放到服务器环境,为了解决浏览器缓存的问题,我们可以在生成环境输出设置[contenthash]这个值是根据输出的时候,内容发生变化就会去更新hash值,不变化的js文件会缓存到浏览器中

// webapck.prod.js
output: {     // 考虑去掉浏览器缓存更新的问题
        // publicPath:'gttp://cdn.com.cn', //但是一般我们做好的项目都会上传到线上,域名下到文件,这个时候自动生成的index.html里面的js路径不是我想要到,我想要在路径前面加上域名
        filename: 'js/[name].[contenthash].js',   // 打包文件名,可以是bundle.js默认生成的main.js,如果打包多个文件,可以改成占位符,这样打包出来的名字就是entry配置的main,sub 
        chunkFilename: 'js/[name].[contenthash].js',
    },
缓存失效问题

当js内容没变更的时候,但是由于模块与模块之间的对应关系发生变化,两次打包contenthash的不一样,此时可能老版本的用户引用的缓存文件会找报错,解决这个问题,去webpac配置 runtimeChunk

// webpack.prod.js
optimization: {
        runtimeChunk: {  // webpack 直接可以使用这个内置特性 //解决打包两次 contHash 值不一样的情况
            name: 'runtime'
        },
    }
  • 打包结果如下:


    8-14

webpack 会把main.js 与vender.js 中,在版本不同的情况下,manifest内置的模块及js与jis直接的关系发生变化,这个runtime.js
就是会把manifest 处理关系的代码抽离出来,避免影响业务代码和模块代码

7.打包分析工具介绍

  1. 第一种在项目中生成stats.json
  • 在package.json 中打包命令配置
--profile --json > stats.json

"scripts": {
    "start": "webpack-dev-server --config ./build/webpack.dev.js",
    "dev": "webpack  --profile --json > stats.json --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },
  • 能够 看打包的关系以及过程的分析


    8-15
  1. Bundle Analysis
    //其他分析工具
    https://webpack.js.org/guides/code-splitting/#bundle-analysis
  • 例如:webpack-chart 通过打开生成的stats.json
8-16
  1. webpack-bundle-analyzer插件,这个比较常用,且查看的信息比较全
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349