概念
loader 是一个Node.js模块,只是必须以函数格式导出来使用;它作用于符合条件的模块并按照一定格式输出符合webpack要求的模块。然后webpack将这些模块打包起来生成对应的js文件
- 这个函数不可以是 箭头函数,因为
webpack提供了loader api都是挂载到this上的。 - 这个函数必须有 返回值,否则会报错。
使用方式:
- 配置方式(推荐):在 webpack.config.js 文件中指定 loader。
- 内联方式:在每个
import语句中显式指定 loader
调用方式:
- loader 支持链式调用,从右到左(或
从下到上)地执行,也就是说,先写的后执行,类似栈。上一个loader的处理结果传给下一个,上一个loader的参数options也可以传给下一个loader- loader 可以是同步的,也可以是异步的。
除了常见的通过 package.json 的 main 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块。
loader工作流程
- webpack.config.js 里配置了一个模块的 Loader;
- 遇到相应模块文件时,触发了该模块的 loader;
- loader 接受了一个表示该模块文件内容的 source;
- loader 使用 webapck 提供的一系列 api 对 source 进行转换,得到一个 result;
- 将 result 返回或者传递给下一个 Loader,直到处理完毕。
自定义Loader
目标:将代码中的 "world" 替换成指定字符串
// webpack.config.js
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve(__dirname, './myLoaders/first.js'),
options: {
name: "三石的世界"
}
},
]
},
]
}
// first.js
import { getOptions } from 'loader-utils';
module.exports = function(source, sourceMap?, data?) {
// 获取到用户给当前 Loader 传入的 options
const options = getOptions(this);
const _source = source.replace('world', options.name )
return _source
}
目标:替换return 在大多数情况下,我们还是更希望使用
this.callback方法去导出数据,它有以下几个参数
this.callback(
err: Error | null, 当loader出错时向外跑出一个Error
content: string | Buffer, 经过loader编译后需要导出的内容
sourceMap?: SourceMap, 为方便调试生成的编译后内容的source map
ast?: AST,本次编译生成的AST静态语法树
);
// first.js
import { getOptions } from 'loader-utils';
module.exports = function(source, sourceMap?, data?) {
const options = getOptions(this);
const _source = source.replace('world', options.name )
this.callback(null, _source)
}
目标:可进行异步操作
- 方法1,使用 Promise
// first.js
import { getOptions } from 'loader-utils';
module.exports = function(source) {
const options = getOptions(this);
function timeout(dalay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const _source = source.replace('world', options.name)
resolve(_source)
}, delay)
})
}
const data = await timeout(1000);
return data
}
- 方法2,通过
this.async告诉loader的解析器这个loader将会异步地回调
// first.js
import { getOptions } from 'loader-utils';
module.exports = function(source) {
const _callback = this.async();
const options = getOptions(this);
const _source = source.replace('world', options.name)
setTimeout(() => {
_callback(null, _source)
}, 3000)
}
异步 loader 不会影响其他模块的 loader,但是会影响多个 loader 作用于一个模块的 loader。
一个 loader 执行完,才会交给下一个 loader 直到没有 loader ,最后交给 webpack
在下例中,异步 first loader 执行完成后,才会执行 second loader
// webpack.config.js
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve(__dirname, './myLoaders/second.js'),
},
{
loader: path.resolve(__dirname, './myLoaders/first.js'),
options: {
name: "三石的世界"
}
},
]
},
]
}
目标:关闭loader缓存 webpack增量编译机制会观察每次编译时的变更文件,在默认情况下,webpack会对loader的执行结果进行缓存,这样能够大幅度提升构建速度,不过我们也可以手动关闭它(不过为什么呢?)
// first.js
import { getOptions } from 'loader-utils';
module.exports = function(source) {
this.cacheable(false);
const options = getOptions(this);
const _source = source.replace('world', options.name )
this.callback(null, _source)
}
目标:loader的前置执行 在 loader 文件里你可以 exports 一个命名为 pitch 的函数,它会先于 loader 执行
// first.js
import { getOptions } from 'loader-utils';
module.exports.pitch = (remaining, preceding, data) => {
console.log('***remaining***', remaining)
console.log('***preceding***', preceding)
// data会被挂在到当前loader的上下文this上在loaders之间传递
data.value = "world"
}
module.exports = function(source) {
const options = getOptions(this);
// 替换"world"
const _source = source.replace(this.data.value, options.name)
this.callback(null, _source)
}
目标:优化自定义 Loader 路径 如上可以看到,我们引入自定义loader时,都使用绝对路径的引用方式,实在是太繁琐了。
解决方法就是通过配置参数resolveLoader来配置 loader 检索路径
// webpack.config.js
resolveLoader: {
// 默认是 node_modules
modules: ["node_modules", "./myLoaders"]
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'second.js',
},
{
loader: 'first.js',
options: {
name: "三石的世界"
}
},
]
},
]
}
官网参考:编写一个Loader
常见Loader
js相关
babel-loader
将 es6+ 语法转换为 es5语法
cnpm i babel-loader @babel/core @babel/preset-env -D
- babel-loader 这是使babel和webpack协同工作的模块
- @bable/core 这是babel编译器核心模块
- @babel/preset-env 这是babel官方推荐的预置器,可根据用户的环境自动添加所需的插件和补丁来编译Es6代码 三石的未完成webpack.config.js(babel篇)
ts-loader
为webpack提供的 TypeScript loader,打包编译Typescript
安装依赖:
npm install ts-loader --save-dev
npm install typescript --dev
webpack配置:
module.exports = {
mode: "development",
devtool: "inline-source-map",
entry: "./index.ts",
output: {
filename: "bundle.js"
},
resolve: {
extensions: [".ts", ".tsx", ".js"]
},
module: {
rules: [
{ test: /.tsx?$/, loader: "ts-loader" }
]
}
};
配置tsconfig.json:
{
"compilerOptions": {
// 目标语言的版本
"target": "esnext",
// 生成代码的模板标准
"module": "esnext",
"moduleResolution": "node",
// 允许编译器编译JS,JSX文件
"allowJS": true,
// 允许在JS文件中报错,通常与allowJS一起使用
"checkJs": true,
"noEmit": true,
// 是否生成source map文件
"sourceMap": true,
// 指定jsx模式
"jsx": "react"
},
// 编译需要编译的文件或目录
"include": [
"src",
"test"
],
// 编译器需要排除的文件或文件夹
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
更多配置请看 官网
图片相关
file-loader
用于处理文件类型资源,如jpg,png等图片。返回值为publicPath为准
// file.js
import img from './webpack.png';
console.log(img); // 编译后:https://www.pics.com/webpack_605dc7bf.png
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /.(png|jpe?g|gif)$/i,
loader: 'file-loader',
options: {
// 占位符 [name] 名称,[ext] 后缀
name: '[name]_[hash:8].[ext]',
// 资源的使用位置,publicPath + name = css中图片的完整使用路径
publicPath: "https://www.pics.com",
},
},
],
},
};
css文件里的图片路径变成如下:
/* index.less */
.tag {
background-color: red;
background-image: url(./webpack.png);
}
/* 编译后:*/
background-image: url(https://www.pics.com/webpack_605dc7bf.png);
url-loader:
它与file-loader作用相似,也是处理图片的,只不过url-loader可以设置一个根据图片大小进行不同的操作,如果该图片大小大于指定的大小,则将图片进行打包资源,否则将图片转换为base64字符串合并到js文件里。
module.exports = {
module: {
rules: [
{
test: /.(png|jpg|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
name: '[name]_[hash:8].[ext]',
// 如果小于10kb则转换为base64打包进js文件
limit: 10240,
}
}
]
}
]
}
}
html-withimg-loader
我们在编译图片时,都是使用file-loader和url-loader,这两个loader都是查找js文件里的相关图片资源,但是html里面的文件不会查找所以我们html里的图片也想打包进去,这时使用html-withimg-loader
module.exports = {
module: {
rules: [
{
test: /\.(png|jpeg|jpg)/,
use: "html-withimg-loader"
}
]
}
}
// index.html
<html lang="en">
...
<body>
<h4>我是图片</h4>
<img src="./src/img/pic.jpg" alt="">
</body>
</html>
image-webpack-loader
用于图片压缩
这个 loader 需要使用 cnpm 才能安装成功。
字体文件的处理和图片是一样的,用的
loader也是一样的。 字体文件也可以转成base64格式的,所以也可以用url-loader。
css相关
style-loader
通过注入<style>标签将CSS插入到DOM中
- 如果需要将CSS单独提取为一个文件,可使用插件
mini-css-extract-plugin - 对于development模式可以使用style-loader,因为它是通过
<style></style>标签的方式引入CSS的,加载会更快 - 不要将 style-loader 和 mini-css-extract-plugin 针对同一个CSS模块一起使用
需要注意loader执行顺序,处理css时,应该将style-loader放到第一位,因为loader都是从下往上执行,最后全部编译完成挂载到style上
css-loader
用于识别.css文件, 仅处理css的各种加载语法(@import和url()函数等),就像 js 解析 import/require() 一样。
处理css必须配合style-loader共同使用,只安装css-loader样式不会生效
less-loader
解析less,转换为css
sass-loader
解析sass,转换为css
postcss-loader
PostCSS 是一个允许使用 JS 插件转换样式的工具集。 这些插件可以检查 CSS,支持 CSS Variables 和 Mixins, 编译尚未被浏览器广泛支持的先进的 CSS 语法,内联图片,以及其它功能。
其中, autoprefixer 插件添加了浏览器前缀,使用 Can I Use 上面的数据。
安装依赖:
npm install postcss-loader autoprefixer --save-dev
webpack配置:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const isDev = process.NODE_ENV === 'development';
module.exports = {
module: {
rules: [
{
test: /\.(css|less)$/,
exclude: /node_modules/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
{
loader: 'postcss-loader'
},
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true
}
}
}
]
}
]
}
}
postcss配置: 在项目根目录创建postcss.config.js,并且设置支持哪些浏览器,必须设置支持的浏览器才会自动添加添加浏览器兼容
// postcss.config.js
module.exports = {
plugins: [
require('precss'),
require('autoprefixer')({
'browsers': [
'defaults',
'not ie < 11',
'last 2 versions',
'> 1%',
'iOS 7',
'last 3 iOS versions'
]
})
]
}
html相关
html-loader
有时候想引入一个html页面代码片段赋值给DOM元素内容使用,这时就用到html-loader
建议安装低版本,高版本可能会不兼容导致报错。
cnpm i html-loader@0.5.5 -D
// index.js
import Content from "../template.html"
document.body.innerHTML = Content
module.exports = {
module: {
rules: [
{
test: /.html$/,
use: "html-loader"
}
]
}
}
文件相关
markdown-loader
markdown编译器和解析器
webpack配置: 只需将 loader 添加到您的配置中,并设置 options。
// file.js
import md from 'markdown-file.md';
// webpack.config.js
const marked = require('marked');
const renderer = new marked.Renderer();
module.exports = {
module: {
rules: [
{
test: /.md$/,
use: [
{
loader: 'html-loader'
},
{
loader: 'markdown-loader',
options: {
pedantic: true,
renderer
}
}
]
}
],
},
};
raw-loader
可将文件作为字符串导入
// index.js
import txt from './file.txt';
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /.txt$/,
use: 'raw-loader'
}
]
}
}
性能相关
thread-loader
作用
把这个 loader 放置在其他 loader 之前, 放置在这个 loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行
在 worker 池(worker pool)中运行的 loader 是受到限制的。例如:
- 这些 loader 不能产生新的文件。
- 这些 loader 不能使用定制的 loader API(也就是说,通过插件)。
- 这些 loader 无法获取 webpack 的选项设置。 每个 worker 都是一个单独的有 600ms 限制的 node.js 进程。同时跨进程的数据交换也会被限制。
所以,一般仅在耗时的 loader 上使用
安装
npm install --save-dev thread-loader
配置
module.exports = {
module: {
rules: [
{
test: /.js$/,
include: path.resolve('src'),
use: [
{
loader: 'thread-loader',
// 有同样配置的 loader 会共享一个 worker 池
options: {
// 产生的 worker 的数量,默认是 cpu 的核心数
workers: 2,
// 一个 worker 进程中并行执行工作的数量
// 默认为 20
workerParallelJobs: 50,
// 额外的 node.js 参数
workerNodeArgs: ['--max-old-space-size', '1024'],
// 闲置时定时删除 worker 进程
// 默认为 500ms
// 可以设置为无穷大, 这样在监视模式(--watch)下可以保持 worker 持续存在
poolTimeout: 2000,
// 池分配给 worker 的工作数量
// 默认为 200
// 降低这个数值会降低总体的效率,但是会提升工作分布更均一
poolParallelJobs: 50,
// 池的名称
// 可以修改名称来创建其余选项都一样的池
name: 'my-pool',
},
},
,
'expensive-loader',
],
},
],
},
};
当项目较小时,使用多进程打包反而造成打包时间延长,因为进程之间通信产生的开销比多进程能够节约的时间更长
cache-loader
在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。默认保存在 node_modueles/.cache/cache-loader 目录下。
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
use: ['cache-loader','babel-loader']
}
]
}
}