认识 Webpack (二)

loader


前面我们说过,模块不仅仅可以是 js 文件,还可以是图片、css 等等任何内容。那我们首先尝试打包一张图片试试,还是用前面的代码,我们随便拉一张图片到文件夹中,然后在 index.js 中尝试使用,如下栗子:

import Header from './header'
import Content from './content'
import Sidebar from './sidebar'
const img = require('./01.jpg')

new Header()
new Content()
new Sidebar()

file-loader

然后使用 npm run build 进行打包,果不其然,程序给我们报错了。webpack 默认只会处理 js 文件,当它遇到 jpg 文件时,它不知道该如何对其进行打包。而此时就需要我们手动告诉 webpack 该如何打包 jpg 文件,此时就需要我们在 webpack.config.js 中进行一些手动的配置:

const path = require('path')
module.exports = {
  entry: {
    main: './src/index.js'
  }, // 项目打包的入口文件
  module: {
    rules: [{ // 规则效验集合数组,里面承载多个效验对象
      test: /\.jpg$/, // 效验文件类型,检测到以 jpg 为后缀的文件就是用 file-loader 进行打包
      use: {
        loader: 'file-loader'
      }
    }]
  },
  output: { // 打包的最终文件放在哪里
    filename: 'bundle.js', // 打包生成的文件名
    path: path.resolve(__dirname, 'dist') // 打包目录
  }
}

上述代码中,我们新增了 module 模块,里面包含了 rules,主要用来做一些规则校验,里面可以包含多个效验对象。而我们又在其中新增了一个效验对象,它代表的含义为:当 webpack 遇到 .jpg 结尾的文件无法正确打包时,我们就用 file-loader 来进行打包处理。

当然 file-loader 不仅可以处理图片文件,它也可以处理 txtpngsvgexcel 等。file 的含义为文件,所以其实 file-loader 可以帮助我们处理一切基础文件类型的打包构建。这里我们先对原来的代码进行整合处理,将 header.jscontent.jssidebar.js 删除,我们主要是验证图片文件的处理,如下栗子:

首先安装 file-loader

npm install file-loader -D

修改 index.js

import avatar from './01.jpg'

const img = new Image()
img.src = avatar
const root = document.getElementById('root')
root.append(img)

此时使用 npm run build 即可完成打包构建,此时我们看下项目的整体目录结构:

项目目录结构.png

webpack 帮我们将 index.js 的内容打包成了 dist 目录下的 bundle.js 文件,而 01.jpg 则被 file-loader 也打包到了 dist 文件,但是文件名变成了一个随机的哈希值。也就是说通过 file-loader 的初步配置,我们成功完成了第一步:图片的打包。

我们在写 vue 的时候引入组件的时候通常都会写下面这段代码:

import Header from 'Header.vue'

我们知道 webpack 打包构建的时候肯定是不认识 .vue 结尾的文件,那么要如何对其进行打包呢?我们查看 vue 官方文档就会发现,其实 vue 帮我们集成了一个叫做 vue-loader 的东西来帮助 webpack 进行协同打包。所以写到这里,相信大家应该都知道 loader 的基本作用了吧!!!

loader 是一个打包的方案,它知道对于某一个特定的文件,webpack 应该如何进行打包。本身 webpack 对于后缀名非 js 的文件是不知道该如何处理和打包,但是 loader 知道,所以 webpack 去求助 loader 就可以了。

了解了 loader 的含义之后,回过头来优化代码,首先开发中我们肯定不希望被打包出来的图片名是一段随机的哈希值,如果我们希望打包的过程中图片名就用原来的名称,那该如何配置呢?

module: {
  rules: [{ // 规则效验集合数组,里面承载多个效验对象
     // 效验文件类型,检测到以 jpg/png/gif 为后缀的文件就是用 file-loader 进行打包
    test: /\.(jpg|png|gif)$/,
    use: {
      loader: 'file-loader',
      options: { // 参数配置
        name: '[name].[ext]'
      }
    }
  }]
},

