# loader篇

webpack 可以使用 loader 来预处理文件。这允许你打包除 JavaScript 之外的任何静态资源。你可以使用 Node.js 来很简单地编写自己的 loader。

loader的配置有三种方式:

  1. 通过在webpack配置中的module.rules配置(最推荐的方式)
// webpack.config.js
module.exports = {
	module: {
		rules: [
			{
				test: /.txt$/,
				use: 'raw-loader' // 获取到内容
			}
		]
	}
}
  1. 内联的方式(使用!将资源中的loader分开)
// 单个loader
import Styles from 'style-loader!./styles.css';

// 多个loader且带参数
import Styles from 'style-loader!css-loader?modules!./styles.css';
  1. 命令行CLI
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'

会对 .jade 文件使用 jade-loader,对 .css 文件使用 style-loadercss-loader

以下所有教材案例GitHub地址: LinDaiDai/webpack-loader

# 文件

# raw-loader

加载文件原始内容.

例如加载text文件:

import txt from './assets/file.txt'
// webpack.config.js
module.exports = {
	module: {
		rules: [
			{
				test: /.txt$/,
				use: 'raw-loader' // 获取到内容
			}
		]
	}
}

# file-loader

将文件发送到输出文件夹,并返回(相对)URL

也就是使用了file-loader的资源文件在打包后会输出到dist文件夹中, 同时如果用import或者require引入的话获取到的是相对路径.

# 1. js代码加载图片

例如加载img文件:

import img from './assets/file.png'
// webpack.config.js
module.exports = {
	module: {
		rules: [
			{
      test: /\.(png|svg|jpg|gif)$/,
      use: [
        	 'file-loader' // 得到的是bundle中的相对路径 4sj89003nknkdsdf.png
        	}
        ]
      }
		]
	}
}

这里又一点需要注意的⚠️:

如果你是使用import引用的话得到的是图片的相对路径

如果是使用require引用的话得到的是一个模块对象, 这时候需要需要配置loader的一个参数options.esModulefalse 才会得到相对路径.

// require引入
const img = require('./assets/file.png')

// 得到模块对象
Module{ defalut: '4sj89003nknkdsdf.png' }

// webpack.config.js 中配置参数
rules: [{
	loader: 'file-loader',
	options: {
		esModule: false
	}
}]

配置了esModule之后, 就能和import引入得到的一样了.

# 2. html加载图片

html中加载一张图片:

<img src="./assets/file.png" />

这里默认应该是以CommonJS的加载方式加载的, 所以需要配置options.esModulefalse, 如上面👆.

# url-loader

像 file loader 一样工作,但如果文件小于限制,可以返回 data URL

# 1. 基本用法

rules: [{
	test: /\.(png|svg|jpg|gif)$/,
	use: [{
		loader: 'url-loader'
	}]
}]
import img from './assets/file.png' // base64格式
const img from './assets/file.png' // Module: { default: data:image/png;base64... }

<img src="./assets/file.png" > // <img src="[object Module]" />

# 2. 参数limit

默认情况下它有一个参数limit是为undefined的, 此时获取文件返回的是base64格式(也就是dataURL).

但如果设置了limit(单位为K)同时文件的大小超过了这个limit的话, 就和file-loader一样返回bundle中的相对路径.

  • 不设置limit, 返回base64格式
  • 设置了limit, 小于该值, 返回base64格式
  • 设置了limit, 大于该值, 交给file-loader处理, 如果没有安装配置file-loader的话就会报错

例如加载下面👇两个img文件:

import img from './assets/file.png' // < 40KB data:image/png;base64
import wpl from './assets/wpl.png' // > 40KB 0565680d883d2c278e70d23f5ee97975.png

