學習webpack之二:基礎配置

上一篇博客起了個頭,介紹了爲何要用webpack,用webpack能夠作什麼,以及最簡單地配置。這篇博客將結合實際需求,一步一步的引入一些配置,全部內容基於實際需求出發。javascript

entry和output

上一篇博客說到,entry是webpack打包的入口文件,webpack會從這個入口文件開始,尋找該入口文件的依賴模塊,以及遞歸的尋找依賴模塊的依賴模塊,直到將全部的依賴模塊打包成單個js文件輸出到output配置的路徑。根據不一樣的使用場景,主要有一對1、多對1、多對多等不一樣狀況。html

默認值

若是沒有設置entry,那麼其默認值是./src/index.js,即webpack會自動尋找根目錄下的src文件夾中的index.js當作入口文件vue

entry爲字符串:一對一

經過字符串值能夠直接指定入口文件的路徑,好比上一篇博客中的例子:java

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  }
}
複製代碼

這種狀況就是將一個輸入文件打包到一個輸出文件。webpack

entry爲數組:多對一

有時爲了業務邏輯更加清晰,咱們的入口文件可能不止一個,這時能夠將多個入口文件放在一個數組中,指定到entry:git

const path = require('path');

module.exports = {
  entry: ['./src/index1.js', './src/index2.js'],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  }
}
複製代碼

這時會將./src/index1.js./src/index2.js兩個文件打包成一個文件輸出。github

entry爲對象:多對多

這種場景其實更常見,好比咱們想作一個SPA,一共有5個page,那麼每一個page應該都是一個單獨的入口文件,最後每一個page都應該打包輸出一個單獨的文件,也就是5個輸入、5個輸出的狀況。這時就要用到對象形式的entry:web

const path = require('path');

