Webpack实战——加载资源

加载图片

使用file-loader

file-loader可以把JavaScript和CSS中导入图片的语句替换成正确的地址,并同时把文件输出到对应的位置。例如CSS源码是这样写的:

#app {
  background-image: url(./imgs/a.png);
}

file-loader转换后输出的CSS会变成这样:

#app {
  background-image: url(5556e1251a78c5afda9ee7dd06ad109b.png);
}

并且在输出目录dist中也多出./imgs/a.png对应的图片文件5556e1251a78c5afda9ee7dd06ad109b.png,输出的文件名是根据文件内容的计算出的Hash值。
同理在JavaScript中导入图片的源码如下:

import imgB from './imgs/b.png';

window.document.getElementById('app').innerHTML = `

`;

经过file-loader处理后输出的JavaScript代码如下:

module.exports = __webpack_require__.p + "0bcc1f8d385f78e1271ebfca50668429.png";

也就是说imgB的值就是图片对应的URL地址。
在Webpack中使用file-loader非常简单,相关配置如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.png$/,
        use: ['file-loader']
      }
    ]
  }
};

使用 url-loader

url-loader 可以把文件的内容经过base64编码后注入到JavaScript或者CSS中去。
例如CSS源码是这样写的:

#app {
  background-image: url(./imgs/a.png);
}

url-loader转换后输出的CSS会变成这样:

#app {
  background-image: url(data:image/png;base64,iVBORw01afer...); /* 结尾省略了剩下的 base64 编码后的数据 */
}

同理在JavaScript中效果也类似。
从上面的例子中可以看出url-loader会把根据图片内容计算出的 base64 编码的字符串直接注入到代码中,由于一般的图片数据量巨大, 这会导致JavaScript、CSS文件也跟着变大。 所以在使用url-loader时一定要注意图片体积不能太大,不然会导致JavaScript、CSS文件过大而带来的网页加载缓慢问题。

一般利用url-loader把网页需要用到的小图片资源注入到代码中去,以减少加载次数。因为在HTTP/1协议中,每加载一个资源都需要建立一次HTTP链接, 为了一个很小的图片而新建一次HTTP连接是不划算的。

url-loader考虑到了以上问题,并提供了一个方便的选择limit,该选项用于控制当文件大小小于limit时才使用url-loader,否则使用fallback选项中配置的loader。 相关Webpack配置如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.png$/,
        use: [{
          loader: 'url-loader',
          options: {
            // 30KB 以下的文件采用 url-loader
            limit: 1024 * 30,
            // 否则采用 file-loader,默认值就是 file-loader 
            fallback: 'file-loader',
          }
        }]
      }
    ]
  },
};

除此之外,你还可以做以下优化:

  • 通过 imagemin-webpack-plugin 压缩图片;
  • 通过 webpack-spritesmith 插件制作雪碧图。

以上加载图片的方法同样适用于其它二进制类型的资源,例如PDF、SWF等等。

加载 SVG

SVG 作为矢量图的一种标准格式,已经得到了各大浏览器的支持,它也成为了Web中矢量图的代名词。 在网页中采用SVG代替位图有如下好处:

  • SVG相对于位图更清晰,在任意缩放的情况下后不会破坏图形的清晰度,SVG能方便地解决高分辨率屏幕下图像显示不清楚的问题。
  • 在图形线条比较简单的情况下,SVG文件的大小要小于位图,在扁平化UI流行的今天,多数情况下SVG会更小。
  • 图形相同的SVG比对应的高清图有更好的渲染性能。
  • SVG采用和HTML一致的XML语法描述,灵活性很高。

画图工具能导出一个个.svg文件,SVG的导入方法和图片类似,既可以像下面这样在CSS中直接使用:

body {
  background-image: url(./svgs/activity.svg);
}

也可以在HTML中使用:


也就是说可以直接把SVG文件当成一张图片来使用,方法和使用图片时完全一样。 所以使用file-loader和使用url-loader对SVG来说同样有效,只需要把Loader test配置中的文件后缀改成.svg,代码如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg/,
        use: ['file-loader']
      }
    ]
  },
};

由于SVG是文本格式的文件,除了以上两种方法外还有其它方法,下面来一一说明。

使用 raw-loader

raw-loader 可以把文本文件的内容读取出来,注入到JavaScript或CSS中去。
例如在JavaScript中这样写:

import svgContent from './svgs/alert.svg';

经过raw-loader处理后输出的代码如下:

module.exports = "" // 末尾省略 SVG 内容

也就是说 svgContent的内容就等于字符串形式的SVG,由于SVG 本身就是HTML元素,在获取到SVG内容后,可以直接通过以下代码将SVG插入到网页中:

window.document.getElementById('app').innerHTML = svgContent;

使用raw-loader时相关的Webpack配置如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: ['raw-loader']
      }
    ]
  }
};

由于raw-loader会直接返回SVG的文本内容,并且无法通过CSS去展示SVG的文本内容,因此采用本方法后无法在CSS中导入SVG。 也就是说在 CSS 中不可以出现background-image: url(./svgs/activity.svg)这样的代码,因为background-image: url(...)是不合法的。

使用 svg-inline-loader