var imgEle = new Image()
var wplEle = new Image()
imgEle.src = img
wplEle.src = wpl
document.body.appendChild(imgEle)
document.body.appendChild(wplEle)
// webpack.config.js
module.exports = {
	module: {
		rules: [
			{
      test: /\.(png|svg|jpg|gif)$/,
      use: [
        	// 'file-loader' // 得到的是bundle中的相对路径 4sj89003nknkdsdf.png
          {
            loader: 'url-loader', // 得到的是base64格式: data:image/png;base64
            options: {
              limit: 40000 // 超过40KB则返回 4sj89003nknkdsdf.png
            }
        	}
        ]
      }
		]
	}
}

旦如果你是想直接在标签中使用一张图片的话, 例如这样:

<img src="./assets/LinDaiDai.png" />

它并不能像预期那样, 它得到的是一个对象:

这个对象长成这样:

Module{
	default: "data:image/png;base64,..."
}

所以对应页面上是不能正常显示的:

<img src="[object Module]" />

此时可以配置options中的esModule选项为false, 默认这个选项是为true的.

{
	loader: 'url-loader',
	options: {
		esModule: false
	}
}

在其他的loader, 例如raw-loader也有这样选项.

# svg-inline-loader

使用url-loader来处理svg文件, 是能将该文件转化为base64格式.

svg-inline-loader的作用是将SVG内联为模块.

它会将引入的SVG转化为字符串.

安装:

$ cnpm i --save-dev svg-inline-loader

配置:

{
  test: /\.svg$/,
  loader: 'svg-inline-loader?classPrefix'
}

使用:

// src/print.js
const svg = require('./assets/add-icon.svg')
// import svg from './assets/add-icon.svg'

console.log(svg)

得到的是:

<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve"><style type="text/css"> .fnCkiuXst0{fill-rule:evenodd;clip-rule:evenodd;fill:#888888;} </style><g><g><polygon class="fnCkiuXst0 " points="13,7 9,7 9,3 7,3 7,7 3,7 3,9 7,9 7,13 9,13 9,9 13,9 "></polygon></g></g></svg>

# 转换编译

# ts-loader

像 JavaScript 一样加载 TypeScript 2.0+

也就是能让你的代码中能写ts类型的文件.

ts-loader必须配合typescript一起使用.

所以在安装时:

$ cnpm i --save-dev ts-loader typescript

同时要在根目录下创建一个tsconfig.json文件:

{
  "compilerOptions": {
    "sourceMap": true
  }
}

完成上述步骤就可以在项目中使用ts了.

让我们写个案例看看:

test.ts:

export function numToWord(num: number): string {
  return config[num]
}

const config = {
  1: 'one',
  2: 'two'
}

然后使用它:

print.js:

import { numToWord } from './assets/test.ts'
console.log(numToWord(2)) // two

# babel-loader

下面介绍一个比较重要的loader: babel-loader.

我们都知道Babel就是一个JS编译器, 主要用于在旧的浏览器或环境中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript代码。

(原理简单说就是先将ES6+的解析生成为ES6+AST,然后把ES6+AST转换为ES5+AST,最后再将这个AST转换为具体的ES5的代码)

例如我们在刚刚的案例src/index.js中加上一个ES6才有的箭头函数, 以及一个 ES7才有的幂运算符:

import { print } from './print'
import './styles/style.css';
import './styles/style.less';
var fileHtml = require("html-loader?attrs=img:src!./assets/file.html")

function component() {
    var element = document.createElement('div');
    element.innerHTML = 'webpack loader';

    element.classList.add('box');

    var btn = document.createElement('button');
    btn.innerHTML = '点击获取环境变量';
    btn.onclick = print;
    element.appendChild(btn);

+    const fn = () => 1; // ES6箭头函数
+    console.log(fn())
+		 let num = 3 ** 2; // ES7求幂运算符
+    console.log(num)
    return element;
}

document.body.appendChild(component());
console.log(fileHtml)
    // document.body.appendChild(fileHtml)

我们知道在没做任何处理的的时候, bundle之后, 这个函数还是箭头函数, 幂运算符还是幂运算符.

