webpack系統學習之路——基礎篇

前言

用vue-cli腳手架自動生成項目,裏面的webpack配置苦澀難懂,因而下定決心想要系統學習webpack,寫下此文來記錄學習過程。javascript

一. webpack主要是作什麼的?

首先,webpack是前端流行的模塊打包工具,使前端項目能夠面向對象開發(模塊化),與Node.js模塊不一樣,webpack模塊能夠用多種方式表達它們的依賴關係。如下是幾個例子:css

  • ES2015 import 語句
  • CommonJS require() 語句
  • AMD definerequire 語句
  • @import 語句能夠引入css/sass/less文件
  • 樣式表 url(...) 或 HTML <img src=...> 文件中的圖像url

二. 安裝並配置webpack.config.js文件

2.1 安裝webpack

// 1. 初始化項目
npm init -y

// 2. 安裝webpack以及安裝webpack-cli(用於在命令行上運行webpack的工具), 不建議全局安裝
npm install webpack webpack-cli --save-dev複製代碼

2.2 配置webpack.config.js文件

  1. 默認配置

    在沒有配置webpack的出入口文件時,webpack有爲咱們提供默認配置,此時只須要執行如下命令行:html

    // 必須告訴webpack你的入口文件是index.js
    npx webpack index.js 
    複製代碼

    其中index.js(index.js依賴着a.js,a.js依賴着b.js,b.js依賴着c.js)是入口文件,webpack的默認配置會自動在當前項目的根目錄下新建dist/main.js做爲出口文件,入口文件通過webpack的打包,就能夠將模塊之間的依賴關係翻譯出瀏覽器可以識別的代碼,此時只須要在index.html文件引入<script src="./dist/main.js"></script> 前端

    可是npx webpack index.js 中的npx 是 什麼意思?vue

    譬如,當咱們全局安裝webpack時,輸入命令行webpack -v就能夠直接獲取版本,但當咱們在項目內局部安裝(--save-dev) webpack時,輸入命令行webpack -v 會報錯(全局webpack刪除的狀況下),由於系統會默認到本地文件(全局)中搜索webpack,此時是搜索不到的,所以會報錯。可是輸入npx webpack -v 就能夠獲取當前項目內的webpack版本。因此,npx的含義就是在當前項目下的依賴包node_modules 去查找webpack,同理可得npx webpack index.js的含義就是在當前項目的node_modules中找到webpack並執行webpack index.js命令打包入口文件。


    java

  2. 本身配置
    node

在當前項目的根目錄下新建webpack.config.js文件webpack

// webpack.config.js

const path = require('path')

module.exports = {
  // mode的做用是配置運行環境(development/production)
  // development:打包後的bundle.js文件不會被壓縮; production: 打包後的bundle.js會被壓縮成一行代碼
  mode: 'development',
  // 入口文件
  entry: './src/index.js',
  // 出口文件
  output: {
    // 文件名
    filename: 'bundle.js',
    // 打包後的文件放在哪一個文件夾,採用絕對路徑,在webpack.config.js當前目錄下新建文件夾dist
    path: path.resolve(__dirname, 'dist')
  }
}複製代碼

 此時再輸入命令行css3

npx webpack複製代碼

就會發現打包完成而且在項目根目錄下多了dist/bundle.js文件,git

此時在index.html文件引入<script src="./dist/bundle.js"></script>

值得注意的是,webpack.config.js文件名是webpack默認的配置文件名,若是把webpack.config.js換成其餘名字如config.js後再輸入npx webpack會發現配置失敗

解決方法是,輸入如下命令行

// 意思就是告訴webpack個人配置文件叫作config.js,並幫我打包
npx webpack --config config.js複製代碼

如何簡化命令?

能夠在package.json文件(一開始初始化項目npm init -y所生成的package.json文件)的"scripts"屬性添加

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

這樣之後要打包文件,直接運行如下命令行就能夠完成打包

npm run build複製代碼


如今,咱們項目的目錄結構是:


