webpack與SPA實踐之管理CSS等資源

熊建剛發表於2019-03-02

上一篇介紹瞭如何使用webpack搭建一個穩定的支援本地服務、自動重新整理、模組熱替換、使用ES6編寫JavaScript的開發環境,本篇主要介紹webpack如何處理HTML應用三大元素的另一元素 - CSS及其他諸如圖片、字型檔案或者資料配置檔案等資源。

前言

在學習使用webpack時,我們需要明白無論它怎麼設計,它的工作原理、流程是什麼,最根本的它處理的還是HTML文件中的HTML標籤、JavaScript、CSS、圖片等資源,而且最終的處理結果依然必須是一個HTML文件,包括DOM、JavaScript、CSS,而CSS在文件中的存在方式,有三種:行內樣式,內聯樣式,外鏈樣式,行內樣式使用方式早已不推薦,所以webpack處理CSS方式也就兩種:

  • 內聯樣式: 以<style>標籤方式在HTML文件中嵌入樣式;
  • 外鏈樣式: 打包生成CSS檔案,通過<link>標籤引入樣式;

webpack與CSS

我們知道,webpack本質是隻能處理JavaScript的,而對於其他資源,需要使用載入器和外掛將其處理成JavaScript模組,然後進行模組依賴管理。webpack提供style-loadercss-loader兩個載入器支援我們模組化CSS,因此可以在其他模組內直接引入。

  • 安裝

    npm install --save-dev style-loader css-loader複製程式碼
  • 配置

在webpack配置檔案的模組載入器選項中新增如下配置:


    module: {
        loaders: [
            { test: /\.css$/, loader: "style-loader!css-loader" }
        ]
    }複製程式碼

當然為了方便使用引用路徑,還可以配置路徑片段別名:


    alias: {
        styles: path.resolve(__dirname, 'src/styles/')
    }複製程式碼

此時,import 'styles/index.css';等同於使用相對路徑,如import '../src/styles/indx.css';

  • 使用

配置好以後,假如我們在styles目錄下建立了一個index.css檔案,現在可以在JavaScript檔案中直接引入該CSS: import 'styles/index.css';require('styles/index.css');

css內容如下:


    html, body {
            width: 100%;
        height: 100%;
    }
    .container {
            color: red;
    }複製程式碼

頁面展示如圖:

webpack與SPA實踐之管理CSS等資源
style

內聯樣式

前面提到了webpack處理CSS的方式有兩種,第一種是以內聯方式在頁面<head>標籤內動態插入<style>內聯樣式,這種方式也是webpack的預設處理方式,只需要簡單配置如下載入器:


    {
        test: /\.css$/,
        exclude: /node_modules/,
        loader: 'style-loader!css-loader'
        // or 
        // loaders: ['style-loader', 'css-loader']
    }複製程式碼

webpack載入器解析順序

如上面程式碼所示,無論是字串語法style-loader!css-loader,亦或是陣列語法['style-loader', 'css-loader'],webpack解析規則都是從右至左,依次解析並執行載入器處理檔案,前一載入器處理的輸出就是下一載入器處理的輸入,直到最後載入器處理完成;此處即webpack先呼叫css-loader載入器處理css檔案,然後將處理結果傳遞給style-loader載入器,style-loader接受該輸入繼續處理。

css-loader

我們已經反覆強調,webpack只能處理JavaScript,所以對於其他諸如css或圖片等資源需要使用載入器將其轉換輸出為JavaScript模組,webpack才能繼續處理。

css-loader載入器的作用就是支援我們像使用JavaScript模組一樣在JavaScript檔案中引用CSS檔案,如require ('./index.css'),所以你可以認為其作用是將CSS檔案轉換成JavaScript模組,於是我們可以直接通過引入JavaScript模組的方式直接引用。

引數

css-loader有兩個常用引數:

  • modules: {boolean}指定是否使用CSS模組(如:local和:global設定區域性或全域性樣式規則),預設是false,開啟設定如css-loader?modules;
  • importLoaders: {number}指定css-loader載入器之前使用的載入器數量,預設是0,設定如css-loader?importLoaders=1

