webpack的核心概念,放到2022年相信很多的小伙伴都已经非常清楚了。但是,对于webpack配置中的output.path、output.filename以及output.publicPath,还有很多小伙伴还不理解。本文讲围绕output.filename、output.path与output.publicPath,讲解它们的功能,并分析这些配置与webpack中常使用到的MiniCssExtractPlugin、HtmlWebpackPlugin等插件的关系。

直接上总结图

基础环境搭建

我们现在基于webpack搭建了一个前端项目,完成项目初始化,并安装webpack三件套:

yarn init
yarn add -D webpack webpack-cli webpack-dev-server

安装完成以后,我们在项目根目录下创建一个webpack.config.js,一个极简的配置如下:

const {resolve} = require('path');
module.exports = {
mode: 'development',
entry: {
main: resolve(__dirname, 'src', 'index.js')
},
output: {
filename: 'main.js',
path: resolve(__dirname, 'dist')
}
}

然后,在package.json中添加脚本:

+ "scripts": {
+ "build": "webpack --config webpack.config.js"
+ },
"devDependencies": {
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
}

接着,我们在src目录下创建一个index.js文件,这个js文件的内容就是从dom上找到id为app的元素,并给其内部添加一个文本"hello, world"

document.getElementById('app').innerText = 'hello, world'

最后,我们运行webpack的构建过程:

yarn build

运行以后,就会在项目根目录下的dist目录下生成main.js。

注意:这里并没有配置关于js的解析,因为webpack默认就会处理js文件。

引入HtmlWebpackPlugin

仅仅是生成目标js文件,可能还不是我们期望的效果。对于一个项目来说,我们通常还希望有一个html来展示UI,并运行js代码,但是手工创建可能不能是一个好的方案。这里,我们引入本项目的第一个插件:HtmlWebpackPlugin

yarn add -D html-webpack-plugin

HtmlWebpackPlugin插件基础功能:

  1. 它会使用一个模板来生成一个html;
  2. 在生成的html中插入节点(譬如,js对应的script节点等)。

安装好该插件以后,在之前的webpack配置中,我们适当的修改:

  • 引用插件,并new一个HtmlWebpackPlugin实例(不添加其他配置)
 const {resolve} = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
@@ -7,5 +8,10 @@ module.exports = {
output: {
filename: 'main.js',
path: resolve(__dirname, 'dist')
- }
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+
+ })
+ ]
}

让我们再次运行构建脚本后,我们会发现,dist目录中,不仅仅生成了main.js,还生成一个index.html:

通过检查这个index.html的内容可以看到,这个插件不仅仅帮我们生成了一个html,还在这个html中的head节点中创建了一个script节点,并且src属性填写的是main.js。

