Webpack4不求人系列(1)

Webpack是一個如今Javascript應用程序的模塊化打包器,在Webpack中JS/CSS/圖片等資源都被視爲JS模塊,簡化了編程。當Webpack構建時,會遞歸造成一個模塊依賴關係圖,而後將全部的模塊打包爲一個或多個bundle。

img

本文內容javascript

  1. 簡介
  2. 經常使用loader && plugin
  3. 傳統網站的webpack配置

簡介

要系統地學習Webpack,須要先了解Webpack的四個核心概念:css

  • 入口(entry)
  • 輸出(output)
  • loader
  • plugin

webpack使用Node.js運行,所以全部的Node.js模塊均可以使用,好比文件系統、路徑等模塊。html

對Node.js基礎不太瞭解的讀者,能夠參考個人Node.js系列java

配置文件webpack.config.js的通常格式爲:node

const path = require('path'); // 導入Node.js的path模塊


module.exports = {
  mode: 'development', // 工做模式
  entry: './src/index', // 入口點
  output: { // 輸出配置
    path: path.resolve(__dirname, 'dist'), // 輸出文件的目錄
    filename: 'scripts/[name].[hash:8].js', // 輸出JS模塊的配置
    chunkFilename:'scripts/[name].[chunkhash:8].js', // 公共JS配置
    publicPath:'/' // 資源路徑前綴,通常會使用CDN地址,這樣圖片和CSS就會使用CDN的絕對URL
  },
  module:{
    rules: [
      {
        test:/\.(png|gif|jpg)$/, // 圖片文件
        use:[
          {
            loader:'file-loader', // 使用file-loader加載
            options:{ // file-loader使用的加載選項
              name:'images/[name].[hash:8].[ext]' // 圖片文件打包後的輸出路徑配置
            }
          }
        ]
      }
    ]
  },
  plugins:[ // 插件配置
    new CleanWebpackPlugin()
  ]
};
Webpack本身只管JS模塊的輸出,也就是output.filename是JS的配置,CSS、圖片這些是經過loader來處理輸出的

入口

入口指明瞭Webpack從哪一個模塊開始進行構建,Webpack會分析入口模塊依賴到的模塊(直接或間接),最終輸出到一個被稱爲bundle的文件中。webpack

使用 entry來配置項目入口。

單一入口git

最終只會生成1個js文件github

module.exports = {
  entry: './src/index',
};

多個入口web

最終會根據入口數量生成對應的js文件npm

module.exports = {
  entry:{
      home:'./src/home/index', // 首頁JS
    about:'./src/about/index' // 關於頁JS
  }
};

多個入口通常會在多頁面應用中使用,好比傳統的新聞網站。

輸出

輸出指明瞭Webpack將bundle輸出到哪一個目錄,以及這些bundle如何命名等,默認的目錄爲./dist

module.exports = {
  output:{
    path:path.resolve(__dirname, 'dist'), // 輸出路徑
    filename:'scripts/[name].[hash:8].js', // 輸出JS模塊的文件名規範
    chunkFilename:'scripts/[name].[chunkhash:8].js', // 公共JS的配置
    publicPath:'/', // 資源路徑前綴,通常會使用CDN地址,這樣圖片和CSS就會使用CDN的絕對URL
  }
};

path

path是打包後bundle的輸出目錄,必須使用絕對路徑。全部類型的模塊(js/css/圖片等)都會輸出到該目錄中,固然,咱們能夠經過配置輸出模塊的名稱規則來輸出到path下的子目錄。好比上例中最終輸出的JS目錄以下:

|----dist
         |---- scripts
                      |---- home.aaaaaaaa.js

filename

入口模塊輸出的命名規則,在Webpack中,只有js是親兒子,能夠直接被Webpack處理,其餘類型的文件(css/images等)須要經過loader來進行轉換。

filename的經常使用的命名以下:

[name].[hash].js
  • [name] 爲定義入口模塊的名稱,好比定義了home的入口點,這裏的name最終就是home
  • [hash] 是模塊內容的MD5值,一次打包過程當中全部模塊的hash值是相同的,因爲瀏覽器會按照文件名緩存,所以每次打包都須要指定hash來改變文件名,從而清除緩存。

