tree shakingcss
tree shaking是一個術語、一般用於打包時移除js中未引用的代碼(dead-code),它依賴於ES6模塊系統中的import 和 export 的靜態結構特性html
開發時引入一個模塊時,若是隻引用其中一個功能,上線打包時只會把用到的功能打包進bundle中,其餘沒有用到的功能都不會打包進來,能夠實現最簡單的基本優化vue
export const add = (a, b) => a + b export const minus = (a, b) => a- b
// tree shaking 分析 // 如果此時使用 require 引入,無論 math 中的方法是否使用,都會被打包 const math = require('./utils/math') // 如果使用 import 引入, 只會打包使用了 math 的方法 import { add } from './utils/math' console.log('index 頁面',math.add(1,2)); console.log('index 頁面',add(1,2));
scope hoistingnode
Scope hositing 做用:是將模塊之間的關係進行結果推測,可讓webpack文件打包出來的代碼文件更小、運行的更快jquery
scope hositing實現原理:分析出模塊之間的依賴關係,儘量的把打散的模塊合併到一個函數中,可是前提是不能形成代碼冗餘, 所以只有哪些被引用了一次的模塊可能被合併webpack
因爲scope hositing 須要分析出模塊之間的依賴關係,所以源碼必須使用ES6模塊化語句,否則就不能生效,緣由和 tree shaking同樣git
在 main.js 中定義幾個變量並輸出github
const a = 1 const b = 2 const c = 3 // webpack 在這裏會進行 預執行,將結果推斷後打包放在這裏 console.log(a + b + c) console.log(a, b, c)
打包以後代碼變成web
console.log(6),console.log(1,2,3)
由於三個變量只是在這個地方定義而且使用,並無在其餘位置使用,webpack會直接以具體的數值進行打包,節省了三個變量的定義vue-router
代碼壓縮
全部代碼使用UglifyJsPlugin進行壓縮、混淆
Mini-css-extract-plugin 是用於將 CSS 提取爲獨立的文件的插件,對每一個包含css的js文件都會建立一個css文件,支持按需加載css和sourceMap
只能用於webpack4中,優點
使用
安裝
npm i -D mini-css-extract-plugin
引用
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
建立插件對象,配置抽離的css文件名,支持placeholder語法
new MiniCssExtractPlugin({ filename:'[name].css' // [name] 就是 placeholder 語法 })
將原來配置的全部 style-loader 替換爲 MiniCssExtractPlugin.loader
{ test:/\.css$/, use:[MiniCssExtractPlugin.loader, 'css-loader'] }, { test:/\.less$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] }, { test:/\.scss$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] },
使用 postcss,須要使用 postcss-loader 和 autoprefixer
安裝
npm i -D postcss-loader autoprefixer
修改配置文件,將 postcss-loader 放置在 css-loader 右邊
{ test:/\.css$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader',] }, { test:/\.less$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'] }, { test:/\.scss$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'] },
項目根目錄下添加 postcss 的配置文件: postcss.config.js
module.exports = { plugins: [ require('autoprefixer')({ browsers: [ // 加這個後能夠出現額外的兼容性前綴 "> 0.01%" ] }) ] }
須要使用 optimize-css-assets-webpack-plugin 插件來完成css壓縮
可是因爲配置css壓縮時會覆蓋掉webpack默認的優化設置,致使JS代碼沒法壓縮,因此還須要把JS代碼壓縮插件倒入進來 terser-webpack-plugin
安裝
npm i -D terser-webpack-plugin optimize-css-assets-webpack-plugin
引用
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
配置
optimization:{ minimizer: [ new TerserPlugin({}), new OptimizeCssAssetsPlugin({}) ] }
webpack4默認採用的JS壓縮插件是 uglifyjs-webpack-plugin,在 mini-css-extract-plugin上一個版本中還推薦使用該插件,可是新的版本卻建議使用 terser-webpack-plugin
code splitting 是webpack打包時用到的重要的優化特性之1、此特性可以把代碼分離到不一樣的bundle中,而後能夠按需加載或者並行加載這些文件,代碼分離能夠用於獲取更小的bundle,以及控制資源加載優先級,若是可以合理的使用可以極大影響加載時間
在webpack配置文件中配置多個入口
entry:{ main: './src/main.js', other: './src/other.js' }, output:{ path: path.join(__dirname, '..', './dist'), filename: '[name].js', publickPath: '/' }
在main.js 和 other.js 中都共同引入一個模塊, 並使用其功能
Main.js
import $ from 'jquery' $(() => { $('<div></div>').html('main').appendTo('body') })
other.js
import $ from 'jquery' $(() => { $('<div></div>').html('other').appendTo('body') })
打包文件,能夠看到 main 和 other 打包的文件中都加載的了 jquery
webpack4 以上使用的插件爲 SplitChunksPlugin,webpack4 以前的使用的 CommonChunkPlugin已經被移除,最新版本的webpack中只須要在配置文件中的optimization節點下添加一個splitChunks屬性便可進行相關配置
修改配置文件
optimization splitChunks:{ chunks: "all" } }
打包查看文件
打包以後會將各自的入口文件進行打包,額外會再生產一份js文件,此文件中就是各個chunk中所引用的公共部分
splitChunksPlugin 配置參數
SplitChunksPlugin 的配置只須要在 optimization 節點下的 splitChunks 進行修改便可,若是沒有任何修改,則會使用默認設置
默認的 SplitChunksPlugin 配置適用於絕大多數用戶
module.exports = { optimization: { splitChunks: { chunks: 'async', // 只對異步加載的模塊進行拆分,import('jquery').then()就是典型的異步加載,可選項還有 all | initial minSize: 30000, // 模塊最少大於 30kb 纔會拆分 maxSize: 0, // 爲0時模塊大小無上限,只要大於 30kb 都會拆分。如果非0,超過了maxSize的值,會進一步拆分 minChunks: 1, // 模塊最少引用一次纔會拆分 maxAsyncRequests: 5, // 異步加載時同時發送的請求數量最大不能超過5,超過5的部分不拆分 maxInitialRequests: 3, // 頁面初始化時,同時發送的請求數量最大不能超過3,超過3的不跟不拆分 automaticNameDelimiter: '~', // 默認的鏈接符 name: true, // 拆分的chunk名,設置爲true表示根據模塊名和CacheGroup的key來自動生成,使用上面的鏈接符鏈接 cacheGroups: { // 緩存組配置,上面配置讀取完成後進行拆分,若是須要把多個模塊拆分到一個文件,就須要緩存,因此命名爲緩存組 vendors: { // 自定義緩存組名 test: /[\\/]node_modules[\\/]/, // 檢查 node_modules 目錄,只要模塊在該目錄下就使用上面配置拆分到這個組 priority: -10, // 權重爲-10,決定了那個組優先匹配,假如node_modules下面有個模塊要拆分,同時知足vendors和default組,此時就會分到 priority 值比較大的組,由於 -10 > -20 因此分到 vendors 組 filename:'vendoes.js' }, default: { // 默認緩存組名 minChunks: 2, // 最少引用兩次纔會被拆分 priority: -20, // 權重 -20 reuseExistingChunk: true // 若是主入口中引入了兩個模塊,其中一個正好也引用了後一個,就會直接複用,無需引用兩次 } } } } };
webpack4默認是容許import語法動態導入的,可是須要babel的插件支持,最新版babel的插件包爲:@babel/plugin-syntax-dynamic-import,須要注意動態導入最大的好處就是實現了懶加載,用到那個模塊纔會加載那個模塊,能夠提升SPA應用程序的首屏加載速度,三大框架的路由懶加載原理同樣
安裝
npm i -D @babel/plugin-syntax-dynamic-import
修改 .babelrc ,添加 @babel/plugin-syntax-dynamic-import 插件
{ "presets": ["@babel/env"], "plugins": [ "@babel/plugin-proposal-class-properties", "@babel/plugin-syntax-dynamic-import" ] }
將jq模塊動態導入
function getDivDom(){ // import('jquery') 返回的是一個 promise,如果低版本須要注意 return import('jquery').then(({default: $}) => { return $('<div></div>').html('動態導入') }) }
給某個按鈕添加點擊事件,點擊後調用getDivDom函數建立元素並添加到頁面
window.onload = () => { document.getElementById('btn').addEventListener('click',() => { getDivDom().then(item => { item.appendTo('body') }) }) }
在引入一些第三方模塊時,如jq等,咱們知道其內部確定不會依賴其餘模塊,由於咱們用到的只是一個單獨的js或者css文件,因此此時若是webpack再去解析他們的內部依賴關係,實際上是很是浪費時間的,就須要阻止webpack浪費精力去解析這些明知道沒有依賴的庫,能夠在webpack的配置文件的module節點下加上noParse,並配置正則來肯定不須要解析依賴關係的模塊
module:{ noParse: /jquery|bootstrap/ // jquery|bootstrap 之間不能加空格變成 jquery | bootstrap, 會無效 }
在引入一些第三方模塊時,例如momentJS、dayJS,其內部會作i18n處理,因此會包含不少語言包,而語言包打包時會比較佔用空間,若是項目只須要用到中文或者少數語言,能夠忽略掉全部的語言包,而後按需引入語言包,從而使得構建效率更高,打包生成的文件更小
以moment爲例
import moment from 'moment' moment.locale('zh-CN') // 設置爲中文 console.log(moment().subtract(6, 'days').calendar())
首先要找到moment依賴的語言包時什麼,經過查看moment的源碼來分析
function loadLocale(name) { var oldLocale = null; // TODO: Find a better way to register and load all the locales in Node if (!locales[name] && (typeof module !== 'undefined') && module && module.exports) { try { oldLocale = globalLocale._abbr; var aliasedRequire = require; aliasedRequire('./locale/' + name); getSetGlobalLocale(oldLocale); } catch (e) {} } return locales[name]; }
經過 aliasedRequire('./locale/' + name) 能夠知道momentJS的多語言目錄是locale,全部的語言JS文件都在這個目錄中
使用IgnorePlugin插件忽略其依賴
將momentJS的多語言目錄locale忽略
new webpack.IgnorePlugin(/\.\/locale/, /moment/)
須要使用某些依賴時自行手動引入
忽略其依賴以後,moment.locale('zh-CN')就會失效,由於其所依賴的語言包全都被忽略了,須要手動將其引入
import moment from 'moment' import 'moment/locale/zh-cn' // 須要手動引入方可生效 moment.locale('zh-CN') console.log(moment().subtract(6, 'days').calendar())
在引入一些第三方模塊時,例如Vue、React等,這些框架的文件通常都是不會修改的,而每次打包都須要去解析他們,也會影響打包速度,就算是作了拆分,也只是提升了上線後的用戶訪問速度,並不會提升構建速度,因此若是須要提升構建速度,應該使用動態連接庫的方式,相似windows的dll文件
藉助DLLPlugin插件實現將這些框架做爲一個個的動態連接庫,只構建一次,之後的每次構建都只會生成本身的業務代碼,能夠很好的提升構建效率
豬喲思想在於,講一些不作修改的依賴文件,提早打包,這樣咱們開發代碼發佈的時候就不須要再對這些代碼進行打包,從而節省了打包時間,主要使用兩個插件: DLLPlugin和DLLReferencePlugin
須要注意的是,如果使用的DLLPlugin,CleanWebpackPlugin插件會存在衝突,須要移除CleanWebpackPlugin插件
DLLPlugin
使用一個單獨webpack配置建立一個dll文件,而且它還建立一個manifest.json,DLLReferencePlugin使用該json文件來作映射依賴性,這個文件會告訴webpack哪些文件已經提取打包好了
DLLReferencePlugin
該插件主要用於主webpack配置,它引用的dll須要預先構建的依賴該系
// 此配置文件 是打包VUE全家桶的 const path = require('path') const webpack = require('webpack') module.exports = { mode: 'production', entry:{ vue: [ 'vue/dist/vue', 'vue-router' ] }, output:{ path: path.resolve(__dirname, '../dist'), filename: '[name]_dll.js', library: '[name]_dll' // 最終會在全局暴露出一個[name]_dll的對象 }, plugins:[ new webpack.DllPlugin({ name: '[name]_dll', path: path.resolve(__dirname, '../dist/manifest.json'), }) ] }
webpack.vue.js 只是用來打包生成 [name]_dd.js 文件和 manifest.json文件的,是不須要參與到業務代碼打包的,由於只會在每一次修改了須要生成dll文件的時間纔會執行一次,不然不須要參與到打包
webpack.base.js
中進行插件的配置使用DllReferencePlugin指定manifest文件的位置便可
new webpack.DllReferencePlugin({ manifest: path.resolve(__dirname, '../dist/manifest.json'), })
安裝add-asset-html-webpack-plugin
npm i -D add-asset-html-webpack-plugin
配置插件自動添加script標籤到HTML中,須要注意的是,必須在HtmlWebpackPlugin後面引入,由於HtmlWebpackPlugin是生產一個html文件,AddAssetHtmlWebpackPlugin是在已有的html中注入一個script,不然會被覆蓋
new AddAssetHtmlWebpackPlugin({ filepath: path.resolve(__dirname, '../dist/vue_dll.js') })
在作了衆多代碼分離的優化後,其目的是爲了更好的利用瀏覽器緩存,達到提升訪問速度的效果,因此構建項目時作代碼分割是必須的,
例如將固定的第三方模板抽離,下次修改了業務代碼,從新發布上線不重啓服務器,用戶再次訪問服務器就不須要再次加載第三方模板了
可是此時會遇到一個問題,若是再次打包上線不重啓服務器,客戶端會把之前的業務代碼和第三方模塊同時緩存,再次訪問時依舊會訪問緩存中的業務代碼,因此會致使業務代碼也沒法更新
須要在output節點的filename中使用placeholder語法,根據代碼內容生產文件名的hash,以後每次打包業務代碼時,若是有改變,會生成新的hash做爲文件名,瀏覽器就不會使用緩存了,而第三方模塊不會從新打包生成新的名字,則會繼續使用緩存
output: { path: path.join(__dirname, '..','./dist'), filename:'[name].[contenthash:8].bundle.js', publicPath: '/' },
項目構建完成後,須要經過一些工具對打包後的bundle進行分析,經過分析能夠獲得一些有用的信息
--profile --josn
參數,以json格式來輸出打包後的結果到某個指定的文件中webpack --profile --json > stats.json
官方工具: analyse
webpack-chart:webpack stats 可交互餅圖。
webpack-visualizer:可視化並分析你的 bundle,檢查哪些模塊佔用空間,哪些多是重複使用的。
webpack-bundle-analyzer:一個 plugin 和 CLI 工具,它將 bundle 內容展現爲便捷的、交互式、可縮放的樹狀圖形式。是一個插件,能夠以插件安裝到項目中
npm i -D webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [ new BundleAnalyzerPlugin() ] }
webpack bundle optimize helper:此工具會分析你的 bundle,併爲你提供可操做的改進措施建議,以減小 bundle 體積大小。
在優化訪問性能時,除了利用瀏覽器緩存以外,還須要涉及到一個性能指標: 覆蓋率(coverage rate)
能夠在chrome瀏覽器的控制檯中按 ctrl + shift + p,查找 coverage,打開覆蓋率面板,開始錄製後刷新頁面,便可看到每一個js文件的覆蓋率,以及總的覆蓋率
想提升覆蓋率,須要儘量多的使用impor動態導入,也就是懶加載的功能,將一切能使用懶加載的地方都是用懶加載,這樣能夠大大的提升覆蓋率
可是有時候使用懶加載會影響用戶體驗,因此能夠在使用懶加載的時候使用魔法註釋(Magic Comments): prefetching,是指在首頁資源加載完畢後,空閒的時候,將動態導入的資源加載進來,這樣既能夠提升首屏加載速度,也能夠解決懶加載可能會影響用戶體驗的問題