1. 程式人生 > 實用技巧 >第一節:webpack打包、壓縮及相容性處理

第一節:webpack打包、壓縮及相容性處理

一.前言

當我們使用vue-cli3建立專案時,會自動生成相應的webpack配置,不過明白webpack的原理和基本設定方法對我們區域性修改某些webpack配置還是很有必要的;

二.為什麼需要構建工具?

  • 轉換ES6語法;
  • 轉換JSX
  • CSS字首補全/前處理器;
  • 壓縮混淆;
  • 圖片壓縮;

官方文件

三.Webpack五個核心概念

1.入口(Entry)

入口(Entry)指示webpack以哪個檔案為入口起點開始打包,分析構建內部依賴圖;

2.出口(Output)

輸出(Output)指示Webpack打包後的資源bundles輸出到哪裡去,以及如何命名;

3.Loader

Loader

Webpack能夠去處理那些非JavaScript檔案(Webpack自身只理解JavaScript

4.外掛(Plugins)

外掛(Plugins)可以用於執行範圍更廣的任務,外掛的作用範圍包括,從打包優化和壓縮,一直到重新定義環境中的變數等;

5.模式(Mode)

通過設定 mode 引數為 developmentproduction 之中的一個,來啟用相應模式下 webpack 內建的優化;

module.exports = {
  mode: 'production'
};
  • development模式:程式碼本地除錯和執行的環境;
  • production模式:程式碼優化上線執行的環境;

四.原始碼倉庫

webpack系列中使用到的演示例項原始碼已上傳至該倉庫:

webpack-demo

示例:

建立下圖所示的檔案目錄:

其中src表示原始檔目錄,儲存著webpack的入口起點檔案,比如index.jsbuild用於webpack打包處理之後輸出的目錄,其餘檔案可通過執行:

可通過下列指令建立一個演示例項:

npm init//生成package.json
webpack_test //資料夾名字

一路回車取預設值生成:

隨後執行以下命令全域性安裝webpack,其中的-g引數表示全域性安裝,即使已經安裝過了,也沒關係。該指令會覆蓋原來的安裝並進行版本更新。

五.npm i 和 npm install 的區別

iinstall的縮寫;

實際使用的區別點主要如下(windows下):

  1. npm i安裝的模組無法用npm uninstall刪除,用npm uninstall i才解除安裝掉

  2. npm i會幫助檢測與當前node版本最匹配的npm包版本號,並匹配出來相互依賴的npm包應該提升的版本號

  3. 部分npm包在當前node版本下無法使用,必須使用建議版本

  4. 安裝報錯時intall肯定會出現npm-debug.log 檔案,npm i不一定

npm i webpack webpack-cli -g

然後在本地安裝,引數-D--save-dev的縮寫,表示它會將webpack新增到package.json中的開發依賴中:

npm i webpack webpack-cli -D

webpack中下載的所有東西都屬於開發依賴,不屬於生產依賴,所以都使用-D

index.js檔案內容:

/*
  index.js:webpack入口起點檔案

  1.執行指令:
    開發環境指令:webpack ./src/index.js -o ./build/built.js --mode=development
    翻譯:webpack會以./src/index.js為入口環境開始打包,打包後輸出 ./build/built.js;整體打包環境是開發環境;程式碼不會被壓縮,可以看清楚邏輯;
    
    生產環境指令:webpack ./src/index.js -o ./build/built.js --mode=production
    翻譯:webpack會以./src/index.js為入口環境開始打包,打包後輸出 ./build/built.js;整體打包環境是生產環境;程式碼會被壓縮,無法看清程式碼邏輯;
*/

function add(x, y){
  return x + y
}

console.log(add(1, 2));

先進入02-webpack初體驗目錄,然後打包檔案

開發環境指令:

輸入下列開發環境指令打包檔案:

webpack ./src/index.js -o ./build/built.js --mode=development

輸出結果:

  • 其中的Hash值代表打包的結果,每次打包都會生成一個唯一的雜湊值,唯一地ID
  • 其餘的有版本,打包時間,資源Asset,打包後的檔案built.js的大小等;

此時build目錄下會多出一個built.js檔案

生產環境指令

輸入下列生產環境指令打包檔案:

webpack ./src/index.js -o ./build/built.js --mode=production

執行結果:

檢視生成的build目錄下的built.js檔案,發現程式碼進行了壓縮:

這個程式碼是可執行的:

每次src資料夾中的入口檔案index.js引入了新依賴之後,都要重新進行打包,才能看到最新的結果;

也就是說只要指定了入口檔案index.js,就可以在index.js中通過import引入很多依賴檔案,這樣

webpack在打包入口檔案index.js的時候就會根據其中引入關係,一起打包index.js的依賴檔案;

那麼引入其他檔案呢?

比如在index.js中引入css檔案的時候:

會出現打包錯誤,打包出來的built.js中的該部分會丟出一個錯誤:

得出結論:

  • webpack能處理js/json資源,不能處理css/img等資源;
  • 生產環境和開發環境將ES6模組化編譯成瀏覽器能識別的模組化;
  • 生產環境(production)比開發環境(development)多了一個壓縮js程式碼;

六.打包樣式資源

雖然webpack不能直接打包css檔案,但是可以藉助於各種各樣的Loaderwebpack不能識別的檔案轉換成它能識別的格式;

需要在根目錄的package.json.js檔案中進行配置:

整體配置為:

const { resolve } = require('path')

module.exports= {
  entry: './src/index.js',
   //輸出:這是一個物件,裡面有兩個東西。filename表示輸出的檔名,path表示輸出的路徑,寫路徑的時候通常會寫一個絕對路徑,避免出錯。絕對路徑會引入一個nodejs的path模組
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules:[ 
      {
      //詳細loader配置
        //匹配哪些檔案
        test: /\.css$/,
        //處理這些檔案
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    //詳細plugins配置
  ],
  mode: 'development',
  //mode: 'production'
 }

首先, webpack.json.jswebpack的配置檔案。作用為:指示webpack幹哪些活(當執行webpack指令時,要載入哪些配置);

所有構建工具都是基於node.js平臺執行的,模組化預設採用commonjs。可以看到commonjs會匯出一個物件,在該物件中寫webpack的配置:

1.入口起點

  entry: './src/index.js'

表示打包的入口檔案為src目錄下的index.js檔案;

2.輸出

  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
   }

這是一個物件,其中:

  • filename表示輸出的檔名;

  • path表示輸出的路徑;

  • 寫路徑的時候為了避免出錯,通常會寫一個絕對路徑。需要引入一個nodejs的模組path模組:

    const { resolve } = require('path')
    

    其中的resolve是用來拼接絕對路徑的方法,格式為:

     path: resolve(__dirname, 'build')
    

    傳入兩個引數:__dirname和當前的build目錄;其中__dirnamenodejs的變數,它代表當前檔案(指webpack.config.js)的目錄的絕對路徑:

  • 該絕對路徑就是03-打包樣式資源,也就是說__dirname的值就是03-打包樣式資源,拼接上build,再加上第一個引數filename指明的built.js一起表示,打包後輸出到build目錄下的built.js檔案中;

3.Loader配置

module: {
  rules: {
  	//詳細loader配置
  }
}

4.外掛(plugins

  plugins: [
    //詳細plugins配置
  ],

5.模式

  mode: 'development',
  //mode: 'production'

開發模式development和生產模式production兩種模式選一種,不能同時寫;

6.打包樣式資源

可以使用css-loader來翻譯css檔案:

module: {
    rules: [ 
        {
            //詳細loader配置
            //使用正則表示式指定匹配哪些檔案
            test: /\.css$/,//該正則表示式表明,匹配以css結尾的檔案,\為轉義符
            //通過test的正則表示式匹配了檔案後,這裡指定使用哪些loader進行處理
            use: [
                //需要使用兩個loader
                //作用:建立style標籤,將js中的樣式資源插入進去,新增到head中生效
                'style-loader',
                //將css檔案轉換成一個commonjs模組並載入到js中,裡面內容是樣式字串
                'css-loader'
            ]       
        }
    ]
}

注意:rules屬性是一個數組,裡面的每一個元素都為物件,每個物件匹配並處理一類檔案。並且物件中的use屬性也是一個數組,其中loader的執行順序為:從右到左,從下到上依次執行。為了不用每次都安裝同樣的依賴檔案包,可以在根目錄執行下列指令安裝這些包,因為子目錄中找不到包會依次往根目錄找:

npm init //生成package.json
npm i webpack webpack-cli -D //下載webpack包,-D是--save-dev的縮寫,表示是開發時依賴
npm i css-loader style-loader -D //下載兩個loader,都是開發時依賴

進入03-打包樣式資源目錄,執行webpack進行打包:

開啟打包生成的built.js,可以看到index.css被成功打包了:

build目錄下新建index.html引入生成的打包檔案built.js

<script src="./built.js"></script>

隨後使用瀏覽器開啟該檔案,發現樣式發生了變化,原始碼多了style標籤

這便是style-loader的作用。

注意:不同型別的檔案要配置不同的loader來處理;比如為了處理less檔案,需要webpack.config.js中的rules陣列中再增添一個物件:

  module: {
    rules:[ 
      //匹配並處理css檔案
      {
        test: /\.css$/,
        //處理這些檔案
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      //匹配並處理less檔案
      {
       test: /\.less$/,
       use: [
         //以style標籤的形式在head標籤中插入樣式
         'style-loader',
         //將css檔案裝換為js形式
         'css-loader',
         //將less檔案編譯成css檔案
         'less-loader'
       ] 
      }
    ]
  }

可以看到處理less檔案需要三個loader,注意loader執行的順序為由下往上,由右往左:

  • less-loader:將less檔案編譯成css檔案;
  • css-loader:將css檔案內容整合到js中;
  • style-loader:從js中找到這些css程式碼,將它傳入style標籤,插入head標籤中;

所以處理less檔案需要使用三個loader,注意:使用loader之前要先進行下載:

npm i less less-loader -D //全域性安裝less和less-loader

注意:最好統一在根目錄下載包,這樣其他子目錄向上查詢時都能找到相應的包,避免重複下載;

安裝完依賴包,並且正確配置package.config.js之後,執行webpack指令,就可以成功打包less檔案了:

七.打包html資源

1.新增基本配置

首先給webpack.config.js新增基本的配置:

const {resolve} = require('path')

module.exports = {
  entry : './src/index.js',
  output : {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      //laoder的配置
    ]
  },
  plugins: [
    //plugins的配置
  ],
  //為了方便除錯,使用不壓縮程式碼的development模式
  mode: 'development'
}

打包html檔案需要使用外掛:

  • 使用loader:下載 、使用(配置laoder);
  • 使用plugins: 下載 、引入 、使用;

2.下載和引入外掛

首先下載外掛,同樣採用全域性下載,和開發時依賴:

npm i html-webpack-plugin -D

然後引入外掛:

const HtmlWebpackPlugin = require('html-webpack-plugin')

由於它是建構函式,直接new一個例項就可以使用了:

  plugins: [
    //plugins的配置
    new HtmlWebpackPlugin()
  ],

3.打包檔案

我們執行webpack打包看看有什麼效果:

注意要進入專案目錄04-打包html資源再進行打包;

build目錄下多了一個html檔案:

開啟該html檔案:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
  <meta name="viewport" content="width=device-width, initial-scale=1"></head>
  <body>
  <script src="built.js"></script></body>
</html>

發現自動引入了built.js檔案,注意,src目錄下的原始檔index.html中是沒有引入過built.js檔案的。

所以,外掛html-webpack-plugin的作用是,建立一個空的HTML檔案,自動引入打包輸出的所有資源(包括js/css);

如果想要打包出有結構的html檔案,則需要給該外掛傳入一個物件,裡面有模板引數template

  plugins: [
    //plugins的配置
    new HtmlWebpackPlugin({
      //複製一個/src/index.html檔案,並自動引入打包輸出的所有資源
      template: './src/index.html'
    })
  ],

此時再次執行webpack打包,打包出來的html檔案就擁有了src目錄下index.html檔案的所有結構,也就是原樣複製了一份。

4.完整配置

webpack.config.js的完整配置:

const {resolve} = require('path')
//引入外掛
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry : './src/index.js',
  output : {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      //laoder的配置
    ]
  },
  plugins: [
    //plugins的配置
    new HtmlWebpackPlugin({
      //複製一個/src/index.html檔案,並自動引入打包輸出的所有資源
      template: './src/index.html'
    })
  ],
  //為了方便除錯,使用不壓縮程式碼的development模式
  mode: 'development'
}

5.總結

我們在入口檔案index.js中並沒有引入html檔案。這個外掛會根據template屬性指定的路徑去尋找該檔案,並且把該檔案的內容複製進來並輸出。只不過在輸出之前會將打包生成的所有資源都引入到這個複製的html檔案中。如果是js檔案,就通過script標籤引入;如果是css檔案就通過link標籤引入;

需要注意的是,千萬不要手動引入這些html檔案,因為外掛幫我們自動引入了這些檔案,我們再引入就重複了。

注意,與loader的使用不同,使用外掛時要多一步引入操作;

八.打包圖片資源

專案目錄如下:

在入口檔案index.js中引入樣式檔案index.lessindex.html檔案不用引入,該檔案會由外掛自動引入;在樣式檔案index.less中又引入圖片資源small.pngbig.png;通過配置webpack.config.js來打包這些檔案:

1.初始配置

const { resolve }  = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path:resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          'less-loader'
        ]
      },
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }
    )
  ],
  mode: 'development'
}

此時沒有新增處理圖片的loader,執行webpack時會出現錯誤:

注意:webpack不能識別資源的時候,第一時間就要想到loader。新增以下loader處理圖片資源:

rules: [
    //匹配並處理圖片
    {
        test: /\.(jpg|png|gif)$/,
        //只使用一個loader可以不用use陣列
        loader: 'url-loader', 
        options: {
            limit: 8 * 1024
        }
    }
]

