紀錄如何使用 Webpack 編譯 CSS 文件,內容包括

  1. 將 CSS 注入 HTML 文件
  2. 把 CSS 從 JavaScript 抽離,輸出獨立檔案
  3. 壓縮 CSS 文件
  4. 壓縮 JavaScript 文件

1. 將 CSS 注入 HTML 文件

安裝 CSS-loader

要使用 Webpack 編譯 CSS 需要使用以下兩個套件

npm install css-loader style-loader --save-dev
  • css-loader 幫助 Webpack 讀取 CSS 檔案
  • style-loader 將編譯好的 CSS 樣式整合到 HTML 文件上,因此不需要再額外連結 CSS 的檔案,難怪 Webpack 會用注入的名詞。

修改 Webpack 設定檔

編輯 webpack.config.js 檔案,在 rules 裡面新增 CSS 的規則。

// Node.js 內建用來處理路徑的模組
const path = require('path');
module.exports = {

  // Webpack 的進入點
  entry: './src/index.js',
  
  // Webpack 輸出點
  output: {

    // 輸出的檔案名稱
    filename: 'main.js',
    
    // 輸出的檔案位置
    path: path.resolve(__dirname, './dist/'),
  },
  module: {
    rules: [

      // Babel
      {
        test: /\.(js)$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
      
      // CSS 
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

Loader 的引用順序

  module: {
    rules: [ 
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },

注意: Webpack 的 Loader 引用順序是從右到左,很重要,順序錯誤就無法執行 先使用 css-loader 讀取檔案後 再使用 style-loader 將檔案注入 HTML 頁面中

注入 CSS 到 HTML 文件

src 資料夾中,新增兩個 CSS 檔案

  • style.css
  • reset.css

style.css 的樣式內容,簡單為 H1 標籤加上紅色

h1{
  color: red;
}

reset.css 的內容,使用 Eric Meyer 的 CSS reset

CSS Reset

在 index.js 檔案的最上面 import 這兩個 CSS 檔案

import '../src/reset.css'
import '../src/style.css'

const Name = ['Mike', 'Jacky', 'Andy', 'Scars']
Name.forEach((obj, idx)=> console.log(`${idx} => ${obj}`))

所以在 src 目錄中會有以下這些檔案

在 index.html 中,加入 H1 標籤並填寫內容

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <h1>Hello</h1>
  <script src="./dist/main.js"></script>  
</body>
</html>

呼叫 Webpack 進行編譯

npx webpack --mode development

使用瀏覽器檢查 Element 資訊,兩個 CSS 檔案都被注入 HTML 文件的 style 標籤裡面。

2. 抽離 CSS 輸出獨立檔案

從 Webpack 4 開始,官方推荐使用 mini-css-extract-plugin 來打包 CSS 文件,原因是extract-text-webpack-plugin 插件已經被棄用了。

如果將 CSS 抽離變成獨立的檔案,就不可以使用 style-loader 注入 CSS 到 HTML 文件中,需要自己 link 文件。

安裝 mini-css-extract-plugin 插件

npm install mini-css-extract-plugin --save-dev

修改 Webpack 設定檔

  1. 引入 mini-css-extract-plugin 插件
  2. 加入 plugins 屬性,設定 mini-css-extract-plugin 的輸出方式
  3. 修改 rules 裡面 CSS 的 loader 設定,將 style-loader 改成 MiniCssExtractPlugin.loader

    // Node.js 內建用來處理路徑的模組
    const path = require('path');
    
    // 1. 引入 MiniCssExtractPlugin
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    module.exports = {
    
    // Webpack 的進入點
    entry: './src/index.js',
      
    // Webpack 輸出點
    output: {
    
    // 輸出的檔案名稱
    filename: 'main.js',
        
    // 輸出的檔案位置
    path: path.resolve(__dirname, './dist/'),
    },
      
    // 2. 加入 plugins 屬性,設定 mini-css-extract-plugin 的輸出方式
    plugins: [
    new MiniCssExtractPlugin({
      filename: './css/[name].bundle.css',
    }),
    ],
    module: {
    rules: [
    
      // Babel
      {
        test: /\.(js)$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
          
      // CSS
      // 3. 修改 rules 裡面 CSS 的 loader 設定,將 style-loader 改成 MiniCssExtractPlugin.loader
      {
        test: /\.css$/,
        //use: ['style-loader', 'css-loader'], 註解掉不使用原本的設定
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
    },
    };
    

開始編譯輸出 CSS 檔

呼叫 Webpack 進行編譯,指令與先前相同

npx webpack --mode development

準備開始編譯,目前的檔案結構是,一個 index.js 裡面引入兩個 CSS 檔案。編譯之後會將兩個 CSS 整合成一個檔案,輸出到 ./dist/css/ 目錄下,檔名是 main.bundle.css ,檢查檔案內容的確包含 style.cssreset.css 的樣式。

在 index.html 文件 link 編譯出來的 CSS , 可以順利在瀏覽器中使用

3. 壓縮 CSS 文件

Webpack 預設的 production mode 只能壓縮 JavaScript 文件,所以需要安裝外掛來壓縮 CSS 文件。

安裝 optimize-css-assets-webpack-plugin 插件

npm install optimize-css-assets-webpack-plugin --save-dev

修改 Webpack 設定檔

  1. 引入 OptimizeCSSAssetsPlugin 外掛
  2. 加入 optimization 屬性,設定 minimizer 為 OptimizeCSSAssetsPlugin

    // Node.js 內建用來處理路徑的模組
    const path = require('path');
    
    // 1. 引入 MiniCssExtractPlugin
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    // 1. 
    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    
    
    module.exports = {
    
    // Webpack 的進入點
    entry: './src/index.js',
      
    // Webpack 輸出點
    output: {
    
    // 輸出的檔案名稱
    filename: 'main.js',
        
    // 輸出的檔案位置
    path: path.resolve(__dirname, './dist/'),
    },
    
    // 2. 
    optimization: {
    minimizer: [new OptimizeCSSAssetsPlugin()],
    },
      
    // 2. 加入 plugins 屬性,設定 mini-css-extract-plugin 的輸出方式
    // filename 使用[name]變數,它會自動抓取你在 entry 裡的屬性名稱來命名檔案
    plugins: [
    new MiniCssExtractPlugin({
      filename: './css/[name].bundle.css',
    }),
    ],
    module: {
    rules: [
    
      // Babel
      {
        test: /\.(js)$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
          
      // CSS
      // 3. 修改 rules 裡面 CSS 的 loader 設定,將 style-loader 改成 MiniCssExtractPlugin.loader
      {
        test: /\.css$/,
        //use: ['style-loader', 'css-loader'], 註解掉不使用原本的設定
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
    },
    };
    

執行編譯

呼叫 Webpack 進行編譯,由於要壓縮 CSS 檔案,因此指令要使用 production 模式

npx webpack

檢查編譯後的結果

CSS 成功被壓縮了,但是原本的 Webpack 預設的 JavaScript 壓縮卻失效,為什麼?

因為 optimization.minimizer 會將預設值覆蓋掉,變成沒有 JavaScript 的壓縮設定,很瞎吧!

所以我們還要加上另一個套件。

4. 壓縮 JavaScript 文件

安裝 terser-webpack-plugin 插件

npm install terser-webpack-plugin --save-dev

調整 Webpack 設定

  1. 引入 terser-webpack-plugin 模組
  2. 設定 optimization.minimizer

    // Node.js 內建用來處理路徑的模組
    const path = require('path');
    
    // 抽離 CSS 檔案的套件
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    // 壓縮 CSS 的套件
    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    
    // 壓縮 JS 的套件
    const TerserJSPlugin = require('terser-webpack-plugin');
    
    module.exports = {
    
    // Webpack 的進入點
    entry: './src/index.js',
      
    // Webpack 輸出點
    output: {
    
    // 輸出的檔案名稱
    filename: 'main.js',
        
    // 輸出的檔案位置
    path: path.resolve(__dirname, './dist/'),
    },
    
    // 在生產模式會壓縮 CSS 與 JS 檔案
    optimization: {
    minimizer: [new TerserJSPlugin(), new OptimizeCSSAssetsPlugin()],
    },
      
    // 設定 CSS 的輸出方式
    // 使用[name]變數,代表使用 entry 文件的檔名來命名檔案
    plugins: [
    new MiniCssExtractPlugin({
      filename: './css/[name].bundle.css',
    }),
    ],
    module: {
    rules: [
    
      // Babel
      {
        test: /\.(js)$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
          
      // CSS
      // style-loader 會將 CSS 注入 HTML 
      // MiniCssExtractPlugin.loader 會將 CSS 抽離成獨立檔案
      {
        test: /\.css$/,
        //use: ['style-loader', 'css-loader'],
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
    },
    };
    

進行編譯

呼叫 Webpack 進行編譯,指令使用 production 模式

npx webpack

檢查編譯後的結果

JavaScript 與 CSS 的檔案都順利被壓縮了!!

參考資料