style-loader

無論webpack怎麼處理CSS檔案,最終都需要將其輸出到頁面,才能實際使用該CSS規則,style-loader載入器就是將CSS以內聯方式插入到頁面文件,即:針對每一個輸入(通過require引入,使用css-loader轉換為JavaScript模組,傳遞給style-loader作為輸入),style-loader在頁面<head>標籤內插入一個<style>標籤,該標籤內樣式規則即此輸入內容,如下例項:

webpack與SPA實踐之管理CSS等資源
內聯樣式

外鏈樣式

當然,我們並不總是希望所有樣式都以內聯方式存在頁面中,很多時候我們也希望通過外鏈方式使用樣式表,特別是樣式規則較多的時候,webpack開發者們當然考慮了這樣的需求。

webpack提供的style-loader載入器預設是以內聯方式將樣式插入文件,我們需要使用webpack extract-text-webpack-plugin外掛以實現輸出單獨CSS檔案。

Extract Text Plugin

  • 安裝

首先安裝該外掛:


    npm install --save-dev extract-text-webpack-plugin複製程式碼
  • 配置

然後新增如下配置:


    var ExtractTextPlugin = require('extract-text-webpack-plugin');
    ...
    module: {
        loaders: [
            {
                test: /\.css$/,
                exclude: /node_modules/,
                // 老版本 loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
                loader: ExtractTextPlugin.extract({
                    fallback:'style-loader',
                    use: 'css-loader'
                })
            }
        ]
    },
    plugins: [
        // 生成獨立css檔案
        new ExtractTextPlugin({
            filename: 'css/[name].css'
        })
    ]複製程式碼

執行webpack命令,我們會看到在dist/css/資料夾下生成相應的CSS檔案。

  • 引數

    • filename {String | Function}

      Extract Text Plugin為每個入口生成一個CSS檔案,所以對於多入口專案需要指定filename引數[name]或[id]或[contenthash]生成唯一識別檔名;

    • disable {Boolean}

      禁用此外掛;

    • allChunks {Boolean}

      allChunks: true;時指定從所有模組中抽取CSS輸出至單獨CSS檔案,包括非同步引入的額外模組;此外掛預設是隻抽取初始模組內引入的CSS;

  • extract方法

該方法可以以引數指定載入器,然後接受該載入器的輸出,進行處理。需要在載入器和外掛配置中同時宣告相關配置,才能實現效果;在載入器配置中呼叫其extract方法傳入通常以下兩個引數:

1. use: 將CSS轉換成模組的載入器;
2. fallback: 對於不被抽取輸出至單獨CSS檔案的CSS模組使用的載入器,上例中`style-loader`即說明以內聯方式使用,該載入器通常在`allChunks: false`時處理額外的模組;複製程式碼

filename與output

在上一篇介紹了輸出檔案配置output相關內容,其中:

  • output.path是webpack處理檔案後輸出的路徑,對於CSS檔案輸出依然適用,即CSS檔案也將輸出至該目錄;
  • output.publicPath是指瀏覽器訪問資源時的相對基礎路徑,規則是: output.publicPath + output.filename;

你可以看到在本系列文章例項中filename都新增了字首目錄,如cssscripts,你可能看到很多專案是不新增的,但檔案入口較多時建議分型別目錄輸出,而且需要記得在瀏覽器訪問資源時也需要新增該目錄層級。

CSS前處理器

通常在開發較複雜的應用時,我們都會選擇一種CSS的強化輔助工具,以更高效,更方便的使用CSS開發應用樣式,這些擴充工具就是所說的CSS前處理器.

CSS前處理器(preprocessors)在CSS語法的基礎上增加了變數 (variables)、巢狀 (nested rules)、混合 (mixins)、匯入 (inline imports) 等高階功能,令CSS更加強大與優雅,有助於更好地組織管理樣式檔案,以及更高效地開發專案。