只使用一個loader時可以不使用use陣列;使用多個loader時需要使用use陣列。還可以通過option物件進行引數的配置;

關於options物件中的limit屬性:

  • 規定圖片大小小於8kb,就會被base64處理。這樣的好處是:減少伺服器的請求數量,減輕伺服器壓力;缺點是:圖片體積會增大,導致檔案請求速度更慢;
  • 所以就需要找到一個平衡點,我們規定8~12KB以下的圖片進行base64處理。所以上面limit中配置的8就表示小於等於8KB的圖片都進行base64處理。開發中可根據實際需要適當調整這個閾值;

2.打包圖片資源

注意,需要下載兩個loaderurl-loaderfile-loader,因為前者依賴於後者;

回到根目錄下,下載這兩個包,依舊是開發時依賴:

npm i url-loader file-loader -D

安裝完成後執行webpack進行打包:

可以看到,成功地對兩張圖片進行了打包。

檢視打包檔案輸出的目錄build,發現少了一張圖片:

開啟built.js,可以發現,小於8KBsmall.png被裝換為了base64編碼,所以會少了一張圖片:

我們再開啟打包出來的index.html檔案,可以看到,small.png也正常顯示了;

開啟除錯工具,選中small/png,可以看到該圖片被轉換成了base64編碼:

3.打包html檔案中的圖片

上面是通過樣式檔案index.less引入的圖片,那麼通過src目錄下的index.html檔案引入圖片能被正常打包嗎:

  <img src="./big.png" alt="">