爲了接下來操做方便,咱們將Index.html放入dist根目錄下(index.html修改相關配置<script src=".bundle"></script>


三. 更多基礎配置

webpack除了能夠打包js文件,還能夠打包其餘文件,如:css/img

那麼咱們來嘗試加載.jpg文件

// index.js
const img = require('img.jpg')複製代碼

運行npm run build

報錯:
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type.

意思就是:
模塊解析失敗:意外字符'�'(1:0]您可能須要適當的加載程序來處理此文件類型。複製代碼

所以,對於非js文件,webpack雖然能夠打包,可是webpack殊不知如何打包,必須你去告訴webpack該怎麼執行,這時就須要用到loader

3.1 使用loader打包非js文件


打開咱們的webpack.config.js,添加"module"屬性

const path = require('path')

module.exports = {
  // mode的做用是配置運行環境(development/production)
  // development: 打包後的bundle.js不會被壓縮; production: 打包後的bundle.js會被壓縮成一行代碼
  mode: 'development',
  // 入口文件
  entry: './src/index.js',
  // 出口文件
  output: {
    // 文件名
    filename: 'bundle.js',
    // 打包後的文件放在哪一個文件夾,採用絕對路徑
    path: path.resolve(__dirname, 'dist')
  },
  // 該模塊表示當打包除了js文件外webpack不知如何打包時在這裏查看打包規則
  module: {
    rules: [{
      test: /\.(jpg|png|gif)$/,        // 當打包以 .jpg|.png|.gif 結尾的文件      use: {                    // 使用file-loader來打包
        loader: 'file-loader'
      }
    }]
  }
}複製代碼

配置完後,不要忘記下載file-loader依賴npm install file-loader --save-dev

此時再從新打包,會發現.jpg打包成功。此時打開dist文件,裏面除了bundle.js文件,還多了一個.jpg文件

在index.js嘗試打印require('img.jpg')的返回值,發現返回值是img在dist目錄下的文件名


此時咱們能夠得出loader的打包機制:

