從零配置你的Webpack

從零配置你的Webpack

前言

上篇文章簡單的介紹了一下,前端進階之腳手架的搭建。其實我我的認爲重要的仍是Webpack的配置出來的template,至於腳手架的交互體驗能夠後期去優化,也能夠更加的個性化。可是咱們的核心仍是放在Webpack等一系列的配置上。javascript

這篇文章的目的css

  • 能夠給本身一個回顧的地方
  • 增強對Webpack的理解,每一個知識點都會認認真真的徹查!儘可能讓每一步都是很是清晰明瞭的!
  • 若是能夠幫助到你們那是更好不過了

好了,收!話很少說開始!html

前提

相信來到這裏的小夥伴,都是有一些些前端的經驗了,至於什麼node安裝環境變量這裏就不贅述了。本人使用的是mac os,若是是Windows的而且碰到問題的話,能夠留言,或者直接Google。前端

開始

創建一個空的文件夾📁

新建一個文件夾,名爲【webpackInit】java

而且使用你的編輯器打開他,而後打開命令行執行:node

npm init -y
複製代碼

這個命令是node幫你初始化一個項目用的,幫你新建一個package.jsonreact

至於-y是用於默認都以yes繼續執行。若是想了解一下里面到底有什麼的同窗能夠不用*-y*繼續跑一遍。其實裏面的東西后期均可以本身修改package.json。因此不須要太在乎。android

安裝Webpack🔧

如今是北京時間:2020/06/07 06:42:49。webpack

Webpack5有Beta版,這裏就不考慮了,後續上正式版的話我應該會出新的文章介紹。ios

由於咱們使用的是 webpack 4+ 版本,還須要安裝 webpack-cli ,執行如下命令:

npm install --save-dev webpack webpack-cli
複製代碼

由於Webpack主要是編譯時使用因此放到「devDependencies」。

確認一下如今的目錄結構,以防有同窗掉隊!

webpackInit
  |- node_modules
  |- package-lock.json
  |- package.json
複製代碼

這裏說下題外話。

package.json和package-lock.json的區別

package.json 文件只能鎖定大版本,也就是版本號的第一位,並不能鎖定後面的小版本,npm install 都是拉取的該大版本下的最新的版本,爲了穩定性考慮咱們幾乎是不敢隨意升級依賴包的,這將致使多出來不少工做量,測試/適配等,因此 package-lock.json 文件出來了,當你每次安裝一個依賴的時候就鎖定在你安裝的這個版本。

那若是咱們安裝時的包有bug,後面須要更新怎麼辦?

在之前可能就是直接改 package.json 裏面的版本,而後再 npm install 了,可是 5 版本後就不支持這樣作了,由於版本已經鎖定在 package-lock.json 裏了,因此咱們只能 npm install xxx@x.x.x 這樣去更新咱們的依賴,而後 package-lock.json 也能隨之更新。

新建配置文件📃

咱們在根目錄新建文件夾【config】用於存儲一些相關的配置文件,而後在【config】裏面新建一個文件夾【webpack】表示,專門用於存儲webpack的配置文件。而後在【webpack】這個文件夾下面新建文件【webpack.common.config.js】

並敲入如下代碼

const path = require('path');

module.exports = {
  // 配置入口文件
  entry: {
    app: './src/index.js',
  },
  // 打包📦以後的出口
  output: {
    // 若是不加哈希值,瀏覽器會有緩存,可能你部署了,可是用戶看到的仍是老頁面
    // 8是hash的長度,若是不設置,webpack會設置默認值爲20。
    filename: 'js/[name].[chunkhash:8].bundle.js',
    /** * Node.js 中,__dirname 老是指向被執行 js 文件的絕對路徑, * 因此當你在 /d1/d2/myscript.js 文件中寫了 __dirname, 它的值就是 /d1/d2 。 * path.resolve * 1.path.resolve()方法能夠將路徑或者路徑片斷解析成絕對路徑 * 2.傳入路徑從右至左解析,遇到第一個絕對路徑是完成解析,例如path.resolve('/foo', '/bar', 'baz') 將返回 /bar/baz * 3.若是傳入的絕對路徑不存在,那麼當前目錄將被使用 * 4.當傳入的參數沒有/時,將被傳入解析到當前根目錄 * 5.零長度的路徑將被忽略 * 6.若是沒有傳入參數,將返回當前根目錄 * * _dirname表示絕對路徑 * 咱們碰到的./xx就是相對路徑 * 1.只傳入__dirname也能夠自動調用path.resolve方法 * 2.能夠拼接路徑字符串,可是不調用path.resolve()方法拼接失敗 * 3.__dirname表明的是當前文件(a.js)的絕對路徑 * 4.從右至左解析,遇到了絕對路徑/src,所以直接返 */
    path: path.resolve(__dirname, '../../dist')
  }
}
複製代碼

