Webpack
性能包含
- 模块打包器(Module bundler)
- 模块加载器(Loader)
- 代码拆分(Code Splitting)模块增量加载,渐进式加载
- 资源模块(Asset Module)
打包工具解决的是前端整体的模块化,并不是单指 JavaScript 模块化
装置,因为是基于 npm 的包,所以
- yarn init –yes
- yarn add webpack webpack-cli –dev (^4.40.2, ^3.3.9)
- yarn webpack –version 查看版本
- yarn webpack 开始打包,webpack 主动从 src/index.js 开始打包
4.0 当前 webpack 执行反对零配置打包,将 src/index.js
作为打包入口 -> dist/main.js
生成地址
增加配置文件 webpack.config.js
,运行在 node 环境的 js 文件,按 CommonJs 形式编写代码
# webpack.config.js
const path = require('path')
module.exports = {
entry:'./src/main.js', // 我的项目打包入口文件门路
output:{ // 输入文件配置,是个对象
filename:'bundle.js', // 输入文件名称
path:path.join(__dirname,'output') // 输入文件所在目录, 必须是 absolute path!},
mode:'development' //webpack4 以上 工作模式(不同环境的几组预设配置)// 默认是 production, 主动启动优化,优化打包后果
//development 优化打包速度,增加一些调试过程须要的辅助
//none 原始打包不会进行额定解决
// 应用 yarn webpack --mode development 执行
}
资源打包
非 js 代码通过 loader
加载,Webpack 外部的 loader
默认只能解决 js,json 文件
yarn add css-loader --dev
(^3.2.0)
yarn add style-loader --dev
(^1.0.0)
const path = require('path')
module.exports = {
entry:'./src/main.css', // 我的项目打包入口文件门路
output:{ // 输入文件配置,是个对象
filename:'bundle.js', // 输入文件名称
path:path.join(__dirname,'dist') // 输入文件所在目录, 必须是 absolute path!},
mode:'none',//production,development
module: {
rules:[ // 除 js 外其余资源模块加载规定配置
{
test:/.css$/,
use:['style-loader','css-loader'] // 匹配到的文件应用的 loader
}
]
}
}
loader 内的 rules,use 从后向前执行,通过 style 标签挂载到 html 上
导入资源模块
下面导入 css 通过批改 entry 入口文件,然而个别状况下入口文件还是 js 文件,打包的入口就相当于运行的入口,JS 驱动整个前端利用业务,
通过入口文件引入 css 的形式也能达到目标
# main.js
import createHeading from './heading.js'
import './main.css'
const heading = createHeading()
document.body.append(heading)
依据代码须要动静导入资源,例如 js 中引入 css
为什么不是 js,css 拆散的形式?
js 能够比作驱动文件,而 css 和款式是辅助 js 进行丑化的,因而在 js 中引入 css 逻辑更正当,而且 js 的确须要这些资源文件
确保上线资源不缺失,都是必要的
文件资源加载器
文件图片资源加载形式通过yarn add file-loader --dev
(^4.2.0)
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
// publicPath: 'dist/' // 默认值 '' 网站的根目录
},
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/,
use: 'file-loader'
}
]
}
}
然而图片资源并没有被加载进去,查看下加载门路
默认加载网站根目录下的资源(应该加载 dist 下的资源),这是因为 index.html 没有生成到 dist 目录,而是放在我的项目的根目录下,所以把我的项目根目录作为网站根目录,
而 webpack 会默认认为所有打包后果都放在网站的根目录上面,解决办法配置publicPath: 'dist/'
。
webpack 打包时遇到图片文件,依据配置文件配置,匹配到文件加载器,文件加载器开始工作,先将导入的文件 copy 到输入目录,而后将文件 copy 到输入目录过后的门路,作为以后模块的返回值返回, 这样资源就被公布进去了。不了解能够看下 bundle.js 文件
URL 加载器
data urls 是非凡的 url 协定,能够用来间接示意一个文件,传统 url 要求服务器有一个对应文件,而后咱们通过申请这个地址失去这个对应文件。而 data url 是一种以后 url 就能够间接示意文件内容的形式,这种 url 中的文本蕴含文件内容
应用时不会发送任何 http 申请
data:text/html;charset=UTF-8,<h1>xxx</h1> //html 内容,编码 utf-8
对于图片,字体无奈间接通过文本去示意的二进制类型文件,通过将文件内容进行 base64 编码,编号后的字符串示意文件内容
data:image/png;base64,iVBORw0KGgoAAAANSUhE... //png 类型文件,编码 base64,编码
webpack 打包动态资源模块时,通过 data urls 咱们能够用代码模式示意任何类型文件了
yarn add url-loader --dev
(^2.2.0)
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/,
use: 'url-loader'// 将其转化为 dataurl 模式
}
]
}
适宜体积比拟小的资源,小文件应用 data urls 缩小申请次数,大资源会导致生成的文件体积过大,影响加载效率,因而大文件用 file-loader 独自提取寄存,进步加载速度 , 因而为 loader 增加 options
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/,
// use: 'url-loader'// 将其转化为 dataurl 模式
use:{
loader:'url-loader',
options:{ //loader 配置选项
limit:10*1024 //10kb
}
}
}
]
}
须要留神的如果你应用 url-loader 的话,肯定要同时装置 file-loader,url-loader 对于超出 10kb 的文件还是会调用 file-loader
罕用加载器分类
- 编译转换类
- 文件操作类
- 代码查看类
对写的代码进行校验的加载器,目标对立代码格调,进步代码品质, 个别不会批改生产环境代码
webpack 与 es2015
webpack 因为模块打包须要,所以解决了 import 和 export,然而并不能转化代码中其余的 es6 个性
yarn add babel-loader @babel/core @babel/preset-env --dev
(^8.2.2, ^7.14.3, ^7.14.2)
module: {
rules: [
{
test:/.js$/,
use:{
loader:'babel-loader',
options:{presets:['@babel/preset-env']
}
}
},
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/,
// use: 'url-loader'// 将其转化为 dataurl 模式
use:{
loader:'url-loader',
options:{ //loader 配置选项
limit:10*1024 //10kb
}
}
}
]
}
webpack 只是打包工具,loader 加载器能够用来编译转换代码
模块加载形式
模块加载的形式有遵循 ES Modules 规范的 import
申明,遵循 CommonJS 规范的 require
函数,遵循 AMD 规范的 define
函数和 require
函数,倡议不要混着用,会升高我的项目可维护性
除了下面形式,触发模块加载形式还有 css 款式代码中的 url
函数和 @import
指令,html 代码中图片标签的 src
属性
css 款式文件中 url 触发模块加载
# main.css
body {
min-height: 100vh;
background: #f4f8fb;
background-image: url(background.png);
/* url 触发了模块加载,留神下 url-loader 版本 2.2.0,当初 4 不显示 */
background-size: cover;
}
@import url(reset.css)
# reset.css
* {
margin:0;
padding:0;
}
# main.js
import 'main.css'
html 中的 src 属性触发模块加载
yarn add html-loader --dev
(^0.5.5)
# footer.html
<footer>
<img class="lazy" referrerpolicy="no-referrer" data-src="better.png" alt="">
</footer>
# main.js
//html 文件默认会将 html 代码作为字符串导出, 还须要为 html 模块配置 loader
import footerHtml from './footer.html'
document.write(footerHtml)
# webpack.config.js
{
test:/.html$/,
use:{loader:'html-loader'}
}
然而 html-loader 只能解决 html 下 img:src
属性,其余额定属性通过attrs
配置
{
test:/.html$/,
use:{
loader:'html-loader', // 默认只能解决 html img src 属性,其余额定属性通过 attrs 配置
options:{attrs:['img:src','a:href']
}
}
}
这个文件能够被解决了
# footer.html
<footer>
<!-- <img src="better.png" alt=""> -->
<a href="better.png">download png</a>
</footer>
外围工作原理
我的项目中散落各种各样代码及资源,webpack 会依据咱们的配置,找到其中一个文件作为打包的入口,个别状况下这个文件都是.js 文件,而后他会顺着咱们入口文件中的代码,依据代码中呈现的 import 或 require 之类的语句,解析推断出这个文件所依赖的资源模块,而后别离去解析每一个资源模块对应的依赖,最初造成了依赖关系的依赖树,webpack 会递归这个依赖树,找到每个节点对应的资源文件,最初依据配置文件中的 rules 属性,去找到模块对应的加载器,交给对应的加载器去加载这个模块,最初将加载到的后果放入到 bundle.js 中,从而实现整个我的项目打包。
整个过程中,loader 机制事 webpack 的外围
开发一个 loader
创立一个 markdown-loader,引入.md 文件,输入转化后的 html
先在根文件夹下创立配置和 loader 文件
每个 webpack loader 都须要导出一个函数,这个函数就是 loader 对加载到的资源的处理过程
输出就是加载到的资源文件的内容,输入就是此次加工过后的后果
# markdonw-loader.js
module.exports = source => {// console.log(source);
//webpack 加载资源过程相似于工作管道,能够在加载过程中顺次应用多个 loader,然而最终后果必须是 js 代码
// return 'hello ~' //x
// return 'console.log("hello ~")' //√
//yarn add marked --dev (markdown 解析模块)
const html = marked(source) // 返回值是 html 字符串
// return html // 面临下面同样问题,正确做法把他变成 js 代码
// 形式 1
// html 作为模块导出的字符串, 通过 module.exports = 这样一个字符串,// 然而简略拼接的话,html 中存在的换行符或者引号拼接一起会造成语法错误
// return `module.exports = ${html}` //x
// return `module.exports = ${JSON.stringify(html)}` //webpack 会解析模板字符串中的 js 代码
// return `export default ${JSON.stringify(html)}` // 能够应用 esm 语法
// 形式 2
// 返回 html 字符串,而后再向管道中增加一个 loader 解决字符串
// 交给下一个 loader 解决,须要装置 html-loader(组合 loader 的模式)return html
}
# webpack.config.js
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/'
},
module:{
rules:[
{
test:/.md$/,
use:[
'html-loader',
'./markdown-loader' // 模块名称或文件门路,相似 nodejs 的 require
]
}
]
}
}
当然也能够把自定义 loader 公布到 npm 包上
总结
loader 负责资源文件从输出到输入的转换,实际上是一种管道概念,对同一资源能够顺次应用多个 loader
插件机制
加强 webpack 自动化能力
loader 专一实现资源模块加载,plugin 解决其余自动化工作
e.g. 革除 dist 目录
e.g. 拷贝动态文件至输入目录
e.g. 压缩输入代码
webpack+plugin 实现了绝大多数前端工程化工作
主动革除目录插件
yarn add clean-webpack-plugin --dev
第三方插件
# webpack.config.js
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
plugins:[new CleanWebpackPlugin()
]
主动生成应用 bundle.js 的 HTML
yarn add html-webpack-plugin --dev
第三方插件(^3.2.0)
dist 下生成 html,解决以前根下 html 中的引入门路产生扭转须要硬编码的问题(通过 publicpath
解决的),当初通过动静注入。
# webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins:[new HtmlWebpackPlugin()
]
会在 dist/
生成 index.html 文件。留神要去掉 publicpath,因为此时 index 在 dist 下,咱们默认把根目录设置成了dist/
,拜访时浏览器会把 index.html 所在的门路设置为根门路,至此咱们就能够删除我的项目中的 index.html 了,通过 webpack 主动生成
改良 html-webpack-plugin 生成后果
默认生成在index.html
, 如果想自定义批改通过配置。例如批改 html 的 title,自定义 html 根底 dom 构造,html-webpack-plugin 参数参考文章
# webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins:[
new HtmlWebpackPlugin({
title:'Webpack Plugin Sample',
meta:{ // 设置页面中元数据标签
viewport:'width=device-width'
},
template:'./src/index.html'// 对于大量的配置通过创立模板文件,依据模板生成页面
})
]
模板文件
# ./src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Webpack</title>
</head>
<body>
<div class="container">
<!-- 拜访插件配置数据 -->
<h1><%= htmlWebpackPlugin.options.title %></h1>
</div>
</body>
</html>
同时输入多个页面文件
除非是单页面利用,否则须要输入多个 html, 通过增加多个实例对象到 plugins 中
# webpack.config.js
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' // 默认的 filename 是 index.html})
]
动态文件拷贝插件 copy-webpack-plugin
yarn add copy-webpack-plugin --dev
(^5.0.4)
拷贝根目录下 public/xx.ico
动态文件至 dist
# webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin')
plugins:[
// 参数数组,指定拷贝的文件门路(通配符,目录,相对路径)new CopyWebpackPlugin([
// 'public/**'
'public'
])
]
总结
每个我的项目用到的,常见的 plugin
- clean-webpack-plugin
- html-webpack-plugin
- copy-webpack-plugin
通过 github 查看个性,做到成竹在胸。其余插件通过 github 非凡应用时非凡查找,例如imagemin-webpack-plugin
图片压缩
插件机制工作原理
相比于 loader(加载模块时候),plugin(波及 webpack 工作每个环节)领有更宽的能力范畴
webpack plugin 机制就是软件开发中最常见的钩子机制,通过钩子机制实现。
钩子机制相似于 web 中的事件。在 webpack 工作过程中会有很多环节,为了便于插件的扩大,webpack 简直给每一个环节都埋下了钩子,这样咱们在开发插件时,能够通过往这些不同的节点下来挂载不同工作,就能够轻松扩大 webpack 的能力。具体有哪些事后定义好的钩子参考文档
webpack 开发一个插件
定义一个插件往钩子上挂载工作,这个插件用于革除 bundle.js 中无用的正文 //
webpack 要求插件必须是一个函数或者是一个蕴含 apply 办法的对象,
个别咱们都会把插件定义为一个类型,而后再这个类型中定义一个 apply 办法
应用时通过类型构建实例去应用
# webpack.config.js
class MyPlugin{apply (compiler){ // 此办法在 webpack 启动时主动被调用,compile 配置对象,配置信息
console.log('MyPlugin 启动');
// 通过 hooks 属性拜访钩子 emit
// 参考:https://webpack.docschina.org/api/compiler-hooks/
// tap 办法注册钩子函数(参数 1: 插件名称,参数 2: 挂载到钩子上的函数)
compiler.hooks.emit.tap('MyPlugin',compilation=>{
// compilation 能够了解为此次打包的上下文, 所有打包过程产生的后果都会放到这个对象中
// compilation.assets 属性是个对象,用于获取行将写入目录当中的资源文件信息
for(const name in compilation.assets)
{// console.log("文件名称:",name); 如图
// 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 // 返回内容的大小,webpack 要求必须加}
}
}
})
}
}
// 应用自定义插件
plugins:[new MyPlugin()
]
从下面的类咱们理解了插件是通过在生命周期的钩子中挂载函数扩大