- 工作模式
- yarn webpack --mode [opition]
- production 自动启动优化选项,将打包后的文件压缩
- development 自动优化打包的速度,添加调试的辅助
- no 使用最原始的大包方式,不会做任何处理
- 另外一种配置选择工作模式,可以直接在配置文件中配置
- yarn webpack --mode [opition]
const path = require('path')
module.exports = {
mode: 'development', // 模式
entry: './src/index.js', // 入口文件,可以相对路经
output: {
filename: 'bundle.js', // 打包文件名,默认是dist文件下的main.js
path:path.join(__dirname,'dist') // 必须是绝对路径
}
}
- 资源模块加载
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/main.css',
output: {
filename: 'bundle.js',
path:path.join(__dirname,'dist')
},
module: {
rules:[
{
text:/.css$/, // 正则表达式,用来匹配在打包过程中所遇到的文件路径,以.css结尾
use: 'css-loader', // 用来处理匹配的文件使用的loader
}
]
}
}
- 添加上述配之后,打包之后的css文件并没有生效,原因:css-loader 只是将样式转换成js模块,这样才能正常打包,但是并没有被使用
- yarn add style-loader --dev 添加style-loader 会将css-loader 转换后的结果,以标签的形式 追加到页面上
3. loader是webpack的核心,通过不同的loader就可以加载任何类型的资源
- 导入资源模块
import createHeading from './heading'
import './main.css' // 导入资源文件
const heading = createHeading()
document.body.append(heading)
5.文件资源加载器 - 大多数加载器都是像cssLoad一样 将文件转化成js,但是有些资源例如字体,图片,是没有办法通过js的方式去表示,则对于这种资源,需要用到文件资源加载器 - yarn add file-loader --dev
module: {
rules:[
{
test:/.css$/, // 正则表达式,用来匹配在打包过程中所遇到的文件路径,以.css结尾
use:[ 'style-loader','css-loader'], // 用来处理匹配的文件使用的loader
},
{
test: /.png$/,
use: 'file-loader'
}
]
}
- webpack 因为index.html文件并没有生成到dist路径下,webpack默认生成的img图片和index.html同级,可通过publicPath 配置,publicPath默认为空,代表根目录,
output: {
filename: 'bundle.js',
path:path.join(__dirname,'dist'),
publicPath: 'dist/' // 代表根目录下得dist文件下得路径位置
},
- URL加载器 (和文件资源加载器一样可以加载静态资源) - Data URLs 直接表示一个文件,既不会发http请求,也不会生成独立文件 - yarn add url-loader - 这种方式比较适合体积比较小的资源,如果资源比较大的情况,就会导致打包结果比较大,从而影响运行速度 - 小文件使用这种方式Data URLs 打包,减少请求次数 - 大文件单独提取存在,提高加载速度
{
// 超出10kb会使用file-loader的时候打包,不然使用url-loader,所以使用url-loader的方式时,也要同时安装file-loader
test: /.png$/,
use: {
loader: 'url-loader',
size: 10 * 1024 ,// 限制10kb时候,应乘以1024,因为单位默认是字节,
}
}
- 常见加载器分类
- 编译转换类 如 css-loader 将css转换成js模块
- 文件操作类 如 file-loader
- 代码检查类 如 eslint-loader
- webpack与ES2015
- 由于webpack默认就可以处理import 与export ,但是webpack不能编译es6的代码,因为模块打包需要,所以webpack裁处理了import与export
- yarn add bable-loader @babel/core @babel/preset-env --dev
{
test: /.js$/,
use: {
// babel-loader 只是提供了es6转换新特性的平台,具体需要怎么转换还需要引入options中提供的插件
loader:'babel-loader',
options: ['@babel/preset-env']
}
}
- webpack 加载资源的方式
- 通过ES Module标准的import声明
- 通过 CommonJs 标准的 require函数
- webpack 核心工作原理
- loader机制是webpack的核心
- 如果没有loader webpack只能是合并js代码的工具
- webpack开发一个loader
- 每一个loader都是一个函数,接受一个要转换的参数,返回一个结果,但是返回的结果必须是一个字符且符合js语法
- loader负责资源从输入到输出的转换,对同一个资源可以依次使用多个loader
{
test: /.md$/,
use:['html-loader', './markdown-loader.js'] // html-loader 将markdowen-loader导出的字符串 传递给html-loader,转换成js代码
}
const {marked} = require('marked')
module.exports = (source) => {
const html = marked(source)
return `module.exports = ${JSON.stringify(html)}`
// 或者
return `exports default = ${JSON.stringify(html)}`
}
- 插件机制
- 目的是为了增强webpack自动化能力
- loader专注实现资源模块加载,从而实现整体项目的大包,而Plugin解决其他自动化的工作
- 比如 Plugin实现在每一次打包之前 清除dist目录,拷贝不需要打包的资源到输出目录,或者压缩打包的代码
- webpack+ Plugin实现大多前端工程化工作
- webpack 常见插件
- clean-webpack-plugin 自动清除输出目录,清除不需要的东西
- html-webpack-plugin 自动生成使用bundle.js(output属性配置的输出路径)的html,wbpack知道自己生成了多少个bundle.js 文件,并自动在生成的html文件中引用
- 同时输入多个页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= htmlWebpackPlugin.options.title %></title>
// 这种语法对应lodash.template
</head>
<body>
<script src="../dist/bundle.js" ></script>
</body>
</html>
plugins: [
new htmlWebpackPlugin({
title: 'webpack chenyun update',
meta: {
viewport:'width=device-width'
},
template: './src/index.html',
})
]
多文件
new htmlWebpackPlugin({
title: 'webpack chenyun update',
meta: {
viewport:'width=device-width'
},
template: './src/index.html',
}),
new htmlWebpackPlugin({
filename:'about.html',
template: './src/index.html',
})
- Plugin插件核心原理
- Plugin通过钩子机制实现
- 必须是一个函数或者是一个包含apply方法的对象
- apply 方法会在webpack启动时自动调用,传入的参数包含了这次构建的所有配置信息
- 插件通过在生命周期的钩子中挂载函数实现扩展
class MyPlugin {
apply(compiler){
console.log('webPack 启动的时候,该方法自动调用');
// emit 是钩子函数,MyPlugin是插件名称,箭头函数式挂载的函数
compiler.hooks.emit.tap('MyPlugin',compilation => {
// compilation 可以理解为此次打包的上下文
for(let name in compilation.assets) {
console.log(name);
if(name.endsWith('.js')){
const contents = compilation.assets[name].source()
const withoutContents = contents.replace(/\/\*\*+\*\//g,'') // 去掉打包文件的注释
compilation.assets[name]={
source:()=> withoutContents,
size: ()=>withoutContents.lenght
}
}
}
})
}
}
- webpack自动编译
- yarn webpack -- watch,监听文件是否发生改变,一旦改变就自动编译
- webpack自动刷新浏览器
- 安装一个插件 自己百度
- webpack dev serve
-
webpack Dev Serve 提供用户开发的http server,集成自动编译和自动刷新浏览器等功能
-
yarn add webpack-dev-server --dev
-
yarn webpack-dev-serve 启动
-
webpack-dev-serve 为了提高开发效率,并不会每次编译打包都会生成到dist目录,将打包结果暂时存在在内存当中,而http server又从内存当中将文件读出来,发送给浏览器,这样就不会频繁的读写磁盘操作,从而提高工作效率
-
yarn webpack-dev-serve open 自动唤起浏览器,打开运行地址
-
- webp-dev-serve 配置静态资源
- 主要是webpack的输出文件,都可以直接被访问,如果有其他静态资源也需要作为开发服务器的资源被访问到,需要额外告诉webpack-dev-serve
- devServe 属性就是专门为webp-dev-serve配置的选项
devServe: {
// 指定静态资源路径
contentBase:'./public'
},
- webpack-dev-serve 代理api
- 跨域资源共享(cors),使用cors的前提是api必须支持,并不是任何情况下api都支持,前后端如果同源部署,并没有必要去部署cors
- 支持配置代理
devServe: {
contentBase: './public',
proxy: {
'/api': {
// http//:localhost:8080/api/users -> https://api.github.com/api/users
target: 'https://api.github.com',
// 实际请求的地址是:https://api.github.com/users,则需要重写路径
pathRewrite: {
'/^api': '' // 以api开头的 替换成""
},
// 不使用localhost:8080 作为请求github的主机名
changeOrign: true
}
}
},
- source Map 源代码地图
- sourece map 解决了源代码与运行代码不一致产生的问题,可以将线上的运行代码转换成源代码 22.webpack配置sourece Map
- 有12种模式,每种方式的效率和效果不同
- devtool 就是配置source map
devtool:'soure-map',
- eval模式
eval可以运行传递给它的字符串,比如eval('console.log(111)')
devtool:eval模式,不能生成sourceMap只能定位出现问题的源代码的文件名称,但是无法定位到具体的位置行列信息
- eval source map
生成sourceMap,能够准确定位行列信息
- cheap eval source map
生成sourceMap,但是只能定位到行信息,不能定位到列信息
- cheap module eval source map
和cheap eval source map效果一样,不过cheap eval source map 生成的map代码 已经将es6转换成es5代码,而cheap module eval source map没有
- inline-source-map
将生成的source map代码嵌入到源代码中,这种一般不会使用,因为这会使打包的代码体积会变大很多
- nosoucer-souce-map
能定位行信息,但是看不到源代码
- ...
- 根据个人习惯不同的开发环境选择不同的模式
- 本地开发模式 选择 eval source map,能够准确定位信息
- 线上环境 选择no或者nosouces-souce-map 会暴漏源代码,防止别有用心之人,线上测试环境更倾向于nosouces-souce-map ,这样不会暴漏源代码,虽然看不到源代码,但是可以定位出现问题的行数
-
HMR(hot module Replacement) - 模块热替换或者模块热更新
webpack 自身的刷新机制,页面状态会丢失 webpack的模块热替换主要指 应用运行过程中实时替换某个模块,应用运行状态不受影响 HMR已经集成在webpack-dev-server- webpack的HMR并不是开箱即用,需要我们手动处理模块热替换逻辑,但是开启热更新后,样式文件的热更新是可以使用的,因为style-loader已经手动处理热更新 。样式模块的替换是有规律的,直接替换掉样式的值就行,所以有一个统一的替换方案,style-loader顺便给处理了。但是js是没有规律的,所以需要我们自己手动处理。
- 当我们使用框架搭建项目的时候,不需要手动处理热更新了,因为框架下的每种文件都是有规律的,框架内部已经继承了HRM方案
- HMR APIs
module.hot.accept(a,b)module.hot.accept 是HMR api的核心,参数a代表以来的模块,b代表当依赖的模块发生改变后的处理函数 - 处理js模块热替换
module.hot.accept('/editor',()=> { const value = lastEditor.innerHTML document.body.removeChild(lastEditor) const newEditor = createEditor() newEditor.innerHTML = value document.body.appendChild(editor) lastEditor = newEditor }) - 处理图片的热替换
module.hot.accept('./better.png',()=> { img.src = background })
4.HMR注意事项
- hot:true,hotOnly的区别
devServer: { static:'./', host: "localhost", port: 8080, hot:true }, plugins: [ new htmlWebpackPlugin(), new webpack.HotModuleReplacementPlugin() ]处理HMR的代码如果出现错误会自动刷新,自动刷新之后,页面的错误代码也会被清除,报错信息也会消失,这样的话不容易发现出错的位置. 为了能够找到处理HMR代码出现错误的地方,将hot:true,改为hot:'only';,即可
-
如果插件中未实例化 HotModuleReplacementPlugin插件,则直接使用module.hot方法会报错,所以使用module.hot方法之前应该先判断下。
-
不同环境下的webpack配置
-
配置文件根据环境的不同导出不同的配置
module.exports = (env,args)=> { let config = { module: { rules: [ { test:/\.(png)$/, use:'file-loader' } ], }, } // 公共配置信息 if(env == 'production'){ config.mode = 'production' config.devtool = false ... }else { } }或者直接在module.export = { 通过全局变量判断 if(process.env.NODE_ENV == 'production'){} }- 一个环境一个配置文件
为什么不用Object.assign,因为Object.assign 该函数,后面的对象会把前一个对象的同名属性完全替换,而目前我们只是想合并同名属性,可以使用webpack-merge提供的函数,将多个配置信息合 并
yarn webpack --config webpack.prod.js -
-
DefinePlugin插件 为代码注入全局成员
plugins: [
new htmlWebpackPlugin(),
new webpack.HotModuleReplacementPlugin(),
new CleanWebpackPlugin(),
// 值必须用引号包裹一层,即使是字符串
new webpack.DefinePlugin({
// API_BASE_URL:'http:api.example.com'
API_BASE_URL: "'http:api.example.com'" // JSON.stringify('http:api.example.com')
})
]
-
Tree-shaking 摇掉代码中没有被用到的部分
webpack中的配置,如果mode是production,就自动开启这个功能
- Tree-shaking不是指某个配置的选项,而是一组功能搭配使用后的优化效果,这种效果会在生产模式下自动启用
optimization:{
usedExports: true,// 只导出用到的引用
concatenateModules:true,//尽量将所有的打包模块合并输出到一个函数中,即提升了运行效率,又会减少运行体积
minimize:true // 移除未引用变量的定义代码
}
使用yarn webpack -- no 就可以看到和生产模式一样的效果
-
代码分割/代码分隔
-
方式1:多入口打包
适用于多页面程序,一个页面对应一个打包入口,不同页面对应的公共部份单独提取
-
entry:{
index:'./src/index.html',
album:'./src/album.html'
},
output: {
filename: '[name].bundle.js',
path: path.join(__dirname,'dist')
},
plugins:[
htmlWebpackPlugin生成的html文件默认引入打包的所有js文件,如果不想这样,可以通过chunks指定引入哪一个打包后多文件,chunks中的值对应output中的name,而output中的name又对应entry中的键值
new htmlWebpackPlugin({
title:'Mulity-Entry ',
template:'./src/index.html',
filename:'index.html',
chunks:['index']
}),
new htmlWebpackPlugin({
title:'Mulity-Entry ',
template:'./src/album.html',
filename:'album.html',
chunks:['album']
}),
]
index.html和album.html两个文件都使用了global.css和fetch.js,我们可以将其单独打包
optimization:{
splitChunks:{
chunks:all
}
}
会生成ablum-index.bundle.js文件,公共的文件都会被打包到这里
-
方式2:动态导入:需要用到哪一个模块,再加载这个模块
动态导入的模块会被自动分包,相对于多入口打包,这种方式更加灵活
yarn webpack,自动分包,如下生成了三个bundle.js,其中两个bundle.js 分别对用album,index两个组件,另外一个bundle.js 对应 album,index两个组件的公共部分。单页面应用可以使用这种方式
28.MinCssExtractPlugin插件 提取css到单个文件
- yarn add min-css-extra-plugin -dev
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module: {
rules: [
{
test:/\.(png)$/,
use:'file-loader'
},
{
test:/\.css$/,
use:[
MiniCssExtractPlugin.loader,
css-loader
]
}
],
},
plugins:[
new MiniCssExtractPlugin(),// 自动提取打包后的css文件到单独的一个文件中
]
new MiniCssExtractPlugin(),
经过css-loader,style-loader处理后的结果,通过style标签的形式注入到页面当中,从而使样式可以工作,而如果使用MiniCssExtractPlugin插件进行处理,则会生成单独的文件,并以link的形式注入到文件。这两种打包css的方式,可以分体积进行区分,如果css比较大的话,使用该插件,不然还是使用style-loader处理比较好,这样可以减少一次请求
- optimize-css-assets-webpack-plugin 压缩代码 上面的css抽取插件,在生产模式打包后,会发现css文件并没有被压缩,这是因为webpack内置的压缩功能只是用来针对js文件,对于其他文件的压缩都需要额外的插件来支持
不要将该插件配置在plugins中,可以配置在optimization中的minimizer中,搭配minimize:true(该属性在生产模式下自动开启),这样css压缩才会生效。如果开发模式下我们不设置minimize,则不会生效。如果将该插件配置在plugins中,则在任何模式下都会生效,有时候开发模式我们并不想压缩代码
optimization:{
如果我们使用了minimizer压缩器这个属性,则webpack认为我们自定义使用哪一种压缩器。如果我们仅仅实例化OptimizeCssAssetsPlugin这个插件,则js就不会被压缩,js压缩要通过terser-webpack-plugin
minimizer:[
new OptimizeCssAssetsPlugin(),
new TerserWebpackPlugin()
],
},
-
terser-webpack-plugin 压缩js代码的 看28