webpack 配置是標準的 Node.js的CommonJS 模塊,它經過require來引入其餘模塊,經過module.exports導出模塊,由 webpack 根據對象定義的屬性進行解析。

在根目錄新建【src】文件夾📁

在【src】文件夾下新建文件index.js

ok👌,確認一下目錄結構

webpackInit
+ |- config
+ |- webpack
+ |- webpack.common.config.js
  |- node_modules
+ |- src
+ |- index.js
  |- package.json
  |- package-lock.json
複製代碼

那咱們怎麼打包呢?在 package.json 中配置以下屬性:

"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "webpack --config ./config/webpack/webpack.common.config.js"
},

複製代碼

好了,咱們試試怎麼打包吧,雖然你的 index.js 中什麼代碼也沒有。 在控制檯中輸入如下代碼:

npm run start
複製代碼

npm run xxxx 會去執行當前目錄下package.json裏面的script同名腳本

咱們的【npm run start】至關於直接執行了咱們寫在【start】裏面的代碼。

執行以後,你會發現根目錄多出了一個文件夾: dist/js ,其中有一個js文件: bundle.js ,那麼至此,咱們已經成功編譯打包了一個js文件,即入口文件: index.js

安裝React

在控制檯輸入如下代碼:

npm install --save react react-dom
複製代碼

--save就是運行時會用到的代碼

具體和--dev-save的區別能夠本身Google一下

在【src/index.js】裏面加入如下代碼

import React from 'react';
import ReactDOM from 'react-dom';

const App = () => (
  <> Hello World! </> ) ReactDOM.render(<App />, document.getElementById('root')); 複製代碼

在根目錄加入文件夾【public】,而後在【public】裏面加入【index.html】

目錄以下:

webpackInit
+ |- public
+ |- index.html
  |- config
  	|- webpack
      |- webpack.common.config.js
  |- node_modules
  |- src
      |- index.js
  |- package.json
  |- package-lock.json
複製代碼

而後在【index.html】加入如下代碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible">
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
  <title>從零配置Webpack</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
複製代碼

OK

萬事俱備,咱們運行:

npm run start
複製代碼

打包失敗了。。。爲何呢?

使用babel

爲何咱們上面寫jsx會打包不了呢,由於webpack根本識別不了jsx語法,那怎麼辦?使用loader對文件進行預處理。 其中,babel-loader,就是這樣一個預處理插件,它加載 ES2015+ 代碼,而後使用 Babel 轉譯爲 ES5。那開始配置它吧!

首先安裝babel相關的模塊:

npm install --save-dev babel-loader @babel/preset-react @babel/preset-env @babel/core babel-plugin-import
複製代碼
  • **babel-loader:**使用Babel和webpack來轉譯JavaScript文件。
  • **@babel/preset-react:**轉譯react的JSX
  • **@babel/preset-env:**轉譯ES2015+的語法
  • **@babel/core:**babel的核心模塊
  • babel-plugin-import:按需加載所須要的babel解析

理論上咱們能夠直接在 webpack.common.config.js 中配置"options",但最好在當前根目錄,注意,必定要是根目錄!!! 新建一個配置文件 .babelrc 配置相關的"presets":

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          // 大於相關瀏覽器版本無需用到 preset-env
          "edge": 17,
          "firefox": 60,
          "chrome": 67,
          "safari": 11,
          // 兼容到android4 ios6
          "browsers": ["Android >= 4.0", "ios >= 6"]
        }
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": [
    ["import", { "libraryName": "antd-mobile", "style": "css" }] // `style: true` 會加載 less 文件
  ]
}
複製代碼

這裏有關bebel的配置可上官網查詢文檔。

再修改 webpack.common.config.js ,添加以下代碼:

const path = require('path');

