一次webpack體驗

liuyueershihao發表於2017-11-10

目錄結構

    project
        - css
            - bootstrap.min.css
            - jb.css
        - fonts
            - 一些bootstrap的字型
        - images
            - 一些專案用到的圖片
        - js
            - bootstrap.min.js
            - jquery.min.js
            - jb.js
        - index.html
        - favicon.ico複製程式碼

專案背景

這個是公司要做的一個官方網站。由於專案比較簡單,要求是單頁的,沒有頁面跳轉,所以只有一個 .html 檔案。專案用了比較常規的 bootstrap + jquery 的開發,這個也沒啥好說的。考慮到 CDN 的可控性,所以把所有 bootstrap 的資源都下載到了本地進行引用。專案開始時是用了常規的 js 和 css 引用(css 放前面,js 放後面),在開發完成後,發現有時間多餘,就考慮用 webpack 對他進行處理一下,以鞏固和學習一下 webpack 所用到的知識

初始化工作

1. 初始化

    npm init

先初始化一個 package.json 檔案來管理我們 webpack 所依賴的檔案包。一路無腦回車即可。複製程式碼

2. 安裝 webpack

想要用 webpack ,那麼你首先肯定要安裝 webpack 才可以啊。用以下命令:

    npm install webpack --save複製程式碼

3. webpack.config.js

在根目錄下新建一個 webpack.config.js 檔案,用來對 webpack 進行配置。
當有這個檔案後,我們系可以在命令列中用以下命令來啟動配置好的 webpack 了。

    webpack --config webpack.config.js複製程式碼

4. 修改 webpack 打包命令

以上雖然也可以啟動配置好的 webpack。但是每次要輸這麼一串命令好像有點太長了(懶啊)。所以在 package.json 中修改這樣一項:

    "script": {

      + "start": "webpack --config webpack.config.js",

        "test": "echo \"Error: no test specified\" && exit 1"
    }複製程式碼

這樣的話我們就可以在命令列中少敲幾個鍵盤了。直接用以下命令,就等同於上面的命令了:

    npm start複製程式碼

好了,到這裡,初始化工作就做完了,那麼開始我們的 webpack 配置吧

webpack.config.js

1. 哪裡來的入口檔案?

什麼事入口檔案

webpack 建立應用程式所有依賴的關係圖(dependency graph)。圖的起點被稱之為入口起點(entry point)。入口起點告訴 webpack 從哪裡開始,並根據依賴關係圖確定需要打包的內容。可以將應用程式的入口起點認為是根上下文(contextual root) 或 app 第一個啟動檔案

跟其他的 spa 應用不一樣,這樣的普通應用,其所有依賴都來自於 index.html 檔案。如果說要有入口檔案的話,怎麼也應該是 index.html 他本身吧。但是 webpack
基本都是用 .js 檔案作為入口檔案,用 .html 作為入口檔案的...(反正我是沒有見到過)。至於這裡有啥原因的話,大家就參考一下這篇文章吧。(其實我也不懂)

所以說,不管怎麼樣,我們都需要有個入口檔案。

那就不管怎麼樣,我們經常看到的 webpack 的配置都是這樣的

    module.exports = {
        entry: './index.js'
    }複製程式碼

那麼我們不管三七二十一,先在根目錄下新建一個 index.js,然後讓他作為我們的入口檔案。

index.js

要知道,對於我們原來的專案而言,我們根本就是不需要這麼一個 index.js 檔案的(沒有他,我們可以活得更好)。但是我們又不得不建立了這樣一個檔案。那麼問題來了,這麼建立出來的檔案,裡面又該放什麼內容呢?我們總不該救這麼建立一個空檔案就算了吧。

在考慮這個問題的時候,我們可以先去看下 webpack 官方的那個很經典的圖(我很懶,就不放圖了,大家自己去網上找吧)。webpack 把左邊亂七八糟的 .js .css .png .jpg .sass。。。等等檔案全部打包成了靜態資源。也就是說,webpack 打包的是除 html 外的所有資源,那麼我們是不是隻要把這些資源都放到入口檔案中那麼就可以讓 webpack 幫我們打包了呢?

但是等等。其他的都沒有問題,什麼 css 啊,什麼 js 啊,都好說,因為用來也不過這麼幾個,但是圖片呢,字型呢?我在專案中用了那麼多圖片,要全部再在 index.js 裡面再寫一遍!天吶!這是要命的啊!那麼我能不能偷懶下,就只寫 css 和 js 呢,其他亂七八糟的我先不管?那就先這麼來吧。修改我們的 index.js 檔案,新增以下內容。

    require('./css/bootstrap.min.css')
    require('./css/jubang.css')
    require('./js/jquery.min.js')
    require('./js/bootstrap.min.js')
    require('./js/jb.js')複製程式碼

