管理资源

如果你是从开始一直遵循着指南的示例,现在会有一个小项目,显示 “Hello webpack”。现在我们尝试混合一些其他资源,比如 images,看看 webpack 如何处理。

在 webpack 出现之前,前端开发人员会使用 grunt 和 gulp 等工具来处理资源,并将它们从 /src 文件夹移动到 /dist/build 目录中。JavaScript 模块也遵循同样方式,但是,像 webpack 这样的工具,将动态打包所有依赖(创建所谓的 依赖图(dependency graph))。这是极好的创举,因为现在每个模块都可以明确表述它自身的依赖,可以避免打包未使用的模块。

webpack 最出色的功能之一就是,除了引入 JavaScript,还可以通过 loader 引入任何其他类型的文件。也就是说,以上列出的那些 JavaScript 的优点(例如显式依赖),同样可以用来构建 web 站点或 web 应用程序中的所有非 JavaScript 内容。让我们从 CSS 开始起步,或许你可能已经熟悉了下面这个设置。

安装

在开始之前,让我们对项目做一个小的修改:

dist/index.html

  1. <!doctype html>
  2. <html>
  3. <head>
  4. - <title>起步</title>
  5. + <title>管理资源</title>
  6. </head>
  7. <body>
  8. - <script src="./main.js"></script>
  9. + <script src="./bundle.js"></script>
  10. </body>
  11. </html>

webpack.config.js

  1. const path = require('path');
  2. module.exports = {
  3. entry: './src/index.js',
  4. output: {
  5. - filename: 'main.js',
  6. + filename: 'bundle.js',
  7. path: path.resolve(__dirname, 'dist')
  8. }
  9. };

加载 CSS

为了在 JavaScript 模块中 import 一个 CSS 文件,你需要安装 style-loadercss-loader,并在 module 配置 中添加这些 loader:

  1. npm install --save-dev style-loader css-loader

webpack.config.js

  1. const path = require('path');
  2. module.exports = {
  3. entry: './src/index.js',
  4. output: {
  5. filename: 'bundle.js',
  6. path: path.resolve(__dirname, 'dist')
  7. },
  8. + module: {
  9. + rules: [
  10. + {
  11. + test: /\.css$/,
  12. + use: [
  13. + 'style-loader',
  14. + 'css-loader'
  15. + ]
  16. + }
  17. + ]
  18. + }
  19. };

webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。在这个示例中,所有以 .css 结尾的文件,都将被提供给 style-loadercss-loader

这使你可以在依赖于此样式的 js 文件中 import './style.css'。现在,在此模块执行过程中,含有 CSS 字符串的 <style> 标签,将被插入到 html 文件的 <head> 中。

我们尝试一下,通过在项目中添加一个新的 style.css 文件,并将其 import 到我们的 index.js 中:

project

  1. webpack-demo
  2. |- package.json
  3. |- webpack.config.js
  4. |- /dist
  5. |- bundle.js
  6. |- index.html
  7. |- /src
  8. + |- style.css
  9. |- index.js
  10. |- /node_modules

src/style.css

  1. .hello {
  2. color: red;
  3. }

src/index.js

  1. import _ from 'lodash';
  2. + import './style.css';
  3. function component() {
  4. var element = document.createElement('div');
  5. // lodash 是由当前 script 脚本 import 导入进来的
  6. element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  7. + element.classList.add('hello');
  8. return element;
  9. }
  10. document.body.appendChild(component());

现在运行 build 命令:

  1. npm run build
  2. ...
  3. Asset Size Chunks Chunk Names
  4. bundle.js 76.4 KiB 0 [emitted] main
  5. Entrypoint main = bundle.js
  6. ...

再次在浏览器中打开 index.html,你应该看到 Hello webpack 现在的样式是红色。要查看 webpack 做了什么,请检查页面(不要查看页面源代码,因为它不会显示结果),并查看页面的 head 标签。它应该包含 style 块元素,也就是我们在 index.js 中 import 的 css 文件中的样式。

注意,在多数情况下,你也可以进行 CSS 提取,以便在生产环境中节省加载时间。最重要的是,现有的 loader 可以支持任何你可以想到的 CSS 风格 - postcss, sassless 等。

加载 images 图像