module.exports = {
  // 配置入口文件
  entry: {
    app: './src/index.js',
  },
  // 打包📦以後的出口
  output: {
    // 若是不加哈希值,瀏覽器會有緩存,可能你部署了,可是用戶看到的仍是老頁面
    // 8是hash的長度,若是不設置,webpack會設置默認值爲20。
    filename: 'js/[name].[chunkhash:8].bundle.js',
    /** * Node.js 中,__dirname 老是指向被執行 js 文件的絕對路徑, * 因此當你在 /d1/d2/myscript.js 文件中寫了 __dirname, 它的值就是 /d1/d2 。 * path.resolve * 1.path.resolve()方法能夠將路徑或者路徑片斷解析成絕對路徑 * 2.傳入路徑從右至左解析,遇到第一個絕對路徑是完成解析,例如path.resolve('/foo', '/bar', 'baz') 將返回 /bar/baz * 3.若是傳入的絕對路徑不存在,那麼當前目錄將被使用 * 4.當傳入的參數沒有/時,將被傳入解析到當前根目錄 * 5.零長度的路徑將被忽略 * 6.若是沒有傳入參數,將返回當前根目錄 * * _dirname表示絕對路徑 * 咱們碰到的./xx就是相對路徑 * 1.只傳入__dirname也能夠自動調用path.resolve方法 * 2.能夠拼接路徑字符串,可是不調用path.resolve()方法拼接失敗 * 3.__dirname表明的是當前文件(a.js)的絕對路徑 * 4.從右至左解析,遇到了絕對路徑/src,所以直接返 */
    path: path.resolve(__dirname, '../../dist')
  },
  module: {
    /** * test 規定了做用於以規則中匹配到的後綴結尾的文件, * use 便是使用 babel-loader 必須的屬性, * exclude 告訴咱們不須要去轉譯"node_modules"這裏面的文件。 */
    rules:[
      {
        test: /\.(js|jsx)?$/,
        // 開啓緩存
        options: { cacheDirectory: true },
        loader: 'babel-loader',
      },
    ]
  }
}
複製代碼

接下來激動人心的時刻:

npm run start
複製代碼

是否是能打包成功了呢?

打開【dist/】你的html頁面,看一下是不是「Hello World!」吧!

使用webpack-merge🈴️

咱們將使用一個名爲 webpack-merge 的工具。經過「通用」配置,咱們沒必要在環境特定(environment-specific)的配置中重複代碼。簡單來講就是生產環境不一樣,咱們要給的配置也有所不一樣,可是能夠共用一個共有的配置。

咱們先從安裝 webpack-merge 開始:

npm install --save-dev webpack-merge
複製代碼

安裝結束以後,咱們在 config 這個文件夾下新建兩個文件,分別爲 webpack.prod.config.jswebpack.dev.config.js ,這兩個文件分別對應生產和開發兩個環境的配置。固然你也能夠添加test環境。名字也能夠本身取,儘可能保持一致。

如今的目錄結構:

webpackInit
	|- config
		|- webpack
	    |- webpack.common.config.js
+ |- webpack.prod.config.js
  省略
複製代碼

在【webpack.prod.config.js】加入

const merge = require('webpack-merge'); // 版本爲4.x
// webpack-merge 5.x版本應該改成 const { merge } = require('webpack-merge');
const common = require('./webpack.common.config.js');

module.exports = merge(common, {
  mode: 'production',
});
複製代碼

而後修改【package.json】

{
  "name": "webpackInit",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack --config ./config/webpack/webpack.common.config.js",
+ "build": "webpack --config ./config/webpack/webpack.prod.config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.10.2",
    "@babel/preset-env": "^7.10.2",
    "@babel/preset-react": "^7.10.1",
    "babel-loader": "^8.1.0",
    "babel-plugin-import": "^1.13.0",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11",
    "webpack-merge": "^4.2.2"
  },
  "dependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  }
}

複製代碼

而後

刪除【dist文件夾】

以後

npm run build
複製代碼

是否是也build也打包成功了!

html是否是也能夠正常訪問!

可是沒有顯示Hello World,仔細一看還報錯了。。。爲何呢。。由於

// 若是不加哈希值,瀏覽器會有緩存,可能你部署了,可是用戶看到的仍是老頁面
    // 8是hash的長度,若是不設置,webpack會設置默認值爲20。
    filename: 'js/[name].[chunkhash:8].bundle.js',
複製代碼

咱們給js文件設置了hash值。不可以直接在html裏面加上【】這麼一句,並且每次生成的hash值都會變,那麼咱們要怎麼處理這個問題呢?

使用HtmlWebpackPlugin

安裝HtmlWebpackPlugin

在控制檯執行如下代碼:

npm install --save-dev html-webpack-plugin
複製代碼

修改【webpack.prod.config.js】

const merge = require('webpack-merge'); // 版本爲4.x
// webpack-merge 5.x版本應該改成 const { merge } = require('webpack-merge');
const common = require('./webpack.common.config.js');

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

