webpack4 学习之路

webpack4 学习之路(1)-起源

1、开篇小啰嗦

这次webpack学习总结是经过慕课网上webpack教学。根据课程内容以及本身的理解,写成系列文章,增强本身的学习印象,共勉javascript

开始讲解以前,咱们先了解一下为何要使用webpack. webpack到底能够干什么啊? 好多面试的时候,都会问你会代码分割吗? 你使用框架开发,离开脚手架,还会配置吗?怎么配置啊?看起来都是小问题,可是问起来有时候真的要命。如今写一下webpack4的系列文章。最后就应该能够解决这类问题了。css

2、开发模式的演变

1.面向过程开发

特色:乱七八糟。爱咋引就咋引html

那个时候没有什么规范,一个html引入一个js文件。全部的js代码。所有写到一个js文件里面。前端

<!html的页面>

<div id="root"></div>
<script src="./index.js"></script>
复制代码
// index.js代码
var root = document.getElementById('root');

// header模块
var header = document.createElement('div');
header.innerText = 'header';
root.appendChild(header);

// content模块
var content = document.createElement('div');
content.innerText = 'content';
root.appendChild(content);

// footer模块
var footer = document.createElement('div');
footer.innerText = 'footer';
root.appendChild(footer);
复制代码

问题来了,一个简单的头部,内容,尾部的布局,须要写在一个js文件。要是几十个上百个模块。是否是就多的爆炸。找问题,能累死vue

2.面向对象开发

特色:把js的代码一块块的分割,每一个部分就是一个单独的js的代码,遇到问题,快速查找,容易维护java

<!-- index.html代码 -->
<p>这里是咱们网页的内容</p>
<div id="root"></div>
<script src="./src/header.js"></script>
<script src="./src/content.js"></script>
<script src="./src/footer.js"></script>
<script src="./index.js"></script>
复制代码
// header.js
function Header() {
  var header = document.createElement('div');
  header.innerText = 'header';
  root.appendChild(header);
}
复制代码
// content.js
function Content() {
  var content = document.createElement('div');
  header.innerText = 'content';
  root.appendChild(content);
}
复制代码
// footer.js
function Footer() {
  var footer = document.createElement('div');
  header.innerText = 'footer';
  root.appendChild(footer);
}
复制代码
// index.js代码
var root = document.getElementById('root');
new Header();
new Content();
new Footer();
复制代码

你们好,我叫问题,我又来了。请问。js代码分隔开了,好处理了。html怎么办。一会儿引入这么多js,并且顺序不能乱。不然就报错。少了还好,若是几百上千个咋整啊。我怎么拍顺序啊。node

3.模块开发模式

特色:html 仍是引入一个js代码,js代码仍是分开。用模块化工具管理。咋看咋舒服。随后各类模块化加载愈来愈多,例如:ES Module(es6)、AMD(没用过)、CMD(没用过)以及CommonJS(服务端,node)等,咱们介绍ES Module模块化加载方案,固然其余模块化标准也是生效的react

<!-- index.html代码 -->
<p>这里是咱们网页的内容</p>
<div id="root"></div>
<script src="./index.js"></script>
复制代码
// header.js
export default function Header() {
  var root = document.getElementById('root');
  var header = document.createElement('div');
  header.innerText = 'header';
  root.appendChild(header);
}
复制代码
// content.js代码
export default function Content() {
  var root = document.getElementById('root');
  var content = document.createElement('div');
  content.innerText = 'content';
  root.appendChild(content);
}
复制代码
// footer.js
export default function Footer() {
  var root = document.getElementById('root');
  var footer = document.createElement('div');
  footer.innerText = 'footer';
  root.appendChild(footer);
}
复制代码
// index.js代码
import Header from './src/header.js';
import Content from './src/content.js';
import Footer from './src/footer.js';

new Header();
new Content();
new Footer();
复制代码

看的很爽啊,这样js代码既能分开,html也只须要本身引入一个js文件,爽啊,爽啊。哈哈,你们好。我叫浏览器,我不一样意,怎么办。webpack来了。webpack就是编译一下,让浏览器赞成jquery

webpack也不是单纯的js编译器,官网和新定义是模块打包工具webpack

webpack4 学习之路(2)-安装

1、开篇小啰嗦

上一篇文章说了,浏览器不会赞成咱们使用ES module模块化方式写代码。它不认,此时webpack出马就能够解决这个问题 。

2、开始安装吧

  1. 默认你装好node环境了啊。在桌面新建一个文件夹,建立一个node的包文件,生成一个package.json文件。
npm init 
复制代码
  1. 全局安装webpack
npm install webpack webpack-cli -g
复制代码

你们好,我叫问题。我又来了,这个webpack是在全局安装的,若是你有两个项目,一个依赖是webpack3,一个依赖webpack4.那么webpack3项目就废了。因此这种全局的也不必定好使,最好那个项目用,那个项目自己安装webpack4,别打扰别人

  1. 全局卸载掉
npm uninstall webpack webpack-cli -g
复制代码
  1. 进入项目文件夹
npm install webpack webpack-cli -D 或者 npm install webpack webpack-cli --save-dev
复制代码

5.检查webpack版本号

npx webpack -v
复制代码

若是你直接输入webpack -v,是直接默认查看你的全局webpack。如今咱们卸载了,因此是空。node提供npx,能够查看本文件的安装状况

  1. 版本号安装
npm install webpack@4.25.0 -D
复制代码

webpack4 学习之路(3)-配置文件

1、开篇小啰嗦

安装完,就该开始配置了。为何webpack须要配置啊。咱们说过webpack是个模块打包工具,可是打包js,图片,或者其余的后缀的文件,打包的流程确定不同的,因此须要配置文件。另外,webpack开发团队为了咱们使用的爽。会有个一个文件默认配置基本的代码,咱们就是在这个文件下修改代码。配置以知足咱们的项目需求

2、开始配置吧

1.建立基础配置文件

在总结一下须要输入的命令吧

1. npm init(生成node规范的文件)

2. npm install webpack webpack-cli -D (进入项目文件夹,局部安装webpack)

3. 把第一章的几个js,html文件本身手动建立

4. 手动建立 webpack.config.js文件

复制代码

命令敲完,应该会造成一个下面的目录

|-- webpack(名字随便起)
|   |-- index.html
|   |-- index.js
|   |-- header.js
|   |-- content.js
|   |-- footer.js
|   |-- webpack.config.js
|   |-- package.json
复制代码

webpack.config.js文件里面写代码

// path为Node的核心模块
const path = require('path');

module.exports = {
  entry: './index.js',  // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
  output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}
复制代码

package.json 修改代码为

{
  "name": "webpack",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "bundle": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.31.0",
    "webpack-cli": "^3.3.2"
  }
}
复制代码

执行命令

npm run bundle
复制代码

看结果

2.webpack执行的方式小总结

到如今,webpack 执行方式有三种

(1)若是你是全局安装的,就执行 webpack index.js 打包

