webpack從0到1使用指南

李牧羊發表於2018-05-19

文章同步發表在:部落格地址

為什麼要用webpack

關於為什麼要使用webpack,我比較認同的一種說法是:

webpack可以很好地管理你開發中遇到的各種HTML、JS、CSS以及各種圖片資原始檔,同時對應不同的資源,webpack還提供了對應的Loaders將其進行轉化為適用於瀏覽器使用的格式

如何從0開始上手webpack

後會無期裡面,阿呂說過這麼一句話:"你連世界都沒有觀過,哪裡來的世界觀",在我們實際的專案開發中,比如你用的是vue,那麼vue已經有很好的腳手架工具(vue-cli)供你使用了,或者有的開發團隊,會有技術Leader專門預先做好相關的模板,方便後來新加入的成員儘快上手專案,但是隨著你開發的專案越多,你可能會越不注意到那些最基本的東西,比如說這個模板到底是如何搭建的,或者說讓你自己來搭建一個模板給其他人用,是否也能做到如此的簡單易上手,我想這是每一個想要在前端路上進階的人需要考慮的問題。

最好的方式就是自己試著去搭一個最簡單的模板,從0到1的過程是最痛苦的,但是1到2或者說2到3都是水到渠成的事,下面就看看怎麼開始從0搭建一個基於webpack的vue的開發環境

建立專案

注:使用的webpack版本為^3.10.0 首先通過webstorm或者你手上的其他IDE建立一個空的專案,我這裡暫且叫proWebpack,建立好後是這個樣子的:

檔案目錄

因為我們通過npm來管理包,然後每個專案的根目錄下都會有一個package.json檔案來管理專案的配置資訊,包括名稱、版本、許可證等後設資料以及記錄所需的各種模組,包括 執行依賴,和開發依賴,我們可以通過在命令控制檯用npm init命令建立一個package.json檔案,但是這樣很麻煩,因為這樣npm會通過命令列問答的方式來初始化並建立package.json檔案,為了方便起見我們使用npm init -y,這樣npm就會使用一些預設值進行初始化:

執行結果

ok現在我們得到一個package.json檔案,包含了一些簡單的資訊比如name、version等欄位,接下來,因為我們是想搭建一個用於vue開發環境的webpack目錄結構,到目前為止還沒看到vue的影子,依然是在命令列使用npm install vue --save,這裡注意package.json中的依賴包有dependenciesdevDependencies兩種,這兩種的區別:dependencies 表示我們要在生產環境下使用該依賴,devDependencies 則表示我們僅在開發環境使用該依賴,我們install時候使用--save會把包安裝在dependencies 下面,所以執行完上面的命令你可以看到這樣的目錄結構:

{
  "name": "proWebpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "vue": "^2.5.16"
  }
}
複製程式碼

然後在devDependencies下面安裝webpack,再次提醒,由於webpack4變化較大,這裡使用^3.10.0版本的webpack: npm install -D webpack@3.10.0,此時目錄結構如下:

{
  "name": "proWebpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "vue": "^2.5.16"
  },
  "devDependencies": {
    "webpack": "^3.10.0"
  }
}

複製程式碼

建立入口檔案

接下來建立入口檔案,在根目錄下建立一個index.html作為啟動頁面,一個webpack.config.js作為webpack配置檔案(實際專案中這裡會有webpack配置檔案,分別用於開發環境和生產環境,這裡簡便起見就用一個配置檔案),新建一個src目錄,在該目錄下新建一個index.js作為打包入口檔案:

proWebpack
├─ index.html 啟動頁面
├─ package-lock.json
├─ package.json 包管理
├─ src
│    └─ index.js 入口檔案
└─ webpack.config.js  webpack配置檔案
複製程式碼

為了更接近vue-cli建立出來的模板,我們還需要建立一個Vue例項提供給入口檔案的el掛載,這個Vue檔案很簡單長這樣:

<template>
  <div>proWebpack</div>
</template>

複製程式碼

然後寫好入口檔案:

import Vue from 'vue'
import App from '../App.vue'

new Vue({
  el: '#app',
  render: h => h(App)
})
複製程式碼

同時別忘記在Index.html裡面要提供一個供vue例項掛載的HTMLElement例項:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>proWebpack</title>
</head>
<body>
<div class="app">proWebpack</div>
</body>
</html>

複製程式碼

這樣準備工作做得差不多了,下面做最核心的部分

webpack.config.js的編寫

