less应用

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2022-02-19

vite+vue3全局定义less变量在单文件中使用

export default defineConfig({
  css: {
    preprocessorOptions: {
      less: {
        globalVars: {
          hack: `true; @import (reference) "${path.resolve('src/styles/vars.less')}";`
        },
        javascriptEnabled: true
      }
    }
  }
})

相当于使用增加全局变量的方式,加入了这样一段话:

@hack: true; @import (reference) "src/styles/vars.less";

相当于import进来一个变量文件。

所以这里的hack属性可以用任意变量和属性来代替,比如

theme: "red; @import (reference) 'src/styles/vars.less';"

@import (reference) 代表着less中import导入时的采用 reference 选项,使用文件,但不会输出其内容(即,文件作为样式库使用)。

  • reference: 使用文件,但不会输出其内容(即,文件作为样式库使用)
  • inline: 对文件的内容不作任何处理,直接输出
  • less: 无论文件的扩展名是什么,都将作为LESS文件被输出
  • css: 无论文件的扩展名是什么,都将作为CSS文件被输出
  • once: 文件仅被导入一次 (这也是默认行为)
  • multiple: 文件可以被导入多次
  • optional: 当文件不存在时,继续编译(即,该文件是可选的)

编写插件来实现全局变量

思路是一样的,通过在在每个less文件中添加import语句。

// my-plugin.ts
export default function myPlugin(options: any): any {
  return {
    name: 'transform-file',
    enforce: 'pre',
    // apply: 'build',
    transform(src, id): any {
      if (id.endsWith('less')) {
        const code = `@import '${options.varsFile}'; ${src}`;
        return {
          code,
        };
      }
      return undefined;
    },
  };
}

vite.config.js 中这样使用:

import { defineConfig } from 'vite';
import myPlugin from './my-plugin';

export default defineConfig({
  plugins: [vue(), myPlugin({
    varsFile: './src/styles/vars.less',
  })],
});
  • name: 插件名称,必须的,将会在 warning 和 error 中显示
  • enforce: 插件的执行时机,‘pre’会在Vite核心插件之前执行,更多详情查看插件顺序
  • apply: 默认情况下插件在开发(serve)和构建(build)模式中都会调用。使用 apply 属性指明它们仅在 ‘build’ 或 ‘serve’ 模式时调用:
  • transform: 插件钩子,transform会在文件加载完成之后执行,更多详情查看通用钩子

使用less编译多套主题

基本样式文件

// style.less
div {
  color: @c1;
}

主题样式文件

// ./themes/red.less
@import (multiple) './style.less';
@c1: red;
// ./themes/blue.less
@import (multiple) './style.less';
@c1: blue;

需要注意的是,使用 @import 需要增加 multiple 选项,否则只会编译出一份style的样式,下面这样生成多套主题时就会有问题了。

// main.less
.body-red {
  @import './themes/red.less';
};

.body-blue {
  @import './themes/blue.less';
};

编译 lessc main.less main.css 就会得到多套主题的样式如下:

/* theme.css */
.body-red div {
  color: red;
}

.body-blue div {
  color: blue;
}

编写vite插件实现多套主题编译

import type { Plugin, IndexHtmlTransformResult } from 'vite';

export interface MultiThemeOptions {
  /** 多主题变量所在目录 */
  dir: string;
  /** 需要打包哪些主题 */
  themes: string[];
}

/**
 * 如果有的组件完全没有使用到主题变量可以避免进行多皮肤代码转换 在less文件前采用注释/* no-theme-vars *\/
 */
export default function multiTheme(options: MultiThemeOptions): Plugin {
  return {
    name: 'multiTheme',
    enforce: 'pre',
    transform(src, id): any {
      let code = '';
      if (id.endsWith('less')) {
        const { themes } = options;
        if (src.trim().startsWith('/* no-theme-vars */') || !themes.length) {
          return { code: src };
        }
        // 多套皮肤
        if (themes.length > 1) {
          themes.forEach((theme) => {
            const sentence = `@import '${options.dir}${theme}.less';\n${src}`;
            const template = `[theme=${theme}] {\n  ${sentence}\n};\n\n`;
            code += template;
          });
        } else if (themes.length === 1) { // 单套皮肤
          const theme = themes[0];
          code = `@import '${options.dir}${theme}.less';\n${src}`;
        }

        return {
          code,
        };
      }
      return undefined;
    },
    /**
     * 为html增加对应的主题自定义属性
     */
    transformIndexHtml(html: string): IndexHtmlTransformResult | void | Promise<IndexHtmlTransformResult | void> {
      if (options.themes.length > 1) {
        const reg = /<html(.*)>/g;
        return html.replace(reg, `<html $1 theme=${options.themes[0]}>`);
      }
      return html;
    },
  };
}

export function setTheme(theme: string): void {
  document.documentElement.setAttribute('theme', theme);
}

export function getTheme(): string {
  return document.documentElement.getAttribute('theme') || '';
}

vite.config.js中这样使用:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import multiTheme from './my-plugin';

export default defineConfig({
  plugins: [vue(), multiTheme({
    dir: './src/styles/themes/', // 指定less样式变量文件所在的目录
    themes: ['red', 'blue'], // 指定需要处理哪些主题样式
  })],
});

该插件中transform会在文件加载完成后执行,由于设置enforcepre,所以执行时机会在Vite核心插件在编译less之前,这个时候将less文件做更改,将源文件包括在对应主题选择器之下并引入对应的样式变量文件,具体如下:

div {
  color: @c1;
}
span {
  color: chocolate;
}

// 将上面的less代码转换成如下的less代码

[theme=red] {
  @import './src/styles/red.less';
  div {
    color: @c1;
  }
  span {
    color: chocolate;
  }
};

[theme=blue] {
  @import './src/styles/blue.less';
  div {
    color: @c1;
  }
  span {
    color: chocolate;
  }
};

之后,将更改后的less文件通过 { code: 'xxx' } 的形式交给后面的插件来处理,并有Vite核心插件帮我们完成编译,最后打包出来的css文件如下:

[theme=red] div[data-v-a6a41576] {
  color: red
}

[theme=red] span[data-v-a6a41576] {
  color: #d2691e
}

[theme=blue] div[data-v-a6a41576] {
  color: #00f
}

[theme=blue] span[data-v-a6a41576] {
  color: #d2691e
}

编写webpack loader实现多套主题编译

简易版,原理一样。

const loaderUtils = require('loader-utils');

module.exports = function(source, map) {
  const options = loaderUtils.getOptions(this);
  const { dir, themes }  = options;
  let code = '';
  themes.forEach((theme) => {
    const sentence = `@import '${dir}${theme}.less';\n${source}`;
    const template = `[theme=${theme}] {\n  ${sentence}\n};\n\n`;
    code += template;
  });

  return code; 
}

使用

module.exports = {
  configureWebpack: {
    resolveLoader: {
      modules: ['node_modules','./my-loader/'],
    },
  },
  chainWebpack: config => {
    config.module
      .rule('less')
      .oneOf('vue')
      .test(/\.less$/)
      .use('my-less-loader')
        .loader('my-less-loader')
        .options({
          dir: '/src/styles/themes/', // 指定less样式变量文件所在的目录
          themes: ['default', 'red'], // 指定需要处理哪些主题样式  
        })
        .after('less-loader')
  },
}