(2)若是你是项目安装的,就执行 npx webpack index.js 打包

(3)你若是配置了webpack.config.js文件了,并且在packag.json里面修改了script. 直接执行 npm run bundle 不用特指index.js文件了。由于配置文件里面给你指明了

3.看官方档案

讲的这里,就能够查看官网网址的开始了。看官方源码才是最原汁原味的讲解 看官网讲解

4.打包完页面信息解释

打完包,控制台会显示不少条信息。hash,version,等等啊。这些都是打包的信息提示,告知咱们打包的情况,下面咱们详细解释一下信息都是啥意思:

npm run bundle 
复制代码

打包信心

  1. Hash: hash表明本次打包的惟一hash值,每一次打包此值都是不同的

  2. Version: 详细展现了咱们使用webpack的版本号

  3. Time: 表明咱们本次打包的耗时

  4. Asset: 表明咱们打包出的文件名称

  5. Size: 表明咱们打包出的文件的大小

  6. Chunks: 表明打包后的.js文件对应的id,id从0开始,依次日后+1

  7. Chunks Names: 表明咱们打包后的.js文件的名字,至于为什么是main,而不是其余的内容,这是由于在咱们的webpack.config.js中,entry:'./index.js'是对以下方式的简写形式:

// path为Node的核心模块
const path = require('path');

module.exports = {
  // entry: './index.js',
  entry: {
    main: './index.js'
  }
  // 其它配置
}
复制代码
  1. Entrypoint main = bundle.js: 表明咱们打包的入口为main

  2. warning in configuration: 提示警告,意思是咱们没有给webpack.config.js设置mode属性,mode属性有三个值:development表明开发环境、production表明生产环境、none表明既不是开发环境也不是生产环境。若是不写的话,默认是生产环境,可在配置文件中配置此项,配置后再次打包将不会再出现此警告。

// path为Node的核心模块
const path = require('path');

module.exports = {
  // 其它配置
  mode: 'development'
}
复制代码

webpack4 学习之路(4)-核心概念

1、开篇小啰嗦

基本配置完,你们就明白怎么回事了,如今若是有个需求须要你手动搭建一个脚手架,你就知道怎么下手了,哈哈哈,搭建vue.react的脚手架也是从这里开始的。只不过vue的后缀是.vue,webpack不认识。有的图片,后缀是.png.webapck也不认识,因此这章节就是webpack的核心配置。解决各类遇到的问题。让webpack发光发热

2、核心概念-loader

1. 啥是loader

loader是一种打包规则,它告诉了 Webpack 在遇到非.js文件时,应该如何处理这些文件,一个项目总有不少后缀不同的文件。这个时候你要用loader告诉webpack怎么使用loader配置。

loader有以下几种固定的运用规则:(后面会解释)

使用test正则来匹配相应的文件
使用use来添加文件对应的loader
对于多个loader而言,从 右到左 依次调用
复制代码

2.使用loader打包图片-file-loader/url-loader

开发场景中,几乎离不开图片,webpack怎么识别图片后缀呢? 蹬蹬蹬蹬。。。。

file-loader或者url-loader,后面介绍区别。需使用npm install进行安装 如今咱们重点讲解file-loader

下面咱们稍微修改一下代码,在网上随便找个图片放到项目文件夹里面

// index.js代码
import Header from './header.js';
import Content from './content.js';
import Footer from './footer.js';
import img from './1.png';

new Header();
new Content();
new Footer();
复制代码

再次执行,npm run bundle 会报错,为啥。由于webpack 不认识png啊。咋办呢。在webpack.config.js配置文件里面配置

// path为Node的核心模块
const path = require('path');

module.exports = {
  entry: './index.js',  // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
  module:{ // 告诉webpack打包的模块
    rules:[{  // 遇到不一样的后缀打包的规则
      test:/\.png$/, // 正则,就是说遇到这个后缀怎么办
      use:{  // 遇到上面的后缀文件就使用下面的loader
        loader:'file-loader'
      }
    }]
  },
  output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}
复制代码

配置完别忘了下载啊

npm install file-loader -D
复制代码
npm run bundle
复制代码

哇擦擦擦。不报错了,解决了。

这个时候看dist包,里面就多了一个图片

打包的图片

这个时候再引入图片。挂在在页面上。打包就不会出错了

运用占位符

在以上打包图片的过程当中,咱们发现打包生成的图片好像名字是一串乱码,若是咱们要原样输出原图片的名字的话,又该如何进行配置呢?这个问题,可使用 占位符 进行解决。

文件占位符它有一些固定的规则,像下面这样:

[name]表明本来文件的名字
[ext]表明本来文件的后缀
[hash]表明一个md5的惟一编码
复制代码
//webpack.config.js

// path为Node的核心模块
const path = require('path');

module.exports = {
  // 其它配置
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name]_[hash].[ext]'
          }
        }
      }
    ]
  }
}
复制代码

这样配置生成的文件名字就变了

|-- dist
|   |-- avatar_bd7a45571e4b5ccb8e7c33b7ce27070a.jpg
复制代码

说完了file-loader,那什么是url-loader呢

其实url-loader跟file-loader功能差很少,可是url-loader多了个功能就是图片打包的时候多了个配置项 limit

npm install url-loader -D
复制代码
//webpack.config.js

// path为Node的核心模块
const path = require('path');

module.exports = {
  // 其它配置
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            limit:2000 // 字节判断
          }
        }
      }
    ]
  }
}
复制代码

limit 的意思是若是你的图片大小超过2000个字节的话,就跟file-loader吧图片打包到dist目录下,可是若是小于2000字节,就直接默认吧图片转换成base64添加到bundle.js里面。这样就能减小没必要要的图片请求。增长性能。vue脚手架的webpack配置就是这样写的啊

3.使用loader打包静态样式-css-loader/style-loader

css打包部分就很复杂,设置很乱,由于css后缀有不少个文件,.css,.scss,.less 等等。并且css在写的时候,可能会有互相影响的问题,这就互相干扰了,因此打包的时候可能还要配置css模块化,避免互相干扰,别急,一步步来。慢慢把这块啃干净

打包说明

样式文件分为几种状况,每一种都须要不一样的loader来处理:

普通.css文件,使用style-loader和css-loader来处理
.less文件,使用less-loader来处理
.sass或者.scss文件,须要使用sass-loader来处理
.styl文件,须要使用stylus-loader来处理
复制代码

乍一看。我去。这么多loader,啥时候能记住啊,没事,一步步来。先说前两个

css-loader:负责解析 CSS 代码,主要是为了处理 CSS 中的依赖,例如 @import 和 url() 等引用外部文件的声明,好比一个css的文件,引入了另外一个css的文件,css-loader就能够吧全部的css整合到一快。不然就是没法解析了

style-loader:会将 css-loader 解析的结果转变成 JS 代码,运行时动态插入 style 标签来让 CSS 代码生效。

样式生效之后,不会生成一个css的文件夹,webpack会经过 style-loader自动的吧样式加在头部的 style 里面

  1. 打包css文件