我们增加 options 项进行一些额外的参数配置,这里我们其实使用的是 file-loader | placholders[name] 表示资源的基本名称,而 [ext] 表示资源的扩展名,有些时候我们也可以给资源的名称加上一段哈希值,如:name: '[name]_[hash].[ext]' 。通过配置这些基础参数,我们就可以保证打包出来的文件名使我们想要的。

当然更多的时候我们希望被打包的文件是放置在一个 images 文件夹中,而不是直接裸露在外面, 如下栗子:

options: { // 参数配置
  name: '[name].[ext]',
  outputPath: 'images/' // 将图片文件打包到 images 文件夹下
}

此时运行我们的程序,依然可以完美使用,同时图片也被放置在了 images 文件夹下,当然官方文档中 file-loader 的使用发法还有很多,如果日常开发中我们遇到相关问题,可以移步 file-loader | webpak 官方文档。

url-loader

我们首先去官方文档查阅 url-loader 的基础用法:

url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL

这个大概意思虽然有点糊,但是应该也能明白一些,大致就是说功能和 file-loader 差不多,但是如果该文件很小的话,url-loader 会自动帮我们处理该文件将其转化为一个 base64 链接形式直接引入到打包出来的 js 文件中。

那我们还是先拿上面的栗子用 url-loader 改造一下:

const path = require('path')
module.exports = {
  entry: {
    main: './src/index.js'
  }, // 项目打包的入口文件
  module: {
    rules: [{ // 规则效验集合数组,里面承载多个效验对象
      test: /\.(jpg|png|gif)$/,
      use: {
        loader: 'url-loader', // 使用 url-loader 协同打包
        options: { // 参数配置
          name: '[name].[ext]',
          outputPath: 'images/' // 将图片文件打包到 images 文件夹下
        }
      }
    }]
  },
  output: { // 打包的最终文件放在哪里
    filename: 'bundle.js', // 打包生成的文件名
    path: path.resolve(__dirname, 'dist') // 打包目录
  }
}

我们发现打包构建之后并没有生成 images 文件夹,而是只生成了一个 bundle.js 文件,那么我们的图片哪去了呢?在页面中我们会发现,它被转译成了一个 base64 格式的链接被直接引用了。这样确实存在一定的好处:

  • 我们的图片打包到 js 文件中,js 加载好了之后图片就会被直接渲染出来

  • 减少了请求资源,不用在额外请求一个图片资源,省了一次 http 请求

当然,这也延伸出来了一些问题:

  • 如果图片非常大,那么打包出来的 js 文件就会非常的大,这样就会导致加载 js 文件的过程非常长~~~导致页面上很长一段时间都没有东西可以展示

综合优缺点,其实我们更希望使用 url-loader 的时候,将小于 20kb 的图片打包成 base64 链接直接引入到 js 中,将大于 20kb 的图片直接生成到 images 文件夹中。

当然,这些我们能想到的问题,大神们肯定早就知道了并给出了最优的解决办法,我们可以做如下配置:

options: { // 参数配置
  name: '[name].[ext]',
  outputPath: 'images/', // 将图片文件打包到 images 文件夹下
  limit: 20480 // 单位为字节 1kb = 1024字节
}

上述代码中,我们增加了 limit 字段,保证大小在 20kb 内的图片直接打包到 js 中,而超过 20kb 的图片还是直接生成对应文件放置到 images 文件夹中。

此处延展学习文档:url-loaderfile-loader

css-loader

前端开发中,最常见的三种件类型无外乎 cssjspng | jpg 这三种。webpack 自身支持打包 js 文件,而 jpg | png 等图片类型的打包可以依赖 url-loader ,那么 css 文件 webpack 肯定本身也是不支持直接对其打包的,这里我们就要用到 css-loader

一般开发中对 css 文件打包我们需要引入两个基本的 loader,引入如下:

npm install style-loader css-loader@3.6.0 -D
  • style-loader:把整合好的 css 文件放在 htmlhead 标签中

  • css-loader:可能我们的 css 文件中还引入其他 css 文件,如在 index.css 文件中引入 @import ‘main.css’,通过这个 loader 可以把各个 css 文件整合至一个 css 文件

