不懂Webpack4的前端不是好工程師(基礎篇)

NO IMAGE

webpack 究竟是什麼?

webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(module bundler),當 webpack 處理應用程序時,會遞歸構建一個依賴關係圖,其中包含應用程序需要的每個模塊,然後將這些模塊打包成一個或多個 bundle

傳統模式開發

新建 index.html,如下:

<!DOCTYPE html>
<html lang="en">
<body>
<p>這是網頁內容</p>
<div id="root"></div>
<script src="./header.js"></script>
<script src="./content.js"></script>
<script src="./footer.js"></script>
<script src="./index.js"></script>
</body>
</html>

再建立index.jsheader.jscontent.jsfooter.js

//index.js
new Header()
new Content()
new Footer()
//header.js
function Header () {
var dom = document.getElementById('root')
var header = document.createElement('div')
header.innerText = 'header'
dom.append(header)
}
//content.js
function Content () {
var dom = document.getElementById('root')
var content = document.createElement('div')
content.innerText = 'content'
dom.append(content)
}
//footer.js
function Footer () {
var dom = document.getElementById('root')
var footer = document.createElement('div')
footer.innerText = 'footer'
do
m.append(footer)
}

這樣面向對象 new 出各個模塊的構造函數,在一定程度上比面相過程全部寫在一個文件上好維護,但是HeaderContet這些構造函數指向問題還要回到index.html文件一一對應查找,就造成了在index.js上指定不明確,如果我們能像以下代碼一樣導入就能解決這個問題了

//index.js
import Header from './header.js'
import Content from './content.js'
import Footer from './footer.js'

new Header() new Content() new Footer()

遺憾的是,瀏覽器不會識別這種 es6 的語法,使用瀏覽器訪問index.html會發現控制檯會報錯。此時我們的webpack就可以為了解決這種問題而誕生了

使用 webpack 模塊化開發

首先想使用webpack,我們得先npm初始化包並安裝 webpack

npm init
npm install webpack webpack-cli -D

想用ESmodule 語法導出,我們得修增加導出語法

//header.js
export default Header
//content.js
export default Content
//footer.js
export default Footer

然後我們就可以使用npx 運行 webpack 執行 index.js 文件

npx webpack index.js

執行後,我們發發現根目錄下生成了一個 dist/main.js目錄文件,這便是 webpack 默認出口輸出(output),後面會介紹。這個文件便是 webpack 翻譯我們的index.js生成後的文件,所以有的人會稱webpackJS的翻譯器,但這說法也並不完成正確。既然我們翻譯了文件,我們就需要在index.html修改成導入後的文件,如下修改:

//index.html
<script src="./dist/main.js"></script>

修改完成後打開瀏覽器重新訪問index.html發現可以生成相對於模塊並且沒有報錯了,這就是webpack的模塊開發。

使用 webpack 的配置文件(核心概念)

webpack 的核心概念

  • entry: 入口

  • output: 輸出

  • loader: 模塊轉換器,用於把模塊原內容按照需求轉換成新內容

  • 插件(plugins): 擴展插件,在 webpack 構建流程中的特定時機注入擴展邏輯來改變構建結果或做你想要做的事情

使用一個配置文件

webpack v4 中,可以無須任何配置,因為webpack提供了默認配置,然而大多數項目會需要很複雜的設置,這就是為什麼 webpack 仍然要支持 配置文件。這比在 terminal(終端) 中手動輸入大量命令要高效的多,所以讓我們在根目錄創建一個配置文件:webpack.config.js(默認配置文件,可通過npx webpack --config webpack.config.js修改)

//webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index.jss' , //入口文件 默認:src/index.js
output: { //出口文件 默認: dist/main.js
filename: 'bundle.js', //輸出的文件名
path: path.resolve(__dirname,'dist') //輸出的路徑,只能是絕對路徑
}
}

新建 src 文件夾,移動index.js,header.js等 js 到 src 文件夾,執行以下代碼會發現 dist 文件夾生成了新的出口文件bundele.js

npx webpack src/index.js

entry

配置需要打包入口文件