webpack有這麼幾個核心的概念:

  • entry 入口起點,webpack會找出入口起點的直接或間接依賴項,將他們進行處理輸出為我們稱之為bundle的檔案中
  • output 輸出路徑,告訴 webpack 在哪裡輸出它所建立的 bundles以及如何給這些buldles命名
  • loader 因為webpack本身只能理解javascript檔案,所以當我們要用vue或者React的時候可以使用相關的loader(比如我們熟知的vue-loader)來將其轉換成webpack 能夠處理的有效模組
  • plugins 擴充套件外掛,可以用於打包的優化和壓縮,這個可能要結合實際使用更好理解
  • module 模組 webpack中的一切你都可以理解為模組
  • chunk 程式碼塊,一個 chunk 由多個模組組合而成,用於程式碼合併與分割。

下面開始配置,因為所有輸出檔案的目標路徑必須是絕對路徑,所以這裡要用到使Node.js 的 path 模組

// 引入webpack和path模組
const path = require('path')
const webpck = require('webpack')
複製程式碼

簡單配置檔案的entry和output

const path = require('path')
const webpck = require('webpack')
const productionPath = require('./package.json').name

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

複製程式碼

這裡程式碼應該不難理解,就是從index.js這個入口進去,把直接或間接依賴項打包輸出dist資料夾目錄下, 怎麼執行呢,我們需要在package.json的scripts物件裡面新增我們自己的build命令:

//  這裡用最簡單的命令
"build": "webpack -w",
複製程式碼

因為我們的webpack配置檔案就叫webpack.config.js,預設情況下,webpack在執行的時候會搜尋當前目錄webpack.config.js 檔案執行,實際開發中我們會使用 --config 選項來指定配置檔案來對開發環境和生產環境的配置做出區分

直接在控制檯npm run build,果不其然報錯了:

ERROR in ./App.vue
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
| <template>
|   <div>proWebpack</div>
| </template>
 @ ./src/index.js 2:0-28
複製程式碼

很明顯,webpack告訴我們他沒法識別vue檔案,需要我們提供一個相關的loader來處理成webpack可以識別的檔案,這裡先npm install vue-loader -D,然後回到webpack.config.json配置loader:

const path = require('path')
const webpck = require('webpack')

module.exports = {
  entry: {
    index: './src/index.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    // 模組配置
    rules: [
      //模組規則(配置 loader、解析器等選項)
      {
        // 這裡是匹配條件,每個選項都接收一個正規表示式或字串
        // test是必須匹配選項
        // exclude 是必不匹配選項(優先於 test 和 include)
        // 對選中後的檔案通過 use 配置項來應用 Loader,可以只應用一個 Loader 或者按照從後往前的順序應用一組 Loader,同時還可以分別給 Loader 傳入引數。
        test: /\.vue$/,
        exclude: /node_modules/,
        use: {loader: "vue-loader"}
      }
    ]
  }
}

複製程式碼

配置好之後再run一遍,咦,又報錯,我們看下主要的報錯資訊:

ERROR in ./App.vue
Module build failed: Error: Cannot find module 'vue-template-compiler'
    at Function.Module._resolveFilename (module.js:527:15)
    at Function.Module._load (module.js:476:23)

複製程式碼

cannot fount那就是需要我們去install唄,但是為什麼要下這個模組,我找到一個比較靠譜的說法:

其中 vue-template-compiler 是 vue-loader 的 peerDependencies,npm3 不會自動安裝 peerDependencies,然而 vue-template-compiler 又是必備的,那為什麼作者不將其放到 dependencies 中呢?有人在 github 上提過這個問題,我大致翻譯一下作者的回答(僅供參考):這樣做的原因是因為沒有可靠的方式來固定巢狀依賴的關係,怎麼理解這句話?首先 vue-template-compiler 和 vue 的版本號是一致的(目前是同步更新),將 vue-template-compiler 指定為 vue-loader 的 dependencies 並不能保證 vue-template-compiler 和 vue 的版本號是相同的,讓使用者自己指定版本才能保證這一點。檢視作者的回答(英文) 。如果兩者版本不一致,執行時就不會錯誤提示。

那隻需要我們npm i vue-template-compiler -D一下,然後在跑一遍build指令碼,不出意外你應該能在專案根目錄看到一個dist資料夾,把這個資料夾裡面的js檔案引入啟動頁面:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>proWebpack</title>
</head>
<body>
<div class="app">proWebpack</div>
<script src="/dist/bundle.js"></script>
</body>
</html>
複製程式碼