發現非js文件-->將非js文件複製一份放到dist並修更名稱-->將文件名返回來方便你去根據路徑引用它,接下來將對loader進行詳細的介紹


  1.  使用Loader 打包靜態資源(圖片篇)—— file-loader/url-loader

    a. 使用file-loader對圖片進行打包

    上面的簡單例子讓咱們對loader有初步的認識,那麼若是當咱們打包後的圖片文件名不想要一串哈希值的時候,又或者不想直接存放在dist目錄,想在dist目錄下建立一個images目錄專門存放圖片時該怎麼辦?繼續打開webpack.config.js,給use添加"options"屬性

    const path = require('path')
    
    module.exports = {
      // mode的做用是配置運行環境(development/production)
      // development: 打包後的bundle.js不會被壓縮; production: 打包後的bundle.js會被壓縮成一行代碼
      mode: 'development',
      // 入口文件
      entry: './src/index.js',
      // 出口文件
      output: {
        // 文件名
        filename: 'bundle.js',
        // 打包後的文件放在哪一個文件夾,採用絕對路徑
        path: path.resolve(__dirname, 'dist')
      },
      // 該模塊表示當打包除了js文件外webpack不知如何打包時在這裏查看打包規則
      module: {
        rules: [{
          test: /\.(jpg|png|gif)$/,        // 當打包以 .jpg|.png|.gif 結尾的文件
          use: {                    // 使用file-loader來打包
            loader: 'file-loader',
    	options: {        // 可選的
                // placeholder 佔位符配置打包後的文件名:[name]是原先的文件名,[ext]是原先的文件後綴,[hash]是一串哈希值
                 name: '[name]_[hash].[ext]', 
                // 打包後在dist目錄下的路徑
                 outputPath:  'images/'    // 打包後圖片存放在dist/images
            }
          }
        }]
      }
    }複製代碼

    以上是關於圖片打包的經常使用配置,能夠知足大多數業務需求,關於file-loader的更多配置,詳情可見webpack的官方文檔

    b. 使用url-loader對圖片進行打包

    事實上,url-loader能夠完成file-loader所能完成的任務

    先下載url-loader npm install url-loader --save-dev

    並把webpack.config.js中的loader配置換成'url-loader'

    從新對項目進行打包試試

    // webpack.config.js
    
    const path = require('path')
    
    module.exports = {
      // mode的做用是配置運行環境(development/production)
      // development: 打包後的bundle.js不會被壓縮; production: 打包後的bundle.js會被壓縮成一行代碼
      mode: 'development',
      // 入口文件
      entry: './src/index.js',
      // 出口文件
      output: {
        // 文件名
        filename: 'bundle.js',
        // 打包後的文件放在哪一個文件夾,採用絕對路徑
        path: path.resolve(__dirname, 'dist')
      },
      // 該模塊表示當打包除了js文件外webpack不知如何打包時在這裏查看打包規則
      module: {
        rules: [{
          test: /\.(jpg|png|gif)$/,        // 當打包以 .jpg|.png|.gif 結尾的文件
          use: {                    // 使用url-loader來打包
            loader: 'url-loader',
    	options: {        // 可選的
                // placeholder 佔位符配置打包後的文件名:[name]是原先的文件名,[ext]是原先的文件後綴,[hash]是一串哈希值
                 name: '[name]_[hash].[ext]', 
                // 打包後在dist目錄下的路徑
                 outputPath:  'images/'    // 打包後圖片存放在dist/images
            }
          }
        }]
      }
    }複製代碼

    // index.js
    
    const avatar = require('../avatar.jpg')
    const root = document.querySelector('.root')
    const image = new Image()
    image.src = avatar
    root.append(image)複製代碼

    // index.html
    
    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport"
            content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    <body>
      <div class="root"></div>
    <script src="./bundle.js" type="text/javascript"></script>
    </body>
    </html>
    
    複製代碼

    打包完成會發現,dist目錄下並無image目錄,也沒有圖片文件,可是在網頁上打開index.html卻發現圖片依然能夠顯示在頁面上,這是咱們打開bundle.js下拉到最下面發現


    咱們再打開index.html頁面查看img元素

    因而咱們能夠得出結論:url-loader能夠將打包後的圖片生成base64並嵌入bundle.js文件中,這樣帶來的結果是:

    好處:減小頁面對圖片發送http請求,提高了性能

    壞處:若圖片太大,會致使bundle.js文件太大,網頁加載js請求時間太長,而這段時間頁面上沒有任何東西,用戶體驗差。

    那咱們是否作一個限制:當圖片比較小的時候,用url-loader對圖片進行打包以減小http請求;當圖片比較大時,用file-loader打包,提升用戶體驗。

    因而咱們繼續打開webpack.config.js爲options添加屬性"limit"

    // webpack.config.js
    
    const path = require('path')
    
    module.exports = {
      // mode的做用是配置運行環境(development/production)
      // development: 打包後的bundle.js不會被壓縮; production: 打包後的bundle.js會被壓縮成一行代碼
      mode: 'development',
      // 入口文件
      entry: './src/index.js',
      // 出口文件
      output: {
        // 文件名
        filename: 'bundle.js',
        // 打包後的文件放在哪一個文件夾,採用絕對路徑
        path: path.resolve(__dirname, 'dist')
      },
      // 該模塊表示當打包除了js文件外webpack不知如何打包時在這裏查看打包規則
      module: {
        rules: [{
          test: /\.(jpg|png|gif)$/,        // 當打包以 .jpg|.png|.gif 結尾的文件
          use: {                    // 使用url-loader來打包
            loader: 'url-loader',
    	options: {        // 可選的
                // placeholder 佔位符配置打包後的文件名:[name]是原先的文件名,[ext]是原先的文件後綴,[hash]是一串哈希值
                 name: '[name]_[hash].[ext]', 
                // 打包後在dist目錄下的路徑
                 outputPath:  'images/'    // 打包後圖片存放在dist/images,
                 limit: 204800       // 當圖片小於200KB時使用url-loader打包
            }
          }
        }]
      }
    }複製代碼

    從新打包,經過修改limit的值,能夠發現當小於limit值,圖片以base64的形式打包到bundle.js,當大於limit值,圖片被打包到dist/images目錄下

  2.  使用Loader 打包靜態資源(樣式篇)

    a. 使用style-loader 和 css-loader對css文件進行打包

    首先咱們在src目錄下新建一個index.css文件,對上文的圖片添加樣式

    // src/index.css
    
    .avatar {
        width: 150px;
        height: 200px;
        transform: translate(100px, 100px);
    }複製代碼

    在index.js中引入

    // src/index.js
    
    import './index.css'
    import avatar from '../avatar.jpg'
    
    const root = document.querySelector('.root')
    const image = new Image()
    image.src = avatar
    image.classList.add('avatar')
    root.append(image)
    
    複製代碼

    而後從新對項目進行打包,若是對loader理解透徹的話不用打包也知道webpack是不知道如何打包非js文件的,須要藉助loader,所以如今打包文件確定會報錯。

    打開webpack.config.js進行配置,添加如下的配置,並下載style-loader以及css-loader,下載完成後從新打包


    打包後打開Index.html,發現圖片確實添加上了樣式



    仔細一看發現樣式被自動添加到index.html的<style></style>標籤下面,如今咱們大概能夠知道style-loader和css-loader的做用:

    style-loader: 將打包後的css代碼添加到index.html的<style></style>標籤

    css-loader: 將從js文件下引入的css文件打包

    b. 使用sass-loader對.scss文件進行打包

    因爲咱們在開發項目時更多地是使用sass/stylus/less來寫樣式,下文舉sass的例子,stylus/less是相似的操做

    將index.css的後綴改爲.scss後對index.css的代碼修改爲.scss的語法

    // index.scss
    
    body {
        .avatar {
            width: 150px;
            height: 200px;
            transform: translate(100px, 100px);
        }
    }複製代碼

    在webpack.config.js的配置中添加sass-loader,並下載

    sass-loader須要您本身安裝Node Sass或Dart Sass,這裏咱們下載node-sass。這使您能夠控制全部依賴項的版本,並選擇要使用的Sass實現.

    npm install sass-loader node-sass webpack --save-dev



    c. 使用postcss-loader

    上面咱們使用到了css3的transform這個屬性,咱們知道css3的新屬性須要兼容瀏覽器,可是若是咱們每次都手動去兼容瀏覽器會很繁瑣,因而webpack爲咱們提供了postcss-loader

    首先下載

    npm install postcss-loader --save-dev

    打開webpack.config.js配置postcss-loader


    官方文檔要求使用postcss-loader須要單獨在項目根目錄下建立一個postcss.config.js,當webpack打包時發現須要用到postcss-loader時,就會去postcss.config.js查看

    // postcss.config.js
    
    module.exports = {
      plugins: [
        // 插件autoprefixer,須要下載(npm install autoprefixer --save-dev),用來自動添加兼容瀏覽器的廠商前綴
         require('autoprefixer')
      ]
    }複製代碼

    從新打包打開頁面發現已經自動爲咱們兼容了css3了


    d. 補充——importLoaders, modules

    webpack遇到scss文件打包時,會自下而上/自右向左調用webpack.config.js的loader,如

    根據咱們上面的配置,webpack會依次調用postcss-loader-->sass-loader-->css-loader-->style-css

    但有時候咱們的scss文件不止一個,好比在index.scss文件中引入@import "./avatar.scss",

    此時對@import進來的scss文件,webpack可能已經調用了postcss-loader-->sass-loader,因此此時@import進來的scss文件會錯過這兩個loader,爲了防止這種狀況的發生,咱們須要對css-loader進一步配置

    {
          test: /\.scss$/,
          use: [
             // 將css添加到<style></style>標籤
            'style-loader',
             // 將css文件能被webpack打包
            {
              loader: 'css-loader',
              options: {
                // 2 ==> 加載css-loader以前都必須先從新加載一次postcss-loader-->sass-loader
                importLoaders: 2
              }
            },
             // 將sass代碼能被webpack轉換成css代碼
            'sass-loader',
             // 將sass代碼中相關的css3屬性添加瀏覽器兼容代碼
             'postcss-loader'
          ]
    }複製代碼

    另外一方面,若是咱們怕在index.js中全局引入的index.scss會污染其餘scss文件形成衝突甚至覆蓋,也就是說咱們想要css也能模塊化編程,避免耦合 。

    {
          test: /\.scss$/,
          use: [
             // 將css添加到<style></style>標籤
            'style-loader',
             // 將css文件能被webpack打包
            {
              loader: 'css-loader',
              options: {
                // 2 ==> 加載css-loader以前都必須先從新加載一次postcss-loader-->sass-loader
                importLoaders: 2,
                // 使css文件模塊化,互不干擾
                modules: true
              }
            },
             // 將sass代碼能被webpack轉換成css代碼
            'sass-loader',
             // 將sass代碼中相關的css3屬性添加瀏覽器兼容代碼
             'postcss-loader'
          ]
    }複製代碼

    此時咱們在index.html中引用index.scss時,能夠採用模塊化編程



  3.  使用Loader 打包靜態資源(字體文件篇)

    如何引入字體文件詳情可見 icomoon的使用

    這裏只講如何打包圖標字體相關的文件woff|eot|ttf|otf


