一、概述
(1)webpack本身只认识javascript,对于其他类型的资源必须预先定义一个或多个loader对其进行转译,输出为webpack能够接收的形式再继续进行。因此loader做的实际上是一个预处理的工作。
(2)loader本质上是一个函数。在webpack4之前,函数的输入和输出都必须为字符串;在webpack4之后,loader也同时支持抽象语法树(AST)的传递,通过这种方法来减少重复的代码解析。
output = loader(input)
1、input可能是工程源文件的字符串,或者是上一个loader转化后的结果,包括转化后的结果(也是字符串类型)、source map,以及AST对象。
2、output同样包含这几种信息,转化后的文件字符串、 source map,以及AST。
3、如果这是最后一个loader,结果将直接被送到webpack进行后续处理,否则将作为下一个loader的输入向后传递。
4、loader是可以链式的。
(3)源码结构
module.exports = function loader(content, map, meta) {
var callback = this.async();
var result = handler(content, map, meta);
callback(
null,//error
result.content,//转换后的内容
result.map,//转换后的source-map
result.meta,//转换后的AST
);
}
二、loader的配置
与loader相关的配置都在module对象中,其中module.rules代表了模块的处理规则。每条规则可以包含很多配置项,这里只使用了最重要的两项(test和use)
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: ['style-loader','css-loader']
}]
}
}
test:可接收一个正则表达式或者一个元素为正则表达式的数组,只有正则匹配上的模块才会使用这条规则。
use:可接收一个数组,数组包含该规则所使用的loader。
(1)css-loader:处理CSS各种加载语法(@import和url()函数等)
(2)style-loader:将样式插入页面
(3)webpack打包时是按照数组从后往前的顺序将资源交给loader处理的,因此要把最后生效的放在前面。
-
loader options
配置项。
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
//css-loader 配置项
}
}]
}]
}
}
-
exclude与include
(1)作用:排除或包含指定目录下的模块,可接收正则表达式或者字符串(文件绝对路径),以及由它们组成的数组。
(2)exclude与include同时存在时,exclude的优先级更高。
rules: [
{
use: ['style-loader', 'css-loader'],
exclude: /src\/lib/,
include: /src/,
}
]
-
resource与issuer
(1)作用:更加精准得确定模块规则得作用范围。
(2)resource:被加载模块。issuer:加载者。
(3)与上面那种形式无法共存,只能选择一种风格进行配置
rules: [
{
use: ['style-loader', 'css-loader'],
resource: {
test: /\.css$/,
exclude: /node_modules/,
},
issuer: {
test: /\.js$/,
exclude: /node_modules/,
},
}
]
-
enforce
(1)作用:指定一个loader的种类(按照执行顺序)。值为“pre”或“post” 。
pre:将在所有正常loader之前执行。
post:将在所有正常loader之后执行。
三、常用loader
-
babel-loader
(1)作用:处理ES6+并将其编译为ES5。
npm install babel-loader @babel/core @babel/preset-env
babel-loader:它是使Babel与Webpack协同工作的模块。
@babel/core:它是babel编译器的核心模块。
@babel/preset-env:它是Babel官方推荐的预置器,可根据用户设置的目标环境自动添加所需的插件和补丁来编译ES6+代码。
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'css-loader',
options: {
//启用缓存机制,可接收一个字符串类型的路劲来做为缓存路径
//这个值也可以是true,此时其缓存目录会指向node_modules/.cache/babel-loader
cacheDirectory: true,
presets: [[
'env', {
//@babel/preset-env会将ES6 Module转化为CommonJS的形式,这会导致webpack中的tree-shaking特性失效。
//设置为false可以禁用模块语句的转化,而将ES6 Module的语法交给webpack本身处理。
module: false
}
]]
}
}
}
]
(2) babel-loader支持从.babelrc文件读取Babel配置,因此可以将presets和plugins从webpack配置文件中提取出来,也能达到相同的效果。
-
ts-loader
npm install ts-loader typescript
(1)作用:用于连接webpack与typescript的模块。
(2)注意: typescript本身的配置并不在ts-loader中,而是必须要放在工程目录下的tsconfig.json中。
-
html-loader
(1)作用:将HTML文件转化为字符串并进行格式化,这使得我们可以把一个HTML片段通过JS加载进来
npm install html-loader
-
handlebars-loader
(1)作用:处理handlebars模块。
(2)handlebars文件加载后得到的是一个函数,可以接收一个变量对象并返回最终的字符串。
-
file-loader
(1)作用:打包文件类型的资源,并返回其publicPath
rules: [
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'file-loader',
options: {
//文件名
name: '[name].[ext]',
//可覆盖output中配置的publicPath
publicPath: './another-path/'
}
}
}
]
//图片路径生成为:./another-path/avatar.jpg
-
url-loader
(1)作用:与file-loader类似,唯一不同在于用户可以设置一个文件大小的阈值,当大于该阈值时与file-loader一样返回publishPath,而小于该阈值时返回文件base64形式编码。
rules: [
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 10240,
//文件名
name: '[name].[ext]',
//可覆盖output中配置的publicPath
publicPath: './another-path/'
}
}
}
]
-
vue-loader
(1)作用: 用于处理vue组件
四、自定义loader
实现一个loader,为所有JS文件启用严格模式,即在文件头部加上‘user strict’的代码
(1)loader初始化
1、创建一个force-strict-loader目录
2、创建index.js,也就是loader的主体
module.exports = function (content) {
var useStrictPrefix = '\'use strict\';\n\n';
return useStrictPrefix + content
}
3、安装并使用这个loader
npm install <path-to-loader>/force-strict-loader
在webpack目录下使用相对路径安装,会在项目的node_modules中创建一个指向实际force-strict-loader目录的软链,也就是我们之后可以随时修改loader源码并且不需要重复安装了。
4、添加webpack配置
module: {
rules: [
{
test: /\.js$/,
use: 'force-strict-loader',
}
]
}
(2)启用缓存
module.exports = function (content) {
if (this.cacheable) {
this.cacheable();
}
var useStrictPrefix = '\'use strict\';\n\n';
return useStrictPrefix + content
}
(3)获取options
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'force-strict-loader',
options: {
sourceMap: true
}
}
}
]
}
需要安装一个依赖库loader-utils,主要用于提供一些帮助函数。
var loaderUtils = require('loader-utils')
module.exports = function (content) {
if (this.cacheable) {
this.cacheable();
}
var options = loaderUtils.getOptions(this) || {};
//....
var useStrictPrefix = '\'use strict\';\n\n';
return useStrictPrefix + content
}
(4)支持souce-map
var loaderUtils = require('loader-utils')
var SourceNode = require('source-map').SourceNode;
var SourceMapConsumer = require('source-map').SourceMapConsumer
module.exports = function (content, sourceMap) {
if (this.cacheable) {
this.cacheable();
}
var options = loaderUtils.getOptions(this) || {};
if (options.sourceMap && sourceMap){
//......
}
var useStrictPrefix = '\'use strict\';\n\n';
return useStrictPrefix + content
五、loader相关知识
(1)loader-runner
1、定义:
loader-runner 允许你在不安装 webpack 的情况下运行 loaders
2、作用:
- 作为 webpack 的依赖,webpack 中使用它执行 loader
- 进行 loader 的开发和调试
3、loader-runner 的使用
import { runLoaders } from "loader-runner";
runLoaders(
{
resource: "/abs/path/to/file.txt?query", // String: 资源的绝对路径(可以增加查询字符串)
loaders: ["/abs/path/to/loader.js?query"], // String[]: loader 的绝对路径(可以增加查询字符串
context: { minimize: true }, // 基础上下文之外的额外 loader 上下文
readResource: fs.readFile.bind(fs), // 读取资源的函数
},
function (err, result) {
// err: Error?
// result.result: Buffer | String
}
);
4、例子
a、自定义一个loader:src/raw-loader.js(将source转化为string)
module.exports = function (source) {
const json = JSON.stringify(source)
.replace(/\u2028/g, "\\u2028 ") // 为了安全起见, ES6模板字符串的问题
.replace(/\u2029/g, "\\u2029");
return `export default ${json}`;
};
b、新建一个资源为:src/demo.txt 内容为一个文本 foobar
c、使用 loader-runner 调试 loader(新建一个run-loader.js文件)
const fs = require("fs");
const path = require("path");
const { runLoaders } = require("loader-runner");
runLoaders(
{
resource: "./demo.txt",
loaders: [path.resolve(__dirname, "./src/raw- loader")],
readResource: fs.readFile.bind(fs),
},
(err, result) => (err ? console.error(err) : console.log(result))
);
d、运行run-loader.js输出结果
(2)loader 的参数获取
通过 loader-utils 的 getOptions 方法获取
const loaderUtils = require("loader-utils");
module.exports = function (content) {
const { name } = loaderUtils.getOptions(this);
};
(3)loader 异常处理
1、loader 内直接通过 throw 抛出
2、通过 this.callback 传递错误
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
(4)loader 的异步处理
通过 this.async 来返回一个异步函数(第一个参数是 Error,第二个参数是处理的结果)
module.exports = function (input) {
const callback = this.async();
// No callback -> return synchronous results
// if (callback) { ... }
callback(null, input + input);
};
(5)在 loader 中使用缓存
1、webpack 中默认开启 loader 缓存,可以使用 this.cacheable(false) 关掉缓存。
2、缓存条件:
- loader 的结果在相同的输入下有确定的输出
- 有依赖的 loader 无法使用缓存
(6)loader 文件输出
通过 this.emitFile 进行文件写入
const loaderUtils = require("loader-utils");
module.exports = function (content) {
const url = loaderUtils.interpolateName(this, "[hash].[ext]", { content });
this.emitFile(url, content);
const path = `__webpack_public_path__ + ${JSON.stringify(url)};`;
return `export default ${path}`;
};