何为loader
就是把一个文件读出来,然后根据自己的喜好各种操作字符串,但最终的字符串一定要是js代码,然后丢给webpack处理压缩打包。
例如vue-loader,是把文件组件里面改装成用Vue.component注册的组件,变成直接在html页面引入vue.js的开发模式的写法。
来写一个自己的loader
假设名为cn-loader,用来解析扩展名为.cn的文件
- 第一步:先搭一个webpack项目,如下:
目录:
- loaders
|- cn-loader.js //这个就是我们的loader
- public
|- index.html //html模板
- src
|- index.cn //入口文件
|- test-1.cn
|- test-2.cn
- webpack.config.js
- package.json
package.json配置:
{
"name": "loader",
"version": "1.0.0",
"description": "",
"main": "index.cn",
"scripts": {
"start": "webpack-dev-server --mode development",
"dev": "webpack --mode development",
"build": "webpack --mode production"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.0",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"html-webpack-plugin": "^4.0.3",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
}
webpack.config.js配置:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
module: {
rules: [{
test: /\.js$/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
},{
test: /\.cn$/,
use: './loaders/cn-loader.js',
exclude: /node_modules/
}]
},
resolve: {
extensions: ['.js', '.cn']
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html'
})
],
stats: {
modules: false
}
}
好,项目基本就搭好了。
- 第二步:假设我们用.cn文件来实现中文写代码
先看看test-1.cn:
导入:./test-2.cn 为:test2
打印:test2
变量:计数 = 100
函数:增加
计数 = 计数 + 1
打印:"计数的值为:" + 计数
返回值:计数
执行:增加
导出:增加
没错,这是一段代码,它最终会被cn-loader解析成为:
import test2 from './test-2.cn'
console.log(test2)
var 计数 = 100
function 增加(){
计数 = 计数 + 1
console.log("计数的值为:" + 计数)
return 计数
}
增加()
export default 增加
再看看简单的index.cn和test-2.cn
index.cn:
导入:./test-1.cn 为:test1
执行:test1
test-2.cn:
变量:哦嚯 = "哦嚯,这是test-2"
导出:哦嚯
注释:我们甚至可以js和中文混写
document.body.innerHTML = '<h1>cn-loader 是无敌的</h1>'
那么,关键的loader来了:
function parse(code){
return code
.replace(/导入[::]\s*(.+?)\s*(?:为[::]\s*(.+?))?(\n|$)/g, ($0, $1, $2) => {
if($2 !== undefined){
return 'import '+ $2 +' from "' +$1 + '";\n'
}else{
return 'import'+$1+';\n'
}
})
.replace(/注释[::]/g, '//')
.replace(/变量[::]\s*(.+?)(?:\s*=\s*(.+?))?(\n|$)/g, ($0, $1, $2) => {
if($2 !== undefined){
return 'var '+ $1 + '=' + $2 + ';\n'
}
return 'var '+$1+';\n';
})
.replace(/打印[::]\s*(.+?)(\n|$)/g, 'console.log($1)\n')
.replace(/返回值[::]\s*(.+?)(\n|$)/g, 'return $1\n')
.replace(/执行[::]\s*(.+?)(\n|$)/g, '$1()\n')
.replace(/导出[::]\s*(.+?)(\n|$)/g, 'export default $1\n')
}
function parseFn(code){
let lines = code.split(/\n/), fnStart = false
for(let i=0; i<lines.length; i++){
if(fnStart) {
if(/^\s+/.test(lines[i])){
continue
}else{
lines[i] = '}\n' + lines[i]
fnStart = false
}
}
if(!fnStart && /^函数[::]/.test(lines[i])){
fnStart = true
lines[i] = lines[i].replace(/^函数[::]\s*(.+?)\s*$/, 'function $1(){')
}
}
return lines.join('\n')
}
//===== 华丽分隔线上部分是各种造作代码的逻辑: 就是把中文翻译成js=====
//===== 华丽分隔线下面这里就是这么回事,简单得要命================
module.exports = (code) => {
// 一顿操作猛如虎
code = parseFn(code)
code = parse(code)
// console.log(code)
// 之后把操作好的东西给回webpack
return code
}
顿时没什么神秘感,loader里module.exports 出去的函数,会被webpack在读取文件之后调用,并且会传来读取到的内容code。你只管对code操作,最后返回webpack认识的代码即可。
可能有人好奇文件的各种导入层级是不是要自己写递归?不是,导入导出的逻辑完全不用管,那是webpack基本的能力,你只管把它给你的每一份code解析成webpack认识的,webpack会根据我们给它的code中是否有import去递归找文件,一份一份输出code给我们操作。
现在是不是可以想得通.vue、.ts、tsx这些文件的loader了,脑瓜不疼了。
OK,撒花,鼓掌,各种夸!