//webpack.config.js
<!--單個文件-->
module.exports = {
entry: './src/index.jss' , //入口文件 默認:src/index.js
}

<!--打包多個入口文件--> module.exports = { entry: { main: './src/index.js', //入口文件 默認:src/index.js sub: './src/sub.js' }, }

output

配置打包輸出的文件

  output: { //出口文件 默認: dist/main.js
filename: 'bundle.js', //輸出的文件名
path: path.resolve(__dirname,'dist') //輸出的路徑,只能是絕對路徑
}

<!--多個入口文件需要不同名稱文件輸出配置--> output: { //出口文件 默認: dist/main.js filename: '[name].js', //輸出的文件名 path: path.resolve(__dirname, 'dist') //輸出的路徑,只能是絕對路徑 },

npm scripts

考慮到用 CLI 這種方式來運行本地的 webpack 副本並不是特別方便,我們可以設置一個快捷方式。調整 package.json 文件,添加在 npm scripts 中添加一個 npm 命令:

//package.json
"scripts": {
"bundle" : "webpack"
},

現在,可以使用 npm run build 命令,來替代我們之前使用的 npx 命令。注意,使用 npm scripts,我們可以像使用 npx 那樣通過模塊名引用本地安裝的 npm packages。這是大多數基於 npm 的項目遵循的標準,因為它允許所有貢獻者使用同一組通用腳本(如果必要,每個 flag 都帶有 –config 標誌)。

現在運行以下命令,然後看看你的腳本別名是否正常運行:

npm run bundle

淺析 webpack 打包輸出內容

npm run bundle
> [email protected] bundle E:\project\webpack4
> webpack
Hash: 768c04b37ed214487576
Version: webpack 4.42.0
Time: 98ms
Built at: 2020-03-24 4:57:54 PM
Asset      Size  Chunks             Chunk Names
bundle.js  1.29 KiB       0  [emitted]  main
Entrypoint main = bundle.js
[0] ./src/index.js + 3 modules 741 bytes {0} [built]
| ./src/index.js 147 bytes [built]
| ./src/header.js 202 bytes [built]
| ./src/content.js 198 bytes [built]
| ./src/footer.js 194 bytes [built]

WARNING in configuration The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. You can also set it to 'none' to disable any default behavior. Learn more: webpack.js.org/configurati…

webpack打包輸出的結果我們可以看出有個警告,那是因為我們沒有指定打包輸出的環境('development' or'production'),我們可以在webpack.config.js新增以下代碼:

module.exports = {
mode: 'development', //默認為production
...
}

重新執行npm run bundle 發現不會提出警告了,並且生成的bundle.js的代碼沒有被壓縮

什麼是 loader

webpack 可以使用 loader 來預處理文件。這允許你打包除 JavaScript 之外的任何靜態資源。如果你在默認配置下打包除 js 文件外出錯,所以我們要藉助loader來打包 js 外的文件

現在我們在src文件下存放一張logo.jpg的圖片,然後在 index.js 引入後使用webpack打開

//index.js
const logo = require('./logo.jpg')

執行npm run bundle打包後會拋出如下錯誤:

ERROR in ./src/logo.jpg 1:0
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)
@ ./src/index.js 5:13-34

這是因為 webpack 像我們上面說的一樣,只能打包 js 文件,如果想打包除 js 以外的靜態資源,此時我們就需要借用loader來幫助我們打包圖片資源。想打包圖片我們首先得先安裝相關的loader

file-loader

npm install file-loader -D

然後我們需要在webpack.config.js文件下配置相關的module規則:

//webpack.config.js
...
module: {
rules: [
{
test: /\.jpg$/,
use: [
{
loader: 'file-loader' //使用相對應的loader
}
]
}
]
}

控制檯npm run bundle再次運行時,發現打包成功沒有報錯,並且在 dist 出口目錄生成了相應的圖片資源。那我們導入的圖片資源變量會是什麼呢,我們試著打印一下:

const logo = require('./logo.jpg')
console.log('logo',logo)

不懂Webpack4的前端不是好工程師(基礎篇)
可以在瀏覽器中看出,我們獲取到的是打包生成的文件相關信息,包括圖片名字。經過這個例子,我們就明白了在vue的腳手架項目中可以這樣引入.vue 相關文件的了

import Header from 'Header.vue'
//webpack.config.js
rules: [
...
{
test: /\.vue$/,
use: { //只使用一個loader可以直接用對象配置
loader: 'vue-loader'
}
}
]

上面案例中,我們可以看到options字段,這是我們可以打包文件資源的時候定義相對應的規則,比如:

rules: [
{
test: /\.jpg$/,
use: [
{
loader: 'file-loader', //使用相對應的loader
options: {
name:'[name].[ext]', //定義打包生成的文件名字
outputPath: 'images' //定義輸出的文件目錄:dist/images 下
},
}
]
}
]

這樣我們就可以自定義打包文件的名字和目錄,更多的規則可以查看官方文檔配置

url-loader

我們可以用url-loader取代file-loader來實現靜態文件資源打包,那為什麼我們有 file-loader 還要用 url-loader,讓我看看下面的例子就知道了:

首先我們先npm install url-loader --save-dev安裝file-loader,然後在webpack.config.js進行相對應的配置

rules: [
{
test: /\.jpg$/,
use: [
{
loader: 'url-loader', //使用相對應的loader
options: {
limit: 10240 //單位:字節 超過10kb 的文件生成圖片,否則生成base64編碼
}
}
]
}
]

運行打包後我們可以發現,超過 10kb 的文件生成圖片,否則生成base64編碼。這樣做的好處是什麼呢,圖片生成 base64 編碼可以大大減少我們 https 請求,但不是所有的圖片都生成 base64 編碼,比如圖片幾 M 的生成的話,相對應 js 文件大小也會增加,網頁打空白的時間也相對應增加,至於哪些需要轉,limit 需要設置多大限制根據自己的項目來,我的項目中一般是 icon 圖標類的會轉 base64 編碼,其他大的相對應打包生成文件。

使用 loader 打包樣式靜態資源

當我們嘗試打包 css 文件的時候,如果沒有使用相對應的樣式loader就會打包失敗。我們在src目錄下創建新的文件index.csslogo.css,並且在 index.js 引入該樣式文件:

//logo.css
.logo{
width: 100px;
height: 100px;
}
//index.css
@import './logo.css'
//index.js
import logo from './logo.jpg'
import './index.css'

var img = new Image() img.src = logo img.classList.add('logo') var root = document.getElementById('root') root.append(img)

打包之後可以看到控制檯拋出了相對應的報錯,此時我們應該接入css-loaderstyle-loader來解決這個問題,首先我們先得安裝兩個loader

npm install css-loader style-loader -D
   rules: [
...
{
test: /\.css$/,
//注:打包執行順利從右到左,從下到上,不能顛倒,先接css-loader轉換語法在使用style-loader解析到頭部標籤
use: ['style-loader','css-loader']
}
]

css-loader

主要用於打包 css 文件中解析@import等語法,將 CSS 轉化成 CommonJS 模塊。

css-loader還可以配置更多的選項,比如importLoadersmodules等。如果沒有配置importLoaders,在一個 scss 文件中@import其他的 scss 文件,可能該導入的 scss 文件不會生效 css-loader 後面配置的 loader(sass-loader,postcss-loader)

use: ['style-loader',{
loader: 'css-loader',
options: {
importLoaders: 2 // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
}
},'sass-loader','postcss-loader'
]

配置modules參數為 true 可以模塊化導入相關的樣式文件,否則會全局樣式汙染。如下列案例:

//index.js
import logo from './logo.jpg'
import './index.scss'
import createLogo from './logo.js'

createLogo() var img = new Image() img.src = logo img.classList.add('logo') var root = document.getElementById('root') root.append(img)

//logo.js
import logo from './logo.jpg'
function createLogo () {
var img = new Image()
img.src = logo
img.classList.add('logo')
var root = document.getElementById('root')
root.append(img)
}
export default createLogo

上面因為沒有配置相關的樣式模塊導入,所以導入index.scss文件的樣式都在兩張圖片成功生效,下面我們增加下模塊配置引入:

    //webpack.congig.js
...
test: /\.scss$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
importLoaders: 2, // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
modules: true //按模塊化引入
}
}, 'sass-loader', 'postcss-loader'
]
}
//index.js
import logo from './logo.jpg'
import style from './index.scss'
import createLogo from './logo.js'