首先安装style-loader和css-loader

npm install style-loader -D
npm install css-loader -D
复制代码

如今实战啊,

把页面目录该删删,改加加,改为这个样子,吧头部,内容。脚步的js文件删除,在根目录新建一个src的文件夹,吧index.js移动过去

|-- webpack(名字随便起)
|   |--src
    | |--index.js
    | |--index.css
|   |-- webpack.config.js
|   |-- package.json
复制代码

index.js代码以下

// index.js代码
import './index.css';

img.src = avatar;
img.classList.add('avatar');
root.appendChild(img);

复制代码

index.css代码以下

.avatar{
  width: 200px;
  height: 200px;
  border:1px solid #0f0;
}
复制代码

webpack.config.js代码以下

// path为Node的核心模块
const path = require('path');

module.exports = {
  entry: './src/index.js',  // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
  module:{
    rules:[{
      test:/\.png$/,
      use:{
        loader:'file-loader'
      }
    },
    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader'] // 由于须要两个loader.用数组。
    }]
  },
  output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}
复制代码

若是代码中使用sass怎么办呢。这个时候就是有sass-laoder了

npm install sass-loader -D

npm install node-sass -D
复制代码

下载好之后,修改一下代码

// webpack.config.js代码以下
const path = require('path');

module.exports = {
  entry: './src/index.js',  // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
  module:{
    rules:[{
      test:/\.png$/,
      use:{
        loader:'file-loader'
      }
    },
    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader', 'sass-loader'] // 由于须要两个loader.用数组。
    }]
  },
  output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}
// 这里注意下,loader数组里面。都是从下到上,从右到左执行的,
//先是把scss语法转换为css,
//而后处理css文件的互相引用。
//最后挂载到html上。样式生效。
复制代码

目录中的css文件改成 .scss后缀。里面的内容修改成 嵌套。就是这样

body{
    .avatar{
        .....
    }
}
复制代码

最后记得修改引用这个scss的文件的后缀

执行打包命令。页面上。语法会转换成 css的语法,而且生效。

写到css咱们就不可避免的涉及到兼容啊。浏览器厂商支持程度啊之类的问题,尤为是css3 每一个浏览器厂商支持的,兼容都须要前缀。webpack有这个loader 能够帮咱们实现

  1. 安装这个loader
npm install postcss-lader -d
复制代码
  1. 咱们先修改样式文件,加一个css3的属性transform
body{
  .avatar{
    width: 150px;
    height: 150px;
    border:1px solid #0f0;
    transform: translate(100px,100px)
  }
}
复制代码
  1. 咱们看官网的这个loader配置方式。 官网

  2. 根据官网咱们得知,接下来就是在根目录下新建一个文件

postcss.config.js

module.exports = {
  plugins: [
   require('autoprefixer')
  ]
}
复制代码

别忘了下载这个插件

npm install autoprefixer -D
复制代码

最后别忘了在webpack.config.js里面的loader数组再加一个postcss-loader

执行打包命令。看一下控制台的的样式 会加上厂商前缀

4.css打包的模块化

CSS的模块化打包的理解是:我是一个单独的模块。若是我引用了你的样式,就生效,若是没有引入,那就不要干扰个人样式。

开发中有这中场景。

我是一个独立的模块A。我里面有我本身的一堆东西。这个时候,index.js入口文件全局引入了一个样式。同时index.js引入了A。那么。此时全局的样式就会影响A。这个时候就须要css模块化打包解决问题

修改下目录和里面的内容,吧index.html移动到dist文件下面

|-- webpack(名字随便起)
|   |--src
    | |--1.png
    | |--create.js
    | |--index.js
    | |--index.scss
|   |-- webpack.config.js
|   |-- package.json
复制代码

create.js内容

// create.js内容
import avatar from './1.png';

function create() {
  var root = document.getElementById('root');
  var img = new Image();
  img.src = avatar;
  img.classList.add('avatar');
  root.appendChild(img);
}
export default create
复制代码

index.js内容

// index.js代码
import avatar from './1.png';
import './index.scss';
import create from './avator'

create()

var root = document.getElementById('root');
var img = new Image();
img.src = avatar;
img.classList.add('avatar');
root.appendChild(img);
复制代码

此时修改完之后,执行打包的命令。你会发现,你的独立模块样式被index.css影响了。这可就很差了。怎么办呢

使用css模块化。修改代码 webpack.config.js代码

// path为Node的核心模块
const path = require('path');

module.exports = {
  mode:'development',
  entry: './src/index.js',  // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
  module:{
    rules:[{
      test:/\.png$/,
      use:{
        loader:'url-loader',
        options:{
          name:'[name]_[hash].[ext]',
          outputPath:'images/',
          limit:20000    
        }
      }
    },
    {
      test: /\.scss$/,
      use: ['style-loader', 
            {
              loader:'css-loader',
              options:{
               // 意思是若是一个css引入另外一个css.那么也要把下面的loader走完
                importLoaders:2, 
               // 开启css模块化
                modules:true 
              }
            },
            'sass-loader',
            'postcss-loader'
          ]
    }]
  },
  output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}
复制代码
body{
  .avatar{
    width: 150px;
    height: 150px;
    border:1px solid #0f0;
    transform: translate(100px,100px)
  }
  .mm{
    width: 250px;
    height: 250px;
    border:1px solid #f00;
    transform: translate(100px,100px)
  }
}
复制代码

index.js

// index.js代码
import avatar from './1.png';
import style from  './index.scss';
import create from './avator'

create()

var root = document.getElementById('root');
var img = new Image();
img.src = avatar;
img.classList.add(style.avatar); // 样式使用要求模块化了
root.appendChild(img);

复制代码

avator.js代码

import avatar from './1.png';
import style from './index.scss'
function create() {
  var root = document.getElementById('root');
  var img = new Image();
  img.src = avatar;
  img.classList.add(style.mm); // 这么使用样式,用啥就是用啥,互不干扰
  root.appendChild(img);
}
export default create
复制代码

3、webpacK的plugins使打包更加便捷

plugins 是插件的意思,插件的意思是,在执行的过程当中,来帮助webpack作一些事

上面内容咱们在写的时候,有这样的一个问题。就是webpack 只管吧js css 图片等文件经过loader 打包好之后,就直接放在dist里面,无论了,咱们每次都须要手动建立一个index.html。来引入打包好的js文件,这样多费劲啊,若是能自动生成html.并引入js文件那该多好啊。下面就是插件们登场的时候了

  1. html-webpack-plugin
npm install html-webpack-plugin -D
复制代码

这个插件就能解决上面的问题

咱们如今从新把页面目录删删减减

|-- webpack(名字随便起)
|   |--src
    | |--1.png
    | |--index.js
    | |--index.scss
    | |--index.html
|   |-- webpack.config.js
|   |-- package.json
复制代码

webpack.config.js代码

// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
module.exports = {
  mode:'development',
  entry: './src/index.js',  // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
  module:{
    rules:[{
      test:/\.png$/,
      use:{
        loader:'url-loader',
        options:{
          name:'[name]_[hash].[ext]',
          outputPath:'images/',
          limit:20000    
        }
      }
    },
    {
      test: /\.scss$/,
      use: ['style-loader', 
            {
              loader:'css-loader',
              options:{
                importLoaders:2,
                modules:true
              }
            },
            'sass-loader',
            'postcss-loader'
          ]
    }]
  },
  plugins:[new HtmlWebpackplugin({
  // 注入模板,这个html的内容会注入到自动生成的html里面
    template:'src/index.html' 
  })],
  output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}
复制代码

index.js的代码

// index.js代码
import avatar from './1.png';
import style from  './index.scss';

var root = document.getElementById('root');
var img = new Image();
img.src = avatar;
img.classList.add(style.avatar);
root.appendChild(img);

复制代码

index.html代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
复制代码

如今吧dist 文件夹删除掉,从新打包,会发现dist文件下会自动生成html的文件,并且还自动引入了打包好的js文件

自动生成的html文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
<script type="text/javascript" src="main.js"></script></body>
</html>
复制代码
  1. clean-webpack-plugin

假若有如下场景,咱们须要修改打包好的js文件。好比如今打包好咱们叫bundle.js文件。咱们如今改为dist.js。文件。执行如下打包,咱们发现。dist的文件从新生成一个dist.js的文件。可是。。。可是 bundle.js 这个咱们上次生成的文件竟然还在。这可不行。咱们须要一个插件就是每次生成新的打包js文件,就把之前的清除掉

npm install clean-webpack-plugin -D
复制代码

webpack.config.js代码

// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
 mode:'development',
 entry: './src/index.js',  // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
 module:{
   ......
 },
 plugins:[
   new CleanWebpackPlugin(), // 在打包以前执行,清空全部的dist内容
   new HtmlWebpackplugin({template:'src/index.html'})
 ],
 output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的
   filename: 'mm.js',
   path: path.resolve(__dirname, 'dist')
 }
}
复制代码

注意版本问题,老版本的clean-webpack-plugin可能须要参数,可是新版本的不须要

4、entry和output的基本配置

  1. entry 能够是一个字符串,也能够是一个对象。 (1)假如entry 不设置名字的话,打包就以output的filename的输出为主
entry: './src/index.js', // 入口文件
output: { //输出文件
  filename: 'app.js',
  path: path.resolve(__dirname, 'dist') // path 会在根目录生成一个dist的文件
},
复制代码

这种情形打包出来的是 app.js

(2)若是filename不设置名字,就医entry的名字为主

entry: {
  main:'./src/index.js', // 入口文件
}
output: { //输出文件
  path: path.resolve(__dirname, 'dist') // path 会在根目录生成一个dist的文件
},
复制代码

这种情形打包出来的是 main.js

  1. 有这个需求,须要把打包文件里面的js文件生成两个, 修改entry和output的配置,让output的输出变成一个占位符。这样就能生成两个js
entry: {
  main:'./src/index.js',
  sub:'./src/index.js'
}, // 入口文件
output: { //输出文件
  filename: '[name].js',  
  path: path.resolve(__dirname, 'dist') // path 会在根目录生成一个dist的文件
},
复制代码

3.有时候咱们须要打包好吧index.html给后端做为入口文件,里面引入的js文件前面加上域名,好比cdn什么的,这时候在output里面添加 publicPath:域名,

output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的
  publicPath:'https://ww.cdn.com',
  filename: '[name].js',
  path: path.resolve(__dirname, 'dist')
}
复制代码

打包好的html文件

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="https://ww.cdn.com/aa.js"></script>
<script type="text/javascript" src="https://ww.cdn.com/bb.js"></script>
</body>
</html>
复制代码

官网解释

5、sourceMap的配置

咱们知道webpack把全部的js代码打包到一快,有时候开发环境下没问题,可是生产环境下就有问题。可是代码所有都打包压缩了,根本不知道那里有问题,这个时候sourceMap就出场了。sourceMap顾名思义 就是 资源导图,它会告诉你如今打包好的js文件,运行出错的时候,对应没有打包的js文件的哪一行那一列。这下子就知道怎么修改了。

它是一种映射关系,它映射了打包后的代码和源代码之间的对应关系,通常经过devtool来配置。

webpack.config.js的代码

// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode:'development',
  devtool:'source-map', // 配置这一行
  entry: {

  },  // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
  module:{
    rules:[{
     
    }]
  },
  plugins:[
  
  ],
  output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的

  }
}
复制代码

devtool:的sourceMap的配置不少个选项。涉及到打包速度的快慢问题,这是官网的配置

sourceMap

devtool: 配置的选项

build: 第一次打包的速度

rebuild: 之后打包的速度

production: 是不是生产环境

quality: 打包的质量

  1. devtools:'sourceMap',能够开启这个功能,若是配置了sourcemap.打包的速度会变慢的。

  2. 使用sourcemap之后,你会发现,打包好的文件里面,有个.js.map的映射文件

  3. 官方文档 配置 里面, 有个选项 devtool.里面有很详细的使用方法,

(1)sourceMap.打包出一个xx.js.map的文件

(2)inline-source-map,会把map的文件取消,转换成一个base64的行字符串加在打包的js文件里面.

(3)inline-cheap-source-map,上面的两个会把哪一行,那一列错的位置告诉咱们,可是这个会把那一列去到,提升性能。

(4)cheap-module-eval-source-map,开发使用这个最好,全面,速度还快一点 开发环境

(5)cheap-module-source-map,生产使用这个比较好,mode:producton 生产环境

6、使用WebpackDevServer提高开发的效率

这一个插件很重要,由于跟咱们前端开发平常息息相关。咱们有时候使用vue,react 会发现启动了一个8080端口,3000端口。帮咱们本地开了一个小型的服务。同时ajax请求数据(ajax必须在服务器上才能经过http协议请求数据),都是WebpackDevServer的功劳。搭配热更新效率更加好,下面咱们就详细学习一下

  1. 咱们在开发中会遇到这样的问题,就是每次修改完代码,都须要手动执行打包的命令,而后在dist 文件夹打开index.html的页面,更新了。还要从新打包,回到浏览器刷新,费劲。

  2. 如今有了WebpackDevServer就不用这么费劲了,由于它能时刻监听你是否更新,而且自动打包,生成一个小型本地服务,并自动更新浏览器的页面,那么怎们用呢

npm install webpack-dev-server -D
复制代码

在index.js页面内容里面随便加一行代码

index.js代码

console.log(1233)
复制代码

webpack.config.js代码

// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode:'development',
  devtool:'source-map',
  entry: {
    main:'./src/index.js',
  },  
  devServer:{ // 添加一个devServer的模块,启动小服务,端口8020的(能够本身设置)
    contentBase:'./dist',
    port:8020
  },
  module:{
    rules:[{
      ....
    }]
  },
  plugins:[
   ...
  ],
  output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}
复制代码

package.json里面

