經過如何利用webpack來提高前端開發效率(一)的學習,咱們已經可以經過webpack
的loader
和piugin
機制來處理各類文件資源。細心的小夥伴們發現了缺乏了對字體文件和HTML
中<img>
標籤的資源處理,那讓咱們先來解決這個問題。css
接上篇文章,咱們的目錄結構,如圖所示: html
首先是對字體文件的處理,修改webpack.config.js
// webpack.config.js
// 新增對字體的loader
{
test: /\.(eot|woff|woff2|ttf)$/,
use: [{
loader: 'url-loader',
options: {
name: '[name].[hash:7].[ext]',
limit: 8192,
outputPath: 'font', // 打包到 dist/font 目錄下
}
}]
},
複製代碼
如何咱們從網上隨意下載了一種字體,放置於src
文件夾下,並修改src/index.html
前端
<!-- src/index.html -->
<!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>my-webpack</title>
</head>
<body>
<h1>webpack大法好!!前端大法好!!</h1>
</body>
</html>
複製代碼
在index.scss
中引入字體node
/* src/index.scss */
/* 添加如下樣式 */
@font-face {
font-family: 'myFont';
src: url('./font/ZaoZiGongFangQiaoPinTi-2.ttf');
}
h1 {
font-family: 'myFont';
}
複製代碼
在此以前,每次從新打包都要刪除dist
文件夾,實在是麻煩,如今咱們能夠藉助clean-webpack-plugin
,它可以在每次打包時刪除指定的文件夾,咱們在命令行執行 npm i clean-webpack-plugin -D
webpack
修改webpack.config.js
git
// webpack.config.js
// 新增如下引入
const CleanWebpackPlugin = require('clean-webpack-plugin');
// 新增如下插件
plugins: [
new CleanWebpackPlugin(['dist']) // 在最新的v2版本中,若是默認刪除dist文件夾,只需new CleanWebpackPlugin()
],
複製代碼
隨後在命令行執行npm run build
,咱們的dist
文件夾會被自動刪除,並輸出如下結果,能夠看到咱們雖然成功打包了字體文件,但字體文件是在太大,連webpack
都發出了警告[big]
。github
CDN
加速font-spider
對字體進行壓縮咱們實踐一下第三種方案,也是我推薦的方案 在命令行依次執行web
npm i font-spider -D
font-spider ./dist/index.html
複製代碼
能夠看到將近4MB的字體文件體積瞬間壓縮至不足6KB!!!而頁面效果和以前如出一轍。 npm
而對於HTML
文檔中
<img>標籤的引入問題
,咱們須要藉助
html-loader
,它能將
HTML
文檔中
img.src
解析成
require
,從而實現引入圖片,話很少說,咱們直接看效果。在命令行執行
npm i html-loader -D
修改如下文件json
// webpack.config.js
// 新增對html的loader
{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src'] // img表明解析標籤,src表明要解析的值,以key:value形式存在於attrs數組中
}
}
}
複製代碼
<!-- src/index.html -->
<body>
+ <img src="./leaf.png" alt="">
</body>
複製代碼
在命令行執行npm run build
,查看dist/index.html
,看來已經成功啦
設想若是咱們的入口文件很大(包含了全部的業務邏輯代碼),就會形成首屏加載變慢,用戶體驗感降低。 這裏咱們從兩個方面解決:
src
目錄下新增dynamic.js
// dynamic.js
export default () => {
console.log('Im dynamically loaded.');
}
複製代碼
修改如下文件
<!-- src/index.html -->
<body>
+ <button id="btn">點擊我,動態加載dynamic.js</button>
</body>
複製代碼
// src/index.js
// 新增如下內容
const btn = document.getElementById('btn');
// 點擊按鈕,動態加載dynamic.js
btn.onclick = () => {
import(/* webpackChunkName: "dynamic" */ './dynamic.js').then(function (module) {
const fn = module.default;
fn();
})
}
複製代碼
執行npm run build
,能夠看到
/* webpackChunkName: "dynamic" */
,則是
能夠得出得結論是:設置
ChunkName
爲
"dynamic"
是必要的,不然打包完成會是以自動分配的、可讀性不好的
id
命名的
JS
文件。且沒有
Chunk Names
標識。
如今咱們打開dist/index.html
,此時
dynamic.js
至此,咱們成功實現了動態加載。
回頭看咱們的webpack.config.js
,不知不覺就寫了這麼多代碼,鑑於咱們在開發實際項目時,是開發和生產兩套工做模式,各司其職,咱們不如作個了斷,分離配置。
命令行執行npm i webpack-merge cross-env -D
webpack-merge
能夠合併webpack配置項,cross-env
能夠設置及使用環境變量。
新增webpack.base.js
,提供基本的webpack loader plugin
配置
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const pathResolve = (targetPath) => path.resolve(__dirname, targetPath);
const devMode = process.env.NODE_ENV !== 'production';
// 在node中,有全局變量process表示的是當前的node進程。
// process.env包含着關於系統環境的信息。
// NODE_ENV是用戶一個自定義的變量,在webpack中它的用途是來判斷當前是生產環境或開發環境。
// 咱們能夠經過 cross-env 將 NODE_ENV=development 寫入 npm run dev的指令中,從而注入NODE_ENV變量。
module.exports = {
entry: {
index: pathResolve('js/index.js')
},
output: {
path: pathResolve('dist'),
},
module: {
rules: [
{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src']
},
},
},
{
test: /\.(eot|woff|woff2|ttf)$/,
use: [{
loader: 'url-loader',
options: {
name: '[name].[hash:7].[ext]',
limit: 8192,
outputPath: 'font',
},
}],
},
{
test: /\.(sa|sc|c)ss$/,
use: [
devMode ? 'style-loader' : { // 若是處於開發模式,則無需再外鏈CSS,直接插入到<style>標籤中
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'css-loader',
'postcss-loader',
'sass-loader',
],
},
{
test: /\.(png|jpg|jpeg|svg|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192,
name: '[name].[hash:7].[ext]',
outputPath: 'img',
},
}],
},
],
},
plugins: [
new htmlWebpackPlugin({
minify: {
collapseWhitespace: true, // 移除空格
removeAttributeQuotes: true, // 移除引號
removeComments: true // 移除註釋
},
filename: pathResolve('dist/index.html'),
template: pathResolve('src/index.html'),
})
]
};
複製代碼
新增webpack.dev.js
,服務於開發模式下
const path = require('path');
const webpack = require('webpack');
const base = require('./webpack.base.js');
const { smart } = require('webpack-merge');
const pathResolve = (targetPath) => path.resolve(__dirname, targetPath);
module.exports = smart(base, {
mode: 'development',
output: {
filename: 'js/[name].[hash:7].js'
},
devServer: {
contentBase: pathResolve('dist'),
port: '8080',
inline: true,
historyApiFallback: true,
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()
]
})
複製代碼
新增webpack.prod.js
,服務於生產模式下
const path = require('path');
const base = require('./webpack.base.js');
const { smart } = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const pathResolve = (targetPath) => path.resolve(__dirname, targetPath);
module.exports = smart(base, {
mode: 'production',
devtool: 'source-map', // 會生成對於調試的完整的.map文件,但同時也會減慢打包速度,適用於打包後的代碼查錯
output: {
filename: 'js/[name].[chunkhash:7].js',
chunkFilename: 'js/[name].[chunkhash:7].js',
},
plugins: [
new CleanWebpackPlugin(['dist']),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:7].css',
}),
],
});
複製代碼
相應的,package.json
也須要修改
// 新增如下兩條命令
// cross-env 決定運行環境 --config 決定運行哪一個配置文件
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.prod.js "
複製代碼
緩存在前端的地位毋庸置疑,正確的利用緩存就能極大地提升應用的加載速度和性能。 webpack
利用了hash
值做爲文件名的組成部分,能有效利用緩存。當修改文件,從新打包時,hash值就會改變,致使緩存失效,HTTP請求從新拉取資源。
而webpack
有三種hash處理策略,分別是:
屬於項目工程級別的,即每次修改任何一個文件,全部文件名的hash
值都將改變。因此一旦修改了任何一個文件,整個項目的文件緩存都將失效。如將整個項目的filename
的命名策略改成name.[hash:7]
(:7的意思是從完整hash值中截取前七位),咱們能夠看到,打包後的文件hash值是同樣的,因此對於沒有改變的模塊而言,hash也被更新了,致使緩存失效了。
chunkhash
根據不一樣的入口文件(Entry
)進行依賴文件解析、構建對應的chunk
,生成對應的哈希值。如將整個項目filename
的命名策略改成name.[chunkhash:7]
,咱們能夠看到Chunk Names
爲"index"
的文件hash值一致,而不一樣chunk
的hash
值不一樣。這也就避免了修改某個文件,整個工程hash值都將改變的狀況。
但問題隨之而來,index.scss
是做爲模塊導入到index.js
中的,其chunkhash
值是一致的,只要其中之一改變,與其關聯的文件chunkhash
值也會改變。這時候就要用到contenthash
,它是根據文件的內容計算,該文件的內容改變了,contenthash值纔會改變。咱們將css文件的命名策略改成name.[contenthash:7]
,並修改src/index.js
,不改動其餘文件,再次打包,發現:
字面意思理解爲從一棵樹上把葉子搖晃下來,這樣數的重量就減輕了,類比程序,就如同從咱們的應用上刪除沒用的代碼,從而減小體積。借於ES6
的模塊引入是靜態分析的,故而webpack
能夠在編譯時正確判斷到底加載了什麼代碼,即沒有被引用的模塊不會被打包進來,減小咱們的包大小,縮小應用的加載時間,呈現給用戶更佳的體驗。那麼怎麼使用呢?
新建src/utils.js
// src/utils.js
const square = (num) => num ** 2;
const cube = num => num * num * num;
// 導出了兩個方法
export {
square,
cube
}
複製代碼
新建src/shake.js
// src/shake.js
import { cube } from './utils.js';
// 只使用了cube方法
console.log('cube(3) is' + cube(3));
複製代碼
在webpack.base.js
中新增入口文件shake.js
entry: {
+ shake: pathResolve('src/shake.js')
},
複製代碼
命令行執行npm run build
,查看打包後的shake.js
,並無發現square
方法沒有被打包進來,說明tree-shaking
起做用了。 而這一切都是webpack
在production
環境下自動爲咱們實現的。
字面意思爲拆分代碼塊,默認狀況下它將只會影響按需加載的代碼塊,由於改變初始化的代碼塊將會影響HTML
中運行項目須要包含的script
標籤。還記得咱們在src/index.js
中動態引入了src/dynamic.js
嗎,最終dynamic.js
被獨立打包,就是歸功於splitChunks
。
在實際生產中,咱們常常會引入第三方庫(JQuery
,Lodash
),每每這些第三方庫體積高達幾十KB摻雜在業務代碼中,而且不會像業務代碼同樣常常更新,這時候咱們就須要將他們拆分出來,既能保持第三方庫持久緩存,又能縮減業務代碼的體積。
修改webpack.prod.js
// 在module.exports中新增以下內容
optimization: {
runtimeChunk: {
name: 'manifest', // 被注入了webpackJsonp的定義及異步加載相關的定義,單獨打包模塊信息清單,利於緩存
},
splitChunks: {
cacheGroups: { // 緩存組,默認將全部來源於node_modules的模塊分配到叫作'venders'的緩存組,全部引用超過兩次的模塊分配到'default'緩存組.
vendor: {
chunks: "all", // all, async, initial 三選一, 插件做用的chunks範圍,推薦all
test: /[\\/]node_modules[\\/]/, // 緩存組所選擇的的模塊範圍
name: "vendor", // Chunk Names及打包出來的文件名
minChunks: 1, // 引用次數>=1
maxInitialRequests: 5, // 頁面初始化時加載代碼塊的請求數量應該<=5
minSize: 0, // 代碼塊的最小尺寸
priority: 100, // 緩存優先級權重
},
}
}
},
複製代碼
命令行執行npm i lodash -S
修改src/index.js
// 新增如下內容
import _ from 'lodash';
複製代碼
執行npm run build
,能夠看到優化前lodash
被打包進index.js
,優化後lodash
被打包進vendor.js
。
每每在CSS代碼中,存在不少咱們沒有用到的樣式,它們是冗餘的,咱們須要將它們剔除,並壓縮剩餘的CSS樣式,以減小CSS文件體積。
在命令行執行npm i glob optimize-css-assets-webpack-plugin purifycss-webpack purify-css -D
修改webpack.prod.js
// 新增如下引入
const glob = require('glob'); // 匹配所需文件
const PurifyCssWebpack = require('purifycss-webpack'); // 去除冗餘CSS
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); // 壓縮CSS
// 新增如下插件
new PurifyCssWebpack({
paths: glob.sync(pathResolve('src/*.html')) // 同步掃描全部html文件中所引用的css,並去除冗餘樣式
})
// 新增如下優化
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({}) // 壓縮CSS
]
}
複製代碼
執行npm run build
CSS
,並壓縮至一行。接下來,咱們須要壓縮
JS
代碼。 因爲咱們使用的是
uglifyjs-webpack-plugin
,它須要ES6的支持,因此咱們先讓工程支持ES6的語法。 Babel 是一個 JavaScript 編譯器。它能把下一代 JavaScript 語法轉譯成ES5,以適配多種運行環境。
@babel/core
提供了babel的轉譯API,如babel.transform
等,用於對代碼進行轉譯。像webpack
的babel-loader
就是調用這些API來完成轉譯過程的。
@babel/preset-env
能夠根據配置的目標瀏覽器或者運行環境來自動將ES2015+
的代碼轉換爲ES5
。
先在命令行執行npm i @babel/core @babel/preset-env babel-loader @babel/plugin-syntax-dynamic-import -D
新建.babelrc
文件
{
"presets": [ // 配置預設環境
["@babel/preset-env", {
"modules": false
}]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import" // 處理src/index.js中動態加載
]
}
複製代碼
修改webpack.base.js
// 新增js的解析規則
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
exclude: /node_modules/
},
複製代碼
而後命令行執行npm i uglifyjs-webpack-plugin -D
修改webpack.prod.js
// 新增如下引入
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
// 新增如下優化
optimization: {
minimizer: [
+ new UglifyJsPlugin({ // 壓縮JS
cache: true,
parallel: true,
sourceMap: true
})
]
}
複製代碼
執行npm run build
,能夠看到打包的文件體積大大減小,大功告成,JS
也被壓縮了。
以index.html
爲例,咱們能夠打開Chrome的開發者工具,選擇More tools,點擊Coverage面板,能夠看到JS、CSS等文件的使用率,配合咱們定製的webpack配置進行極致優化。
有時候,咱們須要同時構建多個頁面,藉助html-webpack-plugin
,只需在plugins
中添加新頁面的配置項。
新增src/main.html
<!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>main page</title>
</head>
<body>
<h1>I am Main Page</h1>
</body>
</html>
複製代碼
修改webpack.base.js
// 修改如下內容
plugins: [
new htmlWebpackPlugin({ // 配置index.html
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true
},
filename: pathResolve('dist/index.html'),
template: pathResolve('src/index.html'),
chunks: ['manifest', 'vendor', 'index', ] // 配置index.html須要用的chunk塊,即加載哪些JS文件,manifest模塊管理的核心,必須第一個進行加載,否則會報錯
}),
new htmlWebpackPlugin({ // 配置main.html
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true
},
filename: pathResolve('dist/main.html'),
template: pathResolve('src/main.html'),
chunks: ['manifest', 'shake'] // 配置index.html須要用的chunk塊,加載manifest.js,shake.js
}),
],
複製代碼
執行npm run build
,成功構建了index.html
,main.html
。
至此,咱們擺脫了第三方腳手架的的禁錮,按部就班的搭建了屬於本身的前端流程工具,作到了即改即用,功能俱全,快速便捷,複用性強的特色。但願小夥伴能親自動手,別總是紙上談webpack
,要理解它的構建、優化原理,駕輕就熟得融入到本身的工程項目中,拒絕再用之前繁瑣,不規範的開發流程,不作「CV工程師」,建立屬於本身的知識體系、工做流程,提升前端的開發效率。
最後,本項目源碼已部署在Github
上,並增長了許多額外優化(less
的支持,ESLint
檢測,針對圖片格式的壓縮...),讓你們能夠直接下載體驗,並協助項目開發,往後也會持續維護,但願小夥伴們能夠互相學習,提提建議。
GitHub地址:easy-frontend 一個快速,簡單,易用的前端開發效率提高工具
Star該項目,就是大家對我最大的的鼓勵!!
前端路上,不忘初心,祝你們早日發財!!