Bootstrap

clsx源码阅读分析

clsx源码

版本 2.1.1

一个处理类名的函数。

地址:https://github.com/lukeed/clsx/blob/master/src/index.js

源码文件index.js

文件 src/index.js

//src/index.js
function toVal(mix) {
  var k,
    y,
    str = "";

  // 判断参数min类型, 字符串或数值类型 直接 拼接
  if (typeof mix === "string" || typeof mix === "number") {
    str += mix;
  } else if (typeof mix === "object") {
    // 数组类型
    if (Array.isArray(mix)) {
      var len = mix.length;
      for (k = 0; k < len; k++) {
        // 为真 才递归调用toVal函数处理
        if (mix[k]) {
          if ((y = toVal(mix[k]))) {
            str && (str += " ");
            str += y;
          }
        }
      }
    } else {
      // 对象类型
      for (y in mix) {
        // 为真 才拼接
        if (mix[y]) {
          str && (str += " ");
          str += y;
        }
      }
    }
  }

  return str;
}

export function clsx() {
  var i = 0,
    tmp,
    x,
    str = "",
    len = arguments.length;

  // 参数遍历
  for (; i < len; i++) {
    if ((tmp = arguments[i])) {
      // 处理参数, 最后都是拼接成字符串
      if ((x = toVal(tmp))) {
        str && (str += " ");
        str += x;
      }
    }
  }
  return str;
}

export default clsx;

源码文件lite.js

只处理字符串。

//src/lite.js
export function clsx() {
  // 定义声明变量
  var i = 0,
    tmp,
    str = "",
    len = arguments.length;

  // 参数遍历
  for (; i < len; i++) {
    if ((tmp = arguments[i])) {
      // 当参数为字符串时处理,拼接字符串
      if (typeof tmp === "string") {
        str += (str && " ") + tmp;
      }
    }
  }
  return str;
}

export default clsx;

使用

import clsx from "clsx";
// or
import { clsx } from "clsx";

// Strings (variadic)
clsx("foo", true && "bar", "baz");
//=> 'foo bar baz'

// Objects
clsx({ foo: true, bar: false, baz: isTrue() });
//=> 'foo baz'

// Objects (variadic)
clsx({ foo: true }, { bar: false }, null, { "--foobar": "hello" });
//=> 'foo --foobar'

// Arrays
clsx(["foo", 0, false, "bar"]);
//=> 'foo bar'

// Arrays (variadic)
clsx(["foo"], ["", 0, false, "bar"], [["baz", [["hello"], "there"]]]);
//=> 'foo bar baz hello there'

// Kitchen sink (with nesting)
clsx(
  "foo",
  [1 && "bar", { baz: false, bat: null }, ["hello", ["world"]]],
  "cya"
);
//=> 'foo bar hello world cya'

打包分析

{
  "name": "clsx",
  "version": "2.1.1",
  "repository": "lukeed/clsx",
  "description": "A tiny (239B) utility for constructing className strings conditionally.",
  "module": "dist/clsx.mjs",
  "unpkg": "dist/clsx.min.js",
  "main": "dist/clsx.js",
  "types": "clsx.d.ts",
  "license": "MIT",
  "exports": {
    //条件加载,利用.这个别名
    ".": {
      "import": {
        "types": "./clsx.d.mts",
        //default条件指定其他情况的入口(即 ES6 的入口)。
        "default": "./dist/clsx.mjs"
      },
      "default": {
        "types": "./clsx.d.ts",
        "default": "./dist/clsx.js"
      }
    },
    "./lite": {
      "import": {
        "types": "./clsx.d.mts",
        "default": "./dist/lite.mjs"
      },
      "default": {
        "types": "./clsx.d.ts",
        "default": "./dist/lite.js"
      }
    }
  },
  "author": {
    "name": "Luke Edwards",
    "email": "[email protected]",
    "url": "https://lukeed.com"
  },
  "engines": {
    "node": ">=6"
  },
  "scripts": {
    "build": "node bin",
    "test": "uvu -r esm test"
  },
  "files": ["*.d.mts", "*.d.ts", "dist"],
  "keywords": ["classes", "classname", "classnames"],
  "devDependencies": {
    "esm": "3.2.25",
    "terser": "4.8.0",
    "uvu": "0.5.4"//UVU是Node.js和浏览器的一个非常快速和轻量级的测试运行器
  }
}

main属性

定义这个 package 的入口文件位置。

在不支持 ES6 的 Nodejs 中,它指向的就是CommonJs模块系统的入口位置。

在 Nodejs 支持 ES6 的版本中,只要显示定义 "type": "module",那么它指向的就是 ESM 模块系统的入口位置

exports属性

export的使用:https://es6.ruanyifeng.com/#docs/module-loader#package-json-%E7%9A%84-exports-%E5%AD%97%E6%AE%B5