createLogo() var img = new Image() img.src = logo img.classList.add(style.logo) var root = document.getElementById('root') root.append(img)

重新打包後,我們發現只有 index.js 文件的圖片生效了樣式,我們模塊化導入樣式成功,更多的options配置樣式可以查看官方文檔

style-loader

配合css-loader使用,以形式在 html 頁面中頭部標籤插入css代碼。

sass-loader

npm install sass-loader node-sass webpack --save-dev

我們除了安裝sass-lader外,並且還需要你預先安裝 Node Sass。 這可以控制所有依賴的版本, 並選擇要使用的 Sass 實現。新建src/index.sass

//index.sass
body {
.logo{
width: 100px;
height: 100px;
}
}
//index.js
import logo from './logo.jpg'
import './index.sass'

var img = new Image() img.src = logo img.classList.add('logo') var root = document.getElementById('root') root.append(img)

我們需要在webpack.config.js新增相對應的規則配置:

//webpack.config.js
rules: [
...
{
test: /\.sass$/,
use: ['style-loader','css-loader','sass-loader'] //先把sass轉成css ,再進行起來的loader操作(右到左)
}
]

配置後重新執行npm run bundle打包,在瀏覽器中可以正常訪問,把sass-loader去掉再打包後,可以查看控制檯頭部樣式中sass的語法沒有轉成css,這就是 sass-loader 的作用