3.2 使用plugins(插件)使打包更加便捷

  1. html-webpack-plugin

    因爲咱們的index.html文件在dist目錄下,可是項目新建使並無dist目錄,這意味咱們須要先打包,打包完成後再手動添加index.html文件在dist目錄並引入bundle.js

    爲了解決這個繁瑣的過程,webpack爲咱們提供了一個插件——html-webpack-plugin,它使得咱們打包完成後自動在dist目錄下生成index.html文件並自動導入bundle.js

    首先,下載 npm install html-webpack-plugin --save-dev
    在webpack.config.js

    // webpack.config.js
    
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports =  {
      mode: 'development',
      entry: './src/index.js',
      output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
      },
      // 該屬性是關於webpack插件的配置
      plugins: [
    // 實例化     new HtmlWebpackPlugin()
      ]
    }
    
    
    複製代碼

       打包,打開dist目錄下


     另外一個問題出現,若是咱們須要對index.html文件作一些初始化呢,好比須要添加一個根節點呢<div id="app"></div>。咱們能夠在實例化new HtmlWebpackConfig 的時候進行一些配置

// webpack.config.js

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

module.exports =  {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  // 跟webpack插件相關的配置
  plugins: [
     new HtmlWebpackPlugin({
       template: 'src/index.html'     // 模板
     })
  ]
}