module.exports = merge(common, {
  mode: 'production',
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html', // 打包以後的html文件名字
      // 這裏有小夥伴可能會疑惑爲何不是 '../public/index.html'
      // 個人理解是不管與要用的template是否是在一個目錄,都是從根路徑開始查找
      template: 'public/index.html', // 以咱們本身定義的html爲模板生成,否則咱們還要到打包以後的html文件中寫script
      inject: 'body',// 在body最底部引入js文件,若是是head,就是在head中引入js
      minify: { // 壓縮html文件
        removeComments: true, // 去除註釋
        collapseWhitespace: true, // 去除空格
      },
    })
  ]
});
複製代碼

如今咱們再來打包試試

npm run build
複製代碼

看看dist中是否是多出了html文件,而且自動引入了script,用瀏覽器打開它試試看是否是能正確輸出內容了!

起飛!!🛫️

使用clean-webpack-plugin

有些同窗已經厭倦了每次都須要刪除dist文件夾來驗證是否成功。

其實假如咱們不刪除的話,咱們須要修改js文件,這樣讓webpack知道咱們改了東西,他就會從新打包,可是咱們每次測試都沒有去修改,因此咱們須要刪除。

可是,若是說咱們不去刪除dist文件夾的話,咱們修改了【src/index.js】。而後再從新build,就會發現【dist/js】下面會又多出一個js文件,這樣的話咱們就須要觀察日誌,查看新鮮「出爐」的是哪個,而後刪掉別的。這樣很是麻煩。

OK,咱們如今就來解決一下這個問題

安裝clean-webpack-plugin

npm install --save-dev clean-webpack-plugin
複製代碼

修改【webpck.prod.config.js】

const merge = require('webpack-merge'); // 版本爲4.x
// webpack-merge 5.x版本應該改成 const { merge } = require('webpack-merge');
const common = require('./webpack.common.config.js');

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

module.exports = merge(common, {
  mode: 'production',
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html', // 打包以後的html文件名字
      // 這裏有小夥伴可能會疑惑爲何不是 '../public/index.html'
      // 個人理解是不管與要用的template是否是在一個目錄,都是從根路徑開始查找
      template: 'public/index.html', // 以咱們本身定義的html爲模板生成,否則咱們還要到打包以後的html文件中寫script
      inject: 'body',// 在body最底部引入js文件,若是是head,就是在head中引入js
      minify: { // 壓縮html文件
        removeComments: true, // 去除註釋
        collapseWhitespace: true, // 去除空格
      },
    }),
+ new CleanWebpackPlugin()
  ]
});
複製代碼

咱們先查看如今的js文件前面的hash值。而後咱們修改【src/index.js】,隨便改爲什麼,再從新

npm run build
複製代碼

就會發現文件並無新增,並且換了新鮮的哈希值。

固然了,我以前說的那些話,同窗不相信的話,能夠把【new CleanWebpackPlugin()】這一行註釋掉,而後再修改【src/index.js】,會發現【dist/js】下面的js文件會增多一條。

使用webpack-dev-server

既然剛剛都提到優化了,咱們每次都須要build一下,感受很呆。而webpack官方也提供熱部署,那咱們如今就使用起來

安裝webpack-dev-server

npm install webpack-dev-server --save-dev
複製代碼

新建文件【webpack.dev.config.js】📃

webpackInit
	|- config
		|- webpack
	    |- webpack.common.config.js
      |- webpack.prod.config.js
+ |- webpack.dev.config.js
  省略
複製代碼

而後加入以下代碼

const path = require('path');
const merge = require('webpack-merge'); // 版本爲4.x
// webpack-merge 5.x版本應該改成 const { merge } = require('webpack-merge');
const common = require('./webpack.common.config.js');

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

module.exports = merge(common, {
  mode: 'development',
  output: {
    filename: 'js/[name].[hash:8].bundle.js',
  },
  devServer: {
    contentBase: path.resolve(__dirname, '../dist'),
    open: true,
    port: 9000,
    compress: true,
    hot: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      inject: 'body',
      hash: false
    }),
    new webpack.HotModuleReplacementPlugin()
  ]
});
複製代碼

修改文件【package.json】📃

"scripts": {
- "start": "webpack --config ./config/webpack/webpack.common.config.js",
+ "start": "webpack-dev-server --inline --config ./config/webpack/webpack.dev.config.js",
    "build": "webpack --config ./config/webpack/webpack.prod.config.js",
  },
複製代碼

而後咱們

npm run start
複製代碼

是否是自動開了一個端口爲9000的網頁,上面是咱們寫的頁面內容,這和咱們的配置都是一一對應的。 如今你隨意修改index.js中的代碼,再回到頁面看下是否是也跟着變了,那咱們就整合webpack-dev-server成功!

使用source-map