目前最常見的CSS前處理器有LESS,SASS,Stylus,個人用過的是前兩種,使用SASS的還是居多。

SASS

  • 安裝

    npm install --save-dev sass-loader複製程式碼

安裝sass-loader以後會發現,package.json中多了一個node-sass依賴,這是使用SASS必須的。

  • 配置

然後新增以下配置:


    {
        test: /\.s[ac]ss$/,
        exclude: /node_modules/,
        loader: 'style-loader!css-loader!sass-loader'
    }複製程式碼

如上,配置中傳遞了三個載入器,相對於前文處理CSS檔案的載入器,在最後面多了一個sass-loader,首先載入sass-loader載入器處理SASS檔案成CSS程式碼,然後繼續按照前文描述流程處理CSS檔案。

Extract Text Plugin

和處理CSS檔案一樣,上述配置最終通過style-loader將轉換後的CSS程式碼內聯到頁面,我們需要使用Extract Text Plugin生成單獨CSS檔案,以外鏈方式引用:


    {
         test: /\.s[ac]ss$/,
         exclude: /node_modules/,
         loader: ExtractTextPlugin.extract({
         fallback:'style-loader',
             use: [
                  'css-loader',
                  'sass-loader'
             ]
         })
    }

    ...

    // 生成獨立css檔案
        new ExtractTextPlugin({
            filename: 'css/[name].css'
        })複製程式碼

CSS後處理器

前面講到CSS前處理器,如SASS,他們提供對CSS的擴充,包括語法擴充,高階特性擴充,如巢狀,變數,自動處理新增屬性字首等,使得我們可以以其定義的語法與模板方式更高效的編寫CSS,然而這些前處理器都是另外對CSS進行擴充,各自定義了語法和模板,其處理流程是對程式碼進行解析處理,然後轉換成CSS程式碼。

不同前處理器有各自的定義和規範,假如你需要從LESS轉到SASS,原始碼轉換成本和學習成本頗高,而接下來要介紹的CSS後處理器並沒有這個問題。

不同於前處理器預定義好一個語法和模板,然後對按照該語法和模板編寫的程式碼進行處理轉換成CSS,其輸入是自定義語法檔案,輸出是CSS程式碼;後處理器(postprocessor)是對原生CSS程式碼根據配置進行處理,其輸入輸出依然是CSS程式碼。

postcss

現在最受歡迎的CSS後處理器,就是postcss:

PostCSS is a tool for transforming styles with JS plugins. These plugins can lint your CSS, support variables and mixins, transpile future CSS syntax, inline images, and more.
PostCSS是一個使用Js外掛轉換樣式的根據,外掛支援擴充CSS,如變數,混合,CSS屬性語法相容,行內圖片等等功能。

特性

不同於SASS提供一個功能性擴充工具,postcss更多的是提供一個CSS高效開發工具解決方式,其本身只包含CSS解析器只能將CSS處理成一棵抽象語法樹(AST),同時提供一個豐富的CSS節點樹API,可以對語法樹進行操作,另外它有一個高擴充性的外掛系統支援我們通過引入不同外掛對CSS進行處理,一個外掛的輸出還可以是下一個外掛的輸入,更值得一提的是,這些外掛都是JavaScript外掛,前端開發者們很容易就能根據專案需求定製自己的外掛,所以可以總結幾點一以下特性:

  1. postcss只處理CSS,概念簡潔;
  2. 提供高擴充性的外掛系統支援按需引入不同外掛,實現不同處理;
  3. 使用JavaScript外掛,開發者可以很方便定製專案外掛;
  4. 提供CSS節點樹API,可以高效的進行CSS處理;
  • 安裝

在webpack中使用,需要先安裝對應載入器:


    npm install --save-dev postcss-loader複製程式碼
外掛

postcss目前有200+外掛,足夠滿足絕大部分專案開發需求,可以檢視postcss外掛,我們介紹幾個主要使用的外掛。

Autoprefixer

