模块化打包工具的由来
- ES Modules存在环境兼容问题
- 模块文件过多,网络请求频繁
- 所有的前端资源都需要模块化
- 毋庸置疑,模块化是必要的
解决问题
- 将es6等新功能变成es5等浏览器能识别的语言
- 将很多个模块打包成一个bundle.js(在开发模式下用模块化开发 在生产模式下打包成一个bundle.js文件)
模块化打包工具概述
- webpack 模块化打包工具
- 模块加载器(Loader)
- 代码拆分(Code splitting)
- 资源模块(Asset Module)
上面的到爆工具解决的是前端整体的模块化并不单指Javascript模块化
webpack快速上手
- 初始化 yarn init -y
- webpack数据工具模块需要安装 yarn add webpack webpack-cli --dev
- 配置webpack.config.js文件(运行在nodejs的文件所以要符合commjs)
const path = require('path');
module.exports={
entry:'./src/main.js', //文件入口
output:{ //输出目录
filname:'bundle.js', //输出文件名称
path:path.join(_dirname,'output') //在outuput文件里面生成bundle.js文件 //指定输出文件的目录(需要绝对路径)
}
}
webpack模式
- none 最原始状态打包
- production
- development
webpack资源模块加载
借助loader可以加载任何形式的资源(loader对于同一个源可以依次使用多个loader,通过多个loader完成同一个功能。例如style-loader,css-loader)
dataUrl 可以用base64编码进行表示任何文件 url-loader
- 解析css 模块 安装
- yarn add style-loader --dev
- yarn add css-loader --dev
- 文件资源加载器 例如有些内容是不能转化成js的例如图片、字体 就需要使用file-loader
- yarn add file-loader --dev
- main.js中
- 使用dataUrl加载任何文件
- yarn add url-loader
- 适合体积比较小的文件,因为这样减少请求次数,如果文件过大会影响打包速度 url-
- 较大的文件使用file-loader单个文件存放,提高加载速度
- 超过10kb文件单独提取存放
- 小于10kb转换为dataUrl嵌入代码中
- 使用babel-loader转化新特性
- yarn add babel-loader @babel/core @babel/preset-env --dev
- babel只是一个转化代码的平台,具体转化需要转化工具进行配置
import icon from './icon.png' const img = new Image(); img.src = icon; document.body.append(img);
- 解析html(使用html-loader)
- yarn add html-loader --dev
<!-- footer.html --> <footer> <a href="better.png">download</a> </footer>
<!-- main.js --> import footerHtml from './footer.html' documnet.write(footerHtml)
// 使用module:
module:{
rules:[
{
test:'/.js$/',
use: {
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
},
},
{ //将css转换成js的模块
test:'/.css$/',
//这里需要注意 数组中的执行顺序是从右往左执行的 必须先解析css在加载style-loader
use:['style-loader',css-loader']
},
{
test:'/.png$',
// use:'file-loader'url
// use:'url-loader'
use:{
loader:'url-loader',
options:{
limit:10*1024 //10kb
}
}
},
{
test:'/.html$/',
loader:'html-loader'
//html中img src属性触发打包如果想让别的也触发可以进行如下配置
options:{
attrs:['img:src','a:href']
}
}
]
}
需要注意如果我们配置了url-loader 的大小限制,小于走url-loader否则走file-loader 但是需要注意的是卸载掉file-loader会报错(可不配置file-loader但是不能卸载)
webpack只是一个打包工具,如果你想处理代码编译转化的功能需要使用对应的loader进行转化
常用的加载器
* 编译转化加载类(css-loader)
* 文件操作类型加载器(file-loader)
* 代码检查加载器
webpack导入资源模块
- 在使用的js中使用import ‘./main.css’
需要注意我们平时写的项目要求前后端分离 webpack建议我们根据代码的需要动态导入资源,真正需要资源的不是应用,而是代码,代码什么时候需要就要加载,代码更新不需要资源这个时候就不需要加载,webpack驱动了整个前端的应用。
- 逻辑合理,js需要这些资源文件
- 确保上线资源不缺失,都是必要的
webpack中模块的加载方式
- 遵循ESmodules 标准的import
- 支持commonJS标准的require函数 但需要注意如果载入一个ESmodule的defalut导出 需要使用
require('./header.js').default;
- 支持AMD标准的define函数和require函数
- 需要注意的是只建议在一个项目中使用一种引用方式,不然会增加维护成本
- 样式代码中的@import指令和url函数
- html代码中图片标签的src属性
webpack打包的工作原理
项目当中都会散落着各种各样的代码和资源文件,weboack会根据配置找到一个主入口文件一般都是.js的文件根据代码import export等依赖的模块和整个的模块依赖,形成依赖数,webpack会递归遍历依赖数,找到每个节点的所对应的资源文件,根据模块对应的ruls找到模块对应的加载器,然后交给对应的加载器加载模块,加载到的结果放到bundle.js文件中,实现项目的打包。loader是webpack的核心。
webpack loader的工作原理
自己开发一个markdown-loader,注意你可以使用多个loader但是最终返回的结果是一个javascript的代码
- 安装一个 marked的解析模块
- yarn add marked --dev
- 导出的文件支持module.exports 和esModule的格式 export.defaule
- 如果选择第二种方式需要安装处理html的loader add html-loader
markdown-loader.js
const marked = required('marked');
module.exports = source =>{
const html = marked(source);
// 第一种导出将 导出内容直接转化成javascript的格式
//以下两种格式都支持导出
<!--return `module.exports = ${JSON.stringify(html)}`
return `export.defaule = ${JSON.stringify(html)}`
-->
//第二种格式 返回的html字符串直接交给下一个loader来处理
return html;
<!--console.log(source)-->
<!--return source;-->
}
配置webpack.json
//返回javascript的格式
{
test:'./md$/',
use:'./markdown-loader' //不仅可以使用模块还可以使用路径地址
}
//返回html的格式
{
test:'./md$/',
use:['html-loader','./markdown-loader'] //再次强调数组的执行顺序是从后面像前面执行的
}
webpack插件功能
loader处理资源 plugin处理那些非资源的内容实现了大多的功能
- 自动清除目录的插件 clean-webpack-plugin
- yarn add clean-webpack-plugin --dev
- 自动生成html文件(如果不输出html文件 那么我就就需要将html引入的script路径手动指向打包的dist目录下相应用的路径地址如果打包的js文件名有所变化那我们就需要手动更改。)
- yarn add html-webpack-plugin --dev
- webpack动态生成html文件
- html中可以自己创建 用loadsh的语法输出动态的内容
<h1><%= htmlwebpackPlugin.options.title %></h1>
- webpack多页面输出
- webpack c处理不参与打包的静态文件需要复制到对应目录
- yarn add copy-webpack-plugin --dev
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin') //默认导出的插件类型
const CopyWebpackPlugin = require('copy-webpack-plugin')
plugins:[
new CleanWebpackPlugin()
<!--new HtmlWebpackPlugin()-->
new HtmlWebpackPlugin({ //生成index.html
title:'webpack',
meta:{
viewport:'width=device-width'
},
template:'./src/index.html'
})
//添加多个实例输出多个文件
new HtmlWebpackPlugin({
filename:'about.html'
}),
//接受的是一个数组,可以写路径,目录,通配符等
new CopyWebpackPlugin([
'public'
//public/**
])
]
webpack开发一个插件
定义插件一个函数或者是一个包含apply方法对象
// 清除bundle.js中没用注释 emit在webpack输出文件时候执行
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();
console.log(contents);
const withoutComments = contents.replace(/\/\*\*+\*\//g, '');
// /******/ \/\*\*+\*\/
compilation.assets[name] = { .//webpack必须要暴露出来的两个方法
source: () => withoutComments,
size: () => withoutComments.length
}
}
}
})
}
}
在plugin中new MyPlugin();
webpack增强开发体验 实现自动编译
- webpack dev server 高度集成工具 自动编译,自动刷新浏览器
yarn add webpack-dev-server --dev
// 通过yarn运行cli
yarn webpack-dev-server 运行命令会直接打包应用
*处理静态资源 webpack-dev-server
// 注释:上面代码中new CopyWebpackPlugin 可以实现资源的copy,但是一般new CopyWebpackPlugin都留在上线前的打包使用而已,因为在开发过程中每次打包都copy会影响速度。
module.exports = {
devServer:{
contentBase:['./public'] //指定静态资源
}
}
webpack-dev-server 支持代理服务
devServer:{
contentBase:'./public',
proxy:{
// 请求前缀
'/api':{
// http://localhost:8080/api/users -> https:api.geithub.com/api/users
target:"https:api.github.com", //代理目标
pathRewrite:{
'^/api':'' //代理路径重写
},
//不能使用localhost:8080 作为请求GitHub的主机名
changeOrign:true
}
}
}
source Map
- 运行代码和源代码之间有很大差异。
- 帮助开发者调试和定位错误
- sourceMappingUrl = jquery-3.4.1.min.map 根据映射关系定位到源代码
- 解决了源代码与运行代码不一致所导致的问题
如何配置source Map
devtool 开发过程中的辅助工具
- 目前source Map支持12种
- 每种方式的功效和效率都不相同
webpack配置source Map (eval模式下的source Map)
- eval(‘console.log(123)’)
- eval(‘console.log(123) //# sourceURL=./foo/’)
- 将webpack.config.js中的属性设置为 devtool:‘eval’
- 使用eval 构建速度最快 但是不知道错误的行内信息
模式
- eval 将模块代码放到eval中但是不会生成对应的sourvcemao
- eval-source-map 生成sourceMAP
- cheap-eval-source-map 生成速度较快可以定义行错误信息不能定义到列,加工过后的结果,loader处理过后的结果
- cheap-module-eval-source-map 能够定义到行 没有经过加工手写的源代码
- inline-source-map和普通的相同inline-source-map以dataurl嵌入进来和eval相同 这个不常用因为体积较大
- hidden-source-map在这个模式下代码并没有看到引用,但是确实是已经生成了
- nosources-source-map 能看到错误信息,但是没有源代码开发过程中看不到源代码,为了在生产环境中不爆露源代码
webpack选择Source Map模式
- 开发环境打包cheap-eval-source-map
原因:
个人编写代码风格每一行不会超过80个字符,所以定义到行就可以了
使用框架比较多,loader转化之后和之前都有很多差别,所以要选择有module
首次打包启动会慢,但是重写打包速度较快
- none或者生产环境使用nosources-source-map
原因:
会暴露源代码
调试是开发阶段的事情,开发阶段尽量把所有的问题都找出来解决
## webpack dev server
webpack dev server刷新会对样式有影响,每次构建都需要刷新页面
HRM
- 开启HRM 集成在webpack-dev-server 中
第一种
## webpack-dev-server --host
第二种
##devServer:{
hot:true
// 如果出错可以报错不会自动刷新 hot:only
}
const webpack = require('webpack');
plugins:[
new webpack.HotModuleReplacementPlugin();
]
//启动 yarn webpack-dev-server --open
HMR疑惑
- webpack中的HRM需要手动配置
- 样式文件可以自动处理,js脚本不可以
HMR API
module.hot.accept('./editor',()=>{
})
webpack 不同环境下的配置
- 配置文件根据环境不同导出不同配置
module.exports = (env,argv){
//env cli传过来的参数
//argv 除了env之后的所有参数
if(env ==='production'){
config.mode = 'production'
config.devtool = false
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),
new CopywebpackPlugin(['public'])
]
}
return config
}
//运行 yarn webpack --env production
- 一个环境对应一个配置文件
// 一般有三个文件 一个线上一个开发 一个公共文件
// ## webpack.prod.js
const common = require(./webpack.common)
const merge = require('webpack-merge')
const { CleanWebpackPlugin} = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = merge(common,{
mode:'production',
plugins:[
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
})
//单独运行 yarn webpack --config webpack.prod.js
// ## webpack.dv.js
// ## webpack.common.js
// 公共配置
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:'./src/main.js',
output:{
filename:'js/bundle.js'
},
module:{
rules:[
{
test:'/.js$/',
use: {
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
},
},
{ //将css转换成js的模块
test:'/.css$/',
//这里需要注意 数组中的执行顺序是从右往左执行的 必须先解析css在加载style-loader
use:['style-loader',css-loader']
},
{
test:'/.png$',
// use:'file-loader'url
// use:'url-loader'
use:{
loader:'url-loader',
options:{
limit:10*1024 //10kb
}
}
},
{
test:'/.html$/',
loader:'html-loader'
//html中img src属性触发打包如果想让别的也触发可以进行如下配置
options:{
attrs:['img:src','a:href']
}
}
]
}
}
DefinePlugin
- 为代码注入全局成员 默认启动 默认为全局注册了 process.env.NODE_ENV
const webpack = require('webpack');
PLUGINS :[
new webpack.DefinePlugin({
API_BASE_URL:'"https://api.example.com"'
//JSON.stringigy 如果生产和开发的值不一样可以通过这个来注入
})
]
tree-shaking
在生产模式下自动开启
集中配置webpack的优化功能
module.exports = [
mode :'none',
entry:'./src/index.js',
output:{
filename:'bundle.js'
},
optimization:{ //优化
usedExports:true, //只标记使用的代码,
minimize:true, //把这些无用的摇掉
}
]
webpack 模块合并
optimization:{ //优化
concatenatModules:true
//注释:平时打包会有很多模块函数,这样合并成一个,尽可能将所有的模块合并一个模块被称为 Scope Hoisting 作用域的提升,既提升了运行效率,又减少了代码体积
}
Tree-shaking & Babel
Tree-shaking前提是ES Modles,由webpack打包的代码必须使用ESM,babel-loader处理代码时有可能会把esmodule转化成了comminjs 所以 tree-shaking就不会生效 在最新版本中babel支持了tree-shaking原因是 源代码中并没有转化esmodule
//自己配置 如果使用commonjs这个时候就不会执行tree-shaking
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:[['@babel/preset-env',{modules:'commonjs'}]
//{modules:'commonjs'} 就不会执行 将modules:false就会禁止转化esmodule 所以会执行tree-shking
}
}
}
sideEffects 副作用
模块执行除了导出成员之外所做的其他的事情 一般用于npm包标记是否有副作用
- 使用场景 在多模块开发中,加入我们将多个组件都放到index.js中引用 但是 我们在外部调用的时候只应用了其中的button组件 但是这个组件导入的是index.js这个时候打包会把所有的组件都打包进去这个时候我们就需要使用 optimization中的sideEffects
optimization:{
sideEffects:true, //打包看是否有标识看是否有副作用 如果没有则别移除
}
//在package.json中增加
"sideEffects":false //标识当前项目没有副作用
- 注意你的代码真的没有副作用 css都属于副作用
解决方法:
在package.json中
"sideEffects":['./extend.js','./extnd.css'] 数组进行配置
代码分割 Code Splitting
并不是每个模块在启动的时候都需要加载 要不然bundle的文件过大
- 根据不同需求 可分为多入口打包
- 动态导入按需加载
多页面打包 Multi Entry (多入口打包)
应用于多应用程序 一个页面提供一个打包入口 公共的可以提出
## 在entry 中进行配置多页面 记住是对象
entry:{
index:'./src/index.js',
album:'./src/album.js'
}
## 出口自动设置相应的文件
output:[name].bundle.js
## html中 new html-webpack-plgins中设置chunks:['index']另一个设置 chunks:['album']
公共模块的提取
多入口打包会存在公共的模块
例如index.js中和album.js中公共的模块使用了global.css的模块
optimization:{
splitChunks:{
chunks:'all'
}
}
MiniCssExtractPlugin提取css单位件
- 安装插件 yarn add mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
//style-loader //将样式是通过style标签注入
MiniCssExtractPlugin.loader,//如果文件小于150kb可以不用单独提出来
css-loader //css 解析
压缩样式文件
- 安装 yarn add optimize-css-assets-webpack-plugin --dev
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
plugins:[
new OptimizeCssAssetsWebpackPlugin(); //配资到plugins中 都可以使用
]
optimization:{
minimizer:[
new OptimizeCssAssetsWebpackPlugin(); //配置到这里 在没有开启压缩的时候插件就不会工作
//需要注意的是配置了minimizer 这个时候打包默认手动填写,需要配置js打包压缩不然js无法压缩
new TerserWebpackPlugin() //需要安装和引用
]
}
const TerserWebpackPlugin = ('terser-webpack-plugin')
输出文件名Hash
在生产模式下,文件名使用hash
output:{
//针对项目,有一个发生改变就会重新生成
filename:'[name]-[hash].bundle.js'
// 打包过程中同一类,改动哪里那个类js.css都会改变
filename:'[name]-[chunkhash].bundle.js'
//解决缓存最好的方法 每个文件都有hashcontenthash:8 也可以指定位数 默认20
filename:'[name]-[contenthash].bundle.js'
}
注释
-
output 输出的必须是绝对路径
-
webpack加载资源的方式:
- 遵循ES Modules标准的import声明
- 遵循CommonJS标准的require函数
- 遵循AMD标准的define函数和require函数
- 样式代码中的@import指令和url函数
- HTML代码中图片标签的src属性
-
webpack本身只能打包js文件,但对于其他资源例如css,图片,或者其他的语法集比如jsx,是没有办法加载的,这就需要对应的loader将资源转化,加载进来
-
TreeShaking在生产模式会自动开启,他是一组功能搭配使用后的效果,起哄usedExports负责标记(枯树叶),sideEffects一般标记npm包标记是否有副作用