webpack之devServer.publicPath、output.publicPath快速理解 | 小册免费学

1,326 阅读6分钟

随着 vue-cli 等脚手架工具对webpack的集成度越来越高,竟然项目都做完了还浑然不知自己对于打包进行了哪些配置和优化。基于这种现状自己动手尝试了一下对于打包的配置,这个过程中确实遇到了一些傻傻分不清楚的问题,个人觉得 webpack 官网的中文翻译有些地方比较晦涩,也可能是自己水平有限理解有限,这个暂且不说了唯有多学习来提高。

本文记录了在配置过程中通过反复尝试,对于 output.publicPath、output.path、devServer.publicPath、devServer.contentBase 的一些理解,因为这几个配置项如果使用错误通常会在请求资源时出现404的情况。

devServer

需要提到的一点是:webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server 根路径上的真实文件一样。如果你的页面希望在其他不同路径中找到 bundle 文件,则可以通过 devServer 配置中的 publicPath 选项进行修改。(注:serve,将资源作为server的可访问文件)

devServer.publicPath

顾名思义,devServer 的各种配置只在development模式下有效。根据官方文档 publicPath 默认值为/,所以,假设服务器运行在http://localhost:8080并且output.filename被设置为bundle.js,我们可以通过http://localhost:8080/bundle.js来访问 bundle 文件。

也可以修改devServer.publicPath,将 bundle 放在指定目录下:

module.exports = {
  // ...
  devServer: {
    publicPath: '/assets/'
  }
}

现在可以通过http://localhost:8080/assets/bundle.js访问bundle。

确保devServer.publicPath总是以斜杠/开头和结尾。

webpack-dev-server 可以看作是一个服务者,它的主要工作就是接收浏览器的请求,然后将资源返回。当服务启动时,会先让 webpack 进行模块打包并将资源准备好。当 webpack-dev-server 接收到浏览器的资源请求时,它会首先进行 URL 地址校验。如果该地址是资源服务地址(上面配置的publicPath),就会从 webpack 的打包结果中寻找该资源并返回给浏览器。反之,如果请求地址不属于资源服务地址,则直接读取硬盘中的源文件并将其返回。

对于上述我们做一个简单的验证:

配置文件如下:

image-20210328221636259.png

项目目录结构如下:

image-20210328232132559.png

在浏览器中访问http://localhost:8081/girl.jpeg,因为该地址并非 publicPath 配置的资源服务地址,所以直接读取硬盘中的源文件进行返回。

image-20210328222816171.png

综上所述:publicPath字段的意义应该比较清楚了,即在浏览器中能够访问到的构建文件的路径

devServer.contentBase

对于contentBase的解释官网描述为:告诉服务器从哪个目录中提供内容,只有在你想要提供静态文件时才需要。

其实就是 index.html 所在的目录,如果不配置默认为项目根目录。

下面来验证一下:

image-20210328231227613.png 浏览器中访问:http://localhost:8081,展示如下:

image-20210328231404950.png

我们将 contentBase 修改为contentBase: path.join(__dirname, 'public'),继续访问http://localhost:8081,展示如下(项目public目录中有favicon.ico和girl.jpeg两个资源):

image-20210328231735412.png

output

output.publicPath

output 也有一个 publicPath 属性,那和 devServer.publicPath 有没有关联呢?答案是有的,如果 devServer.publicPath 没有设定值,那么开发服务器就会尝试去读取 output.publicPath 的值,如果 output.publicPath 显式设定了值则使用这个值,如果没有设定值就使用 devServer.publicPath 的默认值/

那我们现在来看一下只给output.publicPath设定值,而不给devServer.publicPath设定值会是什么情况,比如设定output.publicPath: /assets/

image-20210329212929251.png

image-20210329212646189.png

接下来,同时给二者设定不同的值,比如设定devServer.publicPath: /path/

image-20210329213136162.png

image-20210329214941747.png

但是,这两个值不同会带来其他的问题:

image-20210329215128518.png

浏览器中访问的是http://localhost:8081/path/index.html,但是,script 标签引用的资源文件前缀是assets,也就是说以output.publicPath的值为准。