module.exports = {
  entry: {
    index1: './src/index1.js', 
    index2: './src/index2.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  }
}
複製代碼

在output中的filename中,咱們沒有直接指定文件名,而是用[name].js,這裏的name就是entry中的key值index1和index2,也就是告訴webpack,對entry中的兩個入口文件分別打包,最後分別輸出到dist下的index1.jsindex2.jsshell

更完美一點的用法

在一個SPA項目中,有可能後面還會增長新的page,按照前面的配置,每次新加一個page,就要把這個page的路徑放到entry裏面去,告訴webpack把新增的這個page打包。顯然這種方式並不完美,能夠有更懶的辦法。爲了便於管理,通常會將全部的頁面都放到pages目錄下,那麼在webpack構建時,只須要讀取這個目錄下的全部js文件,而後動態生成entry對象就能夠了。npm

const path = require('path');
const glob = require('glob');

function getEntries() {
    const pagePath = path.resolve(__dirname, 'src/pages');
    let entries = {};
    // 讀取pages目錄下的全部js文件
    let files = glob.sync(pagePath + '/*.js');
    // 根據文件名,生成entry對象
    files.forEach(item => {
        let filename = item.split(path.sep).pop().split('.')[0];
        entries[filename] =  item;
    })
    return entries   
}

module.exports = {
    entry: getEntries(),
    output: {
        path: path.join(__dirname, '/dist'),
        filename: '[name].js'
    }
}
複製代碼

這樣,就能夠一勞永逸,之後添加的全部頁面均可以自動打包了。

輸出添加hash

你們都知道瀏覽器是有緩存機制的,若是是同一個js文件,即便服務器上已經存在更新的版本,瀏覽器仍有可能從本地緩存讀取,從而不能拿到最新的結果。要解決這個問題,就須要讓每次打包生成的js文件名不同,而後讓HTML去引用這個新名稱的js文件,從而繞過瀏覽器的緩存。修改文件名最簡單的方法就是在文件名後面加上md5戳,在webpack中這很容易實現

hash

modules.exports = {
   entry: getEntries(),
   output: {
        path: path.join(__dirname, '/dist'),
        filename: '[name].[hash].js'
    }
}
複製代碼

只用在filename後面加上[hash],最後打包生成的js文件名就會帶上md5戳。好比:

index1.20e52ce145885ab03243.js
index2.20e52ce145885ab03243.js
複製代碼

若是不但願後面的md5戳那麼長,也能夠指定md5戳的長度:

modules.exports = {
   entry: getEntries(),
   output: {
        path: path.join(__dirname, '/dist'),
        filename: '[name].[hash:5].js'
    }
}
複製代碼

這樣最後打包生成的js文件的md5長度只有5位:

index1.20e52.js
index2.20e52.js
複製代碼

hash的計算方法是基於整個項目的,只要整個項目中的任何一個文件發生變化,生成的md5戳就會改變,即便真正的入口js及其依賴並無發生變化。此外,全部的輸出文件都共用這一個md5戳的值。這種方式有些過於粗暴,對瀏覽器的緩存機制很不友好。咱們但願的是,若是文件有改動,文件名後面的hash值改變,若是沒有改動,則保持不變,瀏覽器直接從緩存讀取。這樣既保證了及時更新,又保證了性能。

chunkhash

chunkhash基於每一個入口文件及其依賴進行計算,每一個輸出文件的md5戳只跟本身的依賴文件有關,因此每一個輸出文件的md5戳都是不一樣的,而且只有本身的入口文件或依賴文件發生改變時纔會變化。

modules.exports = {
   entry: getEntries(),
   output: {
        path: path.join(__dirname, '/dist'),
        filename: '[name].[chunkhash:5].js'
    }
}
複製代碼

最後輸出的結果:

index1.f85c4.js
index2.dd9e3.js
複製代碼

顯然,chunkhash的方式更加理想。

動態生成HTML

每次打包生成的js文件名都會不同,那麼index.html中的script標籤每次都要去改引入的文件名,這也太麻煩了。一樣的,webpack能夠幫咱們解決這個問題。前面的博客中提到,webpack的強大之處在於強大的生態系統,提供了不少有用的插件。在這裏,咱們就可使用插件來幫咱們實現。

首先,安裝這個插件:

npm i html-webpack-plugin -D
複製代碼

而後,在webpack配置文件中使用這個插件:

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

module.exports = {
  ...
  plugins: [
    new HtmlWebpackPlugin({
      // options配置
    })
  ]
}
複製代碼

這時候再執行npm run build,就會看到dist文件下會自動生成一個index.html,而且這個Html文件會自動引入文件名變化的index.[md5].js文件。直接將這個文件拖到瀏覽器中就能夠顯示。

若是不作任何配置,html-webpack-plugin會建立一個新的空白HTML(官方文檔說會默認尋找src/index.ejs,不過我在實測中未實現,有多是我哪一個地方配置不對),只是在這個HTML中引入了打包後的js文件,沒有設定任何的DOM。這種狀況是合理的,由於當咱們用webpack時,不少時候HTML自己就是空白的,全部的DOM都是經過js渲染出來而後掛載上去的,好比常見的vue。能夠經過title去設定這個HTML的title標籤的內容。固然也能夠指定一個模板HTML,html-webpack-plugin會在這個HTML中自動引入打包後的js文件。options的主要配置有:

  • template:一個字符串路徑,指定HTML的模板,將在該HTML中引入打包後的js
  • filename:指定輸出文件的文件名
  • Chunks:默認狀況下,html-webpack-plugin會引入打包後的全部的js文件,能夠在chunk中指定引入哪些文件
plugins: [
  new HtmlWebpackPlugin({
    // 指定index.html做爲模板,即讓index.html自動引用打包後的js文件
    template: 'src/index.html',
    // 輸出到dist目錄下的index1.html
    filename: 'index1.html',
    // 讓index.html只引用dist目錄下的page1.[hash].js文件,注意這裏只用寫文件名的前綴,若是寫成page1.js會直接在dist文件夾下找page1.js文件,而不能找到帶hash的結果
    chunks: ['page1']
  })
]
複製代碼

若是但願有多個HTML,那麼就能夠設置多個html-webpack-plugin:

plugins: [
  // 第一個頁面,index1.html,引用page1.js打包後的文件
  new HtmlWebpackPlugin({
    template: 'src/index.html',
    filename: 'index1.html',
    chunks: ['page1']
  }),
  // 第二個頁面,index2.html,引用page2.js打包後的文件
  new HtmlWebpackPlugin({
    template: 'src/index.html',
    filename: 'index2.html',
    chunks: ['page2']
  })
]
複製代碼

固然,若是頁面個數是動態變化的,也能夠用多entry類型的形式讀取文件夾下的全部文件。更多的配置能夠Github

清除dist

webpack每執行一次,就會在dist目錄下生成一些文件。若是此次生成的文件和上次是同名的,則會直接覆蓋,問題不大。但若是是不一樣名的,則會在dist文件中累加,最後致使dist文件夾中存在不少沒必要要的文件。例如上面咱們加上md5戳以後,每次打包生成的輸出文件名並不相同,這就會形成dist文件夾的累積。即便不是用md5戳,也會有相似場景。好比開始我在pages下有一個page3.js,打包後在dist目錄下生成了page3.js,後來我不須要這個頁面了,把pages下的page3.js刪除了,執行webpack,但此時dist目錄下的page3.js依然存在。顯然這些不是咱們但願的。一種解決方法是每次執行webpack前手動清空dist,不過這也太麻煩了,這種麻煩的事情固然須要webpack來替咱們作。

首先,須要安裝這個插件

npm i clean-webpack-plugin
複製代碼

而後,在配置文件中使用這個插件

const {CleanWebpackPlugin}  = require('clean-webpack-plugin');
modules.exports = {
  ...,
  plugins: [
    new CleanWebpackPlugin()
  ]
}
複製代碼

這樣,每次執行webpack時就會自動清除dist目錄下的全部文件。

watch模式

到目前爲止,每次修改js文件後,都須要手動的執行npm run build來啓動webpack進行編譯。一樣的,這種機械性的重複工做很煩,應該交給webpack來作纔對。只須要在執行webpack時加上--watch啓動監聽模式就能夠了,直接在package.json中修改:

"scripts": {
  "build": "webpack --watch"
}
複製代碼

這樣只須要執行一遍npm run build,後面只要咱們修改了入口文件或其依賴文件中的任意一個,都會自動從新執行webpack打包。注意,若是是修改了webpack的配置文件webpack.config.js,並不會自動從新執行,仍是要手動執行如下。

webpack-dev-server自動刷新

雖然修改文件後能夠自動打包了,但仍是要手動刷新一下瀏覽器才能看到效果,這步操做也仍是有點煩,一樣的交給webpack來作吧。

單個輸出

咱們這裏先考慮只有一個輸出的狀況,即html-webpack-plugin爲默認配置:

plugins: [new HtmlWebpackPlugin()]
複製代碼
  • 安裝一下webpack-dev-sever插件
npm i webpack-dev-server -D
複製代碼
  • 在配置文件webpack.config.js中啓用:
module.exports = {
  devServer: {
    // 指定dist文件夾做爲服務器的目錄
    contentBase: './dist'
  }
}
複製代碼
  • 這時候,在package.json中直接經過webpack-dev-server啓動
"scripts": {
  "build": "webpack --watch",
  "start": "webpack-dev-server --opem"
}
複製代碼

而後執行npm run start,瀏覽器窗口就會自動打開,顯示index.html引用全部js文件的結果。這是隻要改變index.html或者任意一個引用的js文件,瀏覽器都會自動刷新以顯示最新的結果。

多個輸出

單個輸出的狀況比較簡單,dist目錄下只有一個index.html文件,瀏覽器一打開就會顯示這個文件。可是,若是是上面「動態生成HTML」一節描述的多個html文件,DevSever確定沒法知道顯示哪個了,這時有如下2種辦法:

  1. Webpack-dev-server通常默認打開的是localhost:8080,在contentBase中咱們配置的就是dist文件夾,因此這個地址等同於dist文件夾,若是輸入localhost:8080/index1.html就會顯示index1.html,輸入localhost:8080/index2.html就會顯示index2.html

  2. 直接配置openPage,告訴webpack-dev-server默認打開哪一個:

    devServer: {
      contentBase: "./dist",
      // 告訴webpack-dev-server默認打開index2.html
      openPage: './index2.html'
    }
    複製代碼

熱更新

在已經使用了webpack-dev-server的狀況下,修改文件已經不須要手動刷新了,可是,瀏覽器自動刷新也會有一些問題:好比在調試一個表單驗證問題,已經填寫了一些信息,若是瀏覽器自動刷新,會直接將以前填寫的表單內容清空。而webpack的熱更新(HMR, Hot Module Replacement),就可讓瀏覽器在不刷新的狀況下直接更新瀏覽器頁面。

  1. 使用HotModuleReplacementPlugin,它包含在webpack中。在配置文件中添加:

    const webpack = require('webpack');
    
    module.exports = {
      plugins: [new webpack.HotModuleReplacementPlugin()]
    }
    複製代碼
  2. 在以前devServer中設置hot爲true

    devSever: {
      contentBase: './dist',
      hot: true
    }
    複製代碼
  3. 入口文件index.js底部添加對子模塊的監聽

    if (module.hot) {
      module.hot.accept();
    }
    複製代碼

這樣,若是再修改子模塊a.js中的文件時,在不刷新瀏覽器的狀況下就能夠更新。

Devtool調試

到目前爲止,都是介紹如何用webpack打包從而進行開發,卻忽視了一個問題於webpack將多個文件打包成了單個文件的。最後在瀏覽器中,咱們能看到的也就是打包後的這個文件。若是是打斷點調試,咱們固然但願是能在打包以前原始模塊中,這樣定位問題和修改也比較方便。爲了實現這個功能,webpack中提供了devtool配置,只須要設置以下:

module.exports = {
  devtool: 'source-map'
}
複製代碼

這樣,在瀏覽器的控制檯中就能夠看到打包以前的原始模塊文件。

mode

一般,js代碼都要分開發模式和生產模式。好比,在開發模式中,不用太糾結代碼的性能,更加劇視代碼的可讀性,而在生產模式中,代碼通常須要進行優化和壓縮等。在webpack4中,能夠經過mode來配置development模式和production模式,主要有兩種配置方法:

  1. 直接在配置文件中指定mode

    module.exports = {
      mode: 'development'
    }
    複製代碼
  2. 在運行webpack時增長--mode,能夠在package.json中添加

    "scripts": {
      "dev": "webpack --mode development",
      "prod": "webpack --mode production"
    }
    複製代碼

    這時,運行npm run dev就是開發模式,npm run prod就是生產模式

分別看一下dist目錄下輸出的文件,能夠發現production模式下的代碼體積更小,dev模式下的代碼可讀性更強。

多配置文件

在webpack 4中,已經能夠經過mode來指定不一樣生產環境下代碼的編譯模式了,但這仍是不夠的。好比,在開發環境下,咱們但願開啓devtool調試,而在生產模式下就不須要。像這樣的區分配置還有不少,若是每次都是去修改webpack.config.js,顯然很是麻煩。一種簡單的方法是,咱們建一個build文件夾,在這個文件下下放三個配置文件。爲了整合,先安裝一個叫webpack-merge的插件,其主要做用是合併兩個webpack配置文件:

npm i webpack-merge -D
複製代碼

而後分別寫三個配置文件:

  • webpack.base.config.js:這個文件用來存放公共的配置,也就是在開發環境和生產環境都要用到的

    module.exports = {
      // 公共配置,如entry/output
    }
    複製代碼
  • webpack.dev.config.js:開發環境下的獨有配置

    const merge = require('webpack-merge');
    const baseConfig = require('./webpack.base.config')
    module.exports = merge(baseConfig, {
        mode: 'development',
        devtool: 'source-map',
        devServer: {
            contentBase: './dist',
            // 在開發模式下,默認打開index1 
            openPage: './index1.html',
            hot: true
        },
    })
    複製代碼
  • webpack.prod.config.js:生產環境下的獨有配置

    const merge = require('webpack-merge');
    const baseConfig = require('./webpack.base.config');
    
    module.exports = merge(baseConfig, {
        mode: 'production',
        devServer: {
            contentBase: './dist',
            // 在生產模式下,默認打開index2
            openPage: './index2.html',
            hot: true
        },
    })
    複製代碼

能夠看到,base只是一個公共文件,真正傳到webpack使用的是dev和prod。那麼如何傳到webpack呢?在webpack或者webpack-dev-server運行時,能夠經過--config指定配置文件。因此,咱們直接修改package.json

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

此時,執行npm run dev,就會按照webpack.dev.config.js的配置,打開index1.html,而且開啓debug;執行npm run prod,就會按照webpack.prod.config.js的配置,打開index2.html

相關文章
相關標籤/搜索