執行webpack再次打包,發現並沒有報錯:

但是,檢視輸出資料夾下的index.html檔案,發現圖片的引入路徑並沒有發生變化:

這顯然是不對的,打包過後,輸出資料夾build中顯然不會存在big.png。因此得出結論:url-loader不能處理html中的圖片。

我們可以新增一個html-loader來處理:

rules: [
      {
        test: /\.html$/,
        loader: 'html-loader'
      }
]

不能對該loader顧名思義,它是專門處理html中的img圖片的,負責引入img從而能被url-loader進行處理;

再次提醒:凡是使用到loader都是要先下載;

npm i html-loader -D

隨後打包出來的index.html中的圖片路徑就是正確的了:

較低版本的webpack可能會出現解析問題:打包出來的圖片路徑為[object module]。這是由於url-loader採用的是es6模組化解析,而html-loader引入圖片時採用的是commonjs而發生了衝突;

解決方法為:通過設定esModule: false,來關閉url-loaderes6模組化,使用commonjs解析:

rules: [
    {
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader', 
        options: {
            limit: 8 * 1024,
            esModule: false
        }
    }
]

注意到,打包過後的圖片是一串雜湊值,比較長,可以通過name屬性重新命名打包後的圖片:

rules: [
    {
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader', 
        options: {
            limit: 8 * 1024,
            name: '[hash:10].[ext]'
        }
    }
]
  • [hash:10]表示:取圖片雜湊值的前十位;

  • [ext]表示:取檔案的原副檔名;

再次打包,可以看見另外生成了雜湊值只有十位的圖片:

還有一個細節:在樣式index.less中我們引入了三張圖片,兩張是相同的;webpack不會打包出三張圖片,它會進行識別,不打包重複的檔案,這也是webpack的優點之一;

4.完整配置

至此webpack的完整配置為:

const { resolve }  = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path:resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        //要使用多個loader時使用use陣列
        use: [
          'style-loader',
          'css-loader',
          'less-loader'
        ]
      },
      //匹配並處理圖片
      {
        test: /\.(jpg|png|gif)$/,
        //需要下載url-loader 和 file-loader
        loader: 'url-loader', 
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]'
        }
      },
      {
        test: /\.html$/,
        //該loader專門處理html中的img圖片
        loader: 'html-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }
    )
  ],
  mode: 'development'
}

九.打包其他資源

1.安裝file-loader

所謂其他資源,指的是自定義的資源,可能是字型檔案,指令碼檔案等。我們希望這些檔案不需要進行壓縮或其他處理,直接原封不動地打包到輸出資料夾中。

比如打包iconfont檔案,需要採用專門的laoder進行處理:

module: {
    rules: [
        {
	      exclude: /\.(css|js|html)$/,
          loader: 'file-loader',
        }
    ]
}

打包其他資源(除了html/css/js以外的資源),可以使用exclude屬性排除其他資源。使用file-loader進行打包。

使用前需要在根目錄安裝這個loader

npm i file-loader -D

2.打包

隨後對src檔案下的iconfont檔案和其他檔案進行打包:

打包出來的檔案目錄為:

執行其中的index.html可以正常看到iconfont,說明打包成功:

同樣也可以新增option屬性,重新命名打包過後的檔名:

重新打包成功生成重新命名後的檔案:

3.完整配置

此時webpack的完整配置為:

const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules:[
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      //打包其他資源(除了html/css/js以外的資源),可以使用exclude屬性排除其他資源
      {
        exclude: /\.(css|js|html)$/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }) 
  ],
  mode: 'development'
}

十.devServer

當我們每次給src目錄下新增檔案和內容時,都需要重新打包。這顯然很麻煩,webpack也考慮到這一點,因此提供了devServer的功能。

1.安裝devServer

開發伺服器 devServer:用於自動化(自動編譯,自動開啟瀏覽器,自動重新整理瀏覽器);
特點:只會在記憶體中編譯打包,不會有任何輸出;
啟動devServer指令為:webpack-dev-server

首先,需要在根目錄下載該包,同樣採用開發時依賴:

npm i webpack-dev-server -D

2.配置devServer

webpack.config.js中配置devServer,它與五個基本配置是同一等級的:

  devServer: {
    //代表執行時的目錄(打包後目錄),也是使用字串拼接絕對目錄
    contentBase: resolve(__dirname, 'build'),
    //該引數表示啟動gzip壓縮
    compress: true,
    //埠號
	port: 8081,
    //自動開啟瀏覽器
    open: true
  }

3.開啟devServer

開啟時新增npx,找到該指令:

 npx  webpack-dev-server

成功執行後,可通過http://localhost:8081埠檢視執行結果:

能夠成功顯示:

此時只要改變src目錄下的檔案,都會進行自動編譯。這樣不用頻繁輸入webpack重新打包就可以實時看到變化了。

一旦開啟devServer它就會一直執行,可以通過ctrl+ c關閉它:

也可以通過改變引數portopen來設定埠和是否自動開啟瀏覽器,注意:只要重新配置了webpack.config.js就需要重啟devServer

npx webpack-dev-server

