这里有从零开始构建现代化前端UI组件库所需要的一切(二)

《这里有从零开始构建现代化前端UI组件库所需要的一切(一)》中,我们已经确定了项目的组织策略和构建工具(Monorepopnpm Workspace&turborepo),也为组件提供了一个专业的开发环境(Storybook),接下来我们将为组件库添加代码打包的功能。

打包工具

前端开发中,有很多打包工具可以选择,它们用于将源代码转换、优化,并生成可在浏览器中运行的静态资源。以下是一些常见的前端打包工具:

  1. Webpack
    • 特点: 强大的模块打包工具,支持各种资源的打包和优化。
    • 适用场景: 适用于大型项目,复杂的模块依赖,以及需要处理多种资源类型的应用。具有丰富的插件生态,支持热模块替换(HMR)。
  2. Rollup
    • 特点: 针对 ES6 模块的打包工具,专注于打包 JavaScript 库,生成更小的代码包。
    • 适用场景: 适用于构建库或组件,提供 Tree-shaking 功能,减小输出文件大小。对于发布给其他开发者使用的库,Rollup 是一个不错的选择。
  3. Gulp
    • 特点: 自动化构建工具,基于任务流的方式执行各种构建任务。
    • 适用场景: 适用于多任务、多步骤的构建流程,可集成各类插件。Gulp 提供了灵活的配置和插件系统,使得可以处理各种构建需求,例如文件压缩、图片优化、CSS 合并等。
  4. esbuild
    • 特点: 极快的 JavaScript 打包器和构建工具,以速度著称。
    • 适用场景: 适用于快速构建、打包 JavaScript 项目,支持 TypeScript。esbuild 的快速编译速度使其在开发和构建过程中表现出色。
  5. tsup
    • 特点: 针对 TypeScript 项目的零配置打包工具,基于 esbuild。
    • 适用场景: 适用于简单的 TypeScript 项目,提供简洁的开发体验。tsup 的零配置设计使得 TypeScript 项目的打包变得非常简单,同时结合 esbuild 的性能,具备快速的构建速度。
  6. Snowpack
    • 特点: 非常快速的构建工具,支持 ESM(ES Modules)直接在浏览器中运行。
    • 适用场景: 适用于现代前端开发,以及需要零配置和快速开发的项目。Snowpack 在开发环境中提供了零延迟的模块热替换。
  7. Parcel
    • 特点: 零配置的打包工具,支持多种资源类型,自动化处理依赖。
    • 适用场景: 适用于快速搭建简单项目,无需繁琐的配置。它的零配置特性使得初学者和小型项目能够更快速地上手。

每个工具都有其独特的优势和适用场景。选择合适的打包工具通常取决于项目的规模、技术栈和开发团队的偏好。但是...

那么我们直接打开终端运行pnpm add tsup -D -w,安装好tsup之后我们开始为项目加上对应打包的配置:
还是补充一下选择 tsup 的理由吧 😃:

  1. 零配置设计: tsup 提供了零配置的设计,减少了繁琐的配置过程,使得项目能够更快速上手。这对于简单的 TypeScript 项目而言是一个优势,降低了学习曲线。
  2. esbuild 驱动: tsup 基于 esbuild,这是一个非常快速的打包工具。这意味着构建速度会很快,使得开发者在修改代码后能够更快地看到变更生效。
  3. 适用于简单项目: 如果项目相对简单,不需要复杂的配置和多任务构建流程,tsup 提供了一种简便而高效的构建选择。
  4. 快速迭代: tsup 的快速构建速度支持快速迭代开发,提高了开发效率。这对于需要频繁调整代码的阶段尤为重要。

而且tsup很适合我们的组件库的开发场景。