假想,现在我们正在下载 CSS,但是像 background 和 icon 这样的图像,要如何处理呢?使用 file-loader,我们可以轻松地将这些内容混合到 CSS 中:

  1. npm install --save-dev file-loader

webpack.config.js

  1. const path = require('path');
  2. module.exports = {
  3. entry: './src/index.js',
  4. output: {
  5. filename: 'bundle.js',
  6. path: path.resolve(__dirname, 'dist')
  7. },
  8. module: {
  9. rules: [
  10. {
  11. test: /\.css$/,
  12. use: [
  13. 'style-loader',
  14. 'css-loader'
  15. ]
  16. },
  17. + {
  18. + test: /\.(png|svg|jpg|gif)$/,
  19. + use: [
  20. + 'file-loader'
  21. + ]
  22. + }
  23. ]
  24. }
  25. };

现在,在 import MyImage from './my-image.png' 时,此图像将被处理并添加到 output 目录,_并且_ MyImage 变量将包含该图像在处理后的最终 url。在使用 css-loader 时,如前所示,会使用类似过程处理你的 CSS 中的 url('./my-image.png')。loader 会识别这是一个本地文件,并将 './my-image.png' 路径,替换为 output 目录中图像的最终路径。而 html-loader 以相同的方式处理 <img src="./my-image.png" />

我们向项目添加一个图像,然后看它是如何工作的,你可以使用任何你喜欢的图像:

project

  1. webpack-demo
  2. |- package.json
  3. |- webpack.config.js
  4. |- /dist
  5. |- bundle.js
  6. |- index.html
  7. |- /src
  8. + |- icon.png
  9. |- style.css
  10. |- index.js
  11. |- /node_modules

src/index.js

  1. import _ from 'lodash';
  2. import './style.css';
  3. + import Icon from './icon.png';
  4. function component() {
  5. var element = document.createElement('div');
  6. // lodash,现在由此脚本导入
  7. element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  8. element.classList.add('hello');
  9. + // 将图像添加到我们已经存在的 div 中。
  10. + var myIcon = new Image();
  11. + myIcon.src = Icon;
  12. +
  13. + element.appendChild(myIcon);
  14. return element;
  15. }
  16. document.body.appendChild(component());

src/style.css

  1. .hello {
  2. color: red;
  3. + background: url('./icon.png');
  4. }

重新构建并再次打开 index.html 文件:

  1. npm run build
  2. ...
  3. Asset Size Chunks Chunk Names
  4. da4574bb234ddc4bb47cbe1ca4b20303.png 3.01 MiB [emitted] [big]
  5. bundle.js 76.7 KiB 0 [emitted] main
  6. Entrypoint main = bundle.js
  7. ...

如果一切顺利,你现在应该看到你的 icon 图标成为了重复的背景图,以及 Hello webpack 文本旁边的 img 元素。如果检查此元素,你将看到实际的文件名已更改为 5c999da72346a995e7e2718865d019c8.png。这意味着 webpack 在 src 文件夹中找到我们的文件,并对其进行了处理!

合乎逻辑下一步是,压缩和优化你的图像。查看 image-webpack-loaderurl-loader,以了解更多关于如果增强加载处理图像功能。

加载 fonts 字体

那么,像字体这样的其他资源如何处理呢?file-loader 和 url-loader 可以接收并加载任何文件,然后将其输出到构建目录。这就是说,我们可以将它们用于任何类型的文件,也包括字体。让我们更新 webpack.config.js 来处理字体文件:

webpack.config.js

  1. const path = require('path');
  2. module.exports = {
  3. entry: './src/index.js',
  4. output: {
  5. filename: 'bundle.js',
  6. path: path.resolve(__dirname, 'dist')
  7. },
  8. module: {
  9. rules: [
  10. {
  11. test: /\.css$/,
  12. use: [
  13. 'style-loader',
  14. 'css-loader'
  15. ]
  16. },
  17. {
  18. test: /\.(png|svg|jpg|gif)$/,
  19. use: [
  20. 'file-loader'
  21. ]
  22. },
  23. + {
  24. + test: /\.(woff|woff2|eot|ttf|otf)$/,
  25. + use: [
  26. + 'file-loader'
  27. + ]
  28. + }
  29. ]
  30. }
  31. };