我們將打包輸出目錄build下的檔案都刪除,再次執行上述指令。打包過後,build目錄下並不會生成任何檔案。這就驗證了:devServer只會在記憶體中編譯打包,不會有任何輸出的特點。

十一.開發環境的基本配置

通過前面知識的學習,我們學會了打包樣式資源,html資源,圖片資源和其他資源。以及學會了通過devServer開啟熱更新。現在我們便可以開始配置基本的開發環境了;

1.開發環境基本配置

webpack.config.js的配置如下:

/**
 * 開發環境配置
 */

const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports ={
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      //loader的配置
      //處理less資源
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      //處理css資源
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      //處理圖片資源
      {
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          // esModule: false
        }
      },
      //處理html中圖片的引入
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      //處理其他資源
      {
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]'
        }
      }
    ]
  },
  plugins: [
    //plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true
  }
}

2.優化輸出目錄

首先我們整理一下src目錄下的檔案,並修改webpack.config.js中的路徑,隨後使用webpack打包。打包出來的build目錄下的檔案還是很亂:

如果想要打包出來的build目錄下的檔案能與src目錄結構相同呢?

可以給webpack.config.js中的output中的filename新增字首,這樣打包過後的檔案會自動建立這個指定的目錄:

在處理圖片時,只需要新增outputPath屬性就能指定打包後的目錄結構了:

重新打包,圖片檔案被打包進了imgs目錄:

同理,可以在處理其他資源的loaderoption屬性中新增outputPath屬性指定打包後的目錄結構:

注意了,主要的打包輸出目錄是由五大配置之一的output中的path屬性決定的,上面的這一屬性為build。所以,之後使用outputPath指定的路徑都要拼接在build後面。最後形成完整的路徑。

十二.單獨打包CSS檔案

從現在開始我們將部署生成壞境;

常見錯誤:

 ERROR in Entry module not found: Error: Can't resolve 'D:\webpack5\09-提取css成單獨檔案\src\index.html' in 'D:\webpack5\09-提取css成單獨檔案'

上面的這種錯誤大部分都是由於相關檔案路徑錯誤導致的。

1.安裝mini-css-extract-plugin

可通過以下指令安裝mini-css-extract-plugin外掛,提取打包檔案built.js中的css檔案:

npm i mini-css-extract-plugin -D
  • 第一步:為了使用該外掛:首先引入該外掛:
const miniCssExtractPlugin = require('mini-css-extract-plugin')
  • 第二步:然後在plugins屬性中通過建立例項的方式使用:
plugins: [
    new miniCssExtractPlugin()
  ],
  • 第三步:配合相關的loader
rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader':建立style標籤,將樣式放入
          //使用以下loader取代style-loader,作用:提取js中的css成單獨檔案
          miniCssExtractPlugin.loader,
          //將css檔案整合到js檔案中
          'css-loader',
        ]
      }
    ]

2.單獨打包css檔案

完成配置後,再次執行webpack打包檔案,沒有報錯,檢視生成的打包輸出檔案目錄build,發現其中多了一個main.css檔案:

這個檔案就是提取出來的css檔案;

也可以在第二步中傳入引數進行一些配置:比如對輸出路徑和檔案進行命名:

plugins: [
    new miniCssExtractPlugin({
      //對輸出的css檔案進行重新命名
      filename: 'css/built.css'
    })
  ],

隨後,刪除原有打包出來的build目錄,再次執行webpack,重新生成打包目錄build

可以看到,成功地設定了css檔案存放的目錄和檔名。

3.優點

這種做法的好處是:

  • 提取後的css檔案是通過link標籤引入的,這樣就避免了閃屏現象:

  • 並且css檔案與js檔案分離開了,所以js檔案體積縮小了,解析速度也會更好一些。

十三.CSS相容性處理

css3新增了許多的樣式屬性,但是並不是每一個瀏覽器都完全支援了這些樣式屬性,因此我們需要對某些樣式屬性進行相容性處理,通過webpack可以很容易地實現這一點;

需要使用postcss-loaderpostcss-preset-env外掛;

這個外掛能幫助postcss識別某些環境,而載入指定的配置,從而使相容性做到精確到某一個瀏覽器版本;

1.安裝依賴

首先,全域性下載這個兩包,依舊是開發時依賴:

 npm i postcss-loader postcss-preset-env -D

2.基本配置

配置有兩種寫法:

  • 第一種:使用loader預設配置。直接寫:

    'postcss-loader'
    

    這種方法不能修改配置,如果想要修改配置,採用第二種寫法;

  • 第二種:修改配置。寫成物件的形式,在options屬性中修改配置:

    {
        loader: 'postcss-loader',
            options: {
                //固定寫法
                ident: 'postcss',
                    plugins: () => [
                        //postcss的外掛
                        require('postcss-preset-env')()
                    ]
            }
    }
    

該外掛的作用為:幫助postcss找到package.jsonbrowserslist裡面的配置,通過配置載入指定的css相容性樣式。

此時需要在根目錄的package.json中新增相關配置:

在其中新增下列配置:

  "browserslist": {
      //這是開發環境
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
      //生產環境,
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  }

browserslist物件中可以寫兩個引數:

  • development代表開發環境的配置,值為陣列;
  • production代表生產環境的配置;

關於browserslist的配置github上又很詳盡的介紹。