source-map能夠展現咱們代碼的錯誤位置,由於咱們的代碼都是被webpack打包過的,只有機器看得懂,咱們人類沒法正常識別。因此須要他。

想試一下未開啓是什麼狀態的同窗能夠本身故意把代碼寫錯,而後看看控制檯的報錯。

開啓也十分簡單。

它的配置很是簡單,只須要在 【webpack.dev.config.js】 中增長一個 devtool 屬性便可!

module.exports = {
+ devtool: 'cheap-module-eval-source-map',
	//...
}
複製代碼

由於咱們只有本身寫代碼的時候才須要查看,到生產環境就要關閉啦,否則咱們的智慧結晶就要被【竊取】啦!因此我放在了

中場休息

這裏回顧一下知識點。

  1. 咱們先npm init 新建一個空白項目。
  2. 而後安裝webpack,react。
  3. 發現沒法編譯jsx。
  4. 因此咱們尋求了babel的幫助,並配置了所須要解析的內容。
  5. 以爲在html裏面加入js很呆,因此引入了HtmlWebpackPlugin
  6. 由於以爲每次刪除打包出來的東西很呆,因此引入了clean-webpack-plugin
  7. 由於每次都須要從新打包,因此使用webpack-dev
  8. 至於webpack-merge是爲了打包和編譯兩個或者說多個狀態作預備的,在上述的例子只有本地的dev和build兩個環境。

基本上上述的操做過程我都有解釋,或者是註釋,或者是在文章中說明。你們能夠跟着節奏一步一步來,由於我也是一邊寫blog一邊跑代碼一邊回顧知識點。

至此,webpack算是告一……

誒誒誒誒!js是能夠解析了,那css呢!

哦哦,好的,那咱們繼續啓程

從新起航

使用css-loader和style-loader兩兄弟

假如咱們直接引用css的話,會報錯了。這裏就不演示了,有興趣的同窗能夠本身試試。

因此,咱們先走命令行敲擊

npm install --save-dev style-loader css-loader 
複製代碼

兩兄弟的關係

來講說css-loader和style-loader他們這對鴛鴦的關係。

首先css-loader會把你的CSS文件進行解析,由於webpack是用JS寫的,運行在node環境,因此默認webpack打包的時候只會處理JS之間的依賴關係!

因此咱們以前的react裏面的jsx須要babel的幫助,或者說須要【babel-loader】的幫助,因此咱們的css一樣須要【css-loader】的幫助,那麼又關【style-loader】什麼事?可不能夠不裝他呢?

答案是:能夠的,可是你使用起來會很是的麻煩。怎麼個麻煩法呢?

若是隻用了【css-loader】解析出來的是這樣的

["./src/index.css", ".test{↵ color: red;↵}", ""]
複製代碼

這樣咋用嘛,你是解析了,但是你解析的是個XX。

這個時候就須要咱們的天降猛男【style-loader】

style-loader 是經過一個JS腳本建立一個style標籤,裏面包含一些樣式。style-loader是不能單獨使用的,應爲它並不負責解析 css 以前的依賴關係,每一個loader的功能都是單一的,各自拆分獨立。

上手!

加入index.css

src
+ |- index.css
   |- indexjs
複製代碼

index.css文件的內容以下

.test{
  color: red;
}
複製代碼

修改index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css'

const App = () => (
  <div className="test"> Hello World!! </div>
)

ReactDOM.render(<App />, document.getElementById('root')); 複製代碼

修改【webpack.common.config.js】📃

//...
rules:[
      {
        test: /\.(js|jsx)?$/,
        // 開啓緩存
        options: { cacheDirectory: true },
        loader: 'babel-loader',
      },
+ {
+ test: /\.css$/,
+ use: [ 
+ 'css-loader', 
+ 'style-loader',
+ ]
+ }
    ]
    //....
複製代碼

記得從新運行,由於webpack的配置他讀取一次,因此若是你修改了配置,須要【ctrl+c】關閉從新運行。

嘿嘿……是否是運行不了啊。

其實我是故意的,我想告訴你們一個知識點。

loader的加載順序是從右往左。這裏的編譯順序是先用css-loader將css代碼編譯,再交給style-loader插入到網頁裏面去。因此css-loader在右,style-loader在左。

雖然咱們的數組換行了,可是仔細看不難看出順序。

因此咱們只須要將他們換個位置就能夠了。代碼我就不貼了。

如今你們應該記憶很深入了吧!

你們從新【ctrl+c】關閉從新運行就好了。咱們的hello world是否是變紅啦~

安裝less-loader