这里我指定了版本,因为不指定版本直接引入最新版会报错,调试了很久~~~都说 css-loader 的版本过高,所以用了这个 3.6.0,学习的话可以跟着用,日常开发中其实我们都是通过官方提供的脚手架构建项目,这些基础配置官方都会帮我们集成~~~

在项目的 src 目录下新建 index.css 目录,随便写一点点样式:

.pic {
  width: 150px;
  height: 150px;
}

index.js 中引入我们写的 index.css 文件:

import avatar from './01.jpg'
import './index.css'

const img = new Image()
img.src = avatar
img.classList.add('pic')
const root = document.getElementById('root')
root.append(img)

webpack.config.js 文件进行配置:

const path = require('path')
module.exports = {
  entry: {
    main: './src/index.js'
  }, // 项目打包的入口文件
  module: {
    rules: [{ // 规则效验集合数组,里面承载多个效验对象
      test: /\.(jpg|png|gif)$/,
      use: {
        loader: 'url-loader',
        options: { // 参数配置
          name: '[name].[ext]',
          outputPath: 'images/', // 将图片文件打包到 images 文件夹下
          limit: 2048 // 单位为字节 1kb = 1024字节
        }
      }
    }, {
      test: /\.css$/, // 遇到后缀为 css 结尾的文件用下面两个 loader 对其进行打包
      use: ["style-loader", "css-loader"]
    }]
  },
  output: { // 打包的最终文件放在哪里
    filename: 'bundle.js', // 打包生成的文件名
    path: path.resolve(__dirname, 'dist') // 打包目录
  }
}

最后我们使用 npm run build 发现样式完美生效,达到了我们预期的效果。当然,我们日常开发中肯定会使用 css 预处理的第三方工具,如 lesssassstylus。写起样式来确实快,而且好用。因为 sass-loaderless-loader 都有详细的官方文档,而 stylus-loader 官方文档中没有提及,但是我日常开发中其实还是比较喜欢 stylus-loader,因为不用写分号,毕竟咱 js 也没有写分号的习惯,css 如果可以不写岂不妙哉。

stylus | stylus-loader

不得不说,webpack 的版本兼容好乱~~~我直接引入了最新版的 stylus-loader 总是报错,百度也说版本过高,最好引入低版本,最后只好引入了一个低版本才没报错。所以日常开发中切记锁定一个版本,不要胡乱升级,不然后患无穷啊。

要想使用 stylus-loader 需要引入两个文件:

npm install stylus stylus-loader@3.0.2 -D

然后我们将 index.style 修改成 index.styl 并修改为对应的 stylus 语法:

.pic
  width: 150px
  height: 150px

index.js 中引入 index.styl:

import avatar from './01.jpg'
import './index.styl'

最后修改 webpack.config.js 中的配置:

module: {
  rules: [{
    test: /\.(jpg|png|gif)$/, 
    use: {
      loader: 'url-loader',
      options: { // 参数配置
        name: '[name].[ext]',
        outputPath: 'images/',
        limit: 2048
      }
    }
  }, {
    test: /\.styl$/, // 匹配对应.styl后缀的文件
    // 处理 `stylus` 文件可能需要使用到这些loader
    loader: ['style-loader', 'css-loader', 'stylus-loader'] 
  }]
},

最后我们使用 npm run build 发现样式完美生效,达到了我们预期的效果。

你以为这就完了吗?日常开发中,当我们使用一些 css3 样式时,总要加上浏览器的厂商前缀,比如 transform 属性。这就非常麻烦,而这种事情也有 loader 可以帮我们实现,就是 postcss-loader

使用也非常简单,先安装 postcss-loader

npm i -D postcss-loader
  • postcss-loader:在属性前加兼容性前缀如 -webkit-transform,使用这个loader时可以在根目录添加postcss.config.js文件,配置如下:
module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

当然,上述代码中引入了 autoprefixer,所以我们也需要引入安装插件 autoprefixer

npm install autoprefixer@8.0.0 -D

请一定要进行版本管理,不然未知名的错误可能会令你崩溃,加入了 postcss-loader 之后所以我们的代码变为如下格式

index.styl 中加入 css3 的属性

.pic
  width: 150px
  height: 150px
  transform: translate(100px, 100px)

webpack.config.js 中引入 postcss-loader

