10

前言

webpack,作为一个处理模块加载、资源依赖管理、构建化的工具,已经逐渐成为了前端工程化领域的新贵。其创造性的把每个静态资源归为一个 module(模块)并能被其强大的 loader 所加载的这种方式,成功的开辟了前端工程界的另一大生态。基于其官网文档的完善度较高,这篇文章就不对 webpack 的主要内容做过多的介绍,而是回归到本文的主题,即通过介绍几款 webpack 相关的插件,来解决一个常见的工程问题:如何做到静态资源路径可以在不同的环境下自动切换。

问题说明

这到底是个怎样的问题?设想一下,在使用 webpack 打包编译之后,它会生成一个 js 文件,随后我们需要在 html 或者模板文件里指定这个文件的路径确保其被正确的引入,

<script type="javascript" src="app.js"></script>

对于开发环境下的单入口文件(稍后会介绍 webpack 打包到多个入口的解决方案 ),这个标签内的引入文件路径完全可以写死,而且在 webpack-dev-server 热替换机制的帮助下,我们也无须通过对打包生成的文件添加 hash 值来处理因浏览器缓存的缘故引起的引用不到最新资源。

但在产品模式下,我们非常有必要在 webpack 的 output 属性里的 filename 里配置一个 chunkhash 来变向的为静态资源注入版本号,如下,

output: {
 filename: [name].[chunkhash].js,
}

以便上线之后页面可以引入版本更新后的代码。chunkhash 是一个基于文件内容,通过摘要算法(如md5)生成的一个被称之为文件指纹的序号,即只有当文件内容发生改变的时候,这个值才会相应更改。

通过给静态资源注入 hash 值来作为版本号的好处主要有两个:

  1. 实现 long term caching 策略。当发布新版本时,我们只需要更新更改了的资源。这比起将新版资源存放在例如/v1.3/xx.js这种带版本号的路径或文件夹下的部署方式会显得更科学一点:减少手动配置版本号的额外操作、已经缓存过且缓存尚未过期的浏览器只需请求更新过的资源,确保未变更过的资源可以依旧从缓存内读取。

  2. 实现非覆盖式发布策略。张云龙老师的原文中提到的这种平滑的版本升级方式更加完美的解决了静态资源部署至CDN出现的问题。

这个时候我们再来看下线上的 script 引入,

<script type="javascript" src="http://xxx.cdn.com/app.82076244596568c8c929.js
"></script>

Fine, 也许你会说我可以手动 copy/paste 这个版本号当你需要从开发切到产品环境,额额,单个入口文件这么处理虽是可以,但想象下当有多个入口文件的时候。。。(感觉我的左手大拇指肌腱炎又要犯了。。),这么经典的问题webpack早已准备好了它的解决方案。

从 webpack 的编译数据里获取开发与生产的资源路径对应关系

这一部分的工作可以说是解决这个问题的一个核心环节,即我们需要通过 webpack 来生成类似如下一张对应关系图:

{
    'app.js': 'http://xxx.cdn.com/app.82076244596568c8c929.js'
}

像在 webpack 的 plugin 属性里配置如下,我们就可以通过返回 webpack 的编译数据里获取到带有 chunkhash 的文件信息:

// webpack.config.js
module.exports = {
  ...
  plugins: [
    function() {
      this.plugin("done", function(stats) {
        require("fs").writeFileSync(
          path.join(__dirname, "..", "stats.json"),
          JSON.stringify(stats.toJson()));
      });
    }
  ]
}

stats.json require 到项目中,通过读取 publicPathassetsByChunkName 属性,可以得到开发与线上环境资源路径的对应关系。

webpack 官方也推荐了几个有同样效果,我个人觉得更好用的插件:assets-webpack-plugin 或者 webpack-manifest-plugin 来生成出一个 JSON 对应关系文件。

切换资源路径

接下来的工作基本上就是如何利用这个对应关系来切换对应环境下的路径。这个还要取决于你的页面是否会涉及到服务端的渲染。

服务端渲染资源路径

以 node 作为服务端语言,handlebars(或者ejs)为模板语言为例,我们通过编写模板语言的 helper 来读取由 assets-webpack-plugin 生成的 stats.json,在不同的环境下实现路径切换:

stats.json -- webpack 跑开发配置

{
    "app": {
        "js": "app.js"
    }
}

stats.json -- webpack 跑生产配置

{
    "app": {
        "js": "http://xxx.cdn.com/app.82076244596568c8c929.js"
    }
}

example.handlebars

<script type="text/javascript" src="{{app.js}}"></script>

后台通过 require stats.json 数据并传入到模板即可实现根据环境动态渲染资源路径。

如果你的后台是使用 Rails 来搭建的话,那么这篇文章更详细的介绍了处理这种情况下处理资源切换的问题

前端渲染页面模板

如果你的项目不依赖任何后端渲染,那么 html-webpack-plugin 这款插件可以为你动态生成一个带有 css、js 等资源路径的 html 文件。

html-webpack-plugin 具体的用法可以点击这里,其中 inject 这个属性可以让你将 script 标签插入到 dom 的指定位置。为了能够更大权限的将 webpack 编译过的资源可以插入到 html 文件的任意位置,我们可以在 HtmlWebpackPlugin 里指定的 template 文件里写入如下代码:

  <% for (var css in htmlWebpackPlugin.files.css) { %>
  <link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
  <% } %>
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
<% } %>

就可以同样实现静态资源的切换,所以对于前端渲染模板的这种情况,我们无须再生成一个 json 文件,对于使用诸如 react、vue 这种框架,仅使用这个插件也是极好的。

htmlWebpackPlugin 具体还有哪些属性可以配置,可以参考下这个 default template 查看完整例子


cxhtml
188 声望1 粉丝

Test my limit~