回顧一下在前處理器中,如果我們需要為CSS程式碼新增屬性字首,需要這麼實現呢?對於Sass,我們通常使用mixin,即混合巨集,處理CSS屬性字首,如:


    // 定義
    @mixin prefix-animation($animation-name){
        animation:$animation-name;
        -webkit-animation:$animation-name;
    }

    // 使用
    body{
        @include prefix-animation(loading .5s linear infinite);
    }複製程式碼

如上,我們需要按照定義的語法和模板:先定義一個mixin,然後通過@include方式使用,最後才能輸出新增字首的CSS程式碼,當程式碼越來越多時,我們需要定義的mixin也會越來越多,而且不同前處理器定義的語法和模板都有差異,學習成本、轉換成本都很可能令人難以接受。

那麼postcss外掛怎麼處理的呢?postcss提供了Autoprefixer外掛處理CSS屬性字首:

Autoprefixer外掛基於Can I Use的資料,對CSS規則進行字首處理。

  • 安裝

首先還是要安裝Autoprefixer:


    npm install --save-dev autoprefixer複製程式碼
  • 配置

新增如下配置:


    module: {
        loaders: [
            {
                 test: /\.css$/,
                 exclude: /node_modules/,
                 loaders: [
                    'style-loader',
                    'css-loader',
                    { 
                        loader: 'postcss-loader', 
                        options: {
                            plugins: [
                                require('autoprefixer')({
                                    browsers: ['last 2 versions']
                                })
                            ]  
                        } 
                    }
                ]
            }
        ]
    }複製程式碼

如上,我們知道postcss是一個樣式開發解決方案,其特定功能需要引入外掛實現,上例中在指定postcss-loader載入器時為其設定了外掛配置autoprefixer;當然webpack還支援直接設定一個postcss配置檔案,然後在專案根目錄建立postcss.config.js配置檔案,內容格式如下:


    module.exports = {
          plugins: [
            require('autoprefixer')({
                browsers: ['last 2 versions']
            })
            // or just require('autoprefixer')
          ]
    }複製程式碼

使用autoprefixer外掛時可選傳入browsers引數,可以設定新增字首的適配範圍,詳細可查閱browsers配置說明

混合使用CSS前處理器與後處理器 - PreCSS

也許你迫不及待想在專案中引入postcss,又希望能繼續使用CSS前處理器語法,而且需要保證以前按照某前處理器預定語法和模板(如SASS)編寫的原始碼繼續穩定使用,不需要太多的遷移和學習成本,可以做到嗎?當然可以,可以使用前處理器PreCSS外掛包,另外我們需要安裝一個postcss的scss解析器,因為postcss預設只包含一個CSS解析器,postcss配置檔案更新如下:


    module.exports = {
        parser: require('postcss-scss'),
        plugins: [
            require('autoprefixer')({
                browsers: ['last 2 versions']
            }),
            require('precss')
        ]
    }複製程式碼

webpack配置檔案更新配置:


    modules: {
        loaders: [
            {
                test: /\.s?[ac]ss$/,
                exclude: /node_modules/,
                // or 內聯方式 loader: 'style-loader!css-loader!postcss-loader'
                loader: ExtractTextPlugin.extract({
                    fallback:'style-loader',
                    use: [
                        'css-loader',
                        'postcss-loader'
                    ]
                })
             }
        ]
    }複製程式碼

可以看到檔案匹配規則,修改為/\.s?[ac]ss$/,可以匹配包括.sass, .scss, .css樣式檔案;在css-loader載入器之前新增了postcss-loader載入器(webpack載入器解析順序為從右至左)。

當然你可以不使用precss,依然使用sass-loader,則只需要修改配置:


    loader: 'style-loader!css-loader!postcss-loader!sass-loader'複製程式碼

對於如下SCSS程式碼:


    $column: 200px;
    .menu {
        display: flex;
        width: calc(4 * $column);
    }複製程式碼

轉換生成如下CSS程式碼:


    .menu {
        display: -webkit-box;
        display: -ms-flexbox;
        display: flex; 
        width: calc(4 * 200px);
    }複製程式碼

處理圖片與字型檔案