其实,publicPath 是一个非常重要的配置项,并且容易与 path 相混淆。从功能上来说,path 用来指定资源的输出位置,而 publicPath 则用来指定资源的请求位置。

  • 输出位置:打包完成后资源产生的目录,一般将其指定为工程中的dist目录。

  • 请求位置:由 JS 或 CSS 所请求的间接资源路径。页面中的资源分为两种,一种是由 HTML 页面直接请求的,比如通过 script 标签加载的 JS ;另一种是由 JS 或 CSS 请求的,如异步加载的 JS、从 CSS 请求的图片字体等。publicPath 的作用就是指定这部分间接资源的请求位置。

publicPath 有三种形式,下面逐一介绍:

  1. HTML相关

    与 HTML 相关,也就是说我们可以将 publicPath 指定为 HTML 的相对路径。在请求这些资源时会以当前页面 HTML 所在路径加上相对路径,构成实际请求的URL。如:

    // 假设当前HTML地址为 https://example.com/app/index.html
    // 异步加载的资源名为 0.chunk.js
    publicPath: '' // 实际路径 https://example.com/app/0.chunk.js
    publicPath: './js' // 实际路径为 https://example.com/app/js/0.chunk.js
    publicPath: '../assets/' // 实际路径为 https://example.com/assets/0.chunk.js
    
  2. Host相关

    若 publicPath 的值以/开始,则代表此时 publicPath 是以当前页面的 host name 为基础路径的。如:

    // 假设当前HTML地址为 https://example.com/app/index.html
    // 异步加载的资源名为 0.chunk.js
    publicPath: '/' // 实际路径 https://example.com/0.chunk.js
    publicPath: '/js/' // 实际路径为 https://example.com/js/0.chunk.js
    publicPath: '/dist/' // 实际路径为 https://example.com/dist/0.chunk.js
    
  3. CDN相关

    上面两种配置都是相对路径,我们可以使用绝对路径的形式配置 publicPath。这种情况一般发生于静态资源放在 CDN上 面时,由于其域名与当前页面域名不一致,需要以绝对路径的形式进行指定。当 publicPath 以协议头或相对协议的形式开始时,代表当前路径是 CDN 相关。如:

    // 假设当前HTML地址为 https://example.com/app/index.html
    // 异步加载的资源名为 0.chunk.js
    publicPath: 'http://cdn.com/' // 实际路径 http://cdn.com/0.chunk.js
    publicPath: 'https://cdn.com/' // 实际路径为 https://cdn.com/0.chunk.js
    publicPath: '//cdn.com/assets/' // 实际路径为 //cdn.com/assets/0.chunk.js
    

output.path

path 可以指定资源输出的位置,要求值必须为绝对路径。如:

const path = require('path');
module.exports = {
  entry: './src/app.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist')
  }
}

上述配置将资源输出位置为工程的 dist 目录。在 webpack v4 以前的版本中,打包资源默认会生成在工程根目录,因此我们需要上述配置;而在 webpack v4 之后,output.path 已经默认为 dist 目录,除非我们需要更改它,否则不必单独配置。

结合devServer.publicPath(指定webpack-dev-server的静态资源服务路径),我们看下面的例子:

const path = require('path');
module.exports = {
  entry: './src/app.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist')
  },
  devServer: {
    publicPath: '/assets/',
    port: 3000
  }
}

从上面可以看到,webpack配置中 output.path 为 dist 目录,因此 bundle.js 应该生成在 dist 目录。但是当我们启动webpack-dev-server的服务后,访问 localhost:3000/dist/bundle.js 时却会得到404.这是因为devServer.publicPath将资源位置指向了localhost:3000/assets/,因此只有访问localhost:3000/assets/bundle.js才能得到我们想要的结果。

为了避免开发环境和生产环境产生不一致而造成开发者的疑惑,我们可以将webpack-dev-server的publicPath与webpack中的output.path保持一致,这样在任何环境下资源输出的目录都是相同的。看下面的示例:

const path = require('path');
module.exports = {
  entry: './src/app.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist')
  },
  devServer: {
    publicPath: '/dist/',
    port: 3000
  }
}

上面的配置可以保证访问localhost:3000/dist/bundle.js 时得到预期的结果。

本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情

参考:Webpack中文网