实战webpack4.0常用配置与优化
注意:
1、在webpack里,所有文件都是模块 例如:JS模块--->模块化(AMD、CMD、ES6 Module、Commonjs) 关于模块化参见https://www.cnblogs.com/jianxian/p/12753375.html. 如下所示
接下来便可以导入使用
最后做下验证输出,可以直接打包生成文件
接下来做下测试,引入脚本文件,打开html文件进行测试
但我们想知道这里为什么是main.js,接下来做个配置文件进行修改
接下来开始编写代码,但注意webpack是基于node的,所以必须遵循Commonjs规范。
依次填充入口与出口文件
接下来进行打包测试,则如下所示
至此便可以实现自定义打包并验证,但不可能每次都需要自己新建html引入验证,所以我们可以在webpack引入服务,给其配置一个开发服务器。
(1)接下来配置开发服务器webpack-dev-server
1、首先进行安装
2、启动指令--npx webpack-dev-server,此时便可以在localhost:8000端口查看文件
3、为了方便使用指令,我们使用scripts字段设置命令简称
此时执行npm run start或者npm start即可,结果如下
4、打开指定文件,渲染到指定html文件,而不是文件目录界面
分析如下,此时打开的是项目跟目录
所以我们需要配置开发服务器
测试如下,此时再次打开localhost:8000时便会直接到该目录
5、配置开发服务器端口号、压缩文件、自启动
此时再运行便会自动打开浏览器的http:localhost:3000端口
(2)webpack插件plugins------动态添加html文件
目前为止都是手动添加html文件,然后手动引入js文件,最后查看效果的... ...过于繁琐。
因此我们换个思路,先将打包文件放到src的index.html里,然后再将整个打包
1、下载依赖
>npm i html-webpack-plugin -D
webpack使用插件有个共同点:即在使用前都必须用node语法即CommonJS语法导入.
2、编写打包指令缩写,进行打包
接下来执行npm run build开始打包,结果如下
且里面自动引入了脚本文件js
3、添加其他参数:修改标题等(这里可以参考npm官方文档进行配置)
接着配置src/index.html文件,如下所示
接下来进行打包并启动服务,效果如下
4、压缩html文件
加上hash后src文件会价格hash随机数值,避免缓存
此外,也可以在src前面价格,随机src文件名,避免缓存
此时前后都有随机数,如下
此后每次打包都会产生新文件... ....,接下来配置下删除文件操作,此时需要用到另一个插件clean-webpack-plugin
1、首先安装依赖
2、引入插件依赖
3、开始使用
(3)多文件打包问题
此时入口文件为src/index.js,我们打包时会查找与index.js相关联依赖的文件,例如a.js
但是此时如果将a和index的关联去掉,如下
此时两个文件便没有任何关联了... ...
但此时我们如果想让两个文件关联打包,则如下
如下所示
此时便会将两个文件都打包进去
(4)单页与多页html
a.html引用index.js文件,b.html引用a.js文件
打包测试如下,只打包了a.js文件... ...
修改如下,多入口对应多出口
此时再次打包测试如下,此时index.html引入了两个文件
但是,我们这里的需求是a.html引用index.js文件,b.html引用a.js文件,所以需要打包多个html文件,所以添加HtmlWebpackPlugin即可
打包后如下,且a.html引入两个,b.html引入一个
(5)热更新
还原之前所有配置,如下
let path = require('path') let HtmlWebpackPlugin = require('html-webpack-plugin')/**打包Html插件,自动产生html,并引入打包后的文件 */ let { CleanWebpackPlugin } = require('clean-webpack-plugin')/**手动清除某个文件夹内容,清空指定的目录 */ module.exports = { /**入口配置 */ entry:'./src/index.js', /**出口配置 */ output:{ filename:'[name].[hash:5].js',/**自定义打包文件名 */ path:path.resolve(__dirname,'dist')/**node里路径操作需要绝对路径 */ }, /**开发服务器配置 */ devServer:{ contentBase:'./dist',/**启动目录 */ port:3000,/**端口号 */ compress:true,/**服务器压缩 */ open:true,/**自动打开浏览器 */ hot:true,/**热更新 */ }, /**插件 */ plugins:[ /**打包Html插件,自动产生html,并引入打包后的文件 */ new HtmlWebpackPlugin({ filename:'a.html', /* 打包出来的文件名*/ template:path.resolve(__dirname,'./src/index.html'),/**打包的html文件 */ title:'首页', minify:{/**压缩 */ removeAttributeQuotes:true,/**去除双引号 */ collapseWhitespace:true,/**折叠代码为一行 */ }, hash:true,/**清除缓存用的 */ }), /**清空匹配的路径 */ new CleanWebpackPlugin() ], /**模块设置 */ module:{}, /**模式设置 */ mode:'development', /**配置解析 */ resolve:{} }
当我们在编辑代码保存时,浏览器会自动更新... ...
但是有时我们并不想页面所有数据更新,例如vuex和redux状态会丢失,所以我们可以使用插件局部更新
1、开启开发服务配置
2、引入插件,webpack内置的哦
3、分析:此时还是会整个页面刷新,因为并不知道修改了哪个模块,所以需要添加判断(热更新应用通知,实现局部刷新,即结合该方法的回调函数实现该模块的更新)
接下来进行优化,直接调用即可
注意位置:在index.js文件进行配置添加
(6)样式打包
1、新建src/index.css文件
2、分析:如果直接引入样式文件进行打包,则无效... ...因为webpack默认只打包js文件
所以处理css模块需要利用loader
3、安装相关依赖
①style-loader将样式代码插入style ②css-loader将css作为模块,插入style标签内部 ③less、less-loader ④stylus、stylus-loader ⑤node-sass、sass-loader ...
这里我们演示下css-loader、style-loader、less、less-loader
接下来开始配置
4、配置module模块处理规则,写成对象形式方便传参option(注意解析顺序:自下而上,因为先解析成css模块,再将其放到style内)
接下来新建一个less文件
然后引入less
结果如下
(7)样式抽离---css 和 less 抽离到同一个 css 文件 common.css 里
此时存在问题,style标签太多,我们利用link引入更加优雅一些...
测试可以得知改变less文件,也可以触发热更新
原理如下:CSS-loader具有热更新功能
如果想实现样式抽离,需要安装下面两个plugin插件
extract-text-webpack-plugin在webpack3里就存在,在新版本后面加@next后缀
mini-css-extract-plugin将来可能替代上述插件的插件,目前不太稳定
1、引入
2、使用:注意这里我们要将其改为link形式,所以不再需要style-loader
3、实例化调用插件方法
4、抽离文件名
5、打包测试如下
(8)样式抽离---分开抽离,分别把 css 和 less 放在两个 css 文件中 link 到页面
打包测试结果
(9)样式抽离--最新方案
弱点:只能合成一个css文件,不能分开抽离
从上图我们可以看到,我们所写的css、less样式都会放到head标签中,那么我们如何单独把css的内容抽离出来用link标签的形式引入呢:
- 首先,先安装抽离
css
的插件 :yarn add mini-css-extract-plugin -D
- 安装好之后,在 配置文件中 引入:
let MiniCssExtractPlugin = require('mini-css-extract-plugin')
- 然后在配置文件中的插件配置中进行配置:
plugins: [ //数组, 放着所有的 webpack插件 new MiniCssExtractPlugin({ filename:'main.css', //抽离出来的css的文件的名字 }) ],
- 然后选择,到底是 css 文件要抽离,还是 less 文件要抽离,那个要抽离就给那个 加上抽离插件的内置loader。(在模块中的 css 、less 规则中加 ):如下:
module: { //模块 rules: [ //规则 { test: /\.css$/, //用正则来匹配以 css 结尾的文件 use: [ MiniCssExtractPlugin.loader,//这个loader的作用是:抽离出来 css然后用 link 标签引入到 模板文件中 'css-loader' ] ![](https://user-gold-cdn.xitu.io/2019/11/14/16e68118b17b28e6?w=1477&h=705&f=jpeg&s=127105) }, { test: /\.less$/, use: [ MiniCssExtractPlugin.loader,//这个loader的作用是:抽离出来 css然后用 link 标签引入到 模板文件中 'css-loader', 'less-loader' //把 less --->转换为 css ] } ] }
- 运行
npm run build
命令,看一下是否单独抽离出来css文件:
可以看到,我们的css文件已经被单独抽离出来了,启动一下服务看一下页面效果:
现在已经变成抽离出来的css文件是用link标签来引入的。
(10)样式抽离后的注意事项
样式抽离后因为放到link标签,所以更改样式时,服务器无法热更新哦。设置如下
将disable属性改为true后便无法将其改为link引入,即该设置无效,便可以在开发时候热更新,上线前记得改为false。
注意2:fallback改为style-loader,即当无法改为link引入时,使用style内敛,以此实现热更新目的
(11)多余样式代码的删除
场景:项目引入bootstrap,但有很多多余没用的而样式,我们可能只用到一两种... ....
如下所示,这里我们手动添加一些无用的多余样式
我们希望打包时,如果用不到,则不打包这些样式代码,此时需要用到插件purifycss-webpack,它会在打包时调用purify-css插件,且搜索时需要glob插件
1、安装依赖
2、引入使用(使用注意事项,必须在css-plugin的下面,因为需要先去除,再打包)
结合文档看先使用规范
结果如下
(12)css代码自动添加前缀
如下所示,希望在添加时自动加上前缀
1、安装依赖
npm install postcss-loader autoprefixer -D
2、添加配置文件src/postcss.config.js
3、使用
4、打包测试
(13)解析ES6
参考文章:https://juejin.im/post/5e195c6b6fb9a02fcd130b69
1.ES6
或者 更高级的语法 转化为 ES5
(babel)
1.安装 babel-loader:转换加载器,和 babel/core(babel 的核心),和babel/preset-env
(转化模块)
yarn add babel-loader @babel/core @babel/preset-env
复制代码
2.安装完成之后,在配置文件中配置 js
规则:
{
test: /\.js$/,
use:{
loader: 'babel-loader',
options: { // 用 babel-loader,需要把 es6 转换为 es5
presets: [ //预设库
'@babel/preset-env' //包含把es6转换为es5的模块
'@babel/preset-react' //解析 react语法
]
}
}
},
复制代码
//配置完成后就可以解析 `es6` 语法。
//更高一级的语法的配置:(注意:先安装完在配置)
{
test: /\.js$/,
use:{
loader: 'babel-loader',
options: { // 用 babel-loader,需要把 es6 转换为 es5
presets: [ //预设库
'@babel/preset-env' //包含把es6转换为es5的模块
],
plugins: [
//更高级的语法解析(如装饰器写法)
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
}
},
复制代码
2.ES6
或者 更高级的语法 转化为 ES5
常用插件:
-
@babel / plugin-transform-runtime
(可以处理异步,如 Promise等)代码运行时的包 开发依赖:yarn add @babel / plugin-transform-runtime -D
先进行插件的安装 -
@babel/runtime
: 这个包上线时需要,所以安装时不要加 -Dyarn add @babel/runtime
-
安装好之后,进入到配置文件中配置安装好的插件:
{
test: /\.js$/,
use:{
loader: 'babel-loader',
options: { // 用 babel-loader,需要把 es6 转换为 es5
presets: [ //预设库
'@babel/preset-env' //包含把es6转换为es5的模块
],
plugins: [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }],
"@babel/plugin-transform-runtime" //加入到这个位置
]
}
},
include:path.resolve(__dirname,'src'), //找 src下面的 js
exclude:/node_modules/ //不包含 node_modules 下面的 js
},
复制代码
默认找到的是全部的 js
, 我们还得写一些规则来让它只找 src
下面的 js
文件。
4.还有一些更高级的语法不支持转换,安装一个补丁模块:
yarn add @babel/polyfill
这个模块在代码运行时需要,所以安装时不要加 -D。
用的时候需要在上面引入这个模块: require('@babel/polyfill')
3.JS校验:(校验器:eslint)
-
需要先安装
eslint
和eslint-loader
:yarn add eslint eslint-loader -D
-
然后在配置文件中的模块配置中去配置
js
规则:
{
//在配置一个 js 规则,用来校验 js
test: /\.js$/,
use: {
loader: 'eslint-loader',
options: {
enforce: 'pre' // 强制 强制在下一个 loader 之前执行 post 是后面
}
}
},
{
test: /\.js$/,
use:{
loader: 'babel-loader',
options: { // 用 babel-loader,需要把 es6 转换为 es5
presets: [ //预设库
'@babel/preset-env' //包含把es6转换为es5的模块
],
plugins: [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }],
"@babel/plugin-transform-runtime"
]
}
},
include: path.resolve(__dirname, 'src'), // 包含 src 下面的 js 文件
exclude: /node_modules/ // 去除掉 node_modules
},
复制代码
4.全局变量引入问题(第三方模块的使用):
loader 的类型:
pre
前面执行的 loader, normal 普通 loader,内联 loader,post 后置 loader。
用 jquery
举例说明:
>>>>> 内联 loader,就是在 代码中就可以直接使用的 loader。
1. 先安装 jquery
, yarn add jquery
2. 在代码中引入:import $ from 'jquery'
3. 将 $ 符 暴露到全局去:那么这就需要一个暴露 全局的 loader:expose-loader
,这个loader是内联的。
import $ from 'expose-loader?$!jquery'
复制代码
这是内联 loader 在代码中的直接用法。 jquery
暴露 出 一个 $ 符 到全局。
-
也可以在 配置文件中去配置 内联 loader:
module: { //模块 rules: [ //规则 { test: require.resolve('jquery'), //只要引用了 jquery 就去匹配 use: 'expose-loader?$' }, ] } 复制代码
-
注解的方式: 在每个模块中注入 $ 对象:
-
这个时候需要
webpack
的插件: 先要引入webpack
-
let webpack = require('webpack') 复制代码
-
然后在配置文件的 插件配置中 去配置:
-
new webpack.ProvidePlugin({ // 在每个模块中都注入 $ (提供插件) $:'jquery' }) 复制代码
-
-
在模板中直接引入
jquery
的cdn
,但是这样在打包的时候会将jquery
一起打包,这个时候 需要在 配置文件中添加一个属性来不打包jquery
:// 表明这是外部引入的,并不需要打包。 externals: { jquery: '$' } 复制代码
5.webpack
打包图片:
在 js
中创建图片来引入:
// 1)在 js 中创建图片来引入
let image = new Image()
image.src = './logo.png' //就是一个普通的字符串
document.body.appendChild(image)
复制代码
这样来引入的话,找不到这张图片,因为这样写的话图片只是一个普通的字符串。要用es6
的语法来导入图片.
import logo from './logo.png' //把图片引入,返回的结果是一个新的图片地址
let image = new Image()
console.log(logo)
image.src = logo
document.body.appendChild(image)
复制代码
这样写的话是不支持的,需要引入一个 loader 来实现这种写法:file-loader
。
安装完成 loader 之后,我们需要到配置文件中去配置 加载图片的规则:
module: { //模块
rules: [ //规则
{
test: /\.(png|jpg|gif)$/,
use: "file-loader"
},
]
}
复制代码
在 css
中引入图片:
background: url('图片地址') /* 可以直接这样来写,这是默认支持的,因为我们用了 css-loader css-loader会吧这个转化为 require()这种写法*/
复制代码
在 html
中直接引入
<img src="./logo.png" alt="">
复制代码
这样写会报错,因为在我们打包的文件中根本找不到这张图片。这个时候我们需要一个 loader 来支持这种写法。
yarn add html-withimg-loader -D
这个 loader 会帮我们解析 html
编译图片.
安装完成之后到配置文件中配置我们的 loader:
module: { //模块
rules: [ //规则
{
test: /\.html$/,
use: 'html-withimg-loader'
},
]
}
复制代码
一般我们用于图片的loader 不是用 file-loader, 而是用 url-loader
{
test: /\.(png|jpg|gif)$/,
use: {
// 做一个限制,当我们的图片 小于多少k的时候 用base64 来转换
// 否则用 file-loader 来产生真实的图片
loader: "url-loader",
options: {
limit: 1
}
}
},
复制代码
6.打包文件分类
图片放到 img
文件夹下面:
在配置文件中的 图片 规则中加上一个输出的路劲:
{
test: /\.(png|jpg|gif)$/,
use: {
// 做一个限制,当我们的图片 小于多少k的时候 用base64 来转换
// 否则用 file-loader 来产生真实的图片
loader: "url-loader",
options: {
limit: 1,
outputPath: 'img/' //这样打包的图片就会放到 img 文件夹下面
}
}
},
复制代码
这样就会把图片生成到 img
文件夹下面。
css
文件生成到 css
文件夹下面:
在配置文件中,压缩 css
的插件中加入路径:
new MiniCssExtractPlugin({
filename:'css/main.css', //抽离出来的css的文件的名字
}),
复制代码
如何在 引用 资源的时候加上一个域名,在输出的时候加上一个公共的路径:
output: { //出口
filename: 'bundle.js', //打包后的的文件名
path: path.resolve(__dirname, 'build'),
publicPath: 'http://www.fanqiang.com' //加上一个公共的路径
},
复制代码
这样的话引用的资源(css
,js
,img
等)都会加上公共的路径。
如果你只想要给某一个资源前面加上公共的路径,比如 图片上面加上公共路径,那么就不要在出口中写,只需要在 图片规则中定义即可
{
test: /\.(png|jpg|gif)$/,
use: {
// 做一个限制,当我们的图片 小于多少k的时候 用base64 来转换
// 否则用 file-loader 来产生真实的图片
loader: "url-loader",
options: {
limit: 1,
outputPath: '/img/',
publicPath: 'http://www.fanqiang.com' //公共路径
}
}
},
.