Webpack--理解“publicPath”的奥秘(翻译)

原文:Webpack — Understanding the ‘publicPath’ mystery

你曾多少次被webapck's publicPath的配置绊倒?老实说,在每次开始一个新项目的时候,我总是遇到这个问题,并且花费很长时间去探索它到底是怎么工作的。我阅读了官方文档,但并没有什么用。
所以,这里我决定根据我的经验和理解去揭开publicPath的神秘面纱。如果对你有帮助,欢迎反馈意见。

当你的publicPath配置错误时,通常会遇到这2个问题:

  1. webpack-dev-server: live-reload(浏览器自动刷新)机制没有正常执行,比如说,浏览器不会自动重新加载,或者加载的代码不是最新的。
  2. 生成的css文件,图片、字体路径发生报错,打断编译打包的过程。

让我们来从一个基础的配置去分析这件事。我们有一个文件结构,如下显示:

appMain [Project Folder]
|-- src
     |-- index.js
|-- index.html
|-- webpack.config.js
|-- package.json
|-- dist
// 极简配置的webpack
module.exports = {
  entry: './src/index.js',
  output: {
    filename: './dist/bundle.js'
  }
};
// index.html中引入生成的bundle.js

如果我们只运行 webpack命令,会默认执行webpack.config.js, 生产打包文件到 dist 文件夹下。

此外,如果你使用了 webpack-dev-server启动,live-reload就开始生效了,即是,任何在js源文件 [index.js] 中的修改都会立即在浏览器中生效,不需要手动刷新。
我们可以通过console.log()打印一些消息来测试这个功能。

注意:webpack-dev-server 启动的服务,使用的并不是真实生成的包文件,它只是监听你的源代码,当它们发生变化时去重新编译。这个修改的包是直接从内存中读取的。

现在让我们加入配置path到webpack output 对象中,来代替在output.filename中使用完整路径的配置。

const resolve = require('path').resolve;

module.exports = {
  entry: './src/index.js',
  output: {
    // seperated path and filename of generated output
    path: resolve('dist'),
    filename: 'bundle.js'
  }
};

请注意output.path需要使用绝对路径而不是相对路径,否则webpack会报错。所以,我们使用了path模块的resolve方法。

报错信息: Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema. \- configuration.output.path: The provided value "./dist" is not an absolute path!

在这种情况下,webpack会生成错误的输出,比如有bundle.js文件,但webpack-dev-serverlive-reload会突然停止执行。并且,关于源文件[index.js]的任何修改,无论怎么刷新浏览器都无法显示,尽管终端显示源文件已经编译成功。

现在,如果你停止你的webpack-dev-server 服务,再次执行webpack命令,然后再重新启动 webpack-dev-server,修改的自动更新又正常了,但是我们不能每次都这样做吧,真的神烦。

所以,是哪个环节出错了?

主要是因为当path的未配置时,webpack-dev-config 会把它的值默认为项目的根目录,即 ./

const resolve = require('path').resolve;

module.exports = {
  entry: './src/index.js',
  output: {
    // 如果path未配置,默认值是 './'
    path: resolve('./'),
    filename: './dist/bundle.js'
  }
};

所以,可以正常工作。
但当我们配置为真实的路径,比如reslove('dist'), webpack仍然在./位置编译生成输出文件。
你可以直接在浏览器中访问服务的URL地址验证这一点

http://localhost:8080/dist/bundle.js
// 总是指向我们运行webpack命令时生成的旧代码
http://localhost:8080/bundle.js
// 总是指向最新编译后的代码,在src变化后会立即重新加载
注意:这两个打包的文件并不是完全相同的,后者因为使用dev-server会注入一些额外的代码

所以,这里live-reload旧代码导致的问题是,我们使用了前者打包的 bundle.js,却在index.html使用的是 webpack 命令生成的路径。
任何代码的改变,webpack-dev-server 会生成一个包,但地址是不同的。

解决这个问题,只需要改变一下index.html中的script:src属性,一切又能正常运行了。
神速!我们理解并修复了这个问题。

进一步来说,我们使用 webpack 的publicPath去配置 webpack-dev-server 生成的打包文件的位置,是个虚拟位置, 而不是真实的文件系统。
这个虚拟位置可以用来在 index.html 中定位引入生成的文件。

const resolve = require('path').resolve;

module.exports = {
  entry: './src/index.js',
  output: {
    path: resolve('dist'),
    filename: 'bundle.js',
    // Add vitual path for generating the bundle files
    publicPath: 'some-virtual-location'
  }
};
// 更新script:src到新的虚拟位置(Virtual-Path)

这样,我们就可以在本地开发中模拟真实服务器部署环境或者CDN。
一些开发者更喜欢命名为 assets, public, dist, / 等等,这完全看你喜欢如何维护生成的文件结构和命名方式。
底线就是,index.html 中引入的script:src的值必须与 publicPath 的配置保持一致。

publicPath对CSS的其他影响 -- 图片、字体的路径等等

在生成最终的styles时,webpack默认会把 publicPath的配置添加到 图片的URL,字体的路径上。
示例,我添加2个文件到当前的项目中:

appMain
|-- src
     |-- index.js
     |-- main.css [Source CSS/SCSS file]
     |-- background-image.jpg
|-- index.html
|-- webpack.config.js
|-- package.json
|-- dist

main.css (使用相对路径引入背景图片)

.main {
    background-image: url("background.jpg");
}

我们使用了 css/scss 文件和图片,所以也要更新webpack.config配置,添加相应的 loaders 或 plugin。
webpack.config.js

const resolve = require('path').resolve;
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: resolve('dist'),
    filename: 'bundle.js',
    publicPath: 'some-virtual-location/'
  },
  // 添加 CSS 和 Style Loader
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader'
        })
      },
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name].[ext]'
            }
          }
        ]
      }
    ]
  },
  // 生成单独的样式文件
  plugins: [
    new ExtractTextPlugin('styles.css')
  ]
};

当你运行webpack命令时,会触发下面行为:

  1. background-image.jpg 文件会被 file-loader拷贝到dist文件夹中。
  2. styles.css会被插件 ExtractTextPlugin 根据main.css生成到dist文件夹中。
  3. 生成的css文件中background-image: url会被自动添加publicPath的配置。
.main {
    background-image: url(some-virtual-location/background.jpg);
}

这个规则同样适用于其他的静态资源,比如fonts.
所以正确的配置publicPath非常重要,Webpack 的 Loaders 和 Plugins 会使用这个配置。

希望这篇文章能让你深入的理解神秘的publicPath是怎么工作的,怎样正确的使用它。

欢迎在评论区写下你的想法和意见,我们可以进一步的讨论。✌✌

你可能感兴趣的:(Webpack--理解“publicPath”的奥秘(翻译))