在项目中添加一些字体文件:

project

  1. webpack-demo
  2. |- package.json
  3. |- webpack.config.js
  4. |- /dist
  5. |- bundle.js
  6. |- index.html
  7. |- /src
  8. + |- my-font.woff
  9. + |- my-font.woff2
  10. |- icon.png
  11. |- style.css
  12. |- index.js
  13. |- /node_modules

配置好 loader 并将字体文件放在合适的位置后,你可以通过一个 @font-face 声明将其混合。本地的 url(...) 指令会被 webpack 获取处理,就像它处理图片一样:

src/style.css

  1. + @font-face {
  2. + font-family: 'MyFont';
  3. + src: url('./my-font.woff2') format('woff2'),
  4. + url('./my-font.woff') format('woff');
  5. + font-weight: 600;
  6. + font-style: normal;
  7. + }
  8. .hello {
  9. color: red;
  10. + font-family: 'MyFont';
  11. background: url('./icon.png');
  12. }

现在,让我们重新构建,然后看下 webpack 是否处理了我们的字体:

  1. npm run build
  2. ...
  3. Asset Size Chunks Chunk Names
  4. 5439466351d432b73fdb518c6ae9654a.woff2 19.5 KiB [emitted]
  5. 387c65cc923ad19790469cfb5b7cb583.woff 23.4 KiB [emitted]
  6. da4574bb234ddc4bb47cbe1ca4b20303.png 3.01 MiB [emitted] [big]
  7. bundle.js 77 KiB 0 [emitted] main
  8. Entrypoint main = bundle.js
  9. ...

重新打开 index.html 看看我们的 Hello webpack 文本显示是否换上了新的字体。如果一切顺利,你应该能看到变化。

加载数据

此外,可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于 NodeJS,JSON 支持实际上是内置的,也就是说 import Data from './data.json' 默认将正常运行。要导入 CSV、TSV 和 XML,你可以使用 csv-loaderxml-loader。让我们处理加载这三类文件:

  1. npm install --save-dev csv-loader xml-loader

webpack.config.js

  1. const path = require('path');
  2. module.exports = {
  3. entry: './src/index.js',
  4. output: {
  5. filename: 'bundle.js',
  6. path: path.resolve(__dirname, 'dist')
  7. },
  8. module: {
  9. rules: [
  10. {
  11. test: /\.css$/,
  12. use: [
  13. 'style-loader',
  14. 'css-loader'
  15. ]
  16. },
  17. {
  18. test: /\.(png|svg|jpg|gif)$/,
  19. use: [
  20. 'file-loader'
  21. ]
  22. },
  23. {
  24. test: /\.(woff|woff2|eot|ttf|otf)$/,
  25. use: [
  26. 'file-loader'
  27. ]
  28. },
  29. + {
  30. + test: /\.(csv|tsv)$/,
  31. + use: [
  32. + 'csv-loader'
  33. + ]
  34. + },
  35. + {
  36. + test: /\.xml$/,
  37. + use: [
  38. + 'xml-loader'
  39. + ]
  40. + }
  41. ]
  42. }
  43. };

在项目中添加一些数据文件:

project

  1. webpack-demo
  2. |- package.json
  3. |- webpack.config.js
  4. |- /dist
  5. |- bundle.js
  6. |- index.html
  7. |- /src
  8. + |- data.xml
  9. |- my-font.woff
  10. |- my-font.woff2
  11. |- icon.png
  12. |- style.css
  13. |- index.js
  14. |- /node_modules

src/data.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <note>
  3. <to>Mary</to>
  4. <from>John</from>
  5. <heading>Reminder</heading>
  6. <body>Call Cindy on Tuesday</body>
  7. </note>

现在,你可以 import 这四种类型的数据(JSON, CSV, TSV, XML)中的任何一种,所导入的 Data 变量,将包含可直接使用的已解析 JSON:

src/index.js

  1. import _ from 'lodash';
  2. import './style.css';
  3. import Icon from './icon.png';
  4. + import Data from './data.xml';
  5. function component() {
  6. var element = document.createElement('div');
  7. // lodash,现在通过 script 标签导入
  8. element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  9. element.classList.add('hello');
  10. // 将图像添加到我们已经存在的 div 中。
  11. var myIcon = new Image();
  12. myIcon.src = Icon;
  13. element.appendChild(myIcon);
  14. + console.log(Data);
  15. return element;
  16. }
  17. document.body.appendChild(component());

