前言:在看本文前,建議你看下,下面這兩篇文章 順便給個贊和github
的贊哦~
-
如果你對
webpack
不是很瞭解,請你關注我之前的文章,都是百星以上star
的高質量文- 9102年:手寫一個React完美版移動端腳手架
- 前端效能優化不完全手冊
- GIT倉庫地址
- 歡迎你關注我的
《前端進階》
專欄 一起突破學習 文章內容都會不定期更新 記得一定要收藏
- 本文書寫於2019年5月17日 未經作者允許不得轉載 使用最新版4.31版本
webpack
-
webpack
用了會上癮,它也是突破你技術瓶頸的好方向,現在基本上任何東西都離不開webpack
,webpack
用得好,什麼next nuxt
隨便上手(本人體會很深),本人蔘考了Vue
腳手架,京東的webpack
優化方案,以及本人的其他方面優化,著重在生產模式
下的構建速度優化提升非常明顯(當然開發環境下也是~),效能提升很明顯哦~ - 本配置完成功能:
- 識別
.Vue
檔案和template模板
-
tree shaking
搖樹優化 刪除掉無用程式碼 - 引入
babel polifill
並且按需載入,識別一切程式碼 - 識別
async / await
和 箭頭函式 -
PWA
功能,熱重新整理,安裝後立即接管瀏覽器 離線後仍讓可以訪問網站 還可以在手機上新增網站到桌面使用 -
preload
預載入資源prefetch
按需請求資源 ,這裡除了dns
預解析外,建議其他的使用按需載入元件,順便程式碼分割,這也是京東的優化方案 - 配置
nginx
,攔截非預期請求(京東的方案) -
CSS
模組化,不怕命名衝突 - 小圖片的
base64
處理 - 檔案字尾省掉j
sx js json
等 - 實現
VueRouter
路由懶載入,按需載入 , 程式碼分割 指定多個路由同個chunkName
並且打包到同個chunk
中 實現程式碼精確分割 - 支援
less sass stylus
等預處理 -
code spliting
優化首屏載入時間 不讓一個檔案體積過大 - 提取公共程式碼,打包成一個chunk
- 每個chunk有對應的
chunkhash
,每個檔案有對應的contenthash
,方便瀏覽器區別快取 - 圖片壓縮
-
CSS
壓縮 - 增加
CSS
字首 相容各種瀏覽器 - 對於各種不同檔案打包輸出指定資料夾下
- 快取babel的編譯結果,加快編譯速度
- 每個入口檔案,對應一個chunk,打包出來後對應一個檔案 也是
code spliting
- 刪除HTML檔案的註釋等無用內容
- 每次編譯刪除舊的打包程式碼
- 將
CSS
檔案單獨抽取出來 - 讓babel不僅快取編譯結果,還在第一次編譯後開啟多執行緒編譯,極大加快構建速度
效能優化沒有盡頭,本人僅表達自己目前掌握的知識點,士別三日,刮目相看:每隔三天,技術就會進步一次
正式開始吧,假設你已經懂什麼是entry output loader plugin
,如果不懂,看我上面的文章哦~
webpack常見配置
// 入口檔案
entry: {
app: './src/js/index.js',
},
// 輸出檔案
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/' //確保檔案資源能夠在 http://localhost:3000 下正確訪問
},
// 開發者工具 source-map
devtool: 'inline-source-map',
// 建立開發者伺服器
devServer: {
contentBase: './dist',
hot: true // 熱更新
},
plugins: [
// 刪除dist目錄
new CleanWebpackPlugin(['dist']),
// 重新穿件html檔案
new HtmlWebpackPlugin({
title: 'Output Management'
}),
// 以便更容易檢視要修補(patch)的依賴
new webpack.NamedModulesPlugin(),
// 熱更新模組
new webpack.HotModuleReplacementPlugin()
],
// 環境
mode: "development",
// loader配置
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
}
]
}
這裡面我們重點關注module
和plugins
屬性,因為今天的重點是編寫loader
和plugin
,需要配置這兩個屬性。
-
webpack
啟動後,在讀取配置的過程中會先執行new MyPlugin(options)
初始化一個MyPlugin
獲得其例項。在初始化compiler
物件後,再呼叫myPlugin.apply(compiler)
給外掛例項傳入compiler
物件。
外掛例項在獲取到 compiler
物件後,就可以通過 compiler.plugin
(事件名稱, 回撥函式) 監聽到 Webpack
廣播出來的事件。
並且可以通過 compiler 物件去操作 webpack。
-
Compiler
物件包含了Webpack
環境所有的的配置資訊,包含options,loaders,plugins
這些資訊,這個物件在Webpack
啟動時候被例項化,它是全域性唯一的,可以簡單地把它理解為Webpack
例項;-
Compilation
對象包含了當前的模組資源、編譯生成資源、變化的檔案等。當 Webpack 以開發模式執行時,每當檢測到一個檔案變化,一次新的
Compilation將被建立。
Compilation物件也提供了很多事件回撥供外掛做擴充套件。通過
Compilation也能讀取到
Compiler` 物件。 -
Compiler 和 Compilation
的區別在於: -
Compiler
代表了整個Webpack
從啟動到關閉的生命週期,而Compilation
只是代表了一次新的編譯。 - 事件流
-
webpack
通過Tapable
來組織這條複雜的生產線。 -
webpack
的事件流機制保證了外掛的有序性,使得整個系統擴充套件性很好。 -
webpack
的事件流機制應用了觀察者模式,和Node.js 中的 EventEmitter
非常相似。
-
1.2 打包原理
- 識別入口檔案
- 通過逐層識別模組依賴。(
Commonjs、amd
或者es6
的import,webpack
都會對其進行分析。來獲取程式碼的依賴) -
webpack
做的就是分析程式碼。轉換程式碼,編譯程式碼,輸出程式碼 - 最終形成打包後的程式碼
- 這些都是
webpack
的一些基礎知識,對於理解webpack的工作機制很有幫助。
腳手架一般都是遵循了commonjs
模組化方案,如果你不是很懂,那麼看起來很費勁,我寫的腳手架,就不使用模組化方案了,簡單粗
暴
- 開始開發環境配置
- 包管理器 使用
yarn
不解釋 就用yarn
- 配置
webpack.dev.js
開發模式下的配置 yarn init -y
-
yarn add webpack webpack-cli
(yarn
會自動新增依賴是線上依賴還是開發環境的依賴)
配置入口
entry: path.resolve(__dirname, '../src/main.js')}
配置輸出目錄
output: {
filename: 'js/[name].[hash:5].js',
path: path.resolve(__dirname, '../dist'),
},
引入Vue
腳手架裡基本配置的loader
,後面的loader
都是往rules
陣列裡加就行了~
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000,
name: 'img/[name]-[hash:5].[ext]',
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name]-[hash:5].[ext]',
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
name: 'media/[name]-[hash:5].[ext]',
}
}
]
}
]
},
有人會問 這麼多我怎麼看啊 別急 第一個url-loader
是處理base64
圖片的,讓低於limit
大小的檔案以base64
形式使用,後面兩個一樣的套路,只是換了檔案型別而已 ,不會的話,先複製過去跑一把?
配置識別.vue
檔案和tempalte
模板 , yarn add vue vue-loader vue-template-compiler
加入loader
{
test:/\.vue$/,
loader:"vue-loader"
}
加入plugin
const vueplugin = require('vue-loader/lib/plugin')
在webpack的plugin中
new vueplugin()即可
入口指定babel-polifill
,vendor
程式碼分割公共模組,打包後這些程式碼都會在一個公共模組
app: ['babel-polyfill', './src/index.js', './src/pages/home/index.js', './src/pages/home/categorys/index.jsx'],
vendor: ['vuex', 'better-scroll', 'mint-ui', 'element-ui']
指定 html
檔案為模板打包輸出,自動引入打包後的js
檔案
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname,'../index.html'),
filename: 'index.html'
}),
]
省掉.vue
的字尾 ,直接配置在module.exports
物件中,跟entry
同級
resolve: {
extensions: ['.js','.json','.vue'],
}
加入識別html
檔案的loader
{
test: /\.(html)$/,
loader: 'html-loader'
}
開啟多執行緒編譯
const os = require('os')
{
loader: 'thread-loader',
options: {
workers: os.cpus().length
}
}
加入babel-loader
加入 babel-loader 還有 解析JSX ES6語法的 babel preset
@babel/preset-env解析es6語法
@babel/plugin-syntax-dynamic-import解析vue的 import按需載入,附帶code spliting功能
{
loader: 'babel-loader',
options: { //jsx語法
presets: ["@babel/preset-react",
//tree shaking 按需載入babel-polifill
["@babel/preset-env", { "modules": false, "useBuiltIns": "false", "corejs": 2 }]],
plugins: [
//支援import 懶載入
"@babel/plugin-syntax-dynamic-import",
//andt-mobile按需載入 true是less,如果不用less style的值可以寫'css'
["import", { libraryName: "antd-mobile", style: true }],
//識別class元件
["@babel/plugin-proposal-class-properties", { "loose": true }],
],
cacheDirectory: true
},
}
在使用上面的babel
配置後 我們躺著就可以用vueRouter
的路由懶載入了
路由懶載入
- 當打包構建應用時,JavaScript 包會變得非常大,影響頁面載入。如果我們能把不同路由對應的元件分割成不同的程式碼塊,然後當路由被訪問的時候才載入對應元件,這樣就更加高效了。
- 結合 Vue 的非同步元件和 Webpack 的程式碼分割功能,輕鬆實現路由元件的懶載入。
- 首先,可以將非同步元件定義為返回一個 Promise 的工廠函式 (該函式返回的 Promise 應該 resolve 元件本身):
- const Foo = () => Promise.resolve({ / 元件定義物件 / })
-
第二,在 Webpack 中,我們可以使用動態 import語法來定義程式碼分塊點 (split point):
import('./Foo.vue') // 返回 Promise
注意
- 如果您使用的是 Babel,你將需要新增 syntax-dynamic-import 外掛,才能使 Babel 可以正確地解析語法。
- 結合這兩者,這就是如何定義一個能夠被 Webpack 自動程式碼分割的非同步元件。
const Foo = () => import('./Foo.vue')
在路由配置中什麼都不需要改變,只需要像往常一樣使用 Foo:
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
# 把元件按組分塊
有時候我們想把某個路由下的所有元件都打包在同個非同步塊 (chunk) 中。只需要使用 命名 chunk,一個特殊的註釋語法來提供 chunk name (需要 Webpack > 2.4)。
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
Webpack 會將任何一個非同步模組與相同的塊名稱組合到相同的非同步塊中。
加入外掛 熱更新plugin和html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin(),
devServer: {
contentBase: '../build',
open: true,
port: 5000,
hot: true
},
加入less-css
識別的模組
{
test: /\.(less|css)$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader'
, options: {
modules: false, //不建議開啟css模組化,某些ui元件庫可能會按需載入失敗
localIdentName: '[local]--[hash:base64:5]'
}
},
{
loader: 'less-loader',
options: { javascriptEnabled: true }
}
]
},
下面正式開始生產環境
踩坑是好事 為什麼這次不放完整的原始碼 因為不去踩坑 永遠提升不了技術
html
殺掉無效的程式碼
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}
}),
加入圖片壓縮 效能優化很大
{
test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/,
use:[
{loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[name].[hash:8].[ext]',
outputPath:'/img'
}},
{
loader: 'img-loader',
options: {
plugins: [
require('imagemin-gifsicle')({
interlaced: false
}),
require('imagemin-mozjpeg')({
progressive: true,
arithmetic: false
}),
require('imagemin-pngquant')({
floyd: 0.5,
speed: 2
}),
require('imagemin-svgo')({
plugins: [
{ removeTitle: true },
{ convertPathData: false }
]
})
]
}
}
]
}
加入file-loader 把一些檔案打包輸出到固定的目錄下
{
exclude: /\.(js|json|less|css|jsx)$/,
loader: 'file-loader',
options: {
outputPath: 'media/',
name: '[name].[contenthash:8].[ext]'
}
}
加入壓縮css的外掛
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
new OptimizeCssAssetsWebpackPlugin({
cssProcessPluginOptions:{
preset:['default',{discardComments: {removeAll:true} }]
}
}),
加入code spliting程式碼分割 vue
腳手架是同步非同步分開割,我是直接一起割
optimization: {
runtimeChunk:true, //設定為 true, 一個chunk打包後就是一個檔案,一個chunk對應`一些js css 圖片`等
splitChunks: {
chunks: 'all' // 預設 entry 的 chunk 不會被拆分, 配置成 all, 就可以了拆分了,一個入口`JS`,
//打包後就生成一個單獨的檔案
}
}
加入 WorkboxPlugin , PWA的外掛
pwa這個技術其實要想真正用好,還是需要下點功夫,它有它的生命週期,以及它在瀏覽器中熱更新帶來的副作用等,需要認真研究。可以參考百度的lavas框架發展歷史~
const WorkboxPlugin = require('workbox-webpack-plugin')
new WorkboxPlugin.GenerateSW({
clientsClaim: true, //讓瀏覽器立即servece worker被接管
skipWaiting: true, // 更新sw檔案後,立即插隊到最前面
importWorkboxFrom: 'local',
include: [/\.js$/, /\.css$/, /\.html$/,/\.jpg/,/\.jpeg/,/\.svg/,/\.webp/,/\.png/],
}),
單頁面應用的優化核心 :
- 最重要的是路由懶載入 程式碼分割
- 部分渲染在服務端完成 極大加快首屏渲染速度
VUE
首選nuxt
框架,也可以使用它的腳手架 - 圖片壓縮和圖片懶載入是對頁面層次最大的優化之一
- 後面繼續書寫
next nuxt
和pwa
的使用~
腳手架的搭建過程很多坑,但是卻能大大提升你的技術天花板,跟著作者一起踩坑吧,別忘了來我的github
點贊哦~ 倉庫原始碼地址~歡迎star