說到css,說句實在話,沒幾人真的是在寫純css的吧?如今誰不是less,sass或者其餘css預處理呢?並且這些預處理的好處我就不細說了,感興趣的本身Google吧,本文用的是less(由於ant design用的也是less,哈哈,僞裝是螞蟻的一員)

在命令行輸入

npm install --save-dev less less-loader
複製代碼

less沒什麼好說的,用他確定要裝,less-loader,顧名思義,就是less的解析者。

在【webpack.common.config.js】增長

{
        test: /\.css$/,
        use: [ 
          'style-loader',
          'css-loader', 
        ]
      },
+ {
+ test: /\.less$/,
+ use: [ 
+ 'style-loader',
+ 'css-loader', 
+ 'less-loader',
+ ]
+ }
複製代碼

依舊是順序問題,先解析less,把less解析成常規的css,而後再解析css,最後插入到網頁中去。

修改【index.js】引入本身寫的【.less】文件

你們從新【ctrl+c】關閉從新運行就好了。至於你寫了什麼less特性,只要有效果就好了。

安裝url-loader和file-loader

說完CSS,美麗的網頁固然離不開咱們動人的圖片啦。

【file-loader】的做用是,把你的文件打包起來,和js文件放在一塊兒,這樣用戶訪問咱們的網頁的時候,其實也須要訪問咱們的url,既增長了服務器的壓力,也增長了用戶升級流量的壓力,須要去下載這個文件。

【url-loader】

若是頁面圖片較多,發不少http請求,會下降頁面性能。這個問題能夠經過url-loader解決。url-loader會將引入的圖片編碼,生成dataURl並將其打包到文件中,最終只須要引入這個dataURL就能訪問圖片了。

url-loader和file-loader兩兄弟的搭配能夠有效的減小沒必要要的url請求,由於有的圖片你要去請求url獲取,若是小的話徹底能夠用base64替代。若是圖片很大的話就用file-loader,這樣能夠減小編碼的壓力。

在命令行輸入

npm install file-loader url-loader --save-dev
複製代碼

修改【webpack.common.config.js】📃

module: {
  rules: [
    //...
+ {
+ test: /\.(jpg|png|gif)$/,
+ use: {
+ loader: 'url-loader',
+ options: {
+ name: '[name].[ext]', //輸出的文件名
+ outputPath: 'images/', // 輸出到dist目錄下的路徑(dist/images/)
						/**
             * 若是你這個圖片文件大於8192b,即8kb,那我url-loader就不用,轉而去使用file-loader,
             * 把圖片正常打包成一個單獨的圖片文件到設置的目錄下,如果小於了8kb,
             * 那好,我就將圖片打包成base64的圖片格式插入到bundle.js文件中,
             * 這樣作的好處是,減小了http請求,可是若是文件過大,js文件也會過大,
             * 得不償失,這是爲何有limit的緣由!
             */
+ limit: 8192,
+ },
+ }
+ }
  ]
}
複製代碼

進階

基本上咱們的webpack能夠正常運行了,css,js,jsx均可以解析了。可是咱們須要考慮一些進階的東西,優化。

使用uglifyjs-webpack-plugin

在控制檯執行如下代碼:

npm install uglifyjs-webpack-plugin --save-dev
複製代碼

在【webpack.prod.config.js】添加代碼

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
//...
  optimization: {
    minimizer: [
      new UglifyJsPlugin({
        test: /\.js(\?.*)?$/i,  //測試匹配文件,
        include: /\/includes/, //包含哪些文件
        excluce: /\/excludes/, //不包含哪些文件

        cache: false,   //是否啓用文件緩存,默認緩存在node_modules/.cache/uglifyjs-webpack-plugin.目錄
        parallel: true,  //使用多進程並行運行來提升構建速度

        //容許過濾哪些塊應該被uglified(默認狀況下,全部塊都是uglified)。 
        //返回true以uglify塊,不然返回false。
        chunkFilter: (chunk) => {
            // `vendor` 模塊不壓縮
            if (chunk.name === 'vendor') {
              return false;
            }
            return true;
          }
        }),
  
    ],
  },
  //..
};
複製代碼

使用splitChunks

其實咱們寫的代碼,有些庫的代碼是不須要每次都編譯的,最簡單的例子就是React,這個咱們幾乎每一個js文件都會用到。因此咱們能夠將它們單獨打包,這樣只須要打包一次。

修改【webpack.common.config.js】

entry: {
     index: './src/index.js',
+ common: ['react', 'react-dom']
  },
複製代碼

修改【webpack.prod.config.js】

