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的性能問題也是一個面試或者工做中常見的問題。

相關文章
相關標籤/搜索