webpack --- 使用 loaders 处理静态资源

4,388 阅读4分钟

默认情况下,webpack 只处理 JS 文件。如果要处理其他文件,webpack 需要配置 loaders 对文件进行预处理,这样 webpack 就能处理任意静态资源。 处理静态资源的常用 webpack loaders 分别有 file-loaderurl-loadercss-loaderstyle-loader ,下面我将结合实例分析这些 loaders 的使用。

1. 创建 webpack 项目

我们的 webpack 项目目录如下所示,静态资源包括图片和字体,放在 src/assets 目录下

webpack 仅仅配置输入输出,输入是 scr/index.js 文件,打包后输出到 dist 文件夹下,JS 文件最后打包到 dist/js/bundle.js

// webpack.config.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/bundle.js',
  },
  plugins: [
    new CleanWebpackPlugin() // 为了测试方便,我们使用 CleanWebpackPlugin 在下次打包前清除之前的打包文件
  ]
};
// src/index.js
import './style.css'
// src/style.css
#app {
  font-family: 'BalsamiqSans';
  width: 100vw;
  height: 100vh;
}

这时候控制台运行打包指令 npm run build,报错,报错信息如下

ERROR in ./src/style.css
Module parse failed: Unexpected character '@' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file

就如一开始说的,默认情况下 webpack 只打包 JS 文件,而我们的入口文件不仅仅有 JS 文件,还包括 css 文件,于是 webpack 打包报错了。

2. 使用 style-loader css-loader 处理 css 文件

• style-loader 将 css 注入到 DOM 中,style-loader 需要和 css-loader 一起使用

• css-loader 用来解析 @import 与 url() ,解析方式如同 import / require()

我们完善 webpack 的配置:

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
  ]
};

这时候打包成功没有报错,但是你会发现,打包后只有一个 bundle.js 文件,css 代码也注入进了 bundle.js 文件中。我们可以通过 MiniCssExtractPlugin 这个 plugin 将 CSS 提取到单独的文件中。请注意, MiniCssExtractPlugin.loaderstyle-loader 会冲突,当两者同时存在时,会报错 document is not defined

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
    })
  ]
};

成功的打包出了 JS 文件与 CSS 文件,并且以想要的文件路径输出了

3. 使用 file-loader url-loader 处理静态资源

• file-loader 用来处理静态资源,例如字体、图片等等,将资源注入到 webpack 中,并且解析资源的相互依赖

• url-loader 功能与 file-loader 类似,并且可以设置文件大小。当资源小于限制时,能够返回一个 DataURL,也就是以 base64 编码的形式注入到文件中。简单来讲就是将图片数据翻译成一串字符,把这串字符打包进文件中,这样引入文件就能访问到图片了

项目中准备了字体资源与图片资源,进一步更新 webpack 的配置:

// src/style.css
@font-face {
  font-family: 'BalsamiqSans';
  src: url('./assets/fonts/BalsamiqSans-Bold.ttf');
}
#app {
  font-family: 'BalsamiqSans';
  background: url('./assets/image/mai.png');
  width: 100vw;
  height: 100vh;
}
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          limit: 8900,
          outputPath: 'img'
        },
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf|)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'fonts'
        },
      },
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
    })
  ]
};

如预期一样,我们成功打包出了 JS 文件与静态资源文件。

在根目录的 index.html 中引入 JS 与 CSS,测试看看打包后的资源文件的路径是否使用正确:

<!DOCTYPE html>
<head>
  <title> webpack-demos </title>
  <link rel="stylesheet" type="text/css" href="./dist/css/main.css">
</head>
<body>
  <div id="app">
    hello world
    <script src="./dist/js/bundle.js"></script>
  </div>
</body>

很不幸,打包后的资源路径并没有找到

查看打包后的 CSS 文件,发现资源的引用路径都是使用的 绝对路径 ,比如打包后图片的真实路径是 dist/img/mai.png ,而 css 引入的路径是 dist/css/img/mai.png ,路径不对,资源就找不到了。

因为配置 css 文件在 dist/css 目录下,css 文件多了一层结构,修改 webpack 配置:

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          limit: 8900,
          outputPath: 'img'
        },
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf|)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'fonts'
        },
      },
      {
        test: /\.css$/i,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '../', // css 多了一层
            }
          },
          'css-loader'],
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
    })
  ]
};

这时候,打包后的静态资源配置路径引用正常了。

@font-face {
  font-family: 'BalsamiqSans';
  src: url(../fonts/BalsamiqSans-Bold.ttf);
}
#app {
  font-family: 'BalsamiqSans';
  background: url(../img/mai.png);
  width: 100vw;
  height: 100vh;
}

至此,使用 webpack 成功编译了 JS 文件,图片字体等资源文件,并且分析处理了静态资源打包路径的问题。看到这里你以为本文结束了,其实并没有。 在 Vue 项目中,静态资源可以放置在根目录的 public 文件夹下、 src/assets 文件夹下,放置在这两个路径下有什么差别呢?

4. webpack 处理静态资源

webpack 是如何处理静态资源的?在前面的篇幅中,我们已经介绍了 loaders 处理静态资源。Vue 项目中, template 和 css 分别会被 vue-loader 与 css-loader 解析。例如 <img src="./logo.png" />background: url(./logo.png)"./logo.png" 是一个相对资源路径,将由 Webpack 解析为模块依赖。logo.png 不是 JavaScript,当作为一个模块依赖时,我们需要使用 url-loader 与 file-loader 进行处理。由于这些资源在构建过程中可能被内联/复制/重命名,所以它们可以作为源代码的一部分。因此建议将资源文件放在 src 目录中与其他源文件一起。其实不必专门放在 src/assets ,可以是基于 模块/组件 的方式放置资源文件。例如,可以在每个放置组件的目录中存放静态资源。 放置在 public 目录下的文件不会被 webpack 处理,它们会直接被复制到最终目录(默认是 dist/static)下。必须使用绝对路径引用这些文件。

webpack 是打包模块化工具,在 webpack 里一切文件都是模块。本文介绍了通过 loaders 解析文件,了解了通过 file-loaderurl-loadercss-loaderstyle-loader 一步步的处理静态资源。比较 Vue 项目中放置静态资源的两处区别,放置在 src/assets 下的资源在构建的时候会被 webpack 打包到最终输出文件,适合放置本项目的资源文件。public 下的文件在构建的时候不会被 webpack 处理,将被直接拷贝到最终路径并引用,所以适合放置一些第三方类库文件。

参考: Handling Static Assets