不懂Webpack4的前端不是好工程師(基礎篇)
不懂Webpack4的前端不是好工程師(基礎篇)

postcss-loader

npm install postcss-loader -D

postcss-loader可以對css3樣式前綴自動補全,兼容各個瀏覽器,使用postcss-loader前我們得配置相關的插件等,根目錄下新建postcss.config.js,安裝相對應的插件:autoprefixer(補全 css3 語法插件)

npm install autoprefixer -D
//postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
//webpack.config.js
rules: [
...
{
test: /\.scss$/,
use: ['style-loader','css-loader','sass-loader','postcss-loader']
}
]

autoprefixer補全得結合browserslist一起使用

//package.json
"browserslist": [
"defaults",
"ie >= 10",
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
],

使用 plugins 讓打包更快捷

插件(plugins): 擴展插件,在webpack構建流程中的特定時機注入擴展邏輯來改變構建結果或做你想要做的事情,就類似vue生命週期鉤子一樣,在某種場景,幫你做某些事情。官方已收錄的插件

HtmlWebpackPlugin

一種用於打包生成 html 的插件:HtmlWebpackPlugin會在打包結束後,自動生成 html 文件,並把打包生成的 js 自動引入到這個 html 文件中。具體配置可查看HtmlWebpackPlugin 文檔

//安裝HtmlWebpackPlugin文檔
npm install --save-dev html-webpack-plugin
//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html' //生成的模板文件
}),

] }

//public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>html 模板</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

CleanWebpackPlugin

有時候我們打包總是要手動刪除掉上一次打包的文件,我們就想有沒有什麼工具能幫助我們在打包前自動刪除掉 dist 目錄,CleanWebpackPlugin就可以幫我們解決這個問題,詳細配置

//webpack.config.js
...
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html'
}),
]

source-map

source-map 可以解決打包後代碼報錯的地方是打包後的代碼而不是源業務代碼的問題

//index.js
console.log('devtool',test)

這是未設置 source-map 報錯的打包代碼
不懂Webpack4的前端不是好工程師(基礎篇)
我們在 webpack.config.js 配置下

devtool: 'source-map'

配置後重新打包久可以看到報錯的是原業務代碼,但是我們不建議直接用source-map,我建議開發環境使用eval-cheap-module-source-map,生成環境用cheap-module-source-map,不同的配置打包的速度不一樣,可以簡單總結,source-map 會生成.map 文件來映射,打包速度會很慢,因為還要映射打包文件,inline可以不生產.map 文件,直接打包在出門文件裡面轉成和 base64,cheap可以只報行除出錯而不加列出錯,module 可以讓第三方 loader 插件也生效報錯,eval可以直接執行 eval 函數所以速度最快,具體可以參考官方文檔配置

