手写loader
const 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')],
alias: {
loader1: path.resolve(__dirname, 'loaders', 'loader1')
}
},
module: {
rules: [
{
test: /\.js$/,
use: [
'loader1',
'loader2',
'loader3'
]
}
]
}
module: {
rules: [
{
test: /\.js$/,
use: ['loader1'],
enforce: 'pre'
},
{
test: /\.js$/,
use: ['loader2'],
enforce: 'normal'
},
{
test: /\.js$/,
use: ['loader3'],
enforce: 'port'
}
]
}
}
function loader(source){
console.log(source)
return source
}
module.exports = loader
const str = require('!inline-loader(loader文件名)!./test.js')
- loader默认由两部分组成 pitchLoader normalLoader
function loader(source){
console.log('loader1-normal')
return source
}
loader.pitch = function(){
console.log('loader1-pitch')
}
function loader(source){
console.log('loader2-normal')
return source
}
loader.pitch = function(){
console.log('loader2-pitch')
}
module = {
rules: [
'loader1',
'loader2'
]
}
- loader的特点
- 最后一个执行的loader要返回js脚本
- 每个loader都是无状态的,确保loader在不用模块转换之间不保存状态
手写babel-loader
const babel = require('@babel/core')
function loader(source){
const cb = this.async();
babel.transform(source, {
...this.query,
sourceMap: true,
filename: this.resourcePath.split('/').pop()
}, function(err, result){
cb(null, result.code, result.map)
})
}
module.exports = loader
手写banner-loader
const {validate} = require('schema-utils')
const fs = require('fs')
function loader(source){
const options = this.query;
const cb = this.async()
let schema = {
type: 'object',
properties: {
text: {
type: 'string'
},
filename: {
type: 'string'
}
}
}
validate(schema,options, 'banner-loader');
if(options.filename){
this.addDependency(options.filename)
fs.readFile(options.filename, 'utf-8', function(err, data){
cb(err, `/**${data}**/${source}`)
})
}else{
cb(null, `/**${options.text}**/${source}`)
}
}
手写file-loader url-loader
const loaderUtils = require('loader-utils');
function loader(source){
const filename = loaderUtils.interpolateName(this, '[hash].[ext]', {
content: source
})
this.emitFile(filename, source)
return `module.exports="${filename}"`
}
loader.raw = true;
module.exports = loader
const mime = require('mime')
function loader(source){
const options = this.query
if(options.limit && options.limit > source.limit){
return `module.exports = "data:/${mime.getType(this.resourcePath)};base64,${source.toString('base64')}"`
}else{
return require('./file-loader').call(this, source)
}
}
loader.raw = true
手写less-loader css-loader style-loader
- 实际执行顺序: style-loader(pitch), less-loader(normal), css-loader(normal)
const less = require('less')
function loader(source){
let css;
less.render(source, function(err, result){
css = result.css
})
return css
}
module.exports = loader
function loader(source){
const reg = /url\((.+?)\)/g;
let pos = 0;
let current;
let arr = ['let list = []'];
while(current = reg.exec(source)){
let [matchUrl, group] = current;
let last = reg.lastIndex - matchUrl.length;
arr.push(`list.push(${JSON.stringify(source.slice(pos, last))})`)
arr.push(`list.push('url('+require(${group})+')')`)
pos = reg.lastIndex
}
arr.push(`list.push(${JSON.stringify(source.slice(pos))})`)
arr.push(`module.exports = list.join('')`)
return arr.join('\r\n')
}
module.exports = loader;
function loader(source){
let str = `
let style = document.createElement('style')
style.innerHTML = ${JSON.stringify(source)}
document.head.appendChild(style)
`
return str
}
loader.pitch = function(remainingRequest){
let str = `
let style = document.createElement('style');
style.innerHTML = require("${'!!' + 'css-loader.js!less-loader!./index.less'}")
document.head.appendChild(style)
`
return str
}
module.exports = loader