Webpack5中的路径问题详解

1,161 阅读7分钟

在开发环境、生产环境或者直接用文件系统(file://) 打开我们的页面,我们的页面都是如何正确引用到需要的资源文件的,本文将进行详细讲解。

文件系统Url

假如我们没有使用webpack-dev-server,我们直接打包,然后右键我们的index.html文件在chrome里打开,这个时候各种资源是如何正确加载的?

最基础的配置:

// webpack.config.js
module.exports = {
	mode: 'development',
	entry: './main.js',
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: 'main.js',
	}
 }

output.path是我们打包好的资源放置的目录,filename是文件名。这个时候打包好的文件为dist/main.js,这个时候如果我们的index.html文件与dist目录同级,则使用

<script src="./dist/main.js"></script>

就可以访问到这个资源了。

index.html的url为:

file:///Users/***/Documents/myTest/Webpack5_code/index.html

main.js与index.html的相对路径为./dist/main.js,所以最终的url为:

file:///Users/***/Documents/myTest/Webpack5_code/dist/main.js

上面是最简单的模式,现在开始增加复杂度,如果JS中引用了图片之类的资源呢?

// main.js
import shit from './src/img/Shit.png';

console.log(shit);

因为我们的资源是存放在dist目录的,所以对应的文件会被拷贝到dist目录下,然后返回对应的访问路径,这时候会输出:

file:///Users/***/Documents/myTest/Webpack5_code/dist/static/e147b.png

现在关键的问题来了,这个路径是怎么生成的,来看下生成的JS文件,里面的关键代码如下:

module.exports = __webpack_require__.p + "static/e147b.png"

就是说当我们import shit from './src/img/Shit.png';的时候,模块里导出内容是__webpack_require__.p + "static/e147b.png"

这段代码是运行时的代码,其中static/e147b.png是资源在dist目录下的路径,所以__webpack_require__.p是dist目录的路径。那么Webpack是怎么在运行时得到Webpack的路径的呢?原理是Webpack会在我们打包生成的文件中加入一些通用的代码来辅助我们的业务代码,这些通用代码执行后会计算出dist目录的url路径,简化后的关键代码如下:

// 获取当前的document对象
var document = window.document;
var scriptUrl;
// 当前执行的Script是main.js,获取它的url
// 可以得到 file:///Users/***/Documents/myTest/Webpack5_code/dist/main.js
scriptUrl = document.currentScript.src
// 通过正则匹配到main.js的所在的目录
//得到file:///Users/***/Documents/myTest/Webpack5_code/dist/
scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
// 把这个路径赋值给__webpack_require__.p即是dist目录的URL
__webpack_require__.p = scriptUrl;

通过以上的代码我们就很容易理解了,通过当前执行的JS文件,我们可以在运行时得到这个JS文件的URL,然后再通过这个URL可以得到dist目录的URL,dist目录的URL + 对应资源在dist目录下的相对路径,我们就可以得到资源的完整URL。

还有一个问题,如果我们的main.js不是刚好放在dist目录下的第一级,那么上面的计算方式是有问题的,比如当我们的webpack配置是这样的时候:

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

我们的main.js是dist/js下,而scriptUrl计算出的是main.js所在的Url目录,这个时候的scriptUrl不是dist目录的Url,那么如何处理呢?实际上这种情况生成的__webpack_require__.p会做一点改变。

__webpack_require__.p = scriptUrl;
会变成
__webpack_require__.p = scriptUrl; + '../'

也就是webpack在编译过程中就知道生成的main.js实在dist/js下,那么这里编译生成的代码就会加上../。这个时候

__webpack_require__.p + "static/e147b.png"

会得到

file:///Users/***/Documents/myTest/Webpack5_code/dist/js/../static/e147b.png

相当于

file:///Users/***/Documents/myTest/Webpack5_code/dist/static/e147b.png

CSS文件里的资源路径

运行时

通过上面的方法我们就可以在运行时计算出资源正确的路径,可是如果是在CSS文件里的路径呢?如果我们没有使用mini-css-extract-plugin插件来单独提取CSS资源,那么这时候的CSS其实也是运行时生成的,比如我们在main.js里引入一个css文件:

// main.js
import './styles/index.css';

然后css文件里有一个background属性:

// index.css
background: url('../src/img/666.png');

这个时候当我们用css-loader处理后,index.css文件实际被处理成类似下面这样:

// 被css-loader处理后的简单理解
'background: url(' + __webpack_require__.p + '666.png';

所以也是返回的运行时计算的资源在dist目录下的URL路径。

