Hot Module Replacement(HMR:热模块替换)
- css模块HMR JS模块HMR
# 启动hmr
devServer: {
contentBase: './dist',
open: true,
hot: true,
// 即使HMR不生效,浏览器也不自动刷新,就开启hotOnly
hotOnly: true
}
# 配置文件头部引入webpack
const webpack = require('webpack')
#在插件配置处添加
plugins:[
new webpack.HotModuleReplacementPlugin()
]
案例一:处理css模块HMR
import "./css/index.css";
var btn = document.createElement("button")
btn.innerHTML = "新增"
document.body.appendChild(btn)
btn.onclick = function(){
var div = document.createElement('div')
div.innerHTML = 'item'
document.body.appendChild(div)
}
// index.css
div:nth-of-type(odd){
background: yellow;
}
启动HMR后,css抽离会不生效,还有不支持contenthash,chunkhash
案例二:处理js模块HMR
需要使用module.hot.accept来观察模块更新,从而进行更新
// counter.js
function counter(){
var div = document.createElement('div');
div.setAttribute('id','counter');
div.innerHTML = 1;
div.onclick = function(){
div.innerHTML = parseInt(div.innerHTML, 10) + 1;
}
document.body.appendChild(div)
}
export default counter
// number.js
function number(){
var div = document.createElement('div')
div.setAttribute('id', 'number')
div.innerHTML = 13000
document.body.appendChild(div)
}
export default number
index.js
import counter from './counter'
import number from './number'
counter()
number()
if(module.hot){
// 监听number模块变化
module.hot.accept('./number.js', function(){
document.body.removeChild(document.getElementById("number"))
number()
})
}
Babel处理ES6
官方网站:babeljs.io/
Babel是JavaScript编译器,能将ES6代码转换成ES5代码,可以在开发过程中放心使用JS新特性而不用担心兼容性问题,还可以根据插件机制根据需求灵活扩展。
Babel在执行编译过程中,会从项目根目录下的.babelrc JSON文件中读取配置,如没有该文件会从loader的options读取配置
测试代码
# index.js
const arr = [new Promise(()=>{}),new Promise(()=>{})]
arr.map(item => {
console.log(item)
})
# 安装
npm i babel-loader @babel/core @babel/preset-env -D
babel-loader是webpack与babel的通信桥梁,不会做把ES6转为ES5的工作,转化工作需要用到@babel/preset-env来做
# webpack.config.js
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
}
默认的Babel只支持let等一些基础的特性转换,Promise等一些还没有转换过来,这是需要借助@babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性。
babel/polyfill
以全局变量方式注入进来的,windows.Promise,但是这样会造成全局对象的污染
npm install --save @babel/polyfill
// # index.js顶部
import "@babel/polyfill"
优化
- 按需加载,减少冗余
引入@babel/polyfill后,发现打包后的体积打了很多,这是因为polyfill默认会把所有的特性都注入进来,如何实现需要的注入,不需要的无需注入,从而减少打包的体积。
# 修改webpack.config.js
options: {
presets: [
[
"@babel/preset-env",
{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1"
},
corejs:2, // 新版本需要指定核心版本库
useBuiltIns: "usage" // 按需注入
}
]
]
}
useBuiltIns选项是babel 7的新功能,这个选项告诉babel如何配置@babel/polyfill,有三个参数可以使用:
- entry:需要在webpack的入口文件中import “@babel/polyfill” 一次,babel会根据使用情况导入垫片,没有使用的功能不会被导入相应的垫片
- usage:不需要import,全自动检测,但是要安装@babel/polyfill
- false:如果import "@babel/polyfill",不会排除掉没有使用的垫片,程序体积会变大。(不推荐使用)
- 使用babelrc文件
新建.babelrc文件,把上述options中的配置移入到该文件中即可。
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"corejs":2, // 新版本需要指定核心版本库
"useBuiltIns": "usage" // 按需注入
}
]
]
}
# webpack.config.js进行相应调整
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
配置React打包环境
# 安装
npm install react react-dom --save
# index.js
import React, { Component } from 'react'
import ReactDom from "react-dom"
class App extends Component {
render(){
return <div>hello world</div>
}
}
ReactDom.render(<App/>, document.getElementById('app'))
# 安装babel与react转换的插件
npm install --save-dev @babel/preset-react
# 在.babelrc文件里添加相应配置:
"babel/preset-react"
如何实现一个plugin
webpack在编译代码过程中,有生命周期的概念,对应不同的打包阶段
plugin本质上是一个类,那么它是如何注册到webpack的对应阶段的?
webpack打包的流程:
- 拿到webpack.config.js中的配置,初始化工作
- 实现一个compiler类,注册插件,对应生命周期绑定相应的事件
- 执行编译, compiler.run()
- compiler(构建阶段)->compilation(在某个阶段,bundle加工的状态)
- 递归处理所有的依赖模块,生成chunk
- 把chunk输出到output指定的位置
案例
实现功能:实现一个打包清单插件,打包结束后,输出目录多出一个fileList.txt文件,文件内容是:bundle文件的数量以及所有换行输出所有文件名称
代码截图:
使用:
#webpack.config.js
在文件中引入
const txtWebpackPlugin = require('./txt-webpack-plugin.js')
在plugins选项中添加一项:
new txtWebpackPlugin()
之后可在dist目录中看到相应结果