Home Vue打包时url-loader处理CSS中的图片资源遇到的问题
Post
Cancel

Vue打包时url-loader处理CSS中的图片资源遇到的问题

处理CSS中的图片资源时,我们常用的两种loader是file-loader和url-loader,两者的主要差异在于:url-loader可以设置图片大小限制,当图片超过限制时,其表现行为等同于file-loader,而当图片不超过限制时,则会将图片以base64的形式打包进CSS文件,以减少请求次数。本文主要想说的是我们在使用file-loader或url-loader时经常出现的图片地址错误导致图片引用不到的情况,及相应解决办法。

工程目录

index页面目录

1
2
3
4
5
-index
  -1.jpg
  -index.html
  -index.js
  -index.scss

webpack配置文件中,关于图片的处理

1
2
3
4
5
6
7
8
{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    options: {
        limit: 10000,
        name: 'img/[name].[hash:7].[ext]'
    }
},

index.js文件

1
import './index.scss'

index.html文件

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div class="pic"></div>
</body>
</html>

index.scss文件

1
2
3
4
5
6
.pic{
  width:100px;
  height:100px;
  background: url('./1.jpg');
  background-size: 100px 100px;
}

打包和分析

当我们执行npm run build后查看dist中的情况如下:

1
2
3
4
5
6
7
-dist
  -css
    -index.min.css
  -img
    -1.d1efbb3.jpg
  -js
    -index.html

我们通过服务打开index.html会发现页面中并没有图片,并且报错:

1
Failed to load resource:the server responded with a status 1.d1efbb3.jpg of 404 (Not found)

这时我们当然是要去看看打包后的CSS文件中的图片路径了:

1
2
3
4
5
6
7
.pic {
    width: 100px;
    height: 100px;
    background: url(img/1.d1efbb3.jpg);
    background-size: 100px 100px
}
/*# sourceMappingURL=index.min.css.map?v=7b7c89f8*/

background:url(img/1.d1efbb3.jpg);index.min.css文件竟然去自己的同级目录找img文件夹,当然找不到了,img文件夹位于index.min.css上层,如果是background:url(../img/1.d1efbb3.jpg);就对了。然后我们尝试去修改webpack的配置文件,以达到我们的预期:

1
2
3
4
5
6
7
8
{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    options: {
        limit: 10000,
        name: '../img/[name].[hash:7].[ext]'
    }
},

然后再npm run build,index.min.css内的background的图片地址的确变成了我们想要的:

1
2
3
4
5
6
7
.pic {
    width: 100px;
    height: 100px;
    background: url(../img/1.d1efbb3.jpg);
    background-size: 100px 100px
}
/*# sourceMappingURL=index.min.css.map?v=eef74865*/

但是img竟然被打包到了dist文件夹的外面,不再位于dist内了:

1
2
3
4
5
6
7
-dist
  -css
    -index.min.css
  -js
    -index.html
-img
  -1.d1efbb3.jpg

看来我们修改配置文件的方法是不对的。分析其中的原因:打包的时候webpack会把scss文件中的background url替换成我们webpack配置文件中的 options的name属性中设置的内容,同时把scss文件中的background url中的图片文件复制到 webpack配置文件中的options的name属性所指向路径下,关键就在这里了。webpack配置文件中的options的name属性所指向路径是相对路径,那么这个路径到底是相对于谁呢?仔细观察会发现它是相对于dist文件夹的,也就是webpack的出口路径。举个例子:

1
2
3
4
5
6
7
8
{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    options: {
        limit: 10000,
        name: 'img/[name].[hash:7].[ext]'
    }
},

上面的配置就会把图片复制到dist/img/1.jpg,然后将index.min.css的background属性改为background:url('img/1.png'),该路径也是相对路径,但是它不相对于dist,而是相对于dist/css,因为我的CSS文件并没有被输出到dist的直接路径下,而是输出到了dist/css下,所以CSS文件就会去dist/css/img/1.png去拿图片,但是图片却位于dist/img/1.png,这就最终导致了CSS文件找不到图片。

配置文件复制图片是相对于webpack出口路径的,CSS文件引用图片是相对于CSS文件所在路径的,如果这两个路径相同,也就是webpack出口路径 = CSS文件所在路径,那么很幸运,你的图片是可以找到的。但是一般情况下是不同的,我们习惯于将出口路径定为dist/然后将CSS文件输出到dist/css/,最终导致了引用不到图片的结果。至此原因分析完毕。

解决方法

在配置项内加入publicPath属性,设置为部署时的绝对路径。比如你的页面会通过如下url方式让用户访问,所有前端文件都放置于http://localhost:63342/url-loader-test/dist/,那么pubilcPath的值就应该是/url-loader-test/dist/,也就是你的部署接口地址。

1
2
3
4
5
6
7
8
9
10
{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    options: {
        limit: 10000,
        name: 'img/[name].[hash:7].[ext]'
        publicPath: "/url-loader-test/dist/" 
        // 该地址不是唯一的,根据你的代码实际路由地址进行修改
    }
},