安装完tsup之后,我们开始为组件添加对应的打包的配置:

  1. 首先来到/packages/components/button/目录,新建tsup.config.ts

    ts 复制代码
    import { defineConfig } from "tsup";
    
    export default defineConfig({
      clean: true,
      target: "esnext",
      format: ["cjs", "esm"], // 打包出 Commonjs & ESMoudle 规范的代码
    });

    这样就可以了,几乎不需要写什么配置,esBuild默认启用Tree Shaking

  2. 对于CSStsup则支持了PostCSS插件:

    • pnpm add postcss autoprefixer -D -w因为所有组件都需要用,所以我们将postcss安装到根项目下。

    • 然后/packages/components/button/下新建postcss.config.cjs文件:

      js 复制代码
      const config = {
        plugins: {
          autoprefixer: {},
        },
      };
      
      module.exports = config;
  3. 同时我们也为packages/core/react/也添加上述的配置,这里就不展开了。

  4. 然后我们更改一下相关的packae.json文件,完善build命令: packages/components/button/package.json & packages/core/react/package.json

    json 复制代码
    // ...
    "scripts": {
        "build": "tsup src --dts"
      },
    // ...

    加上--dts会自动生成TypeScript的类型声明文件。

    根目录下package.json

    json 复制代码
    // ...
    "scripts": {
        "dev": "turbo dev",
        "dev:sb": "turbo dev --filter=@blankui-org/storybook",
        "build": "turbo build --filter=!@blankui-org/storybook",
        "lint": "turbo lint"
      },
    // ...

    过滤掉@blankui-org/storybook

这时候我们在终端运行pnpm build,可以看到@blankui-org/button&@blankui-org/react的代码已经成功编译并输出到其对应目录下的dist/文件下了(button为例):

但是对于esBuild本身来说,它不会处理样式文件注入到HTML中,意思就是我们在项目中实际使用这些组件的时候需要手动引入对应的CSS文件:

esbuild 生成的捆绑 JavaScript 不会自动将生成的 CSS 导入到您的 HTML 页面中。相反,您应该自己将生成的 CSS 与生成的 JavaScript 一起导入到您的 HTML 页面中。这意味着浏览器可以并行下载 CSS 和 JavaScript 文件,这是最有效的方法。

看起来像这样:

ts 复制代码
import {Button} from "@blankui-org/button"

import "@blankui-org/button/dist/index.css";

我们可以使用sasslessstylusCSS预处理器(需要安装对应的PostCSS插件),但是这些方式在组件的主题开发方面其实还不是那么友好(代码的组织以及扩展方面),所以后续我们会使用CSS-in-JS方案来替换,这是一些比较好的方案:

大家可以提前去了解一下。

到这里为止的源代码:commit 02a5bc4

自动化测试

自动化测试对于组件库的开发和维护是非常重要的,它可以确保组件在不断迭代中保持稳定、可靠,并防止引入新的 bug。以下是一些常见的JavaScript测试框架:

  1. Jest:由Facebook开发,全能型选手,具备完善的生态系统。Jest内置了断言库、模拟和覆盖率报告等功能,具有快速且易用的特点。
  2. Mocha:一个灵活且可扩展的测试框架,适用于浏览器和Node.js环境。Mocha提供了多种测试运行器和支持多种断言库,使其非常灵活。
  3. Jasmine:一个行为驱动开发(BDD)的测试框架,适用于JavaScript和TypeScript。Jasmine提供了清晰的语法和测试套件的结构。
  4. QUnit:由jQuery团队开发,专注于简单的单元测试。QUnit适用于测试浏览器中的JavaScript代码。它的语法简单明了,适合初学者。
  5. AVA:一个具有并行测试运行的JavaScript测试框架,适用于Node.js环境。AVA注重并发和隔离性,能够快速运行测试。
  6. Tape:一个简单的测试框架,适用于Node.js环境。Tape的设计理念是简洁、小巧,可以轻松与其他工具集成。它产生的输出格式也比较容易解释。
  7. Cypress:一个端到端测试框架,专注于实际浏览器中的用户行为。Cypress提供了实时查看、自动等待和调试功能,适用于JavaScript项目。

这里选择了 Jest 作为我们组件库的测试框架 😃:

  1. 全面的功能:Jest 提供了一个全面的测试框架,包括单元测试、集成测试和端到端测试。它内置了断言库、模拟功能和覆盖率报告等功能,使得可以在一个工具中完成多种测试任务。
  2. React 生态系统支持:如果你的项目中使用了 React,Jest 是一个为 React 应用设计的测试框架,可以很好地集成和支持 React 组件的测试。它还提供了 Enzyme 和 React Testing Library 的集成。
  3. 易用性和配置:Jest 的配置相对简单,而且默认配置已经足够满足大多数项目的需求。同时,Jest 提供了灵活的配置选项,可以根据项目的特定需求进行定制。
  4. 快速运行速度:Jest 在并行运行测试用例方面表现出色,因此可以更快地执行测试套件。这对于大型项目或拥有大量测试用例的项目来说是一个重要的优势。
  5. 自动化 Mocking:Jest 提供了自动化 Mocking 的功能,可以方便地模拟模块、函数等,简化了对依赖关系的测试。
  6. 强大的断言库:Jest 自带了强大而灵活的断言库,同时也支持使用其他流行的断言库,如 chai。
  7. 社区支持和文档丰富:Jest 拥有庞大的社区支持,有丰富的文档和示例,使得解决问题和获取支持变得更加容易。
  8. 持续更新和改进:Jest 是一个活跃维护的项目,定期发布新版本,引入新功能和改进。这意味着你可以始终享受到最新的测试工具和功能。