chunkFilename

非入口模塊輸出的命名規則,通常是代碼中引入其餘依賴,同時使用了optimization.splitChunks配置會抽取該類型的chunk

hash

Webpack中常見的hash有hash,contenthash,chunkhash,很容易弄混淆,這裏說明一下。

  • hash 整個項目公用的hash值,無論修改項目的什麼文件,該值都會變化
  • chunkhash 公共代碼模塊的hash值,只要不改該chunk下的代碼,該值不會變化
  • contenthash 基於文件內容生成的hash,只要改了文件,對應的hash都會變化

publicPath

資源的路徑前綴,打包以後的資源默認狀況下都是相對路徑,當更改了部署路徑或者須要使用CDN地址時,該選項比較經常使用。

好比咱們把本地編譯過程當中產生的全部資源都放到一個CDN路徑中,能夠這麼定義:

publicPath: 'https://static.ddhigh.com/blog/'

那麼最終編譯的js,css,image等路徑都是絕對連接。

loader

loader用來在import時預處理文件,通常用來將非JS模塊轉換爲JS能支持的模塊,好比咱們直接import一個css文件會提示錯誤,此時就須要loader作轉換了。

好比咱們使用loader來加載css文件。

module.exports = {
  module:{
    rules:[
      {
        test: /\.(css)$/,
                use: ['css-loader']
      }
    ]
  }
};

配置方式

Webpack中有3種使用loader的方式:

  1. 配置式:在webpack.config.js根據文件類型進行配置,這是推薦的配置
  2. 內聯:在代碼中import時指明loader
  3. 命令行:經過cli命令行配置

配置式

module.rules用來配置loader。test用來對加載的文件名(包括目錄)進行正則匹配,只有當匹配時纔會應用對應loader。

多個loader配置時從右向左進行應用

配置式Webpack的loader也有好幾種形式,有些是爲了兼容而添加的,主要使用的方式有如下3種。

module.exports = {
  module:{
    rules:[
      {
        test: /\.less$/,
        loader:'css-loader!less-loader', // 多個loader中用感嘆號分隔
      },
      {
        test:/\.css/,
        use:['css-loader'],//數組形式
      },
      {
        test:/\.(png|gif|jpg)$/,
        use:[ // loader傳遞參數時建議該方法
          {
            loader: 'file-loader',
            options:{ // file-loader本身的參數,跟webpack無關
              name: 'images/[name].[hash:8].js'
            }
          }
        ]
      }
    ]
  }
};
每一個loader的options參數不必定相同,這個須要查看對應loader的官方文檔。

Plugin

loader通常用來作模塊轉換,而插件能夠執行更多的任務,包括打包優化、壓縮、文件拷貝等等。插件的功能很是強大,能夠進行各類各樣的任務。

下面是打包以前清空dist目錄的插件配置示例。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
    plugins: [
        new CleanWebpackPlugin(),
      ]
};

插件也能夠傳入選項,通常在實例化時進行傳入。

new MiniCssPlugin({
    filename: 'styles/[name].[contenthash:8].css',
  chunkFilename: 'styles/[name].[contenthash:8].css'
})

提取公共代碼

Webpack4中提取公共代碼只須要配置optimization.splitChunks便可。

optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: { // 名爲vendor的chunk
          name: "vendor",
        test: /[\\/]node_modules[\\/]/,
        chunks: 'all',
        priority: 10
      },
          styles: { // 名爲styles的chunk
        name: 'styles',
        test: /\.css$/,
        chunks: 'all'
      }
    }
  }
},
  • cacheGroups 緩存組
  • name chunk的名稱
  • test 加載的模塊符合該正則時被打包到該chunk
  • chunks 模塊的範圍,有initial(初始模塊),async(按需加載模塊),all(所有模塊)

上面的例子中將node_modules中的js打包爲vendor,以css結尾的打包爲styles