为了方便查看我将webpack.config.js中的mode属性配置为development, 这样我们就能查看bundle之后的代码了:

# @babel/preset-env

现在让我们来安装一下babel-loader, 为了能看到效果, 同时还要安装一下@babel/core@babel/preset-env

(@babel/core是Babel的核心, @babel/preset-env它是能将ES6+的语法转成ES5的一组插件集合)

$ cnpm i --save-dev babel-loader @babel/core @babel/preset-env

然后我在webpack.config.js中配置使用一下babel-loader:

// webpack.config.js
module.exports = {
	module: {
		rules: [
			{
				test: /.js$/,
				exclude: /(node_moudules|bower_components)/,
				use: [
					{
						loader: 'babel-loader',
						options: {
							presets: ["@babel/preset-env"]
						}
					}
				]
      }
		]
	}
}

这里的意思是对除了node_modulesbower_components文件夹以外的所有js文件使用babel-loader. 同时Babel配置使用的是@babel/preset-env这个preset.

保存成功之后重新执行npm run serve来看看效果:

可以发现, 现在我们的箭头函数以及幂运算符都被转换成了ES5的语法.

在如果你的vue项目不是使用vue-cli, 而是自己通过webpack配置的话, 那么其中的exclude可以这样配置:

rules: [{
	test: /\.js$/,
	exclude: file => ( // 排除依赖 && 但是保证依赖文件夹中的vue单文件
		/node_modules/.test(file) &&
		!/\.vue\.js/.test(file)
	),
	use: [{
    loader: 'babel-loader',
    options: {
    	presets: ["@babel/preset-env"]
    }
  }]
}]

# plugins

在上面我们使用的@babel/preset-env, 它是一组插件(plugin)的集合.

所以使用了它能将ES6+的语法都转换为ES5的语法.

除了这种做法, 我们还能指定只转换哪些语法.

例如我现在只想要将箭头函数转换为普通函数, 而幂运算符不转换.

那么我们就可以使用options里的另一个参数plugins, 只传入转换箭头函数的插件: @babel/plugin-transform-arrow-functions

// webpack.config.js
module.exports = {
	module: {
		rules: [
			{
				test: /.js$/,
				exclude: /(node_moudules|bower_components)/,
				use: [
					{
						loader: 'babel-loader',
						options: {
-							presets: ["@babel/preset-env"],
+							plugins: [require('@babel/plugin-transform-arrow-functions')]
						}
					}
				]
      }
		]
	}
}

(注⚠️: 由于我们前面下载安装了@babel/preset-env, 它里面包含了所有ES6+的转换插件, 当然也包括箭头函数转换的插件, 所以我们可以直接引用babel/plugin-transform-arrow-functions, 如果你没有装@babel/preset-env的话, 则需要单独下载箭头函数转换的插件)

现在重新编译之后就会发现, 编译的结果中只将箭头函数转换了, 而幂运算符没有.

另外, plugins: ['@babel/plugin-transform-arrow-functions']这样的写法也是可以的.

# 使用优化

在使用babel-loader是会有以下几个问题, 我们可以针对问题点做不同的优化:

  1. babel-loader使得编译很慢

解决办法: 一种是确保转译尽可能少的文件, 所以可以用exclude选项来去除node_modulesbower_components中文件. 另一种你可以设置cacheDirectory选项为true, 开启缓存, 转译的结果将会缓存到文件系统中, 这样使babel-loader至少提速两倍(代码量越多效果应该越明显).

  1. babel-loader使得打包文件体积过大

Babel 对一些公共方法使用了非常小的辅助代码, 比如 _extend.默认情况下会被添加到每一个需要它的文件中,

所以会导致打包文件体积过大.

解决办法: 引入babel runtime作为一个单独的模块, 来避免重复.

使用方式: 执行npm install @babel/plugin-transform-runtime --save-dev 来把它包含到你的项目中

并且使用 npm install babel-runtime --savebabel-runtime 安装为一个依赖:

rules: [
  // 'transform-runtime' 插件告诉 babel 要引用 runtime 来代替注入。
  {
    test: /\.js$/,
    exclude: /(node_modules|bower_components)/,
    use: {
      loader: 'babel-loader',
      options: {
        presets: ['@babel/preset-env'],
        plugins: ['@babel/plugin-transform-runtime']
      }
    }
  }
]

官网给出的是安装babel-plugin-transform-runtime, 但如果你使用的Babel是7以上的话, 你就得和我一样安装使用@babel/plugin-transform-runtime.

# 模版

# html-loader

先让我们想一个场景, 写一个file.html页面, 然后直接用require引入会怎么样 🤔️?

src/assets/file.html:

<img src="file.png" data-src="fileJpg.jpg" />

src/index.js:

var fileHtml = require("./assets/file.html")
console.log(fileHtml)

答案是编译的时候就会报错了:

ERROR in ./src/assets/file.html 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> <img src="file.png" data-src="fileJpg.jpg" />
 @ ./src/index.js 2:15-44

html-loader的作用就是将html里面的内容转换为字符串

  1. 安装它:
$ cnpm i html-loader --save-dev
  1. 使用它:
// src/index.js
var fileHtml = require('html-loader!./assets/file.html')
console.log(fileHtml) // '<img src="[object Module]" data-src="fileJpg.jpg" />'

此时虽然能够成功的转换成字符串, 但是src属性好像是有点问题的.

# 样式

# style-loader和css-loader

  • style-loader: 将模块的导出作为样式添加到 DOM 中
  • css-loader: 解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码

# less-loader

# 基本使用

加载和转译 LESS 文件

前提条件需要安装style-loader、css-loader和less

$cnpm i --save-dev style-loader less-loader less

配置:

// webpack.config.js
module.exports = {
    ...
    module: {
        rules: [{
            test: /\.less$/,
            use: [{
                loader: "style-loader" // creates style nodes from JS strings
            }, {
                loader: "css-loader" // translates CSS into CommonJS
            }, {
                loader: "less-loader" // compiles Less to CSS
            }]
        }]
    }
};

使用:

index.js:

import './styles/style.less'

style.css:

@import './style.less';
.box {
    background-color: coral;
}

(不过记得配置一下.css类型的loader)

# 提取less文件

用上面👆的方法能将less文件转换成css并最终和其它的css代码一并添加到页面head的style标签里.

也就是说并不会在最终的bundle中生成对应的css文件.

但是在实际使用来说, 我们更希望能将less或者css文件提取出来作为一个单独的文件加载到页面上.

主要就是依靠一个插件:

extract-text-webpack-plugin.

# extract-text-webpack-plugin

  1. 安装插件
$ cnpm i --save-dev extract-text-webpack-plugin@next

(如果你用的是webpack4的话, 就要安装@next版本的, 不然打包的时候会报错了)

  1. 在webpack.config.js中配置:
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
+ const ExtractTextPlugin = require('extract-text-webpack-plugin');
+ const extractLess = new ExtractTextPlugin({
+    filename: "[name].[hash].css",
+    disable: process.env.NODE_ENV === "development"
+ });
module.exports = {
	entry: {
        index: './src/index.js'
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HTMLWebpackPlugin(),
+       extractLess
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    devtool: 'inline-source-map',
+    module: {
+    	rules: [
+    		{
+    			test: /.less$/,
+    			use: extractLess.extract({
+            use: [{
+              	loader: "css-loader"
+             }, {
+              	loader: "less-loader"
+             }],
+            fallback: "style-loader"
+          })
+    		}
+    	]
+    }
}

此时执行生产环境的打包命令, 可以看到在最终的bundle中看到生成的css文件.

# 清理和测试

# eslint-loader

PreLoader,使用 ESLint 清理代码

安装使用:

$ cnpm i --save-dev eslint eslint-loader
阅读全文