webpack
作为前端项目的打包工具,具有很好的学习价值。下面来学习下其中的 Loader
Loader
可以帮助webpack
将不同类型的文件转换为webpack
可识别的模块
webpack
中Loader
使用:https://www.webpackjs.com/loaders/
webpack
中Loader API
的介绍:https://www.webpackjs.com/api/loaders/
在Webpack
中,默认的配置文件为:webpack.config.js
。在学习Loader
之前,要先了解下webpack.config.js
和vue.config.js
的区别:
-
webpack.config.js
是webpack
的配置文件,所有使用webpack
作为打包工具的项目都可以使用,vue
项目可以使用,react
项目也可以使用 -
vue.config.js
是vue
项目的配置文件,专用于vue
项目。通过vue.config.js
中常用功能的配置,简化配置工作,当然如果需要更专业的配置工作,两者在vue
项目中是可以并存的 -
vue-cli3
创建项目时并不会自动创建vue.config.js
,因为这个是可选项,所以一般都是修改webpack
时才会自己创建一个vue.config.js
-
因为
vue-cli3
内部高度集成了webpack
,一般来说使用者不需要再为webpack
做什么配置,所以没有暴露webpack
的配置文件,但开发中依然可以创建vue.config.js
去修改默认的webpack
-
Vue
项目中vue.config.js
文件就等同于webpack
的webpack.config.js
vue 中配置文件说明:https://cli.vuejs.org/zh/config/
1. Loader 分类与执行顺序
1.1 Loader 分类
pre
: 前置loader
normal
: 普通loader
inline
: 内联loader
post
: 后置loader
1.2 Loader 执行顺序
- 4 类
loader
的执行优级为:pre > normal > inline > post
- 相同优先级的
loader
执行顺序为:从右到左,从下到上
// 此时loader执行顺序:loader3 - loader2 - loader1
module: {
rules: [
{
test: /\.js$/,
loader: "loader1",
},
{
test: /\.js$/,
loader: "loader2",
},
{
test: /\.js$/,
loader: "loader3",
},
],
},
// 此时loader执行顺序:loader1 - loader2 - loader3
module: {
rules: [
{
enforce: "pre",
test: /\.js$/,
loader: "loader1",
},
{
// 没有enforce就是normal
test: /\.js$/,
loader: "loader2",
},
{
enforce: "post",
test: /\.js$/,
loader: "loader3",
},
],
},
1.3 使用Loader方式
- 配置方式:在
webpack.config.js
文件中指定loader
。(pre
、normal
、post
) - 内联方式:在每个
import
语句中显式指定loader
。(inline loader
)
1.3.1 内联 Loader
用法:import Styles from 'style-loader!css-loader?modules!./styles.css';
含义:
- 使用
css-loader
和style-loader
处理styles.css
文件 - 通过
!
将资源中的loader
分开
inline loader
可以通过添加不同前缀,跳过其他类型 loader
!
跳过normal loader
import Styles from '!style-loader!css-loader?modules!./styles.css';
-!
跳过pre
和normal loader
import Styles from '-!style-loader!css-loader?modules!./styles.css';
!!
跳过pre
、normal
和post loader
import Styles from '!!style-loader!css-loader?modules!./styles.css';
2. 开发 Loader 步骤
2.1 开发环境
新建一个文件在,并在里面新建一个webpack.config.js
文件,同时新建src
、public
文件夹,具体目录结构如下:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'js/[name].js',
clean: true,
},
module: {
rules: [
{
test: /\.js$/,
loader: './loaders/test-loader/test-loader.js',
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html'),
}),
],
mode: 'development',
}
然后打开终端,来到项目根目录,分别运行以下指令:
- 初始化
package.json
,此时会生成一个基础的package.json
文件
npm init -y
- 下载依赖
npm i webpack webpack-cli -D
-
安装
html-webpack-plugin
插件该插件生成一个
HTML
文件,使用自己提供的模板 -
编写
main.js
文件
// 很简单的一句打印
console.log('hello main')
- 启用
Webpack
开发模式:npx webpack ./src/main.js --mode=development
生产模式:npx webpack ./src/main.js --mode=production
npx webpack
: 是用来运行本地安装 Webpack
包的。./src/main.js
: 指定 Webpack
从 main.js
文件开始打包,不但会打包 main.js
,还会将其依赖也一起打包进来。--mode=xxx
:指定模式(环境)
npx是执行Node软件包的工具,它从npm5.2版本开始,就与npm捆绑在一起
npx作用:
- 默认情况下,首先检查路径中是否存在要执行的包(即在项目中)
- 如果存在,它将执行
- 若不存在,意味着尚未安装该软件包,npx将安装其最新版本,然后执行它
执行完上面操作后,会在项目根目录下生成一个dist
文件夹,把其中的index.html
文件在浏览器打开
2.2 最简单的 Loader
在loaders
文件夹下新建test-loader/test-loader.js
文件
// loaders/test-loader/test-loader.js
module.exports = function (content, map, meta) {
console.log('hello test loader ... ')
// 将源文件中的main替换成webpack
return content.replace('main', 'webpack')
}
它接受要处理的源码作为参数,输出转换后的 js
代码。Loader
接受的参数
content
:源文件的内容map
:SourceMap
数据meta
数据:可以是任何内容
在webpack.config.js
文件中,使用该Loader
module: {
rules: [
{
test: /\.js$/,
loader: './loaders/test-loader/test-loader.js',
},
],
},
执行打包命令,并查询运行结果:
2.3 Loader 分类
2.3.1 同步 Loader
module.exports = function (content, map, meta) {
return content;
};
this.callback
方法则更灵活,因为它允许传递多个参数,而不仅仅是 content
。
module.exports = function (content, map, meta) {
/*
第一个参数:err 代表是否有错误
第二个参数:content 处理后的内容
第三个参数:source-map 继续传递source-map
第四个参数:meta 给下一个loader传递参数
*/
this.callback(null, content, map, meta);
// 同步loader中不能进行异步操作
return; // 当调用 callback() 函数时,总是返回 undefined
};
2.3.2 异步 loader
module.exports = function (content, map, meta) {
// 异步操作
const callback = this.async();
// 进行异步操作
setTimeout(() => {
callback(null, result, map, meta);
}, 1000);
};
由于同步计算过于耗时,在 Node.js 这样的单线程环境下进行此操作并不是好的方案,建议尽可能地使loader 异步化。但如果计算量很小,同步 loader 也是可以的。
2.3.3 Raw Loader
默认情况下,资源文件会被转化为 UTF-8
字符串,然后传给 loader
。通过设置 raw
为 true
,loader
可以接收原始的 Buffer
。
module.exports = function (content) {
// content是一个Buffer数据
return content;
};
module.exports.raw = true; // 开启 Raw Loader
2.3.4 Pitching Loader
module.exports = function (content) {
return content;
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
console.log("do somethings");
};
webpack
会先从左到右执行 loader
链中的每个 loader
上的 pitch
方法(如果有),然后再从右到左执行 loader
链中的每个 loader
上的普通 loader
方法
在这个过程中如果任何 pitch
有返回值,则 loader
链被阻断。webpack
会跳过后面所有的的 pitch
和 loa
der,直接进入上一个 loader
。
2.4 loader API
方法名 | 含义 | 用法 |
---|---|---|
this.async | 异步回调 loader。返回 this.callback | const callback = this.async() |
this.callback | 可以同步或者异步调用的并返回多个结果的函数 | this.callback(err, content, sourceMap?, meta?) |
this.getOptions(schema) | 获取 loader 的 options | this.getOptions(schema) |
this.emitFile | 产生一个文件 | this.emitFile(name, content, sourceMap) |
this.utils.contextify | 返回一个相对路径 | this.utils.contextify(context, request) |
this.utils.absolutify | 返回一个绝对路径 | this.utils.absolutify(context, request) |
3. 自定义Loader
3.1 clean-log-loader
需求:清理 js
代码中的console.log
- 在
loaders
文件夹下新建clean-log-loader/clean-log-loader.js
文件
// loaders/clean-log-loader/clean-log-loader.js
module.exports = function cleanLogLoader(content) {
// 将console.log替换为空
return content.replace(/console\.log\(.*\);?/g, "");
};
- 在
webpack.config.js
文件中,使用该Loader
module: {
rules: [
{
test: /\.js$/,
loader: './loaders/clean-log-loader/clean-log-loader.js',
},
],
},
3.2 banner-loader
需求:给 js
代码添加文本注释
- 在
loaders
文件夹下新建banner-loader/banner-loader.js
和schema.json
文件
const schema = require("./schema.json");
module.exports = function (content) {
// schema对options的验证规则
// schema符合JSON Schema的规则
const options = this.getOptions(schema);
const prefix = `
/*
* Author: ${options.author}
*/
`;
return prefix + content;
};
loaders/banner-loader/schema.json
文件可以对在配置loader
时的参数进行校验
{
"type": "object",
"properties": {
"author": {
"type": "string"
}
},
"additionalProperties": false
}
webpack.config.js
配置文件
{
test: /\.js$/,
loader: "./loaders/banner-loader/banner-loader.js",
// 传入的参数
options: {
author: "scorpios",
},
},
3.3 babel-loader
需求:编译 js
代码,将 ES6+
语法编译成 ES5
语法。(虽然这个功能已经有现场的loader
,但可以自己模拟,加深理解)
- 下载依赖
npm i @babel/core @babel/preset-env -D
- 在
loaders
文件夹下新建babel-loader/babel-loader.js
和schema.json
文件
const babel = require("@babel/core");
const schema = require("./schema.json");
// https://www.babeljs.cn/docs/babel-core
module.exports = function (content) {
// 异步loader
const callback = this.async();
const options = this.getOptions(schema);
// 使用babel对代码进行编译
babel.transform(content, options, function (err, result) {
if (err) callback(err);
else callback(null, result.code);
});
};
loaders/banner-loader/schema.json
{
"type": "object",
"properties": {
"presets": {
"type": "array"
}
},
"additionalProperties": true
}
webpack.config.js
配置文件
{
test: /\.js$/,
loader: "./loaders/babel-loader/babel-loader.js",
options: {
presets: ["@babel/preset-env"],
},
}
3.4 file-loader
需求:将文件原封不动输出出去
- 下载包
npm i loader-utils -D
- 在
loaders
文件夹下新建file-loader/file-loader.js
文件
const loaderUtils = require("loader-utils");
module.exports = function (content) {
// 1. 根据文件内容生成带hash值文件名
let interpolatedName = loaderUtils.interpolateName(this, "[hash].[ext][query]", {
content,
});
interpolatedName = `images/${interpolatedName}`
// console.log(interpolatedName);
// 2. 将文件输出出去
this.emitFile(interpolatedName, content);
// 3. 返回:module.exports = "文件路径(文件名)"
return `module.exports = "${interpolatedName}"`;
};
// 需要处理图片、字体等文件。它们都是buffer数据
// 需要使用raw loader才能处理
module.exports.raw = true;
webpack.config.js
配置文件
{
test: /\.(png|jpe?g|gif)$/,
loader: "./loaders/file-loader/file-loader.js",
type: "javascript/auto", // 解决图片重复打包问题
},
4. 小结
Loader
就是一个函数, 当webpack
解析资源时,会调用相应的Loader
去处理, Loader
接受到文件内容作为参数,返回内容出去。
content
: 文件内容map
:SourceMap
meta
: 别的Loader
传递的数据