当然我们处理资源的时候还需要用到style-loader,被style-loader处理后的代码可以简单理解为以下这样:

const cssContent = 'background: url(' + __webpack_require__.p + '666.png';;
const style = document.createElement('style'); 
style.type = 'text/css'; 
style.innerHTML=cssContent; 
document.getElementsByTagName('head').item(0).appendChild(style); 

所以当main.js执行到import './styles/index.css';这段代码的时候,实际会运行以上这段代码。

提取的CSS

那如果我们的CSS文件是单独提取出来的呢,这个时候就无法在运行时动态计算出资源的完整URL路径,那么是如何处理的呢?我们用mini-css-extract-plugin来单独提取出对应的CSS资源,可以得到:

.box {
    background: url(static/97231.png);
}

这个时候的backgroundUrl变成了static/97231.png,,这是一个相对于css文件的相对路径,因为默认css文件是输出到output.path下的,在本文也就是dist目录下,目录结构如下:

image.png

这时候通过url(static/97231.png);是可以正确找到图片资源的,因为图片资源相对于main.css的相对路径就是static/97231.png

那么如果我们生成的css资源是放在dist/css/main.css目录下呢,比如我们进行如下配置:

	plugins: [
		new MiniCssExtractPlugin({
			filename: 'css/main.css'
		}), 
        ]

可以看到我们给css提取的插件添加了一个filename属性,这个属性告诉插件生成的css资源的文件位置为dist下的css/main.css。这个时候再根据上面的相对路径就找不到正确的图片资源位置了,不过这个不用我们担心,webpack还是很智能的,充分考虑了各种情况,当我们改变生成资源的位置的时候,生成的css代码也会改变,这个时候生成的css资源内容如下:

.box {
    background: url(../static/97231.png);
}

可以看到相对路径变成了../static/97231.png,Very good!!!

所以说就是在生成css资源的时候,webpack会根据css资源相对dist目录的位置,在前面加上../../../来让这个位置到dist目录的位置,然后再和图片资源的目录拼接得到完整的路径。

以上面的这个文件举例,css资源相对于dist目录的位置../,png在dist目录下的位置static/97231.png所以在css文件里访问对应的png资源的路径为../ + static/97231.png = ../static/97231.png

output.publicPath

在我们的webpack配置中还有一个非常重要的属性,就是output.publicPath,比如我们进行如下配置

	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: 'main.js',
		publicPath: 'https://www.baidu.com/ok/',
	},

除了加了一行publicPath: 'https://www.baidu.com/ok/',其它没什么变化,这个时候会发生什么改变呢。publicPath字面意思就是公共路径,什么意思呢?其实就是dist目录的路径。也就是当我们在浏览器输入https://www.baidu.com/ok/这个URL就是dist目录的URL。所以上文中我们需要计算distUrl的地方全部替换成publicPath就可以了。

上文中计算__webpack_require__.p的地方会变成这样

__webpack_require__.p = "https://www.baidu.com/ok/";

是不是很简单,直接一个赋值语句就可以了。

我们提取的css资源也会变成这样:

.box {
    background: url(https://www.baidu.com/ok/static/97231.png);
}

所以当我们把dist目录部署到服务器的https://www.baidu.com/ok/目录下,资源就可以正常访问了。

一般来说我们我们在production把publicpath设置为cdn的url,在development把publicPath设置为/就可以了。

开发环境

我们在本地开发的时候一般会使用devServer,比如我们进行如下配置:

	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: 'main.js',
		publicPath: '/',
	},
	devServer: {
		port: 8899,
        }

这个时候我们执行

npx webpack serve

就可以在8899端口启动本地服务器了,我们通过localhost:8899就可以访问到我们的本地资源。当启动本地服务器的时候,生成的资源文件是存放在内存中的,我们可以理解为生成的dist目录下的资源放到了内存中,内存中dist目录的url为localhost:8899

devserver中的publicPath

devserve中也有一个publicPath路径,这个路径是用于静态资源的。比如说我们想要访问除了webpack内存中的资源,我还想访问src目录下的原始资源,目录如下:

image.png

正常情况下我们在本地服务器是访问不到src目录下的资源的,这个时候可以进行如下配置:

	devServer: {
		static: {
			directory: path.join(__dirname, 'src'),
			publicPath: '/serve-public-path-url',
		}
		port: 8899,
        }

然后我们就可以通过localhost:8899/serve-public-path-url/a.js访问到src下的a.js文件,这里的publicPath就是你可以用这个路径访问到对应directory目录下的静态资源。

生产环境

生产环境就是将output.publicPath设置为对应的CDN地址就可以了。