san-cli-markdown-loader
Version:
san markdown loader
149 lines (132 loc) • 5.01 kB
JavaScript
/**
* @file webpack 4 版本 plguin,添加 san-md 拦截
* @author ksky521
*/
const qs = require('querystring');
const RuleSet = require('webpack/lib/RuleSet');
const id = 'san-cli-markdown-loader-plugin';
const {NS, isSanLoader} = require('./const');
class LoaderPlugin {
apply(compiler) {
if (compiler.hooks) {
// webpack 4
compiler.hooks.compilation.tap(id, compilation => {
let normalModuleLoader;
if (Object.isFrozen(compilation.hooks)) {
// webpack 5
normalModuleLoader = require('webpack/lib/NormalModule').getCompilationHooks(compilation).loader;
}
else {
normalModuleLoader = compilation.hooks.normalModuleLoader;
}
normalModuleLoader.tap(id, loaderContext => {
// 这个是为了在loader里面读取this的ns,然后检测plugin是否使用!!!
loaderContext[NS] = true;
});
});
}
else {
// webpack < 4
compiler.plugin('compilation', compilation => {
compilation.plugin('normal-module-loader', loaderContext => {
loaderContext[NS] = true;
});
});
}
const rawRules = compiler.options.module.rules;
const {rules} = new RuleSet(rawRules);
let sanRuleIndex = rawRules.findIndex(createMatcher('foo.san'));
const sanRule = rules[sanRuleIndex];
if (!sanRule) {
throw new Error(
/* eslint-disable operator-linebreak */
'[SanMarkdownLoaderPlugin Error] No matching rule for .san files found.\n' +
'Make sure there is at least one root-level rule that matches .san files.'
);
}
if (sanRule.oneOf) {
/* eslint-disable max-len */
throw new Error(
'[SanMarkdownLoaderPlugin Error] san-cli-markdown-loader currently does not support san rules with oneOf.'
);
}
const sanUse = sanRule.use;
// 获取 loader 配置
const sanUseIdx = sanUse.findIndex(u => {
return isSanLoader({path: u.loader});
});
if (sanUseIdx < 0) {
throw new Error(
'[SanMarkdownLoaderPlugin Error] No matching use for san-loader is found.\n' +
'Make sure the rule matching .san files include san-loader in its use.'
);
}
// TODO 删掉
const mdLoaderIdx = rawRules.findIndex(createMatcher('foo.md'));
const mdLoader = rules[mdLoaderIdx];
const mdResource = mdLoader.resource;
const picker = {
loader: require.resolve('./picker'),
resource(p) {
if (mdResource && typeof mdResource === 'function') {
return mdResource(p);
}
return true;
},
resourceQuery(query) {
const parsed = qs.parse(query.slice(1));
return parsed['san-md-picker'] != null;
},
options: {}
};
// 这个san-loader是跟picker配合使用的
const clonedRule = cloneRule(sanRule);
// 剔除md loader,移到picker之后
rules.splice(mdLoaderIdx, 1);
// 添加新的 rule,利用loader picker 顺序优先处理`?san-md-picker`的情况
// 将?san-md-picker实际处理转到 picker.js 中
// loader顺序说明:
// 1. picker + san-loader会拦截 san-md-picker,所以需要保证picker出来的是 san file
// 2. md-loader 会放行 san-md-picker,md-loader保证出来的是html(html-loader)和 js
compiler.options.module.rules = [...rules, mdLoader, clonedRule, picker];
}
}
LoaderPlugin.NS = NS;
module.exports = LoaderPlugin;
function cloneRule(rule) {
return _cloneRule(
rule,
resource => {
return true;
},
query => {
const parsed = qs.parse(query.slice(1));
return parsed['san-md-picker'] != null;
}
);
}
function _cloneRule(rule, test, resourceQuery) {
const res = Object.assign({}, rule, {
resource: {
test
},
resourceQuery
});
if (rule.oneOf) {
res.oneOf = rule.oneOf.map(rule => _cloneRule(rule, test, resourceQuery));
}
return res;
}
function createMatcher(fakeFile) {
return (rule, i) => {
const clone = Object.assign({}, rule);
delete clone.include;
const normalized = RuleSet.normalizeRule(clone, {}, '');
return (
!rule.enforce &&
normalized.resource &&
normalized.resource(fakeFile) &&
(!normalized.resourceQuery || (normalized.resourceQuery && normalized.resourceQuery(fakeFile)))
);
};
}