- 1. Loader 分类与执行顺序
- 1.1 Loader 分类
- 1.2 Loader 执行顺序
- 1.3 使用Loader方式
- 1.3.1 内联 Loader
- 2. 开发 Loader 步骤
- 2.1 开发环境
- 2.2 最简单的 Loader
- 2.3 Loader 分类
- 2.3.1 同步 Loader
- 2.3.2 异步 loader
- 2.3.3 Raw Loader
- 2.3.4 Pitching Loader
- 2.4 loader API
- 3. 自定义Loader
- 3.1 clean-log-loader
- 3.2 banner-loader
- 3.3 babel-loader
- 3.4 file-loader
- 4. 小结
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: 前置loadernormal: 普通loaderinline: 内联loaderpost: 后置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)
用法: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';
新建一个文件在,并在里面新建一个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文件在浏览器打开
在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',
},
],
},
执行打包命令,并查询运行结果:
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 和 loader,直接进入上一个 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:SourceMapmeta: 别的Loader传递的数据