複製代碼

// src/index.html ==> 模板

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>html 模板</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>
複製代碼

從新打包,點開打包後的dist/index.html文件查看



2.  clean-webpack-plugin

若是咱們把打包後的js文件名bundle.js更改爲app.js,從新打包,會發現dist目錄下原先的bundle.js沒有刪除,爲了不這種狀況,咱們就須要每次打包前手動刪除dist目錄。

一樣,爲了解決這個繁瑣的過程,咱們可使用第三方插件(webpack官方沒有的插件)—— clean-webpack-plugin,它會幫咱們在每次打包前把output配置的相應目錄刪除

下載 npm i clean-webpack-plugin -D 

在webpack.config.js

// webpack.config.js

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

module.exports =  {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  // 跟webpack插件相關的配置
  plugins: [
     // 自動在dist目錄下生成index.html
     new HtmlWebpackPlugin({
       template: 'src/index.html'     // 模板
     }),
     // 每次打包前先自動刪除output配置的目錄
     new CleanWebpackPlugin()
  ]
}

複製代碼


更多插件的使用見 webpack官網


3.3 entry 和 output 的基礎配置

// webpack.config.js


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

module.exports =  {
  mode: 'development',

  // entry: './src/index.js', ==> 至關於 entry: { main: './src/index.js' }
  entry: {
    // 默認狀況路徑的鍵名爲main
    main: './src/index.js',

    // 第二次打包,當打包入口文件超過一個使,須要在output使用佔位符進行相應的配置
    sub: './src/index.js'
  },

  output: {
    // 公共路徑:publicPath與filename拼接造成最終路徑如http://cdn.com.cn/main.js,
    // 會在webpack打包造成的index.html文件中自動引入該路徑
    publicPath: 'http://cdn.com.cn',

    // [name]佔位符,值對應的是entry的鍵名(main, sub)
    filename: '[name].js',

    path: path.resolve(__dirname, 'dist')
  },

  // 跟webpack插件相關的配置
  plugins: [
     // 自動在dist目錄下生成index.html
     new HtmlWebpackPlugin({
       template: 'src/index.html'     // 模板
     }),
     // 每次打包前先自動刪除output配置的目錄
     new CleanWebpackPlugin()
  ]
}
複製代碼

上面代碼的經常使用配置有詳細的註釋,就不過多說,更多配置可見 官方文檔


3.4 source-map的配置

當js有異常拋出時,咱們到瀏覽器console查看只能查看到在打包後bundle.js中對應的位置,可是在開發環境下,咱們更但願能映射到咱們src目錄下的源代碼對應的文件及行數,因而爲了解決這個問題,sourceMap就出現了,它使得咱們可以知道這種映射關係。

下圖是關於webpack.config.js文件中配置devtool屬性去配置source-map

如: devtool: "source-map"

每一個選項各有利弊,下文開始分析


  • none
    打包速度最快,可是js拋出的異常所在的具體位置沒法被映射出來
  • source-map
    打包速度最慢,可是js拋出的異常所在的具體位置會被映射出來(精確到某個文件的哪一行哪一列),dist目錄下有bundle.js.map文件
  • inline-source-map
    打包速度最慢,效果與source-map一致,可是dist目錄下沒有bundle.js.map文件,而是以base64字符串的形式放在了bundle.js文件的底部,所以帶有inline的選項就是source-map是以base64的形式打包到bundle.js
  • cheap-inline-source-map / cheap-source-map
    帶有cheap的選項的效果是讓source-map的映射關係忽略掉列映射,只要映射出某個文件的某一行便可,同時也只映射業務代碼,不會映射第三方模塊(node_modules)。這樣會使打包速度有所
  • inline-cheap-module-source-map / cheap-moudle-source-map
    因爲cheap會忽略掉第三方模塊的映射關係,而moudle的做用是讓cheap不要忽略第三方模塊,所以cheap-module的做用就是使業務代碼和第三方模塊可以映射,可是隻映射某個文件某行,忽略列的映射。打包速度會稍微慢些
  • eval
    帶有eval的效果執行效率最快,打包速度最快,可是隻能精確地映射到錯誤的文件,對於行數的映射可能牛出錯

    綜上所述,咱們能夠搭配每一個字段的效果來使用,這裏提供兩種方案:
    開發環境development建議:cheap-module-eval-source-map
    線上環境production建議:cheap-module-source-map


