当引入一个模块的时候,只引入 咱们使用过的代码,那些没引用过的代码,咱们就不打包了 另外 tree shaking 只支持 es module ,import模块的引入。 不支持 common js 的引入。css
若是是生产模式html
mode: 'production',
复制代码
那么这个tree shaking 默认就是打开的。vue
另外要注意 要在package.json 这边 额外添加node
"sideEffects": [
"*.css"
]
复制代码
假设你引入了一个css 文件,却没有明确使用它,默认状况下就把css 文件也去除掉。这可能并非咱们想要的结果,因此在这里要进行额外处理。另外 babel-polyfill的实现机制是将转换过的es5代码挂载在window对象里面。 因此若是你用了babel,这里也要额外进行配置。react
若是你是在 开发模式下打包,那么想要开启 tree shaking 模式 则额外须要在webpack config 文件中添加配置:jquery
optimization: {
usedExports: true
},
复制代码
以前咱们的打包文件只有一个。 不论是dev仍是pro 都在一个config文件里面打包。实际上是很不方便的, 好比 dev模式下 咱们须要hmr, pro不须要, dev模式下须要webdevserver,pro也不须要, dev模式下的sourcemap 更加严格,pro模式下更加宽松。等等。webpack
为了使用起来,咱们须要将dev模式和pro 模式下的配置 区分到2个文件内, 而且考虑到这2个模式下虽然有一些配置项是不一样的,可是还有大部分模块配置是相同的,因此还须要第三个文件来放这些同样的配置。git
形如:es6
为了能让webpack支持 这种 配置文件 能够 索引到基础文件的状况,咱们还须要额外安装插件github
cnpm install webpack-merge -D
复制代码
而后修改一下package.json
"scripts": {
"build": "webpack --config webpack.prod.js",
"start": "webpack-dev-server --config webpack.dev.js"
},
复制代码
这样就实现了 dev和pro 分别打包 分别引用不同的配置文件。而后咱们最后看一下 对应的配置文件怎么写
webpack dev js :
const webpack = require('webpack')
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const devConfig = {
//开发者模式默认会打开src 为开发者目录
mode: 'development',
devtool: 'source-map',
devServer: {
contentBase: './dist',
open: true,
hot: true, //开启hmr 模式
hotOnly: true //就算hmr 没有生效 也不刷新浏览器
},
plugins: [new webpack.HotModuleReplacementPlugin()],
}
module.exports=merge(commonConfig,devConfig)
复制代码
webpack pro js:
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const proConfig = {
//开发者模式默认会打开src 为开发者目录
mode: 'production',
devtool: 'cheap-module-source-map',
}
module.exports=merge(commonConfig,proConfig)
复制代码
最后看一下 公共的 webpack common js :
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports={
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240
}
}
}, {
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
}, {
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}, {
test: /\.js$/, exclude: /node_modules/, loader: "babel-loader", options: {
presets: [["@babel/preset-env", {
useBuiltIns: 'usage'
}]]
// "plugins": [["@babel/plugin-transform-runtime"],{
// "corejs": 2,
// "helpers": true,
// "regenerator": true,
// "useESModules": false,
// }]
}
}]
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
}), new CleanWebpackPlugin(['dist'])]
}
复制代码
lodash 这个组件 应该你们都常常用。 很方便。 可是有一个问题就是,当咱们引用这个组件的时候 包大小很差控制。 且每次有更新都得从新下载。影响界面性能。 举例说明:
例如
import _ from 'lodash'
console.log(_.join(['a','b','c'],''))
复制代码
假设咱们的代码是这么写的,当咱们某个版本 输出结果想要改为 abd的时候,实际上咱们只改了一个参数,可是由于咱们打包出来只有一个文件,因此对于用户来说,他就是全量更新了一次。
为了解决这个问题 咱们能够:
1.再写一个lodash文件
2.而后这个lodash文件的 做用 只是挂载一下 而已
import _ from 'lodash'
//把lodash 挂载到window 对象上
window._ = _;
// console.log(_.join(['a','b','c'],'adasda'))
复制代码
console.log(window._.join(['a','b','c'],''))
复制代码
而后修改一下 咱们的 webpack config 文件
entry: {
lodash: './src/lodash.js',
main: './src/index.js'
},
复制代码
如此一来,再次打包就变成
如今的状况就好不少了,由于拆分红了2个js文件,因此并行加载2个文件 确定是比咱们串行加载一个文件要快的,并且 对于咱们的业务代码模块 main.js来讲 ,之后无论修改多少次,用户都只会从新load 这个main.js 而不会去load 这个lodash.js文件。
有那么一点点 增量更新的意思
因此code splitting 就是代码拆分,咱们没有webpack也能够这么作 来提升页面的性能。可是有了webpack之后咱们来作code splitting就会很是简单。
有了webpack之后 要作代码拆分 那就太简单了 只须要再webpack的配置文件中增长:
optimization: {
splitChunks: {
chunks: 'all'
}
},
复制代码
其余的不用变,全部代码拆分的工做 webpack都帮你作好了
上面的例子是同步的,也就是说 对于浏览器而言,咱们是先加载的loadsh js文件,而后再调用咱们的业务方法。
那么还有一种状况是异步的,咱们来看看 异步的状况怎么处理
function getComponent() {
//异步加载 lodash 库
return import('lodash').then(
({ default: _ }) => {
var element = document.createElement('div')
element.innerHTML = _.join(['a', 'b', 'c'], " ")
return element;
}
)
}
getComponent().then(element => {
document.body.appendChild(element)
})
复制代码
也是能够代码分割的:
以前咱们看到动态加载的js库的名字 命名是0.js,这样看起来实际上是不太好的。咱们想要实际的名字这样看起来更有意义。 这里就须要用到魔法注释的功能
function getComponent() {
//异步加载 lodash 库
return import(/* webpackChunkName:"lodash" */'lodash').then(
({ default: _ }) => {
var element = document.createElement('div')
element.innerHTML = _.join(['a', 'b', 'c'], " ")
return element;
}
)
}
复制代码
注意看中间的注释,这个注释加上去之后 再次打包:
这样咱们的动态加载的js库的名字 也有了必定的意义。 固然这里咱们想作的极限一些 就是不要vendors这个前缀。 看看官网的配置 修改咱们的webpack-config js文件
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: false,
default: false
}
}
},
复制代码
而后再次打包
如今动态加载的库 也正常了。
这些相似懒加载的功能 实际运用起来 效果仍是不错的。就是用到的时候才下载, 不用的时候不要下载, 能够有效提高页面加载速度。 这与 vue react 等框架里面的 router的做用有一些相似。
懒加载的特性 跟 import 语法 强相关
惟一要注意的是,在使用相似的技术的时候 必定要记得使用babel等相似的库 作一下es6 to es5 转码的工做。
修改咱们的package.json文件。
"scripts": {
"build": "webpack --config webpack.prod.js",
"start": "webpack-dev-server --config webpack.dev.js",
//主要就是看这里,输出了一个stats的json文件
"dev": "webpack --profile --json > stats.json --config webpack.dev.js"
},
复制代码
再次打包之后 咱们就会看到这个文件了
将这个文件 上传到 官网分析器
看看官网的分析结果:
咱们也能够用一些插件来完成 打包过程的分析: webpack-bundle-analyzer 这个是比较经常使用的
optimization: {
splitChunks: {
chunks: 'all'
}
},
复制代码
这个all的含义就是 不论是同步的代码 仍是 异步的代码 webpack都会对他 作代码分割 若是你不指定这个值得话 他的默认值是async,也就是说 默认状况下 只有异步的代码才会作代码分割。
看一段简单的js代码:
document.addEventListener('click', () => {
const element = document.createElement('div')
element.innerHTML = 'wuyue'
document.body.appendChild(element)
})
复制代码
做用就是点击一下页面 就新增一个div 内容为wuyue的节点。 那么这段代码在咱们打包后的main.js里面。 但对于 咱们的用户来讲 main.js 东西不少,但实际起做用的 只有这么一段代码。且这段代码也是点击之后才起做用的,不点击的话 实际上这段代码也没啥用。属于一个废加载。
能够打开chrome 看一下 这个coverage 面板
当咱们从新刷新页面的时候 能够看出来 这个main.js 的利用率是78.2。
看这里
这段代码的标记是红色的,也就是说 这段代码 在页面首次加载的时候是用不到的。
那么webpack 有没有更好的方式能规避掉 这个影响页面性能的东西呢? 答案是有的。
咱们新建一个click.js 文件
function handleClick(){
const element =document.createElement('div')
element.innerHTML='wuyue'
document.body.appendChild(element)
}
export default handleClick
复制代码
而后修改一下index.js
document.addEventListener('click', () => {
import('./click.js').then(({ default: func }) => {
func()
})
})
复制代码
打包之后 再看看咱们的代码使用率:
使用率 提高了一个点。 并且咱们看看 这个新的listener 里面的代码 和咱们以前的代码是不同的。
咱们这个时候点击屏幕
能够看出来,点击之后 这段js代码才会被下载。
因此 webpack本质上 是但愿你 多写一些异步加载的代码,这样才能够增长你页面的运行速度。 这也就是为何 webpack 默认的split的配置是async 由于webpack认为你只有这样(异步加载)才能真正加快你网页运行的速度,而同步加载的split最多也就是启到一个缓存优化的做用,对于首次加载是没什么做用的
这里有人又要问了:
若是你作成这样的异步下载,那么对于用户的点击事件而言,他点了一个按钮之后 是否是要等待一段事件才有反应?由于毕竟 这么作 之后 相关的代码都是点击之后才会下载下来的。
这确实是一个问题,同时这也是这一个小节 想要解决的问题。
好比你看一下这个网站,首页有个登陆框。默认是不展现的。 当用户点击了 登陆按钮之后 就会展现 这个模块了
因此咱们想把登陆部分的js代码 用异步加载的方式来优化。也就是默认打开这个页面 登陆的js代码不下载, 可是当点击登陆按钮之后 这部分js代码才下载。
可是这个逻辑上面已经说了,点击之后才下载 体验上可能会更慢一些。
因此这里惟一的解决方案就是 当页面首次加载之后空闲的时候 再去加载这个登陆的js 代码。
这样既不会影响页面首次加载的速度,也不会影响点击登陆按钮之后的反应。
这时候就要依赖官方的prefetchingpreloading模块了
通常状况下 咱们用prefetch比较多。
用他也很简单。
加这么一点魔法注释便可
document.addEventListener('click', () => {
import(/* webpackPrefetch: true */'./click.js').then(({ default: func }) => {
func()
})
})
复制代码
而后再次刷新咱们的页面
能够看出来 main.js加载完毕之后 才去加载0.js
总结:
利用缓存来提高页面性能的手段实际上提高的效果十分有限,而提升js代码的利用率则效果非凡,利用好webpack提供给咱们的prefetch这个特性可以极大的提高页面渲染的性能。
webpack 默认状况下 会把css代码 也打包到js 文件里面
如图:
使用该插件之后:
固然这里的css代码 尚未进行压缩 可使用这个插件 来对css代码进行压缩
如图:
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
复制代码
加上contenthash之后 才会 每次打包(当且仅当代码有变化时,hash值才发生变化)有一个hash值
不然是不会有的。
这样就能保证代码有修改之后 每次浏览器访问的代码都是最新的代码 由于文件名变动了。不然的话 每次打包出来的名称都同样 会致使浏览器一直使用缓存文件。
固然咱们这里的contenthash只要配置在pro环境中便可。
看下面这个问题:
看这个jquery ui. js 这个文件 他里面用的实际上是jquery的操做符。可是 jquery咱们是在index 文件中引入的。
这是由于webpack的 模块化打包形成的。要解决这个问题,咱们能够在ui.js这个文件 引入一下jquery import 一下便可。
可是假设这个js 文件是一个第三方库,咱们不想改动他的代码,这种状况应该怎么办?
新增一下这个ProvidePlugin 便可
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
}), new CleanWebpackPlugin(['dist']), new MiniCssExtractPlugin(), new webpack.ProvidePlugin({
$: 'jquery' //发现 有$ 这个字符串 就自动引入jquery 这个库
})],
复制代码
shimming 这个东西 在咱们引用一些老的第三方库的时候会常常遇到,有须要的同窗这里建议通读一遍官方文档