對於一個應用而言,除了需要開發HTML、CSS、JavaScript,通常還好使用到圖片,字型檔案,配置檔案等諸多資源,那麼前端工程化流程也就必然需要對這些資源進行處理與優化,最典型的說處理圖片和字型檔案。

在Grunt或Gulp中,我們對圖片和字型檔案的處理通常是將其從源目錄壓縮優化處理後輸出至輸出目錄,通常是以檔案目錄整體進行處理,每次構建時,對所有資源,包括未使用的圖片均進行處理,效率是有侷限的;而webpack中一切資原始檔都可以處理成模組,然後在編譯時管理模組依賴,可以做到只處理存在依賴的資源(即,使用了的資源)。

圖片與字型

當我們在Js模組中引入CSS檔案時,其中樣式規則中的背景圖片,字型檔案如何處理呢?webpack只能管理模組化的東西,需要將其模組化,然後使用webpack管理依賴,webpack提供了file-loader載入器:

File Loader

Instructs webpack to emit the required object as file and to return its public url.
通知webpack將引入的物件輸出為檔案並返回其公開資源路徑。

  • 配置

    module: {
        loaders: [
              {
                test: /\.(png|svg|jpe?g|gif)$/,
                loader: [
                      'file-loader'
                ]
              }
        ]
      }複製程式碼
  • 說明

當我們在js檔案中import Image from '../images/test.png'或在CSS檔案中url('../images/test.png')時,file-loader將處理該圖片並在output.path目錄下輸出檔案,然後將../images/test.png路徑替換成該輸出檔案路徑。