module: {
  rules: [{
    test: /\.styl$/, // 匹配对应.styl后缀的文件
    // 处理 `stylus` 文件可能需要使用到这些loader
    loader: ['style-loader', 'css-loader', 'stylus-loader', 'postcss-loader'] 
  }]
},

实际构建过程中,上述代码是无法正常运行的,因为这里埋了一个特别重要的坑:

webpack 中,loader 的执行顺序是从右到左,从下到上,stylus-loader 处理好 stylus 文件后向上传递给 css-loader 处理 css 文件,再向上传递给 style-loader 处理 style 文件,每个 loader 互不影响,各司其职。

而按照我们上面加入的顺序,最先处理的为 postcss-loader ,它不认识 stylus-loader 的语法,所以会直接报错。正确的顺序应该是先执行 stylus-loader 将其转化成 css 语法,然后再有 postcss-loadercss 语法中的 css3 特性属性进行兼容处理。所以我们两者之间交换一下位置:

module: {
  rules: [{
    test: /\.styl$/, // 匹配对应.styl后缀的文件
    // 处理 `stylus` 文件可能需要使用到这些loader
    loader: ['style-loader', 'css-loader', 'postcss-loader', 'stylus-loader'] 
  }]
},

最后我们使用 npm run build 发现样式完美生效,而且 transform 也在浏览器中被自动加入了 -webkit-transform 来进行兼容处理。

前面其实已经介绍了 css-loader 的基础用法,但是日常开发中,我们经常可以看到很多项目中有如下配置:

module: {
  rules: [{
    test: /\.styl$/, // 匹配对应.styl后缀的文件
    // 处理 `stylus` 文件可能需要使用到这些loader
    loader: ['style-loader', {
      loader: 'css-loader',
      options: {
        importLoaders: 2
      }
    }, 'postcss-loader', 'stylus-loader'] 
  }]
},

上述代码中又将 css-loader 展开为了一个对象进行单独配置,那么 importLoaders:2 又是什么鬼呢?其实很容易理解,比如我们新建一个 avatar.styl 文件,然后在 index.style 中引入新建的 avatar.styl 文件,如下栗子:

// avatar.styl
body
  width: 100%
  height: 100%
  background: orange

// index.styl
@import './avatar.styl'
.pic
  width: 150px
  height: 150px
  transform: translate(100px, 100px)

css-loader 在处理 index.styl 文件的时候,会保证这个文件中的所有内容都会走下面的 postcss-loaderstylus-loader 这两个 loader,但是却无法保证 index.styl 内部引用的 avatar.styl 走下面两个 loader。而使用 importLoaders: 2 即可以保证只要你通过 import 引入的 stylus 文件,也必须要走下面两个 loader

  • css 模块化

看名字大致应该能猜出一些,就是 css 局部使用,不会影响全局样式~~~,先来看个栗子,我们首先在 src 目录下新建一个 createAvatar.js

import avatar from './01.jpg'
function createAvatar() {
  const img = new Image()
  img.src = avatar
  img.classList.add('pic')
  const root = document.getElementById('root')
  root.append(img)
}
export default createAvatar

接着我们在 index.js 中引入这段代码,并手动执行导出的 createAvatar 函数:

import avatar from './01.jpg'
import './index.styl'
import createAvatar from './createAvatar'

createAvatar()
const img = new Image()
img.src = avatar
img.classList.add('pic')
const root = document.getElementById('root')
root.append(img)

不难看出,我们页面会生成两张图片,但是因为我们引入的 index.styl 会把里面的样式同时作用到 index.jsavatar.js 中,所以导致两个 js 文件中的图片都用了样式,如果我们希望样式的使用私有化,那么就需要在 webpack.config.js 中做如下配置:

module: {
  rules: [{
    test: /\.styl$/, // 匹配对应.styl后缀的文件
    // 处理 `stylus` 文件可能需要使用到这些loader
    loader: ['style-loader', {
      loader: 'css-loader',
      options: {
        importLoaders: 2,
        modules: true
      }
    }, 'postcss-loader', 'stylus-loader'] 
  }]
},