重新执行 npm run build 命令,然后打开 index.html。查看开发者工具中的控制台,你应该能够看到导入的数据会被打印出来!

在使用 d3 等工具实现某些数据可视化时,这个功能极其有用。可以不用在运行时再去发送一个 ajax 请求获取和解析数据,而是在构建过程中将其提前加载到模块中,以便浏览器加载模块后,直接就可以访问解析过的数据。

全局资源

上述所有内容中最出色之处在于,以这种方式加载资源,你可以以更直观的方式将模块和资源组合在一起。无需依赖于含有全部资源的 /assets 目录,而是将资源与代码组合在一起使用。例如,类似这样的结构会非常有用:

  1. - |- /assets
  2. + |– /components
  3. + | |– /my-component
  4. + | | |– index.jsx
  5. + | | |– index.css
  6. + | | |– icon.svg
  7. + | | |– img.png

这种配置方式会使你的代码更具备可移植性,因为现有的集中放置的方式会让所有资源紧密耦合起来。假如你想在另一个项目中使用 /my-component,只需将其复制或移动到 /components 目录下。只要你已经安装过全部外部依赖,并且已经在配置中定义过相同的 loader,那么项目应该能够良好运行。

但是,假如你只能被局限在旧有开发方式,或者你有一些在多个组件(视图、模板、模块等)之间共享的资源。你仍然可以将这些资源存储在一个基本目录(base directory)中,甚至配合使用 alias 来使它们更方便 import 导入

回退处理

对于下篇指南,我们无需使用本指南中所有用到的资源,因此我们会进行一些清理工作,以便为下篇指南 管理输出 做好准备:

project

  1. webpack-demo
  2. |- package.json
  3. |- webpack.config.js
  4. |- /dist
  5. |- bundle.js
  6. |- index.html
  7. |- /src
  8. - |- data.xml
  9. - |- my-font.woff
  10. - |- my-font.woff2
  11. - |- icon.png
  12. - |- style.css
  13. |- index.js
  14. |- /node_modules

webpack.config.js

  1. const path = require('path');
  2. module.exports = {
  3. entry: './src/index.js',
  4. output: {
  5. filename: 'bundle.js',
  6. path: path.resolve(__dirname, 'dist')
  7. },
  8. - module: {
  9. - rules: [
  10. - {
  11. - test: /\.css$/,
  12. - use: [
  13. - 'style-loader',
  14. - 'css-loader'
  15. - ]
  16. - },
  17. - {
  18. - test: /\.(png|svg|jpg|gif)$/,
  19. - use: [
  20. - 'file-loader'
  21. - ]
  22. - },
  23. - {
  24. - test: /\.(woff|woff2|eot|ttf|otf)$/,
  25. - use: [
  26. - 'file-loader'
  27. - ]
  28. - },
  29. - {
  30. - test: /\.(csv|tsv)$/,
  31. - use: [
  32. - 'csv-loader'
  33. - ]
  34. - },
  35. - {
  36. - test: /\.xml$/,
  37. - use: [
  38. - 'xml-loader'
  39. - ]
  40. - }
  41. - ]
  42. - }
  43. };

src/index.js

  1. import _ from 'lodash';
  2. - import './style.css';
  3. - import Icon from './icon.png';
  4. - import Data from './data.xml';
  5. -
  6. function component() {
  7. var element = document.createElement('div');
  8. -
  9. - // lodash,现在通过 script 标签导入
  10. element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  11. - element.classList.add('hello');
  12. -
  13. - // 将图像添加到我们已经存在的 div 中。
  14. - var myIcon = new Image();
  15. - myIcon.src = Icon;
  16. -
  17. - element.appendChild(myIcon);
  18. -
  19. - console.log(Data);
  20. return element;
  21. }
  22. document.body.appendChild(component());

下篇指南

我们继续移步到 管理输出

延伸阅读


贡献人员

EugeneHlushko EugeneHlushko TheDutchCoder TheDutchCoder chenxsan chenxsan michael-ciniawsky michael-ciniawsky skipjack skipjack sudarsangp sudarsangp