"last 1 chrome version"代表相容chrome最近的一個版本;其他瀏覽器也是這個格式。如果是開發模式,我們不需要配置太多瀏覽器,只需要配置除錯用到的瀏覽器就夠了;

但是,生產環境就要多寫一點;他們表示:

//表示相容大於99.8%的瀏覽器
">0.2%",
//不要已經死的瀏覽器,比如IE10
"not dead",
//還有所有的op_mini瀏覽器
"not op_mini all"

這樣就做到了絕大多數瀏覽器的相容了。

打包時,預設看生產環境,與webpack.config.js中的mode是沒關係的;如果想要使用開發環境,需要設定node環境變數:

process.env.NODE_ENV = 'development'

打包前,webpack的配置是這樣的:

const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const miniCssExtractPlugin = require('mini-css-extract-plugin')

//設定nodejs環境變數
process.env.NODE_ENV = 'development'

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          miniCssExtractPlugin.loader,
          'css-loader',
          /**
           * css相容性處理:postcss --> postcss-loader postcss-preset-env
           */
          {
            loader: 'postcss-loader',
            options: {
              //固定寫法
              ident: 'postcss',
              plugins: () => [
                //postcss的外掛
                require('postcss-preset-env')()
              ]
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new miniCssExtractPlugin({
      filename: 'css/built.css'
    })
  ],
  mode: 'development'
}

打包前,我們在css檔案中新增兩個有相容性問題的新樣式:

使用webpack打包後,打包後的樣式發生了變化:

說明相容性處理成功了。

這樣,我們就能專心使用各種樣式,而不需要考慮相容性問題了,webpack會自動幫我們做好這方面的工作。

十四.壓縮CSS

這節介紹使用外掛來完成css的壓縮,可以發現有的時候使用loader,有的時候使用外掛;主要的區別在於:loader處理的東西比較少於專一,外掛處理的東西比較大;

1.下載外掛

使用的外掛為:optimize-css-assets-webpack-plugin,首先全域性下載:

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

使用外掛的時候,要先在webpack.config.js中引入:

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

2.配置外掛

然後在plugins中使用:

  plugins: [
    //壓縮css
    new OptimizeCssAssetsWebpackPlugin()
  ]

只要引用就可以了,不需要額外配置,按照預設的配置已經可以將css檔案壓縮了。

然後使用webpack打包,輸出的css檔案被壓縮成了一行:

在樣式較多的情況下,壓縮是很有必要的。能夠請求速度更快,載入樣式更快,這樣就能更快地渲染,使用者體驗也就更好。

十五.js語法檢查

1.安裝依賴

語法檢查,不僅可以檢查語法錯誤,還可以設定程式碼風格,讓整個團隊的程式碼風格都保持一致;專門做語法檢查的如eslint-loader它依賴於eslint庫,所以首先要在全域性下載這兩個包:

npm i eslint-loader eslint -D

2.配置eslint

注意:語法檢查只會檢查自己寫的程式碼,第三方庫不會也不用檢查;所以需要使用exclude來排除第三方庫:

    rules: [
      {
        test: /\.js$/,
        loader: 'eslint-loader',
        exclude: /node_modules/,
        options: {}
      }
    ]

此外還需要我們在package.json中的eslintConfig中配置檢查規則。關於規則,推薦使用airbnb。在github上的這個專案會詳細告訴你,應該如何配置這些規則:

npm上有兩個將airbnb規則應用到eslint中的包:

  • 不包含React相關內容的外掛也分為兩種:
    • eslint-config-airbnb-base:包含ES6及以上的內容;開發中一般都會使用ES6語法,所以使用上面這個外掛;
    • eslint-config-airbnb-base/legacy:包含ES5ES5以下;
  • eslint-config-airbnb:包含了React的內容;

所以,我們選擇使用eslint-config-airbnb-base,注意,該外掛依賴於eslinteslint-plugin-import兩個包;上面已經下載過eslint了,這裡只需要下載其他兩個包:

 npm i eslint-config-airbnb-base eslint-plugin-import -D

package.json中的具體配置為:

  "eslintConfig": {
    //通過extends欄位繼承airbnb-base就可以了
    "extends": "airbnb-base"
  }

隨後在入口檔案index.js中故意寫一寫不規範的程式碼:不加空格,分號等:

然後執行webpack指令,進行打包,發現出現了很多錯誤:

雖然可以根據錯誤提示,在airbnb文件中查詢修改意見。但是手動修改仍然顯得麻煩,可以在webpack.config.jseslint-loader中的options裡新增fix屬性配置,讓它自動修復:

    rules: [
      {
        test: /\.js$/,
        loader: 'eslint-loader',
        exclude: /node_modules/,
        options: {
            //自動修復eslint的錯誤
            fix: true
        }
      }
    ]

再次打包,通過自動修復就不會再顯示錯誤資訊了:

可以看到,雖然沒有顯示錯誤,但是有一個警告:不建議使用console;在開發中可以使用console進行除錯,但是真正的程式碼中不應該含有它;此時可以通過新增下列註釋,讓eslint忽略下一行程式碼:

//下一行eslint所有規則都失效(即下一個行不進行eslint檢查)
//eslint-disable-next-line
console.log('123')