3.5 使用 WebpackDevServer 來提高開發效率

咱們每次修改完代碼,都必須從新執行npm run build 來從新打包,效率低下。

咱們但願每次修改完代碼後,webpack可以自動爲咱們從新打包。要實現這一需求,

webpack爲咱們提供了三種作法:

  1.  webpack watch mode(webpack 觀察模式)

    // package.json
    
    "scripts": {
        // --watch使得webpack會去監聽咱們src下的目錄,一旦發生改變,會立刻從新從新打包
        "build": "webpack --watch"
    },
    
    複製代碼

      但缺點是咱們須要手動去刷新瀏覽器,也沒辦法幫咱們啓動一個服務器,沒法發送ajax請求。懶人拯救世界,因而webpack-dev-server應運而生


   2. webpack-dev-server

      webpack-dev-server 爲你提供了一個簡單的 web server,而且具備 live reloading(實時從新加載) 功能。設置以下:

       先下載 npm i webpack-dev-server -D 

// package.json

"scripts": {
    // --watch使得webpack會去監聽咱們src下的目錄,一旦發生改變,會立刻從新從新打包
    "build": "webpack --watch",
    // webpac-dev-server會監聽src源代碼的變化從新打包並自動刷新瀏覽器,打包完成會自動打開瀏覽器...
    "start": "webpack-dev-server"
},複製代碼

// webpack.config.js
// 省略其餘配置

module.exports = {
  devServer: {
    // 配置告知webpack-dev-server,將dist目錄下的資源做爲server可訪問文件
    contentBase: './dist',
    // 打包完成後自動幫咱們彈出瀏覽器
    open: true,
    // 服務器端口號,默認8080
    port: 8000
  }
}複製代碼

如今,在命令行中運行 npm run start,咱們會看到瀏覽器自動加載頁面。若是你更改任何源文件並保存它們,web server 將在編譯代碼後自動從新加載。

值得一提的是,咱們看到文件已經打包完成了,可是在dist目錄裏並無看到文件,這是由於

webpack
是把編譯好的文件放在緩存中,沒有磁盤上的IO,可是咱們是能夠訪問到的


3. webpack-dev-middleware

webpack-dev-middleware 是一個封裝器(wrapper),它能夠把 webpack 處理過的文件發送到一個 server。 webpack-dev-server 在內部使用了它,然而它也能夠做爲一個單獨的 package 來使用,以便根據需求進行更多自定義設置。下面是一個 webpack-dev-middleware 配合 express server 的示例。


首先,安裝 expresswebpack-dev-middleware

npm install --save-dev express webpack-dev-middleware複製代碼

// package.json

"scripts": {
    // --watch使得webpack會去監聽咱們src下的目錄,一旦發生改變,會立刻從新從新打包
    "build": "webpack --watch",
    // webpac-dev-server會監聽src源代碼的變化從新打包並自動刷新瀏覽器,打包完成會自動打開瀏覽器...
    "start": "webpack-dev-server",
    // 本身構建的server
    "server": "node server.js"
},複製代碼

webpack.config.js

// webpack.config.js
// 忽略其餘配置