const merge = require('webpack-merge'); // 版本爲4.x
// webpack-merge 5.x版本應該改成 const { merge } = require('webpack-merge');
const common = require('./webpack.common.config.js');

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

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = merge(common, {
  mode: 'production',
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html', // 打包以後的html文件名字
      // 這裏有小夥伴可能會疑惑爲何不是 '../public/index.html'
      // 個人理解是不管與要用的template是否是在一個目錄,都是從根路徑開始查找
      template: 'public/index.html', // 以咱們本身定義的html爲模板生成,否則咱們還要到打包以後的html文件中寫script
      inject: 'body',// 在body最底部引入js文件,若是是head,就是在head中引入js
      minify: { // 壓縮html文件
        removeComments: true, // 去除註釋
        collapseWhitespace: true, // 去除空格
      },
    }),
    new CleanWebpackPlugin()
  ],
  optimization: {
    minimizer: [
      new UglifyJsPlugin({
        test: /\.js(\?.*)?$/i,  //測試匹配文件,
        // include: /\/includes/, //包含哪些文件
        // excluce: /\/excludes/, //不包含哪些文件

        //容許過濾哪些塊應該被uglified(默認狀況下,全部塊都是uglified)。 
        //返回true以uglify塊,不然返回false。
        chunkFilter: (chunk) => {
            // `vendor` 模塊不壓縮
            if (chunk.name === 'vendor') {
              return false;
            }
            return true;
          }
        }),
  
        cache: false,   //是否啓用文件緩存,默認緩存在node_modules/.cache/uglifyjs-webpack-plugin.目錄
        parallel: true,  //使用多進程並行運行來提升構建速度
    ],
    splitChunks: {
      /** * 默認值是async * 拆分模塊的範圍,它有三個值async、initial和all。 * async表示只從異步加載得模塊(動態加載import())裏面進行拆分 * initial表示只從入口模塊進行拆分 * all表示以上二者都包括 */
      chunks: 'all',
      // minSize: 30000, // 生成chunk的最小大小(以字節爲單位)。只有大於這個數字才能夠成一個chunk 
      // minRemainingSize: 0, // 只有剩下一個chunk的時候纔會生效,默認是和minSize同樣的,開發的時候默認是0
      // maxSize: 0, // 告訴webpack嘗試將大於maxSize字節的塊拆分爲較小的部分。
      // minChunks: 1, // 拆分前必須共享模塊的最小塊數。
      // maxAsyncRequests: 6, // 按需加載時最大並行請求數。
      // maxInitialRequests: 4, // 入口點的最大並行請求數。入口文件
      // automaticNameDelimiter: '~', // 默認狀況下,webpack將使用塊的來源和名稱生成名稱(例如vendors~main.js)。此選項使您能夠指定用於生成名稱的定界符。
      cacheGroups: {
        /** * 當webpack處理文件路徑時,它們始終包含/在Unix系統和\Windows上。 * 這就是爲何[\\/]在{cacheGroup}.test字段中使用in 來表示路徑分隔符的緣由。 * /或\in {cacheGroup}.test會在跨平臺使用時引發問題。 */
        // defaultVendors: {
        // test: /[\\/]node_modules[\\/]/, // 分塊目標
        // priority: -10 // 權重
        // },
        // default: {
        // minChunks: 2, // 最小引用
        // priority: -20, // 權重
        // // 若是當前塊包含已從主捆綁包中拆分出的模塊,則將重用該模塊,而不是生成新的模塊。這可能會影響塊的結果文件名。
        // reuseExistingChunk: true
        // },
        // 上述的splitChunks全是webpack4未設置狀況下的默認值,除了chunks從【async】->【all】其餘都沒有改
        // ok,咱們如今加入咱們本身想要的代碼分割
        // 由於我準備加入react等業務變幻無窮而不會變的庫
        common: {
          test: "common", // webpack掃面的關鍵字
          name: "common", // 生成的名字
          enforce: true // 是否緩存
        },
      }
    }
  },
});
複製代碼

不想寫diff啦,直接CV啦。。。

爲何我須要把react提取出來,由於哪裏都須要用,並且他幾乎不可能會變,因此我特別提出來作了緩存,其他的仍是使用的默認配置(除了chunk改成了‘all’)。

Tobias Koppers@Wsokra:optimization. splitchunks. chunks: althe only option you need for vendor and commons splitting in webpackBest combine it with html-webpack-plugin or equivalent html generation18120

做者都發推特說了,那咱們也就接受吧~就改個all,而後補一下react~

再從新打包,你會發現index.bundle.js(不被緩存)的hash值變了,可是common.bundle.js(能被緩存)的hash值沒變。

使用mini-css-extract-plugin

js都獨立📦,那我css也要!