經常使用的loader和plugin

css-loader

加載css文件
{
  test:/\.css$/
  loader:['css-loader']
}

less-loader

加載less文件,通常須要配合css-loader
{
  test:/\.less$/,
  loader:['css-loader','less-loader']
}

file-loader

將文件拷貝到輸出文件夾,並返回相對路徑。通常經常使用在加載圖片
{
  test:/\.(png|gif|jpg)/,
  use:[
      {
      loader:'file-loader',
      options:{
        name:'images/[name].[hash:8].[ext]'
      }
    }    
  ]
}

babel-loader

轉換ES2015+代碼到ES5
{
  test:/\.js$/,
  exclude: /(node_modules|bower_components)/, // 排除指定的模塊
  use:[
    {
      loader:'babel-loader',
      options:{
        presets:['@babel/preset-env']
      }
    }
  ]
}

ts-loader

轉換Typescript到Javascript
{
  test:/\.ts/,
  loader:'ts-loader'
}

html-webpack-plugin

簡化HTML的建立,該插件會自動將當前打包的資源(如JS、CSS)自動引用到HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports =  {
  plugins:[
    new HtmlWebpackPlugin()
  ]
};

clean-webpack-plugin

打包以前清理dist目錄
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports =  {
  plugins:[
    new CleanWebpackPlugin()
  ]
};

mini-css-extract-plugin

提取、壓縮CSS,須要同時配置loader和plugin
const MiniCssPlugin = require('mini-css-extract-plugin');
module.exports =  {
  module:{
    rules:[
            {
                test: /\.less$/,
                use: [MiniCssPlugin.loader, 'css-loader', 'less-loader']
            },
            {
                test: /\.css$/,
                use: [MiniCssPlugin.loader, 'css-loader']
            },
    ]
  },
  plugins:[
    new MiniCssPlugin({
            filename: 'styles/[name].[contenthash:8].css',
      chunkFilename: 'styles/[name].[contenthash:8].css'
    }),
  ]
};

實戰

下面使用Webpack來配置一個傳統多頁面網站開發的示例。

目錄結構

├── package.json
├── src
│   ├── about                        關於頁
│   │   ├── index.html
│   │   ├── index.js
│   │   └── style.less
│   ├── common
│   │   └── style.less
│   └── home                        首頁
│       ├── images
│       │   └── logo.png
│       ├── index.html
│       ├── index.js
│       └── style.less
├── webpack.config.js

使用到的npm包

"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.2.1",
"exports-loader": "^0.7.0",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"html-withimg-loader": "^0.1.16",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.8.0",
"normalize.css": "^8.0.1",
"script-loader": "^0.7.2",
"style-loader": "^1.0.1",
"url-loader": "^3.0.0",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0",
"zepto": "^1.2.0"

配置入口點

因爲是傳統多頁網站,每一個頁面都須要單獨打包一份JS,所以每一個頁面須要一個入口

entry: { // 入口配置,每一個頁面一個入口JS
        home: './src/home/index', // 首頁
        about: './src/about/index' // 關於頁
}

配置輸出

本例咱們不進行CDN部署,所以輸出點配置比較簡單。

output: { // 輸出配置
  path: path.resolve(__dirname, 'dist'), // 輸出資源目錄
  filename: 'scripts/[name].[hash:8].js', // 入口點JS命名規則
  chunkFilename: 'scripts/[name]:[chunkhash:8].js', // 公共模塊命名規則 
  publicPath: '/' // 資源路徑前綴
}

配置開發服務器

本地開發時不須要每次都編譯完Webpack再訪問,經過webpack-dev-server,咱們能夠邊開發變查看效果,文件會實時編譯。

devServer: {
        contentBase: './dist', // 開發服務器配置
        hot: true // 熱加載
},

配置loader

本例中沒有使用ES6進行編程,可是引用了一個非CommonJS的js模塊Zepto,傳統用法中在HTML頁面引入Zepto就會在window下掛載全局對象Zepto。可是在Webpack開發中不建議使用全局變量,不然模塊化的優點將受到影響。