接下來我們要訪問這個頁面,這裡用到webpack提供的devsever來除錯我們的頁面, DevServer 會啟動一個 HTTP 伺服器用於服務網頁請求,同時會幫助啟動 Webpack ,並接收 Webpack 發出的檔案更變訊號,通過 WebSocket 協議自動重新整理網頁做到實時預覽。這就是為什麼你用vue-cli搭建出來的腳手架可以做到頁面實時渲染,配置這個也很簡單,在webpack.config.js裡面新增如下配置:

devServer: {
    port: 3000, // 埠號
  }
複製程式碼

其實webpack-dev-server可以配置很多引數,這裡不過多展開,接下來需要修改下我們的build指令碼:

"build": "webpack-dev-server"
複製程式碼

別忘了npm install一下webpack-dev-server,最後執行npm run build看到控制檯報出成功資訊並告訴你你的專案正執行在localhost:3000:

$ npm run build

> proWebpack@1.0.0 build D:\proWebpack
> webpack-dev-server --open

Project is running at http://localhost:3000/
webpack output is served from /

複製程式碼

訪問localhost:3000看到如下顯示的話,恭喜你,webpack配置如何從0到1你應該已經清楚了:

渲染結果

更進一步

我們知道了怎麼初步配置webpack還遠遠不夠,實際開發中我們會遇到更多樣的情況,比如說 當我們只有一個輸出檔案的時候我們可以在output寫死,就像我們上面寫的:

output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  }
複製程式碼

但是當我們有多個檔案輸出的時候,一個個去寫是一個費力不討好的做法,這個時候就要用到webpack內建的變數了,我們可以這麼寫:

output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].[hash].js'
  }
複製程式碼

這樣會賦予每一個bundle唯一的名稱,並且在每次構建過程中,生成唯一的 hash ,這時候稍微改動一下指令碼檔案:

"scripts": {
    "start": "webpack-dev-server",
    "build": "webpack -w",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
複製程式碼

之前寫一起是為了方便理解, 現在我把跑本地伺服器和打包的指令碼拆開寫,更語義化,一般正常開發也是這麼做的,先跑一下build,你會發現此時dist資料夾下面生成了新的打包檔案index.8ad46f4fb5c8385db614.js,然後把這個檔案同樣引入index.html,跑start指令碼發現結果也是照常輸出,但是又有了新的問題,如果我們每次build之後都要手動引入帶著一長串hash的js檔案也是很蠢的事情,所以這裡我們可以用上一個plugin叫做html-webpack-plugin,這個外掛主要有兩個作用: 1、在每次編譯完成之後動態為html檔案引入外部資源(script、link),對於我me你這種帶hash的檔名來說無疑是極為方便的 2、可以制定一個模板的html檔案,html-webpack-plugin可以根據這個模板來生成html入口檔案

下面看下如何使用:

// 引入
const HtmlWebpackPlugin = require('html-webpack-plugin')
    //省略若干程式碼
 plugins: [
    new HtmlWebpackPlugin({
      filename: './index.html', // 生成的入口檔案的名字,預設就是index.html
      template: './index.tpl.html',// 有時候,外掛自動生成的html檔案,並不是我們需要結構,我們需要給它指定一個模板,讓外掛根據我們給的模板生成html
      inject: 'body',// 有四個選項值 true, body, head, false----true:預設值,script標籤位於html檔案的 body 底部 body:同true head:插入的js檔案位於head標籤內 false:不插入生成的js檔案,只生成一個純html
    })
  ],
複製程式碼

用到的index.tpl.html只是和之前的index.html作了一點小小的區分,讓人知道這是模板生成的:

// index.tpl.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>這是模板生成的</title>
</head>
<body>
<div class="app">proWebpack</div>
</body>
</html>
複製程式碼

方便起見我們在package.json在新增一個clean的指令碼,每次build之前先刪除之前生成的dist檔案:

"scripts": {
    "start": "webpack-dev-server",
    "build": "npm run clean && webpack -w",
    "clean": "rimraf dist", // 新增clean指令碼
    "test": "echo \"Error: no test specified\" && exit 1"
  }
複製程式碼

然後run build一下:

模板生成的結果
我們發現這個時候的入口檔案已經是根據模板自動生成的了,而且自動把生成的js檔案新增到了入口檔案裡面,同時npm run start命令看到的介面也是使用模板生成的頁面,這就大功告成了。

最後

其實這裡還有一點沒有提到的就是css的處理,除了css-loader我們平常開發還會用到一些css前處理器如scss,還有postcss等樣式處理工具,這裡的處理其實也不難,感興趣的朋友可以自己嘗試一下,這裡由於篇幅原因(我想偷懶)就不過多展開了。

相關文章