我们快速的安装一下Jest相关的包:pnpm add jest jest-environment-jsdom @types/jest @swc/jest whatwg-fetch @testing-library/jest-dom @testing-library/react @testing-library/react-hooks -D -w,然后为项目添加Jest的相关配置:

  1. 项目根目录下创建jest.config.js&jest.setup.ts文件:

    js 复制代码
    module.exports = {
      testEnvironment: "jsdom",
      moduleDirectories: ["node_modules"],
      moduleFileExtensions: ["js", "jsx", "ts", "tsx", "json"],
      // collectCoverage: true,
      collectCoverageFrom: [
        "packages/**/*.{ts,tsx}",
        "!packages/storybook/**",
        "!**/stories/**",
      ],
      // React is not defined
      // https://github.com/swc-project/swc-node/issues/635
      transform: {
        "\\.(ts|tsx|js|jsx)$": [
          "@swc/jest",
          {
            jsc: {
              transform: {
                react: {
                  runtime: "automatic",
                },
              },
            },
          },
        ],
      },
      // transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"],
      moduleNameMapper: {
        "\\.(css|less)$": "identity-obj-proxy",
      },
      setupFilesAfterEnv: ["./jest.setup.ts"],
    };

    使用 ES6 Proxy 来模拟 CSS:pnpm add identity-obj-proxy -D -w

    ts 复制代码
    // Polyfill "window.fetch" used in the React component.
    import "whatwg-fetch";
    
    // Extend Jest "expect" functionality with Testing Library assertions.
    import "@testing-library/jest-dom";

    关于 Jest 框架的更多配置可查看其官方文档:jestjs.io/docs/config...

  2. 根目录下package.json文件中添加test命令:

    json 复制代码
    // ...
    "scripts": {
        "dev": "turbo dev",
        "dev:sb": "turbo dev --filter=@blankui-org/storybook",
        "build": "turbo build --filter=!@blankui-org/storybook",
        "test": "jest --verbose",
        "lint": "turbo lint"
      },
    // ...

    --verbose 层次显示测试套件中每个测试的结果。

  3. Button组件添加自动化测试,新建packages/components/button/__tests__/button.test.tsx文件:

    ts 复制代码
    import { act, render } from "@testing-library/react";
    
    import { Button } from "../src";
    
    describe("Button", () => {
      it("should render correctly", () => {
        const wrapper = render(<Button label="button" />);
    
        expect(() => wrapper.unmount()).not.toThrow();
      });
    
      it("should trigger onClick function", () => {
        const onClick = jest.fn();
        const { getByRole } = render(<Button label="button" onClick={onClick} />);
    
        act(() => {
          getByRole("button").click();
        });
    
        expect(onClick).toHaveBeenCalled();
      });
    });

最后在终端运行pnpm test,显示测试通过:

