Webpack 4 學習之路(上)

Webpack simple

以下均在 Webpack 4.42.1 版本環境下

mkdir webpack-simple
cd webpack-simple
npm init
npm i --save-dev webpack webpack-cli
touch index.html
touch webpack.config.js
mkdir src
cd src
touch main.js

在 index.html 中引入 bundle.js

<script src="./dist/bundle.js"></script>

在 main.js 中寫上如下代碼

console.info('hello webpack');

webpack.config.js 如下

const path = require('path'); // node path 模塊用來處理文件路徑

module.exports = {
	entry: {
    	app: './src/main.js'
  	},
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'dist') // __dirname 爲當前文件路徑,dist 爲絕對路徑
	}
}

執行「webpack」命令,在根目錄「dist」文件夾下就會看到一個「bundle.js」文件
bundle.js
打開文件後你會發現代碼量很多,實際上我們卻只寫了一行代碼,這其中前面的代碼都是 webpack 生成的「runtime」和「mainfest」代碼,後面會講到。

用瀏覽器打開 index.html,F12 能看到打印輸出了 “hello webpack”

loaders

有 html 還不夠,我們還得寫樣式呀,現在在 src 目錄下新建 style.css 文件

body {
	background: pink;
}

在 main.js 中引入 style.css

import './style.css'
console.info('hello webpack')

現在我們再次執行「webpack」命令,會發現報下面的錯誤
在這裏插入圖片描述
這是因爲「webpack」只能對「js」文件進行打包編譯,碰到「css」文件它識別不出來,這個時候我們就要加上「loader」來讓它識別「css」文件。

執行 “npm i --save-dev style-loader css-loader”

修改「webpack.config.js」文件如下

const path = require('path')

module.exports = {
  entry: {
    	app: './src/main.js'
  	},
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        // 匹配 css 文件
        test: /\.css$/,
        /*
        先使用 css-loader 處理,返回的結果交給 style-loader 處理。
        css-loader 將 css 內容存爲 js 字符串,並且把 background,@font-face 等引用的圖片,
        字體文件交給指定的 loader 打包
        */
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}

再次執行「webpack」命令,就不會報錯了,打開「index.html」就能看到添加樣式成功了。引入的圖片、字體、html等,都有相應的 loader 來編譯。

rules: [
    {
      /*
      使用 babel 編譯 ES6 / ES7 / ES8 爲 ES5 代碼
      使用正則表達式匹配後綴名爲 .js 的文件
      */
      test: /\.js$/,

      // 排除 node_modules 目錄下的文件,npm 安裝的包不需要編譯
      exclude: /node_modules/,

      /*
      use 指定該文件的 loader,值可以是字符串或者數組。
      這裏先使用 eslint-loader 處理,返回的結果交給 babel-loader 處理。loader 的處理順序是從最後一個到第一個
      eslint-loader 用來檢查代碼,如果有錯誤,編譯的時候會報錯
      babel-loader 用來編譯 js 文件
      */
      use: ['babel-loader', 'eslint-loader']
    },
    {
      // 匹配 html 文件
      test: /\.html$/,
      /*
      使用 html-loader,將 html 內容存爲 js 字符串,比如當遇到
      import htmlString from './template.html';
      template.html 的文件內容會被轉成一個 js 字符串,合併到 js 文件裏
      */
      use: 'html-loader'
    },
    {
      // 匹配 css 文件
      test: /\.css$/,
      /*
      先使用 css-loader 處理,返回的結果交給 style-loader 處理。
      css-loader 將 css 內容存爲 js 字符串,並且把 background,@font-face 等引用的圖片,
      字體文件交給指定的 loader 打包,類似上面的 html-loader,用什麼 loader 同樣在 loaders 對象中定義,等會下面就會看到
      */
      use: ['style-loader', 'css-loader']
    },
    {
      /*
      匹配各種格式的圖片和字體文件
      上面 html-loader 會把 html 中 <img> 標籤的圖片解析出來,文件名匹配到這裏的 test 的正則表達式,
      css-loader 引用的圖片和字體同樣會匹配到這裏的 test 條件
      */
      test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,

      /*
      使用 url-loader,它接受一個 limit 參數,單位爲字節(byte)
      當文件體積小於 limit 時,url-loader 把文件轉爲 Data URI 的格式內聯到引用的地方
      當文件大於 limit 時,url-loader 會調用 file-loader,把文件儲存到輸出目錄,並把引用的文件路徑寫成輸出後的路徑

      比如 views/foo/index.html 中
      <img src="smallpic.png">
      會被編譯成
      <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAA...">

      而
      <img src="largepic.png">
      會被編譯成
      <img src="/f78661bef717cf2cc2c2e5158f196384.png">
      */
      use: [
        {
          loader: 'url-loader',
          options: {
            limit: 10000
          }
        }
      ]
    }
  ]