經過使用exports-loader和script-loader,咱們能夠將Zepto包裝爲CommonJS模塊進入導入。

module: {
        rules: [
            {
                test: require.resolve('zepto'),
                loader: 'exports-loader?window.Zepto!script-loader' // 將window.Zepto包裝爲CommonJS模塊
            },
            {
                test: /\.less$/,
                use: [MiniCssPlugin.loader, 'css-loader', 'less-loader']
            },
            {
                test: /\.css$/,
                use: [MiniCssPlugin.loader, 'css-loader']
            },
            {
                test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: 'images/[name].[hash:8].[ext]'
                        }
                    }
                ]
            },
            {
                test: /\.(htm|html)$/i,
                loader: 'html-withimg-loader'
            }
        ]
    },

配置optimization

主要進行公共模塊的打包配置。

optimization: {
        splitChunks: {
            cacheGroups: {
                vendor: {
                    name: "vendor",
                    test: /[\\/]node_modules[\\/]/,
                    chunks: 'all',
                    priority: 10, // 優先級
                },
                styles: {
                    name: 'styles',
                    test: /\.css$/,
                    chunks: 'all'
                }
            }
        }
    },

配置plugin

plugins: [
        new CleanWebpackPlugin(), // 清理髮布目錄
        new HtmlWebpackPlugin({
            chunks: ['home', 'vendor', 'styles'], // 聲明本頁面使用到的模塊,有主頁,公共JS以及公共CSS
            filename: 'index.html', // 輸出路徑,這裏直接輸出到dist的根目錄,也就是dist/index.html
            template: './src/home/index.html', // HTML模板文件路徑
            minify: { 
                removeComments: true, // 移除註釋
                collapseWhitespace: true // 合併空格
            }
        }),
        new HtmlWebpackPlugin({
            chunks: ['about', 'vendor', 'styles'],
            filename: 'about/index.html', // 輸出到dist/about/index.html
            template: './src/about/index.html',
            minify: {
                removeComments: true,
                collapseWhitespace: true
            }
        }),
        new MiniCssPlugin({
            filename: 'styles/[name].[contenthash:8].css',
            chunkFilename: 'styles/[name].[contenthash:8].css'
        }),
        new webpack.NamedModulesPlugin(), // 熱加載使用
        new webpack.HotModuleReplacementPlugin() // 熱加載使用
    ]

示例代碼

部分示例代碼以下:

// src/about/index.js
const $ = require('zepto');
require('normalize.css');
require('../common/style.less');
require('./style.less');

$('#about').on('click', function () {
    alert('點擊了about按鈕');
});

和傳統的JS有點不太同樣,多了一些css的require,前面說過,webpack把全部資源當作JS模塊,所以這是推薦的作法。

<!--首頁-->
<!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>首頁</title>
</head>

<body>
    <ul>
        <li><a href="/">首頁</a> </li>
        <li><a href="/about">關於</a></li>
    </ul>
    <div class="logo"></div>
    <button id="home">首頁按鈕</button>
</body>

</html>

頁面中再也不須要編寫JS。

注意:html中使用<img />標籤導入圖片的編譯,目前尚未好的解決辦法,能夠經過css background的形式進行處理

開發模式

開發模式下直接啓用webpack-dev-server便可,會自動加載工做目錄下的webpack.config.js

// package.json
"scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server"
}
npm run dev

生產模式

生產模式下使用webpack編譯,編譯完成後輸出最終文件。

npm run build

輸出效果

├── about
│   └── index.html
├── images
│   └── logo.b15c113a.png
├── index.html
├── scripts
│   ├── about.3fb4aa0f.js
│   ├── home.3fb4aa0f.js
│   └── vendor:ed5b7d31.js
└── styles
    ├── about.71eb65e9.css
    ├── home.cd2738e6.css
    └── vendor.9df34e21.css

項目地址

項目已經託管到github,有須要的讀者能夠自取。

https://github.com/xialeistudio/webpack-multipage-example

0.jpeg

相關文章
相關標籤/搜索