一個坑:安裝包的時候,最好按照依賴關係來安裝;即首先安裝下層的包,再安裝上層的包,否則可能會出現意想不到的錯誤;可以通過檢視package.json中的配置來觀察相應的包是否成功安裝了;

十六.js相容性處理

我們將入口檔案index.js內容改為箭頭函式:

直接執行webpack打包,打包出來的檔案中,該部分並沒有做相容性處理:

這樣的話,不支援ES6的瀏覽器就會出錯;這時候就需要使用babel-loader來進行js的相容性處理了;

1.基本配置

webpack.config.js中的配置為:

    rules: [
      /**
       * js相容性處理:babel-loader @babel/preset-env @babel/core
       */
      {
        test: /\.js/,
        //排除不需要js相容性處理的檔案
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          //預設:指示babel做怎樣的相容性處理,一般使用@babel/preset-env就可以了
          presets: ['@babel/preset-env']
        }
      }
    ]

注意:凡是涉及到相容性處理都要使用exclude將不需要進行相容性處理的檔案排除;否則會出錯,如下圖所示,不排除的話,依賴檔案node_module中的js都報錯了:

2.使用babel-loader

方法一:使用babel-loader進行基本的相容性處理;

首先需要在全域性下載babel-loader@babel/preset-env@babel/core

npm i babel-loader @babel/preset-env @babel/core -D  

隨後再次執行webpack打包,這次打包出來的js檔案中,ES6的語法全都轉換為了ES5的語法了,也就是做了相容性處理:

babel-loader存在的問題

只能轉換一些基本語法,如不能轉換promise;比如在入口檔案index.js中新增Promise物件:

直接打包是不會被相容性處理的。

解決方案:通過@babel/polyfill對全部js進行相容性處理;

3.使用@babel.polyfill

方法二:使用@babel.polyfill,對全部js進行相容性處理;

首先還是全域性安裝@babel/polyfill

npm i @babel/polyfill -D

它不是laoder或外掛,只需要在入口檔案index.js中通過import引入就可以使用了:

import '@babel/polyfill';

此時再次執行webpack打包,會發現打包出來的js檔案變得非常大:

引入@babel/polyfill前:built.js只有不到4KB

引入後,變成了441KB

這是因為只要使用了@babel/polyfill,那麼它就會將js檔案涉及到的所有相容性問題都解決掉。

此時入口檔案中的Promise就被相容性處理了:

@babel/polyfill存在的問題

我只要解決部分相容性問題,但是將所有相容性程式碼全部引入,體積太大了;

解決方法:需要做相容性處理就做:按需載入(只加載指定的相容性庫),通過core-js實現;

4.使用core-js

方法三:使用core-js,實現按需載入。

首先全域性下載core-js

 npm i core-js -D

隨後在webpack.config.js中進行相應的配置:

rules: [
      /**
       * js相容性處理:babel-loader
       */
      {
        test: /\.js/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          //預設:指示babel做怎樣的相容性處理,一般使用@babel/preset-env就可以了
          presets: [
            [
              '@babel/preset-env',
              {
                //按需載入
                useBuiltIns: 'usage',
                //指定corejs版本
                corejs: {
                  version: 3
                },
                //指定相容性做到那個版本瀏覽器
                targets: {
                  chrome: '60',
                  firefox: '60',
                  ie: '9',
                  safari: '10',
                  edge: '17'
                }
              }
            ]
          ]
        }
      }
    ]

需要注意的是,使用第三種方法就不能使用第二種方法了,所以要在入口檔案index.js中不再引入@babel/polyfill

隨後再次執行webpack進行打包,會發現,打包出來的js檔案相比於使用第二種方法時的441KB,縮減到了104KB

結合第一種和第三種方法就能實現對所有的js程式碼進行相容性處理;

5.完整配置

最後整個webpack.config.js的配置如下:

const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      /**
       * js相容性處理:babel-loader
       */
      {
        test: /\.js/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          //預設:指示babel做怎樣的相容性處理,一般使用@babel/preset-env就可以了
          presets: [
            [
              '@babel/preset-env',
              {
                //按需載入
                useBuiltIns: 'usage',
                //指定corejs版本
                corejs: {
                  version: 3
                },
                //指定相容性做到那個版本瀏覽器
                targets: {
                  chrome: '60',
                  firefox: '60',
                  ie: '9',
                  safari: '10',
                  edge: '17'
                }
              }
            ]
          ]
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
}

十七.壓縮js和html

1.壓縮js程式碼

只需要就webpack.config.js中的模式mode改為生產模式,就會自動壓縮js程式碼:

mode: 'production'

內部實現是通過外掛UglifyJsPlugin

執行webpack進行打包,打包出來的js檔案被壓縮成了一行:

不需要做html的相容性處理,因為標籤認識就認識,不認識就不認識,不能轉換;只需要對html進行壓縮處理即可;

2.壓縮html程式碼

只需要在配置檔案webpack.config.js中給HtmlWebpackPlugin外掛新增minify屬性即可:

plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      //壓縮html程式碼
      minify: {
        //移除空格
        collapseWhitespace: true,
        //移除註釋
        removeComments: true
      }
    })
  ],

執行webpack進行打包,打包出來的html檔案被去除了所有空格並移除了註釋:

十八.生產環境基本配置

學習了前面的基本配置,現在可以彙總起來配置基本的生產環境了。

1.配置的複用

如相同的配置可以抽離出來封裝一個複用的loader,比如cssless的相容性處理,唯一的不同點是多了個less-loaderless轉換為css,所以其餘部分可以複用;

未複用前,存在大量的重複程式碼:

    rules: [
      //處理css檔案
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          //css相容性處理
          {
            //還需要在webpack.json中定義browserslist
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              //指定外掛
              plugins: () => [
                require('postcss-preset-env')()
              ]
            }
          }
        ]
      },
      //處理less檔案
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          //css相容性處理
          {
            //還需要在webpack.json中定義browserslist
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              //指定外掛
              plugins: () => [
                require('postcss-preset-env')()
              ]
            }
          },
          //由於use陣列執行順序為從下往上(注意執行順序),經過less-loader轉換為css後再進行相容性處理
          'less-loader'
        ]
      }
    ]

複用後:

//複用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  //css相容性處理
  {
    //還需要在webpack.json中定義browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      //指定外掛
      plugins: () => [
        require('postcss-preset-env')()
      ]
    }
  }
]    
//...
    rules: [
      //處理css檔案
      {
        test: /\.css$/,
        //通過擴充套件運算子使用封裝的loader
        use: [...commonCssLoader]
      },
      //處理less檔案
      {
        test: /\.less$/,
        //由於use陣列執行順序為從下往上(注意執行順序),經過less-loader轉換為css後再進行相容性處理
        use: [...commonCssLoader,'less-loader']
      }
    ]
//...

所以,當遇到重複程式碼的時候一定要考慮將重複程式碼抽離封裝,達到複用的效果;

2.生產環境基本配置

完整的webpack.config.js配置如下:

//0.引入path模組解決路徑問題
const { resolve } = require('path');
//1.引入外掛提取和相容性處理css檔案
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
//2.引入壓縮css外掛
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
//3.引入處理html圖片引入的外掛
const HtmlWebpackPlugin = require('html-webpack-plugin');

//複用loader
const commonCssLoader = [
  //這一行作用為將css檔案抽離出來
  MiniCssExtractPlugin.loader,
  'css-loader',
  //css相容性處理
  {
    //還需要在webpack.json中定義browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      //指定外掛
      plugins: () => [
        require('postcss-preset-env')()
      ]
    }
  }
]

//package.json中的browserslist預設使用開發環境,若使用生產環境需要定義nodejs環境變數
process.env.NODE_ENV = 'production';

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      //1.處理css檔案
      {
        test: /\.css$/,
        //通過擴充套件運算子使用封裝的loader
        use: [...commonCssLoader]
      },
      //2.處理less檔案
      {
        test: /\.less$/,
        //由於use陣列執行順序為從下往上(注意執行順序),經過less-loader轉換為css後再進行相容性處理
        use: [...commonCssLoader,'less-loader']
      },
/**
 * 正常來說:一個檔案只能被一個loader處理
 * 當一個檔案要被多個loader處理時,那麼一定要指定loader的執行順序。
 * 比如先執行eslint-loader,再執行babel-loader。這是因為一旦語法出錯進行相容性處理就沒意義了。
 * 如何新增順序:enforce: 'pre'
 */

      //3.進行語法檢查
      {
        //在package.json中配置eslintConfig指定檢查規則 --> airbnb
        test: /\.js$/,
        //配出不需要語法檢查的檔案
        exclude: /node_modules/,
        //優先執行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          //自動修復錯誤
          fix: true
        }
      },
      //4.js相容性處理
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          //新增預設,告訴babel以哪種方式進行相容性處理
          presets: [
            //由於要使用方法一和三,所以使用陣列儲存
            [
              //簡單處理
              '@babel/preset-env',
              //按需載入
              {
                useBuiltIns: 'usage',
                //指定corejs版本
                corejs: {version: 3},
                //指定瀏覽器版本
                targets: {
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ]
        }
      },
      //5.處理圖片
      {
        test: /\.(jpg|png|gif)/,
        loader: 'url-loader',
        options: {
          //通過base64編碼優化
          limit: 8 * 1024,
          //重新命名打包後的圖片
          name: '[hash:10].[ext]',
          //指定輸出路徑
          outputPath: 'imgs'
        }
      },
      //6.處理html中的圖片
      {
        test: /\.html$/,
        loader: 'html-loader',
      },
      //8.處理其他檔案
      {
        //排除其他檔案
        //正則中不加$表示只要匹配到這些詞就行,是不是字尾都可以
        exclude: /\.(js|css|less|html|jpg|png|gif)/,
        //原封不動地輸出檔案
        loader: 'file-loader',
        options: {
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    //相容性處理css並單獨抽離css檔案
    new MiniCssExtractPlugin({
      //設定輸出路徑
      filename: 'css/built.css',
    }),
    //壓縮css
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      //指定html模板
      template: './src/index.html',
      //7.壓縮html檔案
      minify: {
        //移除空格
        collapseWhitespace: true,
        //移除註釋
        removeComments: true
      }
    })
  ],
  //改為production模式自動壓縮js檔案
  mode: 'production'
}