shell 复制代码
> blankui@1.0.0 test /Users/******/Documents/public/blankui
> jest --verbose

 PASS  packages/components/button/__tests__/button.test.tsx
  Button
    ✓ should render correctly (9 ms)
    ✓ should trigger onClick function (23 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.448 s, estimated 1 s
Ran all test suites.

那么我们已经为组件库完成了自动化测试的所有配置,后续只需要为组件完善自动化测试的案例即可。

到这里为止的源代码:commit 00857a0

代码的质量和风格

在项目代码的质量和风格方面我们主要会用到这几个库来保证:

  • ESLint
    • 职责:ESLint 主要用于静态代码分析,检测 JavaScript 代码中的潜在问题、错误和不一致之处。它强调代码质量、最佳实践和规范性。
    • 特点:
      • 提供了丰富的规则集,可以根据项目需求自定义配置。
      • 可以集成到开发工具和持续集成流程中,确保代码在提交前或构建阶段进行检查。
      • 支持自定义规则和插件,适用于不同的项目需求和框架。
  • Prettier
    • 职责:Prettier 主要用于代码格式化,它专注于规范代码的外观,使其具有一致的风格,而不关心代码逻辑。
    • 特点:
      • 自动格式化代码,提供一致的代码风格。
      • 不同于传统的 linters,Prettier 不需要进行复杂的配置,减少了项目中关于代码样式的争议。
      • 支持多种语言,包括 JavaScript、CSS、HTML、Markdown 等。
  • Husky
    • 职责:Husky 是一个 Git 钩子工具,可以在代码提交、推送等操作前执行预定义的脚本。它通常与 ESLint、Prettier 等工具一起使用,确保代码在提交前通过代码检查和格式化。
    • 特点:
      • 通过在 Git 钩子中运行脚本,可以在提交前执行代码检查和格式化,防止低质量的代码进入代码仓库。
      • 配合 lint-staged 可以只对暂存区中的文件执行 lint 和格式化,提高效率。

通过整合ESLintPrettierHusky,可以形成一个强大的代码质量管理和格式化工具链,确保项目代码具有高质量、一致的风格,并在提交前进行严格的代码检查。

那我们就开始将它们集成到项目中来,并且分别添加一些基本的配置规则:

  1. ESLint

    • 安装 pnpm add eslint @types/eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-jest eslint-config-prettier eslint-plugin-prettier eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks -D -w

    • 配置,根目录下新建.eslintrc.cjs&.eslintignore文件:

      js 复制代码
      /** @type {import("eslint").Linter.Config} */
      const config = {
        $schema: "https://json.schemastore.org/eslintrc.json",
        env: {
          browser: true,
          es2021: true,
          node: true,
        },
        extends: [
          "eslint:recommended",
          "plugin:@typescript-eslint/recommended",
          "plugin:react/recommended",
          "plugin:prettier/recommended",
          "plugin:react-hooks/recommended",
          "plugin:jsx-a11y/recommended",
        ],
        settings: {
          react: {
            version: "detect",
          },
        },
        overrides: [
          {
            env: {
              node: true,
            },
            files: [".eslintrc.{js,cjs}"],
            parserOptions: {
              sourceType: "script",
            },
          },
        ],
        parser: "@typescript-eslint/parser",
        parserOptions: {
          ecmaFeatures: {
            jsx: true,
          },
          ecmaVersion: "latest",
          sourceType: "module",
        },
        plugins: ["@typescript-eslint", "react", "jsx-a11y", "prettier", "jest"],
        rules: {
          "no-console": "warn",
          "prettier/prettier": "warn",
          "react/react-in-jsx-scope": "off",
          "@typescript-eslint/no-explicit-any": "warn",
          "@typescript-eslint/ban-ts-comment": [
            "error",
            {
              "ts-expect-error": "allow-with-description",
            },
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              types: {
                // un-ban a type that's banned by default
                "{}": false,
              },
              extendDefaults: true,
            },
          ],
          "@typescript-eslint/no-unused-vars": [
            "warn",
            {
              args: "after-used",
              ignoreRestSiblings: false,
              argsIgnorePattern: "^_.*?$",
            },
          ],
          "jest/no-disabled-tests": "warn",
          "jest/no-focused-tests": "error",
          "jest/no-identical-title": "error",
          "jest/prefer-to-have-length": "warn",
          "jest/valid-expect": "error",
        },
      };
      
      module.exports = config;
      yml 复制代码
      .now/*
      .next/*
      *.css
      .changeset
      dist
      esm/*
      public/*
      tests/*
      scripts/*
      *.config.js
      .DS_Store
      node_modules
      coverage
      .next
      build
      !.storybook
      /**/.storybook/**
      !.commitlintrc.cjs
      !.lintstagedrc.cjs
      !jest.config.js
      !plopfile.js
      !react-shim.js
      !tsup.config.ts
  2. Prettier

    • 安装 pnpm add prettier prettier-eslint prettier-eslint-cli -D -w

    • 配置,根目录下新建.prettierrc.cjs&.prettierignore文件:

      js 复制代码
      /** @type {import("prettier").Config} */
      const config = {
        $schema: "https://json.schemastore.org/prettierrc.json",
        tabWidth: 2,
        semi: true,
        singleQuote: false,
        endOfLine: "auto",
        trailingComma: "all",
      };
      
      module.exports = config;
      yml 复制代码
      dist
      node_modules
      plop
      coverage
      .changeset
      .next
      build
      scripts
      pnpm-lock.yaml
      !.storybook
      !.commitlintrc.cjs
      !.lintstagedrc.cjs
      !jest.config.js
      !tsup.config.ts

