解决postcss-px-to-viewport-8-plugin 设置include不生效的问题

248 阅读4分钟

postcss-px-to-viewport-8-plugin文档中API参数说明

参数说明类型默认值
unitToConvert需要转换的单位,默认为 pxstringpx
viewportWidth设计稿的视口宽度,如传入函数,函数的参数为当前处理的文件路径,函数返回 undefind 跳过转换`numberFunction`320
unitPrecision单位转换后保留的精度number5
propList能转化为 vw 的属性列表string[]['*']
viewportUnit希望使用的视口单位stringvw
fontViewportUnit字体使用的视口单位stringvw
selectorBlackList需要忽略的 CSS 选择器,不会转为视口单位,使用原有的 px 等单位string[][]
minPixelValue设置最小的转换数值,如果为 1 的话,只有大于 1 的值会被转换number1
mediaQuery媒体查询里的单位是否需要转换单位booleanfalse
replace是否直接更换属性值,而不添加备用属性booleantrue
landscape是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)booleanfalse
landscapeUnit横屏时使用的单位stringvw
landscapeWidth横屏时使用的视口宽度,,如传入函数,函数的参数为当前处理的文件路径,函数返回 undefind 跳过转换number568
exclude忽略某些文件夹下的文件或特定文件,例如 node_modules 下的文件,如果值是一个正则表达式,那么匹配这个正则的文件会被忽略,如果传入的值是一个数组,那么数组里的值必须为正则Regexpundefined
include需要转换的文件,例如只转换 'src/mobile' 下的文件 (include: //src/mobile//),如果值是一个正则表达式,将包含匹配的文件,否则将排除该文件, 如果传入的值是一个数组,那么数组里的值必须为正则Regexpundefined

按照文档中说明配置调试了很久,发现所有页面都会生效,不仅仅是include里的页面

  plugins: [
    // 使用 postcss-px-to-viewport-8-plugin 处理大部分页面
    require('postcss-px-to-viewport-8-plugin')({
      unitToConvert: 'px', // 要转化的单位
      unitPrecision: 4, // 转换后的精度,即小数点位数
      propList: ['*', '!font-size','!width', '!height', '!background-position'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
      viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
      fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
      selectorBlackList: ['ignore-', 'ige'], // 指定不转换为视窗单位的类名,
      minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
      mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
      replace: true, // 是否转换后直接更换属性值
      include: [/\/src\/pages\/test-vw\//],
      landscape: false, // 是否处理横屏情况
      viewportWidth: 375 // 直接使用数字值
    })
  ]
};

查看源代码发现作者并没有实现include相关的逻辑,使用时注意避坑

image.png

const postcssPxToViewport = (options: OptionType) => {
  const opts = objectAssign({}, defaults, options);

  const pxRegex = getUnitRegexp(opts.unitToConvert);
  const satisfyPropList = createPropListMatcher(opts.propList);
  const landscapeRules: AtRule[] = [];

  return {
    postcssPlugin: 'postcss-px-to-viewport',
    Once(css: Root, { result }: { result: any }) {
      // @ts-ignore 补充类型
      css.walkRules((rule: RuleType) => {
        // Add exclude option to ignore some files like 'node_modules'
        const file = rule.source?.input.file || '';
        if (opts.exclude && file) {
          if (Object.prototype.toString.call(opts.exclude) === '[object RegExp]') {
            if (isExclude(opts.exclude as RegExp, file)) return;
          } else if (
            // Object.prototype.toString.call(opts.exclude) === '[object Array]' &&
            opts.exclude instanceof Array
          ) {
            for (let i = 0; i < opts.exclude.length; i++) {
              if (isExclude(opts.exclude[i], file)) return;
            }
          } else {
            throw new Error('options.exclude should be RegExp or Array.');
          }
        }

        if (blacklistedSelector(opts.selectorBlackList, rule.selector)) return;

        if (opts.landscape && !rule.parent?.params) {
          const landscapeRule = rule.clone().removeAll();
          rule.walkDecls((decl) => {
            if (decl.value.indexOf(opts.unitToConvert) === -1) return;
            if (!satisfyPropList(decl.prop)) return;
            let landscapeWidth;
            if (typeof opts.landscapeWidth === 'function') {
              const num = opts.landscapeWidth(file);
              if (!num) return;
              landscapeWidth = num;
            } else {
              landscapeWidth = opts.landscapeWidth;
            }

            landscapeRule.append(
              decl.clone({
                value: decl.value.replace(
                  pxRegex,
                  createPxReplace(opts, opts.landscapeUnit, landscapeWidth),
                ),
              }),
            );
          });

          if (landscapeRule.nodes.length > 0) {
            landscapeRules.push((landscapeRule as unknown) as AtRule);
          }
        }

        if (!validateParams(rule.parent?.params, opts.mediaQuery)) return;

        rule.walkDecls((decl, i) => {
          if (decl.value.indexOf(opts.unitToConvert) === -1) return;
          if (!satisfyPropList(decl.prop)) return;

          const prev = decl.prev();
          // prev declaration is ignore conversion comment at same line
          if (prev && prev.type === 'comment' && prev.text === ignoreNextComment) {
            // remove comment
            prev.remove();
            return;
          }
          const next = decl.next();
          // next declaration is ignore conversion comment at same line
          if (next && next.type === 'comment' && next.text === ignorePrevComment) {
            if (/\n/.test(next.raws.before!)) {
              result.warn(
                `Unexpected comment /* ${ignorePrevComment} */ must be after declaration at same line.`,
                { node: next },
              );
            } else {
              // remove comment
              next.remove();
              return;
            }
          }

          let unit;
          let size;
          const { params } = rule.parent;

          if (opts.landscape && params && params.indexOf('landscape') !== -1) {
            unit = opts.landscapeUnit;

            if (typeof opts.landscapeWidth === 'function') {
              const num = opts.landscapeWidth(file);
              if (!num) return;
              size = num;
            } else {
              size = opts.landscapeWidth;
            }
          } else {
            unit = getUnit(decl.prop, opts);
            if (typeof opts.viewportWidth === 'function') {
              const num = opts.viewportWidth(file);
              if (!num) return;
              size = num;
            } else {
              size = opts.viewportWidth;
            }
          }

          const value = decl.value.replace(pxRegex, createPxReplace(opts, unit!, size));

          if (declarationExists((decl.parent as unknown) as ParentExtendType[], decl.prop, value))
            return;

          if (opts.replace) {
            decl.value = value;
          } else {
            decl.parent?.insertAfter(i, decl.clone({ value }));
          }
        });
      });

      // if (landscapeRules.length > 0) {
      //   const landscapeRoot = new AtRule({
      //     params: '(orientation: landscape)',
      //     name: 'media',
      //   });

      //   landscapeRules.forEach((rule) => {
      //     landscapeRoot.append(rule);
      //   });
      //   css.append(landscapeRoot);
      // }
    },
    // https://www.postcss.com.cn/docs/writing-a-postcss-plugin
    // Declaration Rule RuleExit OnceExit
    // There two types or listeners: enter and exit.
    // Once, Root, AtRule, and Rule will be called before processing children.
    // OnceExit, RootExit, AtRuleExit, and RuleExit after processing all children inside node.
    OnceExit(css: Root, { AtRule }: { AtRule: any }) {
      // 在 Once里跑这段逻辑,设置横屏时,打包后到生产环境竖屏样式会覆盖横屏样式,所以 OnceExit再执行。
      if (landscapeRules.length > 0) {
        const landscapeRoot = new AtRule({
          params: '(orientation: landscape)',
          name: 'media',
        });

        landscapeRules.forEach(function(rule) {
          landscapeRoot.append(rule);
        });
        css.append(landscapeRoot);
      }
    },
  };
};

曲线救国方案, 通过设置exclude 在正则表达式中使用?!负向前瞻断言 来化解

  plugins: [
    // 使用 postcss-px-to-viewport-8-plugin 处理大部分页面
    require('postcss-px-to-viewport-8-plugin')({
      unitToConvert: 'px', // 要转化的单位
      unitPrecision: 4, // 转换后的精度,即小数点位数
      propList: ['*', '!font-size','!width', '!height', '!background-position'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
      viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
      fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
      selectorBlackList: ['ignore-', 'ige'], // 指定不转换为视窗单位的类名,
      minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
      mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
      replace: true, // 是否转换后直接更换属性值
      exclude: [
        /\/src\/pages\/(?!test-a|page-a)/,
        
        // 排除第三方库
        /node_modules\//
      ],
      landscape: false, // 是否处理横屏情况
      viewportWidth: 375 // 直接使用数字值
    })
  ]
};