{
  "name": "webpack-jue",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "bundle": "webpack", // 更新后须要每次手动的打包。。。。
    "watch": "webpack --watch", // 更新后,不用手动打包,可是要手动刷新浏览器
    "start": "webpack-dev-server" // 只要代码变动,保存,自动打包,刷新浏览器
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "autoprefixer": "^9.6.1",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.1.0",
    "file-loader": "^4.1.0",
    "html-webpack-plugin": "^3.2.0",
    "install": "^0.13.0",
    "node-sass": "^4.12.0",
    "npm": "^6.10.3",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "url-loader": "^2.1.0",
    "webpack": "^4.38.0",
    "webpack-cli": "^3.3.6",
    "webpack-dev-server": "^3.8.0"
  }
}

复制代码

7、Hot Module Replacement 热模块更新

模块热更新(HMR)的理解:它可以让咱们在不刷新浏览器(或自动刷新)的前提下,在运行时帮咱们更新最新的代码。

模块热更新(HMR)已内置到 Webpack ,咱们只须要在webpack.config.js中像下面这样简单的配置便可,无需安装别的东西。

webpack.config.js代码

const webpack = require('webpack');
module.exports = {
  // 其它配置
  devServer: {
    contentBase: 'dist',
    open: true,
    port: 3000,
    hot: true, // 启用模块热更新
    hotOnly: true // 模块热更新启动失败时,从新刷新浏览器
  },
  plugins: [
    // 其它插件
    new webpack.HotModuleReplacementPlugin()
  ]
}
复制代码

在模块热更新(HMR)配置完毕后,咱们如今来想一下,什么样的代码是咱们但愿可以热更新的,咱们发现大多数状况下,咱们彷佛只须要关心两部份内容:CSS文件和.js文件,根据这两部分,咱们将分别来进行介绍。

CSS中的模块热更新

首先咱们在src目录下新建一个style.css样式文件,它的代码能够这样下:

div:nth-of-type(odd) {
  background-color: red;
}
复制代码

随后咱们改写一下src目录下的index.js中的代码,像下面这样子:

import './style.css';

var btn = document.createElement('button');
btn.innerHTML = '新增';
document.body.appendChild(btn);

btn.onclick = function() {
  var dom = document.createElement('div');
  dom.innerHTML = 'item';
  document.body.appendChild(dom);
}
复制代码

因为咱们须要处理CSS文件,因此咱们须要保留处理CSS文件的loader规则,像下面这样 (其余loader配置不用动,单独加这个css规则就行)

webpack.config.js代码

module.exports = {
  // 其它配置
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}
复制代码

在以上代码添加和配置完毕后,咱们使用npm run start进行打包,咱们点击按钮后,它会出现以下的状况 打包结果

理解: 因为item是动态生成的,当咱们要将red颜色改变成yellow时,模块热更新能帮咱们在不刷新浏览器的状况下,替换掉样式的内容。直白来讲:自动生成的item依然存在,只是颜色变了。

在js中的模块热更新

咱们开发中,vue-laoder已经把这个功能内置了,
因此不用单独去配置这个功能
因此不用单独写什么。每次vue的项目保存一下,
页面自动就改变了,就是这个热模快更新的做用
复制代码

在介绍完CSS中的模块热更新后,咱们接下来介绍在js中的模块热更新。

首先,咱们在src目录下建立两个.js文件,分别叫counter.js和number.js,它的代码能够写成下面这样:

// counter.js代码
export default function counter() {
  var dom = document.createElement('div');
  dom.setAttribute('id', 'counter');
  dom.innerHTML = 1;
  dom.onclick = function() {
    dom.innerHTML = parseInt(dom.innerHTML,10)+1;
  }
  document.body.appendChild(dom);
}
复制代码

number.js中的代码是下面这样的:

// number.js代码
export default function number() {
  var dom = document.createElement('div');
  dom.setAttribute('id','number');
  dom.innerHTML = '1000';
  document.body.appendChild(dom);
}
复制代码

添加完以上两个.js文件后,咱们再来对index.js文件作一下小小的改动:

// index.js代码
import counter from './counter';
import number from './number';
counter();
number();
复制代码

在以上都改动完毕后,咱们使用npm run start进行打包,在页面上点击数字1,让它不断的累计到你喜欢的一个数值(记住这个数值),这个时候咱们再去修改number.js中的代码,将1000修改成3000,也就是下面这样修改:

// number.js代码
export default function number() {
  var dom = document.createElement('div');
  dom.setAttribute('id','number');
  dom.innerHTML = '3000';
  document.body.appendChild(dom);
}
复制代码

咱们发现,虽然1000成功变成了3000,但咱们累计的数值却重置到了1,这个时候你可能会问,咱们不是配置了模块热更新了吗,为何不像CSS同样,直接替换便可?

回答:这是由于CSS文件,咱们是使用了loader来进行处理,有些loader已经帮咱们写好了模块热更新的代码,咱们直接使用便可(相似的还有.vue文件,vue-loader也帮咱们处理好了模块热更新)。而对于js代码,还须要咱们写一点点额外的代码,像下面这样子:

import counter from './counter';
import number from './number';
counter();
number();

// 额外的模块HMR配置
if(module.hot) {
  module.hot.accept('./number.js', () => {
    document.body.removeChild(document.getElementById('number'));
    number();
  })
}
复制代码

写完上面的额外代码后,咱们再在浏览器中重复咱们刚才的操做,即:

累加数字1带你喜欢的一个值 修改number.js中的1000为你喜欢的一个值 如下截图是个人测试结果,同时咱们也能够在控制台console上,看到模块热更新第二次启动时,已经成功帮咱们把number.js中的代码输出到了浏览器。 模块热更新结果

小结:在更改CSS样式文件时,咱们不用书写module.hot,这是由于各类CSS的loader已经帮咱们处理了,相同的道理还有.vue文件的vue-loader,它也帮咱们处理了模块热更新,但在.js文件中,咱们仍是须要根据实际的业务来书写一点module.hot代码的。

8、使用bable处理Es6语法

咱们在开发的时候会写到ES6语法,可是这个语法,好多的浏览器不支持, 因此须要各类办法去吧ES6的语法转换我ES5,这就用到了bable 使用以前,先下载三个依赖

// babel-loader是和webpack创建链接的桥梁,
// @babel/core 是bable的核心库
   
$ npm install babel-loader @babel/core --save-dev

// @babel/preset-env这个模块是吧es6转换为es5的模块

$ npm install @babel/preset-env --save-dev

//  @babel/polyfilles5转换为es5语法转了,可是有些变量或者方法,浏览器仍是不
//  认识,因此这个模块就是这个个别的方法或者变量的补丁,

$ npm install @babel/polyfill --save-dev
复制代码

安装完之后,咱们改一下页面的目录 src只留下index.js入口文件就能够了 同事在根目录新建一个.babelrc的文件

|-- webpack(名字随便起)
|   |--src
    | |--index.js
    | |--index.html
