webpack是现在最常用的打包工具,支持不同类型的资源文件模块
1.Webpack的快速上手
yarn add webpack webpack-cli --dev
//运行
yarn webpack
<!-- 在打包过程中会把import、exports转换掉,所以不需要使用 type="module" -->
2.Webpack的配置文件
在webpack.config.js文件中进行配置
const path = require('path)
module.exports={
mode:'none',
<!-- 入口文件 -->
entry:'./src/main.js',
<!-- 输出文件 -->
output:{
filename:'bundle.js',
<!-- 路径拼接 -->
path:path.join(__dirname,'dist')
}
}
webpack有三种工作模式,在不同的环境使用不同的预设配置,默认使用production模式工作
production 生成阶段,自动优化,压缩打包
development 开发阶段,打包速度,可以进行调试辅助
none 最原始的状态打包,无其他额外操作
3.Webpack资源模块的加载
webpack默认只处理JS文件,其他类型文件的处理需要通过对应类型的加载器(loader)去进行处理
loader是webpack实现前端模块化的核心特性,借助于loader就可以实现加载任意类型的资源
style样式文件依赖模块的安装
yarn add css-loader --dev
yarn add style-loaader --dev
对应loader的配置
module:{
rules:[
{
<!-- 匹配文件 -->
test:'/*.css$/',
<!-- use中的loader从后向前执行 -->
use:[style-loader,css-loader]
<!-- style的作用:将css-loader的转换结果追加到页面-->
}
]
}
4.Webpack文件资源加载器
<!-- 图片、字体资源的处理 -->
yarn add file-loader --dev
<!-- 配置 -->
rules: [
{
test: /.png$/,
use: ['file-loader']
}
]
webpack Data URLs 与 url-loader
Data URLs可以直接表示一个文件(特殊的url)
协议 媒体类型 编码 文件内容
data:[<meditype>][;base64],<data>
data:text/html;charset=UTF-8,<h1>conent</h1>
yarn add url-loader --dev
<!-- 配置 -->
{
test: /.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10 * 1024 //10kB
}
}
}
小文件使用Data URLs,可以减少请求次数,对文件单独提取存放,提高加载速度,url-loader对超出limit的文件自动去调用file-loader进行处理,url-loader将符合limit要求的文件转换为Data URLs。
5.webpack常用资源加载器分类
编译转换类(转为JS代码) 如:css-loader
文件操作类(资源拷贝到目标目录,同时经路径向外导出) 如:file-loader
代码检查类(统一代码风格,提高代码质量) 如:eslint-loader
1. webpack对ES2015(ES6)代码的处理
如下:代码的转换需要babel的支持,安装对应的依赖
yarn add babel-laoder @babel/core @babel/preset-env --dev
<!-- 配置 -->
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
2.webpack模块的加载模式
遵循ES Modules标准的import声明;遵循CommomJS标准的require函数 ;遵循AMD标准的define函数和require函数
在loader中,加载非JavaScript也会触发资源加载:
样式代码中@import指令和url函数;HTML代码中图片样式的src属性等
在html-loader中,默认处理img的src属性
yarn add html-loader --dev
<!-- 配置 -->
{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
attributes: {
list: [{
tag: 'img',
attribute: 'src',
type: 'src',
},
{
tag: 'a',
attribute: 'href',
type: 'src',
},
]
}
}
}
}
6. Webpack的核心工作原理
loader机制是webpack的核心
loader的这个原理:source 通过 md-loader 得到 result(js代码),即对源码进行加工处理,得到目标代码
loader 负责资源文件从输入到输出的转换,对同一资源可以使用多个loader
我们也可以自己是写一个loader,有兴趣的可以自己尝试写一个。
7.webpack 的插件机制
增强webpack的自动化能力 loader专注于实现资源模块加载 Plugin解决除资源加载外,其他自动化工作 e.g. 清除dist目录,拷贝静态文件、压缩输出代码 loader+plugin => 实现大多数前端工程化工作
1.如下一些常用插件的配置
clean-webpack-plugin
yarn add clean-webpack-plugin --dev
<!-- 自动清除输出目录 配置 -->
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
plugins: [
new CleanWebpackPlugin()
]
html-webpack-plugin
yarn html-webpack-plugin --dev
<!-- 配置 -->
plugins: [
// 用于生成 index.html
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
meta: {
viewport: 'width=device-width'
},
template: './src/index.html'
}),
// 用于生成 about.html
new HtmlWebpackPlugin({
filename: 'about.html'
})
]
copy-webpack-plugin
yarn add copy-webpack-plugin --dev
<!-- 配置 -->
const CopyWebpackPlugin = require('copy-webpack-plugin')
plugins: [
new CopyWebpackPlugin([
'public'
])
]
2. webpack的插件机制的工作原理
相比于loader,plugin拥有更广泛的能力范围,plugin通过钩子机制实现
插件:一个函数或者是一个包含apply方法的对象
<!-- 通过在声明周期的钩子挂载函数实现扩展 -->
class MyPlugin {
apply (compiler) {
console.log('MyPlugin 启动')
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation => 可以理解为此次打包的上下文
for (const name in compilation.assets) {
// console.log(compilation.assets[name].source())
if (name.endsWith('.js')) {
const contents = compilation.assets[name].source()
const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length
}
}
}
})
}
}
8.自动编译和自动刷新
在开发过程中,如果每次都手动打包编译是不好的体验,我们可以使用watch去实现自动编译
webpack的自动编译
watch工作模式:监听文件变化,自动重新打包
运行命令:yarn webpack -watch
但实现自动编译后,当我们在开启server服务,每次修改后都要手动刷新页面,也是糟糕的体验,
全局安装 BrowserSync ,启动http server 服务
监听文件的变化,达到自动更新的目的: brower-sync dist --files "**/*"
以上方法虽然实现了自动编译和自动刷新,但操作麻烦,同时使用2个工具;效率低(磁盘读写次数频繁),那有没有一个工具可以解决?
Webpack Dev Server
提供用于开发的 HTTP Server,集成自动编译、自动刷新浏览器等功能
yarn add webpack-dev-server --dev
<!-- 运行 -->
yarn webpack-dev-server --open
为提高效率,Webpack Dev Server 并未将打包结果写入磁盘(未生成dist目录),将打包结果暂存于内存中,http-server从内存读取信息 发送到浏览器,减少磁盘的使用,大大提高了构建效率
Webpack Dev Server 静态资源访问
但是Webpack Dev Server默认只会serve打包输出的文件,只有通过webpack打包输出的文件,才能被直接被访问,但在开发阶段,我们并未对静态资源进行打包处理,通过如下配置,实现Webpack Dev Server 静态资源访问
<!-- 其他静态资源文件也需要serve,配置如下: -->
devServer: {
<!-- contentBase额外为开发服务器指定查找资源目录 -->
contentBase: './public'
}
Webpack Dev Server 代理API服务器
开发与上线地址不同,需要跨域请求 ,Webpack Dev Server支持配置代理
<!-- 将GitHub API 代理到开发服务器 -->
devServer: {
contentBase: './public',
proxy: {
'/api': {
// http://localhost:8080/api/users -> https://api.github.com/api/users
<!-- 目标api -->
target: 'https://api.github.com',
// http://localhost:8080/api/users -> https://api.github.com/users
<!-- 路径重写 -->
pathRewrite: {
'^/api': ''
},
// 不能使用 localhost:8080 作为请求 GitHub 的主机名
changeOrigin: true
}
}
}
<!-- 主机名是HTTP协议的相关概念 -->
Source Map(源代码地图)
实现了自动编译和自动刷新,但在开发过程中,如果代码报错,需要调试,却不能定位源码的位置,如果一个个去找,代码量小还好,若代码量大,是不太现实的
运行代码 ——> 通过Source Map 逆向解析 ——> 源码
Source Map 解决了源代码与运行代码之间不一致所产生的问题
devtool: 'source-map'
<!-- webpack运行时会生成map文件 -->
webpack支持12种不同的方式,每种方式的效率与效果各不相同, 效果好的,生成慢,效果差,生成快
eval —— 是否使用eval执行模块代码
cheap-source-map —— 是否包含行信息
module —— 是否能够得到loader处理之前的源代码
hidden —— 生成第三方包
nosources-source-map —— 生产环境下不暴露源码
因为有12中方式,各不相同,所以要根据不同的环境,选择合适的source map
开发模式下的选择: cheap-module-eval-source-map
<!-- 原因 -->
代码每行不超过80字符(编写习惯),代码经过loader转换后差异较大,首次打包速度慢,但重写打包相对较快
生产模式下的选择: none 或 nosources-source-map
<!-- 原因 -->
调试是开发阶段的事情
Source Map 会暴露源码
9.自动刷新的问题
在开发阶段,以开发编辑器为例,当你去调试代码修改字体样式时,会发现触发自动刷新,编辑器中的内容被清除了。
那么我们如何防止刷新的问题,1.代码内容写死 2,。 额外写代码实现刷新保存,刷新后再读取。
问题的核心:自动刷新时导致页面状态丢失
实现:页面不刷新的前提下,模块也可以及时更新
HMR介绍(模块热替换)--Hot Module Replancement
应用程序运行的过程中,实时替换摸个模块,应用状态不受影响
热替换只将修改的模块实时替换到应用中
HMR是webpack的最强大的功能之一,极大的提高了开发者的工作效率
HMR 集成在webpack-dev-server中
webpack开启 HMR
<!-- 通过命令 -->
webpack-dev-server --hot
<!-- 配置 -->
const webpack = require('webpack')
devServer: {
hot: true
// hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
但在使用中会发现一些问题
Q1:为什么样式文件的热更新开箱即用
style-loader中已经处理了样式文件的热更新逻辑
Q2: 凭什么样式可以自动处理
样式模块更新后,只需要把更新好的css替换到页面,覆盖之前的样式
而js文件是无规律可循的
当使用框架开发时,每种文件都是有规律的
通过脚手架创建的项目内部都集成了HMR方案
webpack 的 HMR 需要手动处理模块热替换逻辑
accept注册,第一个参数代表依赖模块路径,第二个参数:更新后的处理函数
let lastEditor = editor
module.hot.accept('./editor', () => {
const value = lastEditor.innerHTML
document.body.removeChild(lastEditor)
const newEditor = createEditor()
newEditor.innerHTML = value
document.body.appendChild(newEditor)
lastEditor = newEditor
})
HMR 注意事项
1.处理HMR的代码报错会导致自动刷新,采取如下配置解决:
devServer: {
hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading
}
2.没有设置HMR的情况下,HMR API 报错,解决如下:
//先判断是否开启了热替换
if (module.hot) {
<!-- 具体处理过程 -->
}
3.代码中多了些与业务无关的代码
不会影响生产环境中代码的运行