三十九、1.loader:概念
- loader是指用来将一段代码转换成另一段代码的webpack加载器
- 创建文件夹loader-webpack,初始化
$ yarn init -y
,安装包:webpack webpack-cli
- loaders/loader1.js
function loader(source){ // loader的参数就是原代码
console.log(source);
return source; // 什么都不做,直接返回
}
module.exports = loader;
- webpack.config.js
let path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
- 引用loader1的三个方法
- webpack.config.js +
module: {
rules: [
{
test: /\.js$/,
use: path.resolve(__dirname, 'loaders', 'loader1.js')
}
]
}
2. webpack.config.js +
resolveLoader: {
// 别名
alias: {
loader1: path.resolve(__dirname, 'loaders', 'loader1.js')
}
},
module: {
rules: [
{
test: /\.js$/,
use: 'loader1'
}
]
}
3. webpack.config.js +
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loaders')] // 先从node_modules找,找不到再从loaders找
},
module: {
rules: [
{
test: /\.js$/,
use: 'loader1'
}
]
}
四十、2.loader配置
- loader的执行顺序从下到上,从右到左;可以通过enforce的'pre'|'normal'|'inline'|'post'改变执行顺序
- inline-loader执行:
require('!!inline-loader!./a.js')
- -! 不会通过pre和normal loader处理
- ! 不会通过normal loader处理
- !! 什么都不要
- loader.pitch 执行顺序 loader3-pitch loader2-pitch loader1-pitch loader1 loader2 loader3
- loader特点
- 第一个loader要返回js脚本
- 每个loader只做一件内容,为了使loader在更多场景链式调用
- 每一个loader都是一个模块
- 每个loader都是无状态的,确保loader在不同模块转换之间不保存状态。
四十一、3.babel-loader实现
- 安装包 @babel/core @babel/preset-env loader-utils
- webpack.config.js
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
use: {
loader:'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
}
]
}
- loaders/babel-loader.js
//
let babel = require("@babel/core");
let loaderUtils = require('loader-utils');
function loader(source) { // this loaderContext
// console.log(Object.keys(this)); //
//
let options = loaderUtils.getOptions(this);
// console.log(options);
let cb = this.async(); //
//
babel.transform(source, {
...options,
sourceMap: true,
filename: this.resourcePath.split('/').pop() // 文件名
}, function (err, result) {
cb(err, result.code, result.map); //
})
}
module.exports = loader; //
- @babel/core @babel/preset-env 'babel-loader' options presets babel require loader-utils包 getOptions this loaderContext Object.keys .transform source ...options sourceMap cb async rsult.code resource.map class Kft new devtool filename resourcePath split pop
四十二、4.banner-loader实现:给js文件添加注释内容
- 给js文件自动添加注释 /** 注释 */
- webpack.config.js
watch: true,
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'banner-loader', // 添加注释
options: {
text: 'kft',
filename: path.resolve(__dirname, 'banner.js')
}
}
}
]
}
- banner.js
注释内容:作者Fun腾时间2019
- loaders/banner-loader.js
//
let loaderUtils = require('loader-utils');
let validateOptions = require('schema-utils');
let fs = require('fs');
function loader(source) { //
this.cacheable && this.cacheable(); //
//
let options = loaderUtils.getOptions(this);
// console.log(options); // { text: 'kft', filename: '/Desktop/loader-webpack/banner.js' }
let cb = this.async();
//
let schema = {
type: 'object',
properties: {
text: {
type: 'string'
},
filename: {
type: 'string'
}
}
}
//
validateOptions(schema, options, 'banner-loader');
//
if (options.filename) {
this.addDependency(options.filename); //
fs.readFile(options.filename, 'utf8', function (err, data) {
cb(err, `/**${data}**/${source}`);
})
} else {
cb(null, `/**${options.text}**/${source}`);
}
}
module.exports = loader; //
- 自创banner-loader 给所有js添加注释 banner.js schema-utils包校验模块 schema type object properties text type string filename type string validateOptions fs readFile cb cb(err,/**%/) addDependency自动添加文件依赖 cacheable(false)不缓存
四十三、5.file-loader图片模块、url-loader
- file-loader:根据图片生成一个md5戳,发射到dist下,返回当前图片路径
- 安装包 loader-utils'
- webpack.config.js +
{
test: /\.(jpg|png|gif|jpeg)$/,
use: 'file-loader'
}
let loaderUtils = require('loader-utils');
function loader(source) {
let filename = loaderUtils.interpolateName(this, '[hash].[ext]', { content: source }) // 根据当前图片格式生成路径
this.emitFile(filename, source); // 发射文件
return `module.exports="${filename}"`;
}
loader.raw = true;
module.exports = loader;
- url-loader:file-loader会处理路径,根据limit判断转换base64图片还是使用file-loader生成路径
- 安装包 mime loader-utils'
- webpack.config.js
{
test: /\.(jpg|png|gif|jpeg)$/,
use: {
loader: 'url-loader',
options: {
limit: 1*1024
}
}
}
let loaderUtils = require('loader-utils');
let mime = require('mime');
function loader(source) {
let { limit } = loaderUtils.getOptions(this);
if (limit && limit > source.length) {
return `module.exports="data:${mime.getType(this.resourcePath)};base64,${source.toString('base64')}"`
} else {
return require('./file-loader').call(this, source)
}
}
loader.raw = true;
module.exports = loader;
- img creatElement src appenChild import p .jpg file-loader根据图片生成一个md5戳,发射到dist下,返回当前图片路径 file-loader loader .raw二进制 exports loaderUtils filename interpolateName emitFile url-loader会处理路径(file-loader会处理路径,根据大小返回base64还是文件) url-loader loaderUtils getOptions limit source.length return module.exports data:;base64,${source.toString('base64') mime require file-loader
四十四、6.less-loader:处理less和style-loader:插入脚本
- webpack.config.js +
{
test: /\.less$/,
use: ['style-loader','css-loader','less-loader']
}
- loaders/less-loader.js
let less = require('less');
function loader(source) {
let css;
console.log
less.render(source, (err, r) => {
if (err) return console.log(err);
css = r.css;
});
return css;
}
module.exports = loader;
- loaders/css-loader.js
function loader(source){
// 复杂,下一阶段处理
return source;
}
module.exports = loader;
- loaders/style-loader.js
function loader(source){
// 在style-loader导出一个脚本
let str = `
let style = document.createElement('style');
style.innerHTML = ${JSON.stringify(source)};
document.head.appendChild(style);
`
return str;
}
module.exports = loader;
- 样式问题 index.less .less style-loader css-loader less-loader less包 .render source err r r.css css-loader 在style-loader导出一个脚本 str
style=
createElement style innerHTML JSON.stringify head appendChild
四十五、7.css-loader
- 引入的css中有背景图,对css进行分段,'url(...)之前内容' + url(...) + 'url(...)之后内容'
/url\((.+?)\)/g
- 需要有url-loader或file-loader配合使用
- loaders/css-loader.js
function loader(source) {
let reg = /url\((.+?)\)/g;
let pos = 0;
let current;
let arr = ['let list= []'];
while (current = reg.exec(source)) {
let [matchUrl, g] = current;
// console.log(matchUrl, g);
let last = reg.lastIndex - matchUrl.length;
arr.push(`list.push(${JSON.stringify(source.slice(pos, last))})`);
pos = reg.lastIndex;
// 把 g 替换成require的写法 => url(require('xxx'))
arr.push(`list.push('url('+ require(${g}) +')')`)
}
arr.push(`list.push(${JSON.stringify(source.slice(pos))})`);
arr.push(`module.exports = list.join('')`);
return arr.join('\r\n');
}
module.exports = loader;
// 'url(之前' + ...jpg + ')之后' -> 'url(之前' + url(require(...jpg)) + ')之后'
// let list= []
// list.push("body {\n background: red;\n background: ")
// list.push('url('+require(./logo.png)+')')
// list.push(";\n}\n")
// module.exports = list.join('')
- loaders/style-loader.js 使用pitch
let loaderUtils = require('loader-utils');
function loader(source) {
let str = `
let style = document.createElement('style');
style.innerHTML = ${JSON.stringify(source)};
document.head.appendChild(style);
`
return str;
}
// style-loader less-loader!css-loader/ ./index.less
loader.pitch = function (remainingRequest) {
// remainingRequest剩余请求
// console.log(remainingRequest) // "/Users/Desktop/loader-webpack/loaders/css-loader.js!/Users/Desktop/loader-webpack/loaders/less-loader.js!/Users/Desktop/loader-webpack/src/index.less" 转换成相对路径 "../loaders/css-loader.js!../loaders/less-loader.js!./index.less"
// loaderUtils.stringifyRequest方法:绝对路径转为相对路径
let str = `
let style = document.createElement('style');
style.innerHTML = require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)}); // 使用!!,防止重复调用style-loader
document.head.appendChild(style);
`
return str;
}
module.exports = loader;
- 背景图 分段 'url(之前' + ...jpg + ')之后' reg
/url\((.+?)\)/g
pos while exec current matchUrl g console last lastetIndex length arr list push JSON pos last g 替换成require push url push slice pos join style-loader pitch remainingRequest less-loader '!!' loaderUtils stringifyRequest绝对路径转为相对路径
webpack.config.js
let path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loaders')]
},
devtool: 'source-map',
module: {
rules: [
// {
// test: /\.js$/,
// use: {
// loader:'babel-loader',
// options: {
// presets: [
// '@babel/preset-env'
// ]
// }
// }
// },
// {
// test: /\.js$/,
// use: {
// loader: 'banner-loader', // 添加注释
// options: {
// text: 'kft',
// filename: path.resolve(__dirname, 'banner.js')
// }
// }
// }
// {
// test: /\.(jpg|png|gif|jpeg)$/,
// use: 'file-loader'
// }
{
test: /\.(jpg|png|gif|jpeg)$/,
use: {
loader: 'url-loader',
options: {
limit: 0
}
}
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
}
}
package.json
{
"name": "loader-webpack",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.5.0",
"@babel/preset-env": "^7.5.0",
"less": "^3.9.0",
"loader-utils": "^1.2.3",
"mime": "^2.4.4",
"schema-utils": "^1.0.0",
"webpack": "^4.35.2",
"webpack-cli": "^3.3.5"
}
}