|   |-- webpack.config.js
|   |-- package.json
|   |-- .babelrc
复制代码

webpack.config.js的代码,loader部分新加一个babel-loader

// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode:'development',
  devtool:'source-map',
  entry: {
    main:'./src/index.js',
  },  
  devServer:{
   ......
  },
  module:{
    rules:[
      { test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader" ,
      },
      ....
      ]
  },
  plugins:[
   ......
  ],
  output: {  
  ......
  }
}
复制代码

.babelrc文件

{
  "presets": [["@babel/preset-env", {
    "corejs": 2,// 目的是,按需引入须要的es6补充语法,若是不设置,就是全局引入,包会很大。
    "useBuiltIns": "usage" // 若是不配置,polyfill补丁会把全部的es补丁补充上
    ,代码打包很大,这个配置上,按需补充,就会很小
  }]]
}
复制代码

index.js代码

// index.js代码
const arrppp = [
  new Promise(() => {}),
  new Promise(() => {})
]

arrppp.map( item => {
  console.log(item)
})

复制代码

执行打包命令

npx webpack
复制代码

查看main.js的代码,会发现已经转换为es5语法,而且。补充了es6新特性的变量和语法

webpack4 学习之路(5)-高级概念

1、Tree shaking 概念以及如何使用

  • Tree Shaking,树枝摇晃,是一个术语,一般用于描述移除js中未使用的代码。 好比咱们在开发中,有一个模块有大量的方法,可是引用其中一个方法,打包的时候,其余没有用的方法也会打包,就会代码冗余,因此须要设置一下,吧不须要的方法,不打包。

注意

Tree Shaking 只适用于ES
Module语法(既经过export导出,import引入),由于它依赖于ES
Module的静态结构特性。
复制代码

下面咱们从新改造一下目录

咱们须要如今src目录下新建一个math.js文件,它的代码以下:

export function add(a, b) {
  console.log(a + b);
}
export function minus(a, b) {
  console.log(a - b);
}
复制代码

接下来咱们对index.js作一下处理,它的代码像下面这样,从math.js中引用add方法并调用:

import { add } from './math'
add(3, 2);
复制代码

对webpack.config.js作一下配置,让它支持Tree Shaking,它的改动以下:

const path = require('path');
module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: {
    main: './src/index.js'
  },
  ...
  ...
  ...
  optimization: {
    usedExports: true
  },
  ...
  ...
  ...
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname,'dist')
  }
}
复制代码

修改package.json的文件配置,添加一行sideEffects的属性

因为Tree Shaking做用于全部经过import引入的文件,若是咱们引入第三方库,例如:import _ from 'lodash'或者.css文件,例如import './style.css' 时,若是咱们不 作限制的话,Tree Shaking将起反作用,会认为没有导出,就把这个模块所有否认不打包。SideEffects属性能帮咱们解决这个问题:它告诉webpack,咱们能够对哪些文件不作 Tree Shaking

// 若是不但愿对任何文件进行此配置,能够设置sideEffects属性值为false
// *.css 表示 对全部css文件不作 Tree Shaking
// @babael/polyfill 表示 对@babel/polyfill不作 Tree Shaking
"sideEffects": [
  "*.css",
  "@babel/polyfill"
],
复制代码

配置完毕后,咱们依然使用npx webpack进行打包,能够看到,它的打包结果以下所示:

WeChat6bb43657c137362605efd33e85e5d5ff.png

打包代码分析:以上代码是一段被压缩事后的代码,咱们能够看到,上面只有add方法,未使用的minus方法并无被打包进来,这说明在开发环境下咱们的Tree Shaking起了做用。

这是开发环境,若是是生产环境,optimization 这个属性就不用配置了。可是sideEffects仍是须要配置的。打包一下。仍是这个效果,没有用的方法,就不会打包进去。

2、区分开发模式和生产模式

若是咱们要区分Tree Shaking的开发环境和生产环境,那么咱们每次打包的都要去更改webpack.config.js文件,是否是很麻烦啊!

区分开发环境和生产环境,最好的办法是把公用配置提取到一个配置文件,生产环境和开发环境只写本身须要的配置,在打包的时候再进行合并便可,webpack-merge 能够帮咱们作到这个事情。

复制代码

首先,咱们模仿一下vue的脚手架的形式,把 Webpack 相关的配置都放在根目录下的build文件夹下,因此咱们须要新建一个build文件夹,随后咱们要在此文件夹下新建三个.js文件和删除webpack.config.js,它们分别是:

webpack.common.js:Webpack 公用配置文件
webpack.dev.js:开发环境下的 Webpack 配置文件
webpack.prod.js:生产环境下的 Webpack 配置文件
复制代码

新建完webpack.common.js文件后,咱们须要把公用配置提取出来,它的代码看起来应该是下面这样子的:

// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: {
    main:'./src/index.js',
  },  // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
  module:{
    rules:[
      { test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader" ,
      },
      {
      test:/\.png$/,
      use:{
        loader:'url-loader',
        options:{
          name:'[name]_[hash].[ext]',
          outputPath:'images/',
          limit:20000    
        }
      }
    },
    {
      test: /\.scss$/,
      use: ['style-loader', 
            {
              loader:'css-loader',
              options:{
                importLoaders:2,
                modules:true
              }
            },
            'sass-loader',
            'postcss-loader'
          ]
    }]
  },
  plugins:[
    new CleanWebpackPlugin(),
    new HtmlWebpackplugin({template:'src/index.html'})
  ],
  output: {  // 告诉webpack打好包之后放在那个文件夹。dist是自动生成的
    filename: '[name].js',
    path: path.resolve(__dirname, '../dist')
  }
}
复制代码

提取完 Webpack 公用配置文件后,咱们开发环境下的配置,也就是webpack.dev.js中的代码,将剩下下面这些:

// path为Node的核心模块
module.exports  = {
  mode:'development',
  devtool:'source-map',
  devServer:{
    contentBase:'./dist',
    port:8020
  },
  optimization: {
    usedExports:true
  },
}

复制代码

而生产环境下的配置,也就是webpack.prod.js中的代码,多是下面这样子的:

module.exports = {
  mode:'production',
  devtool:'source-map',
  optimization: {
    usedExports:true
  },
}

复制代码

在处理完以上三个.js文件后,咱们须要作一件事情:

  • 当处于开发环境下时,把webpack.common.js中的配置和webpack.dev.js中的配置合并在一块儿
  • 当处于开发环境下时,把webpack.common.js中的配置和webpack.prod.js中的配置合并在一块儿
  • 针对以上问题,咱们可使用webpack-merge进行合并,在使用以前,咱们须要使用以下命令进行安装:
$ npm install webpack-merge -D
复制代码

安装完毕后,咱们须要对webpack.dev.js和webpack.prod.js作一下手脚,其中webpack.dev.js中的改动以下(代码高亮部分):

// path为Node的核心模块
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');

const devConfig = {
  mode:'development',
  devtool:'source-map',
  devServer:{
    contentBase:'./dist',
    port:8020
  },
  optimization: {
    usedExports:true
  },
}

