Webpack 入门-了解Webpack的基础配置
webpack 版本: 4.41.0
生成webpack 项目
新增一个目录myapp,然后进入到目录中,执行下面操作
npm init --y
npm install webpack webpack-cli --save-dev
在根目录中创建src目录,src目录下新增index.js文件; 在根目录下新建webpack.config.js文件,代码如下:
module.exports = {
mode: "development",
entry: {
"main": "./src/index.js",
},
}
在package.json 中新增script
"scripts": {
"build": "npx webpack",
},
执行 npm run build,你会发现根目录中新增了一个dist目录,dist目录下有一个main.js。说明你成功用Webpack打包好项目了,接下来带你们去体会Webpack的不同配置带来的效果。
loader
loader是什么
Webpack的本质上是一个模块打包工具,默认只能打包JS文件,不能处理其他文件。在项目中我们还需要对css、图片进行打包,为了能够让webpack对其他类型的文件进行打包,在打包之前我们就必须将其他类型文件转换成Webpack能够识别的模块。 用于将其他类型文件转换成Webpack能够识别处理的工具我们就称之为 loader
特点
1、单一原则,一个loader只做一件事情 2、多个loader会按照从右到左,从下到上的顺序执行 例如: 从右到左
["style-loader", "css-loader"]
先执行css-loader解析css文件拿到所有的内容呢,
再执行style-loader将内容插入到HTML的HEAD中
file-loader
index.js 中引入图片
import "./images/abc.jpg"
打包后,控制台打印出错误“You may need an appropriate loader to handle this file type” 所以我们要添加 file-loader 去打包图片
npm install file-loader --save-dev
webpack.config.js 中配置file-loader
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
}
]
}
默认情况下 fileloader生成的图片名称就是文件内容的 MD5 哈希值 如果想打包后不修改图片的名称,可以新增配置 name: "[name].[ext]"
默认情况下 fileloader 生成的图片会放在dist 根目录下,如果想要放在你指定的目录下,可以配置 outputPath: "images" 如果你的图片资源会被托管在其他cdn服务器上,可以新增配置 publicPath: "托管服务器地址"
打包iconfont
字体图标也是文件,所以我们用file-loader来处理字体图标文件
{
test: /\.(eot|svg|ttf|woff|woff2)$/,
use:[{
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "font/",
}
}]
}
url-loader
url-loader, url-loader的功能类似于 file-loader 如果你的图片资源体积比较小,可以打包成base64的字符串
使用: 1、npm install url-loader --save-dev 2、
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: "url-loader",
options: {
name: "[name].[ext]",
outputPath: "images",
/**
limt: 指定图片限制的大小
如果被打包的图片大于 limt,就会将图片打包成一个文件;
如果被打包的图片小于 limt,就会将图片打包成 base64的字符串
*/
limit: 1024,
}
}
]
}
css-loader
css-loader: 解析css文件的@import依赖关系 style-loader: 将webpack处理之后的内容插入到HTML的HEAD代码中 npm install style-loader css-loader
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
css-loader 模块化
默认情况下 import "./index.css" 导入的样式是全局样式;也就是只要被导入,在其他文件也可以使用;如果想要导入的css文件只在导入的文件中有效,那么就需要开启css模块化
{
loader: "css-loader",
options: {
modules: true // 开启CSS模块化
}
}
使用: import style from "./index.css" 然后在使用的地方通过 style.classname 方式使用即可
原理: 原来的样式:
.box {
width: 100px;
}
生成的样式
._2cQdM_4_hvjjigyhxT_FtA {
width: 100px;
}
你会发现css的类名变了,不是原来的类名。开启模块化后,会将类名修改为全局唯一的类名,这就使得样式只能在导入的文件中有效
less-loader
自动将less转换成css 使用: 1、npm install less less-loader --save-dev 2、
{
test: /\.css$/,
use: ["style-loader", "css-loader", "less-loader"]
}
scss-loader
自动将scss转换成css 使用 1、npm install node-sass sass-loader 2、
{
test: /\.css$/,
use: ["style-loader", "css-loader", "sass-loader"]
}
postcss-loader
postcss和sass/less不同,它不是css 预处理器。它是一款使用插件去抓换css的工具。postcss有许多非常好用的插件。 例如: autoprefixed(自动补全浏览器前缀) 通常W3C组织成员提出一个新属性,比如:transform、transition,大家都觉得好,但是W3C制定标准,要走很复杂的程序。而浏览器商推广时间紧,如果一个属性已经够成熟了,就会在浏览器中支持。为了必满日后W3C公布标准时有所更改,加了一个私有前缀。比如:-webkit-transform,通过中这种方式来提前支持新属性。
postcss-pxtorem(自动把px转换成rem) 使用: 1、npm install postcss-loader autoprefixer --save-dev 2、在css-loader、less-loader、sass-loader后面加上postcss-loader
{
test: /\.css$/,
use: ["style-loader", "css-loader", "less-loader", "postcss-loader"]
}
3、新增postcss.config.js
module.exports = {
plugins: {
"autoprefixer": {
"overrideBrowserslist": [
// "ie >= 8", // 兼容IE7以上浏览器
// "Firefox >= 3.5", // 兼容火狐版本号大于3.5浏览器
// "chrome >= 35", // 兼容谷歌版本号大于35浏览器,
// "opera >= 11.5" // 兼容欧朋版本号大于11.5浏览器,
"chrome >= 36", // 如果需要适配的浏览器完全兼容则不会添加前缀
]
}
}
};
webpack-dev-server
- 概念
webpack-dev-server 可以将我们打包好的程序运行在一个服务器环境下。可以监听文件的变化,也可以解决企业开发阶段的跨域问题。
- 使用
npm install webpack-dev-server --save-dev
webpack.config.js中配置webpack-dev-server
devServer: {
port: 3000
}
package.json中增加命令
"scripts": {
"watch": "npx webpack-dev-server --config webpack.config.js"
},
执行 npm run watch 然后在浏览器中打开http://localhost:3000/,在入口文件index.js增加一行代码
console.log("监听文件的变化")
保存后你会发现浏览器自动重新刷新了,控制台打印了 “监听文件的变化”
热更新(HMR)
- 概念
通过webpack-dev-server 可以实现实时监听打包内容的变化。但是每次打包后都会重新刷新网页,给我们带来了很多的不便。而HMR 会在内容发生改变的时候更新内容但是不会重新刷新网页。
- 缺点 比如说我的入口文件代码如下:
let btn = document.createElement("button");
btn.innerText = "添加内容";
document.body.appendChild(btn);
let index = 0;
btn.onclick = function () {
let p = document.createElement("p");
p.innerText = "我是第" + index + "个段落";
index++;
document.body.appendChild(p);
};
先点击按钮,界面上会出现一行文字:“我是第0个段落”,然后我此时去修改入口文件的代码,此时界面重新刷新了,界面上的一行文字也消失了。但是在实际的开发中,我们不希望重新刷新,希望能够更新修改的内容,这时就用到了HMR。
- 使用
webpack.config.js中配置
devServer: {
port: 3000,
hot: true
},
入口文件index.js中增加下列代码
if (process.env.NODE_ENV === 'development') {
const HMR = module.hot;
HMR && HMR.accept && HMR.accept();
}
重新执行 npm run watch,重复上面的操作呢,你会发现界面上的“我是第0个段落”还显示,修改的部分也自动更新了。
output
告诉webpack在哪里放置它所创建得bundle,以及如何命名这些文件。默认的输出目录是 "./dist",默认入口文件的名称为 "main.js"
重点讲解下 publicpath(项目中经常用到)
output: {
path: path.join(__dirname, '/dist'),
filename: "js/main.js",
publicPath: "http://localhost:3000/rp/static"
},
多入口文件
webpack.config.js 配置多个入口
entry: {
"main": "./src/js/main.js",
"home": "./src/js/home.js"
},
配置多个出口
plugins: [new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html",
chunks: ["main"]
}), new HtmlWebpackPlugin({
template: "./src/test.html",
filename: "test.html",
chunks: ["home"]
})]
执行npm run build,你会发现dist目录下出现index.html和test.html。index.html中会自动引入 main.js;test.html中会自动引入 home.js;