这时候我们先修改一下根目录下的package.json文件,添加几个命令:

json 复制代码
// ...
"scripts": {
  "dev": "turbo dev",
  "dev:sb": "turbo dev --filter=@blankui-org/storybook",
  "build": "turbo build --filter=!@blankui-org/storybook",
  "test": "jest --verbose",
  "lint": "pnpm lint:packages",
  "lint:packages": "eslint packages/**/*.{ts,tsx}",
  "lint:packages-fix": "eslint --fix ./packages/**/*.{ts,tsx}",
  "format:check": "prettier --check packages/**/**/src --cache",
  "format:write": "prettier --write packages/**/**/src --cache"
},
// ...

此时运行pnpm lint:packages会输出:

yml 复制代码
> blankui@1.0.0 lint:packages /Users/******/Documents/public/blankui
> eslint packages/**/*.{ts,tsx}


/Users/zhangyifan/Documents/public/blankui/packages/components/button/src/button.tsx
  30:3   error    'primary' is missing in props validation          react/prop-types
  31:3   error    'size' is missing in props validation             react/prop-types
  32:3   error    'backgroundColor' is missing in props validation  react/prop-types
  33:3   error    'label' is missing in props validation            react/prop-types
  43:12  warning  Insert `,`                                        prettier/prettier

✖ 5 problems (4 errors, 1 warning)
  0 errors and 1 warning potentially fixable with the `--fix` option.

 ELIFECYCLE  Command failed with exit code 1.

然后我们通过pnpm lint:packages-fix自动修复:

yml 复制代码
> blankui@1.0.0 lint:packages-fix /Users/******/Documents/public/blankui
> eslint --fix ./packages/**/*.{ts,tsx}


/Users/zhangyifan/Documents/public/blankui/packages/components/button/src/button.tsx
  30:3  error  'primary' is missing in props validation          react/prop-types
  31:3  error  'size' is missing in props validation             react/prop-types
  32:3  error  'backgroundColor' is missing in props validation  react/prop-types
  33:3  error  'label' is missing in props validation            react/prop-types

✖ 4 problems (4 errors, 0 warnings)

 ELIFECYCLE  Command failed with exit code 1.

等等,怎么没有效果...(这里自动修复了最后的那个warnbutton.tsx第43行多了一个逗号)。其实上面的eslint错误是由于我们使用React.FC<Props>的形式来声明组件的类型的,这个问题其实由来已久:github.com/jsx-eslint/...,只需要在文件的顶部引入React就行:import React from "react";。(其实通过tsconfig.json"jsx": "react-jsx"配置会自动引入的,但是ESLint会抱怨识别不了,只能加回来)。