module.export = {
  output: {
    // 咱們將會在 server 腳本使用 publicPath
    // 以確保文件資源可以正確地 serve 在 http://localhost:3000 下,稍後咱們會指定 port number(端口號)。
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
}

複製代碼


在項目根目錄下新建server.js

// server.js

const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
// 引入webpack配置文件
const config = require('./webpack.config.js')
// 在node中使用webpack
// webpack(config)會返回一個編譯器complier,每執行一次complier,就會從新編譯一次
const complier = webpack(config)

const app = express()

// webpackDevMiddleware的做用就是隻要代碼發生變化,就會從新運行comliper,
// 從新運行後生成的文件的輸入內容的publicPath是config.output.publicPath的內容
app.use(webpackDevMiddleware(complier, {
  publicPath: config.output.publicPath
}))

// 指定端口號port:3000
app.listen(3000, () => {
  console.log('server is running')
})

複製代碼

如今,打開瀏覽器,訪問 http://localhost:3000。應該看到webpack 應用程序已經運行!


3.6 Hot Module Replacement 熱模塊更新

hot module replacement簡稱HMR,它可使咱們在不刷新瀏覽器(dev-server也不幫咱們刷新)的狀況下能夠更新模塊,這帶來的最大好處就是咱們每次修改完代碼不用經過刷新頁面來從新加載模塊,提高了咱們的開發效率。

使用HMR咱們必須藉助插件HotModuleReplacementPlugin ,該插件是webpack 自帶的插件,所以咱們無需安裝,只須要在配置文件引入

// webpack.config.js
// 省略配置

const webpack = require('webpack')
module.exports = {
    devServer: {
        contentBase: './dist',
        //開啓熱更新
        hot: true
    },
    plugins: [
        // HMR插件
        new webpack.HotModuleReplacementPlugin()
    ]
}複製代碼

如今,修改 index.js 文件,以便在其餘模塊內部發生變動時,告訴 webpack 接受該更新對應的模塊了。

// index.js

import a from './a.js'
import b from './b.js'

b()

// 判斷是否開啓了HMR
if (module.hot) {
    // 監聽a.js,若該模塊發生變化時調用回調函數
    module.hot.accept('./a.js', () => {
        // 更新模塊(即從新執行)
        a()
    })
}複製代碼

// a.js

export defalut function() {
    console.log('hello world')
}複製代碼

// b.js

export defalut function() {
    const btn = document.createElement('button')
    btn.innerHTML = 1
    btn.setAttribute('class', 'btn')
    btn.onclick = function() {
        btn.innerHTML = parseInt(btn.innerHTML) + 1
    }
    document.body.appendChild(btn)
}複製代碼

打開localhost:8080,點擊btn改變數字大小,而後修改a.js代碼,回到瀏覽器發現btn的數字不變,而console卻打印出了新修改的內容。

這樣子就實現了在不刷新瀏覽器的狀況下,更新模塊同時也不影響其餘模塊。

若是給btn添加樣式:

// style.css

.btn {
    background: red;
}複製代碼

運行頁面後修改樣式,神奇的事情發生了,咱們明明在index.js沒有配置監聽style.css,爲何會也能作到不刷新瀏覽器的狀況修改樣式?

其實css-loader已經幫咱們內部實現了相似上面監聽a.js的代碼了,因此咱們無需本身配置。

一樣, vue-loader等許多loader都有內部實現配置了HMR去監聽模塊。


3.7 使用 Babel 處理es6語法

  • 基礎配置

因爲es6語法並非全部瀏覽器都能識別的,所以咱們必須藉此webpack來將咱們的es6語法都轉義成瀏覽器可識別的es5語法。

babel 是專門用於es6轉義,打開babel官方文檔 查看在webpack的使用:

下載babel-loader(webpack打包js文件的方式), @babel/core(babel的核心庫,用於翻譯es6)

npm install babel-loader @babel/core --save-dev

// webpack.config.js
// 忽略其餘配置

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                // 不轉義node_modules第三方庫的js文件
                exclude: path.resolve(__dirname, 'node_modules'),
                loader: 'babel-loader'
            }
        ]
    }
}複製代碼

// index.js

// ...隨便寫點es6語法
const arr = [
   new Promise(() => {}),
   new Promise(() => {})
]
arr.map(item => {
  console.log(item)
})
複製代碼

此時打包 npx webpack.config.js

打開dist/bundle.js,拉到最下面,發現webpack並無把咱們的es6轉義成es5


事實上,babel-loader並不能轉義es6,只能起到創建一個webpack到babel之間的橋樑。 要作到轉義es6,話須要下載es6轉移規則==>@babel/preset-env

npm install @babel/preset-env --save-dev複製代碼

// webpack.config.js
// 忽略其餘配置

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: path.resolve(__dirname, 'node_modules'),
                loader: 'babel-loader',
                options: {
                    presets: ["@babel/preset-env"]
                }
            }
        ]
    }
}
複製代碼

從新打包後打開dist/bundle.js查看es6代碼發現被轉義


可是像Promise等等es6的新特性,並非沒有瀏覽器都有的,因此咱們還須要對這些新特性進行補充。

Babel包含一個polyfill,其中包含一個自定義的再生器運行時和core-js。
這將模擬完整的ES2015 +環境(不包含第4階段的提議),而且打算在應用程序中使用,而不是在庫/工具中使用。
(使用babel-node時會自動加載此polyfill)。
這意味着您可使用新的內置函數(例如Promise或WeakMap),靜態方法(例如Array.from或Object.assign),實例方法(例如Array.prototype.includes)和生成器函數(前提是您使用了regenerator插件)。
爲了作到這一點,polyfill和諸如String之類的本地原型添加到了全局範圍。