此时,我们使用浏览器直接打开这个index.html,尽管是在文件系统,但浏览器还是可以通过script节点中的属性`src="main.js",从index.html所在同级目录中加载main.js。然而,运行起来有报错:

PS:这里有同学可能会认为是script节点在body以前加载的,所以会报错。但是实际不是这样的,这里script节点中有一个defer属性,这个属性表明,文档加载完毕以后才会执行main.js(MDN - defer),所以,我们不用担心由于DOM未加载完就执行js代码而造成报错。

这个地方的问题在于:我们的main.js中会执行查找id为app的元素,但是实际生成的html是没有这个元素的。

为了解决上述的问题,我们希望能够自定义生成index.html。通常的做法就是:

  1. 在项目根目录创建一个public目录,在其中创建一个index.html(项目根目录/public/index.html),内容如下(重点是body里面添加了<div id="app"></div>):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>这是一个模板HTML</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="app">
</div>
</body>
</html>
  1. 然后,修改webpack配置中,关于HtmlWebpackPlugin的配置,配置插件template参数,表明使用上述的创建的index.html:
     plugins: [
new HtmlWebpackPlugin({
+ template: resolve(__dirname, 'public', 'index.html')
})
]

我们再次运行构建,可以看到在dist目录下的index.html是基于我们提供的模板生成:

此时,我们再次打开这个html,可以看到正确的处理后的结果:

output.path与output.filename

让我们回到关于output.path与output.filename上来。回顾我们的webpack配置:

  • output.filename:确定js最终生成的文件名

  • output.path:确定js所在的根路径

js最终生成的路径是:

output.path(绝对路径) + output.filename(文件名,可以有相对路径前缀)

做一个简单的实验便可知,例如,我们修改配置如下,把output.filename改为"js/main.js",output.path改为'resolve(__diranme, "my-dist")'

     output: {
- filename: 'main.js',
- path: resolve(__dirname, 'dist')
+ filename: 'js/main.js',
+ path: resolve(__dirname, 'my-dist')
},

重新经过构建以后,我们会看到my-dist目录被创建,并且这个目录下面还会创建js目录,js目录中会有main.js,正好匹配了output.path(项目根目录/my-dist) + output.filename(js/main.js)

但是,output.filename与output.path仅仅影响js的生成吗?不然,让我看看这两个参数对于HtmlWebpackPlugin的关联关系。

与HtmlWebpackPlugin的关联

对于上述生成结果,我们会注意到,在webpack配置中的HtmlWebpackPlugin插件部分,我们没有编写过任何关于index.html的生成路径的配置,但这个index.html最终也生成到了"my-dist"目录下(与output.path一致);此外,我们还可以发现,生成的index.html里面的script节点的src属性,是"js/mian.js"(与output.filename一致)。

我们可以整理一个图,来描述相关配置与js构建、HtmlWebpackPlugin插件的关联关系:

总结来说,output.path与output.filename不能单纯只作为输出js的配置,HtmlWebpackPlugin也会使用它们:

  • HtmlWebpackPlugin会使用output.path + 插件本身的filename配置,作为html的生成路径;
  • HtmlWebpackPlugin会使用output.filename作为生成的html中script节点src属性的js路径(特别注意:这里还不准确,后续会补充修正!)。

读者可以根据上述的表格,自己进行实验验证。

关于output.filename的注意点

对于output.filename,需要注意的是,不能是一个绝对路径,譬如:"/js/main.js" or "/main.js",一旦配置成了绝对路径,就会看到报错:

configuration.output.filename: A relative path is expected. However, the provided value "/js/main.js" is an absolute path!
Please use output.path to specify absolute path and output.filename for the file name.

你只能写成:"js/main.js""./js/main.js"。然而,由于生成的html中script节点属性src的值,来源于这个output.filename值,如果我们有需求,希望生成的src等于一个绝对路径,譬如:src="/js/main.js",仅仅靠output.filename是不行的。于是乎,output.publicPath就登场了!

output.publicPath

首先,在webpack中,这个参数不配置的话,默认是空字符串""。然后,我们需要纠正我们前面的一个结论

  • HtmlWebpackPlugin会使用output.filename作为生成的html中script节点src属性的js路径

实际上,script节点的src属性的路径,并不只是output.filename来决定的,而是由output.publicPath与output.filename共同决定:

src = output.publicPath(还有斜杠的特殊处理,后面讲)+ output.filename

只是因为output.publicPath默认是空字符串,所以我们前面生成出来的只是src="js/main.js"。这里,我们可以做一个简单的实验,配置publicPath为"/",则生成的节点就会成为:<script src="/js/main.js">

output.publicPath: "abc"(尾部没有"/"),src="abc/js/main.js":

output.publicPath: "/abc"(尾部依然没有"/"),src="/abc/js/main.js":

仔细观察这几种场景,就可以知道HtmlWebpackPlugin插件,在生成html中的script标签时候,其中的src属性依赖output.filename以及output.publicPath,并且规则为:

  • publicPath为空白字符串(默认),则src="${output.filename}";
  • publicPath非空且不以"/"结尾,则src="\({output.publicPath}/\){output.filename}"(补充了一个"/");
  • publicPath非空且以"/"结尾,则src="\({output.publicPath}\){output.filename}";

需要注意的是,谨记js文件与html文件的生成不会受到output.publicPath的影响,只跟output.path和filename(js是output.filename,html是HtmlWebpackPlugin的filename)相关。

于是乎,我们重新整理前面的关系图,把output.publicPath配置引入:

细心的读者已经想到了,假如publicPath配置成了"/static/",影响了HtmlWebpackPlugin中的script节点的src属性路径;而js文件实际生成路径仅受到output.path+output.filename,势必造成js访问路径不匹配的问题:

所以,日常对于webpack的配置一定要注意这种路径问题,保持匹配,否则使用webpack-dev-server就会出现问题~

相信看到这里,很多读者对output中的path、filename以及publicPath能够理解他们的效果了。接下来,我们举一反三,引入常用的CSS打包工具MiniCssExtractPlugin也来分析一下。

引入MiniCssExtractPlugin

我们通常会有这样的需求,一个前端项目打包的时候,希望能够将项目依赖的css文件最终抽离为一个或N个css文件,并让我们的前端html直接以link节点的形式加载。这个时候,我们一般使用MiniCssExtractPlugin来完成这个需求。当然,除了这个插件以外,我们还需要一个最基础的loader:css-loader

yarn add -D css-loader mini-css-extract-plugin

工程结构不会变化:

项目根目录/
├─ package.json
├─ public
│ └─ index.html
├─ src
│ └─ index.js
└─ webpack.config.js

内容主要是新增了css-loader与mini-css-extract-plugin。

接下来,我们编写一个简单的css样式文件存放于src目录下(src/my-style.css):

body {
background-color: aqua;
} #app {
background-color: azure;
}

并修改index.js的代码,在index.js中引用它:

+import './my-style.css';
document.getElementById('app').innerText = 'hello, world'

此时,如果我们不进行任何的配置,运行webpack打包,会看到报错:

ERROR in ./src/my-style.css 1:5
Module parse failed: Unexpected token (1:5)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

核心问题在于,webpack无法处理index.js中关于.css的文件(webpack默认值处理js文件)。所以,需要我们配置专门处理css的规则:

+ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
... ...
plugins: [
new HtmlWebpackPlugin({
template: resolve(__dirname, 'public', 'index.html')
}),
+ new MiniCssExtractPlugin({
+ filename: 'css/main.css'
+ })
],
+ module: {
+ rules: [{
+ test: /\.css/,
+ use: [MiniCssExtractPlugin.loader, 'css-loader']
+ }]
+ }
}

首先引入MiniCssExtractPlugin插件;然后在plugins中,new出MiniCssExtractPlugin插件实例,并传入filename配置css/main.css;最后,配置module.rules中,添加对css的处理:

loader的执行顺序是按照数组从后向前的,所以use数组最后是css-loader,然后才是MiniCssExtractPlugin提供的loader。webpack在构建过程,遇到引用css的场景,则先调用css-loader,对css文件进行处理,然后调用MiniCssExtractPlugin提供的loader进行抽取

完成配置以后,我们再次启动webpack的构建,会看到dist目录下,又会产生一个css目录,里面存放的就是mian.js,并且,检查index.html会发现这一次除了script标签外,还插入了link标签:

有的读者可能已经能够推断出,这个link标签的href路径,也是根据output.publicPath+MiniCssExtractPlugin插件的filename组合而来。这里直接给出结论,就是这样的。我们再次更新图表,把导出css样式文件的MiniCssExtractPlugin插件与相关的配置关系也总结进去,得到如下最终版关系图:

关于关系图的补充

通过关系图,我们很容易知道,webpack中关于文件生成最核心的配置就是output.path以及各种filename,js的生成、css的生成、html的生成都依赖了这套配置;

其次,与js相关的output.filename和与css相关的MiniCssExtractPlugin.filename配置都有两个作用:

  1. js、css的生成文件路径;
  2. 被HtmlWebpackPlugin使用,以生成script节点和link节点中的资源路径(当然这个过程还有output.publicPath的参与)。

最后,本文并没有讲到webpack-dev-server和上述配置的关系,这个会在本《掌握webpack》系列中单独出一期。

掌握webpack(一)一张图让你明白webpack中output的filename、path、publicPath与主流插件的关系的更多相关文章

  1. 两张图说明http协议,tcp协议,ip协议,dns服务之间的关系和区别

    一.理解一个传输流再去扩展 用http举例来说,首先作为发送端的客户端在应用层(http协议)发出一个想看某个web页面的http请求. 接着,为了传输方便,在传输层(tcp协议)把从应用层处收到的数 ...

  2. 一张图记住TCP/IP通讯中的IP地址配置

    TCP/IP通讯情景: 用网线将计算机A(服务器Server)和计算机B(Client)连接起来.程序代码在计算机A中,计算机B中安装有TCP/IP通讯助手. (图中屏幕大的是计算机A,屏幕小的笔记本 ...

  3. 一张图弄懂opengl的诸多库gl glu glut freeglut glew glfw之间关系

    开始学习opengl,但是看opengl编程指南不同版本之间使用了一堆不同的库,概念名称全都搅起的,越看越糊涂,遂整理的一下opengl相关的一些库的名词, 才发现是不同时期不同版本不断发展的结果. ...

  4. 看过这两张图,就明白 Buffer 和 Cache 之间区别

    Buffer常见的是这个: 对,就是铁道端头那个巨大的弹簧一类的东西.作用是万一车没停住,撞弹簧上减速慢,危险小一些.叫缓冲. Cache常见的是这个: 没错,就是一种保管箱.看到右边那个被锈掉的Fo ...

  5. 9张图让你明白什么叫做"一坨屎"一样的iOS垃圾代码

    前言:这是一个两万余行的商业项目,但代码质量却不敢恭维!     //本文永久链接,转载请注明出处:http://www.cnblogs.com/ChenYilong/p/3489939.html  ...

  6. 一张图弄懂js原型和原型链

    前言 JavaScript的原型和原型链是面试的时候经常被问及到的问题,考察了我们对JavaScript的基础掌握情况,今天我们在这里用一张图来梳理下其中的知识点. 下面我来引入这张非常经典的图,我也 ...

  7. 三张图秒懂Redis集群设计原理

    转载Redis Cluster原理 转载https://blog.csdn.net/yejingtao703/article/details/78484151 redis集群部署方式: 单机 主从 r ...

  8. [转帖]几张图让你看懂WebAssembly

    几张图让你看懂WebAssembly https://www.jianshu.com/p/bff8aa23fe4d     (图片来源:giphy.com) 编者按:本文由明非在众成翻译平台上翻译. ...

  9. 一张图告诉你,只会HTML还不够!

    会了HTML和HTML5语法,你就真的会了HTML吗,来看这张图!是这本<超实用的HTML代码段>入门实例书的导览!熊孩子们,赶紧学习去吧! 如果一半以上的你都不会,必须看这本书,阿里一线 ...

  10. 一张图系列——为什么在DllMain里面创建了线程并Wait会卡死

    这是一个老话题了,推荐一篇文章: http://blog.csdn.net/breaksoftware/article/details/8150476#0-tsina-1-83826-39723281 ...

随机推荐

  1. map集合类型/实体类类型的参数

    map集合类型的参数 若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合, 将这些数据放在map中 只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要 ...

  2. 使用doctest代码测试和Sphinx自动生成文档

    python代码测试并自动生成文档 Tips:两大工具:doctest--单元测试.Sphinx--自动生成文档 1.doctest doctest是python自带的一个模块.doctest有两种使 ...

  3. 三、celery执行定时任务

    三.Celery执行定时任务 设定时间让celery执行一个 定时任务,product_task.py from celery_task import send_email from datetime ...

  4. 重新整理 .net core 实践篇 ———— linux上排查问题实用工具 [外篇]

    前言 介绍下面几个工具: Lldb createdump dotnet-dump dotnet-gcdump dotnet-symbol Procdump 该文的前置篇为: https://www.c ...

  5. ValidList

    package com.dlzb.enterprising.config; import javax.validation.Valid; import java.util.*; public clas ...

  6. 读书笔记《A Philosophy of Software Design - John Ousterhout 软件设计哲学》

    软件设计哲学这本书很薄,值得一读.这本书将大家平时碰到的很多软件问题从更深刻的层面进行了抽象分析,同时又给出了具体的解决方案.可以说既有理论高度,又能贴近实践. 但针对软件问题,这本书并没有提出太多与 ...

  7. AcWing第78场周赛

    今天想起来了,就补一下吧~ 第一题 商品分类 货架中摆放着 n 件商品,每件商品都有两个属性:名称和产地. 当且仅当两件商品的名称和产地都相同时,两件商品才视为同一种商品. 请你统计,货架中一共有多少 ...

  8. Android网络请求(3) 网络请求框架OkHttp

    Android网络请求(3) 网络请求框架OkHttp 本节我们来讲解OkHtpp网络请求框架 什么是网络请求框架 在我的理解中,网络请求框架是为了方便我们更加便捷规范的进行网络请求所建的类,我们通过 ...

  9. 关于urllib.request解析网站不能decode

    原因 不能decode,无论以gbk还utf8都无法正常解码,这个原因是因为 网页被gzip压缩了,需要解压缩 解决办法 import urllib.request import gzip url = ...

  10. 写一个linux平台的桌面宠物

    效果图 前言 我一直在用python 写一下有趣的东西,让编程不那么无聊,之前一直有写一个桌面宠物的想法,无奈这些都是依赖资源文件,没有图片资源没办法写里面的逻辑,直到我看见了 shimiji手机桌面 ...