先這樣把他當作我們的入口檔案吧

2. 配置出口檔案

出口檔案就很好配置了,將他打包到根目錄下的 dist 目錄中。嗯 ~ 這很常見!

    var path = require('path');
    module.exports = {
        entry: './index.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        }
    }複製程式碼

3. 配置 loader

這個不知道怎麼配置,就先看這篇文章吧。

所以說,到這裡我們的 webpack.config.js 就是這樣的:

    var path = require('path');

    module.exports = {
        entry: './index.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        },
        module: {
            rules: [{
                test: /\.css$/,
                use: [
                    'style.loader',
                    'css-loader'
                ]
            }, {
                test: /\.(png|jpg|svg|git)$/,
                use: [
                    'file-loader'
                ]
            }, {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    'file-loader'
                ]
            }]
        }
    }複製程式碼

這裡我們用到了三個 loader,需要先安裝下

    npm install css-loader style-loader file-loader --save複製程式碼

這三個 loader 的作用大家還是自己去網上查詢吧

4. 第一次打包

配置到這裡,我們可以先來打包一下,有問題再改嘛!

命令列切換到專案目錄下,執行以下命令:

    npm start


打包結束後,專案的目錄結構

    project
        - css
        - dist
        - fonts
        - images
        - js
        - node-modules
        - favicon.ico
        - index.html
        - index.js
        - package.json
        - webpack.config.js

我們可以看到,專案根目錄下面多出來一個 dist 的目錄。沒錯,這個就是我們 webpack 打包後檔案生成的目錄,至於為什麼會是 dist 目錄,那是因為你在 webpack.config.js 的 output 中設定的 path。


現在我們來檢視下 webpack 打包出了什麼東西

        - dist
            - xxxx.jpg
            - xxxx.woff2
            - xxxx.jpg
            - xxxx.svg
            - bundle.js
            - xxxx.ttf
            - xxxx.eot
            - xxxx.woff複製程式碼

注:xxxx代表一串數字和字母的組合,為了表示方便就這麼寫了

打包檔案中生成了一個 .js 檔案,兩個 .jpg 檔案,四個字型檔案(.woff2、.ttf、.eot、.woff),和一個 .svg 檔案

我們來看下這是個型別的檔案都來自哪裡吧複製程式碼

1. bundle.js

bundle.js 是我們根據我們入口檔案,將我們在入口檔案中所有的依賴資源都打包進去生成的。這個也是我們最主要要關注的檔案。

2. .jpg

兩個 .jpg 檔案是從哪裡來的呢?檢視了兩張圖片之後,其實我們會知道,這兩個圖片都是在我們自己寫的 jb.css 中用來作為 background-image 引入的。我們說了,webpack 會根據入口文作為起點,並根據依賴關係圖來進行打包的。換句話說,我們在入口檔案中依賴了 jb.css,而 jb.css 依賴了兩張 .jpg 圖片,所以 webpack 根據依賴分析,將這兩張圖片也都一起打包進來了。

3. 字型檔案 + svg

四個字型檔案的來源就需要我們對 bootstrap 有一定了解了。如果熟悉 bootstrap 的同學肯定會知道,在 bootstrap 的依賴裡面,他正是依賴了這些字型,也就是說這些字型檔案是從 bootstrap.min.css 檔案中被打包進來的。其實我在做這個專案的時候,把 bootstrap 的原始碼弄到本地的時候,會發現裡面有一個 fonts 的資料夾,也就是我們專案根目錄下的 fonts 資料夾,這裡會有四個同樣檔案結尾的字型檔案和一個 .svg 結尾的 svg 檔案。而這五個檔案不就是我們這裡多出來的五個檔案嘛

如果還不放心的話,我們可以再做個驗證。修改 webpack.config.js 檔案

    {
        test: /\.(png|jpg|svg|git)$/,
        use: [
        -    'file-loader'
        +    'file-loader?name=[hash:8].[name].[ext]'
        ]
    }, {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [
        -    'file-loader'
        +    'file-loader?name=[hash:8].[name].[ext]'
        ]
    }複製程式碼

我們把圖片和字型檔案,在 file-loader 處理後讓他的名字就變成 ’8位hash值-原檔名-字尾名‘的格式,那麼我們就可以比對這幾個檔案的來源了。
重新打包後(你得先刪除原來的 dist 資料夾),我們就可以發現我們的猜測是正確的!

4. 遺留問題