npm install @babel/polyfill --save複製代碼

由於這是一個polyfill(它將在您的源代碼以前運行),因此咱們須要它是一個dependency,而不是devDependency,因此須要用--save安裝而不是--save-dev


因爲polyfill須要在源代碼以前運行,所以咱們須要在index.js最上方引入這個模塊

// index.js

import "@babel/polyfill"

// ...隨便寫點es6語法
let a = [1,2,3,4]
let b = a.map(item => item + 1)複製代碼

從新打包後發現打包後的文件大小相比沒使用@babel/polyfill的時候大了不是一點點

問題來了,咱們代碼中只使用了map這個新特性,卻導入了全部es6新特性,可否按需加載須要的新特性呢?

// webpack.config.js
// 忽略其餘配置

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: path.resolve(__dirname, 'node_modules'),
                loader: 'babel-loader',
                options: {
                    presets: [
                        ['@babel/preset-env', {
                          // 按需加載es6新特性
                          useBuiltIns: 'usage'
                        }]
                    ]
                }
            }
        ]
    }
}複製代碼

從新打包後發現bundle.js文件大小縮小到32.8KiB啦


進一步優化:

// webpack.config.js
// 忽略其餘配置

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: path.resolve(__dirname, 'node_modules'),
                loader: 'babel-loader',
                options: {
                    presets: [
                        ['@babel/preset-env', {
                          // 按需加載es6新特性
                          useBuiltIns: 'usage',
                          // 設定咱們打包後要在什麼瀏覽器的什麼版本上運行
                          // 這樣@babel/preset-env就會根據該瀏覽器版本須要補充新特性而加載
                           targets: {
                             chrome: '67'
                           }
                        }]
                    ]
                }
            }
        ]
    }
}複製代碼

從新打包發現bundle.js文件大小回到跟沒使用@babel/polyfill時同樣

緣由是在chrome67的瀏覽器自己已經支持咱們所須要加載的es6新特性,所以polyfill不會幫咱們加載



  • 進階配置

上面的基礎配置對於平常寫業務的需求是夠用的。

可是由於@babel/polyfill是經過全局變量的方式注入,會污染全局環境。因此若是你要開發一個組件庫,類庫,第三方庫等時,就須要換一種方式配置(經過閉包入注而不是全局注入)去防止污染全局環境


Babel使用很小的幫助器來完成諸如的功能 _extend。默認狀況下,它將被添加到須要它的每一個文件中。有時不須要重複,特別是當您的應用程序分佈在多個文件中時。 這是@babel/plugin-transform-runtime插件的來源:全部幫助程序都將引用該模塊,@babel/runtime以免在編譯後的輸出中出現重複。運行時將被編譯到您的構建中。 該轉換器的另外一個目的是爲您的代碼建立一個沙盒環境。若是你直接導入 core-js or @babel/polyfill ,它提供了諸如內置插件Promise,Set和Map那些會污染全局範圍。雖然這對於應用程序或命令行工具多是能夠的,可是若是您的代碼是要發佈供他人使用的庫,或者您沒法徹底控制代碼運行的環境,則將成爲一個問題。 轉換器會將這些內置別名做爲別名,core-js所以您能夠無縫使用它們,而無需使用polyfill。


安裝

npm install @babel/plugin-transform-runtime --save-dev複製代碼

npm install @babel/runtime --save複製代碼

因爲babel的配置篇幅過於長,就不單獨寫在webpack.config.js中,

在項目的根目錄下新建文件.babelrc

// .babelrc

{
  "plugins": [['@babel/plugin-transform-runtime', {
    "corejs": 2,
    "helpers": true,
    "regenerator": true,
    "useESModules": false
  }]]
}
複製代碼

==> 等同於在webpack.config.js進行如下配置

// webpack.config.jsconst path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: path.resolve(__dirname, 'node_modules'),
        loader: 'babel-loader',
        // 可將options的內容放到.babelrc文件
        options: {
          "plugins": [['@babel/plugin-transform-runtime', {
             "corejs": 2,
             "helpers": true,
             "regenerator": true,
             "useESModules": false
         }]]
        }
      }
    ]
  }
}
複製代碼


根據官方文檔,咱們配置了corejs: 2, 所以必須安裝依賴npm install --save @babel/runtime-corejs2

從新打包便可。



最後,若是對你有幫助,能夠點個贊鼓勵一下我繼續寫下去嗎(●'◡'●)

後續會出webpack進階版(填坑中)

相關文章
相關標籤/搜索