svg-inline-loader 和上面提到的raw-loader非常相似, 不同在于svg-inline-loader会分析SVG的内容,去除其中不必要的部分代码,以减少SVG的文件大小。
在使用画图工具如Adobe Illustrator、Sketch制作SVG后,在导出时这些工具会生成对网页运行来说不必要的代码。 举个例子,以下是Sketch导出的SVG的代码:


  

svg-inline-loader处理后会精简成如下:


也就是说svg-inline-loader增加了对SVG的压缩功能。
使用svg-inline-loader时相关的Webpack配置如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: ['svg-inline-loader']
      }
    ]
  }
};

加载 Source Map

由于在开发过程中经常会使用新语言去开发项目,最后会把源码转换成能在浏览器中直接运行的 JavaScript 代码。 这样做虽能提升开发效率,在调试代码的过程中你会发现生成的代码可读性非常差,这给代码调试带来了不便。

Webpack支持为转换生成的代码输出对应的Source Map文件,以方便在浏览器中能通过源码调试。 控制Source Map输出的Webpack 配置项是devtool,它有很多选项。

devtool 含义
不生成 Source Map
eval 每个 module 会封装到 eval 里包裹起来执行,并且会在每个eval语句的末尾追加注释 //# sourceURL=webpack:///./main.js
source-map 会额外生成一个单独Source Map文件,并且会在JavaScript文件末尾追加 //# sourceMappingURL=bundle.js.map
hidden-source-map source-map类似,但不会在JavaScript文件末尾追加 //# sourceMappingURL=bundle.js.map
inline-source-map 和 source-map 类似,但不会额外生成一个单独Source Map文件,而是把Source Map转换成base64编码内嵌到JavaScript中
eval-source-map 和 eval 类似,但会把每个模块的Source Map转换成base64编码内嵌到eval语句的末尾,例如 //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW...
cheap-source-map source-map类似,但生成的Source Map文件中没有列信息,因此生成速度更快
cheap-module-source-map cheap-source-map类似,但会包含Loader生成的Source Map

其实以上表格只是列举了devtool可能取值的一部分, 它的取值其实可以由source-mapevalinlinehiddencheapmodule这六个关键字随意组合而成。 这六个关键字每个都代表一种特性,它们的含义分别是:

  • eval:用eval语句包裹需要安装的模块;
  • source-map:生成独立的Source Map文件;
  • hidden:不在JavaScript文件中指出Source Map文件所在,这样浏览器就不会自动加载Source Map;
  • inline:把生成的Source Map转换成base64格式内嵌在JavaScript文件中;
  • cheap:生成的Source Map中不会包含列信息,这样计算量更小,输出的Source Map文件更小;同时Loader输出的Source Map不会被采用;
  • module:来自Loader的Source Map被简单处理成每行一个模块;

该如何选择

Devtool配置项提供的这么多选项看似简单,但很多人搞不清楚它们之间的差别和应用场景。

如果你不关心细节和性能,只是想在不出任何差错的情况下调试源码,可以直接设置成 source-map,但这样会造成两个问题:

  • source-map模式下会输出质量最高最详细的Source Map,这会造成构建速度缓慢,特别是在开发过程需要频繁修改的时候会增加等待时间;
  • source-map模式下会把 Source Map 暴露出去,如果构建发布到线上的代码的Source Map暴露出去就等于源码被泄露;

为了解决以上两个问题,可以这样做:

  • 在开发环境下把 devtool 设置成 cheap-module-eval-source-map,因为生成这种 Source Map 的速度最快,能加速构建。由于在开发环境下不会做代码压缩,Source Map 中即使没有列信息也不会影响断点调试;
  • 在生产环境下把 devtool 设置成 hidden-source-map,意思是生成最详细的 Source Map,但不会把 Source Map 暴露出去。由于在生产环境下会做代码压缩,一个 JavaScript 文件只有一行,所以需要列信息。

在生产环境下通常不会把Source Map上传到HTTP服务器让用户获取,而是上传到JavaScript错误收集系统,在错误收集系统上根据Source Map和收集到的JavaScript运行错误堆栈计算出错误所在源码的位置。
不要在生产环境下使用inline模式的Source Map, 因为这会使JavaScript文件变得很大,而且会泄露源码。

加载现有的 Source Map

有些从Npm安装的第三方模块是采用ES6或者TypeScript编写的,它们在发布时会同时带上编译出来的JavaScript文件和对应的Source Map文件,以方便你在使用它们出问题的时候调试它们;

默认情况下Webpack是不会去加载这些附加的Source Map文件的,Webpack只会在转换过程中生成 Source Map。 为了让Webpack加载这些附加的Source Map文件,需要安装 source-map-loader 。 使用方法如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // 只加载你关心的目录下的 Source Map,以提升构建速度
        include: [path.resolve(root, 'node_modules/some-components/')],
        use: ['source-map-loader'],
        // 要把source-map-loader的执行顺序放到最前面,
        // 如果在source-map-loader之前有Loader转换了该 JavaScript文件,会导致Source Map映射错误
        enforce: 'pre'
      }
    ]
  }
};

由于source-map-loader在加载Source Map时计算量很大,因此要避免让该Loader处理过多的文件,不然会导致构建速度缓慢。通常会采用include去命中只关心的文件。
再安装新引入的依赖:

npm i -D source-map-loader

重启Webpack后,你就能在浏览器中调试node_modules/some-components/目录下的源码了。

你可能感兴趣的:(Webpack实战——加载资源)