其實若是把CSS打包成一個文件而後讓html引用的話能夠減少html文件的大小,暗合了HTTP2的多路複用,多文件小數量。包括以前的splitChunks裏面咱們配置的react也是爲了HTTP2。

在命令行輸入:

npm install --save-dev mini-css-extract-plugin
複製代碼

修改【webpack.common.config.js】

+ const MiniCssExtractPlugin = require('mini-css-extract-plugin');

//...
module:{
    rules:[
      {
        test: /\.css$/,
        use: [ 
+ MiniCssExtractPlugin.loader,
- 'style-loader',
           'css-loader', 
        ]
      },
    ],
  },
+plugins: [
+ new MiniCssExtractPlugin({
+ filename: 'css/[name].[hash].css',
+ chunkFilename: 'css/[id].[hash].css',
+ }),
  ]
複製代碼

使用CSS Module

其實很簡單,只須要修改一下配置【wbpack.common.config.js】

{
        test: /\.css$/,
        use: [ 
          MiniCssExtractPlugin.loader,
- 'css-loader'
+ {
+ loader: 'css-loader',
+ options: {
+ // importLoaders: 1,
+ modules: true,
+ },
+ },
        ]
      },
複製代碼

使用PostCSS

postcss 一種對css編譯的工具,相似babel對js的處理,常見的功能如: 1 . 使用下一代css語法 2 . 自動補全瀏覽器前綴 3 . 自動把px代爲轉換成rem 4 . css 代碼壓縮等等 postcss 只是一個工具,自己不會對css一頓操做,它經過插件實現功能,autoprefixer 就是其一。

安裝postcss

npm install postcss postcss-loader --save-dev
複製代碼

安裝postcss某個插件,以Autoprefixer舉例

npm install postcss-aspect-ratio-mini postcss-write-svg postcss-px-to-viewport postcss-viewport-units postcss-flexbugs-fixes postcss-preset-env cssnano --save-dev
複製代碼

在根目標新建文件【postcss.config.js】

/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @typescript-eslint/no-var-requires */
const postcssAspectRatioMini = require('postcss-aspect-ratio-mini');
const postcssPxToViewport = require('postcss-px-to-viewport');
const postcssWriteSvg = require('postcss-write-svg');
const postcssViewportUnits = require('postcss-viewport-units');
const cssnano = require('cssnano');
const postcssPresetEnv = require('postcss-preset-env')

const postcssFlexbugsFixes = require('postcss-flexbugs-fixes')

module.exports = {
  plugins: [
    postcssFlexbugsFixes,
    // 在這個位置加入咱們須要配置的代碼
    // 在這個位置加入咱們須要配置的代碼
    // 在這個位置加入咱們須要配置的代碼
    postcssAspectRatioMini({}),
    postcssPxToViewport({
      viewportWidth: 750, // 基準寬度(通常的設計都是這個基準
      viewportHeight: 1334, // 基準高度(通常的設計都是這個基準
      unitPrecision: 3, // (Number) The decimal numbers to allow the REM units to grow to.
      viewportUnit: 'vw', // (String) 單位
      selectorBlackList: ['.list-ignore', /notTransform/], // 帶上這個單詞的就不會fix爲vw單位
      minPixelValue: 1, // (Number) 最小像素
      mediaQuery: false, // (Boolean) 容許在媒體查詢中轉換px。
      exclude: /(\/|\\)(node_modules)(\/|\\)/,
    }),
    postcssWriteSvg({
      utf8: false
    }),
    postcssPresetEnv({}),
    postcssViewportUnits({
      filterRule: rule => rule.selector.includes('::after')
        && rule.selector.includes('::before')
        && rule.selector.includes(':after')
        && rule.selector.includes(':before')
    }),
    cssnano({
      "cssnano-preset-advanced": {
        zindex: false,
        autoprefixer: false
      },
    })
  ]
};
複製代碼

修改【webpack.common.config.js】

{
        test: /\.css$/,
        use: [ 
          'style-loader',
          'css-loader', 
+ 'postcss-loader'
        ]
      },
      {
        test: /\.less$/,
        use: [ 
          'style-loader',
          'css-loader', 
          'less-loader',
+ 'postcss-loader'
        ]
      }
複製代碼

而後,修改咱們的css文件,看看咱們寫的單位爲px的有沒有被改成vw的自適應單位。

順便試一下帶有【notTransform】的是否是仍是px做爲單位。

結束

到此,咱們的webpack配置,就算是入門了,對於webpack的配置咱們還有很長的路要走。你們加油!

若是有哪裏寫的很差或者寫錯了,歡迎你們在評論區討論。

相關文章
相關標籤/搜索