webpack 是一個現代 JavaScript 應用程序的靜態模塊打包工具,它對於前端工程師來講可謂是如雷貫耳,基本上如今的大型應用都是經過 webpack 進行構建的。javascript
webpack 具備高度可配置性,它擁有很是豐富的配置。在過去一段時間內曾有人將熟練配置 webpack 的人稱呼爲 「webapck 工程師」。固然,這稱呼只是個玩笑話,但也能從側面瞭解到 webpack 配置的靈活與複雜。css
爲了可以熟練掌握 webpack 的使用,接下來經過幾個例子按部就班的學習如何使用 webpack。html
如下 Demo
均可以在 Github 的 webpack-example 中找到對應的示例,歡迎 star~前端
本篇文章內容略長,建議先馬後看。因爲平臺不支持代碼摺疊,所以建議直接看原文 從零構建 webpack 腳手架(基礎篇) | Anran758's blog 以得到更好的閱讀體驗。java
從 webpack@v4.0.0
開始,就能夠不用再引入配置文件來打包項目。若沒有提供配置的話,webpack 將按照默認規則進行打包。默認狀況下 src/index
是項目的源代碼入口,打包後的代碼會輸出到 dist/main.js
上。node
首先來初始化一個項目,項目名爲 getting-started:react
# 建立項目文件夾 mkdir getting-started # 進入項目目錄 cd getting-started # npm 項目 npm init -y
初始化項目後,項目目錄會新增一個 package.json
,該文件記錄了項目依賴的相關信息。若想要使用 webpack 的話須要安裝它的依賴: webpack
(本體)和 webpack-cli
(能夠在命令行操做 webpack 的工具):webpack
# -D 和 --save-dev 選項均可以用於安裝開發依賴 # npm i --save-dev webpack webpack-cli npm i -D webpack webpack-cli # 或者使用 yarn 安裝開發依賴 yarn add -D webpack webpack-cli
接着建立 webpack 所需的默認入口文件 src/index.js
以及測試模塊所用的 src/log.js
文件。此時的項目結構大體以下:git
. ├── package.json + ├── src + │ ├── index.js + │ └── log.js └── node_modules
// src/log.js export const log = (name) => console.log(`Hello ${name}!`); // src/index.js import { log } from './log' log('anran758');
src/log.js
導出了一個工具函數,它負責向控制檯發送消息。src/index.js
是默認的入口文件,它引入 log
函數並調用了它。github
上面的代碼很簡單,像這種模塊化的代碼按照傳統 <script src>
引入的話,瀏覽器是不能正確執行的。能夠在根目錄上建立一個 index.html
引入 js 腳原本測試一下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Test</title> </head> <body> <!-- 引入腳本 --> <script src="./src/index.js"></script> </body> </html>
建立文件後,將上例代碼複製到 index.html
中。保存並打開該文件,看看瀏覽器可否正確處理模塊邏輯。不出意外的話,文件在瀏覽器打開後,瀏覽器開發者工具會拋出錯誤信息:
Uncaught SyntaxError: Cannot use import statement outside a module
言下之意就是說瀏覽器不能正確的解析 ES module
語句,此時 webpack 就能夠派上用場啦~ 在 package.json
中的 scripts
字段中添加以下命令:
"scripts": { + "build": "webpack" - "test": "echo \"Error: no test specified\" && exit 1" },
在命令行輸入 npm run build
調用 webpack
對當前項目進行編譯,編譯後的結果會輸出到 dist/main.js
文件中(即使本地沒有 dist 目錄,它都會自動建立該目錄)。輸出文件後,修改 index.html
對 js 的引用:
<body> + <script src="./dist/main.js"></script> - <script src="./src/index.js"></script> </body>
從新刷新頁面後就能看到 log
正確的輸出了 Hello anran758!
。點擊 log 右側的連接,能夠跳轉至 Source
面板,將代碼格式化後能夠清晰地看到編譯後 js 的變化:
固然,上例代碼只不過是小試牛刀。對於正式的項目會有更復雜的需求,所以須要自定義配置。webpack
主要有兩種方式接收配置:
第一種: 經過 Node.js
API引入 webpack 包,在調用 webpack 函數時傳入配置:
const webpack = require("webpack"); const webpackConfig = { // webpack 配置對象 } webpack(webpackConfig, (err, stats) => { if (err || stats.hasErrors()) { // 在這裏處理錯誤 } // 處理完成 });
第二種: 經過 webpack-cli
在終端使使用 webpack 時指定配置。
webpack [--config webpack.config.js]
兩種方法內配置都是類似的,只是調用的形式不一樣。本篇先使用 webpack-cli
來作示例。
webpack 接受一個特定的配置文件,配置文件要求導出一個對象、函數、Promise
或多個配置對象組成的數組。
如今將上一章的 Demo 複製一份出來,並重命名爲 getting-started-config,在該目錄下新建 webpack.config.js
文件,文件內容以下:
const path = require('path'); module.exports = { // 起點或是應用程序的起點入口 entry: "./src/index", output: { // 編譯後的輸出路徑 // 注意此處必須是絕對路徑,否則 webpack 將會拋錯(使用 Node.js 的 path 模塊) path: path.resolve(__dirname, "dist"), // 輸出 bundle 的名稱 filename: "bundle.js", } }
上面的配置主要是定義了程序入口、編譯後的文件輸出目錄。而後在 src/index.js
中修改一些內容用來打包後測試文件是否被正確被編譯:
import { log } from './log' + log('本節在測試配置噢'); - log('anran758');
隨後在終端輸入 num run build
進行編譯,能夠看到 dist
目錄下多了個 bundle.js
。
$ npm run build > webpack --config ./webpack.config.js Hash: 3cd5f3bbfaf23f01de37 Version: webpack 4.43.0 Time: 117ms Built at: 05/06/2020 1:01:37 PM Asset Size Chunks Chunk Names bundle.js 1010 bytes 0 [emitted] main Entrypoint main = bundle.js [0] ./src/index.js + 1 modules 123 bytes {0} [built] | ./src/index.js 62 bytes [built] | ./src/log.js 61 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: https://webpack.js.org/configuration/mode/
因爲咱們輸出的文件名被修改了,此時還得修改 html
的引入路徑。但每改一次輸出目錄,HTML
中的引入路徑也得跟着改,這樣替換的話就比較容易出紕漏。那能不能讓 webpack 自動幫咱們插入資源呢?答案是能夠的。
webpack 提供插件(plugin)的功能,它能夠用於各類方式自定義 webpack 構建過程。
html-webpack-plugin 能夠在運行 webpack 時自動生成一個 HTML
文件,並將打包後的 js
代碼自動插入到文檔中。下面來安裝它:
npm i --D html-webpack-plugin
安裝後在 webpack.config.js
中使用該插件:
const path = require('path'); + const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { // 起點或是應用程序的起點入口 entry: "./src/index", // 輸出配置 output: { // 編譯後的輸出路徑 // 注意此處必須是絕對路徑,否則 webpack 將會拋錯(使用 Node.js 的 path 模塊) path: path.resolve(__dirname, "dist"), // 輸出 bundle 的名稱 filename: "bundle.js", }, + plugins: [ + new HtmlWebpackPlugin({ + title: 'Test Configuration' + }) + ], }
從新編譯後 HTML
也被輸出到 dist
目錄下。查看 dist/index.html
的源碼能夠發現:不只源碼被壓縮了,同時 <script>
標籤也正確的引入了 bundle.js
。
此時目錄結構以下:
後續目錄展現會將node_modules
、package-lock.json
、yarn.lock
這種對項目架構講解影響不大的目錄省略掉。
. ├── dist │ ├── bundle.js │ ├── index.html │ └── main.js ├── index.html ├── package.json ├── src │ ├── index.js │ └── log.js └── webpack.config.js
處理完資源自動插入的問題後,還有一個問題須要咱們處理:雖然 webpack 如今能自動生成 HTML
並插入腳本,但咱們還得在 HTML
中寫其餘代碼邏輯呀,總不能去改 /dist/index.html
文件吧?
這個問題也很好解決。html-webpack-plugin
在初始化實例時,傳入的配置中能夠加上 template
屬性來指定模板。配置後直接在指定模板上進行編碼就能夠解決這個問題了:
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { // 起點或是應用程序的起點入口 entry: "./src/index", // 輸出配置 output: { // 編譯後的輸出路徑 // 注意此處必須是絕對路徑,否則 webpack 將會拋錯(使用 Node.js 的 path 模塊) path: path.resolve(__dirname, "dist"), // 輸出 bundle 的名稱 filename: "bundle.js", }, plugins: [ // html-webpack-plugin // https://github.com/jantimon/html-webpack-plugin#configuration new HtmlWebpackPlugin({ title: 'Test Configuration', + template: path.resolve(__dirname, "./index.html"), }) ], }
使用模板後 html-webpack-plugin
也會自動將腳本插入到模板中。所以能夠將模板中的 <script>
給去掉了。爲了測試輸出的文件是否使用了模板,在 <body>
內隨便插入一句話,從新打包後預覽輸出的文件是否包含這句話:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Test Config</title> - <title>Test</title> </head> <body> + <p>Test Config</p> - <script src="./dist/main.js"></script> </body> </html>
修改文件後,從新打包就能看到模板也被壓縮輸出至 /dist/index.html
了,script
標籤也正常的插入了。
如今來看編譯後的目錄,咱們發現 dist/mian.js
這文件是使用配置以前編譯出來的文件,如今咱們的項目已經再也不須要它了。這種歷史遺留的舊文件就應該在每次編譯以前就被扔進垃圾桶,只輸出最新的結果。
clean-webpack-plugin 或 rimraf 能夠完成清理功能。前者是比較流行的 webpack 清除插件,後者是通用的 unix 刪除命令(安裝該依賴包後 windows 平臺也能用)。若是僅是清理 /dist
目錄下文件的話,我的是比較傾向使用 rimraf
的,由於它更小更靈活。而 clean-webpack-plugin
是針對 webpack 輸出作的一系列操做。
在終端安裝依賴:
npm i -D rimraf
rimraf
的命令行的語法是: rimraf <path> [<path> ...]
,咱們在 package.json
的 scirpts
中修改 build
的命令:
"scripts": { + "build": "rimraf ./dist && webpack --config ./webpack.config.js" - "build": "webpack --config ./webpack.config.js" }
$ npm run build > rimraf ./dist && webpack --config ./webpack.config.js Hash: 763fe4b004e1c33c6876 Version: webpack 4.43.0 Time: 342ms Built at: 05/06/2020 2:35:49 PM Asset Size Chunks Chunk Names bundle.js 1010 bytes 0 [emitted] main index.html 209 bytes [emitted] Entrypoint main = bundle.js [0] ./src/index.js + 1 modules 123 bytes {0} [built] | ./src/index.js 62 bytes [built] | ./src/log.js 61 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: https://webpack.js.org/configuration/mode/ Child HtmlWebpackCompiler: 1 asset Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0 1 module
這樣 webpack 輸出的 /dist
目錄始終是最新的東西。
在正常的頁面中,引入 css
樣式表會讓頁面變得更美觀。引入圖片可讓頁面內容更豐富。
然而 webpack 本體只能處理原生的 JavaScript 模塊,你讓它處理 css
或圖片資源,它是沒法直接處理的。爲了處理這種問題,webpack 提供了 loader 的機制,用於對模塊外的源碼進行轉換。
loader
通常是單獨的包,咱們能夠在社區找到對應 loader
來處理特定的資源。在使用前經過 npm
安裝到項目的開發依賴中便可。loader
能夠經過配置、內聯或 Cli 這三種方式來使用。下文主要以 配置
的方式來使用。
往常引入 css
樣式表無非就是在 html
中經過 <link>
標籤引入。如今想經過 webpack 來管理依賴得須要安裝對應的 loader
來處理這些事。
css-loader 可讓 webpack 能夠引入 css
資源。光有讓 webpack 識別 css 的能還不夠。爲了能將 css
資源進行導出,還要安裝 mini-css-extract-plugin 插件:
如今將上一節的 Demo 複製並重名爲 getting-started-loader-css。進入新的項目目錄後安裝依賴:
npm install -D css-loader mini-css-extract-plugin
在更改配置以前,爲了使項目結構更清晰,我們按照文件類型從新調整源碼目錄結構。將 src
下的 js
文件都放進 js
文件夾中。同時建立 /src/css/style.css
樣式表。調整後的目錄結構以下:
. ├── package.json ├── src │ ├── index.html │ ├── css │ │ └── style.css │ └── js │ ├── index.js │ └── log.js └── webpack.config.js
如今將 Flexbox 佈局用例 中結尾的 Demo 遷移到項目中,測試一下效果:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Test</title> </head> <body> <div class="panels"> <div class="panel panel1"> <p class="item name">Alice</p> <p class="item index">I</p> <p class="item desc">Pixiv Content ID: 65843704</p> </div> <div class="panel panel2"> <p class="item name">Birthday</p> <p class="item index">II</p> <p class="item desc">Pixiv Content ID: 70487844</p> </div> <div class="panel panel3"> <p class="item name">Dream</p> <p class="item index">III</p> <p class="item desc">Pixiv Content ID: 65040104</p> </div> <div class="panel panel4"> <p class="item name">Daliy</p> <p class="item index">IV</p> <p class="item desc">Pixiv Content ID: 64702860</p> </div> <div class="panel panel5"> <p class="item name">Schoolyard</p> <p class="item index">V</p> <p class="item desc">Pixiv Content ID: 67270728</p> </div> </div> </body> </html>
/src/css/style.css:
html { font-family: 'helvetica neue'; font-size: 20px; font-weight: 200; background: #f7f7f7; } body, p { margin: 0; } .panels { display: flex; min-height: 100vh; overflow: hidden; } .panel { flex: 1; display: flex; align-items: center; justify-content: center; flex-direction: column; color: white; background: #ececec; text-align: center; box-shadow: inset 0 0 0 5px rgba(255, 255, 255, 0.1); transition: font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11), flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11), background 0.2s; font-size: 20px; background-size: cover; background-position: center; cursor: pointer; } .panel1 { background-color: #f4f8ea; } .panel2 { background-color: #fffcdd; } .panel3 { background-color: #beddcf; } .panel4 { background-color: #c3cbd8; } .panel5 { background-color: #dfe0e4; } .item { flex: 1 0 auto; display: flex; justify-content: center; align-items: center; transition: transform 0.5s; font-size: 1.6em; font-family: 'Amatic SC', cursive; text-shadow: 0 0 4px rgba(0, 0, 0, 0.72), 0 0 14px rgba(0, 0, 0, 0.45); } .name { transform: translateY(-100%); } .panel .index { font-size: 4em !important; width: 100%; } .desc { transform: translateY(100%); } .open-active .name, .open-active .desc { transform: translateY(0); width: 100%; } .panel.open { flex: 3; font-size: 40px; }
/src/js/index.js
import { log } from './log' import '../css/style.css'; function installEvent() { const panels = document.querySelectorAll('.panel') function toggleOpen() { panels.forEach(item => { if (item === this) return; item.classList.remove('open') }); this.classList.toggle('open'); } function toggleActicon(e) { if (e.propertyName.includes('flex-grow')) { this.classList.toggle('open-active') } } // 給每一個元素註冊事件 panels.forEach(panel => { panel.addEventListener('click', toggleOpen) panel.addEventListener('transitionend', toggleActicon) }) } installEvent(); log('本節在測試配置噢');
修改 webpack 配置,引入 css-loader
和 mini-css-extract-plugin
。既然已經對源碼目錄進行分類了,那順便也給輸出目錄的文件也進行分類整理吧:
// /webpack.config.js const path = require('path'); + const HtmlWebpackPlugin = require('html-webpack-plugin'); + const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { // 起點或是應用程序的起點入口 entry: "./src/js/index", // 輸出配置 output: { // 編譯後的輸出路徑 // 注意此處必須是絕對路徑,否則 webpack 將會拋錯(使用 Node.js 的 path 模塊) path: path.resolve(__dirname, "dist"), // 輸出 bundle 的名稱 - filename: "bundle.js", + filename: "js/bundle.js", + }, + module: { + rules: [ + { + test: /\.css$/i, + use: [MiniCssExtractPlugin.loader, 'css-loader'], + }, + ], + }, plugins: [ // html-webpack-plugin // https://github.com/jantimon/html-webpack-plugin#configuration new HtmlWebpackPlugin({ title: 'Test Configuration', - template: path.resolve(__dirname, "./index.html"), + template: path.resolve(__dirname, "./src/index.html"), + }), + + // 提取 css 到單獨的文件 + // https://github.com/webpack-contrib/mini-css-extract-plugin + new MiniCssExtractPlugin({ + // 選項相似於 webpackOptions.output 中的相同選項,該選項是可選的 + filename: 'css/index.css', + }) ], }
如今咱們根據上面的配置來解讀 loader
的使用:
在上面的配置中,module 規定了如何處理項目中的不一樣類型的模塊。rules 是建立模塊時,匹配請求的 rule
(規則)數組。rule
是一個對象,其中最多見的屬性就是 test 、 use 和 loader。
rule.test
是匹配條件,一般會給它提供一個正則表達式或是由正則表達式組成的數組。若是配置了 test
屬性,那這個 rule
將匹配指定條件。好比匹配條件寫爲 test: /\.css$/i
,這意味着給後綴爲 .css
的文件使用 loader
。
rule.use
顧名思義就是使用,給符合匹配條件的文件使用 loader
。它能夠接收一個字符串,這個字符串會經過 webpack 的 resolveLoader 選項進行解析。該選項能夠不配置,它內置有解析規則。好比下例中默認會從 node_modules
中查找依賴:
use: 'css-loader'
rule.use
還能夠是應用於模塊的 UseEntry 對象。UseEntry
對象內主要有 loader
和 options
兩個屬性:
// use 傳入 UseEntry 類型的對象 use: { // 必選項,要告訴 webpack 使用什麼 loader loader: 'css-loader', // 可選項,傳遞給 loader 選項 options: { modules: true } },
若是 UseEntry
對象內只設置 loader
屬性,那它與單傳的字符串的效果是同樣的。而 options
是傳遞給 loader
的配置項,不一樣 loader
會提供有不一樣的 options
。值得注意的是,若是 use
是以對象形式傳入,loader
屬性是必填的,而 options
是可選的。
rule.use
還能夠是一個函數,函數形參是正在加載的模塊對象參數,最終該函數要返回 UseEntry
對象或數組:
use: (info) => { console.log(info); return { loader: 'svgo-loader', options: { plugins: [{ cleanupIDs: { prefix: basename(info.resource) } }] } } }
打印出函數的形參 info
能夠看到該對象擁有以下屬性:
compiler
: 當前的 webpack 編譯器(能夠未定義)issuer
: 導入正在加載的模塊的模塊的路徑realResource
: 始終是要加載的模塊的路徑resource
: 要加載的模塊的路徑,一般等於 realResource
。除非在請求字符串中經過 !=! 覆蓋資源名。因而可知,使用函數方式可用於按模塊更改 loader 選項。
rule.use
最多見的使用形式仍是提供一個數組,數組中每項能夠是字符串、UseEntry
對象、UseEntry
函數。這也是一個套娃的過程:
use: [{ loader: MiniCssExtractPlugin.loader }, 'css-loader'],
這裏須要注意的是,rule
中使用多個 loader
要注意其順序。使用數組 loader
將會從右至左進行應用。
好比上例中最早經過 css-loader
來處理 .css
文件的引入問題,再經過 MiniCssExtractPlugin.loader
(Tips: 該值是 loader
的絕對路徑)來提取出文件。若是反過來應用就會出問題了,webpack
都不知道如何引用 css
文件,天然提取不出東西啦。
rule.loader
是 rule.use
的縮寫,等價於 rule.use: [{ loader }]
。webpack 像這樣簡寫的配置屬性還有不少,這樣作有利也有弊。對於熟手來講,提供這種簡便選項能夠減小配置的嵌套關係,但對新手來講,這配置有種錯綜複雜的感受。
{ // 匹配文件規則 test: /\.css$/i, // rule.use 簡寫形式 loader: 'css-loader' }
接下來回歸正題。從新編譯 webpack,編譯後的目錄結構以下:
. ├── dist │ ├── css │ │ └── index.css │ ├── index.html │ └── js │ └── bundle.js ├── package.json ├── src │ ├── css │ │ └── style.css │ ├── index.html │ └── js │ ├── index.js │ └── log.js └── webpack.config.js
圖片資源也是項目中的常見資源,引入圖片資源一樣須要安裝 loader
。處理圖片資源的 loader
主要有兩種,分別是 url-loader
和 file-loader
。
file-loader 是將 import/require()
引入的文件解析爲 url,並把文件輸出到輸出目錄中。
複製一份新 Demo
並重命名爲 getting-started-loader-images。在安裝 loader
以前先作一個小優化:
若是咱們會頻繁修改源碼文件,修改完後又要從新編譯,這個步驟實際是有點繁瑣的。webpack 有個 watch
選項能夠監聽文件變化,若文件有修改 webpack 將自動編譯(若修改的是配置文件的話,仍是須要從新運行命令)。
在 package.json
的 script
中給 webpack 添加 -w
選項:
"scripts": { "build:watch": "rimraf ./dist && webpack --config ./webpack.config.js -w" },
接下來就能夠安裝依賴了:
npm i -D file-loader
新建一個 /src/images
文件夾,往裏面添加一些圖片:
. ├── package.json ├── src │ ├── css │ │ └── style.css + │ ├── images + │ │ ├── 01.jpg + │ │ ├── 02.png + │ │ ├── 03.jpg + │ │ ├── 04.png + │ │ ├── 05.png + │ │ ├── 06.jpg + │ │ ├── webpack.jpg + │ │ └── webpack.svg │ ├── index.html │ └── js │ ├── index.js │ └── log.js └── webpack.config.js
在 webpack.config.js
中配置 loader
:
rules: [ { test: /\.html$/i, loader: 'html-loader', }, { // 匹配文件規則 test: /\.css$/i, // use 從右至左進行應用 use: [MiniCssExtractPlugin.loader, 'css-loader'], }, + { + test: /\.(png|jpe?g|gif|webp|svg)(\?.*)?$/, + use: { + loader: 'file-loader', + options: { + name: 'img/[name].[hash:8].[ext]' + }, + }, + }, ],
默認狀況下圖片會被輸出到 dist
目錄中,文件名也會被更改成一長串的哈希值。爲了保持目錄整潔,將要被輸出的圖片資源都歸類到 img
目錄中。
能夠經過設定 name
或 publicPath
來指定目錄:
// 直接設置 name use: { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]', }, }, // 或者使用 publicPath,效果與上例等價 use: { loader: 'file-loader', options: { publicPath: 'img', name: '[name].[hash:8].[ext]', }, },
name
屬性的值能夠用 /
分層。除去最末尾一層的是文件名,前面每層 /
分隔都是嵌套的文件夾。好比值爲 static/img/[name].[hash:8].[ext]
最後輸出的結果是:根目錄建立一個 static
目錄,static
內又會建立一個 img
目錄,img
內輸出被引用的圖片資源。
因爲匹配的圖片資源有不少,我們不能寫死輸出的文件名,否則會引起重名問題,操做系統不許這樣幹。這時 佔位符(placeholder)就能排上用場了。name
中方括號包裹起來的是佔位符,不一樣佔位符會被替換成不一樣的信息。
好比上例中使用了三個佔位符: name
是文件的名稱、hash
是指定用於對文件內容進行 hash (哈希)處理的 hash 方法,後面冒號加數值表明截取 hash 的長度爲 八、ext
是文件的擴展名。在文件名加入 hash
的用意是針對瀏覽器緩存而特地加入的。如今能夠不用在乎這種優化問題,將來會專門另起一篇文章講優化的問題。
如今修改完 webapck 配置,接着再來完善上一節的 Demo。在 /src/css/styles.css
中使用 backgournd-image
引入圖片:
/* 省略其餘代碼... */ .panel1 { background-color: #f4f8ea; background-image: url('../images/01.jpg'); } .panel2 { background-color: #fffcdd; background-image: url('../images/02.png'); } .panel3 { background-color: #beddcf; background-image: url('../images/03.jpg'); } .panel4 { background-color: #c3cbd8; background-image: url('../images/04.png'); } .panel5 { background-color: #dfe0e4; background-image: url('../images/05.png'); }
從新編譯後的結果以下:
> rimraf ./dist && webpack --config ./webpack.config.js -w webpack is watching the files… Hash: 398663f1f4d417d17c94 Version: webpack 4.43.0 Time: 1086ms Built at: 05/29/2020 2:19:03 PM Asset Size Chunks Chunk Names css/index.css 1.72 KiB 0 [emitted] main img/01.a8e7ddb2.jpg 170 KiB [emitted] img/02.46713ed3.png 744 KiB [emitted] [big] img/03.70b4bb75.jpg 529 KiB [emitted] [big] img/04.b7d3aa38.png 368 KiB [emitted] [big] img/05.875a8bc2.png 499 KiB [emitted] [big] index.html 990 bytes [emitted] js/bundle.js 1.33 KiB 0 [emitted] main Entrypoint main = css/index.css js/bundle.js [0] ./src/css/style.css 39 bytes {0} [built] [1] ./src/js/index.js + 1 modules 938 bytes {0} [built] | ./src/js/index.js 873 bytes [built] | ./src/js/log.js 60 bytes [built] + 1 hidden module 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: https://webpack.js.org/configuration/mode/ WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). This can impact web performance. Assets: img/04.b7d3aa38.png (368 KiB) img/05.875a8bc2.png (499 KiB) img/02.46713ed3.png (744 KiB) img/03.70b4bb75.jpg (529 KiB) WARNING in webpack performance recommendations: You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application. For more info visit https://webpack.js.org/guides/code-splitting/ Child HtmlWebpackCompiler: 1 asset Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0 [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html 1.01 KiB {0} [built] Child mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!src/css/style.css: Entrypoint mini-css-extract-plugin = * [0] ./node_modules/css-loader/dist/cjs.js!./src/css/style.css 3.09 KiB {0} [built] [3] ./src/images/01.jpg 63 bytes {0} [built] [4] ./src/images/02.png 63 bytes {0} [built] [5] ./src/images/03.jpg 63 bytes {0} [built] [6] ./src/images/04.png 63 bytes {0} [built] [7] ./src/images/05.png 63 bytes {0} [built] + 2 hidden modules
當咱們從新打開 /dist/index.html
時會發現圖片並無加載出來?查看 css
源碼後發現原來是路徑有問題,編譯後的路徑是 img/01.a8e7ddb2.jpg
這種相對路徑。
因爲 css
自己有一個文件夾,經過相對路徑引入,那就會從 css 目錄下進行查找。實際找到的是 dist/css/img/01.a8e7ddb2.jpg
這條路徑。
遇到這種狀況怎麼辦呢?咱們能夠給 MiniCssExtractPlugin.loader
添加 publicPath
選項用以修正路徑,從新編譯後就能夠看到圖片正確被加載了:
{ // 匹配文件規則 test: /\.css$/i, // use 從右至左進行應用 use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: '../', } }, 'css-loader' ], },
在 js 中也能夠引用文件,打開 /src/js/index.js
, 在原先的基礎上添加以下代碼:
import img1 from '../images/06.jpg'; import img2 from '../images/webpack.jpg'; import img3 from '../images/webpack.svg'; // 省略其餘代碼... log('測試圖片引入~'); console.log('img1 --> ', img1); console.log('img2 --> ', img2); console.log('img3 --> ', img3);
從新編譯後能夠在 Console
面板能夠看到 js 輸出了文件資源的路徑:
url-loader 功能也相似於 file-loader
,不一樣的是當文件大小(單位 byte)小於 limit
時,能夠返回一個 DataURL
。
爲何要用 DataURL
呢?咱們知道頁面上每加載一個圖片資源,都會發起一個 HTTP
請求。而創建 HTTP
請求的過程是須要花時間的。所以能夠將文件轉爲 DataURL
嵌入 html/css/js
文件中,這樣能夠有效減小 HTTP
創建鏈接時所帶來額外的時間開銷了。同時 html/css/js
文件也能夠被瀏覽器緩存,DataURL
被引入後也能一同被緩存。
圖片轉 DataURL
也有缺點,那就是編碼後文本儲存所佔的空間比圖片會更大。這其實就是傳輸體積與 HTTP 鏈接數的權衡。因此最佳作法是將小圖片轉爲 DataURL
,轉換後並不會有過多體積溢出,而大尺寸圖片照常引入便可。
安裝 url-loader
:
npm install url-loader -D
修改 webpack.config.js
:
rules: [ { // 匹配文件規則 test: /\.css$/i, // use 從右至左進行應用 use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: '../' } }, 'css-loader' ], }, { test: /\.(png|jpe?g|gif|webp)(\?.*)?$/, use: { loader: 'url-loader', options: { limit: 10000, name: 'img/[name].[hash:8].[ext]' }, }, }, { test: /\.(svg)(\?.*)?$/, use: { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]' }, }, }, ],
在上例中將 png
、jpg
、jpeg
、gif
、webp
文件交給 url-loader
處理,而 svg
仍由 file-loader
處理。這樣作的理由是: DataURL
內聯 svg 會破壞 sprite 系統
(將多個 svg 合爲一張使用的技術) 中使用的Fragment Identifiers,所以不將 svg 轉爲 DataURL
。
給 url-loader
設定匹配規則後,配置 name
和 limit
選項。url-loader
的 name
選項與 file-loader
的 name
做用是相同的,就再也不累述。
limit
是指定以字節(byte) 爲單位的文件最大尺寸。當文件尺寸小於等於 limit
所設的值,那文件將會被轉爲 DataURL
。相反,若文件尺寸大於 limit
時,則使用備用 loader
。默認備用 loader
是 file-loader
。能夠設定 fallback
選項來修改備用 loader
。
{ loader: 'url-loader', options: { limit: 10000, name: 'img/[name].[hash:8].[ext]' fallback: 'file-loader' } }
limit
的選值不易過大,能夠設爲 10240
(10KB)或 10000
,也能夠根據項目實際狀況進行調整。
如今來測試 limit
的效果。unix 系統能夠在終端使用 ls -l
命令來查看文件信息:
➜ getting-started-loader-images git:(master) ✗ cd ./src/images ➜ images git:(master) ✗ ls -l total 6144 -rwxr-xr-x 1 anran staff 173596 May 28 17:41 01.jpg -rwxr-xr-x 1 anran staff 761560 May 28 17:41 02.png -rwxr-xr-x 1 anran staff 542065 May 28 17:41 03.jpg -rwxr-xr-x 1 anran staff 376562 May 28 17:41 04.png -rwxr-xr-x 1 anran staff 510812 May 28 17:41 05.png -rw-r--r-- 1 anran staff 760117 May 28 17:41 06.jpg -rw-r--r--@ 1 anran staff 6943 May 30 13:54 webpack.jpg -rw------- 1 anran staff 647 May 28 21:33 webpack.svg
從輸出的信息能夠看到 webpack.svg
(647B) 和 webpack.jpg
(6943B) 的文件尺寸都低於設定的 limit: 10000
。因爲 svg
文件不經過 url-loader
處理,那按照預想它將會被輸出到 /dist/img
中。webpack.jpg
能夠被 url-loader
,那編譯後應該被嵌入到 js
代碼中。
從新編譯測試一下:
➜ getting-started-loader-images git:(master) ✗ npm run build > getting-started-loader@1.0.0 build /Users/anran/project_my/webpack-example/getting-started-loader-images > rimraf ./dist && webpack --config ./webpack.config.js Hash: 8d2e8c8220e86d46e388 Version: webpack 4.43.0 Time: 692ms Built at: 05/30/2020 2:08:46 PM Asset Size Chunks Chunk Names css/index.css 1.63 KiB 0 [emitted] main img/01.a8e7ddb2.jpg 170 KiB [emitted] img/02.46713ed3.png 744 KiB [emitted] [big] img/03.70b4bb75.jpg 529 KiB [emitted] [big] img/04.b7d3aa38.png 368 KiB [emitted] [big] img/05.875a8bc2.png 499 KiB [emitted] [big] img/06.5b8e9d1e.jpg 742 KiB [emitted] [big] img/webpack.258a5471.svg 647 bytes [emitted] index.html 990 bytes [emitted] js/bundle.js 10.5 KiB 0 [emitted] main Entrypoint main = css/index.css js/bundle.js [0] ./src/css/style.css 39 bytes {0} [built] [1] ./src/js/index.js + 4 modules 10.1 KiB {0} [built] | ./src/js/index.js 881 bytes [built] | ./src/js/log.js 60 bytes [built] | ./src/images/06.jpg 63 bytes [built] | ./src/images/webpack.jpg 9.08 KiB [built] | ./src/images/webpack.svg 68 bytes [built] + 1 hidden module 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: https://webpack.js.org/configuration/mode/ WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). This can impact web performance. Assets: img/04.b7d3aa38.png (368 KiB) img/03.70b4bb75.jpg (529 KiB) img/05.875a8bc2.png (499 KiB) img/02.46713ed3.png (744 KiB) img/06.5b8e9d1e.jpg (742 KiB) WARNING in webpack performance recommendations: You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application. For more info visit https://webpack.js.org/guides/code-splitting/ Child HtmlWebpackCompiler: 1 asset Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0 [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html 1.37 KiB {0} [built] Child mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!src/css/style.css: Entrypoint mini-css-extract-plugin = * [0] ./node_modules/css-loader/dist/cjs.js!./src/css/style.css 2.98 KiB {0} [built] [3] ./src/images/01.jpg 63 bytes {0} [built] [4] ./src/images/02.png 63 bytes {0} [built] [5] ./src/images/03.jpg 63 bytes {0} [built] [6] ./src/images/04.png 63 bytes {0} [built] [7] ./src/images/05.png 63 bytes {0} [built] + 2 hidden modules
輸出目錄:
. ├── dist │ ├── css │ │ └── index.css │ ├── img │ │ ├── 01.a8e7ddb2.jpg │ │ ├── 02.46713ed3.png │ │ ├── 03.70b4bb75.jpg │ │ ├── 04.b7d3aa38.png │ │ ├── 05.875a8bc2.png │ │ ├── 06.5b8e9d1e.jpg │ │ └── webpack.258a5471.svg │ ├── index.html │ └── js │ └── bundle.js ├── package-lock.json ├── package.json ├── src │ ├── css │ │ └── style.css │ ├── images │ │ ├── 01.jpg │ │ ├── 02.png │ │ ├── 03.jpg │ │ ├── 04.png │ │ ├── 05.png │ │ ├── 06.jpg │ │ ├── webpack.jpg │ │ └── webpack.svg │ ├── index.html │ └── js │ ├── index.js │ └── log.js └── webpack.config.js
從新打開 /dist/index.html
後能夠在瀏覽器控制檯看到以下輸出的信息:
在 HTML
中有一種常見的狀況是:在模板中經過相對路徑引入圖片、腳本等資源時,發現引入的資源都沒有被打包進去。
爲何會發生這種狀況呢?原來是 webpack 默認不會處理 html
中的資源引入。爲了能使 HTML
能經過相對路徑引入資源,主要有 3 種解決的方案:
如今項目中 /src/index.html
是做爲 html-webpack-plugin
的模板,在模板中可使用 lodash template 語法(如下簡稱模板語法)來插入內容。語法格式爲: <%= value %>
好比在 src/index.html
的模板中插入圖片:
<div class="panels"> <!-- 其餘代碼略... --> <div class="panel panel6"> <img class="img" src="<%= require('./images/06.jpg').default %>" alt=""> </div> </div>
/* 爲了使頁面美觀,再添加一些樣式 */ .panel6 { position: relative; overflow: hidden; background-color: #061927; } .panel6 .item { position: relative; } .panel6 .img { position: absolute; height: 100%; transform: scale(1); transition: transform 0.4s 0.6s; } .panel6.open { flex: 2; } .panel6.open .img { transform: scale(1.2); }
上例將經過 require()
函數引入圖片。webpack 引入圖片時默認是經過 ESModule
來引入的,所以解析的結果大體爲 {default: module}
這種形式。所以後面還須要再加一個 default
。這樣就能正確的引入資源啦。
第二種就是新增一個靜態目錄 static
(或者叫 public
)。
HTML
默認不是引用不了源碼目錄上的資源嗎?那我就直接將資源輸出到 dist
目錄上。模板引用資源時直接引入輸出後的文件不就行啦?
copy-webpack-plugin 能夠完成這種遷移的功能。它將從 form
處複製文件/文件夾,複製到 to
(默認是 webpack 的輸出目錄)中。如今來安裝它:
npm i -D copy-webpack-plugin
新增 static
目錄,並添加一些測試文件:
. ├── package.json ├── src │ ├── css │ │ └── style.css │ ├── images │ │ ├── 01.jpg │ │ ├── 02.png │ │ ├── 03.jpg │ │ ├── 04.png │ │ ├── 05.png │ │ ├── 06.jpg │ │ ├── webpack.jpg │ │ └── webpack.svg │ ├── index.html │ ├── js │ │ ├── index.js │ │ └── log.js + │ └── static + │ └── images + │ ├── 06.jpg + │ ├── webpack.jpg + │ └── webpack.svg └── webpack.config.js
如今將 src/static/images
的全部文件(無論代碼裏有沒有引入這些文件)都複製到 dist/img
中。
// webpack.config.js { plugins: [ new CopyPlugin({ patterns: [ { from: path.resolve(__dirname, 'src/static/images'), to: path.resolve(__dirname, 'dist/img') }, ], }), ], }
若是你不只想要複製圖片還想要複製其餘諸如 css 樣式表、js 腳本甚至是 excel 文件到輸出目錄的話。那能夠考慮將 static
目錄與 dist 目錄進行合併,將 static
和 dist
下的目錄名保持一致。
好比將 static
的下 images
文件夾改名爲圖片輸出目錄 img
,這樣打包後會輸出到同一個目錄中:
// webpack.config.js { plugins: [ new CopyPlugin({ patterns: [ // 若是隻傳 string 的話,那這個 string 至關於 from // path.resolve(__dirname, 'src', 'static'), // to 默認是 `compiler.options.output`, 也就是 dist 目錄 // { // from: path.resolve(__dirname, 'src/static'), // to: '' // }, // 當前配置中與上面兩例等價 { from: path.resolve(__dirname, 'src/static'), to: path.resolve(__dirname, 'dist') }, ], }), ], }
若指定文件/文件夾不想複製到 dist
中,還可使用 globOptions.ignore
來忽略:
// webpack.config.js { plugins: [ new CopyPlugin({ patterns: [ { from: path.resolve(__dirname, 'src/static'), to: path.resolve(__dirname, 'dist') globOptions: { ignore: ['/**/webpack.jpg', '/**/img/webpack.svg'], } }, ], }), ], }
從新修改模板中的圖片的引入的路徑,使其指向輸出目錄的 img
:
<div class="panel panel6"> <img class="img" src="./img/06.jpg" alt=""> <p class="item index">VI</p> </div>
編譯後就能看到圖片正確被引用了。
最後一種是安裝 html-loader
,讓 webapck 能夠處理 html
資源的引入。
npm install -D html-loader
rules: [ { test: /\.html$/i, loader: 'html-loader', }, // 省略其餘 rule... ]
配置 html-loader
後,HTML
訪問相對路徑的資源就由 html-loader
來進行引入。將模板中的路徑改成源碼相對路徑:
<div class="panel panel6"> <img class="img" src="./images/06.jpg" alt=""> <p class="item index">VI</p> </div>
在實際編譯時,<img class="img" src="./images/06.jpg" alt="">
中 src
的值會被轉爲 require('./images/06.jpg')
,經過 webpack 引入後再將編譯後的結果傳入圖片的 src
屬性中。
此時從新編譯後就能夠正確引入了。但配置 html-loader
的方法會與方法二訪問靜態目錄資源有點衝突。配置 html-loader
後就不能經過 ./
、../
這種相對路徑來訪問資輸出目錄的資源了。
若是咱們配置了 html-loader
的同時又還想訪問靜態資源怎麼辦呢?這時能夠經過根路徑 /
逐層來訪問,這樣 html-loader
就不會處理這種路徑:
<div class="panel panel6"> <img class="img" src="/img/06.jpg" alt=""> <p class="item index">VI</p> </div>
如今問題又來了,若咱們經過根路徑來訪問資源的話,那就不能單純地打開文件來在瀏覽器查看效果了。由於直接打開文件到瀏覽器上,是經過 file://
協議打開的。瀏覽器實際上訪問的路徑是文件的絕對地址。
好比筆者打開文件後,瀏覽器地址欄展現的 url 是: file:///Users/anran/project_my/webpack-example/getting-started-static-assets/dist/index.html
。如今經過根路徑訪問資源,須要瀏覽器補全爲完整的 URL,通過瀏覽器補全後絕對路徑是 file:///img/06.jpg
。這樣路徑都是錯誤的天然就訪問不到想要的資源啦。
若是有寫過 SPA(單頁面應用)
項目的朋友應該很熟悉。將 SPA
項目打包後直接訪問 index.html
頁面是空白的,這種狀況多半就是從根路徑引入資源失敗而引發的。
這個問題解決的辦法也很簡單,就是將編譯後的項目部署到服務器上,直接經過服務器進行訪問,問題就迎刃而解了。爲何這樣就能夠解決了呢?
好比筆者的網站域名是 anran758.github.io
,如今將頁面部署到服務器後,直接在瀏覽器訪問 https://anran758.github.io/
,實際上訪問的是 /dist/index.html
文件。html
經過相對路徑訪問/img/06.jpg
,那補全後圖片的路徑就是 https://anran758.github.io/img/06.jpg
。這樣天然就能訪問資源啦。
咱們不妨經過 Node.js
起一個本地服務器測試一下。在 /dist
同級目錄上新建一個 server.js
腳本,添加以下代碼:
const express = require('express'); const config = require('./webpack.config'); const app = express(); const PORT = 8001; // 設置靜態資源入口 app.use(express.static(config.output.path)); // 監聽端口 app.listen(PORT, (err) => { if (err) { console.log(err); return; } console.log('Listening at http://localhost:' + PORT + '\n'); })
上例腳本代碼是經過 express 快速搭建一個本地服務器,將服務器靜態資源入口設爲 webpack.config.js
的輸出目錄(也就是 /dist
),隨後啓動服務器。
express
是基於 Node.js
的 web 框架,要使用它以前須要安裝依賴:
npm install -D express
package.json
中添加個快捷入口,並在終端運行該腳本:
{ "scripts": { // 其餘腳本.. "test:prod": "node server.js" }, }
➜ getting-started-static-assets git:(master) ✗ npm run test:prod > getting-started-loader@1.0.0 test:prod /Users/anran/project_my/webpack-example/getting-started-static-assets > node server.js Server is running at http://localhost:8001 . Press Ctrl+C to stop.
打開 http://localhost:8001 後就能看到圖片資源正確被引用了。
好啦,如今 webpack 基礎篇也到了尾聲。咱們對上述知識作一個簡單的小結:
webpack 是一個靜態模塊打包工具,它本體雖然只支持處理 javascript 的模塊,但能夠經過 loader 讓 webpack 完成本來它不能處理的功能。
webpack 的提供插件的功能,插件能夠針對某種需求作特定處理,好比自動給 html
插入資源。
除了靜態目錄的文件外,咱們發現 webpack 輸出的文件都是有依賴關係的。爲何會這麼說呢?仔細看看 webpack 處理的邏輯就能想清楚了:
webpack 從程序的入口 /src/js/index.js
開始處理,入口文件引入了 style.css
,而 style.css
內又引用了圖片資源。而後 HTML
再經過 webpack 插件引入模板,再將這些資源插入模板中。這就是文件的依賴關係,這些依賴關係最終會生成一個依賴圖(Dependency Graph)。
想必看到這裏的各位對 webpack 都有了個比較清晰的概念了吧?固然這只是一個開始,後面還有一些高級的概念在本文中因爲篇幅的限制沒法一併理清。若對筆者 webpack 的筆記感興趣的話能夠繼續關注此係列的更新,下一篇將圍繞開發環境進行梳理。
參考資料:
下面是一個普通的 web 寫做交流羣,感興趣的朋友歡迎進來一塊兒交流: