hexh250786313 / blog

hexh 的博客
https://github.com/hexh250786313/blog/issues
40 stars 0 forks source link

father4 预处理编译 less 文件 #30

Open hexh250786313 opened 10 months ago

hexh250786313 commented 10 months ago
不要点开, 博客网站用的
博文标题图片
![pic](https://dev.azure.com/hexuhua/f6126346-6e87-4d62-aa80-ff9b88293af0/_apis/git/repositories/ebd79495-5cbb-4565-8573-fa73ee451b5e/items?path=/github.com/hexh250786313/blog/30/2023-11-18_18-35.png&versionDescriptor%5BversionOptions%5D=0&versionDescriptor%5BversionType%5D=0&versionDescriptor%5Bversion%5D=main&resolveLfs=true&%24format=octetStream&api-version=5.0)
博文置顶说明
本文主要内容为,介绍前端开发中使用 father 进行打包组件库、工具库时,如何预处理编译 less 文件

一、引言

在前端开发中,我们经常会遇到需要对一些组件库进行二次封装的需求,特别是当我们使用阿里系的组件库,如 antd 或 antd-mobile 时。在这种情况下,我们通常会使用 fatherdumi 这一套官方推荐的方案。然而,father 官方并没有提供处理 scss 或 less 的示例,因此我们需要自行实现 father 的样式预处理。本文将介绍如何实现这一目标。

二、father 的工作原理

在深入讨论如何实现样式预处理之前,我们首先需要了解一下 father 在前端打包中的工作原理。

father 不仅可以用于打包前端代码,它也可以用于打包通用的 ts/js 代码库。实际上,father 是一个集成了多个打包工具的打包工具,例如,它集成了 babel 和 esbuild 两种编译器。

当我们需要打包的代码为前端代码(如前端组件库等)时,father 会使用 babel 作为 js/ts 的编译器。当我们需要打包的代码为 node 库时,father 则会使用速度更快的 esbuild。

此外,father 还提供了一些插件功能,例如 extraBabelPluginsaddLoaderextraBabelPlugins 可以让我们使用 babel 插件,而 addLoader 功能则类似于 webpack 的 loaders 功能,可以用于处理非 js/ts 文件。

三、实现样式预处理

了解了 father 的工作原理后,我们就可以开始实现样式预处理了。我们的任务可以分为两部分:

  1. addLoader 功能,实现一个 less 转 css 的插件。
  2. 使用 extraBabelPlugins,实现一个插件,将 js/ts 中的 .less 文件内容转为 .css

接下来,我们将详细介绍如何实现这两个任务。

3.1. 第一步、实现 babel 插件

这一步主要是为了把 js/ts 文件中的与 less 相关的文本替换为 css,例如:import "./index.less" 替换为 import "./index.css"

这一步比较简单,直接实现一个 babel 插件用于进行 .less 替换为 .css 的文本操作即可,代码如下:

// .fatherrc.ts
import { defineConfig } from 'father';

export default defineConfig({

    ...

    esm: { output: 'dist', transformer: 'babel' }, // 必须要使用 babel 模式
    extraBabelPlugins: [
        [
            './babel-less-to-css.js', // 把 js/ts 文件中的 '.less' 字符转为 '.css'
            {
                test: '\\.less',
            },
        ],
    ],

    ...

});
// babel-less-to-css.js
module.exports = function () {
    return {
        visitor: {
            ImportDeclaration(path) {
                if (/\.less$/.test(path.node.source.value)) {
                    path.node.source.value = path.node.source.value.replace(/\.less/, '.css');
                }
            },
        },
    };
};

3.2. 第二步、实现 loader 插件

这一步要实现一个把 less 文件转换为 css 文件的插件,其中:

  1. 使用 less.js 来进行对 less 到 css 的转义。
  2. 使用 postcss 来处理 less 中的相对路径和别名路径的处理以及处理浏览器兼容问题。

这两步本身其实已经和 father 没什么太大的关系,都是 webpack 中常用的对 less 文件的处理方法,所以这里直接给出示例代码,看代码就可以知道如何在 father 中实现这些功能:

// .fatherrc.ts
import { defineConfig } from 'father';

export default defineConfig({

    ...

    plugins: [
        './loader.ts', // 实现 loader 功能
    ],

    ...

});
// loader.ts
import type { IApi } from 'father';
import { addLoader, ILoaderItem } from 'father/dist/builder/bundless/loaders';

export default async (api: IApi) => {
    const loaders: ILoaderItem[] = await api.applyPlugins({
        key: 'addPostcssLoader',
        initialValue: [
            {
                key: 'less-to-css',
                test: /\.less$/,
                loader: require.resolve('./loader-less-to-css'), // less 文件转 css 文件
            },
        ],
    });

    loaders.forEach((loader) => addLoader(loader));
};
// loader-less-to-css.js
const path = require('path');
const less = require('less');
const postcss = require('postcss');
const syntax = require('postcss-less');
const atImport = require('postcss-import');
const autoprefixer = require('autoprefixer');

const loader = function (lessContent) {
    const cb = this.async();
    this.setOutputOptions({
        ext: '.css',
    });
    postcss([
        autoprefixer({
            // 提升兼容性
            overrideBrowserslist: ['last 10 versions'],
        }),
        atImport({
            resolve: (id) => {
                const currentPath = this.resource;
                if (id.startsWith('@')) {
                    // 处理别名路径,把 @ 替换成 src
                    const srcPath = path.join(__filename, './src');
                    const targetPath = id.replace(/^@/, srcPath);
                    return targetPath;
                } else {
                    // 处理相对路径
                    const relativePath = id;
                    const targetPath = path.resolve(currentPath, '..', relativePath);
                    return targetPath;
                }
            },
        }),
    ])
        .process(lessContent, { syntax })
        .then((result) => {
            // less 转 css
            less.render(result.content, (err, css) => {
                if (err) {
                    console.error(err);
                    return;
                }
                cb(null, css.css);
            });
        })
        .catch((err) => {
            console.error(err);
        });
};

module.exports = loader;

经过上述处理,就可以实现 less 的预处理,编译为 css 文件了。

四、补充

4.1 使用 babel 的原因

事实上 father 支持 babel、esbuild 和 SWC 三种构建方式,而本文使用的是 babel 模式。

[father 的配置文档]() 中提到,platformbroswer 的时候,transformer 默认使用 babel 进行 js 的编译器,这意味着,father 官方也是推荐使用 babel 来编译前端的代码的。

即使使用 esbuild 有更快的打包速度,但是 esbuild 处理的是文件的二进制格式,很多现存的前端编译插件无法直接应用到其中;而 babel 则是生成 AST 语法树,其兼容性和拓展性也是 esbuild 和 SWC 无法比拟的。

不过最关键的还是:father 还没有实现自定义 esbuild 插件!它只提供了 babel 的自定义插件能力,这样一来其实我们别无选择。

Lands-1203 commented 6 months ago

请问怎么修改

extraBabelPlugins: [
    [
      './babel-less-to-css.js', // 把 js/ts 文件中的 '.less' 字符转为 '.css'
      {
        test: '\\.less',
      },
    ],
  ],
  plugins: [
    'less-to-css', // 将 less 文件转换为 css 文件
  ],

让他直接转换呢? 不太想自己写loader

hexh250786313 commented 6 months ago

@Lands-1203 extraBabelPlugins 可以直接使用通用的 babel 插件,但是我估计没有相关的这种把 ".css" 变为 ".less" 的插件,而且 babel 只能处理 js/ts 代码,处理 less 只能利用 loader,而 loader 就只能通过我上面的方式来写,我还没找到其他什么好方法

ngwszsd commented 4 months ago

你好 我在做一个基于antd的组件库,我现在需要在father.ts配置一个打包后把antd的类名前缀ant-设置成其他比如xlb-,由于我现在不能全局在Provide配置,因为要打不同的配置包,知道如何解决吗大佬

hexh250786313 commented 4 months ago

@ngwszsd

由于我现在不能全局在Provide配置,因为要打不同的配置包

没看懂啥意思