plugins

這時候如果我們修改「webpack.config.js」中「output」的「filename」的名字,「index.html」中的名字也要修改,有沒有辦法在編譯的時候,自動生成「index.html」並且自動引入「entry」文件?

「html-webpack-plugin」就是用來做這個的

安裝

npm i --save-dev html-webpack-plugin

修改「webpack.config.js」

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: {
    	app: './src/main.js'
  	},
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        // 匹配 css 文件
        test: /\.css$/,
        /*
        先使用 css-loader 處理,返回的結果交給 style-loader 處理。
        css-loader 將 css 內容存爲 js 字符串,並且把 background,@font-face 等引用的圖片,
        字體文件交給指定的 loader 打包
        */
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
	new HtmlWebpackPlugin({
		/*
	    template 參數指定入口 html 文件路徑,插件會把這個文件交給 webpack 去編譯,
	    webpack 按照正常流程,找到 loaders 中 test 條件匹配的 loader 來編譯,那麼這裏 html 就是匹配的 html-loader
	    html-loader 編譯後產生的字符串,會由 html-webpack-plugin 儲存爲 html 文件到輸出目錄,默認文件名爲 index.html
	    可以通過 filename 參數指定輸出的文件名
	    html-webpack-plugin 也可以不指定 template 參數,它會使用默認的 html 模版
	    */
		template: './index.html'
	})
  ]
}

刪除「index.html」中引入「bundle.js」的腳本,執行「webpack」命令,成功後可以看到在「dist」目錄下多了一個「index.html」,並且已經引入了「bundle.js」,用瀏覽器打開的效果跟剛纔是一樣的。

開發

在「main.js」中打印一個錯誤日誌,並重新運行「wenpack」編譯,用瀏覽器打開「index.html」

import './style.css'
console.info('hello webpack')
console.error('這是一個錯誤')

在這裏插入圖片描述
我們看到,瀏覽器只會提示我們「bundle.js」中有錯誤,但是定位不到原始文件的位置,這個時候我們要使用「source map」,在「webpack.config.js」中增加配置

module.exports = {
  entry: {
    	app: './src/main.js'
  	},
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  devtool: 'inline-source-map',
  ...
}

重現編譯,刷新瀏覽器,我們就能看到精確定位代碼位置
在這裏插入圖片描述
當我們在「main.js」中加入包含「ES6」語法的代碼時,運行「npm run build」編譯時並沒有報錯

function printArr(){
  const arr = Array(5).fill('6');
  arr.map(item => {
    console.log(item)
  })
};
printArr();

這是因爲 webpack 4 依賴包中使用「terser-webpack-plugin」替代了之前一直使用的「uglifyjs-webpack-plugin」作爲它的內置插件,並做代碼壓縮處理.

重新加載(live reloading)

每次要編譯代碼時,手動運行 webpack 就會變得很麻煩。
「webpack-dev-server」 提供了一個簡單的 web 服務器,並且能夠實時重新加載(live reloading)。

npm i --save-dev webpack-dev-server

webpack.config.js 加入配置

