概念
webpack是javascript应用程序的静态模块打包器,它把所有依赖打包成一个bundle.js文件,通过代码分隔成单元片段并按需加载
核心
entry:入口(告诉webpack从哪个入口文件开始构建)
output:出口(告诉webpack在哪里输出构建后的包,包的名称等)
loader:(webpack处理非javascript文件)
plugins:插件(处理各种任务,从打包优化,一直到重新定义环境中的变量)
webpack能做什么
-
语法转换
- less/sass/stylus转换成css
- ES6转换成ES5
-
html/css/js代码压缩合并(打包)
-
webpack可以在开发期间提供一个开发环境
- 自动打开浏览器
- 保存时自动刷新
-
项目一般打包再上线
webpack的基本使用
一、webpack基本打包配置
- 建目录 dist src/main.js
- 初始化
npm init -y
- 安装依赖包(-D将依赖记录成开发依赖,只是开发中需要用的依赖,实际上线不需要的)
npm add webpack webpack-cli -D
- 到package.json中,配置scripts
scripts:{
"build":"webpack --config webpack.config.js"
}
- 提供webpack.config.js
const path = require('path')
// 配置webpack的配置文件,需要将配置的对象导出 ,给webpack使用
module.exports = {
// 1. 入口
entry: './src/main.js',
// 2. 出口
output: {
// 打包输出目录(输出的目录必须是绝对路径)
path: path.join(__dirname, 'dist'),
// 打包后生成的文件名
filename: 'bundle.js'
},
// 3. 模式mode development未压缩的,production压缩
mode: 'development',
}
- 执行配置的scripts脚本
npm run build
二、使用webpack插件(处理index.html)
html-webpack-plugin(在index.html自动引入打包后的资源,不用手动引入)
- 安装
npm install html-webpack-plugin -D
- 在webpack.config.js中引入这个模块
const HtmlWebpackPlugin = require('html-webpack-plugin');
- 配置
// 5. 配置插件
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html'})
]
三、使用loader(webpack处理css文件)
1. css-loader/style-loader
- 安装
npm install style-loader css-loader -D
- 配置
// 4. 配置module模块加载规则
// 默认webpack只认识javascript、json。不认识其他文件,
如果希望打包处理其他文件,需要配置对应的loader
module: {
rules: [
{
// 正则,匹配所有以css结尾的文件
test: /\.css$/,
// 实际处理顺序,从右往左,先css-loader再style-loader
// css-loader让webpack能够识别解析css文件
// style-loader通过动态创建style标签的方式,让解析后的css内容,能够作用到页面中
use: ['style-loader', 'css-loader']
}
]
},
2. webpack分离css文件
当我们把css文件放在style标签,请求次数是少了。
但如果css文件太大,会导致bundle.js文件也过大。 所以有什么办法将css文件分离出来呢?
* 有一个插件mini-css-extract-plugin新版本使用(webpack4.x)
* extract-text-webpack-plugin旧版本使用(webpack3.x)
mini-css-extract-plugin插件
- 安装
npm install --save-dev mini-css-extract-plugin
- 引入
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
- 配置(将style-loader替换掉,头部就会link引入一个css文件)
rules: [
{
// 正则,匹配所有以css结尾的文件
test: /\.css$/,
// 实际处理顺序,从右往左,先css-loader再style-loader
// css-loader让webpack能够识别解析css文件
// style-loader通过动态创建style标签的方式,让解析后的css内容,能够作用到页面中
// use: ['style-loader', 'css-loader']
use: [{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
},
}, 'css-loader']
}
]
- 配置插件
plugins: [
// 定义打包好的文件的存放路径和文件名
new MiniCssExtractPlugin({
filename: 'css/index.css'
})
]
四、webpack处理less文件
- 安装less与less-loader
npm install less less-loader -D
- 配置
// 配置less
{
test: /\.less$/,
use: [{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
},
}, 'css-loader', 'less-loader']
}
五、webpack配置图片的加载
webpack需要转换图片的loader来处理图片的问题,主要用到url-loader和file-loader
注意:url-loader中的部分功能要用到file-loader,要下载两个模块
- 安装
npm install url-loader file-loader -D
- 配置
// (3)配置图片文件解析 i表示忽视大小写
{
test: /\.(png|jpg|gif)$/i,
use: [
// url-loader如果不配置,默认都会将图片转换为base64字符串的格式(节约请求的次数)
{
loader: 'url-loader'
}
]
}
-
url-loader图片默认转换为base64字符串格式
- 好处就是浏览器不用发请求了,可以直接读取
- 坏处就是如果图片太大,再转base64就会让图片体积增加30%左右,得不偿失
- 所以需要通过options配置选项进行配置limit,可以设置一个临界点,大于这个值,会整个文件直接打包到目录中,得到的路径。小于这个值,就会转换成base64,节约请求的次数
{
loader: 'url-loader',
// 8k以内,转换base64。8k以外,单独一个文件请求
options: {
limit: 8 * 1024
}
}
六、清除dist目录
每次打包自动清除上一次打包dist文件的内容
- 安装
npm install clean-webpack-plugin -D
- 导入
// 导入清除插件,可以在每次打包之前,清除dist目录的内容
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
- 配置插件
plugins: [
// 清除dist目录的插件
new CleanWebpackPlugin()
]
七、配置图片输出目录
options: {
limit: 8 * 1024,
// 配置文件的输出名
name: '[name].[ext]',
// 配置静态资源的引入路径
publicPath: "../images/",
// 配置输出文件的目录
outputPath: 'images/'
}
webpack开发服务器
webpack使用babel处理高版本的js语法的兼容性
ES6转ES5
- 安装
npm install -D babel-loader @babel/core @babel/preset-env webpack
- 配置规则
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
开发服务器配置(不用每次都执行npm run build)
webpack-dev-serve(开发服务器,保存后自动刷新)
- 安装
npm install webpack-dev-server -D
- 配置scripts
"scripts": {
"build": "webpack --config webpack.config.js",
"dev": "webpack-dev-server --config webpack.config.js"
},
- webpack-dev-server额外配置
// 配置开发服务器
devServer: {
port: 3000, // 配置端口号
open: true // 自动打开浏览器
}
生产环境和开发环境
开发环境在本地运行。生产环境运行在服务器,给用户使用的代码。因此两者构建目标差异很大,比如打包后的文件在生产环境中要尽可能小,逻辑代码分离,优化静态资源等。
因此生产环境和开发环境不能共用一份webpack配置文件,需要分别指定
但两个环境有很多配置是可以共用的,比如entry、output、module等,因此可以把公共部分的配置抽离出来放到一个独立的文件进行合并,我们可以使用webpack-merge工具来进行合并
- 安装
npm install webpack-merge -D
-
拆分webpack.config.js(拆分后这个文件就不要了)
- 新建config文件夹
-config //存放配置文件的文件夹
-webpack.base.js //公共的配置
-webpack.dev.js //开发环境的配置
-webpack.pro.js //生产环境的配置
- 配置文件
config/webpack.base.js
// 存放公共配置
const path = require('path')
// 引入自动生成html插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入分离css文件的模块
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 导入清除插件,可以在每次打包之前,清除dist目录的内容
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
// 配置webpack的配置文件,需要将配置的对象导出 ,给webpack使用
module.exports = {
// 入口
entry: './src/main.js',
// 出口
output: {
// 打包输出目录(输出的目录必须是绝对路径)
path: path.join(__dirname, '../dist'),
// 打包后生成的文件名
filename: 'js/bundle.js'
},
// 配置module模块加载规则
// 默认webpack只认识javascript、json。不认识其他文件,如果希望打包处理其他文件,需要配置对应的loader
module: {
rules: [
// (1)配置css文件解析
{
// 正则,匹配所有以css结尾的文件
test: /\.css$/,
// 实际处理顺序,从右往左,先css-loader再style-loader
// css-loader让webpack能够识别解析css文件
// style-loader通过动态创建style标签的方式,让解析后的css内容,能够作用到页面中
// use: ['style-loader', 'css-loader']
use: [{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
},
}, 'css-loader']
},
// (2)配置less文件解析
{
test: /\.less$/,
use: [{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
},
}, 'css-loader', 'less-loader']
},
// (3)配置图片文件解析 i表示忽视大小写
{
test: /\.(png|jpg|gif)$/i,
use: [
// url-loader如果不配置,默认都会将图片转换为base64字符串的格式(节约请求的次数)
{
loader: 'url-loader',
// 8k以内,转换base64。8k以外,单独一个文件请求
options: {
limit: 8 * 1024,
// 配置文件的输出名
name: '[name].[ext]',
// 配置静态资源的引入路径
publicPath: "../images/",
// 配置输出文件的目录
outputPath: 'images/'
}
}
]
},
// (4)配置对于高版本js的兼容性处理
{
test: /\.m?js$/,
// 配置排除项
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 配置插件
plugins: [
// 自动生成HTML的插件
new HtmlWebpackPlugin({ template: './public/index.html'}),
// 分离css插件,定义打包好的文件的存放路径和文件名
new MiniCssExtractPlugin({
filename: 'css/index.css'
}),
// 清除dist目录的插件
new CleanWebpackPlugin()
],
}
config/webpack.dev.js
// 开发环境的配置
// 导入公共配置
const base = require('./webpack.base.js')
// 导入用于合并的一个包
const merge = require('webpack-merge')
// 导出开发环境的一个配置
// merge可以传入多个参数,将多个参数合并成一个对象
// 如果有重复的属性名,后面的属性会覆盖前面的
module.exports = merge(base,{
devServer: {
port: 3000,
open: true //自动打开浏览器
},
mode: 'development'
})
config/webpack.pro.js
// 生产环境的配置
// 导入公共配置
const base = require('./webpack.base.js')
// 导入用于合并的一个包
const merge = require('webpack-merge')
// 导出开发环境的一个配置
// merge可以传入多个参数,将多个参数合并成一个对象
// 如果有重复的属性名,后面的属性会覆盖前面的
module.exports = merge(base,{
mode: 'production'
})
多入口多出口
修改entry和output
// 入口
entry: {
index: './src/index.js',
about: './src/about.js'
},
// 出口
output: {
// 打包输出目录(输出的目录必须是绝对路径)
path: path.join(__dirname, '../dist'),
// 打包后生成的文件名
filename: 'js/[name].bundle.js' //[name]指代entry中对象的键名
},
提取公共模块
以上情况会导致公共情况被重复引用,比如about.js和index.js重复打包了jquery,所以需要提取公共模块
// 提取公共模块配置
optimization: {
splitChunks: {
chunks: 'all' // 提取所有文件的共同模块
}
}
公共模块的大小必须得大于30kb才会独立打包
webpack处理vue
vue-loader的配置
- 安装
npm install -D vue-loader vue-template-compiler
- webpack配置
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
module: {
rules: [
// ... other rules
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
// make sure to include the plugin!
new VueLoaderPlugin()
]
}
vue-cli4脚手架环境
- 先通过脚手架创建项目
vue create vue-mobile
- 在项目根目录新建 vue.config.js 进行配置, 这个vue.config.js 会覆盖默认cli的webpack配置, 非常方便
module.exports = {
devServer: {
port: 3000,
open: true
}
}
- 运行项目
npm run serve
rem 布局 - 插件 postcss-pxtorem的配置
- 安装插件
npm install lib-flexible postcss-px2rem
- 在 public 中的 index.html 中删除 meta 标签
flexible会为页面根据屏幕自动添加<meta name='viewport'>标签,
动态控制initial-scale,maximum-scale,minimum-scale等属性的值。
- 在 src / main.js 中导入插件包
// 导入 rem 的 js, 动态的设置了, 不同屏幕的html根元素的 font-size
import 'lib-flexible'
- 配置 vue.config.js
module.exports = {
devServer: {
port: 3000,
open: true
},
// rem 的配置
css: {
loaderOptions: {
css: {},
postcss: {
plugins: [
require('postcss-px2rem')({
// 适配 375 屏幕, 设计图750中量出来的尺寸要 / 2
// 配置成 37.5 是为了兼容 没有适配 rem 布局的第三方 ui 库
remUnit: 37.5
})
]
}
}
}
}
反向代理的配置说明
webpack的反向代理, 可以起一个临时的代理服务器, 帮助解决在开发过程中的跨域问题, 就算跨域了也能拿到后台的数据
- 安装axios,发送请求
npm install axios
- 发送请求
import axios from 'axios'
export default {
async created () {
const url = `/music/getmv_by_tag?g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=GB2312¬ice=0&platform=yqq.json&needNewCode=0&cmd=shoubo&lan=all`
const res = await axios.get(url)
console.log(res)
}
}
- 配置代理 (配置vue.config.js文件)
module.exports = {
devServer: {
port: 3000,
open: true,
proxy: {
'/music': {
target: 'https://c.y.qq.com/mv/fcgi-bin/',
pathRewrite: { '^/music': '' }
}
}
},
// rem 的配置
// ....
}
webpack常见面试题
1. 有哪些常见的 loader?他们能解决什么问题?
定义: loader 用于对模块的源代码进行转换。
loader 可以使你在 import 或"加载"模块时预处理文件。
* style-loader 将css添加到标签style里
* css-loader 允许将css文件通过require的方式引入,并返回css代码
* less-loader 处理less
* sass-loader 处理sass
* postcss-loader 用postcss来处理CSS
* file-loader 分发文件到output目录并返回相对路径
* url-loader 和file-loader类似,但是当文件小于设定的limit时可以转换为base64
* html-minify-loader 压缩HTML
* babel-loader 用babel来转换ES6文件到ES5
* eslint-loader:通过 ESLint 检查 JavaScript 代码
* image-loader:加载并且压缩图片文件
2. 有哪些常见的Plugin?他们是解决什么问题的?
* define-plugin:定义环境变量
* commons-chunk-plugin:提取公共代码
* uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
3. loader和plugin的不同
不同的作用
Loader直译为"加载器"。Webpack将一切文件视为模块,但是webpack原生是只能解析js
文件,如果想将其他文件也打包的话,就会用到loader。
所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。
Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在
Webpack 运行的生命周期中会广播出许多事件,Plugin
可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
不同的用法
Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。
类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),
使用什么加载(loader)和使用的参数(options)
Plugin在plugins中单独配置。
类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。
4. webpack 的构建流程是什么?从读取配置到输出文件的整个过程
webpack运行流程是一个串行的过程
-
- 初始化参数:从配置文件和Shell语句中读取合并参数,得出最终参数
-
- 开始编译:用上一步得到的参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译
-
- 确定入口:根据配置的entry找出所有的入口文件
-
- 编译模块:从入口文件出发,调用所有配置的Loader对模块进行翻译,再找出该模块依赖的模块,再递归本步骤知道所有入口依赖的文件都经过了本步骤的处理
-
- 完成模块编译:再经过使用Loader翻译完所有的模块后,得到了每个模块被翻译后的最终内容以及他们之间的依赖关系
-
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
-
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
5. 是否写过 loader 和 Plugin ?描述一下编写 loader 或 Plugin 的思路?
loader像一个翻译官把读到的源文件内容转义成新的文件内容,
并且每个loader通过链式操作,将源文件一步步翻译成想要的样子
编写Loader时要遵循单一原则,每个Loader只做一种"转义"工作。
每个Loader的拿到的是源文件内容(source),可以通过返回值的方式将处理后的内容
输出,也可以调用this.callback()方法,将内容返回给webpack。 还可以通过
this.async()生成一个callback函数,再用这个callback将处理后的内容输出出去。
此外webpack还为开发者准备了开发loader的工具函数集——loader-utils。
相对于Loader而言,Plugin的编写就灵活了许多。
webpack在运行的生命周期中会广播出许多事件,Plugin
可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
6. webpack 的热更新是如何做到的?说明其原理?
webpack的热更新又称热替换(Hot Module Replacement),缩写为HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
第一步,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,
webpack 监听到文件变化,根据配置文件对模块重新编译打包,
并将打包后的代码通过简单的 JavaScript 对象保存在内存中。
第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,
主要是 dev-server 的中间件 webpack-dev-middleware 和
webpack 之间的交互,webpack-dev-middleware 调用 webpack
暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。
第三步是 webpack-dev-server对文件变化的一个监控,这一步不同于第一步,
并不是监控代码变化重新打包。
当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,
Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对
应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。
第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过
sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个
websocket 长连接,将 webpack编译打包的各个阶段的状态信息告知浏览器端,
同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些
socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的
hash 值,后面的步骤根据这一 hash 值来进行模块热替换。
webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,
而把这些工作又交回给了webpack,webpack/hot/dev-server 的工作就是根据
webpack-dev-server/client传给它的信息以及 dev-server
的配置决定是刷新浏览器呢还是进行模块热更新。
当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。
HotModuleReplacement.runtime 是客户端 HMR
的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过
JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,
服务端返回一个 json,该 json 包含了所有要更新的模块的 hash
值,获取到更新列表后,该模块再次通过 jsonp
请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。
而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,
HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,
在决定更新模块后,检查模块之间的依赖关系,
更新模块的同时更新模块间的依赖引用。
最后一步,当 HMR 失败后,回退到 live reload
操作,也就是进行浏览器刷新来获取最新打包代码。
7. 如何利用 webpack 来优化前端性能?(提高性能和体验)
用webpack优化前端性能是指优化webpack的输出结果,
让打包的最终结果在浏览器运行快速高效。
压缩代码。删除多余的代码、注释、简化代码的写法等等方式。
可以利用webpack的UglifyJsPlugin和ParallelUglifyPlugin来压缩JS文件
利用cssnano(css-loader?minimize)来压缩css
利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。
可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径
删除死代码(Tree Shaking),将代码中永远不会走到的片段删除掉。
可以通过在启动webpack时追加参数--optimize-minimize来实现提取公共代码。
8. 如何提高webpack的构建速度?
1. 多入口情况下,使用CommonsChunkPlugin来提取公共代码
2. 通过externals配置来提取常用库
3. 利用DllPlugin和DllReferencePlugin预编译资源模块
通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,
再通过DllReferencePlugin将预编译的模块加载进来。
4. 使用Happypack 实现多线程加速编译
5. 使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。
原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度
6. 使用Tree-shaking和Scope Hoisting来剔除多余代码
9. 如何在vue项目中实现按需加载?
Vue UI组件库的按需加载
为了快速开发前端项目,经常会引入现成的UI组件库如ElementUI、iView等,但是他们的体积和他们所提供的功能一样,是很庞大的。 而通常情况下,我们仅仅需要少量的几个组件就足够了,但是我们却将庞大的组件库打包到我们的源码中,造成了不必要的开销。
不过很多组件库已经提供了现成的解决方案,如Element出品的babel-plugin-component和AntDesign出品的babel-plugin-import 安装以上插件后,在.babelrc配置中或babel-loader的参数中进行设置,即可实现组件按需加载了。
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
10. webpack打包原理
1. 原理
把所有依赖打包成一个bundle.js文件,通过代码分隔成单元片段并按需加载。
2. 核心概念
入口
entry,告诉webpack从哪个入口文件开始构建
出口
output,告诉webpack在哪里输出构建后的包,包的名称等
loader
让webpack处理非javascript文件
plugin
处理各种任务,从打包优化,一直到重新定义环境中的变量
mode
告诉webpack使用相应模式的内置优化
target
webpack能够为多种环境和target构建编译
source map
定位代码中的错误
11. webpack3和webpack4的区别
1. webpack增加了一个mode配置,只有两种值development
production。对不同的环境他会启用不同的配置
2. webpack.optimize.CommonsChunkPlugin已经从webpack4中移除。
可使用optimization.splitChunks进行模块划分(提取公用代码)
3. webpack4,删除原 extract-text-webpack-plugin 配置,增加
mini-css-extract-plugin 配置
4. 安装依赖,新版 babel 使用新的命名空间 @babel
5. vue-loader注意要配合一个 webpack 插件才能正确使用
6. UglifyJsPlugin
现在也不需要使用这个plugin了,只需要使用optimization.minimize为true就行,
production mode下面自动为true
optimization.minimizer可以配置你自己的压缩程序
7. 移除loaders,必须使用rules(在3版本的时候loaders和rules
是共存的但是到4的时候只允许使用rules)
8. 升级happypack插件(happypack可以进行多线程加速打包)
运行在node.js之上的webpack时单线程模型,也就是只能一个一个文件进行处理,不能
并行处理,happypack可以将任务分解给多个子进程,最后将结果发给主进程,js是单线
程模型,只能通过这种多线程的方式提高性能
vue-loader 不支持 HappyPack,官方建议用 thread-loader
12. 什么是bundle,什么是chunk,什么是module?
bundle是由webpack打包出来的文件
chunk是指webpack在进行模块的依赖分析的时候,代码分割出来的代码块。
module是开发中的单个模块