};
详情请参考官网API
开发一个简单的md-loader
const marked = require(“marked”);
const loaderUtils = require(“loader-utils”);
module.exports = function (content) {
this.cacheable && this.cacheable();
const options = loaderUtils.getOptions(this);
try {
marked.setOptions(options);
return marked(content)
} catch (err) {
this.emitError(err);
return null
}
};
上述的例子是通过现成的插件把markdown文件里的content转成html字符串,但是如果没有这个插件,改怎么做呢?这个情况下,我们可以考虑另外一种解法,借助 AST 语法树,来协助我们更加便捷地操作转换。
利用 AST 作源码转换
markdown-ast
是将markdown文件里的content转成数组形式的抽象语法树节点,操作 AST 语法树远比操作字符串要简单、方便得多:
//通过正则的方法把字符串处理成直观的AST语法树
const md = require(‘markdown-ast’);
module.exports = function(content) {
this.cacheable && this.cacheable();
const options = loaderUtils.getOptions(this);
try {
console.log(md(content))
const parser = new MdParser(content);
return parser.data
} catch (err) {
console.log(err)
return null
}
};
const md = require(‘markdown-ast’);
const hljs = require(‘highlight.js’);//代码高亮插件
// 利用 AST 作源码转换
class MdParser {
constructor(content) {
this.data = md(content);
console.log(this.data)
this.parse()
}
parse() {
this.data = this.traverse(this.data);
}
traverse(ast) {
console.log(“md转抽象语法树操作”,ast)
let body = ‘’;
ast.map(item => {
switch (item.type) {
case “bold”:
case “break”:
case “codeBlock”:
const highlightedCode = hljs.highlight(item.syntax, item.code).value
body += highlightedCode
break;
case “codeSpan”:
case “image”:
case “italic”:
case “link”:
case “list”:
item.type = (item.bullet === ‘-’) ? ‘ul’ : ‘ol’
if (item.type !== ‘-’) {
item.startatt = ( start=${item.indent.length}
)
} else {
item.startatt = ‘’
}
body += ‘<’ + item.type + item.startatt + ‘>\n’ + this.traverse(item.block) + ‘</’ + item.type + ‘>\n’
break;
case “quote”:
let quoteString = this.traverse(item.block)
body += ‘
\n’ + quoteString + ‘\n’;
break;
case “strike”:
case “text”:
case “title”:
body += <h${item.rank}>${item.text}</h${item.rank}>
break;
default:
throw Error(“error”, No corresponding treatment when item.type equal${item.type}
);
}
})
return body
}
}
md 转成抽象语树ast抽象语法数转成html字符串md2html-loader源码地址(https://github.com/6fedcom/fe-blog/blob/master/webpack-loader/loaders/md-loader.js)
loader的一些开发技巧
-
尽量保证一个loader去做一件事情,然后可以用不同的loader组合不同的场景需求
-
开发的时候不应该在 loader 中保留状态。loader必须是一个无任何副作用的纯函数,loader支持异步,因此是可以在 loader 中有 I/O 操作的。
-
模块化:保证 loader 是模块化的。loader 生成模块需要遵循和普通模块一样的设计原则。
-
合理的使用缓存 合理的缓存能够降低重复编译带来的成本。loader 执行时默认是开启缓存的,这样一来, webpack 在编译过程中执行到判断是否需要重编译 loader 实例的时候,会直接跳过 rebuild 环节,节省不必要重建带来的开销。但是当且仅当有你的 loader 有其他不稳定的外部依赖(如 I/O 接口依赖)时,可以关闭缓存:
this.cacheable&&this.cacheable(false);
loader-runner
是一个非常实用的工具,用来开发、调试loader,它允许你不依靠 webpack 单独运行 loadernpm install loader-runner --save-dev
// 创建 run-loader.js
const fs = require(“fs”);
const path = require(“path”);
const { runLoaders } = require(“loader-runner”);
runLoaders(
{
resource: “./readme.md”,
loaders: [path.resolve(__dirname, “./loaders/md-loader”)],
readResource: fs.readFile.bind(fs),
},
(err, result) =>
(err ? console.error(err) : console.log(result))
);
执行 node run-loader
认识更多的loader
style-loader源码简析
作用:把样式插入到DOM中,方法是在head中插入一个style标签,并把样式写入到这个标签的 innerHTML 里 看下源码。
先去掉option处理代码,这样就比较清晰明了了返回一段js代码,通过require来获取css内容,再通过addStyle的方法把css插入到dom里 自己实现一个简陋的style-loader.js
module.exports.pitch = function (request) {
const {stringifyRequest}=loaderUtils
var result = [
//1. 获取css内容。2.// 调用addStyle把CSS内容插入到DOM中(locals为true,默认导出css)
‘var content=require(’ + stringifyRequest(this, ‘!!’ + request) + ')’,
‘require(’ + stringifyRequest(this, ‘!’ + path.join(__dirname, “addstyle.js”)) + ')(content)’,
'if(content.locals) module.exports = content.locals’
]
return result.join(‘;’)
}
需要说明的是,正常我们都会用default的方法,这里用到pitch方法。pitch 方法有一个官方的解释在这里 pitching loader。简单的解释一下就是,默认的loader都是从右向左执行,用 pitching loader
是从左到右执行的。
{
test: /.css$/,
use: [
{ loader: “style-loader” },
{ loader: “css-loader” }
]
}
为什么要先执行style-loader呢,因为我们要把css-loader拿到的内容最终输出成CSS样式中可以用的代码而不是字符串。
addstyle.js
module.exports = function (content) {
let style = document.createElement(“style”)
style.innerHTML = content
document.head.appendChild(style)
}
babel-loader源码简析
首先看下跳过loader的配置处理,看下babel-loader输出上图我们可以看到是输出transpile(source, options)
的code和map 再来看下transpile
方法做了啥babel-loader是通过babel.transform来实现对代码的编译的, 这么看来,所以我们只需要几行代码就可以实现一个简单的babel-loader
const babel = require(“babel-core”)
module.exports = function (source) {
const babelOptions = {
presets: [‘env’]
}
return babel.transform(source, babelOptions).code
}
vue-loader源码简析
vue单文件组件(简称sfc)
{{a}}
webpack配置
const VueloaderPlugin = require(‘vue-loader/lib/plugin’)
module.exports = {
…
module: {
rules: [
…
{
test: /.vue$/,
loader: ‘vue-loader’
}
]
}
plugins: [
new VueloaderPlugin()
]
…
}
VueLoaderPlugin作用:将在webpack.config定义过的其它规则复制并应用到 .vue 文件里相应语言的块中。plugin-webpack4.js
const vueLoaderUse = vueUse[vueLoaderUseIndex]
vueLoaderUse.ident = ‘vue-loader-options’
vueLoaderUse.options = vueLoaderUse.options || {}
// cloneRule会修改原始rule的resource和resourceQuery配置,
// 携带特殊query的文件路径将被应用对应rule
const clonedRules = rules
.filter(r => r !== vueRule)
.map(cloneRule)
// global pitcher (responsible for injecting template compiler loader & CSS
// post loader)
const pitcher = {
loader: require.resolve(‘./loaders/pitcher’),
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
return parsed.vue != null
},
options: {
cacheDirectory: vueLoaderUse.options.cacheDirectory,
cacheIdentifier: vueLoaderUse.options.cacheIdentifier
}
}
// 更新webpack的rules配置,这样vue单文件中的各个标签可以应用clonedRules相关的配置
compiler.options.module.rules = [
pitcher,
…clonedRules,
…rules
]
获取webpack.config.js的rules项,然后复制rules,为携带了?vue&lang=xx…query参数的文件依赖配置xx后缀文件同样的loader 为Vue文件配置一个公共的loader:pitcher 将[pitchLoder, …clonedRules, …rules]作为webapck新的rules。
再看一下vue-loader结果的输出当引入一个vue文件后,vue-loader是将vue单文件组件进行parse,获取每个 block 的相关内容,将不同类型的 block 组件的 Vue SFC 转化成 js module 字符串。
// vue-loader使用@vue/component-compiler-utils
将SFC源码解析成SFC描述符,根据不同 module path 的类型(query 参数上的 type 字段)来抽离 SFC 当中不同类型的 block。
const { parse } = require(‘@vue/component-compiler-utils’)
// 将单个*.vue文件内容解析成一个descriptor对象,也称为SFC(Single-File Components)对象
// descriptor包含template、script、style等标签的属性和内容,方便为每种标签做对应处理
const descriptor = parse({
source,
compiler: options.compiler || loadTemplateCompiler(loaderContext),
filename,
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
ame,
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-H64aucLs-1715709116139)]
[外链图片转存中…(img-dkiFtFsn-1715709116140)]
[外链图片转存中…(img-QzYVExKE-1715709116140)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!