在三大框架潮流的推动下,大大小小的SPA
单页面应用层出不穷,工程化 / 模块化 / 自动化 渐渐成为开发的核心思想,但是他们都有一个特点:
源代码无法在浏览器里直接运行,必需通过编译才行
因此也带来了很多构建工具的兴起;诸如具有代表性的 Gulp
、Grunt
、webpack
等等
今天,我们具体介绍 webpack 4.X
,webpack
也从V1过渡到V4,不久之后发布V5版本( lz学不动了!!!)
一、初始化安装
建议node
版本在 5.0以上
npm init // 可选属性创建 | npm init -y // 自动创建
npm i webpack -D
npm i webpack-cli -D
安装完,执行 webpack -v
| webpack-cli -v
,验证是否安装成功
二、搭建项目
这里我们基础配置index.html
用于测试我们的打包后的效果,webpack.config.js
用来配置编译需求,src
项目源码,package.json
项目基本配置
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webpack 4.X</title>
</head>
<body>
<div id="root"></div>
<script src="./dist/build.js"></script>
</body>
</html>
webpack.config.js
webpack
采用CommonJS
的规范,moudle.exports
导出
const path = require('path') // node提供的path工具,用来做路径的拼接、转换等
const fs = require('fs') // node提供的fs文件系统,用来操作文件、文件夹等
module.exports = {
mode: 'development ',
entry: '',
output: {},
module: {},
plugins: [],
devServer: {}
}
src
– index.js
入口文件
– home.js
测试多入口文件
– assets
静态资源
:::-- img
图片
:::-- font
字体文件
:::-- media
音视频文件
:::-- css
样式文件
package.json
这里我们使用 npm run test 命令启动 webpack 编译
...
"scripts": {
"test": "webpack -p --progress --color --config webpack.config.js"
},
webpack
执行命令之后可以添加一些参数,下面是参数列表:
参数名 | 作用 |
---|---|
webpack --config XXX.js | 使用另一份配置文件来打包 |
webpack --watch | 监听变动并自动打包 |
webpack -p | 压缩混淆脚本,这个非常非常重要! |
webpack -d | 生成map映射文件,告知哪些模块被最终打包到哪里 |
webpack --progress | 显示进度条 |
webpack --color | 添加颜色 |
三、基础配置
webpack
从 编译 -> 输出 -> 运行 等等都是由很多配置内容完成
-
1.
mode
:模式----4.X新增,配置当前环境 -
2.
entry
:入口----要打包的文件 -
3.
output
:出口----配置编译完成目录 -
4.
module
:模块----浏览器不识别的文件 -
5.
plugins
:插件----hook函数辅助开发,提高开发效率 -
6.
devServer
:服务器----webpack提供的本地服务器
webpack
的核心配置:
mode
-
作用:代表当前的环境:
development
代表开发模式,production
(默认)代表生产模式 -
区别:mode
entry
-
作用:将要打包的入口文件
-
可选类型:String | Array | Object
// String
entry: './src/index.js',
// Array
entry: ['./src/index.js', './src/home.js'],
// Object
entry: {
'path/js': './src/index.js',
home: './src/home.js'
}
output
- 作用:向硬盘写入编译文件配置
- 属性:↓↓↓↓↓↓↓
属性名 | 作用 |
---|---|
filename | 向硬盘写入编译文件的名称 |
path | 向硬盘写入编译文件的绝对路径 |
publicPath | 指定资源文件引用的目录 |
①:当entry
为String
时,编译入口文件并输出
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist'),
}
}
②:当entry
为Array
时,编译入口文件合并输出
const path = require('path')
module.exports = {
mode: 'development',
entry: ['./src/index.js', './src/home.js'],
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist'),
}
}
③:当entry
为Object
时,编译多个入口文件并输出
entry
入口指定多个key
,value
时,output
的filename
要注意,不可写死entry
入口的key
可以指定为路径(多页面),编译输出也是路径entry
入口的value
可以指定数组形式,编译合并输出
const path = require('path')
module.exports = {
mode: 'development',
entry: {
vendor: ['./src/index.js', './src/home.js'], // 合并打包为输出为vender.js
'path/index': './src/index.js', // 路径模式打包
home: './src/home.js' // 打包输出为home.js
},
output: {
filename: '[name].[hash:7].[ext]', // path原名/路径指定hash输出
path: path.resolve(__dirname, 'dist'),
}
}
module
- 介绍:
webpack
中任何一个东西都称为模块(css/img/video/woff
…),而且只识别js
。 - 作用:借助
loader
编译js
为供浏览器识别
①:处理 Css | Less | Sass | Stylus
install
npm i style-loader -D — 把处理完的 css 插入到 style 标签里
npm i css-loader -D — 处理 css
npm i postcss-loader -D — 处理不用的浏览器厂商前缀
npm i autoprefixer -D — 搭配 postcss-loader 个性化配置
浏览器 | 内核 | 前缀 |
---|---|---|
Chrome、Safari | webkit | -webkit- |
Firefox | gecko | -moz- |
Opera | presto | -o- |
IE | trident | -ms- |
… | … | … |
base
:
// index.css
section {
display: flex;
align-items: center;
transition:all 1s;
}
// index.js
import './asset/css/index.css'
document.getElementById('root').innerHTML = `<section>Webpack</section>`
webpack.config.js
:
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(css)$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},
]
}
-
这里要注意
-
1.首先把
.css
文件利用postcss-loader
过滤 -
2.接着利用
css-loader
处理css
为浏览器所能识别的文件 -
3.最后利用
style-loader
插入到浏览器style
标签中 -
4.如果是
less
、stylus
、sass
,借住各自的loader
追加到postcss-loader
即可
loader
的使用顺序:
从后向前
这里编译会报错:No PostCSS Config found in: W:\webpack\src\asset\css
,缺少PostCSS config
配置文件,这里我们搭配 autoprefixer
进行配置
根目录新建 postcss.config.js
module.exports = {
"plugins": {
"autoprefixer": {}
}
}
package.json
追加 browserslist
...
"browserslist": [
"defaults",
"not ie < 11",
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
...
:::编译完成
:::打开 index.html
,我们可以看到一切OK!
②:处理 JS | JSX
上面的代码我们在 Chrome
无压力,我们在毒瘤 IE11
中 运行试试效果
果不其然,报错了,因为我们在项目中使用到 ES6
提供的 模板字符串
,一些浏览器在ES6
发布后没有做出相应更新,出现不识别的情况
随之出现babel-loader
:可以用来处理ES6语法,将其编译为浏览器可以执行的js
语法
接下来,我们安装使用:
- 这里要注意版本的一致性
babel-preset-es2015
落伍了,官方推荐使用babel-preset-env
install
这里的 babel-loader 与 babel/core 版本要对应
npm i babel-loader @babel/core @babel/preset-env -D
webpack.config.js
...
rules: [
{
test: /\.(js|jsx)$/,
use: {
loader: 'babel-loader',
options: {
include: path.join(__dirname, 'src'), // 具体到 src 更快的搜索速度
exclude: '/node_modules/', // 排除node_modules,第三方代码已经处理,不需要二次处理
presets: '@babel/preset-env' // 将ES6 解析 为ES5
}
},
]
...
:::编译完成
:::然后我们在毒瘤 IE
中 运行,IE11
、IE10
、IE9
都没有问题
因为在IE8
中,Object.defineProperty
没有实现,并且不让别人访问或修改!!!
③:处理 jpe?g | png | gif | svg …
install
npm i url-loader file-loader -D
base
:
// index.css
section {
display: flex;
align-items: center;
transition: all 1s;
width: 100vw;height: 100vh;
background-image: url(../img/background.jpg) // 引入背景图
}
// index.js
import './asset/css/index.css'
import icon from './asset/img/icon.png'
// 创建img对象
let img = new Image()
img.width = 200
img.height = 200
img.src = icon
// 创建section对象
let section = document.getElementById('section')
section.appendChild(img)
section.innerHTML += '<img src="./asset/img/bj.jpg">'
// 插入
document.getElementById('root').appendChild(section)
:::这里配置如果文件大于 10K file-loader
原路径输出,否则利用url-loader
打包为base64
,从而减少http请求,但是会变大!
`webpack.config.js
...
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[hash:8].[ext]', // 以原图片名输出
outputPath: 'images/', // 输出路径
publicPath:'./dist/images', // 公共路径 预防404
limit: 10240 // 超过10K打包为图片,反之打包为base64
}
}
},
]
...
我们看到效果完全一致,icon
打包为base64
,background.jpg
原路径输出
:::但是,有一个问题,我们看到页面无法显示图片,这张图片,我们是直接页面 img
src
引入的
这里因为html
中直接使用img
标签src
加载图片的话,因为没有被依赖,图片将不会被打包
官方文档,提供loader
解决类似问题 html-withimg-loader
npm i html-withimg-loader -D
wepback.config.js
追加插件
{
test: /\.(htm|html)$/i,
loader: 'html-withimg-loader'
}
④:处理 mp4 | webm | ogg | mp3 …
webpack.config.js
...
rules: [
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[hash:8].[ext]', // 以原文件名输出
outputPath: 'media/', // 输出目录
publicPath:'./dist/media', // 公共路径 预防404
limit: 102400
}
}
},
]
...
⑤:处理 woff2 | eot | ttf | otf …
webpack.config.js
...
rules: [
{
test: /\.(woff2|eot|ttf|otf)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[hash:8].[ext]', // 以原文件名输出
outputPath: 'font/',
publicPath:'./dist/font',
limit: 10240
}
}
}
]
...
plugins
- 介绍:用于扩展
webpack
的功能,Hook Funtion
- 作用:提高开发效率,自动化 / 工程化
①:HtmlWebpackPlugin
- 自动或依据模板生成一个
html
,动态hash
引入JS
CSS
等 - 可配置单页面、多页面入口
安装:
npm i html-webpack-plugin -D
使用:
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin') // 引入
...
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack 4.X', // 文件标题
filename: 'index.html', // 文件名
template: path.resolve(__dirname, 'index.html'), // 依赖模板
inject: true, // js放置位置: true -- body 底部 | head -- head标签 | false -- 不加载js
hash: true, // 添加hash
minify: {
collapseWhitespace: true, // 移除空格
removeAttributeQuotes: true, // 移除引号
removeComments: true // 移除注释
}
})
]
...
index.html
模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- EJS 语法引入title,配置 minify :removeComments 会移除注释 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
:::构建成功,测试效果 :::
②:CleanWebpackPlugin
- 作用:清空上一次编译结果目录
安装:
npm i clean-webpack-plugin -D
使用:
webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin') // webpack 4.0 解构赋值,源码 export { CleanWebpackPlugin }
...
plugins: [
new CleanWebpackPlugin(['dist']) // 可配置绝对路径
new CleanWebpackPlugin({
dry:true, // true -- 仅仅报告要删除的文件并不删除 fale -- 全部删除
cleanOnceBeforeBuildPatterns: [path.resolve(__dirname, 'test')] // 指定绝对路径
})
]
...
③:MiniCssExtractPlugin
- 作用:抽取合并
css
,减小JS
文件体积,并自动添加hash
值
安装:
npm i mini-css-extract-plugin -D
使用:
webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
...
module: {
rules: [
{
test: /\.(css)$/,
use: [
MiniCssExtractPlugin.loader, // 当前是production环境,如果是development环境 style-loader
'css-loader',
'postcss-loader'
]
},
]
}
plugins: [
new MiniCssExtractPlugin({
filename: 'css/common.css', // 指定路径,默认hash
})
]
...
:::测试效果 :::
④:OptimizeCSSPlugin
- 作用:用于合并,压缩
css
代码
安装:
npm i optimize-css-assets-webpack-plugin -D
使用:
webpack.config.js
const OptimizeCSSPlugin= require('optimize-css-assets-webpack-plugin');
...
plugins: [
new OptimizeCSSPlugin()
]
...
:::测试效果 :::
⑤:PurifyCssWebpack
- 作用:用于去除无用
css
样式
安装:
npm i purifycss-webpack purify-css glob -D
使用:
webpack.config.js
const PurifyCssWebpack = require('purifycss-webpack') // 依赖 purify-css
const glob = require("glob") // 搜索资源
...
plugins: [
new PurifyCssWebpack({
//*.html 表示 src 文件夹下的所有 html 文件,还可以清除其它文件 *.js、*.php···
paths: glob.sync(path.join(__dirname, 'src/*.js'))
})
]
...
:::测试效果 :::
// index.css
section {
display: flex;
align-items: center;
transition: all 1s;
width: 100vw;height: 100vh;
background-image: url(../img/background.jpg);
background-color: #000;
}
.aaa {
font-size: 12px;
}
.bbb {
font-size: 12px;
}
⑥:UglifyJsPlugin
- 作用:压缩
js
代码,去除debugger
console
等等
安装:
npm i uglifyjs-webpack-plugin -D
使用:
webpack.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
...
plugins: [
new UglifyJsPlugin({
cache: true, // 开启缓存
parallel: true, // 多线程加速构建
sourceMap: true, // 使用sourceMap捕获错误
uglifyOptions: {
compress: {
drop_console:true, // 放弃对 console 函数的调用
drop_debugger:true, // 删除 debugger语句
}
},
})
]
...
:::测试效果 :::
⑦:GenerateAssetPlugin
- 作用:抽离生成额外配置文件
安装:
npm i generate-asset-webpack-plugin -D
使用:
webpack.config.js
const GenerateAssetPlugin = require('generate-asset-webpack-plugin')
// 配置方法
const createJson = function () {
let serveConfigJson = {
baseURI: '172.16.0.94:80'
}
return JSON.stringify(serveConfigJson,null,4)
}
...
plugins: [
new GenerateAssetPlugin({
filename: 'serve.config.json', // 输出到dist根目录下的serve.config.json
fn: (compilation, cb) => {
cb(null, createJson(compilation))
},
extraFiles: []
})
]
...
:::测试效果 :::
⑧:SplitChunksPlugin
- 作用:打包分割代码
配置:
optimization
属性 splitChunks
默认属性:
属性 | 作用 |
---|---|
chunks | all, async, initial 三选一, 插件作用的chunks范围 |
minSize | 最小尺寸 |
misChunks | 最小chunks |
maxAsyncRequests | 最大异步请求chunks |
maxInitialRequests | 最大初始化chunks |
name | split 的 chunks name |
cacheGroups | 缓存组,细分打包 |
… | … |
我们这里就不写默认属性配置了,直接把node_modules
的依赖包打到verder.js
,引用2次以上的公共模块打到common.js
webpack.config.js
...
optimization: {
splitChunks: {
chunks:'initial', // 对入口文件处理
cacheGroups: {
vendor:{
test: /node_modules\//,
name:'js/vendor',
priority: 10,
enforce: true
},
common: {
minChunks:2,
name: 'js/common',
priority: 10,
enforce: true
}
},
},
...
:::测试效果 :::
⑨:BundleAnalyzerPlugin
- 作用:打包体积分析,方便定位优化
安装:
npm i webpack-bundle-analyzer -D
使用:
webpack.config.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
...
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'server', // 服务器启动
analyzerHost: '127.0.0.1', // host
analyzerPort: '5555', // 端口
reportFilename: 'report.html', // 输出文件
defaultSizes: 'parsed', // 报告显示模块大小
openAnalyzer: true, // 浏览器自动打开
generateStatsFile: false, // 不输出json文件
statsFilename: 'stats.json', // 输出json文件名
statsOptions: null, // 外配置
logLevel: 'info' // 插件输出的详细程度
}),
]
...
:::测试效果
⑩:CompressionPlugin
- 作用:开启gizp压缩,打包体积优化
- 注意:需要和服务器配合
- 版本:新版本可能不兼容
webpack 4.X
目前用的@1.1.12
安装:
npm i compression-webpack-plugin@1.1.12 -D
使用:
webpack.config.js
const CompressionWebpackPlugin = require('compression-webpack-plugin')
...
plugins: [
new CompressionPlugin({
test: /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i, // 匹配资源
filename: '[path].gz[query]', // 输出名称
algorithm: 'gzip', // 压缩方式
threshold: 10240, // 处理大于10240字节才会压缩
minRatio: 0.8 // 压缩率小于才会被压缩
}),
]
...
:::测试效果
⑪:CopyWebpackPlugin
- 作用:在
webpack
中拷贝文件或者文件夹
安装:
npm i copy-webpack-plugin -D
使用:
webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
...
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, './src/static'), // 起始位置
to: path.resolve(__dirname, './dist/static') // 结束位置
}
]
})
]
...
:::测试效果 :::
⑫:Happypack | DllPlugin
Happypack
:多进程Loader
转换,加速构建DllPlugin
:抽离第三方模块,加速构建
总结:都是加速webpack
构建,这里就不在演示了
⑬:NotifierWebpackPlugin
- 作用:控制台友好的错误信息提示插件
安装:
npm i friendly-errors-webpack-plugin -D
使用:
webpack.config.js:
...
plugin = [
new NotifierWebpackPlugin({
// 编译成功处理
compilationSuccessInfo: {
messages: ['Compiler result at http://localhost:8080']
},
// 编译失败处理
onErrors:(result,errors) => {},
// 是否每次编译完成清除控制台
clearConsole:true,
})
]
...
:::测试效果 :::
⑭:Notify
- 作用:搭配上面的错误友好信息插件,桌面提示插件
安装:
npm i node-notifier -D
使用:
webpack.config.js
...
plugin = [
new NotifierWebpackPlugin({
// 编译成功处理
compilationSuccessInfo: {
messages: ['Compiler result at http://localhost:8080']
},
// 编译失败处理
onErrors:(result,errors) => {
if (result == 'error') {
notify.notify({
title: 'Webpack error',
message: `${result}:${errors[0].name}`,
subtitle: errors[0].file || '',
icon: path.resolve(__dirname, 'src/assets/img/chat_head_img.jpg')
})
}
},
// 是否每次编译完成清除控制台
clearConsole:true,
})
]
...
:::测试效果 :::
devServer
- 介绍:模块热替换功能会在应用程序运行过程中替换、添加或删除模块,无需重新加载整个页面。
- 保留在完全重新加载页面时丢失的应用程序状态
- 只更新变更内容,以节省宝贵的开发时间
- 调整样式更加快速,几乎相当于在浏览器调试器中更改样式
安装:
npm install webpack-dev-server -D
使用:
webpack.config.js
const webpack = require('webpack')
...
plugins: [
new webpack.NamedModulesPlugin(), // 显示热加载模块名称
new webpack.HotModuleReplacementPlugin() // 热模块替换
],
devServer: {
host: 'localhost', // host地址
port: '8080', // 端口
open: true, //自动拉起浏览器
hot: true, //热加载
hotOnly: true, // 热加载不更新
publicPath: '', // 基础路径
// proxy: {}, // 跨域
// bypass: {} // 拦截器
},
...
package.json
...
"scripts": {
"dev": "webpack-dev-server --progress --mode development",
"test": "webpack -p --progress --color --config webpack.config.js"
},
...
:::测试效果 :::
我们然后修改index.js
,发现热加载不会生效,并且控制台有警告信息
查阅文档,我们缺少热模块更新,在需要的地方,更新即可
if (module.hot) module.hot.accept()
同样,我们可以把webpack-dev-server
的hotOnly
干掉也可以
我们可以看到,热加载成功,但是我们会看到编译过程,我们可以利用配置清除
...
devServer: {
host: 'localhost', // host地址
port: '8080', // 端口
open: true, //自动拉起浏览器
hot: true, //热加载
hotOnly: false, // 热加载不更新
publicPath: '', // 基础路径
// proxy: {}, // 跨域
// bypass: {} // 拦截器
quiet: true, // 隐藏控制台编译过程
clientLogLevel: 'warning', // 隐藏客户端编译过程/结果
overlay: {warnings: true, errors: true}, // 客户端显示警告/报错信息
},
...
总结:这些只是配置方面,研究事物不能局限在表面…
待续…