由于webpack 只能解析 JavaScript 和 JSON 文件,当引入非 JavaScript 文件时,要使用 Webpack的loader机制, loader让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。
loader 模块需要导出一个函数。 这个导出的函数的工作就是获得处理前的原内容,对原内容执行处理后,返回处理后的内容。
Webpack配置里的 module.rules
数组配置了一组规则,告诉 webpack在遇到哪些文件时使用哪些 Loader 去加载和转换。
如 webpack里的配置文件webpack.config.js
module: {
rules: [
{
test: /\.js$/,
// 把 ES6 转换成 ES5
use: ['babel-loader'],
exclude: /node_modules/
},
{
test: /\.css$/,
// postcss-loader:扩展 CSS 语法,使用下一代 CSS,在3-5使用 PostCSS中有介绍。
// css-loader:加载 CSS,支持模块化、压缩、文件导入等特性。
// style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
use: ['style-loader', 'css-loader', 'postcss-loader']
},
{
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
use: [{
// url-loader:能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
loader: 'url-loader',
options: {
limit: 10000
}
}]
}
]
},
加载es6、样式、图片文件,都有其对应的loader
rules的选项配置方式如下:
test
属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。use
属性,表示进行转换时,应该使用哪个 loader,可以指定一个或多个loader,当多个loader时,其执行顺序是从右到左。
当使用loader时,Webpack是如何获取到用户传入的options
使用 loader-utils 中提供的 getOptions 方法 来提取给定 loader 的 option。
const loaderUtils = require('loader-utils')
module.exports = function(source) {
// 获取到用户给当前 Loader 传入的 options
const options = loaderUtils.getOptions(this)
return source
}
获取到options就是webpack配置 module.rules
里的options属性
{
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
use: [{
// url-loader:能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
loader: 'url-loader',
options: {
limit: 10000
}
}]
}
module.exports = function(source, map, meta) {
// 获取到用户给当前 Loader 传入的 options
const options = loaderUtils.getOptions(this)
return source
}
source
,上一个 loader 产生的结果或者资源文件
map
和 meta
参数是可选的
map,传递一个可选的 SourceMap 结果
设置是否可缓存标志的函数:
cacheable(flag = true: boolean)
默认情况下,loader 的处理结果会被标记为可缓存。调用这个方法然后传入 false
,可以关闭 loader 的缓存。
一个可缓存的 loader 在输入和相关依赖没有变化时,必须返回相同的结果。这意味着 loader 除了 this.addDependency
里指定的以外,不应该有其它任何外部依赖。
loader 将会异步地回调。返回 this.callback
。
loader 有同步和异步之分,同步会从头到尾执行,但是一些操作如果是同步,就会阻塞构建进程,如读取本地文件,网络请求,所以可以采取异步操作。
下面编写一个自定义loader replace-html-loader
,该loader的作用是,解析引入的html模板。
@include(../templates/list.html)
转换成 list.html
里面的内容
该loader的使用场景是,在多个文件中,当使用同一段html代码时,可以通过引入模板路径,来渲染模板
replace-html-loader.js
const fs = require('fs')
const path = require('path')
const loaderUtils = require('loader-utils')
module.exports = function(source) {
// 关闭该 Loader 的缓存功能
this.cacheable(false)
// 获取到用户给当前 Loader 传入的 options
const options = loaderUtils.getOptions(this)
const { key } = options
const reg = new RegExp(`(@${key}\\()(.*?)\\)`)
const regResult = reg.exec(source)
// 告诉 webpack本次转换是异步的,Loader 会在 callback 中回调结果
var callback = this.async()
if (regResult) {
const request = loaderUtils.urlToRequest(regResult[2])
// 像 require 语句一样获得指定文件的完整路径
this.resolve('/src', request, (err, rs) => {
if (err) {
rs = path.resolve(this.resourcePath, '../', request)
}
try {
var layoutHtml = fs.readFileSync(rs, 'utf-8')
} catch (error) {
throw error
}
this.addDependency(rs)
// 用读取的文件内容 替换 @include(../templates/list.html)
res = source.replace(regResult[0], layoutHtml)
callback(null, res)
})
} else {
callback(null, source)
}
}
webpack的config增加loader配置:
{
test: /\.vue$/,
use: ['vue-loader',
{
// 因为该loader还没有上传的npm,所以要指定该loader的路径,才能使用
loader: path.resolve(__dirname, './replace-html-loader.js'),
options: {
key: 'include'
},
}
]
}
因为是在 Vue 的项目中使用这个loader的,所以写在Vue 的 vue-loader
后面
然后在Vue 组件中使用
<template>
<div id="user-list">
<div>@include(../templates/list.html)</div>
</div>
</template>
<script>
export default {
name: 'user-list',
data() {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}]
}
},
methods: {
}
}
</script>
<style>
</style>
list.html里面的内容可以使用当前Vue组件中的变量
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="date"
label="日期"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="姓名"
width="180">
</el-table-column>
<el-table-column
prop="address"
label="地址">
</el-table-column>
</el-table>
效果图:
创建npm包目录
mkdir replace-html-loader
初始化loader
npm init
该命令会一步一步提示输入该loader的信息,最后会生成一个 package.json 文件
完整如下:
{
"name": "replace-html-loader",
"version": "1.0.5",
"description": "A defined webpack loader, replace html template",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"loader-utils": "^2.0.0"
},
"keywords": [
"webpack",
"loader"
],
"author": "wenyan",
"license": "ISC"
}
其中,由 main 属性可以看出,入口文件为 index.js,把之前 replace-html-loader.js 的内容赋值到该文件
至此,该loader 的npm 包已经完成,开始正式发布到npm
去 npm 官网 https://www.npmjs.com/ ,注册一个账号,要进行邮箱验证
注册完成后,就可以添加npm账号了
npm adduser
登录 npm
npm login
npm publish
注意:如果是多次修改npm包,发布时要修改package.json的 version 版本号
在项目根目录执行
npm i replace-html-loader
webpack.config.js
{
test: /\.vue$/,
use: ['vue-loader',
{
// 因为该loader还没有上传的npm,所以要指定该loader的路径,才能使用
// loader: path.resolve(__dirname, './replace-html-loader.js'),
loader: 'replace-html-loader',
options: {
key: 'include'
},
}
]
}
现在已经不需要指定该loader的路径,就能和其他loader一样直接使用了
编写一个loader其实很简单,如果是功能比较复杂,可能就会有点棘手。但总的来说,如果在实际的项目中,遇到问题,无法找到合适的解决方案,就只能自己动手,丰衣足食。认识了loader之后,你解决问题的能力,那就不仅仅停留在框架本身了,还可以通过构建工具来解决问题。