注,對於html中引用的圖片,需要使用[html-loader]載入器處理(npm.taobao.org/package/htm…

  • 引數

    1. emitFile: 是否輸出檔案;
    2. name: 指定輸出檔案的檔名,有幾個可用內建變數:
      1. [name]: 引用資源的名稱;
      2. [path]: 引用資源的相對路徑;
      3. [ext]: 資源擴充名;
      4. [hash]: 資源內容的hash值,預設使用md5演算法計算得到,可以指定長度值,如[hash:7]表示返回hash值前7個字元;
      5. [hashType:hash:digestType:length]: 指定hash值計算演算法型別和摘要型別,及摘要長度,如sha512:hash:base64:7表示使用sha512加密演算法計算hash值並且返回7個字元的base64編碼字元
  • 例項

在配置時可以指定引數:file-loader?name=[name].[ext]?[hash:8]或者以配置物件方式:


    {
        test: /\.(png|svg|jpe?g|gif)$/,
        loaders: [
            // 'file-loader?name=[path][name].[ext]?[hash:8]'
            {
                loader: 'file-loader',
                query: {
                    name: '[path][name].[ext]?[hash:8]'
                }
            }
        ]
    }複製程式碼

對於CSS原始碼:


    .wrapper {
        font-size: 18px;
        background: url('../images/test.png') no-repeat 0 0;
    }複製程式碼

輸出CSS程式碼如下:


    .wrapper {
        font-size: 18px;
        background: url(assets/images/test.png?59427321) no-repeat 0 0;
    }複製程式碼

assetsoutput.publicPath指定值,images/test.png?59427321為配置檔案中指定的name模板,在output.path目錄下輸出images/test.png,區別是,不會攜帶?後的引數。

另外,你也可以在js模板中這樣使用:


    <img src={imgSrc} />

    ...
    import imgSrc from 'path/xxx.png';複製程式碼
Url Loader

你可能會發現前面並沒有安裝file-loader,因為有更好用的載入器url-loaderurl-loader載入器是file-loader的升級版,他們唯一的不同之處在於:

url-loader可以通過limit引數指定一個尺寸值,載入器會對小於該值的資源處理返回一個Data URL,以base64的方式嵌入HTML或CSS,如url-loader?limit=65000;對於大於該尺寸的資源將使用file-loader處理並且傳遞所有引數。

  • mimetype

還可以設定mimetype對處理檔案進行過濾,如url-loader?mimetype=image/png將只處理png型別的資源。

  • 安裝

    npm install --save-dev url-loader複製程式碼
  • 配置

該載入器對於圖片和字型檔案資源都適用:


    {
         test: /\.(png|svg|jpe?g|gif)$/,
         loaders: [
             // 'url-loader?name=[path][name].[ext]?[hash:8]'
             {
                 loader: 'url-loader',
                 query: {
                     limit: 6000,
                     name: '[path][name].[ext]?[hash:8]'
                 }
             }
          ]
    }, {
         test: /\.(woff|woff2|eot|ttf|otf)$/,
         loaders: [{
             loader: 'url-loader',
             query: {
                 limit: 10000,
                 name: '[path][name].[ext]?[hash:8]'
             }
         }]
    }複製程式碼

資源優化

完成以上配置後,已經可以在專案中很方便的引用各自資源了,但是通常我們還需要對圖片字型等檔案進行壓縮優化處理,如Grunt中使用的imagemin外掛一樣壓縮資源,webpack則提供了相關載入器img-loader

  • 安裝

    npm install --save-dev img-loader複製程式碼
  • 配置

    {
        test: /\.(jpe?g|png|gif|svg)$/i,
        loaders: [
            'url-loader?name=[path][name].[ext]?[hash:8]',
            {
                loader: 'img-loader',
                options: {
                    // 根據環境判斷是否啟用資源壓縮
                      enabled: process.env.NODE_ENV === 'production', 
                      gifsicle: {
                        interlaced: false // 替換使用漸進式渲染
                      },
                      mozjpeg: {
                        progressive: true, // 建立基準jpeg檔案
                      },
                      optipng: {
                        optimizationLevel: 4, // 優化級別,0-7,值越大,壓縮越多
                    }, 
                      pngquant: {
                        quality: '75-90', // 壓縮質量,0-100,值越高,質量越高
                        speed: 3 // 執行速度,0-10,速度過高質量受損,不建議過高
                      },
                      svgo: {
                          plugins: [
                            { removeTitle: true }, // 去除標題資訊
                              { convertPathData: false } // 轉換路徑為更短的相對或決定路徑
                        ]
                      }
                }
              }
        ]
    }複製程式碼

以上為常見使用配置,更多詳細配置資訊請檢視對應說明imagemin文件,特別注意的是上面使用了process.env.NODE_ENV當前環境變數,只有在生產環境啟用圖片壓縮,因為壓縮過程比較比較耗時,可能會降低開發、除錯效率。

資料資源

對於資料型別檔案資源,webpack內建支援載入解析.json檔案,而其他型別則需要安裝配置相應載入器,如.xml檔案,需要安裝並配置xml-loader

資源管理的思考

在傳統或稍早一點的應用中,我們通常會將所有的圖片,字型等資源放在一個基礎目錄下,如assets/images,但是對於那些在多專案間重複的外掛程式碼或資源來說,每一次遷移,我們都得在一大堆圖片,字型資源裡尋找出我們需要遷移的資源,這對程式碼可重用和其獨立性有一定限制,而且與現在提倡的元件化開發模式也不相符。

webpack對於資源的處理方式給元件化開發提供了很大便利,使得我們以元件為單位,可以在某一元件目錄下存放所有相關的js,css,圖片,字型等資原始檔;元件的遷移公用成本很低。不過元件化開發並不是說不需要資源目錄了,一些公用的資源依然放在專案的基礎目錄下。

說明

終於可以鬆口氣,對於webpack管理CSS、圖片、字型、資料資源的實踐基本總結完成,其實感覺要介紹的還有很多,但是要儘量保證文章思路清晰,語句流暢,而且篇幅不能太長,水平有限,花費較多時間經歷,希望能對讀者有所幫助,後續篇章也會繼續穿插介紹,力爭本系列能較完整、較清晰地描述如何使用webpack開發SPA應用。

原創文章,轉載請註明: 轉載自 熊建剛的部落格

本文連結地址: webpack與SPA實踐之管理CSS等資源

相關文章