当然,仅仅是配置 modules: true 也是远远不够的,此时我们如果希望 index.js 中引入的 index.styl 只作用到该文件下面,而不作用到 avatar.js 文件中,只需要在 index.js 中完成如下配置:

import avatar from './01.jpg'
import style from './index.styl'
import createAvatar from './createAvatar'

createAvatar()
const img = new Image()
img.src = avatar
img.classList.add(style.pic)
const root = document.getElementById('root')
root.append(img)

import 导入的时候引入一个变量进行接收,然后添加样式的时候通过调用该变量的属性方式进行添加,这样两者之间的 css 样式部分就独立出来了,也就完成了我们所说的 css 模块化。

  • 如何引入字体图标

日常开发中,字体图标肯定是必不可少的,相信大家开发选用的字体图标库应该都是 iconfont 吧,我们就先在 iconfont 官网随便下载一些字体图标到本地,本地解压之后我们会得到很多个文件。

我们首先在 src 目录下新建一个 font 文件夹,用来承载对我们有用的字体,将本地解压的 iconfont.eoticonfont.svgiconfont.ttficonfont.woff 这四个文件移入到新建的 font 文件夹内。同时我们清空 index.styl 中的内容,将本地解压的 iconfont.css 里面的内容全部粘贴到 index.styl 中。不用特意将 iconfont.css 中的内容转换成 stylus 语法,因为 stylus 自动兼容识别 css 基本语法。注意此时 index.styl 中关于字体的路径引入要改成 ./font/,因为我们已经把所有有用的字体文件移入到和它同级目录的 font 文件夹中了。

接着我们修改 index.js 中的内容,首先引入 index.styl,然后直接使用通过 class 的方式使用字体图标库:

import './index.styl'

const root = document.getElementById('root')
root.innerHTML = "<div class='iconfont icon-aixin'></div>"

此时如果我们直接运行 npm run build 去打包会报错,会提示我们无法处理 ttf 文件。所以我们需要在 webpack.config.js 文件中引入 file-loader 来对这些字体文件进行处理,同时由于此时我们没有使用 css 模块化,所以也需要将先前配置的 modules: true 删掉。我们希望打包出来的字体文件也放在 dist 文件夹下面的 font 文件夹中,同时所有字体的名称不要发生变化,所以最终我们的 webpack.config.js 文件配置如下:

const path = require('path')
module.exports = {
  entry: {
    main: './src/index.js'
  }, // 项目打包的入口文件
  module: {
    rules: [{ // 规则效验集合数组,里面承载多个效验对象
      test: /\.(jpg|png|gif)$/,
      use: {
        loader: 'url-loader',
        options: { // 参数配置
          name: '[name].[ext]',
          outputPath: 'images/', // 将图片文件打包到 images 文件夹下
          limit: 2048 // 单位为字节 1kb = 1024字节
        }
      }
    }, {
      test: /\.styl$/, // 匹配对应.styl后缀的文件
      loader: ['style-loader', {
        loader: 'css-loader',
        options: {
          importLoaders: 2
        }
      }, 'postcss-loader', 'stylus-loader'] // 使用那些loader来处理这个文件
    }, {
      test: /\.(eot|woff|ttf|svg)$/, // 使用 file-loader 处理字体文件
      use: {
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'font/'
        }
      }
    }]
  },
  output: { // 打包的最终文件放在哪里
    filename: 'bundle.js', // 打包生成的文件名
    path: path.resolve(__dirname, 'dist') // 打包目录
  }
}

此时在运行 npm run build 即可大功告成,页面中展示出了 iconfont-aixin 所对应的图标。此处延伸学习文档:管理资源 | webpack

本来打算这篇记录更多一点的内容,但是发现写完 loader 之后文章都变得好长了~~~过长的文章确实不利于自己阅读和学习,容易分散学习精力,所以这篇到这里就结束算了。此整理仅供记录学习,如果文中有不对的地方或者理解有误的地方欢迎大家提出并指正。每一天都要相对前一天进步一点,加油!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,227评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,755评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,899评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,257评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,617评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,757评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,982评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,715评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,454评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,666评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,148评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,512评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,156评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,112评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,896评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,809评论 2 279
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,691评论 2 272