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,是指在首页资源加载完毕后,空闲的时候,将动态导入的资源加载进来,这样既能够提升首屏加载速度,也能够解决懒加载可能会影响用户体验的问题