webpack作為現代前端開發最火的模組打包工具,已經成為了前端工程師必備的技能之一。
是:
前端資源構建工具;
靜態模組打包器;
webpack從入口檔案開始,根據模組的依賴關係進行分析,然後生成加工後的靜態資源;
將高階語法轉換成相容性高的通用語法;
引入chunk塊概念–打包–>bundles;
基於nodejs平臺的工具,遵循commonjs模組化規範
本章重點講解:
預備技能
環境引數
npm i -D webpack webpack-cli
webpack-cli是做什麼的?
如果你使用 webpack v4+ 版本,並且想要在命令列中呼叫 webpack,你還需要安裝 CLI。
通過檢視webpack指令碼,得出webpack命令,本質就是呼叫了webpack-cli來實現的。webpack4+將這個功能單獨拆解出來了。
const runCli = cli => {
const path = require("path");
const pkgPath = require.resolve(`${cli.package}/package.json`);
// eslint-disable-next-line node/no-missing-require
const pkg = require(pkgPath);
// eslint-disable-next-line node/no-missing-require
require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
};
/** @type {CliOption} */
const cli = {
name: "webpack-cli",
package: "webpack-cli",
binName: "webpack-cli",
installed: isInstalled("webpack-cli"),
url: "https://github.com/webpack/webpack-cli"
};
if (!cli.installed) {
const path = require("path");
const fs = require("graceful-fs");
const readLine = require("readline");
const notify =
"CLI for webpack must be installed.\n" + ` ${cli.name} (${cli.url})\n`;
console.error(notify);
// ...
} else {
runCli(cli);
}
在cli中執行入口檔案。
# 因為沒有全域性安裝webpack,所以需要制定本地webpack
./ node_modules/.bin/webpack ./src/index.js
# 如果global安裝了
webpack ./src/index.js
通過npm指令碼執行-package.json:
scripts: {
// npm 會主動去查詢環境中的webpack
"webpack": "webpack ./src/index.js"
}
上面全部使用webpack的預設設定。
在目錄下新增:webapck.config.js,webpack預設會在專案根目錄下查詢這個檔案,當作自定義設定項。
module.exports = {
mode: 'development'
}
如果放在其他的地址,需要在指令碼中顯式指定。如webpack/dev.js
scripts: {
"webpack": "webpack --config ./webpack/dev.js ./src/index.js"
}
其他的設定項,可以放到組態檔,也可以當作指令碼引數傳入。【推薦:複雜的放到組態檔】
原始程式碼:
// demo1.js
const greeting = 'Hello World!';
console.log(greeting);
// index.js
import './demo1'
執行指令碼webpack後:
npm run webpack
預設情況下,輸出檔案為:dist/main.js
node dist/main.js
# 成功輸出
# Hello World!
結論:打包js成功了!
webpack預設支援js/json檔案,可以省略檔案字尾。
如果想要打包css檔案呢?
// style1.less
body {color: red}
// index.js
import './style'
報錯如下:
[no extension] src/style1 doesn't exist
[.js] src/style1.js doesn't exist
[.json] src/style1.json doesn't exist
[.wasm] src/style1.wasm doesn't exist
[as directory] src/style1 doesn't exist
用所有預設支援的格式去匹配,都找不到該檔案。
然後,指明檔案字尾:
import './style1/css'
報錯如下:
Module parse failed: Unexpected token.
You may need an appropriate loader to handle this file type...
結論:需要安裝loader
需要藉助loader,設定webpack.config.js
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
當webpack匹配到css結尾的模組時:
使用html-webpack-plugin外掛:
npm i -D html-webpack-plugin
使用:
const htmlWebpackPlugin = require('html-webpack-plugin')
// plugin: 下載 引入 使用
plugins: [
// html-webpack-plugin
new htmlWebpackPlugin()
]
執行:npm run webpack
結果:在output指定目錄下,生成了一個index.html檔案。並且將bundle檔案–main.js插入到了script標籤中,預設將script放在head中。
如果在css中引入了圖片資源:
background-image: url(./images/image.png);
那麼需要用到url-loader(用來處理css檔案中url相關圖片檔案的)和file-loader【看情況安裝】:
{
test: /\.png$/,
loader: 'url-loader',
options: {
// 預設情況limit沒有限制;下面限制為:當圖片小於1kb,才會用url-loader進行處理,將圖片轉化為base64編碼字串【優點:可以減少http請求;缺點:加大js檔案體積】
// 當圖片大雨1kb時,url-loader就需要依賴file-loader包,將圖片直接copy到dist目錄下,預設資源名字會變成hash名字
limit: 1 * 1024
}
}
如果在html中引入image標籤,上面的處理就會出問題了,因為,例如file-loader會修改圖片的名字,此時,html會找不到圖片資源,我們需要用到html-loader。
{
test: /\.html$/,
// 處理html中的img標籤資源,負責將img資源引入,然後才能被url-loader處理。
// html-loader引入圖片是es6規範的,解析時會報錯
// 解決辦法:將html-loader的esModule設定為false
loader: 'html-loader',
options: {
esModule: false
}
}
不需要做壓縮等處理,只需要,複製並輸出。
比如字型檔案:iconfont.ttf iconfont.svg
方案:用排除法匹配其他資源,然後用file-loader處理。匹配到的資源,將會用hash重新命名後,輸出到dist目錄
rules: [
{
exclude: /\.(html|css|js)/,
loader: 'file-loader'
}
]
開發伺服器:用來自動構建、開啟瀏覽器,並自動重新整理等功能。大大提升了開發效率。
// 開發伺服器只在記憶體中編譯打包,沒有任何輸出
// 用npx webpack serve
devServer: {
contentBase: './dist',
// 啟動gzip
compress: true,
port: 3000
}
需要安裝webpack-dev-server。
npm i -D webpack-dev-server
當你改動原始碼的時候,webpack就會自動打包,並更新頁面
一般情況下,webpack已經針對不同環境進行了預設的設定設定,只需要用mode去開啟就行了。
所以指令碼上,我們需要傳入不同的引數,或者是建立不同mode的相關config檔案。
// 如果只有一個webpack.config.js檔案,那麼我們可以通過給cli傳入引數
"dist": "webpack --env=dist"
"dev": "webpack --env=dev"
// 使用
const {env} = require('minimist')(process.argv.slice(2), {
string: ['env'],
});
// 然後根據env給webpack的設定項傳入不同的值。
// 或者可以將組態檔指定為:dev.config.js/dist.config.js等,然後在指令碼中使用不同的檔案
"dist": "webpack --config=webpack/dist.config.js"
"dev": "webpack --config=webpack/dev.config.js"
常用的語法檢查工具是eslint。我們可將eslint嵌到webpack中,用eslint-loader【以來esling】
rules: [
{
// 注意:只檢查原始碼,需要排除node_modules中的程式碼
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
// 手動設定不同的規則,或者使用第三方的規則,如:airbnb
// npm i -D eslint-config-airbnb-base eslint-config-import
// 將airbnb設定到package.json中:eslintConfig: {extends: 'airbnb-base'}
options: {
// 自動修復
fix: false
}
}
]
然後執行webpack,如果js程式碼中有些語法不符合airbnb的規範,那麼webpack操作就會失敗,並輸出:錯誤
如果將fix設定為true,那麼錯誤會自動處理,webpack大部分情況不會被打斷,除非eslint處理不了。
正常情況下,原始碼如果使用es6及以上語法,webpack並不會進行轉換,此時若將bundle直接放到低版本瀏覽器(IE)中執行,指令碼會報錯,如:不認識const等等。
那麼我們需要用到babel:
npm i -D babel-loader @babel/core @babel/preset-env @babel/polyfill core-js
rules: [
{
test: /\.js$/,
excludes: /node_modules/,
loader: 'babel-loader',
options: {
// 預設:指示babel做什麼樣的相容性處理,preset-env就是基本處理,將語法轉化為es5及以下相容性強的語法【基本語法】
// presets: '@babel/preset-env'
// 如果用到promise等語法,那上面的preset-env並不能處理,就需要其他的包:@babel/polyfill 在原始碼入口引入就可以了。
// 問題:我只需要做一部分相容性處理,但引入@babel/polyfill體積太大了。。。。那麼就需要做按需載入---core-js。然後不需要引入:@babel/polyfill
presets: [
'@babel/preset-env',
{
// 按需載入
useBuiltIns: 'usage',
// 指定版本
corejs: {version: 3},
// 指定相容到哪個版本的瀏覽器
targets: {chrome: '60', firefox: '60', ie: '9'}
}
]
}
}
]
所以最終方案為:@babel/preset-env + core-js
webpack開啟:
mode: 'prodcution'
設定外掛:
plugins: [
// html-webpack-plugin
new htmlWebpackPlugin({
template: './index.html',
title: 'Demo',
inject: 'body',
minify: {
// 如:移除空格等等
collapseWhitespace: true,
}
})
]
需要考慮哪些點呢?需要考慮如下兩類:
1. 開發環境優化
// html不需要HMR
entry: ['./src/index.js', './index.html']
devtool: 'inline-source-map'
// index.js
if(module.hot) {
// 如果開啟了HMR,則監聽下面模組,如果變了,就只打包下面模組
module.hot.accept('./...js', function(){})
}
2. 生產環境優化
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
// 開啟多程序打包
'tread-loader',
{loader: 'babel-loader', options: {presets: []}}
]
}
]
optmization : {
splitChunk: {
// all的意思:將node_modules中程式碼打包成一個單獨檔案:vendor.js
// 多入口檔案,會共用vendor.js
// 我們還可以將node_modules中包,拆分成單獨的包,例如xlsx太大,可以單獨引入
chunks: 'all'
}
}
plugins: [
new workboxWebpackPlugin.GenerateSW({
// serviceWorker快速啟動
// 刪除舊的serviceWorker
// 生成一個serviceWorker組態檔
clientClaim: true,
skipWaiting: true
})
]
// 入口檔案 index.js中註冊serviceWorker
// 1. eslint預設不認識navigator/window全域性變數,需要改package.json中eslintConfig.env: {browser: true},表示支援瀏覽器的變數
// 2. ==SW的程式碼必須執行在伺服器上==,而不能直接通過file://協定存取。
if('serviceWorker' in navigator) {
window.addEventListener('load', ()=> {
// service-worker.js這個檔案就是上面webpack生成的組態檔
navigator.serviceworker.register('/service-worker.js')
.then((success) => {
console.log(success)
})
.catch((error) => {
console.log(error)
})
})
}
externals: {
// 庫名: npm包名
jquery: 'jQuery'
}