text: /**
   * @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)))
          );
      };
  }
  