webpackDevServer

有時候我們修改了打包入口的文件,總是要重新打包編譯打開瀏覽器訪問,有沒有一種配置能讓我們監聽到入口文件修改,就能自動打包編譯在瀏覽器刷新呢,webpackDevServer就可以幫你做到,webpackDevServer會在本地幫你的項目搭建一個服務器來跑,只要你更新它就可以幫你重新打包編譯~

首先我們得安裝 webpackDevServer

npm install webpack-dev-server -D

然後配置相關的參數

//webpack.config.js
module.exports = {
...
devServer: {
contentBase: './dist',
port: 9000, //服務端口號
open: true, //首次打包編譯自動打開瀏覽器
proxy: {//反向代理,一般用於解決跨域問題
'/api': 'http://localhost:3000'
}
},

執行npm run start 就可以打包編譯幫你打開相關的服務了~

//package.json
"scripts": {
"start": "webpack-dev-server"
},

Hot Module Replacement

有時候我們需要做的是,改了該模塊的代碼,瀏覽器不刷新,只更新該的模塊代碼上去,Hot Module Replacement就能幫我們實現這個效果。

//webpack.config.js
const webpack = require('webpack')
devServer: {
...
hot: true,//使用 Hot Module Replacement
hotOnly: true, //Hot Module Replacement 出錯的時候,瀏覽器照樣不刷新
},
plugins: [
...
new webpack.HotModuleReplacementPlugin()
]

js 模塊代碼更新的話還需要增加,css 模塊的話css-loader已經幫我們處理了,那像 vue 的文件修改vue-loader也已經做了相關的處理

if (module.hot) {
module.hot.accept('./number.js', function() {
// Do something with the updated library module...
});
}

babel

我們在項目中為了更好的提高開發效率會使用一些ES6語法,雖然很方便快捷, 但是ES6語法在低版本瀏覽器並不完全支持,這樣就會導致語法報錯,所以我們需要藉助babel來轉發成ES5來兼容各個瀏覽器

// index.js
import "@babel/polyfill"; //引入polyfill:可實現ES6語法解釋
const p = new Promise(() => {})
let newArr = [1,2,3]
newArr.map((item) => {
return item
})

將其安裝為開發依賴項


<!--正常業務使用babel-->
npm install --save-dev babel-loader @babel/core
npm install @babel/preset-env --save-dev
<!--使用polyfill-->
npm install --save @babel/polyfill
<!--使用插件-->
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save @babel/runtime-corejs2
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader", //只用建於和webpack通訊,並未來解決轉換問題
options: {
// "presets": [["@babel/preset-env",{
//   targets: {
//     chrome: "67"
//   },
//   useBuiltIns: 'usage' //可以根據業務代碼只轉相關ES6,大大減少打包體積
// }]] //用於轉換ES6語法成ES5識別語法,但未能解釋Promise map 等語法
// "plugins": [
//   [
//     "@babel/plugin-transform-runtime",
//     {
//       "absoluteRuntime": false,
//       "corejs": 2, //默認flase
//       "helpers": true,
//       "regenerator": true,
//       "useESModules": false,
//       "version": "7.0.0-beta.0"
//     }
//   ]
// ]
}
},

我們可以使用 presets 的方式,也可以使用 plugins 的方式。當我們需要做第三方庫或者插件的時候,我們就需要使用 plugins 的方式,因為 presets 的方式會造成全局汙染,當我們配置項太多的時候,我們也可以在根目錄抽出單獨的文件.babelrc來配置

//.babelrc
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2, //默認flase
"helpers": true,
"regenerator": true,
"useESModules": false,
"version": "7.0.0-beta.0"
}
]
]
}

本文使用 mdnice 排版

相關文章

專訪李強:我為什麼要讓孩子學習編程?

如何寫出讓同事膜拜的漂亮代碼?

6本Python好書上新,來撩~

入行AI,程序員為什麼要學習NLP?