exports 字段可以配置不同环境对应的模块入口文件,并且当他存在时,它的优先级最高,当package.json文件中存在 exports 字段,设置的 main 字段会失效。exports 字段的优先级高于 main 字段。

示例

利用.这个别名,可以为 ES6 模块和 CommonJS 指定不同的入口。

{
  "type": "module",
  "exports": {
    ".": {
      "require": "./main.cjs",
      "default": "./main.js"
    }
  }
}

上面代码中,别名.require条件指定require()命令的入口文件(即 CommonJS 的入口),default条件指定其他情况的入口(即 ES6 的入口)。

执行打包

命令


{
    "scripts": {
        "build": "node bin",
        "test": "uvu -r esm test"
    },
}

bin/index.js文件

// @ts-check
const fs = require("fs");
// zlib 模块提供了使用 Gzip、Deflate/Inflate、以及 Brotli 实现的压缩功能。
const zlib = require("zlib");

// 一个用于ES6+的JavaScript管理器/压缩器工具包。
const { minify } = require("terser");
const pkg = require("../package.json");


// file 文件的路径 , source文件的内容
function write(file, source) {
  let isModule = !source.startsWith("!function");

  // 一个可配置的高级函数async minify(code, options),它将以可配置的方式执行所有缩小阶段。默认情况下,minify()将启用compress和mangle。
  let result = minify(source, {
    module: isModule,
    compress: true,
  });
// console.log(result.code);  // minified output: function add(n,d){return n+d}
// console.log(result.map);  // source map

  if (result.code) {
    fs.writeFileSync(file, result.code);
    // zlib.gzipSync压缩数据块   byteLength获取字节大小
    let size = zlib.gzipSync(result.code).byteLength;
    console.log('~> "%s" (%d b)', file, size);
  } else {
    console.error('!! "%s" ::', file, result.error);
  }
}

// file文件名称,entry文件别名
function bundle(file, entry) {
  // 判断是否存在文件夹dis,不存在则创建
  fs.existsSync("dist") || fs.mkdirSync("dist");

  //pkg 即package.json默认导出的对象
  let output = pkg.exports[entry];

  // 读取文件
  let input = fs.readFileSync(file, "utf8");

  // copy for ESM file
  write(output.import.default, input);

  // transform ESM -> CJS exports
  write(
    output.default.default,
    input
      .replace("export function", "function")
      .replace(
        "export default clsx;",
        "module.exports = clsx;\n" + "module.exports.clsx = clsx;"
      )
  );

  if (entry === ".") {
    // transform ESM -> UMD exports
    input = input
      .replace("export function", "function")
      .replace("export default clsx;", "return clsx.clsx=clsx, clsx;");

      // 写入文件
    write(
      pkg.unpkg,
      '!function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory():"function"==typeof define&&define.amd?define(factory):global.clsx=factory()}(this,function(){' +
        input +
        "});"
    );
  }
}

bundle("src/index.js", ".");

bundle("src/lite.js", "./lite");

package.josn文件中的exports配置信息


{
  "exports": {
    ".": {
      "import": {
        "types": "./clsx.d.mts",
        "default": "./dist/clsx.mjs"
      },
      "default": {
        "types": "./clsx.d.ts",
        "default": "./dist/clsx.js"
      }
    },
    "./lite": {
      "import": {
        "types": "./clsx.d.mts",
        "default": "./dist/lite.mjs"
      },
      "default": {
        "types": "./clsx.d.ts",
        "default": "./dist/lite.js"
      }
    }
  }
}

npm install过程

首先安装的依赖都会存放在根目录的node_modules,默认采用扁平化的方式安装,并且排序规则.bin第一个,然后@系列,再然后按照首字母排序abcd等,并且使用的算法是广度优先遍历,在遍历依赖树时,npm会首先处理项目根目录下的依赖,然后逐层处理每个依赖包的依赖,直到所有依赖都被处理完毕。在处理每个依赖时,npm会检查该依赖的版本号是否符合依赖树中其他依赖的版本要求,如果不符合,则会尝试安装适合的版本。

扁平化的理想状态

APP————A模块(c1.0)

APP————B模块(c1.0)

安装某个二级模块时,若发现第一层级有相同名称,相同版本的模块,便直接复用那个模块

因为A模块下的C模块被安装到了第一级,这使得B模块能够复用处在同一级下;且名称,版本,均相同的C模块

扁平化的非理想状态

APP————A模块(c1.0)

APP————B模块(c2.0)

因为B和A所要求的依赖模块不同,(B下要求是v2.0的C,A下要求是v1.0的C )所以B不能像2中那样复用A下的C v1.0模块 所以如果这种情况还是会出现模块冗余的情况,他就会给B继续搞一层node_modules,就是非扁平化了。

uvu插件

UVU是Node.js和浏览器的一个非常快速和轻量级的测试运行器.

# via `uvu` cli, for all `/tests/**` files
$ uvu -r esm tests

# via `node` directly, for file isolation
$ node -r esm tests/demo.js
;