module.exports = merge(commonConfig, devConfig);
复制代码

相同的代码,webpack.prod.js中的改动部分以下(代码高亮): js

// path为Node的核心模块
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');

const prodConfig = {
  mode:'production',
  devtool:'source-map',
  optimization: {
    usedExports:true
  },
}

module.exports = merge(commonConfig, prodConfig);
复制代码

要从新在package.json中配置一下咱们的打包命令,它们是这样子写的:

"scripts": {
  "dev": "webpack-dev-server --config ./build/webpack.dev.js",
  "build": "webpack --config ./build/webpack.prod.js"
},
复制代码

配置完打包命令,心急的你可能会立刻开始尝试进行打包,你的打包目录可能长成下面这个样子:

|-- build
|   |-- dist
|   |   |-- index.html
|   |   |-- main.js
|   |   |-- main.js.map
|   |-- webpack.common.js
|   |-- webpack.dev.js
|   |-- webpack.prod.js
|-- src
|   |-- index.html
|   |-- index.js
|   |-- math.js
|-- .babelrc
|-- postcss.config.js
|-- package.json
复制代码

问题分析:当咱们运行npm run build时,dist目录打包到了build文件夹下了,这是由于咱们把Webpack 相关的配置放到了build文件夹下后,并无作其余配置,Webpack 会认为build文件夹会是根目录,要解决这个问题,须要咱们在webpack.common.js中修改output属性,具体改动的部分以下所示:

output: {
  filename: '[name].js',
  path: path.resolve(__dirname,'../dist')
}
复制代码

那么解决完上面这个问题,赶忙使用你的打包命令测试一下吧,个人打包目录是下面这样子,若是你按上面的配置后,你的应该跟此目录相似

|-- build
|   |-- webpack.common.js
|   |-- webpack.dev.js
|   |-- webpack.prod.js
|-- dist
|   |-- index.html
|   |-- main.js
|   |-- main.js.map
|-- src
|   |-- index.html
|   |-- index.js
|   |-- math.js
|-- .babelrc
|-- postcss.config.js
|-- package.json
复制代码

3、webpack的code splitting 代码分割

每次面试都会问,你是怎么进行代码分割的,如今终于能够说出来了。

其实代码分割跟webpack 并无实质联系。只是webpack 如今内置的插件能够帮咱们进行代码分割,全部就绑在一块了。

Code Splitting 的核心是把很大的文件,分离成更小的块,让浏览器进行并行加载。

假如项目打包业务代码1m,引入的第三方库打包也是1m,若是不分割,

webapck就会打包出一个2m的文件,每次加载,都要加载一个2m的文件。很占空间

若是使用代码分割,就是打包出两个1m的文件。

咱们知道,浏览器执行js代码,是能够并行加载的。因此速度会提高

另外,咱们修改业务代码。第三方代码不改变,用户从新刷新,就只会

在单独加载一个1m的文件就行了。
复制代码

常见的代码分割有三种形式:

手动进行分割:例如项目若是用到lodash,则把lodash单独打包成一个文件。

同步导入的代码:使用 Webpack 配置进行代码分割。

异步导入的代码:经过模块中的内联函数调用来分割代码。
复制代码

1. 手动进行分割

手动进行分割的意思是在entry上配置多个入口,例如像下面这样:

module.exports = { entry: { main: './src/index.js', lodash: 'lodash' } } 这样配置后,咱们使用npm run build打包命令,它的打包输出结果为:

Asset       Size  Chunks             Chunk Names
  index.html  462 bytes          [emitted]
    lodash.js   1.46 KiB       1  [emitted]  lodash
lodash.js.map   5.31 KiB       1  [emitted]  lodash
      main.js   1.56 KiB       2  [emitted]  main
  main.js.map   5.31 KiB       2  [emitted]  main
复制代码

它输出了两个模块,也能在必定程度上进行代码分割,不过这种分割是十分脆弱的,若是两个模块共同引用了第三个模块,那么第三个模块会被同时打包进这两个入口文件中,而不是分离出来。(强烈不推荐)

因此咱们常见的作法是关心后两种代码分割方法,不管是同步代码仍是异步代码,都须要在webpack.common.js中配置splitChunks属性,像下面这样子:

module.exports = {
  // 其它配置
  ...
  ...
  ...
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
  ...
  ...
  ...
}
复制代码

你可能已经看到了其中有一个chunks属性,它告诉 Webpack 应该对哪些模式进行打包,它的参数有三种:

  • async:此值为默认值,只有异步导入的代码才会进行代码分割。
  • initial:与async相对,只有同步引入的代码才会进行代码分割。
  • all:表示不管是同步代码仍是异步代码都会进行代码分割

以上只是大概介绍一下同步代码分割和异步代码分割的共同配置项。下面详细讲解。

2. 同步代码分割

首先,咱们先介绍同步代码分割,所谓的同步代码分割,就是引入第三方库,正常使用,下面咱们安装一个第三方库,例如:lodash,

npm install loadsh --save
复制代码

而后对index.js中的代码作一些手脚,像下面这样:

import _ from 'lodash'
console.log(_.join(['Dell','Lee'], ' '));
复制代码

就像上面提到的那样,同步代码分割,咱们只须要在webpack.common.js配置chunks属性值为initial便可:

module.exports = {
  // 其它配置
  optimization: {
    splitChunks: {
      chunks: 'initial'
    }
  }
}
复制代码

在webpack.common.js配置完毕后,咱们使用npm run build来进行打包, 你的打包dist目录看起来应该像下面这样子:

|-- dist
|   |-- index.html
|   |-- main.js
|   |-- main.js.map
|   |-- vendors~main.js
|   |-- vendors~main.js.map
复制代码

打包分析:main.js使咱们的业务代码,vendors~main.js是第三方模块的代码,在此案例中也就是_lodash中的代码。因而可知,业务代码main和第三方的代码vendors~main.js分割了。

3. 异步代码分割

若是咱们只须要针对异步代码进行代码分割的话,咱们只须要进行异步导入,Webpack会自动帮咱们进行代码分割,异步代码分割它的配置以下:

module.exports = {
  // 其它配置
  optimization: {
    splitChunks: {
      chunks: 'async'
    }
  }
}
复制代码

注意:因为异步导入语法目前并无获得全面支持,须要经过 npm 安装

$ npm install @babel/plugin-syntax-dynamic-import -D 插件来进行转译
复制代码

安装完毕后,咱们须要在根目录下的.babelrc文件作一下改动,像下面这样子:

{
  "presets": [["@babel/preset-env", {
    "corejs": 2,
    "useBuiltIns": "usage"
  }]],
  "plugins": ["@babel/plugin-syntax-dynamic-import"]
}
复制代码

配置完毕后,咱们须要对index.js作一下代码改动,让它使用异步导入代码块:

// 点击页面,异步导入lodash模块
document.addEventListener('click', () => {
  getComponent().then((element) => {
    document.getElementById('root').appendChild(element)
  })
})