Dll 构建
就是把打包一些不会经常改变、常用、构建时间长的第三方包提前打包好,然后webpack打包的时候跳过这些包,从而达到提高打包效率的效果; 1、单独配置一个config.js打包不会发生改变的第三方库,并生成一个映射文件 manifest.json(表明打包了哪些第三方库)
module.exports = {
mode: 'production',
entry: {
vendors: 'jquery'
},
output: {
filename: '[name].[contenthash:8].js',
path: path.resolve(__dirname, 'dll'),
library: '[name]' // 表示打包的是一个库, 表示将打包的内容通过全局变量暴露出去
},
plugins: [
new Webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, 'dll/[name].manifest.json')
// 通常我们会把好几个库打包到一个文件中,所以我们需要一个映射文件方便webpack能从中找到其中的库
})
]
};
2、告诉webpack 对应的映射文件,在打包的时候如何webpack回到指定的映射文件中查找对应的动态库, 找到了那么就不会重新打包动态库中的内容了, 如果找不到才会重新打包
new Webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, "dll/venders.manifest.json")
}),
3、将打包好的库引入到界面上 npm install add-asset-html-webpack --save-dev
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname,'dll/venders.dll.js')
})
动态链接库虽然能够减少构建的时间,但是这个效果是非常微弱的。在项目我们考虑使用dll 和 HardSourceWebpackPlugin一起使用去大幅度的减少构建的时间。
构建加速
HardSourceWebpackPlugin
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
// ......
plugins: [
new HardSourceWebpackPlugin() // <- 直接加入这行代码就行
]
}
加这个插件之前项目构建的速度是
Version: webpack 4.28.4
Time: 49739ms
加了这个插件之后项目构建的速度为
Version: webpack 4.28.4
Time: 148730ms
直接缩短了 100000ms 构建速度大大提升了。
如果是webpack5的话,已经这个插件已经加入了webpack5。
externals
以 script的方式引入不会经常变化的第三方库,然后告诉Webpack打包的时候遇到这些第三方库 不需要打包进来我们的项目,从而提升打包速度。
1、html文件中引入 jquery
html lang="en">
<head>
<meta charset="UTF-8" />
<title>Webpack Project</title>
</head>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<body>
<div id="root"></div>
<!--这是test-->
</body>
</html>
2、配置告诉webpack 不去打包 jquery,当遇到使用第三方库jquery 就去使用全局变量 jQuery
externals: {
jquery: "jQuery"
},
3、入口文件引用 jquery
import $ from "jquery"
const divEle = $("<div></div>")
divEle.text("你好呀")
$("body").append(divEle)
npm run build后 你会发现打包的文件中没有 打包进query相关的代码; npm run watch后,你会发现界面上显示 "你好呀"
代码分割
什么是代码分割
webpack打包会默认将加载的所有模块打包到一个文件中。比如说a.js引入了b.js, a.js有1kb,b.js也有1kb;当我们修改了a.js时,但没有修改b.js时,用户需要重新去加载2kb的文件; 解决方案:将不常改变的模块打包成另一个文件;这样每次修改后用户只需要加载修改后的文件即可。
生产环境
生成环境中默认情况下对 异步加载的模块进行代码分割
异步加载模块是什么呢?
同步加载:import $ from 'jquery'
在index.js 中导入了10个模块,那么只要index.js执行,就会一次性将10个模块加载进来
异步加载:import("jquery").then(({default: $}) => { 使用模块代码})
在index.js中导入了10个模块,那么哪怕index.js执行,也要看是否满足条件才去加载。
我们理解了异步加载模块后,开始按下面的步骤操作:
// index.js(入口文件)
const ele = document.createElement("div")
ele.innerText = "你好呀"
ele.onClick = () => {
import("jquery").then(({default: $}) => {
console.log("default", $)
})
}
//webpack.config.js
mode: "production"
npm run build 后你会发现打包后生成的js文件有两个,一个是入口文件,另一个就是jquery文件,jquery文件被分割出来一个单独的文件了。
如果你想要修改代码分割出来的文件的名称,可以使用魔法注释/* webpackChunkName: "jquery" */
import(/* webpackChunkName: "jquery" */"jquery").then(({default: $}) => {
console.log("default", $)
})
如果想要空闲时间去加载模块,而不是等满足条件后去加载模块,可以使用/* webpackPrefetch: true */:
import(/* webpackPrefetch: true */"jquery").then(({default: $}) => {
console.log("default", $)
})
生产环境代码分割默认情况下的配置
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
开发环境
开发环境不会自动对异步加载模块进行代码分割;需要的话,要在webpack.config.js 中配置
// webpack.config.js
optimization: {
splitChunks: {
chunks: "async",
}
},
tree-shaking
什么是 tree-shaking
过滤掉无用的JS代码和css代码,我们称之为 tree-shaking 例如:b.js暴露出两个方法,a.js中引入b.js, 并且导入了其中一个方法并使用。默认情况下会把b模块中的两个方法都会打包到a.js文件中。
为了提高性能,我们希望只把用到的那一个方法打包到a.js文件中。
生产环境
生产环境默认会自动tree-shaking
// a.js
import {fun1} from "./main"
fun1()
// b.js
export const fun1 = function() {
console.log("fun1")
}
export const fun2 = function() {
console.log("fun2")
}
打包后的 a.js不包括 fun2的代码 注意:只有ES 模块才会支持 Tree-shaking
import "./css/index.css"
import "./less/index.less"
import "./scss/index.scss"
生产环境下也会默认不会对 css、less、scss文件进行摇树
开发环境
开发环境默认情况下不会进行 tree-shaking; 如果要tree-shaking,要在webpack.config.js中配置
// webpack.config.js
optimization: {
usedExports: true,
},
// package.json
sideEffects: ["*.css", "*.less", "*.scss"] // 告诉Webpack 哪些文件不做tree-shaking
打包后的a.js文件中依然有fun2的代码,但是有个魔法注释告诉webpack这是没有用到的代码
/* unused harmony export fun2 */
libraryTarget和library
当用Webpack去构建一个可以被其他模块导入使用的库时,需要用到LibraryTarget和library.
当不设置libraryTarget的值时,默认为 var
output: {
library: "MyLib"
}
此时打包后会将入口起点的返回值分配给一个变量。
var MyLibrary = _entry_return_;
// 在一个单独的 script...
MyLibrary.doSomething();
在window环境中,全局变量会默认分配给window,由此我们可以这样使用打包之后的文件。
<script src="./dist/main.js"></script>
<script>
window.MyLib.default();
</script>
当设置 libararyTarget为 umd的时候
要兼容node环境和浏览器环境,需要额外设置 globalObject属性
output:{
library: "MyLib",
libraryTarget: "umd",
globalObject: "this"
}
为了使 UMD 构建在浏览器和 Node.js 上均可用,应将 `output.globalObject` 选项设置为 `'this'`。对于类似 web 的目标,默认为 `self`。
这样才能在node环境上使用
const MyLib = require("../dist/main.js")
console.log("MyLib", MyLib.default())
当需要通过ES导入时,还需要配置experiments.outputModule
experiments: {
outputModule: true,
},
output: {
path: path.resolve(__dirname, "dist"),
library: {
type: "module",
},
},
创建一个库
入口文件index.js,导出一个函数
console.log("Hello World!");
export function getName() {
return "jjj";
}
打包配置文件使用output.library配置项暴露从入口起点导出的内容
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
library: "webpackNumbers",
},
我们将入口暴露为 webpackNumbers,这样用户就可以通过脚本标签使用它。
<script src="./dist/main.js"></script>
<script>
console.log(window.webpackNumbers.getName());
</script>
然而它只能通过被脚本标签引用而发挥作用,而不能运行在CommonJS、AMD、NodeJS等环境中。 作为一个库作者,我们希望它能兼容不同的环境。 接下来更新ouput.library配置项,将其type设置为“umd”
library: {
type: "umd",
name: "webpackNumbers",
globalObject: "this"
},
这样就可以在CommonJS、AMD中加载文件。