├── .nvmrc
├── .npmrc
├── .vscode
├── extensions.json
└── settings.json
├── .github
├── FUNDING.yml
└── workflows
│ ├── pkg.pr.new.yml
│ ├── release.yml
│ └── ci.yml
├── packages
└── core
│ ├── src
│ ├── tools
│ │ ├── index.ts
│ │ ├── postcss.ts
│ │ ├── babel.ts
│ │ └── babel.test.ts
│ ├── style
│ │ ├── index.ts
│ │ └── index.test.ts
│ ├── script
│ │ └── index.ts
│ ├── template
│ │ ├── index.ts
│ │ └── index.test.ts
│ ├── index.ts
│ ├── utils.ts
│ └── options.ts
│ ├── build.config.ts
│ ├── CHANGELOG.md
│ ├── package.json
│ └── README.md
├── pnpm-workspace.yaml
├── playground
├── src
│ ├── static
│ │ └── logo.png
│ ├── styles
│ │ ├── preflight.css
│ │ └── tailwind.css
│ ├── main.ts
│ ├── App.vue
│ ├── pages.json
│ ├── pages
│ │ └── index
│ │ │ └── index.vue
│ └── manifest.json
├── tsconfig.json
├── index.html
├── tailwind.config.ts
├── vite.config.ts
└── package.json
├── lint-staged.config.mjs
├── simple-git-hooks.cjs
├── commitlint.config.mjs
├── vitest.config.ts
├── .editorconfig
├── tsconfig.json
├── lerna.json
├── biome.json
├── LICENSE
├── deprecated.md
├── package.json
├── .gitignore
├── CHANGELOG.md
└── README.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | 22
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
2 | registry=https://registry.npmjs.org/
3 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["biomejs.biome"]
3 | }
4 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [ModyQyW]
2 | custom: ["https://github.com/ModyQyW/sponsors"]
3 |
--------------------------------------------------------------------------------
/packages/core/src/tools/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./babel";
2 | export * from "./postcss";
3 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - docs
3 | - playground
4 | - packages/*
5 | - examples/*
6 |
--------------------------------------------------------------------------------
/playground/src/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uni-helper/vite-plugin-uni-tailwind/HEAD/playground/src/static/logo.png
--------------------------------------------------------------------------------
/lint-staged.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | "*": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true",
3 | };
4 |
--------------------------------------------------------------------------------
/simple-git-hooks.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "commit-msg": "npx commitlint --edit ${1}",
3 | "pre-commit": "npx lint-staged",
4 | };
5 |
--------------------------------------------------------------------------------
/commitlint.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | extends: [
3 | "@commitlint/config-conventional",
4 | "@commitlint/config-pnpm-scopes",
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | clearMocks: true,
6 | },
7 | });
8 |
--------------------------------------------------------------------------------
/playground/src/styles/preflight.css:
--------------------------------------------------------------------------------
1 | button,
2 | button::after {
3 | all: unset;
4 | }
5 |
6 | button {
7 | -webkit-tap-highlight-color: transparent;
8 | box-sizing: border-box;
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.defaultFormatter": "biomejs.biome",
3 | "editor.formatOnSave": true,
4 | "editor.codeActionsOnSave": {
5 | "quickfix.biome": "explicit",
6 | "source.organizeImports.biome": "explicit"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/playground/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createSSRApp } from 'vue';
2 | import App from './App.vue';
3 | import './styles/preflight.css';
4 | import './styles/tailwind.css';
5 |
6 | export function createApp() {
7 | const app = createSSRApp(App);
8 | return {
9 | app,
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/playground/src/App.vue:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/playground/src/pages.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
3 | {
4 | "path": "pages/index/index",
5 | "style": {
6 | "navigationBarTitleText": "uni-app"
7 | }
8 | }
9 | ],
10 | "globalStyle": {
11 | "navigationBarTextStyle": "black",
12 | "navigationBarTitleText": "uni-app",
13 | "navigationBarBackgroundColor": "#F8F8F8",
14 | "backgroundColor": "#F8F8F8"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/playground/src/pages/index/index.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 | {{ title }}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/core/src/style/index.ts:
--------------------------------------------------------------------------------
1 | import postcss from "postcss";
2 | import { defaultOptions } from "../options";
3 | import { postcssTransformSelector } from "../tools";
4 |
5 | export const isStyleFile = (fileName: string) => {
6 | return ["wxss", "acss", "jxss", "ttss", "qss", "css"].some((ext) =>
7 | fileName.endsWith(ext),
8 | );
9 | };
10 |
11 | export const transformStyle = (source: string, options = defaultOptions) => {
12 | const processor = postcss().use(postcssTransformSelector(options));
13 | return processor.process(source).css;
14 | };
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
5 | "module": "ESNext",
6 | "moduleResolution": "Bundler",
7 | "target": "ESNext",
8 | "resolveJsonModule": true,
9 | "paths": {
10 | "core": ["./packages/core/src/index.ts"]
11 | },
12 | "strict": true,
13 | "esModuleInterop": true,
14 | "skipLibCheck": true,
15 | "skipDefaultLibCheck": true,
16 | "types": ["@dcloudio/types", "node"]
17 | },
18 | "exclude": ["**/node_modules/**", "**/dist/**", "**/playground/**"]
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/script/index.ts:
--------------------------------------------------------------------------------
1 | import type { OutputChunk } from "rollup";
2 | import { defaultOptions } from "../options";
3 | import { type babelGetVendorExportMap, babelTransformScript } from "../tools";
4 |
5 | export const isScriptFile = (fileName: string) => /.+\.js$/.test(fileName);
6 |
7 | export const transformScript = (
8 | asset: OutputChunk,
9 | vendorExports: ReturnType,
10 | options = defaultOptions,
11 | ) => {
12 | const { fileName, code } = asset;
13 | if (!options.shouldTransformScript(fileName)) return code;
14 | return babelTransformScript(code, vendorExports, options);
15 | };
16 |
--------------------------------------------------------------------------------
/packages/core/build.config.ts:
--------------------------------------------------------------------------------
1 | import { appendFileSync } from "node:fs";
2 | import { resolve } from "node:path";
3 | import { defineBuildConfig } from "unbuild";
4 |
5 | export default defineBuildConfig({
6 | entries: ["./src/index"],
7 | clean: true,
8 | declaration: true,
9 | rollup: {
10 | emitCJS: true,
11 | esbuild: {
12 | target: "node14.18",
13 | },
14 | },
15 | hooks: {
16 | "build:done": (ctx) => {
17 | const { outDir } = ctx.options;
18 | appendFileSync(
19 | resolve(outDir, "index.cjs"),
20 | "module.exports = Object.assign(exports.default || {}, exports)",
21 | );
22 | },
23 | },
24 | });
25 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "node_modules/@lerna-lite/cli/schemas/lerna-schema.json",
3 | "version": "0.15.2",
4 | "npmClient": "pnpm",
5 | "packages": [
6 | "packages/*"
7 | ],
8 | "command": {
9 | "version": {
10 | "allowBranch": "main",
11 | "conventionalCommits": true,
12 | "changelogIncludeCommitsClientLogin": " - by @%l",
13 | "message": "chore: release %s",
14 | "remoteClient": "github"
15 | }
16 | },
17 | "changelogPreset": "conventional-changelog-conventionalcommits",
18 | "ignoreChanges": [
19 | "**/test/**",
20 | "**/tests/**",
21 | "**/__test__/**",
22 | "**/__tests__/**",
23 | "**/*.md"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/playground/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "sourceMap": true,
10 | "resolveJsonModule": true,
11 | "esModuleInterop": true,
12 | "lib": ["esnext", "dom"],
13 | "types": ["vite/client", "@dcloudio/types"],
14 | "paths": {
15 | "@/*": ["./src/*"]
16 | }
17 | },
18 | "vueCompilerOptions": {
19 | "experimentalRuntimeMode": "runtime-uni-app",
20 | "nativeTags": ["block", "component", "template", "slot"]
21 | },
22 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
23 | }
24 |
--------------------------------------------------------------------------------
/playground/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/playground/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss';
2 | import { basePreset, elementPlusPreset, miniprogramBasePreset } from 'tailwind-extensions';
3 | import { isMp, isQuickapp } from '@uni-helper/uni-env';
4 |
5 | const presets: Config['presets'] = [basePreset];
6 | if (isMp || isQuickapp) {
7 | presets.push(
8 | elementPlusPreset({
9 | baseSelectors: [':root', 'page'],
10 | }),
11 | miniprogramBasePreset,
12 | );
13 | } else {
14 | presets.push(elementPlusPreset());
15 | }
16 |
17 | const theme: Config['theme'] = {};
18 | if (isMp || isQuickapp) theme.screens = {};
19 |
20 | const config: Config = {
21 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
22 | presets,
23 | theme,
24 | };
25 |
26 | export default config;
27 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3 | "files": {
4 | "ignore": [
5 | "**/node_modules",
6 | "**/.git",
7 | "**/.DS_Store",
8 | "**/dist",
9 | "**/cache",
10 | "./playground",
11 | "./**/package.json",
12 | "./lerna.json",
13 | "./.vscode",
14 | "./coverage"
15 | ]
16 | },
17 | "organizeImports": {
18 | "enabled": true
19 | },
20 | "linter": {
21 | "enabled": true,
22 | "rules": {
23 | "recommended": true,
24 | "suspicious": {
25 | "noExplicitAny": "off"
26 | },
27 | "complexity": {
28 | "noBannedTypes": "off"
29 | }
30 | }
31 | },
32 | "formatter": {
33 | "enabled": true,
34 | "indentStyle": "space"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/src/template/index.ts:
--------------------------------------------------------------------------------
1 | import * as wxml from "@vivaxy/wxml";
2 | import { defaultOptions } from "../options";
3 | import { babelTransformClass } from "../tools";
4 |
5 | export const isTemplateFile = (fileName: string) => {
6 | return ["wxml", "axml", "jxml", "ksml", "ttml", "qml", "xhsml", "swan"].some(
7 | (ext) => fileName.endsWith(ext),
8 | );
9 | };
10 |
11 | export const transformTemplate = (source: string, options = defaultOptions) => {
12 | const parsed = wxml.parse(source);
13 | wxml.traverse(parsed, (node: any) => {
14 | if (node?.type === wxml.NODE_TYPES.ELEMENT && node?.attributes) {
15 | const keys = Object.keys(node.attributes).filter((k) =>
16 | options.shouldTransformAttribute(k),
17 | );
18 | for (const k of keys) {
19 | node.attributes[k] = babelTransformClass(node.attributes[k], options);
20 | }
21 | }
22 | });
23 | return wxml.serialize(parsed);
24 | };
25 |
--------------------------------------------------------------------------------
/playground/src/styles/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | html,
7 | page {
8 | font-size: var(--font-size, 16px) !important;
9 | line-height: 1.5;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 | }
14 |
15 | @layer components {
16 | .container {
17 | @apply absolute;
18 | @apply top-0;
19 | @apply left-0;
20 | @apply flex;
21 | @apply flex-col;
22 | @apply w-full;
23 | @apply h-full;
24 | }
25 | }
26 |
27 | @layer utilities {
28 | .expand {
29 | @apply relative;
30 |
31 | /* https://tailwindcss.com/docs/using-with-preprocessors#nesting */
32 | &::after {
33 | @apply absolute;
34 | @apply -inset-2;
35 | @apply text-transparent;
36 | @apply bg-transparent;
37 | @apply border-transparent;
38 | content: '';
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/core/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [0.15.2](https://github.com/uni-helper/vite-plugin-uni-tailwind/compare/v0.15.1...v0.15.2) (2024-11-07)
7 |
8 | ### Bug Fixes
9 |
10 | * fix transformed quotes, close ([ce18454](https://github.com/uni-helper/vite-plugin-uni-tailwind/commit/ce18454c1a3d3273b20317912feeb1bc958dab81)) - by @ModyQyW
11 |
12 | ## [0.15.1](https://github.com/uni-helper/vite-plugin-uni-tailwind/compare/v0.15.0...v0.15.1) (2024-10-28)
13 |
14 | ### Bug Fixes
15 |
16 | * fix style judgement ([76378eb](https://github.com/uni-helper/vite-plugin-uni-tailwind/commit/76378eb60b048b075d6ad14ca9f2e7fb3f013b97)) - by @ModyQyW
17 | * fix template judgement ([fa43006](https://github.com/uni-helper/vite-plugin-uni-tailwind/commit/fa430065e69be2eb864d7ab820ca4b254df3ccd6)) - by @ModyQyW
18 |
--------------------------------------------------------------------------------
/playground/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from 'node:url';
2 | import { defineConfig } from 'vite';
3 | import uni from '@dcloudio/vite-plugin-uni';
4 | import tailwindcss from 'tailwindcss';
5 | // @ts-ignore
6 | import nested from 'tailwindcss/nesting';
7 | import postcssPresetEnv from 'postcss-preset-env';
8 | import uniTailwind from '@uni-helper/vite-plugin-uni-tailwind';
9 | import tailwindcssConfig from './tailwind.config.ts'; // 注意匹配实际文件
10 |
11 | // https://vitejs.dev/config/
12 | export default defineConfig({
13 | css: {
14 | postcss: {
15 | plugins: [
16 | nested(),
17 | tailwindcss({
18 | config: tailwindcssConfig,
19 | }),
20 | postcssPresetEnv({
21 | stage: 3,
22 | features: { 'nesting-rules': false },
23 | }),
24 | ],
25 | },
26 | },
27 | plugins: [
28 | uni(),
29 | uniTailwind({
30 | /* options */
31 | }),
32 | ],
33 | resolve: {
34 | alias: {
35 | '@': fileURLToPath(new URL('src', import.meta.url)),
36 | },
37 | },
38 | });
39 |
--------------------------------------------------------------------------------
/.github/workflows/pkg.pr.new.yml:
--------------------------------------------------------------------------------
1 | # https://github.com/unocss/unocss/blob/fe83a90b59cf4599be57ea825166bb74d92b104c/.github/workflows/pkg.pr.new.yml
2 | name: Publish Any Commit
3 |
4 | on:
5 | pull_request:
6 | push:
7 | branches:
8 | - '**'
9 | tags:
10 | - '!**'
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v4
19 | with:
20 | fetch-depth: 0
21 |
22 | - name: Install pnpm
23 | uses: pnpm/action-setup@v4
24 |
25 | - name: Setup Node.js
26 | uses: actions/setup-node@v4
27 | with:
28 | node-version-file: .nvmrc
29 | cache: pnpm
30 | registry-url: https://registry.npmjs.org
31 |
32 | - name: Setup corepack
33 | run: corepack enable
34 |
35 | - name: Install Dependencies
36 | run: pnpm install
37 |
38 | - name: Build
39 | run: pnpm build
40 |
41 | - name: Release
42 | run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022-present uni-helper
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/core/src/tools/postcss.ts:
--------------------------------------------------------------------------------
1 | import type { Declaration, Plugin, Rule } from "postcss";
2 | import { defaultOptions } from "../options";
3 | import { replaceCharacters, replaceElements, replaceUnicode } from "../utils";
4 |
5 | export function postcssTransformSelector(options = defaultOptions): Plugin {
6 | return {
7 | Rule(node: Rule & { processedByPostcssTransformSelector?: boolean }) {
8 | if (node?.processedByPostcssTransformSelector) return;
9 | let newSelector = node.selector;
10 | newSelector = replaceUnicode(newSelector, "style");
11 | newSelector = replaceElements(newSelector, options);
12 | newSelector = replaceCharacters(newSelector, "style", options);
13 | node.selector = newSelector;
14 | if (node.type === "rule" && /text--\d+rpx-/.test(node.selector)) {
15 | const index = node.nodes.findIndex(
16 | (n) =>
17 | n.type === "decl" && n.prop === "color" && n.value.endsWith("rpx"),
18 | );
19 | if (index >= 0) (node.nodes[index] as Declaration).prop = "font-size";
20 | }
21 | node.processedByPostcssTransformSelector = true;
22 | },
23 | postcssPlugin:
24 | "uni-helper-vite-plugin-uni-tailwind-postcss-transform-selector",
25 | };
26 | }
27 | postcssTransformSelector.postcss = true;
28 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # https://github.com/unocss/unocss/blob/fe83a90b59cf4599be57ea825166bb74d92b104c/.github/workflows/release.yml
2 | name: Release
3 |
4 | on:
5 | push:
6 | tags:
7 | - 'v*'
8 |
9 | jobs:
10 | release:
11 | permissions:
12 | id-token: write
13 | contents: write
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 |
21 | - name: Install pnpm
22 | uses: pnpm/action-setup@v4
23 |
24 | - name: Setup Node.js
25 | uses: actions/setup-node@v4
26 | with:
27 | node-version-file: .nvmrc
28 | cache: pnpm
29 | registry-url: https://registry.npmjs.org
30 |
31 | - name: Setup corepack
32 | run: corepack enable
33 |
34 | - name: GitHub Release
35 | run: pnpx changelogithub
36 | env:
37 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
38 |
39 | - name: Install Dependencies
40 | run: pnpm install
41 |
42 | - name: Build
43 | run: pnpm build
44 |
45 | - name: Publish to NPM
46 | run: pnpm -r publish --access public --no-git-checks
47 | env:
48 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
49 | NPM_CONFIG_PROVENANCE: true
50 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # https://github.com/unocss/unocss/blob/fe83a90b59cf4599be57ea825166bb74d92b104c/.github/workflows/ci.yml
2 | name: CI
3 |
4 | on:
5 | push:
6 | branches:
7 | - main
8 |
9 | pull_request:
10 | branches:
11 | - main
12 |
13 | merge_group: {}
14 |
15 | jobs:
16 | test:
17 | runs-on: ${{ matrix.os }}
18 |
19 | strategy:
20 | matrix:
21 | os: [ubuntu-latest, macos-latest, windows-latest]
22 | node_version: [18, 20, 22]
23 | fail-fast: false
24 |
25 | steps:
26 | - name: Set git to use LF
27 | run: |
28 | git config --global core.autocrlf false
29 | git config --global core.eol lf
30 |
31 | - name: Checkout
32 | uses: actions/checkout@v4
33 |
34 | - name: Install pnpm
35 | uses: pnpm/action-setup@v4
36 |
37 | - name: Setup Node.js ${{ matrix.node_version }}
38 | uses: actions/setup-node@v4
39 | with:
40 | node-version: ${{ matrix.node_version }}
41 | cache: pnpm
42 | registry-url: https://registry.npmjs.org
43 |
44 | - name: Setup corepack
45 | run: corepack enable
46 |
47 | - name: Install Dependencies
48 | run: pnpm install --frozen-lockfile
49 |
50 | - name: Build
51 | run: pnpm build
52 |
53 | - name: Test
54 | run: pnpm test
55 |
56 | - name: Type Check
57 | run: pnpm type-check
58 |
--------------------------------------------------------------------------------
/deprecated.md:
--------------------------------------------------------------------------------
1 | # 废弃 @uni-helper/vite-plugin-uni-tailwind
2 |
3 | 今日起,我们将正式废弃 @uni-helper/vite-plugin-uni-tailwind,并建议现有应用迁移至其它类似工具。
4 |
5 | ## 背景
6 |
7 | @uni-helper/vite-plugin-uni-tailwind 最初创建的目的是为了解决在 uni-app 中使用 TailwindCSS 时遇到的各种兼容性问题。
8 |
9 | 这个插件通过转换特殊字符和元素选择器,使得开发者可以在小程序中使用原生 TailwindCSS 语法进行开发,而无需调整原有的开发习惯。
10 |
11 | 由于 UnoCSS 和 WindiCSS 使用 class 时语法和 TailwindCSS 几乎完全一致,所以这个插件实际上也支持 UnoCSS 和 WindiCSS 使用 class 的情况。
12 |
13 | ## 为什么
14 |
15 | ### 更好的方案
16 |
17 | 随着社区的发展,已经出现了更加完善和活跃的解决方案:
18 |
19 | - [unocss-applet](https://github.com/unocss-applet/unocss-applet)
20 | - [unocss-preset-weapp](https://github.com/MellowCo/unocss-preset-weapp)
21 | - [weapp-tailwindcss](https://github.com/sonofmagic/weapp-tailwindcss)
22 |
23 | 这些工具不仅提供了与我们相似的功能,还具有更多高级特性,如:
24 |
25 | - 更好的 rpx 单位支持
26 | - 更完善的类名转换
27 | - 更强大的可配置性
28 | - 更活跃的维护和更新
29 |
30 | ### 插件实现
31 |
32 | 目前,该插件的实现存在难以解决的边界情况和性能瓶颈。在现有架构下,若不进行重大重构,短期内难以添加重要的全新功能。
33 |
34 | ### 团队重心
35 |
36 | 鉴于团队成员业余时间有限,且已将大量精力投入到公司项目及其他开源项目中,我们已难以确保本插件的维护质量及响应速度。
37 |
38 | ## 这意味着什么?
39 |
40 | 这意味着我们不会
41 |
42 | - 修改代码,包括新增功能和安全性修复;
43 | - 接受社区贡献。
44 |
45 | 但我们仍会:
46 |
47 | - 保持现有文档的可访问性。
48 |
49 | **总而言之,现有项目可以继续使用,但不保证更新相关依赖后此插件仍然能正常使用。**
50 |
51 | ## 建议
52 |
53 | 对于新项目,我们建议考虑以下替代方案:
54 |
55 | 对于 UnoCSS:
56 |
57 | - [unocss-applet](https://github.com/unocss-applet/unocss-applet)
58 | - [unocss-preset-weapp](https://github.com/MellowCo/unocss-preset-weapp)
59 |
60 | 对于 TailwindCSS:
61 |
62 | - [weapp-tailwindcss](https://github.com/sonofmagic/weapp-tailwindcss)
63 |
64 | 这些替代方案都有更活跃的维护团队,能够确保更可持续和可靠的开发体验。
65 |
66 | 你也可以分叉并发布自己的版本。
67 |
68 | ## 致谢
69 |
70 | 我们要衷心感谢:
71 |
72 | - 所有使用过该插件的开发者
73 | - 为项目提供反馈和建议的社区成员
74 | - 项目的贡献者们
75 | - 赞助商们的慷慨支持
76 |
77 | 同时也要特别感谢以下项目,它们为我们提供了灵感和参考:
78 |
79 | - [mini-program-tailwind](https://github.com/dcasia/mini-program-tailwind)
80 | - [weapp-tailwindcss](https://github.com/sonofmagic/weapp-tailwindcss)
81 | - [unocss-applet](https://github.com/unocss-applet/unocss-applet)
82 | - [unocss-preset-weapp](https://github.com/MellowCo/unocss-preset-weapp)
83 |
84 | 最后,感谢 [TailwindCSS](https://tailwindcss.com/)、[WindiCSS](https://windicss.org/) 和 [UnoCSS](https://github.com/unocss/unocss) 的开发者们,是他们让前端开发变得更加美好。
85 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@uni-helper/vite-plugin-uni-tailwind",
3 | "version": "0.15.2",
4 | "description": "支持在 uni-app 中使用 TailwindCSS@3 原有语法开发",
5 | "keywords": [
6 | "vite",
7 | "transform",
8 | "tailwind",
9 | "tailwindcss",
10 | "vite-plugin",
11 | "uni-app",
12 | "uniapp",
13 | "miniprogram",
14 | "mini-program"
15 | ],
16 | "homepage": "https://github.com/uni-helper/vite-plugin-uni-tailwind",
17 | "bugs": {
18 | "url": "https://github.com/uni-helper/vite-plugin-uni-tailwind/issues"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/uni-helper/vite-plugin-uni-tailwind.git",
23 | "directory": "packages/core"
24 | },
25 | "funding": "https://github.com/sponsors/modyqyw",
26 | "license": "MIT",
27 | "author": {
28 | "name": "ModyQyW",
29 | "email": "wurui.dev@gmail.com",
30 | "url": "https://modyqyw.top"
31 | },
32 | "sideEffects": false,
33 | "type": "module",
34 | "exports": {
35 | ".": {
36 | "import": {
37 | "types": "./dist/index.d.mts",
38 | "default": "./dist/index.mjs"
39 | },
40 | "require": {
41 | "types": "./dist/index.d.cts",
42 | "default": "./dist/index.cjs"
43 | }
44 | }
45 | },
46 | "main": "./dist/index.cjs",
47 | "module": "./dist/index.mjs",
48 | "types": "./dist/index.d.ts",
49 | "typesVersions": {
50 | "*": {
51 | "*": [
52 | "./dist/*",
53 | "./dist/index.d.ts"
54 | ]
55 | }
56 | },
57 | "files": [
58 | "dist"
59 | ],
60 | "scripts": {
61 | "build": "unbuild",
62 | "prepublishOnly": "pnpm build",
63 | "stub": "unbuild --stub"
64 | },
65 | "dependencies": {
66 | "@babel/core": "^7.26.0",
67 | "@uni-helper/uni-env": "^0.1.5",
68 | "@vivaxy/wxml": "^2.1.0",
69 | "postcss": "^8.4.47"
70 | },
71 | "devDependencies": {
72 | "@types/babel__core": "^7.20.5",
73 | "tailwindcss": "^3.4.14"
74 | },
75 | "peerDependencies": {
76 | "rollup": "^2.0.0 || ^3.0.0 || ^4.0.0",
77 | "tailwindcss": "^3.0.0",
78 | "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0"
79 | },
80 | "peerDependenciesMeta": {
81 | "rollup": {
82 | "optional": true
83 | }
84 | },
85 | "packageManager": "pnpm@9.12.3",
86 | "engines": {
87 | "node": ">=14.18"
88 | },
89 | "publishConfig": {
90 | "access": "public",
91 | "registry": "https://registry.npmjs.org/"
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/playground/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "",
3 | "appid" : "",
4 | "description" : "",
5 | "versionName" : "1.0.0",
6 | "versionCode" : "100",
7 | "transformPx" : false,
8 | /* 5+App特有相关 */
9 | "app-plus" : {
10 | "usingComponents" : true,
11 | "nvueStyleCompiler" : "uni-app",
12 | "compilerVersion" : 3,
13 | "splashscreen" : {
14 | "alwaysShowBeforeRender" : true,
15 | "waiting" : true,
16 | "autoclose" : true,
17 | "delay" : 0
18 | },
19 | /* 模块配置 */
20 | "modules" : {},
21 | /* 应用发布信息 */
22 | "distribute" : {
23 | /* android打包配置 */
24 | "android" : {
25 | "permissions" : [
26 | "",
27 | "",
28 | "",
29 | "",
30 | "",
31 | "",
32 | "",
33 | "",
34 | "",
35 | "",
36 | "",
37 | "",
38 | "",
39 | "",
40 | ""
41 | ]
42 | },
43 | /* ios打包配置 */
44 | "ios" : {},
45 | /* SDK配置 */
46 | "sdkConfigs" : {}
47 | }
48 | },
49 | /* 快应用特有相关 */
50 | "quickapp" : {},
51 | /* 小程序特有相关 */
52 | "mp-weixin" : {
53 | "appid" : "",
54 | "setting" : {
55 | "urlCheck" : false
56 | },
57 | "usingComponents" : true
58 | },
59 | "mp-alipay" : {
60 | "usingComponents" : true
61 | },
62 | "mp-baidu" : {
63 | "usingComponents" : true
64 | },
65 | "mp-toutiao" : {
66 | "usingComponents" : true
67 | },
68 | "uniStatistics": {
69 | "enable": false
70 | },
71 | "vueVersion" : "3"
72 | }
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monorepo",
3 | "version": "0.0.0",
4 | "private": true,
5 | "description": "Monorepo",
6 | "keywords": [
7 | "vite",
8 | "transform",
9 | "tailwind",
10 | "tailwindcss",
11 | "vite-plugin",
12 | "uni-app",
13 | "uniapp",
14 | "miniprogram",
15 | "mini-program"
16 | ],
17 | "homepage": "https://github.com/uni-helper/vite-plugin-uni-tailwind#readme",
18 | "bugs": {
19 | "url": "https://github.com/uni-helper/vite-plugin-uni-tailwind/issues"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/uni-helper/vite-plugin-uni-tailwind.git"
24 | },
25 | "funding": "https://github.com/sponsors/modyqyw",
26 | "license": "MIT",
27 | "author": {
28 | "name": "ModyQyW",
29 | "email": "wurui.dev@gmail.com",
30 | "url": "https://modyqyw.top"
31 | },
32 | "sideEffects": false,
33 | "type": "module",
34 | "scripts": {
35 | "build": "rimraf packages/*/dist --glob && pnpm -r --filter=./packages/* run build && pnpm -r run build-post",
36 | "check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true",
37 | "dep-update": "taze -fw",
38 | "dev": "pnpm stub",
39 | "play:build:h5": "pnpm build && pnpm -C playground run build:h5",
40 | "play:build:mp-weixin": "pnpm build && pnpm -C playground run build:mp-weixin",
41 | "play:dev:h5": "pnpm dev && pnpm -C playground run dev:h5",
42 | "play:dev:mp-weixin": "pnpm dev && pnpm -C playground run dev:mp-weixin",
43 | "prepare": "is-ci || simple-git-hooks",
44 | "prepublishOnly": "pnpm run build",
45 | "release": "lerna version",
46 | "stub": "pnpm -r --filter=./packages/* --parallel run stub",
47 | "test": "vitest run",
48 | "test:coverage": "vitest run --coverage",
49 | "type-check": "tsc --noEmit",
50 | "preversion": "git-branch-is main && conc \"pnpm:check\" \"pnpm:type-check\" \"pnpm:test\""
51 | },
52 | "devDependencies": {
53 | "@biomejs/biome": "^1.9.4",
54 | "@commitlint/cli": "^19.5.0",
55 | "@commitlint/config-conventional": "^19.5.0",
56 | "@commitlint/config-pnpm-scopes": "^19.5.0",
57 | "@dcloudio/types": "^3.4.14",
58 | "@lerna-lite/cli": "^3.10.0",
59 | "@lerna-lite/version": "^3.10.0",
60 | "@types/node": "^22.9.0",
61 | "@vitest/coverage-v8": "^2.1.4",
62 | "concurrently": "^9.1.0",
63 | "conventional-changelog-conventionalcommits": "^7.0.2",
64 | "git-branch-is": "^4.0.0",
65 | "is-ci": "^3.0.1",
66 | "lint-staged": "^15.2.10",
67 | "rimraf": "^6.0.1",
68 | "simple-git-hooks": "^2.11.1",
69 | "taze": "^0.17.2",
70 | "typescript": "^5.6.3",
71 | "unbuild": "^3.0.0-rc.11",
72 | "vitest": "^2.1.4"
73 | },
74 | "packageManager": "pnpm@9.12.3",
75 | "engines": {
76 | "node": ">=18"
77 | },
78 | "publishConfig": {
79 | "access": "public",
80 | "registry": "https://registry.npmjs.org/"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | import { platform } from "@uni-helper/uni-env";
2 | import type { OutputChunk } from "rollup";
3 | import type * as Vite from "vite";
4 | import {
5 | type UniTailwindPluginOptions,
6 | type UniTailwindPluginUserOptions,
7 | defaultOptions,
8 | } from "./options";
9 | import { isScriptFile, transformScript } from "./script";
10 | import { isStyleFile, transformStyle } from "./style";
11 | import { isTemplateFile, transformTemplate } from "./template";
12 | import { babelGetVendorExportMap } from "./tools";
13 |
14 | export * from "./options";
15 | export * from "./script";
16 | export * from "./style";
17 | export * from "./template";
18 |
19 | export default function UniAppTailwindPlugin(
20 | userOptions?: UniTailwindPluginUserOptions,
21 | ): Vite.Plugin {
22 | const options: UniTailwindPluginOptions = {
23 | characterMap: userOptions?.characterMap ?? defaultOptions.characterMap,
24 | directChildrenElements:
25 | userOptions?.directChildrenElements ??
26 | defaultOptions.directChildrenElements,
27 | divideWidthElements:
28 | userOptions?.divideWidthElements ?? defaultOptions.divideWidthElements,
29 | elementMap: userOptions?.elementMap ?? defaultOptions.elementMap,
30 | shouldApply:
31 | userOptions?.shouldApply === undefined
32 | ? defaultOptions.shouldApply
33 | : typeof userOptions?.shouldApply === "boolean"
34 | ? userOptions.shouldApply
35 | : userOptions.shouldApply(platform),
36 | shouldTransformAttribute:
37 | userOptions?.shouldTransformAttribute ??
38 | defaultOptions.shouldTransformAttribute,
39 | shouldTransformScript:
40 | userOptions?.shouldTransformScript ??
41 | defaultOptions.shouldTransformScript,
42 | spaceBetweenElements:
43 | userOptions?.spaceBetweenElements ?? defaultOptions.spaceBetweenElements,
44 | };
45 |
46 | return {
47 | enforce: "post",
48 | generateBundle: (_, bundle) => {
49 | if (!options.shouldApply) return;
50 |
51 | // 解析
52 | const vendorKey = Object.keys(bundle).find((k) =>
53 | k.endsWith("vendor.js"),
54 | ) as string;
55 | const vendorExportMap = babelGetVendorExportMap(
56 | bundle[vendorKey] as OutputChunk,
57 | );
58 |
59 | // 转换
60 | for (const [fileName, asset] of Object.entries(bundle)) {
61 | if (asset.type === "chunk" && isScriptFile(fileName)) {
62 | asset.code = transformScript(asset, vendorExportMap, options);
63 | } else if (asset.type === "asset") {
64 | const { source } = asset;
65 | if (source && typeof source === "string") {
66 | let newSource = "";
67 | if (isTemplateFile(fileName))
68 | newSource = transformTemplate(source, options);
69 | if (isStyleFile(fileName))
70 | newSource = transformStyle(source, options);
71 | asset.source = newSource || source;
72 | }
73 | }
74 | }
75 | },
76 | name: "vite:uni-tailwind",
77 | };
78 | }
79 |
--------------------------------------------------------------------------------
/playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build:app": "uni build -p app",
7 | "build:app-android": "uni build -p app-android",
8 | "build:app-ios": "uni build -p app-ios",
9 | "build:custom": "uni build -p",
10 | "build:h5": "uni build",
11 | "build:h5:ssr": "uni build --ssr",
12 | "build:mp-alipay": "uni build -p mp-alipay",
13 | "build:mp-baidu": "uni build -p mp-baidu",
14 | "build:mp-kuaishou": "uni build -p mp-kuaishou",
15 | "build:mp-lark": "uni build -p mp-lark",
16 | "build:mp-qq": "uni build -p mp-qq",
17 | "build:mp-toutiao": "uni build -p mp-toutiao",
18 | "build:mp-weixin": "uni build -p mp-weixin",
19 | "build:quickapp-webview": "uni build -p quickapp-webview",
20 | "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
21 | "build:quickapp-webview-union": "uni build -p quickapp-webview-union",
22 | "dev:app": "uni -p app",
23 | "dev:app-android": "uni -p app-android",
24 | "dev:app-ios": "uni -p app-ios",
25 | "dev:custom": "uni -p",
26 | "dev:h5": "uni",
27 | "dev:h5:ssr": "uni --ssr",
28 | "dev:mp-alipay": "uni -p mp-alipay",
29 | "dev:mp-baidu": "uni -p mp-baidu",
30 | "dev:mp-kuaishou": "uni -p mp-kuaishou",
31 | "dev:mp-lark": "uni -p mp-lark",
32 | "dev:mp-qq": "uni -p mp-qq",
33 | "dev:mp-toutiao": "uni -p mp-toutiao",
34 | "dev:mp-weixin": "uni -p mp-weixin",
35 | "dev:quickapp-webview": "uni -p quickapp-webview",
36 | "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
37 | "dev:quickapp-webview-union": "uni -p quickapp-webview-union"
38 | },
39 | "dependencies": {
40 | "@dcloudio/uni-app": "3.0.0-4020920240930001",
41 | "@dcloudio/uni-app-plus": "3.0.0-4020920240930001",
42 | "@dcloudio/uni-components": "3.0.0-4020920240930001",
43 | "@dcloudio/uni-h5": "3.0.0-4020920240930001",
44 | "@dcloudio/uni-mp-alipay": "3.0.0-4020920240930001",
45 | "@dcloudio/uni-mp-baidu": "3.0.0-4020920240930001",
46 | "@dcloudio/uni-mp-kuaishou": "3.0.0-4020920240930001",
47 | "@dcloudio/uni-mp-lark": "3.0.0-4020920240930001",
48 | "@dcloudio/uni-mp-qq": "3.0.0-4020920240930001",
49 | "@dcloudio/uni-mp-toutiao": "3.0.0-4020920240930001",
50 | "@dcloudio/uni-mp-weixin": "3.0.0-4020920240930001",
51 | "@dcloudio/uni-quickapp-webview": "3.0.0-4020920240930001",
52 | "@uni-helper/uni-env": "^0.1.5",
53 | "vue": "~3.4.38",
54 | "vue-i18n": "^9.14.1"
55 | },
56 | "devDependencies": {
57 | "@dcloudio/types": "^3.4.14",
58 | "@dcloudio/uni-automator": "3.0.0-4020920240930001",
59 | "@dcloudio/uni-cli-shared": "3.0.0-4020920240930001",
60 | "@dcloudio/uni-stacktracey": "3.0.0-4020920240930001",
61 | "@dcloudio/vite-plugin-uni": "3.0.0-4020920240930001",
62 | "@uni-helper/vite-plugin-uni-tailwind": "workspace:*",
63 | "postcss": "^8.4.47",
64 | "postcss-preset-env": "^10.0.9",
65 | "tailwind-extensions": "^0.22.1",
66 | "tailwindcss": "^3.4.14",
67 | "typescript": "~5.5.4",
68 | "vite": "^5.4.10"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | pnpm-debug.log*
9 | run
10 |
11 | # Diagnostic reports (https://nodejs.org/api/report.html)
12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 | *.lcov
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules
44 | jspm_packages
45 |
46 | # Snowpack dependency directory (https://snowpack.dev/)
47 | web_modules
48 |
49 | # Temp directory
50 | .temp
51 | .tmp
52 |
53 | # TypeScript cache
54 | *.tsbuildinfo
55 |
56 | # Optional npm cache directory
57 | .npm
58 |
59 | # Optional eslint cache
60 | .eslintcache
61 |
62 | # Microbundle cache
63 | .rpt2_cache
64 | .rts2_cache_cjs
65 | .rts2_cache_es
66 | .rts2_cache_umd
67 |
68 | # Optional REPL history
69 | .node_repl_history
70 |
71 | # Output of 'npm pack'
72 | *.tgz
73 |
74 | # Yarn Integrity file
75 | .yarn-integrity
76 |
77 | # dotenv environment variables file
78 | .env
79 | .env.test
80 | .env.*.test
81 | .env.local
82 | .env.*.local
83 | *.local
84 |
85 | # parcel-bundler cache (https://parceljs.org/)
86 | .cache
87 | .parcel-cache
88 |
89 | # Electron
90 | dist-electron
91 | dist_electron
92 |
93 | # Tauri
94 | dist-tauri
95 | dist_tauri
96 |
97 | # Cordova
98 | dist-cordova
99 | dist_cordova
100 |
101 | # Capacitor
102 | dist-capacitor
103 | dist_capacitor
104 |
105 | # Next.js build output
106 | .next
107 | out
108 |
109 | # Umi.js build output
110 | .umi
111 | .umi-production
112 | .umi-test
113 |
114 | # Nuxt.js build / generate output
115 | .nuxt
116 | dist
117 |
118 | # Rax.js build
119 | .rax
120 |
121 | # Vuepress build output
122 | .vuepress/dist
123 |
124 | # SSR
125 | dist-ssr
126 | dist_ssr
127 |
128 | # SSG
129 | dist-ssg
130 | dist_ssg
131 |
132 | # Serverless
133 | .serverless
134 | .dynamodb
135 | .s3
136 | .buckets
137 | .seeds
138 |
139 | # FuseBox cache
140 | .fusebox
141 |
142 | # TernJS port file
143 | .tern-port
144 |
145 | # Cypress
146 | /cypress/videos/
147 | /cypress/screenshots/
148 |
149 | # Editor
150 | .vscode-test
151 | .vscode/**
152 | !.vscode/extensions.json
153 | !.vscode/settings.json
154 | *.vsix
155 | .idea
156 | .hbuilder
157 | .hbuilderx
158 | *.suo
159 | *.ntvs*
160 | *.njsproj
161 | *.sln
162 | *.sw?
163 |
164 | # yarn v2
165 | .yarn/cache
166 | .yarn/unplugged
167 | .yarn/build-state.yml
168 | .yarn/install-state.gz
169 | .pnp.*
170 |
171 | # Apple
172 | .DS_Store
173 | *.p12
174 | *.mobileprovision
175 |
176 | # Android
177 | *.keystore
178 |
--------------------------------------------------------------------------------
/packages/core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { defaultOptions } from "./options";
2 |
3 | export const replaceUnicode = (source: string, type: "style" | "template") => {
4 | if (type === "style") {
5 | return source.replace(/^\.\\(\d{2})/, (match, p1) => {
6 | const count =
7 | Number.parseInt(String.fromCodePoint(Number.parseInt(p1, 16)), 10) - 1;
8 | return `.${"x".repeat(count)}`;
9 | });
10 | }
11 | const newSource = source.replace(/^(\d)|\s(\d)/, (match) => {
12 | const count = Number.parseInt(match, 10) - 1;
13 | return (match.startsWith(" ") ? " " : "") + "x".repeat(count);
14 | });
15 | return newSource;
16 | };
17 |
18 | export const replaceCharacters = (
19 | source: string,
20 | type: "style" | "template",
21 | options = defaultOptions,
22 | ) => {
23 | let newSource = source;
24 | for (const [key, value] of options.characterMap) {
25 | const regExp = new RegExp(
26 | key.startsWith("\\")
27 | ? key
28 | : `${type === "template" ? "\\" : "\\\\\\"}${key}`,
29 | "g",
30 | );
31 | newSource = newSource.replace(regExp, value);
32 | }
33 | return newSource;
34 | };
35 |
36 | export const replaceElements = (source: string, options = defaultOptions) => {
37 | let newSource = source;
38 |
39 | const {
40 | directChildrenElements,
41 | divideWidthElements,
42 | elementMap,
43 | spaceBetweenElements,
44 | } = options;
45 | newSource = newSource
46 | // direct children * https://tailwindcss.com/docs/hover-focus-and-other-states#styling-direct-children
47 | .replace(
48 | /^\.(\\\*\\:.*>\s?)(\*)/,
49 | directChildrenElements.map((element) => `.$1${element}`).join(","),
50 | )
51 | // space * https://tailwindcss.com/docs/space
52 | .replace(
53 | /^\.(-?space-\w)(-.+?)\s?>.*/,
54 | spaceBetweenElements
55 | .map((element) => `.$1$2>${element}:not([hidden]):not(:first-child)`)
56 | .join(","),
57 | )
58 | .replace(
59 | /^\.(-?space-\w-reverse).*/,
60 | spaceBetweenElements
61 | .map((element) => `.$1>${element}:not([hidden]):not(:first-child)`)
62 | .join(","),
63 | )
64 | // divide * https://tailwindcss.com/docs/divide-width
65 | .replace(
66 | /^\.(-?divide-\w+)(-.+?)?\s?>.*/,
67 | divideWidthElements
68 | .map((element) => `.$1$2>${element}:not([hidden]):not(:first-child)`)
69 | .join(","),
70 | )
71 | .replace(
72 | /^\.(-?divide-\w-reverse).*/,
73 | divideWidthElements
74 | .map((element) => `.$1>${element}:not([hidden]):not(:first-child)`)
75 | .join(","),
76 | );
77 |
78 | for (const [key, value] of elementMap) {
79 | newSource =
80 | key === "*"
81 | ? newSource.replace(
82 | new RegExp(
83 | `(? ])\\${key}(?=[\\,\\s\\0{])|(? ])\\${key}$`,
84 | ),
85 | value.join(","),
86 | )
87 | : newSource.replace(
88 | new RegExp(
89 | `(? ])${key}(?=[\\,\\s\\0{])|(? ])${key}$`,
90 | ),
91 | value.join(","),
92 | );
93 | }
94 |
95 | return newSource;
96 | };
97 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [0.15.2](https://github.com/uni-helper/vite-plugin-uni-tailwind/compare/v0.15.1...v0.15.2) (2024-11-07)
7 |
8 | ### Bug Fixes
9 |
10 | * fix transformed quotes, close ([ce18454](https://github.com/uni-helper/vite-plugin-uni-tailwind/commit/ce18454c1a3d3273b20317912feeb1bc958dab81)) - by @ModyQyW
11 |
12 | ## [0.15.1](https://github.com/uni-helper/vite-plugin-uni-tailwind/compare/v0.15.0...v0.15.1) (2024-10-28)
13 |
14 | ### Bug Fixes
15 |
16 | * fix style judgement ([76378eb](https://github.com/uni-helper/vite-plugin-uni-tailwind/commit/76378eb60b048b075d6ad14ca9f2e7fb3f013b97)) - by @ModyQyW
17 | * fix template judgement ([fa43006](https://github.com/uni-helper/vite-plugin-uni-tailwind/commit/fa430065e69be2eb864d7ab820ca4b254df3ccd6)) - by @ModyQyW
18 |
19 | ## 0.15.0 (2024-03-13)
20 |
21 | * feat: 支持 `tailwindcss@3.4` [设置直接子元素样式](https://tailwindcss.com/docs/hover-focus-and-other-states#styling-direct-children)
22 | * feat: 支持修复 `.text-[32rpx]` 样式设置,现在会正确地使用 `font-size` 而不是 `color`
23 |
24 | ## 0.14.2 (2023-11-27)
25 |
26 | * feat: `transformScript` 支持对象键转换
27 |
28 | ## 0.14.1 (2023-11-27)
29 |
30 | * fix: 修复 unicode 没有被替换的问题
31 |
32 | ## 0.14.0 (2023-11-22)
33 |
34 | * fix: 修复正则表达式不正确的问题
35 |
36 | ## 0.14.0-0 (2023-10-13)
37 |
38 | * feat: 增加 `shouldTransformScript` 选项,允许转换脚本文件
39 | * feat!: 选项 `shouldTransformTemplateAttribute` 重命名为 `shouldTransformAttribute`,该选项目前也被用于脚本文件的转换
40 | * feat!: 调整 `elementMap` 默认值,移除 `span` 的转换,`span` 目前被用于微信小程序 Skyline 引擎元素
41 |
42 | ## 0.13.1 (2023-09-27)
43 |
44 | * revert: 回滚 0.13.0
45 |
46 | ## 0.13.0 (2023-09-27)
47 |
48 | * feat: 移除 `babel`,现在直接替换模板内的相关类值
49 |
50 | ## 0.12.0 (2023-09-20)
51 |
52 | * feat: 增加 `@` 字符支持
53 | * fix: 修复字符替换顺序
54 |
55 | ## 0.11.0 (2023-09-14)
56 |
57 | * feat: 增加 `?` 字符支持
58 |
59 | ## 0.10.0 (2023-05-16)
60 |
61 | * build: 切换到 `unbuild`
62 | * feat: 增加 `shouldTransformTemplateAttribute` 选项,允许自定义更多类名支持
63 | * feat: 增加默认类名支持
64 | * feat: 增加 `$` 字符支持
65 | * feat!: 移除 `apply` 和 `getShouldApply` 选项,请使用 `shouldApply` 代替
66 |
67 | ## 0.9.1 (2023-01-04)
68 |
69 | * build: 切换到 `rollup`
70 |
71 | ## 0.9.0 (2022-12-14)
72 |
73 | * feat: 支持 vite@4
74 | * feat: 支持处理更多的类属性
75 | * feat!: 要求 `node >= 14.18`
76 |
77 | ## 0.8.1 (2022-11-16)
78 |
79 | * fix: 修复构建
80 |
81 | ## 0.8.0 (2022-11-16)
82 |
83 | * feat!: 迁移到 `@uni-helper/vite-plugin-uni-tailwind`
84 | * feat!: 要求 `node >= 14.16`
85 |
86 | ## 0.7.1 (2022-11-03)
87 |
88 | * perf: 优化默认的 `getShouldApply` 方法
89 |
90 | ## 0.7.0 (2022-10-20)
91 |
92 | * feat: 支持 `=` 和 `&` 符号转换
93 |
94 | ## 0.6.2 (2022-10-12)
95 |
96 | * fix: 修复构建
97 |
98 | ## 0.6.1 (2022-10-12)
99 |
100 | * perf: 移除多余元素映射,现在生成的样式代码更少
101 |
102 | * fix: 修复 `space-between` 和 `divide-width` 样式替换不正确的问题
103 |
104 | ## 0.6.0 (2022-09-30)
105 |
106 | * feat: 支持替换模板 `class`、`classname`、`class-name`、`*-class`、`*-classname`、`*-class-name` 中的类名
107 | * feat!: 现在要求使用 `node >= 12.2`
108 | * feat!: 现在构建目标是 `node12.2`
109 | * fix: 修复了构建不正常的问题
110 |
111 | ## 0.5.0
112 |
113 | * feat!: 移除 `*` 特殊处理,现在可以指定插件应用于哪些平台,默认为 `MP` 和 `QUICKAPP`
114 | * feat!: 更名为 `vite-plugin-uni-app-tailwind`,现在只支持 `vite`
115 |
116 | ## 0.4.1
117 |
118 | * fix: 修复编译成 `APP` 时 `uni-app` 插入的样式选择器替换不正确的问题
119 |
120 | ## 0.4.0
121 |
122 | * feat: 增加 `replaceStarSelectorPlatform` 和 `getShouldReplaceStarSelector` 选项
123 | * feat!: 增加 `*` 特殊处理,现在默认只在 `MP` 和 `QUICKAPP` 平台替换 `*`
124 | * fix: 修复选择器带有 `uni-` 前缀时仍被替换的问题
125 | * fix: 修复编译成 `APP` 时 `uni-app` 插入的样式选择器替换不正确的问题
126 |
127 | ## 0.3.1
128 |
129 | * fix: 补充元素映射
130 |
131 | ## 0.3.0
132 |
133 | * feat: 增加 `+` 转码
134 |
135 | ## 0.2.2
136 |
137 | * fix: 修复类型定义
138 |
139 | ## 0.2.1
140 |
141 | * fix: 修复构建和导出
142 |
143 | ## 0.2.0
144 |
145 | * feat!: 重命名为 `unplugin-uni-app-tailwind`
146 | * feat: 使用 `unplugin` 支持 `webpack`
147 |
148 | ## 0.1.0
149 |
150 | 同 0.0.3。
151 |
152 | ## 0.0.3
153 |
154 | * fix: 修复错误处理文件内容的问题
155 |
156 | ## 0.0.2
157 |
158 | * fix: 修复导入
159 |
160 | ## 0.0.1
161 |
162 | 初始化项目。
163 |
--------------------------------------------------------------------------------
/packages/core/src/template/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { transformTemplate } from ".";
3 |
4 | describe("template", () => {
5 | it("replace []", () => {
6 | expect(transformTemplate('')).toBe(
7 | '',
8 | );
9 | });
10 |
11 | it("replace [] and ()", () => {
12 | expect(transformTemplate('')).toBe(
13 | '',
14 | );
15 | });
16 |
17 | it("replace !, [] and .", () => {
18 | expect(transformTemplate('')).toBe(
19 | '',
20 | );
21 | });
22 |
23 | it("replace /", () => {
24 | expect(transformTemplate('')).toBe(
25 | '',
26 | );
27 | });
28 |
29 | it("replace [], (), ' and /", () => {
30 | expect(
31 | transformTemplate(""),
32 | ).toBe('');
33 | });
34 |
35 | it("replace [] and .", () => {
36 | expect(transformTemplate('')).toBe(
37 | '',
38 | );
39 | });
40 |
41 | it("replace :", () => {
42 | expect(transformTemplate('')).toBe(
43 | '',
44 | );
45 | });
46 |
47 | it("replace [] and #", () => {
48 | expect(transformTemplate('')).toBe(
49 | '',
50 | );
51 | });
52 |
53 | it("replace [], (), and ,", () => {
54 | expect(
55 | transformTemplate(''),
56 | ).toBe('');
57 | });
58 |
59 | it("replace [] and %", () => {
60 | expect(transformTemplate('')).toBe(
61 | '',
62 | );
63 | });
64 |
65 | it("replace complex class", () => {
66 | expect(
67 | transformTemplate(
68 | ``,
69 | ),
70 | ).toBe(
71 | ``,
72 | );
73 | });
74 |
75 | it("support *-classname", () => {
76 | expect(
77 | transformTemplate(''),
78 | ).toBe('');
79 | expect(
80 | transformTemplate(
81 | '',
82 | ),
83 | ).toBe('');
84 | });
85 |
86 | it("support *-class-name", () => {
87 | expect(
88 | transformTemplate(''),
89 | ).toBe('');
90 | expect(
91 | transformTemplate(
92 | '',
93 | ),
94 | ).toBe('');
95 | });
96 |
97 | it("replace unicode", () => {
98 | expect(transformTemplate('')).toBe(
99 | '',
100 | );
101 | expect(transformTemplate('')).toBe(
102 | '',
103 | );
104 | expect(
105 | transformTemplate(''),
106 | ).toBe('');
107 | expect(
108 | transformTemplate(''),
109 | ).toBe('');
110 | });
111 |
112 | it("replace *", () => {
113 | expect(transformTemplate('')).toBe(
114 | '',
115 | );
116 | });
117 | });
118 |
--------------------------------------------------------------------------------
/packages/core/src/options.ts:
--------------------------------------------------------------------------------
1 | import { isMp, isQuickapp } from "@uni-helper/uni-env";
2 |
3 | export interface UniTailwindPluginUserOptions {
4 | /** 特殊字符映射 */
5 | characterMap?: [string, string][];
6 | /** Direct children 元素映射 */
7 | directChildrenElements?: string[];
8 | /** Divide width 元素映射 */
9 | divideWidthElements?: string[];
10 | /** 元素映射 */
11 | elementMap?: [string, string[]][];
12 | /**
13 | * 是否应用该插件
14 | *
15 | * 默认编译为小程序和快应用时应用
16 | */
17 | shouldApply?: ((currentPlatform: string) => boolean) | boolean;
18 | /**
19 | * 是否转换某个 attribute
20 | *
21 | * 默认转换以 class、Class、classname、className、ClassName、class-name 结尾的 attribute
22 | */
23 | shouldTransformAttribute?: (attribute: string) => boolean;
24 | /**
25 | * 是否转换某个脚本文件
26 | *
27 | * 默认转换 pages、components、layouts 开头的脚本文件
28 | */
29 | shouldTransformScript?: (fileName: string) => boolean;
30 | /** Space between 元素映射 */
31 | spaceBetweenElements?: string[];
32 | }
33 |
34 | export type UniTailwindPluginOptions = Omit<
35 | Required,
36 | "shouldApply"
37 | > & {
38 | /**
39 | * 是否应用该插件
40 | *
41 | * 默认编译为小程序和快应用时应用
42 | */
43 | shouldApply: boolean;
44 | };
45 |
46 | /**
47 | * 是否应用该插件
48 | *
49 | * 默认编译为小程序和快应用时应用
50 | */
51 | export const defaultShouldApply = isMp || isQuickapp;
52 |
53 | /**
54 | * 是否转换某个 attribute
55 | *
56 | * 默认会转换以 class、Class、classname、className、ClassName、class-name 结尾的 attribute
57 | */
58 | export const defaultShouldTransformAttribute = (attribute: string) =>
59 | ["class", "Class", "classname", "className", "ClassName", "class-name"].some(
60 | (item) => attribute.endsWith(item),
61 | );
62 |
63 | /**
64 | * 是否转换某个脚本文件
65 | *
66 | * 默认会转换路径以 pages、components、layouts 开头的脚本文件
67 | */
68 | export const defaultShouldTransformScript = (fileName: string) =>
69 | ["pages", "components", "layouts"].some((item) => fileName.startsWith(item));
70 |
71 | /** 特殊字符映射 */
72 | export const defaultCharacterMap: [string, string][] = [
73 | ["[", "-"],
74 | ["]", "-"],
75 | ["(", "-"],
76 | [")", "-"],
77 | ["$", "-v-"], // css variable
78 | ["#", "-h-"], // hash
79 | ["!", "-i-"], // important
80 | ["/", "-s-"], // slash
81 | [".", "-d-"], // dot
82 | [":", "_"], // colon
83 | ["%", "-p-"], // percentage
84 | ["'", "-q-"], // quote
85 | ["+", "-a-"], // add
86 | ["=", "-e-"], // equal
87 | ["&", "-n-"], // and
88 | ["?", "-qm-"], // question mark
89 | ["@", "-at-"], // at
90 | ["*", "-w-"], // wildcard
91 | [",\\s", "-c-"], // comma
92 | [",", "-c-"], // comma
93 | ["\\2c\\s", "-c-"], // comma
94 | ["\\2c", "-c-"], // comma
95 | ["\\\\2c\\s", "-c-"], // comma
96 | ["\\\\2c", "-c-"], // comma
97 | ];
98 |
99 | /**
100 | * Space between 元素映射
101 | *
102 | * https://tailwindcss.com/docs/space
103 | */
104 | export const defaultSpaceBetweenElements = ["view", "button", "text", "image"];
105 |
106 | /**
107 | * Divide width 元素映射
108 | *
109 | * https://tailwindcss.com/docs/divide-width
110 | */
111 | export const defaultDivideWidthElements = ["view", "button", "text", "image"];
112 |
113 | /**
114 | * Direct Children 元素映射
115 | *
116 | * https://tailwindcss.com/docs/hover-focus-and-other-states#styling-direct-children
117 | */
118 | export const defaultDirectChildrenElements = [
119 | "view",
120 | "button",
121 | "text",
122 | "image",
123 | ];
124 |
125 | /** 元素映射 */
126 | export const defaultElementMap: [string, string[]][] = [
127 | ["html", ["page"]],
128 | ["body", ["page"]],
129 | ["img", ["image"]],
130 | ["a", ["functional-page-navigator", "navigator"]],
131 | [
132 | "*",
133 | [
134 | "page",
135 | "cover-image",
136 | "cover-view",
137 | "match-media",
138 | "movable-area",
139 | "movable-view",
140 | "scroll-view",
141 | "swiper",
142 | "swiper-item",
143 | "view",
144 | "icon",
145 | "progress",
146 | "rich-text",
147 | "text",
148 | "button",
149 | "checkbox",
150 | "checkbox-group",
151 | "editor",
152 | "form",
153 | "input",
154 | "label",
155 | "picker",
156 | "picker-view",
157 | "picker-view-column",
158 | "radio",
159 | "radio-group",
160 | "slider",
161 | "switch",
162 | "textarea",
163 | "functional-page-navigator",
164 | "navigator",
165 | "audio",
166 | "camera",
167 | "image",
168 | "live-player",
169 | "live-pusher",
170 | "video",
171 | "voip-room",
172 | "map",
173 | "canvas",
174 | "ad",
175 | "ad-custom",
176 | "official-account",
177 | "open-data",
178 | "web-view",
179 | "navigation-bar",
180 | "page-meta",
181 | ],
182 | ],
183 | ];
184 |
185 | export const defaultOptions: UniTailwindPluginOptions = {
186 | characterMap: defaultCharacterMap,
187 | directChildrenElements: defaultDirectChildrenElements,
188 | divideWidthElements: defaultDivideWidthElements,
189 | elementMap: defaultElementMap,
190 | shouldApply: defaultShouldApply,
191 | shouldTransformAttribute: defaultShouldTransformAttribute,
192 | shouldTransformScript: defaultShouldTransformScript,
193 | spaceBetweenElements: defaultSpaceBetweenElements,
194 | } as const;
195 |
--------------------------------------------------------------------------------
/packages/core/src/style/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { transformStyle } from ".";
3 |
4 | describe("style", () => {
5 | it("replace []", () => {
6 | expect(transformStyle(".w-\\[10px\\] {}")).toBe(".w--10px- {}");
7 | });
8 |
9 | it("replace [] and ()", () => {
10 | expect(transformStyle(".bg-\\[url\\(\\)\\] {}")).toBe(".bg--url--- {}");
11 | });
12 |
13 | it("replace !, [] and .", () => {
14 | expect(transformStyle(".\\!w-\\[200\\.5px\\] {}")).toBe(
15 | ".-i-w--200-d-5px- {}",
16 | );
17 | });
18 |
19 | it("replace /", () => {
20 | expect(transformStyle(".top-1\\/2 {}")).toBe(".top-1-s-2 {}");
21 | });
22 |
23 | it("replace [], (), ' and /", () => {
24 | expect(
25 | transformStyle(".bg-\\[url\\(\\'\\/img\\/grid\\.svg\\'\\)\\] {}"),
26 | ).toBe(".bg--url--q--s-img-s-grid-d-svg-q--- {}");
27 | });
28 |
29 | it("replace [] and .", () => {
30 | expect(transformStyle(".w-\\[200\\.5rpx\\] {}")).toBe(".w--200-d-5rpx- {}");
31 | });
32 |
33 | it("replace :", () => {
34 | expect(transformStyle(".sm\\:mx-auto {}")).toBe(".sm_mx-auto {}");
35 | });
36 |
37 | it("replace [] and #", () => {
38 | expect(transformStyle(".bg-\\[\\#fff\\] {}")).toBe(".bg---h-fff- {}");
39 | });
40 |
41 | it("replace [], (), and ,", () => {
42 | expect(transformStyle(".bg-\\[rgba\\(255\\,255\\,255\\,1\\)\\] {}")).toBe(
43 | ".bg--rgba-255-c-255-c-255-c-1-- {}",
44 | );
45 | });
46 |
47 | it("replace [] and %", () => {
48 | expect(transformStyle(".w-\\[10\\%\\] {}")).toBe(".w--10-p-- {}");
49 | });
50 |
51 | it("replace space-between", () => {
52 | expect(
53 | transformStyle(".space-x-0 > :not([hidden]) ~ :not([hidden]) {}"),
54 | ).toBe(
55 | ".space-x-0>view:not([hidden]):not(:first-child),.space-x-0>button:not([hidden]):not(:first-child),.space-x-0>text:not([hidden]):not(:first-child),.space-x-0>image:not([hidden]):not(:first-child) {}",
56 | );
57 | expect(transformStyle(".space-x-0>:not([hidden])~:not([hidden]) {}")).toBe(
58 | ".space-x-0>view:not([hidden]):not(:first-child),.space-x-0>button:not([hidden]):not(:first-child),.space-x-0>text:not([hidden]):not(:first-child),.space-x-0>image:not([hidden]):not(:first-child) {}",
59 | );
60 | expect(
61 | transformStyle(".space-x-reverse > :not([hidden]) ~ :not([hidden]) {}"),
62 | ).toBe(
63 | ".space-x-reverse>view:not([hidden]):not(:first-child),.space-x-reverse>button:not([hidden]):not(:first-child),.space-x-reverse>text:not([hidden]):not(:first-child),.space-x-reverse>image:not([hidden]):not(:first-child) {}",
64 | );
65 | expect(
66 | transformStyle(".space-x-reverse>:not([hidden])~:not([hidden]) {}"),
67 | ).toBe(
68 | ".space-x-reverse>view:not([hidden]):not(:first-child),.space-x-reverse>button:not([hidden]):not(:first-child),.space-x-reverse>text:not([hidden]):not(:first-child),.space-x-reverse>image:not([hidden]):not(:first-child) {}",
69 | );
70 | });
71 |
72 | it("replace divide-width", () => {
73 | expect(
74 | transformStyle(".divide-x > :not([hidden]) ~ :not([hidden]) {}"),
75 | ).toBe(
76 | ".divide-x>view:not([hidden]):not(:first-child),.divide-x>button:not([hidden]):not(:first-child),.divide-x>text:not([hidden]):not(:first-child),.divide-x>image:not([hidden]):not(:first-child) {}",
77 | );
78 | expect(transformStyle(".divide-x>:not([hidden])~:not([hidden]) {}")).toBe(
79 | ".divide-x>view:not([hidden]):not(:first-child),.divide-x>button:not([hidden]):not(:first-child),.divide-x>text:not([hidden]):not(:first-child),.divide-x>image:not([hidden]):not(:first-child) {}",
80 | );
81 | expect(
82 | transformStyle(".divide-x-reverse > :not([hidden]) ~ :not([hidden]) {}"),
83 | ).toBe(
84 | ".divide-x-reverse>view:not([hidden]):not(:first-child),.divide-x-reverse>button:not([hidden]):not(:first-child),.divide-x-reverse>text:not([hidden]):not(:first-child),.divide-x-reverse>image:not([hidden]):not(:first-child) {}",
85 | );
86 | expect(
87 | transformStyle(".divide-x-reverse>:not([hidden])~:not([hidden]) {}"),
88 | ).toBe(
89 | ".divide-x-reverse>view:not([hidden]):not(:first-child),.divide-x-reverse>button:not([hidden]):not(:first-child),.divide-x-reverse>text:not([hidden]):not(:first-child),.divide-x-reverse>image:not([hidden]):not(:first-child) {}",
90 | );
91 | });
92 |
93 | it("replace html", () => {
94 | expect(transformStyle("html {}")).toBe("page {}");
95 | });
96 |
97 | it("replace body", () => {
98 | expect(transformStyle("body {}")).toBe("page {}");
99 | expect(transformStyle(".uv-form-item__body {}")).toBe(
100 | ".uv-form-item__body {}",
101 | );
102 | });
103 |
104 | it("replace img", () => {
105 | expect(
106 | transformStyle("img,svg,video,canvas,audio,iframe,embed,object {}"),
107 | ).toBe("image,svg,video,canvas,audio,iframe,embed,object {}");
108 | expect(
109 | transformStyle(
110 | "uni-image>img{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;display:block;position:absolute;top:0;left:0;width:100%;height:100%;opacity:0}",
111 | ),
112 | ).toBe(
113 | "uni-image>img{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;display:block;position:absolute;top:0;left:0;width:100%;height:100%;opacity:0}",
114 | );
115 | expect(
116 | transformStyle(".ql-container img{display:inline-block;max-width:100%}"),
117 | ).toBe(".ql-container img{display:inline-block;max-width:100%}");
118 | });
119 |
120 | it("replace a", () => {
121 | expect(transformStyle("a {}")).toBe(
122 | "functional-page-navigator,navigator {}",
123 | );
124 | expect(transformStyle(".uv-form-item.data-v-591e6b6a {}")).toBe(
125 | ".uv-form-item.data-v-591e6b6a {}",
126 | );
127 | expect(transformStyle("textarea {}")).toBe("textarea {}");
128 | });
129 |
130 | it("replace *", () => {
131 | expect(transformStyle("*, ::before, ::after {}")).toBe(
132 | "page,cover-image,cover-view,match-media,movable-area,movable-view,scroll-view,swiper,swiper-item,view,icon,progress,rich-text,text,button,checkbox,checkbox-group,editor,form,input,label,picker,picker-view,picker-view-column,radio,radio-group,slider,switch,textarea,functional-page-navigator,navigator,audio,camera,image,live-player,live-pusher,video,voip-room,map,canvas,ad,ad-custom,official-account,open-data,web-view,navigation-bar,page-meta, ::before, ::after {}",
133 | );
134 | expect(transformStyle(".\\*\\:rounded-full>* {}")).toBe(
135 | ".-w-_rounded-full>view,.-w-_rounded-full>button,.-w-_rounded-full>text,.-w-_rounded-full>image {}",
136 | );
137 | });
138 |
139 | it("replace unicode", () => {
140 | expect(transformStyle(".\\32xl\\:rounded-2xl {}")).toBe(
141 | ".xxl_rounded-2xl {}",
142 | );
143 | expect(transformStyle(".\\33xl\\:rounded-3xl {}")).toBe(
144 | ".xxxl_rounded-3xl {}",
145 | );
146 | });
147 |
148 | it("fix rpx", () => {
149 | expect(transformStyle(".text-\\[32rpx\\] { color: 32rpx; }")).toBe(
150 | ".text--32rpx- { font-size: 32rpx; }",
151 | );
152 | });
153 | });
154 |
--------------------------------------------------------------------------------
/packages/core/src/tools/babel.ts:
--------------------------------------------------------------------------------
1 | import * as babel from "@babel/core";
2 | import type { PluginItem } from "@babel/core";
3 | import type { OutputChunk } from "rollup";
4 | import { defaultOptions } from "../options";
5 | import { replaceCharacters, replaceUnicode } from "../utils";
6 |
7 | type Babel = typeof babel;
8 |
9 | export const babelTransformClass = (
10 | source: string,
11 | options = defaultOptions,
12 | ) => {
13 | // 进行匹配
14 | // eslint-disable-next-line unicorn/better-regex
15 | const ScriptsRegExp = /(\{\{)(.+?)(\}\})/g;
16 | const results = [...source.matchAll(ScriptsRegExp)];
17 | // 没有 {{}},直接替换
18 | if (results.length === 0) {
19 | return replaceCharacters(
20 | replaceUnicode(source, "template"),
21 | "template",
22 | options,
23 | );
24 | }
25 | // 有 {{}}
26 | // 先替换 {{}} 中内容为 ReplaceMarker
27 | const ReplaceMarker = "__VITE_PLUGIN_UNI_APP_TAILWIND__";
28 | let newSource = source.replaceAll(ScriptsRegExp, `{{${ReplaceMarker}}}`);
29 | // 遍历处理字符
30 | for (const result of results) {
31 | // 获取 {{}} 中内容
32 | const input = result.input ?? result[0];
33 | const inside = input.replaceAll(ScriptsRegExp, "$2");
34 | // 处理字符
35 | const transformed = babel.transformSync(inside, {
36 | generatorOpts: {
37 | compact: true,
38 | retainLines: true,
39 | },
40 | configFile: false,
41 | plugins: [
42 | (instance: Babel): PluginItem => ({
43 | visitor: {
44 | StringLiteral(path) {
45 | const raw = path.node.value;
46 | const replaced = replaceCharacters(
47 | replaceUnicode(raw, "template"),
48 | "template",
49 | options,
50 | );
51 | if (replaced !== raw)
52 | path.replaceWith(instance.types.stringLiteral(replaced));
53 | },
54 | },
55 | }),
56 | ],
57 | });
58 | // 用处理后的内容替换 ReplaceMarker
59 | if (transformed?.code)
60 | newSource = newSource.replace(
61 | ReplaceMarker,
62 | transformed.code.replace(/;$/, "").replace(/"/g, "'"),
63 | );
64 | }
65 | return newSource;
66 | };
67 |
68 | export const babelGetVendorRenderPropsExportName = (
69 | vendorAsset: OutputChunk,
70 | ) => {
71 | let name = "";
72 | const { modules } = vendorAsset;
73 | for (const [, m] of Object.entries(modules)) {
74 | const { code } = m;
75 | if (!code || !code.includes("renderProps")) continue;
76 | const ast = babel.parseSync(code);
77 | if (!ast) continue;
78 | babel.traverse(ast, {
79 | CallExpression: (path) => {
80 | if (path.node.callee.type !== "Identifier") return;
81 | if (path.node.callee.name !== "renderProps") return;
82 | if (!path.parentPath.isArrowFunctionExpression()) return;
83 | if (!path.parentPath.parentPath.isVariableDeclarator()) return;
84 | if (path.parentPath.parentPath.node.id.type !== "Identifier") return;
85 | name = path.parentPath.parentPath.node.id.name;
86 | path.skip();
87 | },
88 | });
89 | if (name) break;
90 | }
91 | return name;
92 | };
93 |
94 | export const babelGetVendorExportMap = (vendorAsset: OutputChunk) => ({
95 | renderProps: babelGetVendorRenderPropsExportName(vendorAsset),
96 | });
97 |
98 | export const babelGetVendorName = (source: string) => {
99 | let name = "";
100 | const ast = babel.parseSync(source);
101 | if (!ast) return "";
102 | babel.traverse(ast, {
103 | StringLiteral: (path) => {
104 | if (!path.node.value.endsWith("vendor.js")) return;
105 | if (!path.parentPath.isCallExpression()) return;
106 | if (!path.parentPath.parentPath.isVariableDeclarator()) return;
107 | if (path.parentPath.parentPath.node.id.type !== "Identifier") return;
108 | name = path.parentPath.parentPath.node.id.name;
109 | path.stop();
110 | },
111 | });
112 | return name;
113 | };
114 |
115 | export const babelTransformScript = (
116 | source: string,
117 | vendorExportMap: ReturnType,
118 | options = defaultOptions,
119 | ) => {
120 | const vendorName = babelGetVendorName(source);
121 | if (!vendorName) return source;
122 | const transformed = babel.transformSync(source, {
123 | generatorOpts: {
124 | compact: true,
125 | retainLines: true,
126 | },
127 | configFile: false,
128 | plugins: [
129 | (instance: Babel): PluginItem => ({
130 | visitor: {
131 | StringLiteral: (path) => {
132 | if (!path.parentPath.isObjectProperty()) return;
133 | if (!path.parentPath.parentPath.isObjectExpression()) return;
134 | if (
135 | !path.parentPath.parentPath.parentPath.isCallExpression() &&
136 | !path.parentPath.parentPath.parentPath.isObjectProperty()
137 | )
138 | return;
139 | if (path.parentPath.parentPath.parentPath.isCallExpression()) {
140 | if (
141 | path.parentPath.parentPath.parentPath.node.callee.type !==
142 | "MemberExpression"
143 | )
144 | return;
145 | if (
146 | path.parentPath.parentPath.parentPath.node.callee.object
147 | .type !== "Identifier"
148 | )
149 | return;
150 | if (
151 | path.parentPath.parentPath.parentPath.node.callee.object
152 | .name !== vendorName
153 | )
154 | return;
155 | if (
156 | path.parentPath.parentPath.parentPath.node.callee.property
157 | .type !== "Identifier"
158 | )
159 | return;
160 | if (
161 | path.parentPath.parentPath.parentPath.node.callee.property
162 | .name !== vendorExportMap.renderProps
163 | )
164 | return;
165 | if (path.parentPath.node.key.type !== "StringLiteral") return;
166 | if (
167 | !options.shouldTransformAttribute(
168 | path.parentPath.node.key.value,
169 | )
170 | )
171 | return;
172 | } else {
173 | if (
174 | path.parentPath.parentPath.parentPath.node.key.type !==
175 | "StringLiteral"
176 | )
177 | return;
178 | if (
179 | !path.parentPath.parentPath.parentPath.parentPath.isObjectExpression()
180 | )
181 | return;
182 | if (
183 | !path.parentPath.parentPath.parentPath.parentPath.parentPath.isCallExpression()
184 | )
185 | return;
186 | if (
187 | path.parentPath.parentPath.parentPath.parentPath.parentPath.node
188 | .callee.type !== "MemberExpression"
189 | )
190 | return;
191 | if (
192 | path.parentPath.parentPath.parentPath.parentPath.parentPath.node
193 | .callee.object.type !== "Identifier"
194 | )
195 | return;
196 | if (
197 | path.parentPath.parentPath.parentPath.parentPath.parentPath.node
198 | .callee.object.name !== vendorName
199 | )
200 | return;
201 | if (
202 | path.parentPath.parentPath.parentPath.parentPath.parentPath.node
203 | .callee.property.type !== "Identifier"
204 | )
205 | return;
206 | if (
207 | path.parentPath.parentPath.parentPath.parentPath.parentPath.node
208 | .callee.property.name !== vendorExportMap.renderProps
209 | )
210 | return;
211 | if (
212 | !options.shouldTransformAttribute(
213 | path.parentPath.parentPath.parentPath.node.key.value,
214 | )
215 | )
216 | return;
217 | }
218 | const raw = path.node.value;
219 | const replaced = replaceCharacters(
220 | replaceUnicode(raw, "template"),
221 | "template",
222 | options,
223 | );
224 | if (replaced !== raw)
225 | path.replaceWith(instance.types.stringLiteral(replaced));
226 | },
227 | },
228 | }),
229 | ],
230 | });
231 | return transformed?.code ?? source;
232 | };
233 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # @uni-helper/vite-plugin-uni-tailwind
2 |
3 |
14 |
15 | 支持在 uni-app 中使用 TailwindCSS v3 原有语法开发小程序。支持 Vite v2 ~ v5,要求 `node>=14.18`。
16 |
17 | ## 使用
18 |
19 | 参考 [TailwindCSS 文档](https://tailwindcss.com/) 在 `uni-app` 项目中安装配置 `TailwindCSS`。你无需禁用 `preflight`,也无需调整原有语法(如 `.w-[200.5rpx]` 等),你只需要正常书写类名,该插件会替你处理剩下的事情。
20 |
21 | 安装依赖。
22 |
23 | ```shell
24 | npm install @uni-helper/vite-plugin-uni-tailwind -D
25 | ```
26 |
27 | 配置 `vite.config.ts`。
28 |
29 | ```typescript
30 | import { defineConfig } from 'vite';
31 | // @ts-ignore
32 | import nested from 'tailwindcss/nesting';
33 | import tailwindcss from 'tailwindcss';
34 | import tailwindcssConfig from './tailwind.config.ts'; // 注意匹配实际文件
35 | import postcssPresetEnv from 'postcss-preset-env';
36 | import uni from '@dcloudio/vite-plugin-uni';
37 | import uniTailwind from '@uni-helper/vite-plugin-uni-tailwind';
38 |
39 | // https://vitejs.dev/config/
40 | export default defineConfig({
41 | css: {
42 | // 只能在 Vite 配置文件内处理 postcss 配置
43 | // https://github.com/dcloudio/uni-app/issues/3367
44 | postcss: {
45 | plugins: [
46 | nested(),
47 | tailwindcss({
48 | config: tailwindcssConfig,
49 | }),
50 | postcssPresetEnv({
51 | stage: 3,
52 | features: { 'nesting-rules': false },
53 | }),
54 | ],
55 | },
56 | },
57 | plugins: [
58 | uni(),
59 | uniTailwind({
60 | /* options */
61 | }),
62 | ],
63 | });
64 | ```
65 |
66 | 完整示例请查看 [playground](https://github.com/uni-helper/vite-plugin-uni-tailwind/tree/main/playground)。
67 |
68 | ## 配置项 `Options`
69 |
70 | ### `shouldApply`
71 |
72 | - 类型:`boolean | ((currentPlatform: string) => boolean)`
73 | - 默认值:`编译为小程序和快应用时应用`
74 |
75 | 是否应用该插件。
76 |
77 | `APP` 使用 `WebView` 运行,`H5` 使用浏览器运行,基本都支持特殊字符,所以默认编译为小程序和快应用时应用该插件。
78 |
79 | ### `shouldTransformAttribute`
80 |
81 | - 类型:`(attribute: string) => boolean`
82 | - 默认值:`转换以 class、Class、classname、className、ClassName、class-name 结尾的 attribute`
83 |
84 | 是否转换某个 `attribute`。
85 |
86 | ### `shouldTransformScript`
87 |
88 | - 类型:`(fileName: string) => boolean`
89 | - 默认值:`转换路径以 pages、components、layouts 开头的脚本文件`
90 |
91 | 是否转换某个脚本文件。
92 |
93 | ### `characterMap`
94 |
95 | - 类型:`[string, string][]`
96 | - 默认值如下
97 |
98 | ```typescript
99 | [
100 | ['[', '-'],
101 | [']', '-'],
102 | ['(', '-'],
103 | [')', '-'],
104 | ['$', '-v-'], // css variable
105 | ['#', '-h-'], // hash
106 | ['!', '-i-'], // important
107 | ['/', '-s-'], // slash
108 | ['.', '-d-'], // dot
109 | [':', '_'], // colon
110 | ['%', '-p-'], // percentage
111 | ["'", '-q-'], // quote
112 | ['+', '-a-'], // add
113 | ['=', '-e-'], // equal
114 | ['&', '-n-'], // and
115 | ['?', '-qm-'], // question mark
116 | ['@', '-at-'], // at
117 | ['*', '-w-'], // wildcard
118 | [',\\s', '-c-'], // comma
119 | [',', '-c-'], // comma
120 | ['\\2c\\s', '-c-'], // comma
121 | ['\\2c', '-c-'], // comma
122 | ['\\\\2c\\s', '-c-'], // comma
123 | ['\\\\2c', '-c-'], // comma
124 | ];
125 | ```
126 |
127 | 所有生成样式中特殊字符需要替换成什么字符串。
128 |
129 | 如果不替换,可能会导致无法正常运行。如果确认无需替换,请设置为空数组。
130 |
131 | 替换顺序:`directChildrenElements` -> `spaceBetweenElements` -> `divideWidthElements` -> `characterMap` -> `elementMap`。
132 |
133 | ### `spaceBetweenElements`
134 |
135 | - 类型:`string[]`
136 | - 默认值:`['view', 'button', 'text', 'image']`
137 |
138 | [Space Between](https://tailwindcss.com/docs/space) 生成样式中,`*` 需要替换成什么元素。
139 |
140 | 如果不替换,可能会导致无法正常运行。如果确认无需替换,请设置为空数组。
141 |
142 | 替换顺序:`directChildrenElements` -> `spaceBetweenElements` -> `divideWidthElements` -> `characterMap` -> `elementMap`。
143 |
144 | ### `divideWidthElements`
145 |
146 | - 类型:`string[]`
147 | - 默认值:`['view', 'button', 'text', 'image']`
148 |
149 | [Divide Width](https://tailwindcss.com/docs/divide-width) 生成样式中,`*` 需要替换成什么元素。
150 |
151 | 如果不替换,可能会导致无法正常运行。如果确认无需替换,请设置为空数组。
152 |
153 | 替换顺序:`directChildrenElements` -> `spaceBetweenElements` -> `divideWidthElements` -> `characterMap` -> `elementMap`。
154 |
155 | ### `directChildrenElements`
156 |
157 | - 类型:`string[]`
158 | - 默认值:`['view', 'button', 'text', 'image']`
159 |
160 | [Direct Children](https://tailwindcss.com/docs/hover-focus-and-other-states#styling-direct-children) 生成样式中,后一个 `*` 需要替换成什么元素。
161 |
162 | 如果不替换,可能会导致无法正常运行。如果确认无需替换,请设置为空数组。
163 |
164 | 替换顺序:`directChildrenElements` -> `spaceBetweenElements` -> `divideWidthElements` -> `characterMap` -> `elementMap`。
165 |
166 | ### `elementMap`
167 |
168 | - 类型:`[string, string[]][]`
169 | - 默认值如下
170 |
171 | ```typescript
172 | [
173 | ['html', ['page']],
174 | ['body', ['page']],
175 | ['img', ['image']],
176 | ['a', ['functional-page-navigator', 'navigator']],
177 | [
178 | '*',
179 | [
180 | 'page',
181 | 'cover-image',
182 | 'cover-view',
183 | 'match-media',
184 | 'movable-area',
185 | 'movable-view',
186 | 'scroll-view',
187 | 'swiper',
188 | 'swiper-item',
189 | 'view',
190 | 'icon',
191 | 'progress',
192 | 'rich-text',
193 | 'text',
194 | 'button',
195 | 'checkbox',
196 | 'checkbox-group',
197 | 'editor',
198 | 'form',
199 | 'input',
200 | 'label',
201 | 'picker',
202 | 'picker-view',
203 | 'picker-view-column',
204 | 'radio',
205 | 'radio-group',
206 | 'slider',
207 | 'switch',
208 | 'textarea',
209 | 'functional-page-navigator',
210 | 'navigator',
211 | 'audio',
212 | 'camera',
213 | 'image',
214 | 'live-player',
215 | 'live-pusher',
216 | 'video',
217 | 'voip-room',
218 | 'map',
219 | 'canvas',
220 | 'ad',
221 | 'ad-custom',
222 | 'official-account',
223 | 'open-data',
224 | 'web-view',
225 | 'navigation-bar',
226 | 'page-meta',
227 | ],
228 | ],
229 | ];
230 | ```
231 |
232 | 所有生成样式中特定元素需要替换成什么元素。
233 |
234 | 如果不替换,可能会导致无法正常运行。如果确认无需替换,请设置为空数组。
235 |
236 | 替换顺序:`directChildrenElements` -> `spaceBetweenElements` -> `divideWidthElements` -> `characterMap` -> `elementMap`。
237 |
238 | ## FAQ
239 |
240 | ### 可以支持 WindiCSS 吗?
241 |
242 | **请注意:请不要在新项目中使用 WindiCSS!详见 [Windi CSS is Sunsetting](https://windicss.org/posts/sunsetting.html)。**
243 |
244 | 如果你没有使用 WindiCSS 内的高级功能(如 [Attributify Mode](https://windicss.org/features/attributify.html)),这个库可以正常工作。
245 |
246 | ### 可以支持 UnoCSS 吗?
247 |
248 | **建议使用 [unocss-applet](https://github.com/unocss-applet/unocss-applet) 或 [unocss-preset-weapp](https://github.com/MellowCo/unocss-preset-weapp) 以获取更好的支持。**
249 |
250 | 如果你没有使用 UnoCSS 内的高级功能(如 [Attributify Mode](https://unocss.dev/presets/attributify)、[Tagify Mode](https://unocss.dev/presets/tagify)),这个库可以正常工作。
251 |
252 | ### 可以支持 rpx 转换吗?
253 |
254 | 引自 [微信小程序文档](https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html#%E5%B0%BA%E5%AF%B8%E5%8D%95%E4%BD%8D):
255 |
256 | > rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
257 |
258 | 简而言之,rpx 是一个跟屏幕宽度挂钩的响应式单位,不应该也不需要把所有用到 px 或者 rem 的地方换成 rpx。
259 |
260 | 什么时候必须要用 rpx?我个人的经验是侧边栏的宽度需要随屏幕宽度变化、页面主体根据侧边栏宽度变化时,才必须用到 rpx + flexbox 的组合,否则用 flexbox 就已经足够了。
261 |
262 | 所以,这个插件不支持 rpx 转换。你可以直接 [使用任意值](https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values),如 `.w-[750rpx]`、`.w-[200rpx]`,我相信可以满足绝大部分的需求。
263 |
264 | 如果你悲伤地发现这没法满足你的需求,可能这个插件不适合你,请看看以下几个项目是否满足你的需求。
265 |
266 | - [tailwind-extensions](https://www.npmjs.com/package/tailwind-extensions)
267 | - [mini-program-tailwind](https://github.com/dcasia/mini-program-tailwind)
268 | - [weapp-tailwindcss](https://github.com/sonofmagic/weapp-tailwindcss)
269 | - [unocss](https://unocss.dev)
270 | - [unocss-applet](https://github.com/unocss-applet/unocss-applet)
271 | - [unocss-preset-weapp](https://github.com/MellowCo/unocss-preset-weapp)
272 |
273 | ### 这个插件的原理是什么?
274 |
275 | uni-app + TailwindCSS 不能编译出小程序能正常运行的代码的错误原因有以下几种:
276 |
277 | - 样式文件中含有不支持的字符,如 `()[]$#!/.:,%'` 等;
278 | - 样式文件中含有不支持的元素,如 `html`, `body`、`img`、`a`、`*` 等;
279 | - 自带组件传参,模板文件中含有不支持的字符,如 `()[]$#!/.:,%'` 等;
280 | - 自定义组件传参,脚本文件中含有不支持的字符,如 `()[]$#!/.:,%'` 等,导致参数渲染不正常。
281 |
282 | 那么,我们只需要做到以下几点就可以让 TailwindCSS 跑在小程序中,而不需要调整 TailwindCSS 的语法来增加开发时的心智负担:
283 |
284 | - 使用 PostCSS 改写样式文件里面的 `selector`,包括字符和元素;
285 | - 使用 Babel 改写模板文件里面的 `class`,只包括字符,这是为了和样式文件里面的 `selector` 相匹配;
286 | - 使用 Babel 改写脚本文件里面的 `class`,这也是为了和样式文件里面的 `selector` 相匹配。
287 |
288 | 但请注意,这个插件不是万能的。
289 |
290 | - 插件无法突破小程序的限制,比如 `bg-[url(xxxx)]` 经过插件处理后可以正常使用,但是小程序平台不支持使用 `background-image`,此时仍然无法正常渲染出图片。
291 | - 插件不支持特别复杂的情况,如果自定义组件传参过于复杂,仍然可能绕过插件处理。如果你发现这种情况,欢迎提交 Issue 或 PR 协助改进该插件,非常感谢!🙏
292 |
293 | ## 资源
294 |
295 | - [改动日志](https://github.com/uni-helper/vite-plugin-uni-tailwind/tree/main/CHANGELOG.md)
296 |
297 | ## 致谢
298 |
299 | 该项目从以下项目汲取了灵感并参考了代码。在此对它们的开发者表示由衷的感谢。
300 |
301 | - [mini-program-tailwind](https://github.com/dcasia/mini-program-tailwind)
302 | - [weapp-tailwindcss](https://github.com/sonofmagic/weapp-tailwindcss)
303 | - [unocss-applet](https://github.com/unocss-applet/unocss-applet)
304 | - [unocss-preset-weapp](https://github.com/MellowCo/unocss-preset-weapp)
305 |
306 | 也感谢以下项目的开发者,如果没有他们,前端开发比现在更加困难。
307 |
308 | - [TailwindCSS](https://tailwindcss.com/)
309 | - [WindiCSS](https://windicss.org/)
310 | - [UnoCSS](https://github.com/unocss/unocss)
311 |
312 | ## 贡献者们
313 |
314 | 该项目由 [ModyQyW](https://github.com/ModyQyW) 创建。
315 |
316 | 感谢 [所有贡献者](https://github.com/uni-helper/vite-plugin-uni-tailwind/graphs/contributors) 的付出!
317 |
318 | ## 赞助
319 |
320 | 如果这个包对你有所帮助,请考虑 [赞助](https://github.com/ModyQyW/sponsors) 支持,这将有利于项目持续开发和维护。
321 |
322 |
323 |
324 |
325 |
326 |
327 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @uni-helper/vite-plugin-uni-tailwind
2 |
3 | **今日起,我们将正式废弃 @uni-helper/vite-plugin-uni-tailwind,并建议现有应用迁移到其它类似工具。详情请阅读 [废弃 @uni-helper/vite-plugin-uni-tailwind](./deprecated.md)。**
4 |
5 |
16 |
17 | 支持在 uni-app 中使用 TailwindCSS v3 原有语法开发小程序。支持 Vite v2 ~ v5,要求 `node>=14.18`。
18 |
19 | ## 使用
20 |
21 | 参考 [TailwindCSS 文档](https://tailwindcss.com/) 在 `uni-app` 项目中安装配置 `TailwindCSS`。你无需禁用 `preflight`,也无需调整原有语法(如 `.w-[200.5rpx]` 等),你只需要正常书写类名,该插件会替你处理剩下的事情。
22 |
23 | 安装依赖。
24 |
25 | ```shell
26 | npm install @uni-helper/vite-plugin-uni-tailwind -D
27 | ```
28 |
29 | 配置 `vite.config.ts`。
30 |
31 | ```typescript
32 | import { defineConfig } from 'vite';
33 | // @ts-ignore
34 | import tailwindcssNesting from 'tailwindcss/nesting';
35 | import tailwindcss from 'tailwindcss';
36 | import tailwindcssConfig from './tailwind.config.ts'; // 注意匹配实际文件
37 | import postcssPresetEnv from 'postcss-preset-env';
38 | import uni from '@dcloudio/vite-plugin-uni';
39 | import uniTailwind from '@uni-helper/vite-plugin-uni-tailwind';
40 |
41 | // https://vitejs.dev/config/
42 | export default defineConfig({
43 | css: {
44 | // 只能在 Vite 配置文件内处理 postcss 配置
45 | // https://github.com/dcloudio/uni-app/issues/3367
46 | postcss: {
47 | plugins: [
48 | tailwindcssNesting(),
49 | tailwindcss({
50 | config: tailwindcssConfig,
51 | }),
52 | postcssPresetEnv({
53 | stage: 3,
54 | features: {
55 | // 避免处理冲突
56 | 'nesting-rules': false,
57 | // 强制处理全局和局部 CSS Variables,无视浏览器兼容性
58 | 'custom-properties': true,
59 | // 强制处理 @layer 规则,无视浏览器兼容性
60 | 'cascade-layers': true,
61 | // 更多用法见 postcss-preset-env 文档
62 | },
63 | }),
64 | ],
65 | },
66 | },
67 | plugins: [
68 | uni(),
69 | uniTailwind({
70 | /* options */
71 | }),
72 | ],
73 | });
74 | ```
75 |
76 | 完整示例请查看 [playground](https://github.com/uni-helper/vite-plugin-uni-tailwind/tree/main/playground)。
77 |
78 | ## 配置项 `Options`
79 |
80 | ### `shouldApply`
81 |
82 | - 类型:`boolean | ((currentPlatform: string) => boolean)`
83 | - 默认值:`编译为小程序和快应用时应用`
84 |
85 | 是否应用该插件。
86 |
87 | `APP` 使用 `WebView` 运行,`H5` 使用浏览器运行,基本都支持特殊字符,所以默认编译为小程序和快应用时应用该插件。
88 |
89 | ### `shouldTransformAttribute`
90 |
91 | - 类型:`(attribute: string) => boolean`
92 | - 默认值:`转换以 class、Class、classname、className、ClassName、class-name 结尾的 attribute`
93 |
94 | 是否转换某个 `attribute`。
95 |
96 | ### `shouldTransformScript`
97 |
98 | - 类型:`(fileName: string) => boolean`
99 | - 默认值:`转换路径以 pages、components、layouts 开头的脚本文件`
100 |
101 | 是否转换某个脚本文件。
102 |
103 | ### `characterMap`
104 |
105 | - 类型:`[string, string][]`
106 | - 默认值如下
107 |
108 | ```typescript
109 | [
110 | ['[', '-'],
111 | [']', '-'],
112 | ['(', '-'],
113 | [')', '-'],
114 | ['$', '-v-'], // css variable
115 | ['#', '-h-'], // hash
116 | ['!', '-i-'], // important
117 | ['/', '-s-'], // slash
118 | ['.', '-d-'], // dot
119 | [':', '_'], // colon
120 | ['%', '-p-'], // percentage
121 | ["'", '-q-'], // quote
122 | ['+', '-a-'], // add
123 | ['=', '-e-'], // equal
124 | ['&', '-n-'], // and
125 | ['?', '-qm-'], // question mark
126 | ['@', '-at-'], // at
127 | ['*', '-w-'], // wildcard
128 | [',\\s', '-c-'], // comma
129 | [',', '-c-'], // comma
130 | ['\\2c\\s', '-c-'], // comma
131 | ['\\2c', '-c-'], // comma
132 | ['\\\\2c\\s', '-c-'], // comma
133 | ['\\\\2c', '-c-'], // comma
134 | ];
135 | ```
136 |
137 | 所有生成样式中特殊字符需要替换成什么字符串。
138 |
139 | 如果不替换,可能会导致无法正常运行。如果确认无需替换,请设置为空数组。
140 |
141 | 替换顺序:`directChildrenElements` -> `spaceBetweenElements` -> `divideWidthElements` -> `characterMap` -> `elementMap`。
142 |
143 | ### `spaceBetweenElements`
144 |
145 | - 类型:`string[]`
146 | - 默认值:`['view', 'button', 'text', 'image']`
147 |
148 | [Space Between](https://tailwindcss.com/docs/space) 生成样式中,`*` 需要替换成什么元素。
149 |
150 | 如果不替换,可能会导致无法正常运行。如果确认无需替换,请设置为空数组。
151 |
152 | 替换顺序:`directChildrenElements` -> `spaceBetweenElements` -> `divideWidthElements` -> `characterMap` -> `elementMap`。
153 |
154 | ### `divideWidthElements`
155 |
156 | - 类型:`string[]`
157 | - 默认值:`['view', 'button', 'text', 'image']`
158 |
159 | [Divide Width](https://tailwindcss.com/docs/divide-width) 生成样式中,`*` 需要替换成什么元素。
160 |
161 | 如果不替换,可能会导致无法正常运行。如果确认无需替换,请设置为空数组。
162 |
163 | 替换顺序:`directChildrenElements` -> `spaceBetweenElements` -> `divideWidthElements` -> `characterMap` -> `elementMap`。
164 |
165 | ### `directChildrenElements`
166 |
167 | - 类型:`string[]`
168 | - 默认值:`['view', 'button', 'text', 'image']`
169 |
170 | [Direct Children](https://tailwindcss.com/docs/hover-focus-and-other-states#styling-direct-children) 生成样式中,后一个 `*` 需要替换成什么元素。
171 |
172 | 如果不替换,可能会导致无法正常运行。如果确认无需替换,请设置为空数组。
173 |
174 | 替换顺序:`directChildrenElements` -> `spaceBetweenElements` -> `divideWidthElements` -> `characterMap` -> `elementMap`。
175 |
176 | ### `elementMap`
177 |
178 | - 类型:`[string, string[]][]`
179 | - 默认值如下
180 |
181 | ```typescript
182 | [
183 | ['html', ['page']],
184 | ['body', ['page']],
185 | ['img', ['image']],
186 | ['a', ['functional-page-navigator', 'navigator']],
187 | [
188 | '*',
189 | [
190 | 'page',
191 | 'cover-image',
192 | 'cover-view',
193 | 'match-media',
194 | 'movable-area',
195 | 'movable-view',
196 | 'scroll-view',
197 | 'swiper',
198 | 'swiper-item',
199 | 'view',
200 | 'icon',
201 | 'progress',
202 | 'rich-text',
203 | 'text',
204 | 'button',
205 | 'checkbox',
206 | 'checkbox-group',
207 | 'editor',
208 | 'form',
209 | 'input',
210 | 'label',
211 | 'picker',
212 | 'picker-view',
213 | 'picker-view-column',
214 | 'radio',
215 | 'radio-group',
216 | 'slider',
217 | 'switch',
218 | 'textarea',
219 | 'functional-page-navigator',
220 | 'navigator',
221 | 'audio',
222 | 'camera',
223 | 'image',
224 | 'live-player',
225 | 'live-pusher',
226 | 'video',
227 | 'voip-room',
228 | 'map',
229 | 'canvas',
230 | 'ad',
231 | 'ad-custom',
232 | 'official-account',
233 | 'open-data',
234 | 'web-view',
235 | 'navigation-bar',
236 | 'page-meta',
237 | ],
238 | ],
239 | ];
240 | ```
241 |
242 | 所有生成样式中特定元素需要替换成什么元素。
243 |
244 | 如果不替换,可能会导致无法正常运行。如果确认无需替换,请设置为空数组。
245 |
246 | 替换顺序:`directChildrenElements` -> `spaceBetweenElements` -> `divideWidthElements` -> `characterMap` -> `elementMap`。
247 |
248 | ## FAQ
249 |
250 | ### 可以支持 WindiCSS 吗?
251 |
252 | **请注意:请不要在新项目中使用 WindiCSS!详见 [Windi CSS is Sunsetting](https://windicss.org/posts/sunsetting.html)。**
253 |
254 | 如果你没有使用 WindiCSS 内的高级功能(如 [Attributify Mode](https://windicss.org/features/attributify.html)),这个库可以正常工作。
255 |
256 | ### 可以支持 UnoCSS 吗?
257 |
258 | **建议使用 [unocss-applet](https://github.com/unocss-applet/unocss-applet) 或 [unocss-preset-weapp](https://github.com/MellowCo/unocss-preset-weapp) 以获取更好的支持。**
259 |
260 | 如果你没有使用 UnoCSS 内的高级功能(如 [Attributify Mode](https://unocss.dev/presets/attributify)、[Tagify Mode](https://unocss.dev/presets/tagify)),这个库可以正常工作。
261 |
262 | ### 可以支持 rpx 转换吗?
263 |
264 | 引自 [微信小程序文档](https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html#%E5%B0%BA%E5%AF%B8%E5%8D%95%E4%BD%8D):
265 |
266 | > rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
267 |
268 | 简而言之,rpx 是一个跟屏幕宽度挂钩的响应式单位,不应该也不需要把所有用到 px 或者 rem 的地方换成 rpx。
269 |
270 | 什么时候必须要用 rpx?我个人的经验是侧边栏的宽度需要随屏幕宽度变化、页面主体根据侧边栏宽度变化时,才必须用到 rpx + flexbox 的组合,否则用 flexbox 就已经足够了。
271 |
272 | 所以,这个插件不支持 rpx 转换。你可以直接 [使用任意值](https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values),如 `.w-[750rpx]`、`.w-[200rpx]`,我相信可以满足绝大部分的需求。
273 |
274 | 如果你悲伤地发现这没法满足你的需求,可能这个插件不适合你,请看看以下几个项目是否满足你的需求。
275 |
276 | - [tailwind-extensions](https://www.npmjs.com/package/tailwind-extensions)
277 | - [mini-program-tailwind](https://github.com/dcasia/mini-program-tailwind)
278 | - [weapp-tailwindcss](https://github.com/sonofmagic/weapp-tailwindcss)
279 | - [unocss](https://unocss.dev)
280 | - [unocss-applet](https://github.com/unocss-applet/unocss-applet)
281 | - [unocss-preset-weapp](https://github.com/MellowCo/unocss-preset-weapp)
282 |
283 | ### 这个插件的原理是什么?
284 |
285 | uni-app + TailwindCSS 不能编译出小程序能正常运行的代码的错误原因有以下几种:
286 |
287 | - 样式文件中含有不支持的字符,如 `()[]$#!/.:,%'` 等;
288 | - 样式文件中含有不支持的元素,如 `html`, `body`、`img`、`a`、`*` 等;
289 | - 自带组件传参,模板文件中含有不支持的字符,如 `()[]$#!/.:,%'` 等;
290 | - 自定义组件传参,脚本文件中含有不支持的字符,如 `()[]$#!/.:,%'` 等,导致参数渲染不正常。
291 |
292 | 那么,我们只需要做到以下几点就可以让 TailwindCSS 跑在小程序中,而不需要调整 TailwindCSS 的语法来增加开发时的心智负担:
293 |
294 | - 使用 PostCSS 改写样式文件里面的 `selector`,包括字符和元素;
295 | - 使用 Babel 改写模板文件里面的 `class`,只包括字符,这是为了和样式文件里面的 `selector` 相匹配;
296 | - 使用 Babel 改写脚本文件里面的 `class`,这也是为了和样式文件里面的 `selector` 相匹配。
297 |
298 | 但请注意,这个插件不是万能的。
299 |
300 | - 插件无法突破小程序的限制,比如 `bg-[url(xxxx)]` 经过插件处理后可以正常使用,但是小程序平台不支持使用 `background-image`,此时仍然无法正常渲染出图片。
301 | - 插件不支持特别复杂的情况,如果自定义组件传参过于复杂,仍然可能绕过插件处理。如果你发现这种情况,欢迎提交 Issue 或 PR 协助改进该插件,非常感谢!🙏
302 |
303 | ## 资源
304 |
305 | - [改动日志](https://github.com/uni-helper/vite-plugin-uni-tailwind/tree/main/CHANGELOG.md)
306 |
307 | ## 致谢
308 |
309 | 该项目从以下项目汲取了灵感并参考了代码。在此对它们的开发者表示由衷的感谢。
310 |
311 | - [mini-program-tailwind](https://github.com/dcasia/mini-program-tailwind)
312 | - [weapp-tailwindcss](https://github.com/sonofmagic/weapp-tailwindcss)
313 | - [unocss-applet](https://github.com/unocss-applet/unocss-applet)
314 | - [unocss-preset-weapp](https://github.com/MellowCo/unocss-preset-weapp)
315 |
316 | 也感谢以下项目的开发者,如果没有他们,前端开发比现在更加困难。
317 |
318 | - [TailwindCSS](https://tailwindcss.com/)
319 | - [WindiCSS](https://windicss.org/)
320 | - [UnoCSS](https://github.com/unocss/unocss)
321 |
322 | ## 贡献者们
323 |
324 | 该项目由 [ModyQyW](https://github.com/ModyQyW) 创建。
325 |
326 | 感谢 [所有贡献者](https://github.com/uni-helper/vite-plugin-uni-tailwind/graphs/contributors) 的付出!
327 |
328 | ## 赞助
329 |
330 | 如果这个包对你有所帮助,请考虑 [赞助](https://github.com/ModyQyW/sponsors) 支持,这将有利于项目持续开发和维护。
331 |
332 |
333 |
334 |
335 |
336 |
337 |
--------------------------------------------------------------------------------
/packages/core/src/tools/babel.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import {
3 | babelGetVendorName,
4 | babelTransformClass,
5 | babelTransformScript,
6 | } from ".";
7 |
8 | describe("babel", () => {
9 | it("babelTransformClass", () => {
10 | expect(babelTransformClass("w-[0.5px]")).toBe("w--0-d-5px-");
11 | expect(babelTransformClass("bg-[url()]")).toBe("bg--url---");
12 | expect(babelTransformClass("!w-[200.5px]")).toBe("-i-w--200-d-5px-");
13 | expect(babelTransformClass("top-1/2")).toBe("top-1-s-2");
14 | expect(babelTransformClass("bg-[url('/img/grid.svg')]")).toBe(
15 | "bg--url--q--s-img-s-grid-d-svg-q---",
16 | );
17 | expect(babelTransformClass("w-[200.5rpx]")).toBe("w--200-d-5rpx-");
18 | expect(babelTransformClass("sm:mx-auto")).toBe("sm_mx-auto");
19 | expect(babelTransformClass("bg-[#fff]")).toBe("bg---h-fff-");
20 | expect(babelTransformClass("bg-[rgba(255,255,255,1)]")).toBe(
21 | "bg--rgba-255-c-255-c-255-c-1--",
22 | );
23 | expect(babelTransformClass("w-[10%]")).toBe("w--10-p--");
24 | expect(
25 | babelTransformClass(`{{['w-[10%]','bg-[#fff]',virtualHostClass]}}`),
26 | ).toBe(`{{['w--10-p--','bg---h-fff-',virtualHostClass]}}`);
27 | expect(babelTransformClass("*:rounded-full")).toBe("-w-_rounded-full");
28 | });
29 |
30 | it("babelGetVendorName", () => {
31 | expect(
32 | babelGetVendorName(`"use strict";
33 | var __defProp = Object.defineProperty;
34 | var __defProps = Object.defineProperties;
35 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
36 | var __getOwnPropSymbols = Object.getOwnPropertySymbols;
37 | var __hasOwnProp = Object.prototype.hasOwnProperty;
38 | var __propIsEnum = Object.prototype.propertyIsEnumerable;
39 | var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
40 | var __spreadValues = (a, b) => {
41 | for (var prop in b || (b = {}))
42 | if (__hasOwnProp.call(b, prop))
43 | __defNormalProp(a, prop, b[prop]);
44 | if (__getOwnPropSymbols)
45 | for (var prop of __getOwnPropSymbols(b)) {
46 | if (__propIsEnum.call(b, prop))
47 | __defNormalProp(a, prop, b[prop]);
48 | }
49 | return a;
50 | };
51 | var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
52 | const common_vendor = require("../common/vendor.js");
53 | const __unplugin_components_0 = () => "../components/v-status-bar/v-status-bar.js";
54 | if (!Array) {
55 | const _easycom_v_status_bar2 = __unplugin_components_0;
56 | _easycom_v_status_bar2();
57 | }
58 | const _easycom_v_status_bar = () => "../components/v-status-bar/v-status-bar.js";
59 | if (!Math) {
60 | _easycom_v_status_bar();
61 | }
62 | const __default__ = common_vendor.defineComponent({
63 | name: "ComplexLayout",
64 | options: {
65 | virtualHost: true
66 | }
67 | });
68 | const _sfc_main = /* @__PURE__ */ common_vendor.defineComponent(__spreadProps(__spreadValues({}, __default__), {
69 | setup(__props) {
70 | return (_ctx, _cache) => {
71 | return {
72 | a: common_vendor.p({
73 | ["container-class"]: "bg-[#fff]",
74 | ["container-style"]: {
75 | backgroundColor: "#fff"
76 | }
77 | })
78 | };
79 | };
80 | }
81 | }));
82 | const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "/Users/wurui/Documents/millcloud/guangda/miniapp/src/layouts/complex.vue"]]);
83 | wx.createComponent(Component);
84 | `),
85 | ).toBe("common_vendor");
86 | expect(
87 | babelGetVendorName(`"use strict";var e=Object.defineProperty,t=Object.defineProperties,r=Object.getOwnPropertyDescriptors,o=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o;const c=require("../common/vendor.js");if(!Array){(()=>"../components/v-status-bar/v-status-bar.js")()}Math;const p=c.defineComponent({name:"ComplexLayout",options:{virtualHost:!0}}),i=c.defineComponent((f=((e,t)=>{for(var r in t||(t={}))n.call(t,r)&&s(e,r,t[r]);if(o)for(var r of o(t))a.call(t,r)&&s(e,r,t[r]);return e})({},p),t(f,r({setup:e=>(e,t)=>({a:c.p({"container-class":"bg-[#fff]","container-style":{backgroundColor:"#fff"}})})}))));var f;wx.createComponent(i);
88 | `),
89 | ).toBe("c");
90 | });
91 |
92 | it("babelTransformScript", () => {
93 | expect(
94 | babelTransformScript(
95 | `"use strict";
96 | var __defProp = Object.defineProperty;
97 | var __defProps = Object.defineProperties;
98 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
99 | var __getOwnPropSymbols = Object.getOwnPropertySymbols;
100 | var __hasOwnProp = Object.prototype.hasOwnProperty;
101 | var __propIsEnum = Object.prototype.propertyIsEnumerable;
102 | var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
103 | var __spreadValues = (a, b) => {
104 | for (var prop in b || (b = {}))
105 | if (__hasOwnProp.call(b, prop))
106 | __defNormalProp(a, prop, b[prop]);
107 | if (__getOwnPropSymbols)
108 | for (var prop of __getOwnPropSymbols(b)) {
109 | if (__propIsEnum.call(b, prop))
110 | __defNormalProp(a, prop, b[prop]);
111 | }
112 | return a;
113 | };
114 | var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
115 | const common_vendor = require("../common/vendor.js");
116 | const __unplugin_components_0 = () => "../components/v-status-bar/v-status-bar.js";
117 | if (!Array) {
118 | const _easycom_v_status_bar2 = __unplugin_components_0;
119 | _easycom_v_status_bar2();
120 | }
121 | const _easycom_v_status_bar = () => "../components/v-status-bar/v-status-bar.js";
122 | if (!Math) {
123 | _easycom_v_status_bar();
124 | }
125 | const __default__ = common_vendor.defineComponent({
126 | name: "ComplexLayout",
127 | options: {
128 | virtualHost: true
129 | }
130 | });
131 | const _sfc_main = /* @__PURE__ */ common_vendor.defineComponent(__spreadProps(__spreadValues({}, __default__), {
132 | setup(__props) {
133 | return (_ctx, _cache) => {
134 | return {
135 | a: common_vendor.p({
136 | ["container-class"]: "bg-[#fff]",
137 | ["test-class"]: {
138 | "bg-[#fff]": true,
139 | },
140 | ["container-style"]: {
141 | backgroundColor: "#fff"
142 | }
143 | })
144 | };
145 | };
146 | }
147 | }));
148 | const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "/miniprogram/src/layouts/complex.vue"]]);
149 | wx.createComponent(Component);
150 | `,
151 | { renderProps: "p" },
152 | ),
153 | ).toMatchInlineSnapshot(`
154 | ""use strict";
155 | var __defProp=Object.defineProperty;
156 | var __defProps=Object.defineProperties;
157 | var __getOwnPropDescs=Object.getOwnPropertyDescriptors;
158 | var __getOwnPropSymbols=Object.getOwnPropertySymbols;
159 | var __hasOwnProp=Object.prototype.hasOwnProperty;
160 | var __propIsEnum=Object.prototype.propertyIsEnumerable;
161 | var __defNormalProp=(obj,key,value)=>key in obj?__defProp(obj,key,{enumerable:true,configurable:true,writable:true,value}):obj[key]=value;
162 | var __spreadValues=(a,b)=>{
163 | for(var prop in b||(b={}))
164 | if(__hasOwnProp.call(b,prop))
165 | __defNormalProp(a,prop,b[prop]);
166 | if(__getOwnPropSymbols)
167 | for(var prop of __getOwnPropSymbols(b)){
168 | if(__propIsEnum.call(b,prop))
169 | __defNormalProp(a,prop,b[prop]);
170 | }
171 | return a;
172 | };
173 | var __spreadProps=(a,b)=>__defProps(a,__getOwnPropDescs(b));
174 | const common_vendor=require("../common/vendor.js");
175 | const __unplugin_components_0=()=>"../components/v-status-bar/v-status-bar.js";
176 | if(!Array){
177 | const _easycom_v_status_bar2=__unplugin_components_0;
178 | _easycom_v_status_bar2();
179 | }
180 | const _easycom_v_status_bar=()=>"../components/v-status-bar/v-status-bar.js";
181 | if(!Math){
182 | _easycom_v_status_bar();
183 | }
184 | const __default__=common_vendor.defineComponent({
185 | name:"ComplexLayout",
186 | options:{
187 | virtualHost:true
188 | }
189 | });
190 | const _sfc_main=/* @__PURE__ */common_vendor.defineComponent(__spreadProps(__spreadValues({},__default__),{
191 | setup(__props){
192 | return(_ctx,_cache)=>{
193 | return{
194 | a:common_vendor.p({
195 | ["container-class"]:"bg---h-fff-",
196 | ["test-class"]:{
197 | "bg---h-fff-":true
198 | },
199 | ["container-style"]:{
200 | backgroundColor:"#fff"
201 | }
202 | })
203 | };
204 | };
205 | }
206 | }));
207 | const Component=/* @__PURE__ */common_vendor._export_sfc(_sfc_main,[["__file","/miniprogram/src/layouts/complex.vue"]]);
208 | wx.createComponent(Component);"
209 | `);
210 | expect(
211 | babelTransformScript(
212 | `"use strict";var e=Object.defineProperty,t=Object.defineProperties,r=Object.getOwnPropertyDescriptors,o=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o;const c=require("../common/vendor.js");if(!Array){(()=>"../components/v-status-bar/v-status-bar.js")()}Math;const p=c.defineComponent({name:"ComplexLayout",options:{virtualHost:!0}}),i=c.defineComponent((f=((e,t)=>{for(var r in t||(t={}))n.call(t,r)&&s(e,r,t[r]);if(o)for(var r of o(t))a.call(t,r)&&s(e,r,t[r]);return e})({},p),t(f,r({setup:e=>(e,t)=>({a:c.p({"container-class":"bg-[#fff]","test-class":{"bg-[#fff]":true},"container-style":{backgroundColor:"#fff"}})})}))));var f;wx.createComponent(i);`,
213 | { renderProps: "p" },
214 | ),
215 | ).toBe(
216 | `"use strict";var e=Object.defineProperty,t=Object.defineProperties,r=Object.getOwnPropertyDescriptors,o=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o;const c=require("../common/vendor.js");if(!Array){(()=>"../components/v-status-bar/v-status-bar.js")();}Math;const p=c.defineComponent({name:"ComplexLayout",options:{virtualHost:!0}}),i=c.defineComponent((f=((e,t)=>{for(var r in t||(t={}))n.call(t,r)&&s(e,r,t[r]);if(o)for(var r of o(t))a.call(t,r)&&s(e,r,t[r]);return e;})({},p),t(f,r({setup:(e)=>(e,t)=>({a:c.p({"container-class":"bg---h-fff-","test-class":{"bg---h-fff-":true},"container-style":{backgroundColor:"#fff"}})})}))));var f;wx.createComponent(i);`,
217 | );
218 | });
219 | });
220 |
--------------------------------------------------------------------------------