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.jsconst 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.jsimport 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.cssbody { 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.jsimport '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模块配置loaderimport 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.jsmodule.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.jsconst 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.jsconst {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.jsconst 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.jsconst 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.jsconst 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.jsclass 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()]
从下面的类咱们理解了插件是通过在生命周期的钩子中挂载函数扩大