function getComponent () {
  return import(/* webpackChunkName: 'lodash' */'lodash').then(({ default: _ }) => {
    var element = document.createElement('div');
    element.innerHTML = _.join(['Dell', 'lee'], ' ')
    return element;
  })
}
复制代码

写好以上代码后,咱们一样使用npm run build进行打包,dist打包目录的输出结果以下:

|-- dist
|   |-- 1.js
|   |-- 1.js.map
|   |-- index.html
|   |-- main.js
|   |-- main.js.map
复制代码

咱们在浏览器中运行dist目录下的index.html,切换到network面板时,咱们能够发现只加载了main.js,

当咱们点击页面时,才 真正开始加载 第三方模块,

4. SplitChunksPlugin配置参数详解

webpack能进行代码分割的核心就是使用了SplitChunksPlugin这个插件, 这个插件有不少能够配置的属性,它也有一些默认的配置参数,它的默认配置参数以下所示,咱们将在下面为一些经常使用的配置项作一些说明。

打开官网,找到这个插件 SplitChunksPlugin

module.exports = {
 // 其它配置项
 optimization: {
   splitChunks: {
     chunks: 'async', // 异步仍是同步分割代码
     minSize: 30000, // 若是模块大于30k就开始分割。不然就不分割
     minChunks: 1, // 改模块必须引用一次以上才分割
     maxAsyncRequests: 5, // 默认就好了
     maxInitialRequests: 3,// 默认就好了
     automaticNameDelimiter: '~',// 默认就好了
     name: true,
     cacheGroups: { 
       vendors: { //分组,若是模块知足在module包里面,就打包成vender.js形式
         test: /[\\/]node_modules[\\/]/,
         priority: -10// 值越大。越服从谁,好比一个loadsh的包,符合第一个组,也符合默认,就看priority的值,越大就打包到哪一个组
       },
       default: { //分组,若是模块不在module包里面,打包成default.js形式
         minChunks: 2,
         priority: -20,
         reuseExistingChunk: true // 若是一个模块已经被打包了,在遇到的时候,就忽略掉,直接使用之前的包
       }
     }
   }
 }
};
复制代码

参数详细说明

# minSize
minSize默认值是30000,也就是30kb,当代码超过30kb时,才开始进行代码分割,小于30kb的则不会进行代码分割;与minSize相对的,maxSize默认值为0,为0表示不限制打包后文件的大小,通常这个属性不推荐设置,必定要设置的话,它的意思是:打包后的文件最大不能超过设定的值,超过的话就会进行代码分割。

为了测试以上两个属性,咱们来写一个小小的例子,在src目录下新建一个math.js文件,它的代码以下:

export function add(a, b) {
  return a + b;
}
新建完毕后,在index.js中引入math.js:

import { add } from './math.js'
console.log(add(1, 2));
打包分析:由于咱们写的math.js文件的大小很是小,若是应用默认值,它是不会进行代码分割的,若是你要进一步测试minSize和maxSize,请自行修改后打包测试。

--------------------------------------------------------

# minChunks

默认值为1,表示某个模块复用的次数大于或等于一次,就进行代码分割。

若是将其设置大于1,例如:minChunks:2,在不考虑其余模块的状况下,如下代码不会进行代码分割:

// 配置了minChunks: 2,如下lodash不会进行代码分割,由于只使用了一次 
import _ from 'lodash';
console.log(_.join(['Dell', 'Lee'], '-'));
--------------------------------------------------------


# maxAsyncRequests 和 maxInitialRequests
maxAsyncRequests:它的默认值是5,表明在进行异步代码分割时,前五个会进行代码分割,超过五个的再也不进行代码分割。
maxInitialRequests:它的默认值是3,表明在进行同步代码分割时,前三个会进行代码分割,超过三个的再也不进行代码分割。

--------------------------------------------------------

# automaticNameDelimiter
这是一个链接符,左边是代码分割的缓存组,右边是打包的入口文件的项,例如vendors~main.js
--------------------------------------------------------

# cacheGroups
说明

在进行代码分割时,会把符合条件的放在一组,而后把一组中的全部文件打包在一块儿,默认配置项中有两个分组,一个是vendors和default

vendors组: 如下代码的含义是,将全部经过引用node_modules文件夹下的都放在vendors组中

vendors: {
  test: /[\\/]node_modules[\\/]/,
  priority: -10
}
default组: 默认组,意思是,不符合vendors的分组都将分配在default组中,若是一个文件即知足vendors分组,又知足default分组,那么经过priority的值进行取舍,值最大优先级越高。

default: {
  minChunks: 2,
  priority: -20,
  reuseExistingChunk: true
}
--------------------------------------------------------
# reuseExistingChunk
中文解释是复用已存在的文件。意思是,若是有一个a.js文件,它里面引用了b.js,但咱们其余模块又有引用b.js的地方。开启这个配置项后,在打包时会分析b.js已经打包过了,直接能够复用不用再次打包。

// a.js
import b from 'b.js';
console.log('a.js');

// c.js
import b from 'b.js';
console.log('c.js');
复制代码

4、shimming

有时候咱们在引入第三方库的时候,不得不处理一些全局变量的问题,例如jQuery的‘$’符号,

lodash的_符号,但因为一些老的第三方库不能直接修改它的代码,每次每一个页面使用。都须要单独的import ....,这样真的很费劲。这时咱们能不能定义一个全局变量,当文件中存在$或者_的时候自动的帮他们引入对应的包。

  • 咱们可使用ProvidePlugin插件来解决,这个插件已经被 Webpack 内置,无需安装,直接使用便可。

src目录下新建一个juqery.js的文件

export function ui() {
  $('body').css('background','green')
}
复制代码

index.js

import _ from 'lodash';
import $ from 'jquery';
import { ui } from './jquery';

ui();

var dom = $(`<div>${_.join(['ji', 'xin'], '---')}</div>`);
$('#root').append(dom);
复制代码

这个时候npm run dev 打开页面。。你会发现报错,$ 符号找不到的错误

为啥呢? 由于index.js虽然引用了jquery的包。并且使用了ui的模块。可是模块之间是解耦的,没有关系。因此jquery的模块没有$的引用。为此使用ProvidePlugin插件能够在webpack帮忙全局引用

webpack.common.js

// path为Node的核心模块
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack')

module.exports = {
  entry: {
    main:'./src/index.js',
  },  // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
  module:{
    rules:[
    ...
    ...
    }]
  },
  optimization: {
   ...
   ...
  },
  plugins:[
    new CleanWebpackPlugin(),
    new HtmlWebpackplugin({template:'src/index.html'}),
    new webpack.ProvidePlugin({
      $: 'jquery',
      _: 'lodash'
    })
  ],
}
复制代码

再从新打包,就没有问题了。

4、webpack的性能优化

webpack若是配置好了,就能够增长打包的速度,因此webpack的性能问题也是一个面试或者工做中常见的问题。

相关文章
相关标签/搜索