module.exports = {
	devServer: {
		contentBase: './dist'
	}
}

以上配置告知 webpack-dev-server,在 localhost:8080 下建立服務,將 dist 目錄下的文件,作爲可訪問文件。

讓我們添加一個 script 腳本,可以直接運行開發服務器(dev server):

"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "dev": "webpack-dev-server --open"
 }

現在執行「npm run dev」,瀏覽器會自動打開頁面,修改「main.js」的內容

console.log('hello webpack-dev-server')

我們可以看到瀏覽器上的內容已經實時更新了,並不用再刷新瀏覽器來查看結果

HMR(模塊熱替換)

模塊熱替換(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允許在運行時更新各種模塊,而無需進行完全刷新。

啓用此功能實際上相當簡單。而我們要做的,就是更新 webpack-dev-server 的配置,和使用 webpack 內置的 HMR 插件。

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

module.exports = {
  entry: {
    	app: './src/main.js'
  	},
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  devtool: 'inline-source-map',
  devServer: {
	    contentBase: './dist',
	    hot: true // +
	},
  module: {
    rules: [
      {
        // 匹配 css 文件
        test: /\.css$/,
        /*
        先使用 css-loader 處理,返回的結果交給 style-loader 處理。
        css-loader 將 css 內容存爲 js 字符串,並且把 background,@font-face 等引用的圖片,
        字體文件交給指定的 loader 打包,類似上面的 html-loader,用什麼 loader 同樣在 loaders 對象中定義,等會下面就會看到
        */
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    }),
    new webpack.NamedModulesPlugin(), // +
    new webpack.HotModuleReplacementPlugin() // +
  ]
}

分離配置

新建三個文件「webpack.base.conf.js」「webpack.dev.conf.js」「webpack.prod.conf.js」

// webpack.base.conf.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack');

module.exports = {
  entry: {
    	app: './src/main.js'
  	},
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        // 匹配 css 文件
        test: /\.css$/,
        /*
        先使用 css-loader 處理,返回的結果交給 style-loader 處理。
        css-loader 將 css 內容存爲 js 字符串,並且把 background,@font-face 等引用的圖片,
        字體文件交給指定的 loader 打包,類似上面的 html-loader,用什麼 loader 同樣在 loaders 對象中定義,等會下面就會看到
        */
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    })
  ]
}
// webpack.dev.conf.js
const webpack = require('webpack')
const merge = require('webpack-merge'); // 合併配置
const common = require('./webpack.base.conf');

module.exports = merge(common,{
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './dist'
  }
})
// webpack.prod.conf.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.base.conf');

module.exports = merge(common,{
  mode: 'production',
})
// package.json
"scripts": {
   "start": "webpack-dev-server --inline --progress --config webpack.dev.conf.js",
   "build": "webpack --config webpack.prod.conf.js"
 }

代碼分離

先引入「lodash」

npm i --save lodash

main.js 中添加如下代碼, 並執行「npm run build」命令

function component() {
  var element = document.createElement('div')

  element.innerHTML = _.join(['hello', 'webpack'], '')

  return element
}

document.body.appendChild(component())

在「dist」目錄下,我們能看到「app.bundle.js」和「index.html」兩個文件,但是打開「bundle.js」會發現我們「main.js」中引入的「lodash」也打包進去了,這樣在後續引入多個第三方包後會使得我們「app.bundle」文件越來越大,所以我們要把第三方插件抽離出來,統一打包到一個文件中去

1、第三方插件分離

module.exports = {
	optimization: {
    splitChunks: { // 替代 webpack.optimize.CommonsChunkPlugin
      cacheGroups: {
        vendors: {
          chunks: "all",
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          minChunks: 1,
          minSize: 30000,
          priority: 10 // 設置優先級
        }
      }
    }
  }
}

此時在「dist」目錄下會多一個「vendors.cd5da2e8640d819f8b41.js」文件,打開就會發現裏面是「lodash」的代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章