雖然我們的第一次打包成功了,但是還是留下了幾個問題沒有解決:第一,我的 js、css、字型、圖片等資源都被打包進了 dist 目錄,但是作為我們的專案最主要的 index.html 檔案呢?沒有這個檔案,我們打包出來的東西還有什麼意義呢!。第二,我的 images 資料夾裡有那麼多圖片,你這個 webpack 打包後為什麼就只有兩張圖片了,其他的呢?

那麼接下來讓我們急需解決。

5. html-webpack-plugin

要解決第一個問題,我們需要用到 html-webpack-plugin 外掛。這個外掛的具體說明可以檢視這裡。這個外掛的作用是可以將 html 檔案打包,並自動新增對打包後的 output 檔案的引用。具體如何使用,請先安裝:

    npm install html-webpack-plugin --save複製程式碼

修改配置檔案:

    var path = require('path');
    +   var HtmlWebpackPlugin = require('html-webpack-plugin');

在 module 後面加

    plugins: [
        new HtmlWebpackPlugin({
            template: './index.html'
        })
    ]複製程式碼

我們在配置檔案中引入了一個外掛,並向 HtmlWebpackPlugin 建構函式傳遞了一個物件引數,在這個物件引數中,我們指明瞭一個 template 欄位,表面我們要打包的 html 的檔案源。然後我們重新打包,再檢視我們的目錄就可以發現 dist 目錄下多出了一個 index.html。由於這個專案本身就是一個簡單的不依賴任何環境的專案,所以如果正常的話我們直接開啟 index.html 頁面就能在瀏覽器里正常顯示了。雖然不知道會怎麼樣,但是我們還是開啟來試試吧。

當我們開啟 index.html 在瀏覽器中顯示的時候,我們發現瀏覽器中好多圖片都不見了。細想我們的專案程式碼,發現除了在 jb.css 中的背景圖被正確顯示以外,其他的定義在 img 標籤中的圖片沒有一張是顯示出來的。

所以雖然這個檔案配置還有點問題沒解決,那麼我們先來解決 html 中的 img 問題吧,也就是我們上面提到的第二個問題。

6. html-withimg-loader

要解決第二個問題(也就是 html 中的 img 問題),我們需要用到 html-whithimg-loader,具體關於這個怎麼用可以檢視這裡

修改配置檔案,直接在 rules 中再新增一條配置規則

    {
        test: /\.(html|htm)$/,
        use: [
            'html-withimg-loader'
        ]
    }複製程式碼

這條配置規則表明,所有要處理的 html 檔案首先會經過 html-withimg-loader 這個 loader 處理。就是這麼簡單能將我們第二個問題解決嗎?試試看吧,事件是檢驗真理的唯一標準。

修改資源目錄

重新打包,再檢視 dist 目錄,這一檢視不要緊,發現 dist 目錄下多了好多圖片檔案,密密麻麻,亂七八糟的,這些該不會就是我們 html 中的圖片吧。按住自己的強迫症,先找到 index.html (我們最關心的還是他嘛),開啟後再瀏覽器檢視效果。發現果然,我們的圖片都已經在了,而且樣式也差不多對了。但是這麼亂七八糟的 dist 目錄,怎麼會是我們這種強迫症患者所想要的結果呢!我們想要的是圖片都放在圖片資料夾下,字型都放在字型資料夾下,其他的比較少的就先讓他在外面呆著吧。說幹就幹,我們來調整一下我們的配置檔案

        {
            test: /\.(png|jpg|svg|git)$/,
            use: [
            -    'file-loader?name=[hash:8].[name].[ext]'
            +    'file-loader?name=images/[hash:8].[name].[ext]'
            ]
        }, 
        {
            test: /\.(woff|woff2|eot|ttf|otf)$/,
            use: [
            -    'file-loader?name=[hash:8].[name].[ext]'
            +    'file-loader?name=fonts/[hash:8].[name].[ext]'
            ]
        } 複製程式碼

自動刪除 dist

還有一個問題我已經忍了很久了,每次打包前,我們都需要手動先去刪除上次打包留下來的 dist 目錄,這個就煩了,雖然只是一個 delete 的事情,但是做多了也煩啊!我們想能不能讓 webpack 自動幫我們做了這件事,讓我們不需要手動去刪除。還好 webpack 夠智慧,總能滿足你提出來的各種無理取鬧。不過我們先要裝一個外掛:

    npm install clean-webpack-plugin --save複製程式碼

然後再配置檔案中新增對這外掛的引用

    var HtmlWebpackPlugin = require('html-webpack-plugin');
    +   var CleanWebpackPlugin = require('clean-webpack-plugin');複製程式碼