这样做的原因是,webpack打包时,还会将图片复制到dist/img/1.png,但是他会把CSS文件中的background url改写为publicPath + name,本例中最后生成的index.min.css 如下:

1
2
3
4
5
6
7
.pic {
    width: 100px;
    height: 100px;
    background: url(/url-loader-test/dist/img/1.d1efbb3.jpg);
    background-size: 100px 100px
}
/*# sourceMappingURL=index.min.css.map?v=eef74865*/

这时CSS文件中的url地址就变成了一个绝对路由。

总结

如果我们希望在页面引入图片(包括img的src和background的url)。当我们基于webpack进行开发时,引入图片会遇到一些问题。

其中一个就是引用路径的问题。拿background样式用url引入背景图来说,我们都知道,webpack最终会将各个模块打包成一个文件,因此我们样式中的url路径是相对入口html页面的,而不是相对于原始css文件所在的路径的。这就会导致图片引入失败。这个问题是用file-loader解决的,file-loader可以解析项目中的url引入(不仅限于css),根据我们的配置,将图片拷贝到相应的路径,再根据我们的配置,修改打包后文件引用路径,使之指向正确的文件。

另外,如果图片较多,会发很多http请求,会降低页面性能。这个问题可以通过url-loader解决。url-loader会将引入的图片编码,生成dataURl。相当于把图片数据翻译成一串字符。再把这串字符打包到文件中,最终只需要引入这个文件就能访问图片了。当然,如果图片较大,编码会消耗性能。因此url-loader提供了一个limit参数,小于limit字节的文件会被转为DataURl,大于limit的还会使用file-loader进行copy。

url-loader和file-loader是什么关系呢?简答地说,url-loader封装了file-loader。url-loader不依赖于file-loader,即使用url-loader时,只需要安装url-loader即可,不需要安装file-loader,因为url-loader内置了file-loader。通过上面的介绍,我们可以看到,url-loader工作分两种情况:1.文件大小小于limit参数,url-loader将会把文件转为DataURL;2.文件大小大于limit,url-loader会调用file-loader进行处理,参数也会直接传给file-loader。因此我们只需要安装url-loader即可。

url-loader

loader中的参数

上面提到url-loader的参数和file-loader的参数,那么loader的参数是个什么概念呢?loader的参数用来自定义loader处理文件时的工作特性。下面以url-loader为例,介绍一下webpack的loader中的参数。首先看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module.exports = {
    // 入口文件路径,__dirname是根目录
    entry: __dirname + '/src/main.js',
    // 打包生成文件
    output: {
        path: __dirname + '/output',
        filename: 'main.js'
    },
 
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.jpeg$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: '1024'
                        }
                    },
                ]
            }
        ]
    }
}

其中,url-loader配置中的options属性表示的就是url-loader的参数,还有一种等价的写法:

1
2
3
4
{
    test: /\.jpeg$/,
    use: 'url-loader?limit=1024'
}

如果有多个参数,就用&连接起来。和http请求中的参数类似。正如前面介绍的,limit这个参数就是告诉url-loader,在文件小于多少个字节时,将文件编码并返回DataURL。

url-loader的参数

此外url-loader还有一些用于file-loader的参数。我们知道,file-loader的作用是将文件复制到其他目录。file-loader提供了一系列参数允许我们自定义诸如输出文件、文件名规则、发布路径等特性的参数。下面介绍一下这些参数。先看下配置好的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module.exports = {
    // 入口文件路径,__dirname是根目录
    entry: __dirname + '/src/main.js',
    // 打包生成文件
    output: {
        path: __dirname + '/output',
        filename: 'main.js'
    },
 
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.jpeg$/,
                use: 'url-loader?limit=1024&name=[path][name].[ext]&
                outputPath=img/&publicPath=output/',
            }
        ]
    }
}

这里涉及到了4个参数:limit、name、outputPath、publicPath。其中limit已经说明过。file-loader相关的是name、outputPath和publicPath。下面解释一下这3个参数:

  • name表示输出的文件名规则,如果不添加这个参数,输出的就是默认值:文件哈希。加上[path]表示输出文件的相对路径与当前文件相对路径相同,加上[name].[ext]则表示输出文件的名字和扩展名与当前相同。加上[path]这个参数后,打包后文件中引用文件的路径也会加上这个相对路径。
  • outputPath表示输出文件路径前缀。图片经过url-loader打包都会打包到指定的输出文件夹下。但是我们可以指定图片在输出文件夹下的路径。比如outputPath=img/,图片被打包时,就会在输出文件夹下新建(如果没有)一个名为img的文件夹,把图片放到里面。
  • publicPath表示打包文件中引用文件的路径前缀,如果你的图片存放在CDN上,那么你上线时可以加上这个参数,值为CDN地址,这样就可以让项目上线后的资源引用路径指向CDN了。

安装url-loader

1
npm install --save-dev url-loader

推荐文档

file-loader: https://github.com/webpack-contrib/file-loader
url-loader: http://www.cnblogs.com/ghost-xyx/p/5812902.html

demo

https://github.com/KIDFUCKER/webpack-demo.git

参考:url-loader处理css中的图片资源遇到的问题

This post is licensed under CC BY 4.0 by the author.