这时候在运行pnpm lint:packages,一切都正常了。

  1. Husky

    • 我们通过 pnpm dlx husky-init && pnpm install 命令使用husky快速初始化项目,它会:

      1. 添加prepare脚本到package.json,安装husky依赖
      2. 创建一个pre-commit可以编辑的示例挂钩(默认情况下,npm test将在提交时运行)
      3. 配置Git钩子路径
    • 使用husky add再添加另一个钩子:pnpm exec husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

    此时在根目录下会存在".husky/"目录,结构如下:

    lua 复制代码
    |-- .husky
        |-- _
            |-- .gitignore
            |-- husky.sh
        |-- commit-msg
        |-- pre-commit
    • 因为到目前为止我们都是用husky默认提供的配置,接下来我们自定义一下,首先安装lint-stagedcommitlint这两个库 pnpm add lint-staged @commitlint/cli @commitlint/config-conventional commitlint-plugin-function-rules -D -w

    • 根目录下分别新建.lintstagedrc.cjs.commitlintrc.cjs文件:

      .lintstagedrc.cjs

      js 复制代码
      const { relative } = require("node:path");
      const { ESLint } = require("eslint");
      
      const removeIgnoredFiles = async (files) => {
        const cwd = process.cwd();
        const eslint = new ESLint();
        const relativePaths = files.map((file) => relative(cwd, file));
        const isIgnored = await Promise.all(
          relativePaths.map((file) => {
            return eslint.isPathIgnored(file);
          }),
        );
        const filteredFiles = files.filter((_, i) => !isIgnored[i]);
        return filteredFiles.join(" ");
      };
      
      module.exports = {
        // 提交之前对所有的匹配到的文件进行eslint检查
        "**/*.{ts,tsx,js,jsx}": async (files) => {
          const filesToLint = await removeIgnoredFiles(files);
          return [`eslint --max-warnings=0 --fix ${filesToLint}`];
        },
      };

      .commitlintrc.cjs

      js 复制代码
      const conventional = require("@commitlint/config-conventional");
      
      module.exports = {
        extends: ["@commitlint/config-conventional"],
        plugins: ["commitlint-plugin-function-rules"],
        helpUrl: "https://github.com/1111mp/blankui",
        rules: {
          ...conventional.rules,
          "type-enum": [
            2,
            "always",
            [
              "feat",
              "feature",
              "fix",
              "refactor",
              "docs",
              "build",
              "test",
              "ci",
              "chore",
            ],
          ],
          "function-rules/header-max-length": [0],
        },
      };
    • 修改一下.husky/pre-commit.husky/commit-msg文件:

      json 复制代码
      #!/usr/bin/env sh
      . "$(dirname -- "$0")/_/husky.sh"
      
      # Avoid excessive outputs
      if [ -t 2 ]; then
        exec >/dev/tty 2>&1
      fi
      
      pnpm lint-staged -c .lintstagedrc.cjs
      json 复制代码
      #!/usr/bin/env sh
      . "$(dirname -- "$0")/_/husky.sh"
      
      pnpm commitlint --config .commitlintrc.cjs --edit ${1}

这时候在代码提交之前就会进行代码的校验了,一切都通过的话代码才能被提交。当然不通过的话我们跟着控制台的报错信息将相关代码一个一个改正即可。

补充一下lint-stagedcommitlint的相关知识:

  • lint-staged :
    • 职责:
      • lint-staged 是一个在 Git 提交前运行 linters 的工具。它允许你配置只对 Git 暂存区中的文件运行 linters,确保只有相关的文件会受到代码检查和格式化。
    • 特点:
      • 可以配置在提交前运行指定的 linters。
      • 配合 Husky 使用,确保只有通过代码检查和格式化的文件才能被提交。
  • commitlint :
    • 职责:
      • commitlint 用于规范化 commit message,确保团队的提交信息遵循统一的格式和规范。
    • 特点:
      • 提供了一组规则,用于检查提交信息的格式和内容。
      • 可以通过配置规则和使用预设(presets)来满足项目的需求。
      • 帮助维护良好的提交历史,方便生成 changelog。

其实这里对于ESLintPrettierHusky的一些具体的配置并没有过多介绍,特别是ESLint(因为它的配置真的太多了...),这里的建议就是结合官方的文档了解一下各自的功能就行,至于那些具体的规则其实自己在写代码的时候如果不符合规范就会一个一个遇到,遇到的时候结合报错再去了解就行。

我们的组件库的功能又完善了一些了 😃。

到这里为止的源代码:commit 7c42771

这一篇文章应该就到此结束了,下一篇我们会实现组件库的统一发布、变更记录等功能...那我们就下一篇文章见~

相关推荐
时光追逐者19 分钟前
WaterCloud:一套基于.NET 8.0 + LayUI的快速开发框架,完全开源免费!
前端·microsoft·开源·c#·.net·layui·.netcore
小小李程序员1 小时前
CSS3渐变
前端·css·css3
A仔不会笑2 小时前
微服务——分布式事务
分布式·微服务·架构
赵锦川2 小时前
css相关:input输入框中加入搜索图标
java·前端·javascript
yunduor9095 小时前
从零开始搭建UVM平台(九)-加入reference model
前端
莘薪5 小时前
HTML的修饰(CSS) -- 第三课
前端·css·html·框架
某公司摸鱼前端7 小时前
uniapp 上了原生的 echarts 图表插件了 兼容性还行
前端·uni-app·echarts
2401_857297917 小时前
秋招内推--招联金融2025
java·前端·算法·金融·求职招聘
BHDDGT7 小时前
react-问卷星项目(5)
前端·javascript·react.js
小白求学19 小时前
CSS滚动条
前端·css