在 plugins 中新增對這個外掛的使用

    new CleanWebpackPlugin(['dist'])複製程式碼

這樣我們就能不用手動刪除 dist 資料夾了。

重新打包下試試吧!

打包完後我們在檢視 dist 目錄就瞬間感覺清爽多了有木有!

    - dist
        - fonts
            - 一些字型檔案
        - images
            - 一些圖片檔案

        - bundle.js
        - index.html複製程式碼

修改 index.html

將打包後的專案再次在瀏覽器中檢視,並檢視控制檯會發現,控制檯報了一堆錯誤。其中有幾個是對一些 css、js 檔案引用的錯誤。因為我們把需要用的 css、js 都打包進了 bundle.js 中了。而我們原來的專案是通過靜態資源引用的方式一個個匯入 html 檔案中的。所以,當我們 webpack 打包成功後,就不需要對這些資源進行引用了,我們只需要對 bundle.js(我們打包後的檔案)進行引用就可以了,所幸的是,打包後的檔案 webpack 已經自動幫我們引用了。所以直接在原來專案中的 index.html 中幹掉那些 css、js 就可以了。然後重新打包後就沒有這些資源找不到的亂七八糟的錯誤了。

但是,有一個小問題就是關於我們的 .ico 檔案。這是我們網站的圖示檔案。他的引用錯誤該如何解決呢?我們可以在生成 html 的時候,將這個問題先給解決了。修改配置檔案

    new HtmlWebpackPlugin({
        template: './index.html',
    +    favicon: path.resolve(__dirname, './favicon.ico')
    })複製程式碼

這樣的話我們的圖示檔案也就有了。

7. 關於 jquery

雖然一些亂七八糟的引用錯誤解決了,但是控制檯留下了一個讓我們非常頭疼的問題:jquery 的引用問題:

    Uncaught Error: Bootstrap's JavaScript requires jQuery複製程式碼

這裡我們需要用到 expose-loader 這個東西,關於他,可以檢視這裡,廢話不多說:

    npm install expose-loader --save複製程式碼

在 module 的 rules 中再新增一個 loader

    {
        test: require.resolve('./js/jquery.min.js'), // 引入 jquery
        use: [{
            loader: 'expose-loader',
            options: '$'
        }, {
            loader: 'expose-loader',
            options: 'jQuery'
        }]
    }複製程式碼

當然,網上或許有其他方法關於引入 jquery 的,這裡只是說一種。然後再打包我們的頁面就沒有問題了:各種資源都有了,js 寫的效果也出現了。

8. 提取 css

雖然網站看上去沒啥問題了,但是細心的同學肯定會發現:當我們開啟網站的時候,他會先出現一個沒有樣式的頁面,然後一閃而逝,最後才出現我們預期的樣子。這是為什麼呢?

原因很好理解,因為我們把 css 和 js 都打包進了同一個 bundle.js 裡面了。但是,這個 bundle.js 是在頁面最後面才載入進來的。也就是說,我們的樣式被放在了頁面的底部被載入。這完全不符合我們的預期啊。我們希望的是樣式在 head 中載入,而 js 指令碼才放在頁面底部載入。所以我們就不能把 css 和 js 一起打包進 bundle.js 中了。

extract-text-webpack-plugin

詳細資料看這裡

    npm install extract-text-webpack-plugin --save複製程式碼

增加 require

    var ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');複製程式碼

修改 css rules

    {
        test: /\.css$/,
        use: ExtractTextWebpackPlugin.extract[{
            fallback: 'style-loader',
            use: 'css-loader'
        }]
    }複製程式碼

增加 plugin

    new ExtractTextWebpackPlugin('style.css')複製程式碼

打包,然後我們會發現 dist 中多了一個 style.css,然後再 index.html 的 head 中的多了對這個 css 的引用

9. 其他

1. 壓縮 js

增加 plugin

    new webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        }
    })複製程式碼

2. 壓縮 html

new HtmlWebpackPlugin({
    template: './index.html',
    favicon: path.resolve(__dirname, './favicon.ico'),
    minify: {
        removeAttributeQuotes: true,
        removeComments: true,
        removeEmptyAttribute: true,
        collapseWhitespace: true
    }
}),複製程式碼

3. 優化圖片

    {
        test: /\.(jpg|png|gif|svg)$/,
        - use: 'file-loader?name=images/[hash:8].[name].[ext]'
        + use: 'url-loader?limit=8192&name=images/[hash:8].[name].[ext]'
    }複製程式碼

總結

暫時就先那麼多吧,沒時間寫了,以後再說。第一次發文,求輕虐。

參考文件

webpack(v3.5.5)中文文件

相關文章