diff --git a/.eslintignore b/.eslintignore index a4a23f6ed035..16a6f7424c86 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,8 +1,10 @@ dist dist-h5 lib +!**/src/lib node_modules *.d.ts +*.ets coverage examples @@ -14,14 +16,8 @@ packages/taro-components/loader packages/taro-components/src/components packages/taro-components-library-react/components.ts packages/taro-components-library-vue3/components.ts -packages/taro-components-library-vue3/components.ts - +packages/taro-components-library-solid/components.ts -packages/taro-webpack-runner/src/__tests__/__snapshots__ -packages/taro-webpack-runner/src/__tests__/fixtures -packages/taro-mini-runner/src/__tests__/__snapshots__ -packages/taro-mini-runner/src/__tests__/fixtures -packages/taro-mini-runner/src/quickapp packages/taro-webpack5-runner/src/__tests__/__snapshots__ packages/taro-webpack5-runner/src/__tests__/fixtures packages/taro-webpack5-runner/src/__tests__/bundled @@ -31,6 +27,7 @@ packages/taro/types packages/taro-router/__tests__ packages/taro-rn-style-transformer/src/transforms/StyleSheet +packages/taro-rn/src/lib/index.ts packages/taro-rn/src/__tests__ packages/taro-runtime/src/polyfill diff --git a/.eslintrc.js b/.eslintrc.js index e235012d9090..5bfba0dfe627 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,7 +19,10 @@ module.exports = { '@typescript-eslint/explicit-function-return-type': 0, '@typescript-eslint/explicit-module-boundary-types': 0, '@typescript-eslint/indent': [2, 2], - '@typescript-eslint/member-delimiter-style': [1, { multiline: { delimiter: 'none' }, singleline: { delimiter: 'comma' } }], + '@typescript-eslint/member-delimiter-style': [ + 1, + { multiline: { delimiter: 'none' }, singleline: { delimiter: 'comma' } }, + ], '@typescript-eslint/no-empty-function': 0, '@typescript-eslint/no-explicit-any': 0, '@typescript-eslint/no-namespace': 0, @@ -29,58 +32,67 @@ module.exports = { '@typescript-eslint/no-use-before-define': [1, { functions: false, classes: false }], '@typescript-eslint/no-var-requires': 0, camelcase: 0, - 'eol-last': 0, + 'eol-last': 2, 'comma-dangle': 0, - 'no-mixed-operators': 0, - 'no-multiple-empty-lines': 0, - 'import/first': 2, - 'import/newline-after-import': 2, - 'import/no-duplicates': 2, - indent: 'off', + 'comma-spacing': 2, 'no-console': [2, { allow: ['warn', 'error'] }], + 'no-empty': 1, + 'no-multi-spaces': 2, + 'no-multiple-empty-lines': 0, + 'no-mixed-operators': 0, 'no-prototype-builtins': 0, 'no-unused-expressions': 0, 'no-unused-vars': 'off', 'no-use-before-define': 0, + 'import/first': 2, + 'import/newline-after-import': 2, + 'import/no-duplicates': 2, + 'import/no-named-default': 'off', + indent: 0, + 'keyword-spacing': 2, 'object-curly-spacing': 2, - 'no-empty': 1, + 'operator-linebreak': [2, 'after', { + overrides: { '?': 'before', ':': 'before' }, + }], 'prefer-spread': 0, 'prefer-rest-params': 0, 'react/jsx-uses-vars': 1, 'react/prop-types': 0, 'react/no-find-dom-node': 0, 'react/no-unknown-property': 0, - 'import/no-named-default': 'off', quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], semi: [2, 'never'], - 'simple-import-sort/imports': [2, { - groups: [ - // Side effect imports. - ['^\\u0000'], - // Node.js builtins prefixed with `node:`. - ['^node:'], - // Packages. - // Things that start with a letter (or digit or underscore), or `@` followed by a letter. - ['^@?\\w'], - // Absolute imports and other imports such as Vue-style `@/foo`. - // Anything not matched in another group. - ['^'], - // Relative imports. - // Anything that starts with a dot. - ['^\\.'], - // Types Group - ['^node:.*\\u0000$', '^@?\\w.*\\u0000$', '(?<=\\u0000)$', '^\\..*\\u0000$'], - ] - }], + 'simple-import-sort/imports': [ + 2, + { + groups: [ + // Side effect imports. + ['^\\u0000'], + // Node.js builtins prefixed with `node:`. + ['^node:'], + // Packages. + // Things that start with a letter (or digit or underscore), or `@` followed by a letter. + ['^@?\\w'], + // Absolute imports and other imports such as Vue-style `@/foo`. + // Anything not matched in another group. + ['^'], + // Relative imports. + // Anything that starts with a dot. + ['^\\.'], + // Types Group + ['^node:.*\\u0000$', '^@?\\w.*\\u0000$', '(?<=\\u0000)$', '^\\..*\\u0000$'], + ], + }, + ], 'simple-import-sort/exports': 2, - 'space-before-function-paren': [2, 'always'], - 'standard/no-callback-literal': 0 + 'space-before-function-paren': 0, + 'standard/no-callback-literal': 0, }, env: { 'jest/globals': true, browser: true, node: true, - es6: true + es6: true, }, globals: { testRule: 'readonly', @@ -94,16 +106,16 @@ module.exports = { requirePlugin: 'readonly', jd: 'readonly', ks: 'readonly', - LOCATION_APIKEY: 'readonly' + LOCATION_APIKEY: 'readonly', }, parserOptions: { ecmaFeatures: { - jsx: true - } + jsx: true, + }, }, settings: { react: { - version: 'detect' - } - } + version: 'detect', + }, + }, } diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 030d0061af57..372a681af5da 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -66,3 +66,10 @@ updates: interval: daily time: "21:00" open-pull-requests-limit: 10 +- package-ecosystem: "npm" + directory: "/examples" + schedule: + interval: daily + time: "21:00" + ignore: + - dependency-name: "*" \ No newline at end of file diff --git a/.github/workflows/build-rust-binding.yml b/.github/workflows/build-rust-binding.yml index 605411f1aa75..3fb22d3bcbb7 100644 --- a/.github/workflows/build-rust-binding.yml +++ b/.github/workflows/build-rust-binding.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [16.x] + node-version: [18.x] settings: - host: macos-12 target: x86_64-apple-darwin @@ -48,7 +48,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v3.0.0 with: - version: 7 + version: 8 - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 if: ${{ !matrix.settings.docker }} diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index f785a1fab0e8..6242e217a018 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -5,6 +5,13 @@ env: on: push: branches: + - 'chore/**' + - 'feat/**' + - 'fix/**' + - '1.x' + - '2.x' + - '3.x' + - '4.x' - main pull_request: branches: @@ -22,15 +29,16 @@ jobs: uses: ./.github/workflows/build-rust-wasm.yml nodejs-tesing: - name: Testing on Node.js ${{ matrix.node-version }} (${{ matrix.settings.host }}) + name: Testing on Node.js ${{ matrix.node-version }} (${{ matrix.host }}) needs: - build-rust-binding - build-rust-wasm strategy: fail-fast: false matrix: - node-version: [16.x] - settings: + node-version: [18.x, 20.x] + host: [macos-12, windows-latest, ubuntu-latest] + include: - host: macos-12 target: x86_64-apple-darwin - host: windows-latest @@ -39,14 +47,24 @@ jobs: target: x86_64-unknown-linux-gnu - host: ubuntu-latest target: x86_64-unknown-linux-musl - runs-on: ${{ matrix.settings.host }} + exclude: + - node-version: 18.x + host: macos-12 + - node-version: 18.x + host: windows-latest + - node-version: 20.x + host: macos-12 + - node-version: 20.x + host: windows-latest + + runs-on: ${{ matrix.host }} steps: - name: Checkout uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v3.0.0 with: - version: 7 + version: 8 - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: @@ -67,22 +85,21 @@ jobs: run: pnpm -r install --frozen-lockfile - name: Lint run: pnpm lint - - name: Download artifact bindings-${{ matrix.settings.target }} + - name: Download artifact bindings-${{ matrix.target }} uses: actions/download-artifact@v4 with: - name: bindings-${{ matrix.settings.target }} + name: bindings-${{ matrix.target }} path: crates/native_binding - name: Test bindings run: pnpm test:binding - if: ${{ matrix.settings.target != 'x86_64-unknown-linux-gnu' && matrix.settings.target != 'x86_64-unknown-linux-musl'}} + if: ${{ matrix.host != 'ubuntu-latest' }} - name: Test bindings with docker - if: ${{ matrix.settings.target == 'x86_64-unknown-linux-gnu' || matrix.settings.target == 'x86_64-unknown-linux-musl'}} + if: ${{ matrix.host == 'ubuntu-latest' }} # 暂时使用了一个第三方的 docker 镜像 run: docker run --rm -v $(pwd):/build -w /build chf007/pnpm pnpm test:binding # 以下的测试流程应该在所有平台都执行,但 windows 好像还有些问题,因此目前只在 ubuntu-latest 执行 - name: Download all artifacts uses: actions/download-artifact@v4 - if: ${{ matrix.settings.host == 'ubuntu-latest' }} with: path: crates/native_binding/artifacts - name: List Package crates/native_binding @@ -90,20 +107,21 @@ jobs: shell: bash - name: Move artifacts run: pnpm artifacts - if: ${{ matrix.settings.host == 'ubuntu-latest' }} - name: build run: pnpm build - if: ${{ matrix.settings.host == 'ubuntu-latest' }} - name: test run: pnpm test - if: ${{ matrix.settings.host == 'ubuntu-latest' }} env: CI: true + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # 以下 coverage 流程通过 artifact 拆分文件作为单独 job 上传时间损耗过长,因此在在 node test 后直接继续执行 - name: Upload [taro-cli] coverage to Codecov uses: codecov/codecov-action@v4 - if: ${{ matrix.settings.host == 'ubuntu-latest' }} + if: ${{ matrix.host == 'ubuntu-latest' }} with: move_coverage_to_trash: true flags: taro-cli @@ -111,15 +129,15 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} - name: Upload runner coverage to Codecov uses: codecov/codecov-action@v4 - if: ${{ matrix.settings.host == 'ubuntu-latest' }} + if: ${{ matrix.host == 'ubuntu-latest' }} with: move_coverage_to_trash: true flags: taro-runner - files: ./packages/taro-webpack5-runner/coverage/clover.xml,./packages/taro-webpack-runner/coverage/clover.xml,./packages/taro-mini-runner/coverage/clover.xml + files: ./packages/taro-webpack5-runner/coverage/clover.xml token: ${{ secrets.CODECOV_TOKEN }} - name: Upload [taro-runtime] coverage to Codecov uses: codecov/codecov-action@v4 - if: ${{ matrix.settings.host == 'ubuntu-latest' }} + if: ${{ matrix.host == 'ubuntu-latest' }} with: move_coverage_to_trash: true flags: taro-runtime @@ -127,7 +145,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} - name: Upload [taro-web] coverage to Codecov uses: codecov/codecov-action@v4 - if: ${{ matrix.settings.host == 'ubuntu-latest' }} + if: ${{ matrix.host == 'ubuntu-latest' }} with: move_coverage_to_trash: true flags: taro-web @@ -135,7 +153,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} - name: Upload rest coverage to Codecov uses: codecov/codecov-action@v4 - if: ${{ matrix.settings.host == 'ubuntu-latest' }} + if: ${{ matrix.host == 'ubuntu-latest' }} with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 93cb33d8f643..15a7a96465d8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,6 +5,10 @@ on: - 'chore/**' - 'feat/**' - 'fix/**' + - '1.x' + - '2.x' + - '3.x' + - '4.x' # When Release Pull Request is merged pull_request: branches: @@ -84,11 +88,11 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v3.0.0 with: - version: 7 - - name: Setup Node 16 + version: 8 + - name: Setup Node 18 uses: actions/setup-node@v4 with: - node-version: 16 + node-version: 18 cache: 'pnpm' registry-url: 'https://registry.npmjs.org' # Don't touch! - name: Git Identity diff --git a/.github/workflows/sync-components-types.yml b/.github/workflows/sync-components-types.yml index a5597c8187ed..2971c67f104d 100644 --- a/.github/workflows/sync-components-types.yml +++ b/.github/workflows/sync-components-types.yml @@ -36,16 +36,16 @@ jobs: - name: install uses: pnpm/action-setup@v3.0.0 with: - version: 7 + version: 8 run_install: | - recursive: true args: [--frozen-lockfile, --strict-peer-dependencies] - args: [--filter @tarojs/components, -D, miniapp-types@latest] # Note: 当前同步脚本使用 ts-node 与 node20 存在兼容问题,修复后解除版本限制 - - name: Setup Node 16 + - name: Setup Node 18 uses: actions/setup-node@v4 with: - node-version: 16 + node-version: 18 cache: 'pnpm' registry-url: 'https://registry.npmjs.org' # Don't touch! diff --git a/.gitignore b/.gitignore index 1e103109c873..e60e75202003 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,3 @@ artifacts # Node Addons *.node - -# harmony-hybrid extend-h5-apis file -packages/taro-platform-harmony-hybrid/src/api/apis/extend-h5-apis.ts diff --git a/.husky/commit-msg b/.husky/commit-msg old mode 100755 new mode 100644 diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100755 new mode 100644 diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs new file mode 100644 index 000000000000..b325287da3e4 --- /dev/null +++ b/.pnpmfile.cjs @@ -0,0 +1,14 @@ +module.exports = { + hooks: { + readPackage: (pkg) => { + // @stencil/core 没有锁住 jest 的版本,所以需要手动锁住 + if (pkg.name === '@stencil/core') { + pkg.dependencies = { + 'jest-runner': '27.5.1', + 'jest-environment-node': '27.5.1', + } + } + return pkg + }, + }, +}; diff --git a/.vscode/launch.json b/.vscode/launch.json index 7abf2978c8d0..467ea8397f89 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "type": "lldb", + "type": "node", "request": "launch", "name": "debug-init", "sourceLanguages": ["rust"], diff --git a/.vscode/settings.json b/.vscode/settings.json index 14c42a2afc83..ebd73d16e806 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { + "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], "search.exclude": { "**/.git": true, "**/node_modules": true, @@ -10,7 +11,8 @@ "javascript.format.insertSpaceBeforeFunctionParenthesis": true, "typescript.format.insertSpaceBeforeFunctionParenthesis": true, "files.associations": { - "*.json": "jsonc" + "*.json": "jsonc", + "*.ets": "typescript" }, "rust-analyzer.linkedProjects": [ "./crates/taro_init/Cargo.toml", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ca77940c05a..67574c6f0615 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,7 +114,7 @@ $ npm run clear-all **注意:** -`@tarojs/mini-runner`、`@tarojs/webpack-runner`、`@tarojs/webpack5-runner` 使用了 `snapshot`(测试结果快照)。在修改这两个包或其它一些包时,有可能导致这些快照失效,从而通过不了测试。当你修改了这两个包、或 Github CI 提示这些包的测试用例出错时,请运行 `pnpm --filter [package-name] runupdateSnapshot` 更新 snapshot 后重新提交。 +`@tarojs/webpack5-runner` 使用了 `snapshot`(测试结果快照)。在修改这两个包或其它一些包时,有可能导致这些快照失效,从而通过不了测试。当你修改了这两个包、或 Github CI 提示这些包的测试用例出错时,请运行 `pnpm --filter [package-name] runupdateSnapshot` 更新 snapshot 后重新提交。 ### 5. 代码风格 diff --git a/Cargo.lock b/Cargo.lock index 7679800f9bbd..80018fcb0743 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" +checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", "syn 2.0.50", @@ -337,9 +337,9 @@ dependencies = [ [[package]] name = "deunicode" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a1abaf4d861455be59f64fd2b55606cb151fce304ede7165f410243ce96bde6" +checksum = "3ae2a35373c5c74340b79ae6780b498b2b183915ec5dacf263aac5a099bf485a" [[package]] name = "diff" @@ -716,9 +716,9 @@ checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jmespath" @@ -740,9 +740,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libloading" @@ -965,9 +965,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -1262,9 +1262,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "relative-path" -version = "1.9.0" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" [[package]] name = "rend" @@ -1381,9 +1381,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "scoped-tls" @@ -1479,9 +1479,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.27" +version = "0.9.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" +checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129" dependencies = [ "indexmap", "itoa", @@ -1735,9 +1735,9 @@ dependencies = [ [[package]] name = "swc_core" -version = "0.87.2" +version = "0.87.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d258de92df3ac19f6d49a9a6a18750e3cc4fcd483bb08b254db955a75baff71" +checksum = "1b815a0f6e8a5f77aa6c3863e3d4e73864f8c62f6e07adcbac72ce26fec59798" dependencies = [ "once_cell", "swc_atoms", @@ -1776,9 +1776,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.146.45" +version = "0.146.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be75490c5a5cad616587cb8600ce387bb5925c8a9a3e44de674b67bf962fb439" +checksum = "a7d39a90607035cd7d3dca3bcdf1c086a9453ec32e1c9c91c59ea4a9b702cd2b" dependencies = [ "memchr", "num-bigint", @@ -1842,9 +1842,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.135.1" +version = "0.135.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0b068341f5a479875415e0eed6aeb0eb63e09e300cb9b0caef117e98c65200" +checksum = "81d3f06d8da75e01249631d788211aad2a028640c3a4e222cfda7930235f3ea4" dependencies = [ "better_scoped_tls", "bitflags 2.4.1", @@ -1865,9 +1865,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_testing" -version = "0.138.1" +version = "0.138.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f96a473c64e7f777b594d180114500caa917ee3585c3b28b87884876f72a2098" +checksum = "6ebc487d155be02aff745fa8ca356c4df993482a12b58a024565b705d8a4a5c5" dependencies = [ "ansi_term", "anyhow", @@ -1969,6 +1969,7 @@ dependencies = [ name = "swc_plugin_compile_mode" version = "0.1.0" dependencies = [ + "regex", "serde", "serde_json", "swc_core", @@ -2186,18 +2187,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" dependencies = [ "proc-macro2", "quote", @@ -2431,9 +2432,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unsafe-libyaml" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" [[package]] name = "untrusted" diff --git a/Cargo.toml b/Cargo.toml index 493a0755adf9..72215fb1bde0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,22 @@ members = ["crates/*"] resolver = "2" [workspace.dependencies] +napi = { version = "=2.14.1", features = ["napi4", "tokio_rt"] } +napi-build = { version = "=2.1.0" } +napi-derive = { version = "=2.14.4" } +napi-sys = { version = "=2.3.0" } +once_cell = { version = "=1.19.0" } +tokio = { version = "=1.34.0" } +tokio-util = { version = "=0.7.10" } +futures = { version = "=0.3.30" } +handlebars = { version = "=4.5.0" } +handlebars_misc_helpers = { version = "=0.13.0" } serde_json = { version = "=1.0.114" } serde = { version = "=1.0.197", features = ["serde_derive"] } +anyhow = { version = "=1.0.75" } +console = { version = "=0.15.7" } +spinners = { version = "=4.1.1" } +regex = { version = "=1.10.2" } swc_core = { version = "0.87.*", features = ["ecma_plugin_transform", "ecma_utils"] } [patch.crates-io] diff --git a/LICENSE b/LICENSE index d8572bc88d6f..71cfae07e043 100644 --- a/LICENSE +++ b/LICENSE @@ -154,15 +154,8 @@ See `/LICENSE` for details of the license. ================== -MIT (stencil-vue2-output-target): -The following files embed [stencil-vue2-output-target](https://github.com/diondree/stencil-vue2-output-target) MIT: -`/packages/taro-components-library-vue2/src/vue-component-lib/utils.ts` -See `/LICENSE` for details of the license. - -================== - MIT (weui): -The following files embed [stencil-vue2-output-target](https://github.com/Tencent/weui) MIT: +The following files embed [weui](https://github.com/Tencent/weui) MIT: `/packages/taro-components/src/components/*.scss` See `/LICENSE.txt` for details of the license. @@ -172,3 +165,10 @@ Apache-2.0 (intersection-observer): The following files embed [intersection-observer](https://github.com/GoogleChromeLabs/intersection-observer) Apache-2.0: `/packages/taro-api/src/polyfill/intersection-observer.ts` See `/LICENSE.txt` for details of the license. + +================== + +MIT (babel-plugin-jsx-dom-expressions): +The following files embed [babel-plugin-jsx-dom-expressions](https://github.com/ryansolid/dom-expressions/blob/main/packages/babel-plugin-jsx-dom-expressions) MIT: +`/packages/babel-plugin-transform-solid-jsx/src/*` +See `/LICENSE` for details of the license. diff --git a/codecov.yml b/codecov.yml index 5e0e45b7ea60..05d1fcb37246 100644 --- a/codecov.yml +++ b/codecov.yml @@ -35,8 +35,6 @@ flag_management: - name: taro-runner paths: - packages/taro-webpack5-runner/ - - packages/taro-webpack-runner/ - - packages/taro-mini-runner/ - name: taro-runtime paths: - packages/taro-runtime/ diff --git a/crates/native_binding/binding.d.ts b/crates/native_binding/binding.d.ts index 02ecede0636a..d1c87704d427 100644 --- a/crates/native_binding/binding.d.ts +++ b/crates/native_binding/binding.d.ts @@ -27,6 +27,9 @@ export interface CreateOptions { pageName?: string compiler?: CompilerType setPageName?: string + subPkg?: string + pageDir?: string + setSubPkgPageName?: string changeExt?: boolean isCustomTemplate?: boolean pluginType?: string @@ -48,8 +51,9 @@ export const enum CSSType { export const enum FrameworkType { React = 'React', Preact = 'Preact', - Vue = 'Vue', - Vue3 = 'Vue3' + Vue3 = 'Vue3', + Solid = 'Solid', + None = 'None' } export const enum NpmType { @@ -76,6 +80,8 @@ export interface Page { customTemplatePath?: string basePageFiles: Array period: PeriodType + subPkg?: string + pageDir?: string } export const enum PeriodType { diff --git a/crates/native_binding/package.json b/crates/native_binding/package.json index 1b9cdd4a7957..fbf098e7821f 100644 --- a/crates/native_binding/package.json +++ b/crates/native_binding/package.json @@ -1,6 +1,6 @@ { "name": "@tarojs/binding", - "version": "3.6.34", + "version": "4.0.4", "description": "Node binding for taro", "main": "binding.js", "typings": "binding.d.ts", diff --git a/crates/native_binding/src/lib.rs b/crates/native_binding/src/lib.rs index fd1f56a287b0..5100146bb1da 100644 --- a/crates/native_binding/src/lib.rs +++ b/crates/native_binding/src/lib.rs @@ -62,6 +62,8 @@ pub async fn create_page( conf.custom_template_path, conf.base_page_files, conf.period, + conf.sub_pkg, + conf.page_dir, ); let mut thread_safe_functions = HashMap::new(); for (key, callback) in handlers { diff --git a/crates/swc_plugin_compile_mode/Cargo.toml b/crates/swc_plugin_compile_mode/Cargo.toml index d5c336bb7a22..732ddbdccdba 100644 --- a/crates/swc_plugin_compile_mode/Cargo.toml +++ b/crates/swc_plugin_compile_mode/Cargo.toml @@ -10,6 +10,7 @@ crate-type = ["cdylib"] serde = { workspace = true } serde_json = { workspace = true } swc_core = { workspace = true } +regex = "1.10.2" [dev-dependencies] swc_core = { workspace = true, features = ["ecma_parser", "ecma_codegen"] } diff --git a/crates/swc_plugin_compile_mode/src/lib.rs b/crates/swc_plugin_compile_mode/src/lib.rs index dc67c34a668b..593e7e5fdd59 100644 --- a/crates/swc_plugin_compile_mode/src/lib.rs +++ b/crates/swc_plugin_compile_mode/src/lib.rs @@ -24,6 +24,11 @@ impl SerdeDefault { } } +#[derive(Deserialize, Debug)] +pub struct ComponentReplace { + pub current_init: String, + pub dependency_define: String, +} #[derive(Deserialize, Debug)] pub struct PluginConfig { pub tmpl_prefix: String, @@ -41,6 +46,9 @@ pub struct PluginConfig { pub support_components: Vec, #[serde(default)] pub event_adapter: HashMap, + #[serde(default)] + pub component_replace: HashMap, + } /// An example plugin function with macro support. diff --git a/crates/swc_plugin_compile_mode/src/tests/harmony/children.rs b/crates/swc_plugin_compile_mode/src/tests/harmony/children.rs new file mode 100644 index 000000000000..341731a2aace --- /dev/null +++ b/crates/swc_plugin_compile_mode/src/tests/harmony/children.rs @@ -0,0 +1,96 @@ +use swc_core::ecma::transforms::testing::test; +use super::{tr, get_syntax_config}; + +test!( + get_syntax_config(), + |_| tr(), + should_support_render_fn, + r#" + function Index () { + return ( + + {renderHeader()} + + {renderHeader()} + {normalFunc()} + + {this.methods.renderFooter()} + {normalFunc()} + + ) + } + "# +); + +test!( + get_syntax_config(), + |_| tr(), + should_support_fragment, + r#" + function Index () { + return ( + + {content0} + <> + {content1} + hello + + <> + {content2} + hello! + + + {content3} + + {content4} + <> + {content5} + + <> + hello!! + + hello!!! + {content6} + + ) + } + "# +); + +test!( + get_syntax_config(), + |_| tr(), + should_render_react_component, + r#" + function Index () { + return ( + + + {}} /> + + ) + } + "# +); + +test!( + get_syntax_config(), + |_| tr(), + should_render_complex_children, + r#" + function Index () { + return ( + + {showModal && + + + + {haveIcon && } + + + } + + ) + } + "# +); diff --git a/crates/swc_plugin_compile_mode/src/tests/harmony/condition.rs b/crates/swc_plugin_compile_mode/src/tests/harmony/condition.rs index 3fe623fc4324..97c264e49478 100644 --- a/crates/swc_plugin_compile_mode/src/tests/harmony/condition.rs +++ b/crates/swc_plugin_compile_mode/src/tests/harmony/condition.rs @@ -41,4 +41,39 @@ test!( ) } "# -); \ No newline at end of file +); + +test!( + get_syntax_config(), + |_| tr(), + should_support_conditional_and_unkonw_component, + r#" + function Index () { + return ( + + {condition ? hello : hello} + + ) + } + "# +); + +test!( + get_syntax_config(), + |_| tr(), + should_support_complex_condition, + r#" + function Index () { + return ( + + {condition1 && condition2 && doSth()} />} + {condition1 && ident} + {condition1 && obj.property} + {condition1 && fn()} + {condition1 ? : } + {condition1 ? {condition2 ? : } : } + + ) + } + "# +); diff --git a/crates/swc_plugin_compile_mode/src/tests/harmony/mod.rs b/crates/swc_plugin_compile_mode/src/tests/harmony/mod.rs index 48fb71ad865a..858212721f97 100644 --- a/crates/swc_plugin_compile_mode/src/tests/harmony/mod.rs +++ b/crates/swc_plugin_compile_mode/src/tests/harmony/mod.rs @@ -9,6 +9,7 @@ mod entry; mod attributes; mod condition; mod looping; +mod children; pub fn tr () -> impl Fold + VisitMut { let config = serde_json::from_str::( diff --git a/crates/swc_plugin_compile_mode/src/transform.rs b/crates/swc_plugin_compile_mode/src/transform.rs index e17c62a3fa59..723996323344 100644 --- a/crates/swc_plugin_compile_mode/src/transform.rs +++ b/crates/swc_plugin_compile_mode/src/transform.rs @@ -37,11 +37,11 @@ impl VisitMut for PreVisitor { // 收集 JSX 循环表达式到 list children - .iter() + .iter_mut() .enumerate() .for_each(|(i, child)| { if let JSXElementChild::JSXExprContainer(JSXExprContainer { expr: JSXExpr::Expr(expr), .. }) = child { - if let Expr::Call(CallExpr { callee: Callee::Expr(callee_expr), args, .. }) = &**expr { + if let Expr::Call(CallExpr { callee: Callee::Expr(callee_expr), args, .. }) = &mut **expr { if utils::is_call_expr_of_loop(callee_expr, args) { list.push(i); } @@ -212,7 +212,7 @@ impl TransformVisitor { } fn build_xml_element (&mut self, el: &mut JSXElement) -> String { - let is_inner_component = utils::is_inner_component(el, &self.config); + let is_inner_component = utils::is_inner_component(&el, &self.config); let opening_element = &mut el.opening; match &opening_element.name { diff --git a/crates/swc_plugin_compile_mode/src/transform_harmony.rs b/crates/swc_plugin_compile_mode/src/transform_harmony.rs index 7725bd381dbc..acf6cea6a130 100644 --- a/crates/swc_plugin_compile_mode/src/transform_harmony.rs +++ b/crates/swc_plugin_compile_mode/src/transform_harmony.rs @@ -1,117 +1,116 @@ - -use swc_core::{ - common::{ - util::take::Take, - DUMMY_SP as span - }, - ecma::{ - self, - ast::*, - atoms::Atom, - visit::{VisitMut, VisitMutWith}, - }, -}; +use crate::utils::{ self, constants::*, harmony::components::* }; +use crate::{PluginConfig, ComponentReplace}; use std::collections::HashMap; -use std::rc::Rc; use std::collections::HashSet; -use crate::PluginConfig; -use crate::utils::{self, constants::*, harmony::components::*}; +use swc_core::{ + common::{ util::take::Take, DUMMY_SP as span }, + ecma::{ self, ast::*, atoms::Atom, visit::{ swc_ecma_ast, VisitMut, VisitMutWith } }, +}; +use regex::Regex; +pub struct PreVisitor {} -pub struct PreVisitor { - is_in_jsx_expr_container: Rc, - is_in_and_expr: bool, +pub enum EtsDirection { + Row, + Column, + Flex, } + impl PreVisitor { - fn new () -> Self { - Self { - is_in_jsx_expr_container: Rc::new(true), - is_in_and_expr: false, - } + fn new() -> Self { + Self {} + } + fn process_cond_expr(&self, expr: &mut CondExpr) { + let test = &expr.test; + let cons = &mut expr.cons; + let compile_if = utils::create_jsx_expr_attr(COMPILE_IF, test.clone()); + let process_cond_arm = |arm: &mut Box, attr: JSXAttrOrSpread| { + match &mut **arm { + Expr::JSXElement(el) => { + el.opening.attrs.push(attr); + } + Expr::Cond(cond_expr) => { + self.process_cond_expr(cond_expr); + } + _ => (), + } + }; + process_cond_arm(cons, compile_if); } } impl VisitMut for PreVisitor { - fn visit_mut_jsx_expr_container (&mut self, container: &mut JSXExprContainer) { - let _counter = Rc::clone(&self.is_in_jsx_expr_container); - // TODO 目前的判断可能误伤函数内的三元表达式、条件表达式 - container.visit_mut_children_with(self); - } - fn visit_mut_expr (&mut self, expr: &mut Expr) { - if Rc::strong_count(&self.is_in_jsx_expr_container) == 1 { return }; - let mut is_first_and_expr = false; - - // is_in_and_expr 为 false 时,表示为当前 expr 的第一个 && 表达式,即当出现 aa && bb && 这种表达式时,只会处理最右侧的 && 表达式 - match expr { - // 将 aa && 转换为 aa ? : - Expr::Bin(BinExpr { op, left, right, ..}) => { - // C&&A 替换为 C?A:A',原因是为了无论显示还是隐藏都保留一个元素,从而不影响兄弟节点的变量路径 - if *op == op!("&&") && !self.is_in_and_expr { - is_first_and_expr = true; - fn inject_compile_if (el: &mut Box, condition: &mut Box) -> () { - el.opening.attrs.push(utils::create_jsx_expr_attr(COMPILE_IF, condition.clone())); - } - fn get_element_double (element_name: JSXElementName, condition: &mut Box, right: &mut Box) -> Expr { - Expr::Cond(CondExpr { - span, - test: condition.take(), - cons: right.take(), - alt: Box::new(utils::create_self_closing_jsx_element_expr( - element_name, // element 替换为同类型的元素。在显示/隐藏切换时,让运行时 diff 只更新必要属性而不是整个节点刷新 - Some(vec![utils::create_jsx_bool_attr(COMPILE_IGNORE)] - ))) - }) - } - match &mut **right { - Expr::JSXElement(el) => { - let element_name = el.opening.name.clone(); - inject_compile_if(el, left); - *expr = get_element_double(element_name, left, right); - }, - Expr::Paren(ParenExpr { expr: paren_expr, .. }) => { - if paren_expr.is_jsx_element() { - let el: &mut Box = paren_expr.as_mut_jsx_element().unwrap(); - let element_name = el.opening.name.clone(); - inject_compile_if(el, left); - *expr = get_element_double(element_name, left, paren_expr); - } - }, - Expr::Lit(_) => { - *expr = Expr::Cond(CondExpr { + fn visit_mut_jsx_element_child(&mut self, child: &mut JSXElementChild) { + if let JSXElementChild::JSXExprContainer(JSXExprContainer { expr: JSXExpr::Expr(expr), .. }) = child { + if let Expr::Paren(ParenExpr { expr: e, .. }) = &mut **expr { + *expr = e.take(); + } + + match &mut **expr { + // 将 aa && 转换为 aa ? : + Expr::Bin(BinExpr { op, left, right, .. }) => { + // C&&A 替换为 C?A:A',原因是为了无论显示还是隐藏都保留一个元素,从而不影响兄弟节点的变量路径 + if *op == op!("&&") { + fn inject_compile_if(el: &mut Box, condition: &mut Box) -> () { + el.opening.attrs.push(utils::create_jsx_expr_attr(COMPILE_IF, condition.clone())); + } + fn get_element_double(condition: &mut Box, right: &mut Box) -> Expr { + Expr::Cond(CondExpr { span, - test: left.take(), + test: condition.take(), cons: right.take(), - alt: Box::new(Expr::Lit(Lit::Str(Str { span, value: String::new().into(), raw: None }))) + alt: Box::new( + utils::create_self_closing_jsx_element_expr( + JSXElementName::Ident( + swc_ecma_ast::Ident::new( + "View".into(), + span // 使用适当的 Span + ) + ), // element 替换为同类型的元素。在显示/隐藏切换时,让运行时 diff 只更新必要属性而不是整个节点刷新 + Some(vec![utils::create_jsx_bool_attr(COMPILE_IGNORE)]) + ) + ), }) - }, - _ => { - // TODO Unknown fallback to template - println!("unknown expr: {right:?}"); + } + match &mut **right { + Expr::JSXElement(el) => { + inject_compile_if(el, left); + **expr = get_element_double(left, right); + } + Expr::Paren(ParenExpr { expr: paren_expr, .. }) => { + if paren_expr.is_jsx_element() { + let el: &mut Box = paren_expr.as_mut_jsx_element().unwrap(); + inject_compile_if(el, left); + **expr = get_element_double(left, paren_expr); + } + } + Expr::Lit(_) => { + **expr = Expr::Cond(CondExpr { + span, + test: left.take(), + cons: right.take(), + alt: Box::new( + Expr::Lit( + Lit::Str(Str { + span, + value: String::new().into(), + raw: None, + }) + ) + ), + }); + } + _ => (), } } } - }, - Expr::Cond(CondExpr { test, cons, ..}) => { - let compile_if = utils::create_jsx_expr_attr(COMPILE_IF, test.clone()); - let process_cond_arm = |arm: &mut Box, attr: JSXAttrOrSpread| { - match &mut **arm { - Expr::JSXElement(el) => { - el.opening.attrs.push(attr); - }, - _ => () - } - }; - process_cond_arm(cons, compile_if); - }, - _ => (), - } - - if is_first_and_expr { - self.is_in_and_expr = true; - } - - expr.visit_mut_children_with(self); + Expr::Cond(cond_expr) => { + self.process_cond_expr(cond_expr); + } + _ => (), + } - if is_first_and_expr { - self.is_in_and_expr = false; + expr.visit_mut_children_with(self); + } else { + child.visit_mut_children_with(self); } } } @@ -125,11 +124,12 @@ pub struct TransformVisitor { pub templates: HashMap, pub get_tmpl_name: Box String>, pub node_name_vec: Vec, - pub get_node_name: Box String> + pub get_node_name: Box String>, + pub deal_loop_now: bool, } impl TransformVisitor { - pub fn new (config: PluginConfig) -> Self { + pub fn new(config: PluginConfig) -> Self { let get_node_name = Box::new(utils::named_iter(String::from("node"))); let get_tmpl_name = Box::new(utils::named_iter(format!("{}t", config.tmpl_prefix))); @@ -142,17 +142,23 @@ impl TransformVisitor { get_tmpl_name, component_set: HashSet::new(), node_name_vec: vec![], - get_node_name + get_node_name, + deal_loop_now: false, } } - fn build_ets_element (&mut self, el: &mut JSXElement) -> String { + pub fn get_dynmaic_node_name(&mut self, name: String) -> String { + let node_name = if self.deal_loop_now { name } else { format!("this.{}", name) }; + node_name.to_string() + } + + fn build_ets_element(&mut self, el: &mut JSXElement) -> String { // jsx 节点添加动态 id,需要判断是否存在静态节点 let dynmaic_node_name: String; let mut is_node_name_created = false; // 如果是半编译状态下碰到的第一个 jsx,或者 jsx 含有动态属性,则创建新的 node_name - if !self.check_jsx_is_static(el) || self.node_stack.is_empty() { + if !self.deal_loop_now && (!self.check_jsx_is_static(el) || self.node_stack.is_empty()) { dynmaic_node_name = utils::create_jsx_dynamic_id(el, self); self.node_name.push(dynmaic_node_name.clone()); is_node_name_created = true; @@ -170,14 +176,16 @@ impl TransformVisitor { // 内置组件 Some(_) => { // 事件的处理,根据事件添加对应的 ets 事件处理函数 - let event_string: String = self.build_ets_event(opening_element); + let mut event_string: String = self.build_ets_event(opening_element); + let element_direction: EtsDirection = self.build_ets_direction(opening_element); + let mut children = utils::create_original_node_renderer_foreach(self); - // 判断 el 的子元素是否只有一个循环,如果是的话,直接使用 createNode 来生成后续子节点 + // 只处理元素的子元素只有一个循环的情况和子元素没有循环的情况,其他情况先用 createLazyChildren 生成子结点 let is_loop_exist = utils::check_jsx_element_children_exist_loop(el); - let mut children = utils::create_original_node_renderer(self); - - if !is_loop_exist { - children = self.build_ets_children(el); + let el_children_len = utils::get_valid_nodes(&el.children); + if !is_loop_exist || (is_loop_exist && el_children_len == 1) { + let (temp_children, ..) = self.build_ets_children(&mut el.children, None); + children = temp_children; } // 当前 node_name 节点树已全部递归完毕 @@ -185,57 +193,81 @@ impl TransformVisitor { self.node_name.pop(); } - let mut code = match name.as_str() { - VIEW_TAG => { - self.component_set.insert(name.clone()); - get_view_component_str(&dynmaic_node_name, &children) - }, - TEXT_TAG => { - self.component_set.insert(name.clone()); - get_text_component_str(&dynmaic_node_name) - }, - IMAGE_TAG => { - self.component_set.insert(name.clone()); - get_image_component_str(&dynmaic_node_name) - }, - _ => String::new() + let current_node_name = self.get_dynmaic_node_name(dynmaic_node_name); + // 如果config配置的替换组件里有这个,就直接拿配置项里的当组件实例化 + let mut code = if self.config.component_replace.contains_key(name.as_str()) { + self.component_set.insert(name.clone()); + let ComponentReplace{current_init, ..} = self.config.component_replace.get(name.as_str()).unwrap(); + // 把入参的node改成对应的变量 + let reg = Regex::new(r"\bnode\b(:?)").unwrap(); + reg.replace_all(current_init, |caps: ®ex::Captures| { + if &caps[1] == ":" { + "node:".to_string() + } else { + format!("({} as TaroElement)", current_node_name) + } + }).to_string() + } else { + match name.as_str() { + VIEW_TAG => { + self.component_set.insert(name.clone()); + + get_view_component_str( + ¤t_node_name, + &children, + element_direction + ) + } + TEXT_TAG => { + self.component_set.insert(name.clone()); + event_string = "".to_owned(); + get_text_component_str(¤t_node_name) + } + IMAGE_TAG => { + self.component_set.insert(name.clone()); + get_image_component_str(¤t_node_name) + } + _ => String::new(), + } }; code.push_str(event_string.as_str()); utils::add_spaces_to_lines(code.as_str()) - }, + } None => { // React 组件 - // 原生自定义组件 + // 原生自定义组件, 不支持自定义组件的跟节点是一个 fragment!! // 半编译暂未支持的组件 utils::create_original_node_renderer(self) } } } - _ => String::new() + _ => String::new(), }; child_string } - fn build_ets_children (&mut self, el: &mut JSXElement) -> String { + fn build_ets_children( + &mut self, + children: &mut Vec, + retain_start_from: Option + ) -> (String, i32) { let mut children_string = String::new(); - let mut retain_child_counter = 0; + let start = if retain_start_from.is_some() { retain_start_from.unwrap() } else { 0 }; + let mut retain_child_counter = start; // 迭代 el.children - el.children.iter_mut().for_each(|child| { - self.node_stack.entry(self.node_name.last().unwrap().clone()).or_insert(vec![]).push(retain_child_counter); + children.iter_mut().for_each(|child| { + self.push_node_stack(retain_child_counter); match child { JSXElementChild::JSXElement(child_el) => { let child_string = self.build_ets_element(&mut **child_el); children_string.push_str(&child_string); - retain_child_counter = retain_child_counter + 1; - }, - JSXElementChild::JSXExprContainer(JSXExprContainer { - expr: JSXExpr::Expr(jsx_expr), - .. - }) => { + retain_child_counter += 1; + } + JSXElementChild::JSXExprContainer(JSXExprContainer { expr: JSXExpr::Expr(jsx_expr), .. }) => { // 如果套着 (),则获取括号里面的内容 if let Expr::Paren(ParenExpr { expr, .. }) = &mut **jsx_expr { *jsx_expr = expr.take(); @@ -243,39 +275,82 @@ impl TransformVisitor { match &mut **jsx_expr { Expr::Cond(cond_expr) => { children_string.push_str(self.build_ets_cond_expr(cond_expr).as_str()); - }, + } + Expr::Call(CallExpr { callee: Callee::Expr(callee_expr), args, .. }) => { + let mut handle_loop = false; + // 如果这个child是一个loop, {xxx.map(item => )} + if let Some(return_jsx) = utils::extract_jsx_loop(callee_expr, args) { + if !self.deal_loop_now { + handle_loop = true; + let loop_start = format!( + "ForEach(this.{}.childNodes, (item: TaroElement) => {{\n", + self.node_name.last().unwrap() + ); + let loop_foot = "}, (item: TaroElement) => item._nid.toString());"; + + // let loop_body = self.build_ets_children(&mut vec![JSXElementChild::JSXElement(return_jsx) ], None); + + self.deal_loop_now = true; + self.node_name.push("item".to_string()); + let loop_body = self.build_ets_element(return_jsx); + self.node_name.pop(); + self.deal_loop_now = false; + + children_string.push_str(&utils::add_spaces_to_lines(&loop_start)); + children_string.push_str(&utils::add_spaces_to_lines(&loop_body)); + children_string.push_str(&utils::add_spaces_to_lines(&loop_foot)); + } + } + if !handle_loop { + let mut tmpl = utils::create_normal_text_template(self, false); + if utils::is_render_fn(callee_expr) { + tmpl = utils::create_original_node_renderer(self); + } + children_string.push_str(&tmpl); + } + } _ => { - // TODO: 全部当普通文本处理,后续根据前缀是否为 render 来判断是 jsx 还是普通文本,后续会支持 render 开头的函数调用返回 JSX - // Expr::Call(_) - let node_path = self.get_current_node_path(); - let code = utils::add_spaces_to_lines(get_text_component_str(&node_path).as_str()); - self.component_set.insert(TEXT_TAG.to_string()); + let code = utils::create_normal_text_template(self, false); children_string.push_str(&code); } - }; - retain_child_counter = retain_child_counter + 1; - }, + } + retain_child_counter += 1; + } JSXElementChild::JSXText(jsx_text) => { let content = utils::jsx_text_to_string(&jsx_text.value); if !content.is_empty() { let current_path = self.get_current_node_path(); - let code = utils::add_spaces_to_lines(get_text_component_str(¤t_path).as_str()); + let code = utils::add_spaces_to_lines( + get_text_component_str(&self.get_dynmaic_node_name(current_path)).as_str() + ); children_string.push_str(code.as_str()); self.component_set.insert(TEXT_TAG.to_string()); - retain_child_counter = retain_child_counter + 1; + retain_child_counter += 1; } - }, + } + JSXElementChild::JSXFragment(child_el) => { + self.pop_node_stack(); + let (child_string, inner_retain) = self.build_ets_children( + &mut child_el.children, + Some(retain_child_counter) + ); + children_string.push_str(&child_string); + if inner_retain != 0 { + retain_child_counter += inner_retain; + } + self.push_node_stack(retain_child_counter); + } _ => (), } - self.node_stack.entry(self.node_name.last().unwrap().clone()).or_insert(vec![]).pop(); + self.pop_node_stack(); }); - children_string + (children_string, retain_child_counter - start) } - fn build_ets_cond_expr (&mut self, cond_expr: &mut CondExpr) -> String { + fn build_ets_cond_expr(&mut self, cond_expr: &mut CondExpr) -> String { let mut children_string = String::new(); let mut process_condition_expr = |arm: &mut Box| { match &mut **arm { @@ -286,7 +361,7 @@ impl TransformVisitor { } else { self.build_ets_element(el) } - }, + } Expr::Lit(_) => { // {condition1 && 'Hello'} 在预处理时会变成 {condition1 ? 'Hello' : "compileIgnore"} // 而普通文本三元则会被 block 标签包裹,因此处理后只有上述情况会存在 lit 类型的表达式 @@ -294,16 +369,24 @@ impl TransformVisitor { let current_path = self.get_current_node_path(); self.component_set.insert(TEXT_TAG.to_string()); - utils::add_spaces_to_lines(get_text_component_str(¤t_path).as_str()) - }, + utils::add_spaces_to_lines( + get_text_component_str(&self.get_dynmaic_node_name(current_path)).as_str() + ) + } Expr::Cond(cond_expr) => self.build_ets_cond_expr(cond_expr), - _ => String::new() + _ => String::new(), } }; let alt_children_string = process_condition_expr(&mut cond_expr.alt); let cons_children_string = process_condition_expr(&mut cond_expr.cons as &mut Box); - children_string.push_str(format!("if (this.{}._attrs.compileIf) {{\n{}}}", self.get_current_node_path(), cons_children_string).as_str()); + children_string.push_str( + format!( + "if (({} as TaroElement)._attrs.compileIf) {{\n{}}}", + self.get_dynmaic_node_name(self.get_current_node_path()), + cons_children_string + ).as_str() + ); if !alt_children_string.is_empty() { children_string.push_str(format!(" else {{\n{}}}", alt_children_string).as_str()); } @@ -311,7 +394,7 @@ impl TransformVisitor { utils::add_spaces_to_lines(&children_string) } - fn check_jsx_is_static (&self, el: &mut JSXElement) -> bool { + fn check_jsx_is_static(&self, el: &mut JSXElement) -> bool { let opening_element = &mut el.opening; for attr in opening_element.attrs.iter_mut() { @@ -326,20 +409,21 @@ impl TransformVisitor { if let Some(value) = &jsx_attr.value { match value { JSXAttrValue::Lit(..) => (), - JSXAttrValue::JSXExprContainer(JSXExprContainer{ expr, .. }) => { + JSXAttrValue::JSXExprContainer(JSXExprContainer { expr, .. }) => { // jsx_attr 是事件,而且事件的 value 不是一个变量,那么当作静态属性,否则 JSXExprContainer 情况下都当作非静态属性 if is_event { if let JSXExpr::Expr(expr) = &expr { if let Expr::Ident(..) = &**expr { - return false + return false; } } } else if !is_condition { - return false + return false; } - - }, - _ => return false + } + _ => { + return false; + } } } } @@ -349,14 +433,91 @@ impl TransformVisitor { // 判断当前 el 的 children 是否是表达式,表达式的话父节点需要标注为非静态 for child in el.children.iter_mut() { if let JSXElementChild::JSXExprContainer(JSXExprContainer { .. }) = child { - return false + return false; } } return true; } - fn build_ets_event (&self, opening_element: &mut JSXOpeningElement) -> String { + fn build_ets_direction(&self, opening_element: &mut JSXOpeningElement) -> EtsDirection { + // 判断 opening_element 中的 attrs.style._flexDirection 属性,如果值是 FlexDirection.Row,则返回 EtsDirection.Row,否则返回 EtsDirection.Column + let mut direction = EtsDirection::Column; + let mut is_flex = false; + for attr in opening_element.attrs.iter_mut() { + if let JSXAttrOrSpread::JSXAttr(jsx_attr) = attr { + if let JSXAttrName::Ident(Ident { sym: name, .. }) = &jsx_attr.name { + let jsx_attr_name = name.to_string(); + if jsx_attr_name == DIRECTION_ATTR { + if let Some(JSXAttrValue::Lit(Lit::Str(Str { value, .. }))) = &jsx_attr.value { + direction = match value.as_str() { + "row" => EtsDirection::Row, + "flex" => EtsDirection::Flex, + _ => EtsDirection::Column, + }; + } + } else if jsx_attr_name == STYLE_ATTR { + if + let Some(JSXAttrValue::JSXExprContainer(JSXExprContainer { expr, .. })) = + &mut jsx_attr.value + { + if let JSXExpr::Expr(expr) = expr { + if let Expr::Object(ObjectLit { props, .. }) = &mut **expr { + for prop in props.iter_mut() { + if let PropOrSpread::Prop(prop) = prop { + if let Prop::KeyValue(KeyValueProp { key, value, .. }) = &mut **prop { + if let PropName::Ident(Ident { sym: name, .. }) = key { + // 判断 display 是否为 flex 且 _flexDirection 为 FlexDirection.Column,如果是则返回 EtsDirection.Column,否则返回 EtsDirection.Row + if name == "display" { + if let Expr::Lit(Lit::Str(Str { value, .. })) = &mut **value { + if value == "flex" && is_flex == false { + direction = EtsDirection::Row; + } + } + } + + if name == "_flexDirection" { + is_flex = true; + // 判断 value 是否为 attrs.style._flexDirection: FlexDirection.Row 变量 + if + let Expr::Member(MemberExpr { obj, prop, .. }) = + &mut **value + { + if + let Expr::Ident(Ident { sym: obj_name, .. }) = + &mut **obj + { + if + let MemberProp::Ident( + Ident { sym: prop_name, .. }, + ) = &mut *prop + { + if + obj_name == "FlexDirection" && + prop_name == "Column" + { + direction = EtsDirection::Column; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + + direction + } + + fn build_ets_event(&mut self, opening_element: &mut JSXOpeningElement) -> String { let mut event_string = String::new(); // 在 transform 中使用的是 retain_mut 方法,在 retain_mut 中,如果 return false,就代表该属性不需要保留,在小程序中主要用于剪枝静态属性,鸿蒙这里不需要处理 @@ -378,7 +539,12 @@ impl TransformVisitor { jsx_attr_name = harmony_event_name.unwrap().to_string(); } - event_string.push_str(&create_component_event(jsx_attr_name.as_str(), &self.get_current_node_path())) + event_string.push_str( + &create_component_event( + jsx_attr_name.as_str(), + self.get_dynmaic_node_name(self.get_current_node_path()).as_str() + ) + ); } } } @@ -386,11 +552,13 @@ impl TransformVisitor { event_string } - fn get_current_node_path (&self) -> String { + pub fn get_current_node_path(&self) -> String { let current_node_name = self.node_name.last().unwrap().clone(); let current_node_stack = match self.node_stack.get(¤t_node_name) { Some(stack) => stack, - None => return current_node_name + None => { + return current_node_name; + } }; // return: node0.childNodes[0].childNodes[0].... @@ -399,101 +567,143 @@ impl TransformVisitor { return acc; }) } + + pub fn pop_node_stack(&mut self) { + self.node_stack + .entry(self.node_name.last().unwrap().clone()) + .or_insert(vec![]) + .pop(); + } + + pub fn push_node_stack(&mut self, index: i32) { + self.node_stack + .entry(self.node_name.last().unwrap().clone()) + .or_insert(vec![]) + .push(index); + } } impl VisitMut for TransformVisitor { - // Implement necessary visit_mut_* methods for actual custom transform. - // A comprehensive list of possible visitor methods can be found here: - // https://rustdoc.swc.rs/swc_ecma_visit/trait.VisitMut.html - // 半编译模式入口,遍历 jsx 语法树,寻找拥有 COMPILE_MODE 属性节点,预处理节点所在的子树,并根据子树信息生成 tmpl_contents - fn visit_mut_jsx_element (&mut self, el: &mut JSXElement) { - let mut tmpl_name = String::new(); - for attr in &mut el.opening.attrs { - if let JSXAttrOrSpread::JSXAttr(jsx_attr) = attr { - if let JSXAttrName::Ident(jsx_attr_name) = &jsx_attr.name { - if &*jsx_attr_name.sym == COMPILE_MODE { - self.is_compile_mode = true; - tmpl_name = (self.get_tmpl_name)(); - jsx_attr.value = Some(JSXAttrValue::Lit(Lit::Str(Str { - span, - value: tmpl_name.clone().into(), - raw: None, - }))); - break; - } - } - } - } - if self.is_compile_mode { - el.visit_mut_children_with(&mut PreVisitor::new()); - - let tmpl_build_contents = format!("build() {{\n{content}}}", content = self.build_ets_element(el)); - let tmpl_node_declare_contents = self.node_name_vec.iter().fold(String::new(), |mut acc, item| { - acc.push_str(&format!("@State {}: TaroElement = new TaroIgnoreElement()\n", item)); - return acc; - }); - let tmpl_main_contents = utils::add_spaces_to_lines(&format!("{}\n{}", tmpl_node_declare_contents, tmpl_build_contents)); - let tmpl_contents = - HARMONY_IMPORTER.to_owned() + - utils::get_harmony_component_style(self).as_str() + - format!( -r#"@Component -struct TARO_TEMPLATES_{name} {{ - nodeInfoMap: any = {{}} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement + // Implement necessary visit_mut_* methods for actual custom transform. + // A comprehensive list of possible visitor methods can be found here: + // https://rustdoc.swc.rs/swc_ecma_visit/trait.VisitMut.html + // 半编译模式入口,遍历 jsx 语法树,寻找拥有 COMPILE_MODE 属性节点,预处理节点所在的子树,并根据子树信息生成 tmpl_contents + fn visit_mut_jsx_element(&mut self, el: &mut JSXElement) { + let mut tmpl_name = String::new(); + // 遍历 JSX 元素的属性,寻找特定的 COMPILE_MODE 属性比如 xxx + for attr in &mut el.opening.attrs { + if let JSXAttrOrSpread::JSXAttr(jsx_attr) = attr { + if let JSXAttrName::Ident(jsx_attr_name) = &jsx_attr.name { + if &*jsx_attr_name.sym == COMPILE_MODE { + self.is_compile_mode = true; + tmpl_name = (self.get_tmpl_name)(); + jsx_attr.value = Some( + JSXAttrValue::Lit( + Lit::Str(Str { + span, + value: tmpl_name.clone().into(), + raw: None, + }) + ) + ); + break; + } + } + } + } + if self.is_compile_mode { + el.visit_mut_children_with(&mut PreVisitor::new()); + + let tmpl_build_contents = format!("build() {{\n{content}}}", content = self.build_ets_element(el)); + let tmpl_node_declare_contents = self.node_name_vec.iter().fold(String::new(), |mut acc, item| { + acc.push_str(&format!("@State {}: TaroElement = new TaroElement('Ignore')\n", item)); + return acc; + }); + let tmpl_main_contents = utils::add_spaces_to_lines( + &format!("{}\n{}", tmpl_node_declare_contents, tmpl_build_contents) + ); + let tmpl_contents = + HARMONY_IMPORTER.to_owned() + + utils::get_harmony_replace_component_dependency_define(self).as_str() + + format!( + r#" +@Reusable +@Component +export default struct TARO_TEMPLATES_{name} {{ + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () {{ - this.dynamicCenter = new DynamicCenter() + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + }} + + aboutToReuse(params: TaroAny): void {{ + this.node = params.node this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) }} {content}}} -export default TARO_TEMPLATES_{name} "#, - name = tmpl_name, - content = tmpl_main_contents - ).as_str(); - - self.templates.insert(tmpl_name, format!("`{}`", tmpl_contents)); - - // 数据清理 - self.node_stack.clear(); - self.node_name.clear(); - self.node_name_vec.clear(); - self.component_set.clear(); - self.is_compile_mode = false; - self.get_node_name = Box::new(utils::named_iter(String::from("node"))); - } else { - el.visit_mut_children_with(self) - } - } - - // 将生成的模板字符串以变量的形式插入在文件最上面,等待后续编译抽离 - fn visit_mut_module_items (&mut self, body_stmts: &mut Vec) { - body_stmts.visit_mut_children_with(self); - - let mut keys: Vec<&String> = self.templates.keys().collect(); - keys.sort(); - let stmts_being_inserted = keys.into_iter() - .map(|key| { - let value = self.templates.get(key).unwrap(); - ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl { - span, - kind: VarDeclKind::Const, - declare: false, - decls: vec![VarDeclarator { - span, - name: Pat::Ident(Ident::new(format!("TARO_TEMPLATES_{}", key).as_str().into(), span).into()), - init: Some(Box::new(Expr::Lit(Lit::Str(Str { - span, - value: value.as_str().into(), - raw: Some(Atom::new(value.as_str())) - })))), - definite: false, - }], - })))) - }); - ecma::utils::prepend_stmts(body_stmts, stmts_being_inserted); - } + name = tmpl_name, + content = tmpl_main_contents + ).as_str() + + utils::get_harmony_component_style(self).as_str(); + + self.templates.insert(tmpl_name, format!("`{}`", tmpl_contents)); + + // println!("templates: {:?}", self.templates); + + // 数据清理 + self.node_stack.clear(); + self.node_name.clear(); + self.node_name_vec.clear(); + self.component_set.clear(); + self.is_compile_mode = false; + self.get_node_name = Box::new(utils::named_iter(String::from("node"))); + } else { + el.visit_mut_children_with(self) + } + } + + // 将生成的模板字符串以变量的形式插入在文件最上面,等待后续编译抽离 + fn visit_mut_module_items(&mut self, body_stmts: &mut Vec) { + body_stmts.visit_mut_children_with(self); + + let mut keys: Vec<&String> = self.templates.keys().collect(); + keys.sort(); + let stmts_being_inserted = keys.into_iter().map(|key| { + let value = self.templates.get(key).unwrap(); + ModuleItem::Stmt( + Stmt::Decl( + Decl::Var( + Box::new(VarDecl { + span, + kind: VarDeclKind::Const, + declare: false, + decls: vec![VarDeclarator { + span, + name: Pat::Ident( + Ident::new(format!("TARO_TEMPLATES_{}", key).as_str().into(), span).into() + ), + init: Some( + Box::new( + Expr::Lit( + Lit::Str(Str { + span, + value: value.as_str().into(), + raw: Some(Atom::new(value.as_str())), + }) + ) + ) + ), + definite: false, + }], + }) + ) + ) + ) + }); + ecma::utils::prepend_stmts(body_stmts, stmts_being_inserted); + } } diff --git a/crates/swc_plugin_compile_mode/src/utils/constants.rs b/crates/swc_plugin_compile_mode/src/utils/constants.rs index e32b07921c86..1cca70647842 100644 --- a/crates/swc_plugin_compile_mode/src/utils/constants.rs +++ b/crates/swc_plugin_compile_mode/src/utils/constants.rs @@ -17,191 +17,160 @@ pub const TEXT_TAG: &str = "text"; pub const IMAGE_TAG: &str = "image"; pub const SCRIPT_TAG: &str = "script"; -pub const HARMONY_IMPORTER: &str = "import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' +pub const STYLE_ATTR: &str = "style"; +pub const DIRECTION_ATTR: &str = "harmonyDirection"; + +pub const HARMONY_IMPORTER: &str = "import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + "; -pub const HARMONY_FLEX_STYLE_BIND: &str = r#"@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) +pub const HARMONY_TEXT_HELPER_FUNCITON: &str = r#" + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } } -"#; -pub const HARMONY_TEXT_STYLE_BIND: &str = r#"@Extend(Text) -function attrsText ({ - id, - width, - height, - zIndex, - opacity, - margin, - padding, - decoration, - lineHeight, - letterSpacing, - maxLines, - fontColor, - fontSize, - fontWeight, - fontFamily, - textOverflow, - constraintSize, - border, - borderRadius, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - rotate, - scale, - translate, - transform, - textAlign, - }) { - .id(id) - .key(id) - .constraintSize(constraintSize) - .zIndex(zIndex) - .opacity(opacity) - .margin(margin) - .padding(padding) - .decoration(decoration) - .lineHeight(lineHeight) - .letterSpacing(letterSpacing) - .maxLines(maxLines) - .fontColor(fontColor) - .fontSize(fontSize) - .fontWeight(fontWeight) - .fontFamily(fontFamily) - .textOverflow(textOverflow) - .border(border) - .borderRadius(borderRadius) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .textAlign(textAlign) - .width(width) - .height(height) + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) } -function getTextAttributes (node: TaroViewElement) { - const attrs = { - ...getNormalAttributes(node), - ...getFontAttributes(node) +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') } +} - transformW3CToHarmonyInStyle(node._st, attrs) +"#; - return attrs +pub const HARMONY_TEXT_BUILDER: &str = r#"@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } + } + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] + })) + } } -"#; -pub const HARMONY_IMAGE_STYLE_BIND: &str = r#"@Extend(Image) -function attrsImage ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) - .objectFit(ImageFit.Contain) + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } } "#; + diff --git a/crates/swc_plugin_compile_mode/src/utils/harmony/components.rs b/crates/swc_plugin_compile_mode/src/utils/harmony/components.rs index bfedd4282f76..90da51b4b0d4 100644 --- a/crates/swc_plugin_compile_mode/src/utils/harmony/components.rs +++ b/crates/swc_plugin_compile_mode/src/utils/harmony/components.rs @@ -1,61 +1,95 @@ -pub fn get_component_attr_str (node_name: &str, tag_name: &str) -> String { - if tag_name == "text" { - format!(".attrsText(getTextAttributes(this.{}))", node_name) - } else if tag_name == "image" { - format!(".attrsImage(getNormalAttributes(this.{}))", node_name) - } else { - format!(".attrs(getNormalAttributes(this.{}))", node_name) - } +use crate::transform_harmony::EtsDirection; + +pub fn get_component_attr_str(node_name: &str, tag_name: &str) -> String { + if tag_name == "text" { + format!( + ".attributeModifier(commonStyleModify.setNode({} as TaroElement))\n.textSpecialFontStyle(getFontAttributes({} as TaroElement))", + node_name, + node_name + ) + } else if tag_name == "row" { + format!(".attributeModifier(rowModify.setNode({} as TaroElement))", node_name) + } else if tag_name == "column" { + format!(".attributeModifier(columnModify.setNode({} as TaroElement))", node_name) + } else { + format!(".attributeModifier(commonStyleModify.setNode({} as TaroElement))", node_name) + } } -pub fn get_component_style_str (node_name: &str, tag_name: &str) -> String { - format!( -r#"{} -.onVisibleAreaChange(getNodeThresholds(this.{node_id}) || [0.0, 1.0], getComponentEventCallback(this.{node_id}, VISIBLE_CHANGE_EVENT_NAME)) -.onAreaChange(getComponentEventCallback(this.{node_id}, AREA_CHANGE_EVENT_NAME, ({{ eventResult }}) => {{ - const [_, areaResult] = eventResult - this.nodeInfoMap[this.{node_id}._nid].areaInfo = areaResult +pub fn get_component_style_str(node_name: &str, tag_name: &str) -> String { + format!( + r#"{} +.onVisibleAreaChange(getNodeThresholds({node_id} as TaroElement) || [0.0, 1.0], getComponentEventCallback({node_id} as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) +.onAreaChange(getComponentEventCallback({node_id} as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => {{ + ({node_id} as TaroElement)._nodeInfo.areaInfo = res[1] }}))"#, - get_component_attr_str(node_name, tag_name), - node_id = node_name, - ) + get_component_attr_str(node_name, tag_name), + node_id = node_name + ) } +pub fn get_view_component_str(node_name: &str, child_content: &str, direction: EtsDirection) -> String { + let component_name; + let mut component_param = "".to_string(); + let component_children = match child_content { + "" => "".to_string(), + _ => format!("\n{}", child_content), + }; -pub fn get_view_component_str (node_name: &str, child_content: &str) -> String { - format!("Flex(FlexManager.flexOptions(this.{node_id})) {{{children}}}\n{style}", - node_id = node_name, - children = match child_content { - "" => "".to_string(), - _ => format!("\n{}", child_content) - }, - style = get_component_style_str(node_name, "view") - ) + match direction { + EtsDirection::Row => { + component_name = "Row"; + } + EtsDirection::Column => { + component_name = "Column"; + } + EtsDirection::Flex => { + component_name = "Flex"; + component_param = format!("FlexManager.flexOptions({} as TaroElement)", node_name); + } + } + let style = get_component_style_str(node_name, component_name.to_lowercase().as_str()); + + format!( + "{name}({param}) {{{children}}}\n{style}", + name = component_name, + param = component_param, + children = component_children, + style = style + ) } -pub fn get_image_component_str (node_name: &str) -> String { - format!("Image(this.{node_id}.getAttribute('src'))\n{style}", - node_id = node_name, - style = get_component_style_str(node_name, "image") - ) +pub fn get_image_component_str(node_name: &str) -> String { + format!( + "Image(({node_id} as TaroElement).getAttribute('src'))\n.objectFit(getImageMode(({node_id} as TaroElement).getAttribute('mode')))\n{style}\n.borderRadius({{ + topLeft: ({node_id} as TaroElement)._st.hmStyle.borderTopLeftRadius, + topRight: ({node_id} as TaroElement)._st.hmStyle.borderTopRightRadius, + bottomLeft: ({node_id} as TaroElement)._st.hmStyle.borderBottomLeftRadius, + bottomRight: ({node_id} as TaroElement)._st.hmStyle.borderBottomRightRadius +}})", + node_id = node_name, + style = get_component_style_str(node_name, "image") + ) } -pub fn get_text_component_str (node_name: &str) -> String { - format!("Text(this.{node_id}.textContent)\n{style}", - node_id = node_name, - style = get_component_style_str(node_name, "text") - ) +pub fn get_text_component_str(node_name: &str) -> String { + format!("createText({node_id} as TaroTextElement)", node_id = node_name) } +pub fn create_component_event(event_name: &str, node_name: &str) -> String { + let process_event_trigger_name = |name: &str| -> String { + if name == "touch" { String::from("TOUCH_EVENT_MAP.get(e.type)") } else { format!("'{}'", name) } + }; -pub fn create_component_event (event_name: &str, node_name: &str) -> String { - let process_event_trigger_name = |name: &str| -> String { - if name == "touch" { - String::from("TOUCH_EVENT_MAP[e.type]") - } else { - format!("'{}'", name) - } - }; - - format!("\n.{}(e => eventHandler(e, {}, this.{}))", event_name, process_event_trigger_name(&event_name.get(2..).unwrap().to_lowercase()), node_name) + format!( + "\n.{}(e => {{ eventHandler(e, {}, {} as TaroElement) }} )", + event_name, + process_event_trigger_name( + &event_name + .get(2..) + .unwrap() + .to_lowercase() + ), + node_name + ) } diff --git a/crates/swc_plugin_compile_mode/src/utils/mod.rs b/crates/swc_plugin_compile_mode/src/utils/mod.rs index 14ae7a6e32f4..b09aaf77c21b 100644 --- a/crates/swc_plugin_compile_mode/src/utils/mod.rs +++ b/crates/swc_plugin_compile_mode/src/utils/mod.rs @@ -12,15 +12,16 @@ use swc_core::{ }, }; use std::collections::HashMap; +use regex::Regex; -use self::constants::*; +use self::{ constants::*, harmony::components::get_text_component_str }; +use crate::{ transform_harmony::TransformVisitor, ComponentReplace }; use crate::PluginConfig; -use crate::transform_harmony::TransformVisitor; -pub mod harmony; pub mod constants; +pub mod harmony; -pub fn named_iter (str: String) -> impl FnMut() -> String { +pub fn named_iter(str: String) -> impl FnMut() -> String { let mut count = -1; return move || { count += 1; @@ -28,7 +29,7 @@ pub fn named_iter (str: String) -> impl FnMut() -> String { }; } -pub fn jsx_text_to_string (atom: &Atom) -> String { +pub fn jsx_text_to_string(atom: &Atom) -> String { let content = atom.replace("\t", " "); let res = content @@ -37,18 +38,10 @@ pub fn jsx_text_to_string (atom: &Atom) -> String { .identify_last() .fold(String::new(), |mut acc, (is_last, (index, line))| { // 首行不 trim 头 - let line = if index == 0 { - line - } else { - line.trim_start() - }; + let line = if index == 0 { line } else { line.trim_start() }; // 尾行不 trim 尾 - let line = if is_last { - line - } else { - line.trim_end() - }; + let line = if is_last { line } else { line.trim_end() }; if !acc.is_empty() && !line.is_empty() { acc.push(' '); @@ -61,10 +54,9 @@ pub fn jsx_text_to_string (atom: &Atom) -> String { } // 将驼峰写法转换为 kebab-case,即 aBcD -> a-bc-d -pub fn to_kebab_case (val: &str) -> String { +pub fn to_kebab_case(val: &str) -> String { let mut res = String::new(); - val - .chars() + val.chars() .enumerate() .for_each(|(idx, c)| { if idx != 0 && c.is_uppercase() { @@ -75,51 +67,43 @@ pub fn to_kebab_case (val: &str) -> String { res } -pub fn convert_jsx_attr_key (jsx_key: &str, adapter: &HashMap) -> String { +pub fn convert_jsx_attr_key(jsx_key: &str, adapter: &HashMap) -> String { if jsx_key == "className" { return String::from("class"); - } else if - jsx_key == COMPILE_IF || - jsx_key == COMPILE_ELSE || - jsx_key == COMPILE_FOR || - jsx_key == COMPILE_FOR_KEY - { + } else if jsx_key == COMPILE_IF || jsx_key == COMPILE_ELSE || jsx_key == COMPILE_FOR || jsx_key == COMPILE_FOR_KEY { let expr = match jsx_key { COMPILE_IF => "if", COMPILE_ELSE => "else", COMPILE_FOR => "for", COMPILE_FOR_KEY => "key", - _ => "" + _ => "", }; let adapter = adapter.get(expr).expect(&format!("[compile mode] 模板 {} 语法未配置", expr)); - return adapter.clone() + return adapter.clone(); } to_kebab_case(jsx_key) } -pub fn check_is_event_attr (val: &str) -> bool { - val.starts_with("on") && val.chars().nth(2).is_some_and(|x| x.is_uppercase()) +pub fn check_is_event_attr(val: &str) -> bool { + val.starts_with("on") && + val + .chars() + .nth(2) + .is_some_and(|x| x.is_uppercase()) } -pub fn identify_jsx_event_key (val: &str, platform: &str) -> Option { +pub fn identify_jsx_event_key(val: &str, platform: &str) -> Option { if check_is_event_attr(val) { - let event_name = val.get(2..).unwrap().to_lowercase(); - let event_name = if event_name == "click" { - "tap" - } else { - &event_name - }; + let event_name = val + .get(2..) + .unwrap() + .to_lowercase(); + let event_name = if event_name == "click" { "tap" } else { &event_name }; let event_binding_name = match platform { "ALIPAY" => { - if event_name == "tap" { - String::from("onTap") - } else { - String::from(val) - } - }, - _ => { - format!("bind{}", event_name) + if event_name == "tap" { String::from("onTap") } else { String::from(val) } } + _ => { format!("bind{}", event_name) } }; Some(event_binding_name) } else { @@ -137,64 +121,68 @@ pub fn is_inner_component (el: &JSXElement, config: &PluginConfig) -> bool { false } -pub fn is_static_jsx (el: &Box) -> bool { +pub fn is_static_jsx(el: &Box) -> bool { if el.opening.attrs.len() > 0 { - return false + return false; } for child in &el.children { if let JSXElementChild::JSXText(_) = child { } else { - return false + return false; } } true } -pub fn create_self_closing_jsx_element_expr (name: JSXElementName, attrs: Option>) -> Expr { - Expr::JSXElement(Box::new(JSXElement { - span, - opening: JSXOpeningElement { - name, +pub fn create_self_closing_jsx_element_expr(name: JSXElementName, attrs: Option>) -> Expr { + Expr::JSXElement( + Box::new(JSXElement { span, - attrs: attrs.unwrap_or(vec![]), - self_closing: true, - type_args: None - }, - children: vec![], - closing: None - })) + opening: JSXOpeningElement { + name, + span, + attrs: attrs.unwrap_or(vec![]), + self_closing: true, + type_args: None, + }, + children: vec![], + closing: None, + }) + ) } -pub fn create_jsx_expr_attr (name: &str, expr: Box) -> JSXAttrOrSpread { +pub fn create_jsx_expr_attr(name: &str, expr: Box) -> JSXAttrOrSpread { JSXAttrOrSpread::JSXAttr(JSXAttr { span, name: JSXAttrName::Ident(Ident::new(name.into(), span)), - value: Some(JSXAttrValue::JSXExprContainer(JSXExprContainer { - span, - expr: JSXExpr::Expr(expr) - })) + value: Some( + JSXAttrValue::JSXExprContainer(JSXExprContainer { + span, + expr: JSXExpr::Expr(expr), + }) + ), }) } -pub fn create_jsx_bool_attr (name: &str) -> JSXAttrOrSpread { +pub fn create_jsx_bool_attr(name: &str) -> JSXAttrOrSpread { JSXAttrOrSpread::JSXAttr(JSXAttr { span, name: JSXAttrName::Ident(Ident::new(name.into(), span)), - value: None + value: None, }) } -pub fn create_jsx_lit_attr (name: &str, lit: Lit) -> JSXAttrOrSpread { +pub fn create_jsx_lit_attr(name: &str, lit: Lit) -> JSXAttrOrSpread { JSXAttrOrSpread::JSXAttr(JSXAttr { span, name: JSXAttrName::Ident(Ident::new(name.into(), span)), - value: Some(JSXAttrValue::Lit(lit)) + value: Some(JSXAttrValue::Lit(lit)), }) } -pub fn create_jsx_dynamic_id (el: &mut JSXElement, visitor: &mut TransformVisitor) -> String { +pub fn create_jsx_dynamic_id(el: &mut JSXElement, visitor: &mut TransformVisitor) -> String { let node_name = (visitor.get_node_name)(); visitor.node_name_vec.push(node_name.clone()); @@ -202,8 +190,7 @@ pub fn create_jsx_dynamic_id (el: &mut JSXElement, visitor: &mut TransformVisito node_name } -pub fn add_spaces_to_lines(input: &str) -> String { - let count = 2; +pub fn add_spaces_to_lines_with_count(input: &str, count: usize) -> String { let mut result = String::new(); for line in input.lines() { @@ -214,29 +201,54 @@ pub fn add_spaces_to_lines(input: &str) -> String { result } -pub fn get_harmony_component_style (visitor: &mut TransformVisitor) -> String { +pub fn add_spaces_to_lines(input: &str) -> String { + let count = 2; + + add_spaces_to_lines_with_count(input, count) +} + +pub fn get_harmony_replace_component_dependency_define(visitor: &mut TransformVisitor) -> String { + let component_set = &visitor.component_set; + let component_replace = &visitor.config.component_replace; + let mut harmony_component_style = String::new(); + + component_replace.iter().for_each(|(k, v)| { + if component_set.contains(k) { + let ComponentReplace { dependency_define, .. } = v; + + harmony_component_style.push_str(dependency_define); + harmony_component_style.push_str("\n"); + } + }); + + harmony_component_style +} + +pub fn get_harmony_component_style(visitor: &mut TransformVisitor) -> String { let component_set = &visitor.component_set; + let component_replace = &visitor.config.component_replace; let mut harmony_component_style = String::new(); let mut build_component = |component_tag: &str, component_style: &str| { - if component_set.contains(component_tag) { + if component_set.contains(component_tag) && !component_replace.contains_key(component_tag) { harmony_component_style.push_str(component_style); } }; - build_component(VIEW_TAG, HARMONY_FLEX_STYLE_BIND); - build_component(IMAGE_TAG, HARMONY_IMAGE_STYLE_BIND); - build_component(TEXT_TAG, HARMONY_TEXT_STYLE_BIND); + // build_component(IMAGE_TAG, HARMONY_IMAGE_STYLE_BIND); + // build_component(TEXT_TAG, HARMONY_TEXT_STYLE_BIND); + build_component(TEXT_TAG, HARMONY_TEXT_BUILDER); + build_component(TEXT_TAG, HARMONY_TEXT_HELPER_FUNCITON); harmony_component_style } -pub fn check_jsx_element_has_compile_ignore (el: &JSXElement) -> bool { +pub fn check_jsx_element_has_compile_ignore(el: &JSXElement) -> bool { for attr in &el.opening.attrs { if let JSXAttrOrSpread::JSXAttr(JSXAttr { name, .. }) = attr { if let JSXAttrName::Ident(Ident { sym, .. }) = name { if sym == COMPILE_IGNORE { - return true + return true; } } } @@ -247,44 +259,44 @@ pub fn check_jsx_element_has_compile_ignore (el: &JSXElement) -> bool { /** * identify: `xx.map(function () {})` or `xx.map(() => {})` */ -pub fn is_call_expr_of_loop (callee_expr: &Box, args: &Vec) -> bool { - if let Expr::Member(MemberExpr { prop: MemberProp::Ident(Ident { sym, ..}), .. }) = &**callee_expr { +pub fn is_call_expr_of_loop (callee_expr: &mut Box, args: &mut Vec) -> bool { + if let Expr::Member(MemberExpr { prop: MemberProp::Ident(Ident { sym, .. }), .. }) = &mut **callee_expr { if sym == "map" { - if let Some(ExprOrSpread { expr, .. }) = args.get(0) { - return expr.is_arrow() || expr.is_fn_expr() + if let Some(ExprOrSpread { expr, .. }) = args.get_mut(0) { + return expr.is_arrow() || expr.is_fn_expr(); } } } - return false + return false; } -pub fn is_render_fn (callee_expr: &mut Box) -> bool { - fn is_starts_with_render (name: &str) -> bool { +pub fn is_render_fn(callee_expr: &mut Box) -> bool { + fn is_starts_with_render(name: &str) -> bool { name.starts_with("render") } match &**callee_expr { - Expr::Member(MemberExpr { prop: MemberProp::Ident(Ident { sym: name, .. }), .. }) => { - is_starts_with_render(name) - }, - Expr::Ident(Ident { sym: name, .. }) => { - is_starts_with_render(name) - }, - _ => false + Expr::Member(MemberExpr { prop: MemberProp::Ident(Ident { sym: name, .. }), .. }) => + is_starts_with_render(name), + Expr::Ident(Ident { sym: name, .. }) => is_starts_with_render(name), + _ => false, } } -pub fn extract_jsx_loop <'a> (callee_expr: &mut Box, args: &'a mut Vec) -> Option<&'a mut Box> { +pub fn extract_jsx_loop<'a>( + callee_expr: &mut Box, + args: &'a mut Vec +) -> Option<&'a mut Box> { if is_call_expr_of_loop(callee_expr, args) { if let Some(ExprOrSpread { expr, .. }) = args.get_mut(0) { - fn update_return_el (return_value: &mut Box) -> Option<&mut Box> { + fn update_return_el(return_value: &mut Box) -> Option<&mut Box> { if let Expr::Paren(ParenExpr { expr, .. }) = &mut **return_value { - *return_value = expr.take() + *return_value = expr.take(); } if return_value.is_jsx_element() { let el = return_value.as_mut_jsx_element().unwrap(); el.opening.attrs.push(create_jsx_bool_attr(COMPILE_FOR)); el.opening.attrs.push(create_jsx_lit_attr(COMPILE_FOR_KEY, Lit::Str(quote_str!("sid")))); - return Some(el) + return Some(el); } else if return_value.is_jsx_fragment() { let el = return_value.as_mut_jsx_fragment().unwrap(); let children = el.children.take(); @@ -315,40 +327,89 @@ pub fn extract_jsx_loop <'a> (callee_expr: &mut Box, args: &'a mut Vec { + } + Expr::Arrow(ArrowExpr { body, .. }) => match &mut **body { BlockStmtOrExpr::BlockStmt(BlockStmt { stmts, .. }) => { if let Some(Stmt::Return(ReturnStmt { arg: Some(return_value), .. })) = stmts.last_mut() { return update_return_el(return_value); } - }, + } BlockStmtOrExpr::Expr(return_value) => { return update_return_el(return_value); } } - }, - _ => () + _ => (), } } } None } -pub fn check_jsx_element_children_exist_loop (el: &mut JSXElement) -> bool { - for child in el.children.iter_mut() { - if let JSXElementChild::JSXExprContainer(JSXExprContainer { expr: JSXExpr::Expr(expr), .. }) = child { - if let Expr::Call(CallExpr { callee: Callee::Expr(callee_expr), args, .. }) = &mut **expr { - if is_call_expr_of_loop(callee_expr, args) { - return true +pub fn get_valid_nodes(children: &Vec) -> usize { + let re = Regex::new(r"^\s*$").unwrap(); + let filtered_children: Vec<&JSXElementChild> = children + .iter() + .filter(|&item| { + match item { + JSXElementChild::JSXText(JSXText { value, .. }) => { + // 用正则判断value是否只含在\n和空格,如果时,返回false + !re.is_match(value) } + _ => true, } + }) + .collect(); + filtered_children.len() +} + +pub fn check_jsx_element_children_exist_loop(el: &mut JSXElement) -> bool { + for child in el.children.iter_mut() { + if check_jsx_element_child_is_loop(child) { + return true; } } false } +pub fn check_jsx_element_child_is_loop(child: &mut JSXElementChild) -> bool { + if let JSXElementChild::JSXExprContainer(JSXExprContainer { expr: JSXExpr::Expr(expr), .. }) = child { + if let Expr::Call(CallExpr { callee: Callee::Expr(callee_expr), args, .. }) = &mut **expr { + if is_call_expr_of_loop(callee_expr, args) { + return true; + } + } + } + false +} + +pub fn create_original_node_renderer_foreach(visitor: &mut TransformVisitor) -> String { + add_spaces_to_lines( + format!("createLazyChildren({})", visitor.get_dynmaic_node_name(visitor.get_current_node_path())).as_str() + ) +} + +pub fn create_original_node_renderer(visitor: &mut TransformVisitor) -> String { + add_spaces_to_lines( + format!( + "createChildItem({} as TaroElement, createLazyChildren)", + visitor.get_dynmaic_node_name(visitor.get_current_node_path()) + ).as_str() + ) +} + +pub fn create_normal_text_template(visitor: &mut TransformVisitor, disable_this: bool) -> String { + let node_path = visitor.get_current_node_path(); + + let node_name = if disable_this { String::from("item") } else { visitor.get_dynmaic_node_name(node_path) }; + + let code = add_spaces_to_lines(get_text_component_str(&node_name).as_str()); + + visitor.component_set.insert(TEXT_TAG.to_string()); + code +} + pub fn is_static_jsx_element_child (jsx_element: &JSXElementChild) -> bool { struct Visitor { has_jsx_expr: bool @@ -368,10 +429,6 @@ pub fn is_static_jsx_element_child (jsx_element: &JSXElementChild) -> bool { return !visitor.has_jsx_expr; } -pub fn create_original_node_renderer (visitor: &mut TransformVisitor) -> String { - add_spaces_to_lines(format!("ForEach(this.{}.childNodes, item => {{\n createNode(item)\n}}, item => item._nid)", visitor.node_name.last().unwrap().clone()).as_str()) -} - pub fn gen_template (val: &str) -> String { format!("{{{{{}}}}}", val) } @@ -411,7 +468,7 @@ pub fn as_xscript_expr_string (member: &MemberExpr, xs_module_names: &Vec { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[0]._nid].areaInfo = areaResult + Column() { + Column() {} + .attributeModifier(columnModify.setNode(this.node0.childNodes[0] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[0] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[0] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[0] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[0] as TaroElement)._nodeInfo.areaInfo = res[1] })) - .onClick(e => eventHandler(e, 'click', this.node0.childNodes[0])) - Flex(FlexManager.flexOptions(this.node0.childNodes[1])) {} - .attrs(getNormalAttributes(this.node0.childNodes[1])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[1]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[1], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[1], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[1]._nid].areaInfo = areaResult + .onClick(e => { eventHandler(e, 'click', this.node0.childNodes[0] as TaroElement) } ) + Column() {} + .attributeModifier(columnModify.setNode(this.node0.childNodes[1] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[1] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[1] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[1] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[1] as TaroElement)._nodeInfo.areaInfo = res[1] })) - .onClick(e => eventHandler(e, 'click', this.node0.childNodes[1])) - Flex(FlexManager.flexOptions(this.node1)) {} - .attrs(getNormalAttributes(this.node1)) - .onVisibleAreaChange(getNodeThresholds(this.node1) || [0.0, 1.0], getComponentEventCallback(this.node1, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node1, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node1._nid].areaInfo = areaResult + .onClick(e => { eventHandler(e, 'click', this.node0.childNodes[1] as TaroElement) } ) + Column() {} + .attributeModifier(columnModify.setNode(this.node1 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node1 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node1 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node1 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node1 as TaroElement)._nodeInfo.areaInfo = res[1] })) - .onClick(e => eventHandler(e, 'click', this.node1)) - Flex(FlexManager.flexOptions(this.node2)) {} - .attrs(getNormalAttributes(this.node2)) - .onVisibleAreaChange(getNodeThresholds(this.node2) || [0.0, 1.0], getComponentEventCallback(this.node2, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node2, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node2._nid].areaInfo = areaResult + .onClick(e => { eventHandler(e, 'click', this.node1 as TaroElement) } ) + Column() {} + .attributeModifier(columnModify.setNode(this.node2 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node2 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node2 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node2 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node2 as TaroElement)._nodeInfo.areaInfo = res[1] })) - Image(this.node0.childNodes[4].getAttribute('src')) - .attrsImage(getNormalAttributes(this.node0.childNodes[4])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[4]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[4], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[4], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[4]._nid].areaInfo = areaResult + Image((this.node0.childNodes[4] as TaroElement).getAttribute('src')) + .objectFit(getImageMode((this.node0.childNodes[4] as TaroElement).getAttribute('mode'))) + .attributeModifier(commonStyleModify.setNode(this.node0.childNodes[4] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[4] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[4] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[4] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[4] as TaroElement)._nodeInfo.areaInfo = res[1] })) - .onComplete(e => eventHandler(e, 'complete', this.node0.childNodes[4])) - Flex(FlexManager.flexOptions(this.node0.childNodes[5])) {} - .attrs(getNormalAttributes(this.node0.childNodes[5])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[5]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[5], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[5], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[5]._nid].areaInfo = areaResult + .borderRadius({ + topLeft: (this.node0.childNodes[4] as TaroElement)._st.hmStyle.borderTopLeftRadius, + topRight: (this.node0.childNodes[4] as TaroElement)._st.hmStyle.borderTopRightRadius, + bottomLeft: (this.node0.childNodes[4] as TaroElement)._st.hmStyle.borderBottomLeftRadius, + bottomRight: (this.node0.childNodes[4] as TaroElement)._st.hmStyle.borderBottomRightRadius + }) + .onComplete(e => { eventHandler(e, 'complete', this.node0.childNodes[4] as TaroElement) } ) + Column() {} + .attributeModifier(columnModify.setNode(this.node0.childNodes[5] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[5] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[5] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[5] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[5] as TaroElement)._nodeInfo.areaInfo = res[1] })) - .onClick(e => eventHandler(e, 'click', this.node0.childNodes[5])) - .onTouch(e => eventHandler(e, TOUCH_EVENT_MAP[e.type], this.node0.childNodes[5])) + .onClick(e => { eventHandler(e, 'click', this.node0.childNodes[5] as TaroElement) } ) + .onTouch(e => { eventHandler(e, TOUCH_EVENT_MAP.get(e.type), this.node0.childNodes[5] as TaroElement) } ) } - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t0 `; function Index() { return diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/attributes.rs/should_turn_dynamic_attrs.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/attributes.rs/should_turn_dynamic_attrs.js index 70710c618853..e5ab3b9ceb4d 100644 --- a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/attributes.rs/should_turn_dynamic_attrs.js +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/attributes.rs/should_turn_dynamic_attrs.js @@ -1,196 +1,138 @@ -const TARO_TEMPLATES_f0t0 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) -} -@Extend(Image) -function attrsImage ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) - .objectFit(ImageFit.Contain) -} +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable @Component -struct TARO_TEMPLATES_f0t0 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() - @State node1: TaroElement = new TaroIgnoreElement() - @State node2: TaroElement = new TaroIgnoreElement() - @State node3: TaroElement = new TaroIgnoreElement() - @State node4: TaroElement = new TaroIgnoreElement() - @State node5: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + @State node1: TaroElement = new TaroElement('Ignore') + @State node2: TaroElement = new TaroElement('Ignore') + @State node3: TaroElement = new TaroElement('Ignore') + @State node4: TaroElement = new TaroElement('Ignore') + @State node5: TaroElement = new TaroElement('Ignore') build() { - Flex(FlexManager.flexOptions(this.node0)) { - Flex(FlexManager.flexOptions(this.node1)) { - Flex(FlexManager.flexOptions(this.node2)) {} - .attrs(getNormalAttributes(this.node2)) - .onVisibleAreaChange(getNodeThresholds(this.node2) || [0.0, 1.0], getComponentEventCallback(this.node2, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node2, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node2._nid].areaInfo = areaResult + Column() { + Column() { + Column() {} + .attributeModifier(columnModify.setNode(this.node2 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node2 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node2 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node2 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node2 as TaroElement)._nodeInfo.areaInfo = res[1] })) - Flex(FlexManager.flexOptions(this.node1.childNodes[1])) {} - .attrs(getNormalAttributes(this.node1.childNodes[1])) - .onVisibleAreaChange(getNodeThresholds(this.node1.childNodes[1]) || [0.0, 1.0], getComponentEventCallback(this.node1.childNodes[1], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node1.childNodes[1], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node1.childNodes[1]._nid].areaInfo = areaResult + Column() {} + .attributeModifier(columnModify.setNode(this.node1.childNodes[1] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node1.childNodes[1] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node1.childNodes[1] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node1.childNodes[1] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node1.childNodes[1] as TaroElement)._nodeInfo.areaInfo = res[1] })) - Flex(FlexManager.flexOptions(this.node3)) { - Flex(FlexManager.flexOptions(this.node4)) {} - .attrs(getNormalAttributes(this.node4)) - .onVisibleAreaChange(getNodeThresholds(this.node4) || [0.0, 1.0], getComponentEventCallback(this.node4, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node4, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node4._nid].areaInfo = areaResult + Column() { + Column() {} + .attributeModifier(columnModify.setNode(this.node4 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node4 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node4 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node4 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node4 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - .attrs(getNormalAttributes(this.node3)) - .onVisibleAreaChange(getNodeThresholds(this.node3) || [0.0, 1.0], getComponentEventCallback(this.node3, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node3, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node3._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node3 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node3 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node3 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node3 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node3 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - .attrs(getNormalAttributes(this.node1)) - .onVisibleAreaChange(getNodeThresholds(this.node1) || [0.0, 1.0], getComponentEventCallback(this.node1, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node1, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node1._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node1 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node1 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node1 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node1 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node1 as TaroElement)._nodeInfo.areaInfo = res[1] })) - Image(this.node0.childNodes[1].getAttribute('src')) - .attrsImage(getNormalAttributes(this.node0.childNodes[1])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[1]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[1], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[1], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[1]._nid].areaInfo = areaResult + Image((this.node0.childNodes[1] as TaroElement).getAttribute('src')) + .objectFit(getImageMode((this.node0.childNodes[1] as TaroElement).getAttribute('mode'))) + .attributeModifier(commonStyleModify.setNode(this.node0.childNodes[1] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[1] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[1] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[1] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[1] as TaroElement)._nodeInfo.areaInfo = res[1] })) - Image(this.node5.getAttribute('src')) - .attrsImage(getNormalAttributes(this.node5)) - .onVisibleAreaChange(getNodeThresholds(this.node5) || [0.0, 1.0], getComponentEventCallback(this.node5, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node5, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node5._nid].areaInfo = areaResult + .borderRadius({ + topLeft: (this.node0.childNodes[1] as TaroElement)._st.hmStyle.borderTopLeftRadius, + topRight: (this.node0.childNodes[1] as TaroElement)._st.hmStyle.borderTopRightRadius, + bottomLeft: (this.node0.childNodes[1] as TaroElement)._st.hmStyle.borderBottomLeftRadius, + bottomRight: (this.node0.childNodes[1] as TaroElement)._st.hmStyle.borderBottomRightRadius + }) + Image((this.node5 as TaroElement).getAttribute('src')) + .objectFit(getImageMode((this.node5 as TaroElement).getAttribute('mode'))) + .attributeModifier(commonStyleModify.setNode(this.node5 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node5 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node5 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node5 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node5 as TaroElement)._nodeInfo.areaInfo = res[1] })) + .borderRadius({ + topLeft: (this.node5 as TaroElement)._st.hmStyle.borderTopLeftRadius, + topRight: (this.node5 as TaroElement)._st.hmStyle.borderTopRightRadius, + bottomLeft: (this.node5 as TaroElement)._st.hmStyle.borderBottomLeftRadius, + bottomRight: (this.node5 as TaroElement)._st.hmStyle.borderBottomRightRadius + }) } - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t0 `; function Index() { return diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_render_complex_children.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_render_complex_children.js new file mode 100644 index 000000000000..0033f2afc2e6 --- /dev/null +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_render_complex_children.js @@ -0,0 +1,146 @@ +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable +@Component +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() + + aboutToAppear () { + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + @State node1: TaroElement = new TaroElement('Ignore') + @State node2: TaroElement = new TaroElement('Ignore') + @State node3: TaroElement = new TaroElement('Ignore') + @State node4: TaroElement = new TaroElement('Ignore') + + build() { + Column() { + if ((this.node0.childNodes[0] as TaroElement)._attrs.compileIf) { + Column() { + Column() { + Image((this.node3 as TaroElement).getAttribute('src')) + .objectFit(getImageMode((this.node3 as TaroElement).getAttribute('mode'))) + .attributeModifier(commonStyleModify.setNode(this.node3 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node3 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node3 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node3 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node3 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + .borderRadius({ + topLeft: (this.node3 as TaroElement)._st.hmStyle.borderTopLeftRadius, + topRight: (this.node3 as TaroElement)._st.hmStyle.borderTopRightRadius, + bottomLeft: (this.node3 as TaroElement)._st.hmStyle.borderBottomLeftRadius, + bottomRight: (this.node3 as TaroElement)._st.hmStyle.borderBottomRightRadius + }) + if ((this.node2.childNodes[1] as TaroElement)._attrs.compileIf) { + Image((this.node4 as TaroElement).getAttribute('src')) + .objectFit(getImageMode((this.node4 as TaroElement).getAttribute('mode'))) + .attributeModifier(commonStyleModify.setNode(this.node4 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node4 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node4 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node4 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node4 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + .borderRadius({ + topLeft: (this.node4 as TaroElement)._st.hmStyle.borderTopLeftRadius, + topRight: (this.node4 as TaroElement)._st.hmStyle.borderTopRightRadius, + bottomLeft: (this.node4 as TaroElement)._st.hmStyle.borderBottomLeftRadius, + bottomRight: (this.node4 as TaroElement)._st.hmStyle.borderBottomRightRadius + }) + } + } + .attributeModifier(columnModify.setNode(this.node2 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node2 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node2 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node2 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node2 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } + .attributeModifier(columnModify.setNode(this.node1 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node1 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node1 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node1 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node1 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + .onClick(e => { eventHandler(e, 'click', this.node1 as TaroElement) } ) + } + } + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } +} +`; +function Index() { + return + + {showModal ? + + + + + + {haveIcon ? : } + + + + : } + + ; +} diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_render_react_component.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_render_react_component.js new file mode 100644 index 000000000000..cab75de4826f --- /dev/null +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_render_react_component.js @@ -0,0 +1,85 @@ +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable +@Component +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() + + aboutToAppear () { + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + @State node1: TaroElement = new TaroElement('Ignore') + + build() { + Column() { + createChildItem(this.node0.childNodes[0] as TaroElement, createLazyChildren) + createChildItem(this.node1 as TaroElement, createLazyChildren) + } + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } +} +`; +function Index() { + return + + + + {}} _dynamicID="node1"/> + + ; +} diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_support_fragment.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_support_fragment.js new file mode 100644 index 000000000000..ec02c3e59965 --- /dev/null +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_support_fragment.js @@ -0,0 +1,319 @@ +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable +@Component +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() + + aboutToAppear () { + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + @State node1: TaroElement = new TaroElement('Ignore') + @State node2: TaroElement = new TaroElement('Ignore') + @State node3: TaroElement = new TaroElement('Ignore') + @State node4: TaroElement = new TaroElement('Ignore') + @State node5: TaroElement = new TaroElement('Ignore') + @State node6: TaroElement = new TaroElement('Ignore') + + build() { + Column() { + createText(this.node0.childNodes[0] as TaroTextElement) + Column() { + createText(this.node1.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node1 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node1 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node1 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node1 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node1 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node0.childNodes[2].childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node0.childNodes[2] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[2] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[2] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[2] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[2] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + Column() { + createText(this.node2.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node2 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node2 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node2 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node2 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node2 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node0.childNodes[3].childNodes[1].childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node0.childNodes[3].childNodes[1] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[3].childNodes[1] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[3].childNodes[1] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[3].childNodes[1] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[3].childNodes[1] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } + .attributeModifier(columnModify.setNode(this.node0.childNodes[3] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[3] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[3] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[3] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[3] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node3.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node3 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node3 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node3 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node3 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node3 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node4.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node4 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node4 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node4 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node4 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node4 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node5.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node5 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node5 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node5 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node5 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node5 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node0.childNodes[7].childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node0.childNodes[7] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[7] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[7] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[7] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[7] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node0.childNodes[8].childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node0.childNodes[8] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[8] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[8] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[8] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[8] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node6.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node6 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node6 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node6 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node6 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node6 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } +} +@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } + } + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] + })) + } +} + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } +} + + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } +} + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) +} + +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') + } +} + +`; +function Index() { + return + + {content0} + + <> + + {content1} + + hello + + + + <> + + {content2} + + hello! + + + + + + {content3} + + + + {content4} + + <> + + {content5} + + + + <> + + hello!! + + + + hello!!! + + {content6} + + ; +} diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_support_render_fn.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_support_render_fn.js new file mode 100644 index 000000000000..c2717f2c4aca --- /dev/null +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/children.rs/should_support_render_fn.js @@ -0,0 +1,234 @@ +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable +@Component +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() + + aboutToAppear () { + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + @State node1: TaroElement = new TaroElement('Ignore') + @State node2: TaroElement = new TaroElement('Ignore') + @State node3: TaroElement = new TaroElement('Ignore') + @State node4: TaroElement = new TaroElement('Ignore') + + build() { + Column() { + Column() { + createChildItem(this.node1.childNodes[0] as TaroElement, createLazyChildren) + } + .attributeModifier(columnModify.setNode(this.node1 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node1 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node1 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node1 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node1 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createChildItem(this.node2.childNodes[0] as TaroElement, createLazyChildren) + createText(this.node2.childNodes[1] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node2 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node2 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node2 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node2 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node2 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createChildItem(this.node3.childNodes[0] as TaroElement, createLazyChildren) + } + .attributeModifier(columnModify.setNode(this.node3 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node3 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node3 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node3 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node3 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node4.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node4 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node4 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node4 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node4 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node4 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } +} +@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } + } + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] + })) + } +} + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } +} + + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } +} + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) +} + +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') + } +} + +`; +function Index() { + return + + {renderHeader()} + + + + {renderHeader()} + + {normalFunc()} + + + + {this.methods.renderFooter()} + + {normalFunc()} + + ; +} diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_and_expr.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_and_expr.js index c697916ebea1..190194a6efb4 100644 --- a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_and_expr.js +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_and_expr.js @@ -1,236 +1,225 @@ -const TARO_TEMPLATES_f0t0 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) -} -@Extend(Text) -function attrsText ({ - id, - width, - height, - zIndex, - opacity, - margin, - padding, - decoration, - lineHeight, - letterSpacing, - maxLines, - fontColor, - fontSize, - fontWeight, - fontFamily, - textOverflow, - constraintSize, - border, - borderRadius, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - rotate, - scale, - translate, - transform, - textAlign, - }) { - .id(id) - .key(id) - .constraintSize(constraintSize) - .zIndex(zIndex) - .opacity(opacity) - .margin(margin) - .padding(padding) - .decoration(decoration) - .lineHeight(lineHeight) - .letterSpacing(letterSpacing) - .maxLines(maxLines) - .fontColor(fontColor) - .fontSize(fontSize) - .fontWeight(fontWeight) - .fontFamily(fontFamily) - .textOverflow(textOverflow) - .border(border) - .borderRadius(borderRadius) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .textAlign(textAlign) - .width(width) - .height(height) -} +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' -function getTextAttributes (node: TaroViewElement) { - const attrs = { - ...getNormalAttributes(node), - ...getFontAttributes(node) - } +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' - transformW3CToHarmonyInStyle(node._st, attrs) - return attrs -} +@Reusable @Component -struct TARO_TEMPLATES_f0t0 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() - @State node1: TaroElement = new TaroIgnoreElement() - @State node2: TaroElement = new TaroIgnoreElement() - @State node3: TaroElement = new TaroIgnoreElement() - @State node4: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + @State node1: TaroElement = new TaroElement('Ignore') + @State node2: TaroElement = new TaroElement('Ignore') + @State node3: TaroElement = new TaroElement('Ignore') + @State node4: TaroElement = new TaroElement('Ignore') build() { - Flex(FlexManager.flexOptions(this.node0)) { - if (this.node0.childNodes[0]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node1)) { - Text(this.node1.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node1.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node1.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node1.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node1.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node1.childNodes[0]._nid].areaInfo = areaResult - })) + Column() { + if ((this.node0.childNodes[0] as TaroElement)._attrs.compileIf) { + Column() { + createText(this.node1.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node1)) - .onVisibleAreaChange(getNodeThresholds(this.node1) || [0.0, 1.0], getComponentEventCallback(this.node1, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node1, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node1._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node1 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node1 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node1 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node1 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node1 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - if (this.node0.childNodes[1]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node2)) { - Text(this.node2.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node2.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node2.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node2.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node2.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node2.childNodes[0]._nid].areaInfo = areaResult - })) + if ((this.node0.childNodes[1] as TaroElement)._attrs.compileIf) { + Column() { + createText(this.node2.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node2)) - .onVisibleAreaChange(getNodeThresholds(this.node2) || [0.0, 1.0], getComponentEventCallback(this.node2, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node2, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node2._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node2 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node2 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node2 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node2 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node2 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - if (this.node0.childNodes[2]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node3)) { - Text(this.node3.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node3.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node3.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node3.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node3.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node3.childNodes[0]._nid].areaInfo = areaResult - })) + if ((this.node0.childNodes[2] as TaroElement)._attrs.compileIf) { + Column() { + createText(this.node3.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node3)) - .onVisibleAreaChange(getNodeThresholds(this.node3) || [0.0, 1.0], getComponentEventCallback(this.node3, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node3, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node3._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node3 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node3 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node3 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node3 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node3 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - if (this.node0.childNodes[3]._attrs.compileIf) { - Text(this.node0.childNodes[3].textContent) - .attrsText(getTextAttributes(this.node0.childNodes[3])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[3]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[3], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[3], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[3]._nid].areaInfo = areaResult - })) + if ((this.node0.childNodes[3] as TaroElement)._attrs.compileIf) { + createText(this.node0.childNodes[3] as TaroTextElement) } else { - Text(this.node0.childNodes[3].textContent) - .attrsText(getTextAttributes(this.node0.childNodes[3])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[3]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[3], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[3], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[3]._nid].areaInfo = areaResult - })) + createText(this.node0.childNodes[3] as TaroTextElement) } - Flex(FlexManager.flexOptions(this.node4)) {} - .attrs(getNormalAttributes(this.node4)) - .onVisibleAreaChange(getNodeThresholds(this.node4) || [0.0, 1.0], getComponentEventCallback(this.node4, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node4, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node4._nid].areaInfo = areaResult + Column() {} + .attributeModifier(columnModify.setNode(this.node4 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node4 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node4 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node4 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node4 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } +} +@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } + } + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t0 + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } +} + + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } +} + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) +} + +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') + } +} + `; function Index() { return diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_complex_condition.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_complex_condition.js new file mode 100644 index 000000000000..c823f1cb1788 --- /dev/null +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_complex_condition.js @@ -0,0 +1,279 @@ +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable +@Component +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() + + aboutToAppear () { + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + @State node1: TaroElement = new TaroElement('Ignore') + @State node2: TaroElement = new TaroElement('Ignore') + @State node3: TaroElement = new TaroElement('Ignore') + @State node4: TaroElement = new TaroElement('Ignore') + @State node5: TaroElement = new TaroElement('Ignore') + + build() { + Column() { + if ((this.node0.childNodes[0] as TaroElement)._attrs.compileIf) { + Column() {} + .attributeModifier(columnModify.setNode(this.node0.childNodes[0] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[0] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[0] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[0] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[0] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + .onClick(e => { eventHandler(e, 'click', this.node0.childNodes[0] as TaroElement) } ) + } + Column() { + createText(this.node1.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node1 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node1 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node1 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node1 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node1 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node2.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node2 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node2 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node2 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node2 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node2 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + Column() { + createText(this.node3.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node3 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node3 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node3 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node3 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node3 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + if ((this.node0.childNodes[4] as TaroElement)._attrs.compileIf) { + Column() {} + .attributeModifier(columnModify.setNode(this.node4 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node4 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node4 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node4 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node4 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } else { + Column() {} + .attributeModifier(columnModify.setNode(this.node0.childNodes[4] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[4] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[4] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[4] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[4] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } + if ((this.node0.childNodes[5] as TaroElement)._attrs.compileIf) { + Column() { + if ((this.node5.childNodes[0] as TaroElement)._attrs.compileIf) { + Column() {} + .attributeModifier(columnModify.setNode(this.node5.childNodes[0] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node5.childNodes[0] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node5.childNodes[0] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node5.childNodes[0] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node5.childNodes[0] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } else { + Column() {} + .attributeModifier(columnModify.setNode(this.node5.childNodes[0] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node5.childNodes[0] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node5.childNodes[0] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node5.childNodes[0] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node5.childNodes[0] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } + } + .attributeModifier(columnModify.setNode(this.node5 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node5 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node5 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node5 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node5 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } else { + Column() {} + .attributeModifier(columnModify.setNode(this.node0.childNodes[5] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[5] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[5] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[5] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[5] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } + } + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } +} +@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } + } + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] + })) + } +} + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } +} + + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } +} + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) +} + +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') + } +} + +`; +function Index() { + return + + {condition1 ? condition2 && doSth()} compileIf={condition1}/> : } + + {condition1 && ident} + + {condition1 && obj.property} + + {condition1 && fn()} + + {condition1 ? : } + + {condition1 ? {condition2 ? : } : } + + ; +} diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_conditional_and_unkonw_component.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_conditional_and_unkonw_component.js new file mode 100644 index 000000000000..1476bd347f01 --- /dev/null +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_conditional_and_unkonw_component.js @@ -0,0 +1,197 @@ +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable +@Component +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() + + aboutToAppear () { + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + + build() { + Column() { + if ((this.node0.childNodes[0] as TaroElement)._attrs.compileIf) { + Column() { + createText(this.node0.childNodes[0].childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node0.childNodes[0] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[0] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[0] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[0] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[0] as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } else { + createChildItem(this.node0.childNodes[0] as TaroElement, createLazyChildren) + } + } + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } +} +@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } + } + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] + })) + } +} + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } +} + + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } +} + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) +} + +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') + } +} + +`; +function Index() { + return + + {condition ? hello : hello} + + ; +} diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_conditional_expr.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_conditional_expr.js index 831487069fcf..9fb76f032800 100644 --- a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_conditional_expr.js +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/condition.rs/should_support_conditional_expr.js @@ -1,392 +1,291 @@ -const TARO_TEMPLATES_f0t0 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) -} -@Extend(Text) -function attrsText ({ - id, - width, - height, - zIndex, - opacity, - margin, - padding, - decoration, - lineHeight, - letterSpacing, - maxLines, - fontColor, - fontSize, - fontWeight, - fontFamily, - textOverflow, - constraintSize, - border, - borderRadius, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - rotate, - scale, - translate, - transform, - textAlign, - }) { - .id(id) - .key(id) - .constraintSize(constraintSize) - .zIndex(zIndex) - .opacity(opacity) - .margin(margin) - .padding(padding) - .decoration(decoration) - .lineHeight(lineHeight) - .letterSpacing(letterSpacing) - .maxLines(maxLines) - .fontColor(fontColor) - .fontSize(fontSize) - .fontWeight(fontWeight) - .fontFamily(fontFamily) - .textOverflow(textOverflow) - .border(border) - .borderRadius(borderRadius) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .textAlign(textAlign) - .width(width) - .height(height) -} +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' -function getTextAttributes (node: TaroViewElement) { - const attrs = { - ...getNormalAttributes(node), - ...getFontAttributes(node) - } +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' - transformW3CToHarmonyInStyle(node._st, attrs) - return attrs -} +@Reusable @Component -struct TARO_TEMPLATES_f0t0 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() - @State node1: TaroElement = new TaroIgnoreElement() - @State node2: TaroElement = new TaroIgnoreElement() - @State node3: TaroElement = new TaroIgnoreElement() - @State node4: TaroElement = new TaroIgnoreElement() - @State node5: TaroElement = new TaroIgnoreElement() - @State node6: TaroElement = new TaroIgnoreElement() - @State node7: TaroElement = new TaroIgnoreElement() - @State node8: TaroElement = new TaroIgnoreElement() - @State node9: TaroElement = new TaroIgnoreElement() - @State node10: TaroElement = new TaroIgnoreElement() - @State node11: TaroElement = new TaroIgnoreElement() - @State node12: TaroElement = new TaroIgnoreElement() - @State node13: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + @State node1: TaroElement = new TaroElement('Ignore') + @State node2: TaroElement = new TaroElement('Ignore') + @State node3: TaroElement = new TaroElement('Ignore') + @State node4: TaroElement = new TaroElement('Ignore') + @State node5: TaroElement = new TaroElement('Ignore') + @State node6: TaroElement = new TaroElement('Ignore') + @State node7: TaroElement = new TaroElement('Ignore') + @State node8: TaroElement = new TaroElement('Ignore') + @State node9: TaroElement = new TaroElement('Ignore') + @State node10: TaroElement = new TaroElement('Ignore') + @State node11: TaroElement = new TaroElement('Ignore') build() { - Flex(FlexManager.flexOptions(this.node0)) { - if (this.node0.childNodes[0]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node1)) { - Text(this.node1.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node1.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node1.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node1.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node1.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node1.childNodes[0]._nid].areaInfo = areaResult - })) + Column() { + if ((this.node0.childNodes[0] as TaroElement)._attrs.compileIf) { + Column() { + createText(this.node1.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node1)) - .onVisibleAreaChange(getNodeThresholds(this.node1) || [0.0, 1.0], getComponentEventCallback(this.node1, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node1, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node1._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node1 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node1 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node1 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node1 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node1 as TaroElement)._nodeInfo.areaInfo = res[1] })) } else { - Text(this.node0.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node0.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[0]._nid].areaInfo = areaResult - })) + createText(this.node0.childNodes[0] as TaroTextElement) } - if (this.node0.childNodes[1]._attrs.compileIf) { - if (this.node0.childNodes[1]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node4)) { - Text(this.node4.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node4.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node4.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node4.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node4.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node4.childNodes[0]._nid].areaInfo = areaResult - })) + if ((this.node0.childNodes[1] as TaroElement)._attrs.compileIf) { + if ((this.node0.childNodes[1] as TaroElement)._attrs.compileIf) { + Column() { + createText(this.node4.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node4)) - .onVisibleAreaChange(getNodeThresholds(this.node4) || [0.0, 1.0], getComponentEventCallback(this.node4, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node4, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node4._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node4 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node4 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node4 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node4 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node4 as TaroElement)._nodeInfo.areaInfo = res[1] })) } else { - Text(this.node3.textContent) - .attrsText(getTextAttributes(this.node3)) - .onVisibleAreaChange(getNodeThresholds(this.node3) || [0.0, 1.0], getComponentEventCallback(this.node3, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node3, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node3._nid].areaInfo = areaResult - })) + createText(this.node3 as TaroTextElement) } } else { - Flex(FlexManager.flexOptions(this.node2)) { - Text(this.node2.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node2.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node2.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node2.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node2.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node2.childNodes[0]._nid].areaInfo = areaResult - })) + Column() { + createText(this.node2.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node2)) - .onVisibleAreaChange(getNodeThresholds(this.node2) || [0.0, 1.0], getComponentEventCallback(this.node2, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node2, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node2._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node2 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node2 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node2 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node2 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node2 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - if (this.node0.childNodes[2]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node7)) { - Text(this.node7.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node7.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node7.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node7.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node7.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node7.childNodes[0]._nid].areaInfo = areaResult - })) + if ((this.node0.childNodes[2] as TaroElement)._attrs.compileIf) { + Column() { + createText(this.node7.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node7)) - .onVisibleAreaChange(getNodeThresholds(this.node7) || [0.0, 1.0], getComponentEventCallback(this.node7, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node7, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node7._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node7 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node7 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node7 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node7 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node7 as TaroElement)._nodeInfo.areaInfo = res[1] })) } else { - if (this.node0.childNodes[2]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node6)) { - Text(this.node6.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node6.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node6.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node6.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node6.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node6.childNodes[0]._nid].areaInfo = areaResult - })) + if ((this.node0.childNodes[2] as TaroElement)._attrs.compileIf) { + Column() { + createText(this.node6.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node6)) - .onVisibleAreaChange(getNodeThresholds(this.node6) || [0.0, 1.0], getComponentEventCallback(this.node6, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node6, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node6._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node6 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node6 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node6 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node6 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node6 as TaroElement)._nodeInfo.areaInfo = res[1] })) } else { - Text(this.node5.textContent) - .attrsText(getTextAttributes(this.node5)) - .onVisibleAreaChange(getNodeThresholds(this.node5) || [0.0, 1.0], getComponentEventCallback(this.node5, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node5, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node5._nid].areaInfo = areaResult - })) + createText(this.node5 as TaroTextElement) } } - if (this.node0.childNodes[3]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node8)) { - Text(this.node8.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node8.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node8.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node8.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node8.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node8.childNodes[0]._nid].areaInfo = areaResult - })) + if ((this.node0.childNodes[3] as TaroElement)._attrs.compileIf) { + Column() { + createText(this.node8.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node8)) - .onVisibleAreaChange(getNodeThresholds(this.node8) || [0.0, 1.0], getComponentEventCallback(this.node8, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node8, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node8._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node8 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node8 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node8 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node8 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node8 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - if (this.node0.childNodes[4]._attrs.compileIf) { - if (this.node0.childNodes[4]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node10)) { - Text(this.node10.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node10.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node10.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node10.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node10.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node10.childNodes[0]._nid].areaInfo = areaResult - })) - } - .attrs(getNormalAttributes(this.node10)) - .onVisibleAreaChange(getNodeThresholds(this.node10) || [0.0, 1.0], getComponentEventCallback(this.node10, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node10, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node10._nid].areaInfo = areaResult - })) - } + if ((this.node0.childNodes[4] as TaroElement)._attrs.compileIf) { } else { - Flex(FlexManager.flexOptions(this.node9)) { - Text(this.node9.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node9.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node9.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node9.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node9.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node9.childNodes[0]._nid].areaInfo = areaResult - })) + Column() { + createText(this.node9.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node9)) - .onVisibleAreaChange(getNodeThresholds(this.node9) || [0.0, 1.0], getComponentEventCallback(this.node9, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node9, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node9._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node9 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node9 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node9 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node9 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node9 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - if (this.node0.childNodes[5]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node12)) { - Text(this.node12.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node12.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node12.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node12.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node12.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node12.childNodes[0]._nid].areaInfo = areaResult - })) + if ((this.node0.childNodes[5] as TaroElement)._attrs.compileIf) { + Column() { + createText(this.node10.childNodes[0] as TaroTextElement) } - .attrs(getNormalAttributes(this.node12)) - .onVisibleAreaChange(getNodeThresholds(this.node12) || [0.0, 1.0], getComponentEventCallback(this.node12, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node12, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node12._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node10 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node10 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node10 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node10 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node10 as TaroElement)._nodeInfo.areaInfo = res[1] })) - } else { - if (this.node0.childNodes[5]._attrs.compileIf) { - Flex(FlexManager.flexOptions(this.node11)) { - Text(this.node11.childNodes[0].textContent) - .attrsText(getTextAttributes(this.node11.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node11.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node11.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node11.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node11.childNodes[0]._nid].areaInfo = areaResult - })) - } - .attrs(getNormalAttributes(this.node11)) - .onVisibleAreaChange(getNodeThresholds(this.node11) || [0.0, 1.0], getComponentEventCallback(this.node11, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node11, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node11._nid].areaInfo = areaResult - })) - } } - if (this.node0.childNodes[6]._attrs.compileIf) { - Text(this.node0.childNodes[6].textContent) - .attrsText(getTextAttributes(this.node0.childNodes[6])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[6]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[6], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[6], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[6]._nid].areaInfo = areaResult - })) + if ((this.node0.childNodes[6] as TaroElement)._attrs.compileIf) { + createText(this.node0.childNodes[6] as TaroTextElement) } else { - Text(this.node0.childNodes[6].textContent) - .attrsText(getTextAttributes(this.node0.childNodes[6])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[6]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[6], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[6], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[6]._nid].areaInfo = areaResult - })) + createText(this.node0.childNodes[6] as TaroTextElement) } - Flex(FlexManager.flexOptions(this.node13)) {} - .attrs(getNormalAttributes(this.node13)) - .onVisibleAreaChange(getNodeThresholds(this.node13) || [0.0, 1.0], getComponentEventCallback(this.node13, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node13, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node13._nid].areaInfo = areaResult + Column() {} + .attributeModifier(columnModify.setNode(this.node11 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node11 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node11 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node11 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node11 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t0 +@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } + } + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] + })) + } +} + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } +} + + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } +} + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) +} + +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') + } +} + `; function Index() { return @@ -395,17 +294,17 @@ function Index() { {condition1 ? condition2 ? {a} : {b} : {c}} - {condition1 ? {a} : condition2 ? {b} : {c}} + {condition1 ? {a} : condition2 ? {b} : {c}} - {condition1 ? {a} : condition2 ? {b} : {c}} + {condition1 ? {a} : condition2 ? {b} : {c}} - {condition1 ? condition2 ? {a} : : {b}} + {condition1 ? condition2 && {a} : {b}} - {condition1 ? {a} : condition2 ? {b} : } + {condition1 ? {a} : condition2 && {b}} {condition1 ? "someText" : 789} - + ; } diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_compile_child_node.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_compile_child_node.js index 247a62723176..de262eaf47c1 100644 --- a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_compile_child_node.js +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_compile_child_node.js @@ -1,168 +1,112 @@ -const TARO_TEMPLATES_f0t0 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) -} -@Extend(Image) -function attrsImage ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) - .objectFit(ImageFit.Contain) -} +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable @Component -struct TARO_TEMPLATES_f0t0 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') build() { - Flex(FlexManager.flexOptions(this.node0)) { - Flex(FlexManager.flexOptions(this.node0.childNodes[0])) {} - .attrs(getNormalAttributes(this.node0.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[0]._nid].areaInfo = areaResult + Column() { + Column() {} + .attributeModifier(columnModify.setNode(this.node0.childNodes[0] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[0] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[0] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[0] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[0] as TaroElement)._nodeInfo.areaInfo = res[1] })) - Flex(FlexManager.flexOptions(this.node0.childNodes[1])) { - Image(this.node0.childNodes[1].childNodes[0].getAttribute('src')) - .attrsImage(getNormalAttributes(this.node0.childNodes[1].childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[1].childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[1].childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[1].childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[1].childNodes[0]._nid].areaInfo = areaResult + Column() { + Image((this.node0.childNodes[1].childNodes[0] as TaroElement).getAttribute('src')) + .objectFit(getImageMode((this.node0.childNodes[1].childNodes[0] as TaroElement).getAttribute('mode'))) + .attributeModifier(commonStyleModify.setNode(this.node0.childNodes[1].childNodes[0] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[1].childNodes[0] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[1].childNodes[0] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[1].childNodes[0] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[1].childNodes[0] as TaroElement)._nodeInfo.areaInfo = res[1] })) + .borderRadius({ + topLeft: (this.node0.childNodes[1].childNodes[0] as TaroElement)._st.hmStyle.borderTopLeftRadius, + topRight: (this.node0.childNodes[1].childNodes[0] as TaroElement)._st.hmStyle.borderTopRightRadius, + bottomLeft: (this.node0.childNodes[1].childNodes[0] as TaroElement)._st.hmStyle.borderBottomLeftRadius, + bottomRight: (this.node0.childNodes[1].childNodes[0] as TaroElement)._st.hmStyle.borderBottomRightRadius + }) } - .attrs(getNormalAttributes(this.node0.childNodes[1])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[1]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[1], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[1], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[1]._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0.childNodes[1] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[1] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[1] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[1] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[1] as TaroElement)._nodeInfo.areaInfo = res[1] })) } - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t0 -` +`; function Index() { return + + + + - + + ; } diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_component_not_in_config.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_component_not_in_config.js index 35dc23f96f62..3842d89c60e3 100644 --- a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_component_not_in_config.js +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_component_not_in_config.js @@ -1,108 +1,91 @@ -const TARO_TEMPLATES_f0t0 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) -} +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable @Component -struct TARO_TEMPLATES_f0t0 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') build() { - Flex(FlexManager.flexOptions(this.node0)) { - Flex(FlexManager.flexOptions(this.node0.childNodes[0])) { - ForEach(this.node0.childNodes, item => { - createNode(item) - }, item => item._nid) + Column() { + Column() { + createChildItem(this.node0.childNodes[0].childNodes[0] as TaroElement, createLazyChildren) } - .attrs(getNormalAttributes(this.node0.childNodes[0])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[0]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[0], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[0], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[0]._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0.childNodes[0] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[0] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[0] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[0] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[0] as TaroElement)._nodeInfo.areaInfo = res[1] })) - Flex(FlexManager.flexOptions(this.node0.childNodes[1])) { - ForEach(this.node0.childNodes, item => { - createNode(item) - }, item => item._nid) + Column() { + createChildItem(this.node0.childNodes[1].childNodes[0] as TaroElement, createLazyChildren) } - .attrs(getNormalAttributes(this.node0.childNodes[1])) - .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[1]) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[1], VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0.childNodes[1], AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0.childNodes[1]._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0.childNodes[1] as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0.childNodes[1] as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0.childNodes[1] as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0.childNodes[1] as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0.childNodes[1] as TaroElement)._nodeInfo.areaInfo = res[1] })) } - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t0 `; function Index() { return diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_multi_compile_mode.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_multi_compile_mode.js index c0820bc38727..f520450a0eba 100644 --- a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_multi_compile_mode.js +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_multi_compile_mode.js @@ -1,255 +1,272 @@ -const TARO_TEMPLATES_f0t0 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Image) -function attrsImage ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) - .objectFit(ImageFit.Contain) -} +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable @Component -struct TARO_TEMPLATES_f0t0 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') build() { - Image(this.node0.getAttribute('src')) - .attrsImage(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + Image((this.node0 as TaroElement).getAttribute('src')) + .objectFit(getImageMode((this.node0 as TaroElement).getAttribute('mode'))) + .attributeModifier(commonStyleModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] })) + .borderRadius({ + topLeft: (this.node0 as TaroElement)._st.hmStyle.borderTopLeftRadius, + topRight: (this.node0 as TaroElement)._st.hmStyle.borderTopRightRadius, + bottomLeft: (this.node0 as TaroElement)._st.hmStyle.borderBottomLeftRadius, + bottomRight: (this.node0 as TaroElement)._st.hmStyle.borderBottomRightRadius + }) } } -export default TARO_TEMPLATES_f0t0 -` -const TARO_TEMPLATES_f0t1 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) -} -@Extend(Text) -function attrsText ({ - id, - width, - height, - zIndex, - opacity, - margin, - padding, - decoration, - lineHeight, - letterSpacing, - maxLines, - fontColor, - fontSize, - fontWeight, - fontFamily, - textOverflow, - constraintSize, - border, - borderRadius, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - rotate, - scale, - translate, - transform, - textAlign, - }) { - .id(id) - .key(id) - .constraintSize(constraintSize) - .zIndex(zIndex) - .opacity(opacity) - .margin(margin) - .padding(padding) - .decoration(decoration) - .lineHeight(lineHeight) - .letterSpacing(letterSpacing) - .maxLines(maxLines) - .fontColor(fontColor) - .fontSize(fontSize) - .fontWeight(fontWeight) - .fontFamily(fontFamily) - .textOverflow(textOverflow) - .border(border) - .borderRadius(borderRadius) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .textAlign(textAlign) - .width(width) - .height(height) -} +`; +const TARO_TEMPLATES_f0t1 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' -function getTextAttributes (node: TaroViewElement) { - const attrs = { - ...getNormalAttributes(node), - ...getFontAttributes(node) - } +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' - transformW3CToHarmonyInStyle(node._st, attrs) - return attrs -} +@Reusable @Component -struct TARO_TEMPLATES_f0t1 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t1 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() - @State node1: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + @State node1: TaroElement = new TaroElement('Ignore') build() { - Flex(FlexManager.flexOptions(this.node0)) { - Text(this.node1.textContent) - .attrsText(getTextAttributes(this.node1)) - .onVisibleAreaChange(getNodeThresholds(this.node1) || [0.0, 1.0], getComponentEventCallback(this.node1, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node1, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node1._nid].areaInfo = areaResult - })) + Column() { + createText(this.node1 as TaroTextElement) + } + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } +} +@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } } - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t1 -` + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } +} + + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } +} + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) +} + +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') + } +} + +`; function Index() { return + + + {myText} + - + + ; } diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_single_compile_mode.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_single_compile_mode.js index a17219333158..a5f059cb98fe 100644 --- a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_single_compile_mode.js +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/entry.rs/should_support_single_compile_mode.js @@ -1,86 +1,75 @@ -const TARO_TEMPLATES_f0t0 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) -} +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable @Component -struct TARO_TEMPLATES_f0t0 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') build() { - Flex(FlexManager.flexOptions(this.node0)) {} - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + Column() {} + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t0 -` +`; function Index() { - return + return ; } diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_arrow_function_with_blockstmt.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_arrow_function_with_blockstmt.js index 4dd3704cf651..a0d84c51a211 100644 --- a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_arrow_function_with_blockstmt.js +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_arrow_function_with_blockstmt.js @@ -1,95 +1,196 @@ -const TARO_TEMPLATES_f0t0 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) -} +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable @Component -struct TARO_TEMPLATES_f0t0 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') build() { - Flex(FlexManager.flexOptions(this.node0)) { - ForEach(this.node0.childNodes, item => { - createNode(item) - }, item => item._nid) + Column() { + ForEach(this.node0.childNodes, (item: TaroElement) => { + Column() { + createText(item.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(item as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(item as TaroElement) || [0.0, 1.0], getComponentEventCallback(item as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(item as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (item as TaroElement)._nodeInfo.areaInfo = res[1] + })) + }, (item: TaroElement) => item._nid.toString()); } - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t0 +@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } + } + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] + })) + } +} + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } +} + + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } +} + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) +} + +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') + } +} + `; function Index() { return {list.map((item)=>{ - return {item}; + return {item}; })} ; diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_arrow_function_with_blockstmt_and_set_parent_dynamic_id.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_arrow_function_with_blockstmt_and_set_parent_dynamic_id.js index fc4a2224dec9..83f4c7e43aef 100644 --- a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_arrow_function_with_blockstmt_and_set_parent_dynamic_id.js +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_arrow_function_with_blockstmt_and_set_parent_dynamic_id.js @@ -1,105 +1,209 @@ -const TARO_TEMPLATES_f0t0 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) -} +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable @Component -struct TARO_TEMPLATES_f0t0 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() - @State node1: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') + @State node1: TaroElement = new TaroElement('Ignore') build() { - Flex(FlexManager.flexOptions(this.node0)) { - Flex(FlexManager.flexOptions(this.node1)) { - ForEach(this.node1.childNodes, item => { - createNode(item) - }, item => item._nid) + Column() { + Column() { + ForEach(this.node1.childNodes, (item: TaroElement) => { + Column() { + createText(item.childNodes[0] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(item as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(item as TaroElement) || [0.0, 1.0], getComponentEventCallback(item as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(item as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (item as TaroElement)._nodeInfo.areaInfo = res[1] + })) + }, (item: TaroElement) => item._nid.toString()); } - .attrs(getNormalAttributes(this.node1)) - .onVisibleAreaChange(getNodeThresholds(this.node1) || [0.0, 1.0], getComponentEventCallback(this.node1, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node1, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node1._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node1 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node1 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node1 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node1 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node1 as TaroElement)._nodeInfo.areaInfo = res[1] })) } - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] + })) + } +} +@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } + } + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t0 + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } +} + + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } +} + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) +} + +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') + } +} + `; function Index() { return + + {list.map((item)=>{ - return {item}; + return {item}; })} + + ; } diff --git a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_function_expr.js b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_function_expr.js index c81d692bba46..f69198b58838 100644 --- a/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_function_expr.js +++ b/crates/swc_plugin_compile_mode/tests/__swc_snapshots__/src/tests/harmony/looping.rs/should_loop_with_function_expr.js @@ -1,99 +1,210 @@ -const TARO_TEMPLATES_f0t0 = `import { FlexManager } from './utils/FlexManager' -import { getNodeThresholds, getNormalAttributes, getTextAttributes } from './utils/helper' -import { TaroIgnoreElement, eventHandler, DynamicCenter, getComponentEventCallback, AREA_CHANGE_EVENT_NAME, VISIBLE_CHANGE_EVENT_NAME } from '../runtime' -import type { TaroElement } from '../runtime' -import { TOUCH_EVENT_MAP } from './utils/constant/event' -@Extend(Flex) -function attrs ({ - flexBasis, - flexGrow, - flexShrink, - alignSelf, - clip, - width, - height, - margin, - padding, - linearGradient, - zIndex, - borderStyle, - borderWidth, - borderColor, - borderRadius, - opacity, - backgroundColor, - backgroundImage, - backgroundRepeat, - backgroundImageSize, - constraintSize, - rotate, - scale, - translate, - transform -}) { - .flexGrow(flexGrow) - .flexShrink(flexShrink) - .flexBasis(flexBasis) - .alignSelf(alignSelf) - .width(width) - .height(height) - .constraintSize(constraintSize) - .margin(margin) - .padding(padding) - .linearGradient(linearGradient) - .zIndex(zIndex) - .borderStyle(borderStyle) - .borderWidth(borderWidth) - .borderColor(borderColor) - .borderRadius(borderRadius) - .opacity(opacity) - .backgroundColor(backgroundColor) - .backgroundImage(backgroundImage, backgroundRepeat) - .backgroundImageSize(backgroundImageSize) - .rotate(rotate) - .scale(scale) - .translate(translate) - .transform(transform) - .clip(clip) -} +const TARO_TEMPLATES_f0t0 = `import { + rowModify, + FlexManager, + columnModify, + DynamicCenter, + getButtonColor, + TOUCH_EVENT_MAP, + getFontAttributes, + commonStyleModify, + getNodeThresholds, + BUTTON_THEME_COLOR, + getStyleAttr, + getNormalAttributes, + shouldBindEvent, + textModify, + setNormalTextAttributeIntoInstance, + getImageMode +} from '@tarojs/components' +import { + NodeType, + convertNumber2VP, + TaroElement, + eventHandler, + getComponentEventCallback, + AREA_CHANGE_EVENT_NAME, + VISIBLE_CHANGE_EVENT_NAME +} from '@tarojs/runtime' +import { + createLazyChildren, + createChildItem +} from '../render' + +import type { + TaroTextElement, + HarmonyStyle, + TaroButtonElement, + TaroViewElement, + TaroAny, + TaroStyleType, + TaroTextStyleType +} from '@tarojs/runtime' +import { isString } from '@tarojs/shared' + + +@Reusable @Component -struct TARO_TEMPLATES_f0t0 { - nodeInfoMap: any = {} - dynamicCenter: DynamicCenter - @ObjectLink node: TaroElement +export default struct TARO_TEMPLATES_f0t0 { + node: TaroViewElement = new TaroElement('Ignore') + + dynamicCenter: DynamicCenter = new DynamicCenter() aboutToAppear () { - this.dynamicCenter = new DynamicCenter() this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) } - @State node0: TaroElement = new TaroIgnoreElement() + aboutToReuse(params: TaroAny): void { + this.node = params.node + this.dynamicCenter.bindComponentToNodeWithDFS(this.node, this) + } + + @State node0: TaroElement = new TaroElement('Ignore') build() { - Flex(FlexManager.flexOptions(this.node0)) { - ForEach(this.node0.childNodes, item => { - createNode(item) - }, item => item._nid) + Column() { + ForEach(this.node0.childNodes, (item: TaroElement) => { + Column() { + createText(item.childNodes[0] as TaroTextElement) + createText(item.childNodes[1] as TaroTextElement) + createText(item.childNodes[2] as TaroTextElement) + createText(item.childNodes[3] as TaroTextElement) + } + .attributeModifier(columnModify.setNode(item as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(item as TaroElement) || [0.0, 1.0], getComponentEventCallback(item as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(item as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (item as TaroElement)._nodeInfo.areaInfo = res[1] + })) + }, (item: TaroElement) => item._nid.toString()); } - .attrs(getNormalAttributes(this.node0)) - .onVisibleAreaChange(getNodeThresholds(this.node0) || [0.0, 1.0], getComponentEventCallback(this.node0, VISIBLE_CHANGE_EVENT_NAME)) - .onAreaChange(getComponentEventCallback(this.node0, AREA_CHANGE_EVENT_NAME, ({ eventResult }) => { - const [_, areaResult] = eventResult - this.nodeInfoMap[this.node0._nid].areaInfo = areaResult + .attributeModifier(columnModify.setNode(this.node0 as TaroElement)) + .onVisibleAreaChange(getNodeThresholds(this.node0 as TaroElement) || [0.0, 1.0], getComponentEventCallback(this.node0 as TaroElement, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(this.node0 as TaroElement, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + (this.node0 as TaroElement)._nodeInfo.areaInfo = res[1] })) } } -export default TARO_TEMPLATES_f0t0 -` +@Builder +function createText (node: TaroTextElement) { + if (node.nodeType === NodeType.TEXT_NODE) { + if (node.parentNode) { + if ((node.parentNode as TaroElement).tagName === 'BUTTON') { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement, { + fontSize: getButtonFontSize(node.parentNode as TaroButtonElement), + color: getButtonColor(node.parentNode as TaroButtonElement, BUTTON_THEME_COLOR.get((node.parentNode as TaroButtonElement)._attrs.type || '').text) + })) + } else { + Text(node.textContent) + .attributeModifier(textModify.setNode(node?.parentElement as TaroElement)) + .width(getTextInViewWidth(node.parentElement)) + } + } + } else { + Text(node.textContent) { + // text 下还有标签 + if (node.childNodes.length > 1 || ((node.childNodes[0] && node.childNodes[0] as TaroElement)?.nodeType === NodeType.ELEMENT_NODE)) { + ForEach(node.childNodes, (item: TaroElement) => { + if (item.tagName === 'IMAGE') { + ImageSpan(item.getAttribute('src')) + .attributeModifier(commonStyleModify.setNode(item)) + .objectFit(getImageMode(item.getAttribute('mode'))) + .verticalAlign(getImageSpanAlignment(node?.hmStyle?.verticalAlign)) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } else if (item.nodeType === NodeType.TEXT_NODE) { + Span(item.textContent) + } else if (item.tagName === 'TEXT') { + Span(item.textContent) + .attributeModifier((new SpanStyleModify()).setNode(item as TaroTextElement)) + .letterSpacing(item._st.hmStyle.letterSpacing) + .textBackgroundStyle({ + color: item._st.hmStyle.backgroundColor, + radius: { + topLeft: item._st.hmStyle.borderTopLeftRadius, + topRight: item._st.hmStyle.borderTopRightRadius, + bottomLeft: item._st.hmStyle.borderBottomLeftRadius, + bottomRight: item._st.hmStyle.borderBottomRightRadius, + } + }) + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', item) }, item, ['click'])) + } + }, (item: TaroElement) => item._nid.toString()) + } + } + .onClick(shouldBindEvent((e: ClickEvent) => { eventHandler(e, 'click', node) }, node, ['click'])) + .attributeModifier(textModify.setNode(node).withNormalStyle()) + .onVisibleAreaChange(getNodeThresholds(node) || [0.0, 1.0], getComponentEventCallback(node, VISIBLE_CHANGE_EVENT_NAME)) + .onAreaChange(getComponentEventCallback(node, AREA_CHANGE_EVENT_NAME, (res: TaroAny) => { + node._nodeInfo.areaInfo = res[1] + })) + } +} + +function getImageSpanAlignment (align: TaroAny): TaroAny { + if (align === Alignment.Top) { + return ImageSpanAlignment.TOP + } else if (align === Alignment.Bottom) { + return ImageSpanAlignment.BOTTOM + } else if (align === Alignment.Center) { + return ImageSpanAlignment.CENTER + } +} + + + +class SpanStyleModify implements AttributeModifier { + node: TaroTextElement | null = null + style: HarmonyStyle | null = null + overwriteStyle: Record = {} + withNormal = false + + setNode (node: TaroTextElement) { + this.node = node + this.style = getNormalAttributes(this.node) + return this + } + + applyNormalAttribute(instance: SpanAttribute): void { + if (this.node && this.style) { + setNormalTextAttributeIntoInstance(instance, this.style, this.node) + } + } +} + + +function getButtonFontSize (node: TaroButtonElement): string | number { + const isMini = node._attrs.size === 'mini' + + return isMini ? convertNumber2VP(26) : convertNumber2VP(36) +} + +function getTextInViewWidth (node: TaroElement | null): TaroAny { + if (node) { + const hmStyle: TaroAny = node.hmStyle || {} + const isFlexView = hmStyle.display === 'flex' + const width: TaroAny = getStyleAttr(node, 'width') + const isPercentWidth = isString(width) && width.includes('%') + + return isFlexView || isPercentWidth ? null : getStyleAttr(node, 'width') + } +} + +`; function Index() { return + {list.map(function(item, index) { - return + return + index: + {index} + item: + {item} - + + ; })} - + + ; } diff --git a/crates/taro_init/src/constants.rs b/crates/taro_init/src/constants.rs index 64e945566477..e6d0398e92c4 100644 --- a/crates/taro_init/src/constants.rs +++ b/crates/taro_init/src/constants.rs @@ -29,8 +29,8 @@ pub static FRAMEWORK_TYPE_MAP: Lazy> = Lazy::new(| let mut map = HashMap::new(); map.insert(&FrameworkType::Preact, "preact"); map.insert(&FrameworkType::React, "react"); - map.insert(&FrameworkType::Vue, "vue"); map.insert(&FrameworkType::Vue3, "vue3"); + map.insert(&FrameworkType::Solid, "solid"); map }); @@ -68,7 +68,7 @@ pub static PACKAGES_MANAGEMENT: Lazy> = Lazy:: }); pub static MEDIA_REGEX: Lazy = - Lazy::new(|| regex::Regex::new(r"\.(png|jpe?g|gif|svg|webp|jar|keystore)$").unwrap()); + Lazy::new(|| regex::Regex::new(r"\.(png|jpe?g|gif|svg|webp|jar|keystore|tgz)$").unwrap()); pub static TEMPLATE_CREATOR: &str = "template_creator.js"; @@ -95,8 +95,9 @@ pub enum CSSType { pub enum FrameworkType { React, Preact, - Vue, Vue3, + Solid, + None, } #[derive(Debug, PartialEq, Eq, Hash, Serialize)] diff --git a/crates/taro_init/src/creator.rs b/crates/taro_init/src/creator.rs index 89356f7c0b49..a01a7cac72a1 100644 --- a/crates/taro_init/src/creator.rs +++ b/crates/taro_init/src/creator.rs @@ -34,6 +34,9 @@ pub struct CreateOptions { pub page_name: Option, pub compiler: Option, pub set_page_name: Option, + pub sub_pkg: Option, + pub page_dir: Option, + pub set_sub_pkg_page_name: Option, pub change_ext: Option, pub is_custom_template: Option, pub plugin_type: Option, @@ -43,6 +46,7 @@ pub struct CreateOptions { pub struct JSReturnObject { pub set_page_name: Option, pub change_ext: Option, + pub set_sub_pkg_page_name: Option, } impl FromNapiValue for JSReturnObject { @@ -51,12 +55,17 @@ impl FromNapiValue for JSReturnObject { let mut js_return_object = JSReturnObject { set_page_name: None, change_ext: None, + set_sub_pkg_page_name: None, }; let has_set_page_name = obj.has_named_property("setPageName")?; let has_change_ext = obj.has_named_property("changeExt")?; + let has_set_sub_pkg_page_name = obj.has_named_property("setSubPkgName")?; if has_set_page_name { js_return_object.set_page_name = Some(obj.get_named_property::("setPageName")?); } + if has_set_sub_pkg_page_name { + js_return_object.set_sub_pkg_page_name = Some(obj.get_named_property::("setSubPkgName")?); + } if has_change_ext { js_return_object.change_ext = Some(obj.get_named_property::("changeExt")?); } @@ -197,11 +206,7 @@ impl Creator { for file in files { let file_relative_path = normalize_path_str(file.replace(template_path, "").as_str()); let framework = options.framework; - let is_vue_framework = if let Some(framework) = framework { - framework == FrameworkType::Vue || framework == FrameworkType::Vue3 - } else { - false - }; + let is_vue_framework = framework.is_some_and(|framework| framework == FrameworkType::Vue3); if is_vue_framework && file_relative_path.ends_with(".jsx") { continue; } @@ -226,8 +231,17 @@ impl Creator { JSReturn::Object(obj) => { let set_page_name = obj.set_page_name; let change_ext_re = obj.change_ext; - if let Some(set_page_name) = set_page_name { - page_name = set_page_name; + let set_sub_pkg_page_name = obj.set_sub_pkg_page_name; + let sub_pkg = &options.sub_pkg; + if sub_pkg.is_some() { + // 创建分包页面模式 + if let Some(set_sub_pkg_page_name) = set_sub_pkg_page_name { + page_name = set_sub_pkg_page_name; + } + } else { + if let Some(set_page_name) = set_page_name { + page_name = set_page_name; + } } if let Some(change_ext_re) = change_ext_re { change_ext = change_ext_re; diff --git a/crates/taro_init/src/page.rs b/crates/taro_init/src/page.rs index caab73d0126d..49897e507ae7 100644 --- a/crates/taro_init/src/page.rs +++ b/crates/taro_init/src/page.rs @@ -29,6 +29,8 @@ pub struct Page { pub custom_template_path: Option, pub base_page_files: Vec, pub period: PeriodType, + pub sub_pkg: Option, + pub page_dir: Option, } impl Page { @@ -49,6 +51,8 @@ impl Page { custom_template_path: Option, base_page_files: Vec, period: PeriodType, + sub_pkg: Option, + page_dir: Option, ) -> Self { Page { project_dir, @@ -67,6 +71,8 @@ impl Page { custom_template_path, base_page_files, period, + sub_pkg, + page_dir, } } @@ -101,8 +107,11 @@ impl Page { typescript: self.typescript.clone(), template: self.template.clone(), page_name: Some(self.page_name.clone()), + sub_pkg: self.sub_pkg.clone(), + page_dir: self.page_dir.clone(), compiler: self.compiler.clone(), set_page_name: None, + set_sub_pkg_page_name: None, change_ext: None, is_custom_template: self.is_custom_template.clone(), plugin_type: None, diff --git a/crates/taro_init/src/plugin.rs b/crates/taro_init/src/plugin.rs index a03e8b4246d5..75f395c8613b 100644 --- a/crates/taro_init/src/plugin.rs +++ b/crates/taro_init/src/plugin.rs @@ -63,6 +63,9 @@ impl Plugin { change_ext: None, is_custom_template: None, plugin_type: Some(self.plugin_type.clone()), + sub_pkg: None, + page_dir: None, + set_sub_pkg_page_name: None, }; let all_files = all_files.iter().filter_map(|f| f.to_str()).collect::>(); println!(); diff --git a/crates/taro_init/src/project.rs b/crates/taro_init/src/project.rs index 7d1c8a013271..23b5a30cdac2 100644 --- a/crates/taro_init/src/project.rs +++ b/crates/taro_init/src/project.rs @@ -107,6 +107,9 @@ impl Project { page_name: Some("index".to_string()), compiler: self.compiler.clone(), set_page_name: None, + set_sub_pkg_page_name: None, + sub_pkg: None, + page_dir: None, change_ext: None, is_custom_template: None, plugin_type: None, diff --git a/examples/blended-taro-component-vue3/taro-project/package.json b/examples/blended-taro-component-vue3/taro-project/package.json index e034a17ec9fe..65ac859a9f12 100644 --- a/examples/blended-taro-component-vue3/taro-project/package.json +++ b/examples/blended-taro-component-vue3/taro-project/package.json @@ -62,7 +62,6 @@ "eslint-plugin-vue": "^8.0.0", "jest": "^29.3.1", "jest-environment-jsdom": "^29.5.0", - "postcss": "^8.4.18", "style-loader": "1.3.0", "stylelint": "^14.4.0", "ts-node": "^10.9.1", diff --git a/examples/custom-tabbar-vue3/package.json b/examples/custom-tabbar-vue3/package.json index 6d752cc51594..3362fe1e1d9a 100644 --- a/examples/custom-tabbar-vue3/package.json +++ b/examples/custom-tabbar-vue3/package.json @@ -40,7 +40,7 @@ "@tarojs/components": "3.4.3", "@tarojs/plugin-framework-vue3": "3.4.3", "vuex": "^4.0.0-beta.4", - "vue": "^3.0.0" + "vue": "3.2.47" }, "devDependencies": { "@types/webpack-env": "^1.13.6", @@ -48,7 +48,7 @@ "@tarojs/webpack-runner": "3.4.3", "@babel/core": "^7.8.0", "babel-preset-taro": "3.4.3", - "@vue/compiler-sfc": "^3.0.0", + "@vue/compiler-sfc": "3.2.47", "vue-loader": "^16.0.0-beta.8", "eslint-plugin-vue": "^7.0.0", "eslint-config-taro": "3.4.3", diff --git a/examples/external-prebundle/package.json b/examples/external-prebundle/package.json index e96554b9b096..a70b6dc8518a 100644 --- a/examples/external-prebundle/package.json +++ b/examples/external-prebundle/package.json @@ -38,7 +38,7 @@ "jest-resolve": "^27.4.2", "jest-watch-typeahead": "^1.0.0", "mini-css-extract-plugin": "^2.4.5", - "postcss": "^8.4.4", + "postcss": "^8.4.38", "postcss-flexbugs-fixes": "^5.0.2", "postcss-loader": "^6.2.1", "postcss-normalize": "^10.0.1", diff --git a/examples/mini-program-example/package.json b/examples/mini-program-example/package.json index 018d091f750d..c195a08b9d28 100644 --- a/examples/mini-program-example/package.json +++ b/examples/mini-program-example/package.json @@ -79,7 +79,7 @@ "@typescript-eslint/parser": "^5.20.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "typescript": "^4.1.0", - "postcss": "^8.4.18", + "postcss": "^8.4.38", "ts-node": "^10.9.1", "@types/node": "^18.15.11" } diff --git a/examples/mini-program-example/src/pages/api/media/image/index.tsx b/examples/mini-program-example/src/pages/api/media/image/index.tsx index 641d842d65a1..e1848065cf2b 100644 --- a/examples/mini-program-example/src/pages/api/media/image/index.tsx +++ b/examples/mini-program-example/src/pages/api/media/image/index.tsx @@ -143,7 +143,7 @@ export default class Index extends React.Component { quality: 1, src: res.tempFilePaths[0], compressedWidth: 300, - compressHeight: 200, + compressedHeight: 200, success: (res1) => { TestConsole.consoleNormal('compressImage success ', res1) Taro.saveImageToPhotosAlbum({ diff --git a/examples/mini-split-chunks-plugin/config/index.js b/examples/mini-split-chunks-plugin/config/index.js index 389735e83f49..0434dacff1d5 100644 --- a/examples/mini-split-chunks-plugin/config/index.js +++ b/examples/mini-split-chunks-plugin/config/index.js @@ -1,4 +1,4 @@ -import path from 'path' +import path from 'node:path' const config = { // 使用 Webpack5 进行编译 diff --git a/examples/new-blended/miniapp/project.config.json b/examples/new-blended/miniapp/project.config.json index 775f1a0d6783..e68102fb2ac9 100644 --- a/examples/new-blended/miniapp/project.config.json +++ b/examples/new-blended/miniapp/project.config.json @@ -49,4 +49,4 @@ "include": [] }, "appid": "wxa9abf43f10a7bdb0" -} \ No newline at end of file +} diff --git a/examples/new-blended/miniapp/project.private.config.json b/examples/new-blended/miniapp/project.private.config.json index 5fba65235c00..0f43df783847 100644 --- a/examples/new-blended/miniapp/project.private.config.json +++ b/examples/new-blended/miniapp/project.private.config.json @@ -17,4 +17,4 @@ ] } } -} \ No newline at end of file +} diff --git a/examples/taro-list/package.json b/examples/taro-list/package.json index de778e0d38e3..976d3371ebbc 100644 --- a/examples/taro-list/package.json +++ b/examples/taro-list/package.json @@ -80,7 +80,7 @@ "@typescript-eslint/eslint-plugin": "^6.2.0", "typescript": "^5.1.0", "tsconfig-paths-webpack-plugin": "^4.1.0", - "postcss": "^8.4.18", + "postcss": "^8.4.38", "ts-node": "^10.9.1", "@types/node": "^18.15.11", "@types/jest": "^29.3.1", diff --git a/npm/darwin-arm64/package.json b/npm/darwin-arm64/package.json index 7b158b0fb53c..e873267f6a0e 100644 --- a/npm/darwin-arm64/package.json +++ b/npm/darwin-arm64/package.json @@ -1,7 +1,7 @@ { "name": "@tarojs/binding-darwin-arm64", "description": "Native binding for taro", - "version": "3.6.34", + "version": "4.0.4", "os": [ "darwin" ], @@ -14,7 +14,7 @@ ], "license": "MIT", "engines": { - "node": ">= 16" + "node": ">= 18" }, "publishConfig": { "access": "public" diff --git a/npm/darwin-x64/package.json b/npm/darwin-x64/package.json index 404d641cb723..b01032048c45 100644 --- a/npm/darwin-x64/package.json +++ b/npm/darwin-x64/package.json @@ -1,7 +1,7 @@ { "name": "@tarojs/binding-darwin-x64", "description": "Native binding for taro", - "version": "3.6.34", + "version": "4.0.4", "os": [ "darwin" ], @@ -14,7 +14,7 @@ ], "license": "MIT", "engines": { - "node": ">= 16" + "node": ">= 18" }, "publishConfig": { "access": "public" diff --git a/npm/linux-x64-gnu/package.json b/npm/linux-x64-gnu/package.json index 1d3e1b621054..029597af9186 100644 --- a/npm/linux-x64-gnu/package.json +++ b/npm/linux-x64-gnu/package.json @@ -1,7 +1,7 @@ { "name": "@tarojs/binding-linux-x64-gnu", "description": "Native binding for taro", - "version": "3.6.34", + "version": "4.0.4", "os": [ "linux" ], @@ -14,7 +14,7 @@ ], "license": "MIT", "engines": { - "node": ">= 16" + "node": ">= 18" }, "libc": [ "glibc" diff --git a/npm/linux-x64-musl/package.json b/npm/linux-x64-musl/package.json index 08689edee24b..ccdbb8514c2f 100644 --- a/npm/linux-x64-musl/package.json +++ b/npm/linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@tarojs/binding-linux-x64-musl", - "version": "3.6.34", + "version": "4.0.4", "os": [ "linux" ], @@ -13,7 +13,7 @@ ], "license": "MIT", "engines": { - "node": ">= 16" + "node": ">= 18" }, "libc": [ "musl" diff --git a/npm/win32-x64-msvc/package.json b/npm/win32-x64-msvc/package.json index 0ca138be5d2e..9e40748a144c 100644 --- a/npm/win32-x64-msvc/package.json +++ b/npm/win32-x64-msvc/package.json @@ -1,7 +1,7 @@ { "name": "@tarojs/binding-win32-x64-msvc", "description": "Native binding for taro", - "version": "3.6.34", + "version": "4.0.4", "os": [ "win32" ], @@ -14,7 +14,7 @@ ], "license": "MIT", "engines": { - "node": ">= 16" + "node": ">= 18" }, "publishConfig": { "access": "public" diff --git a/package.json b/package.json index 735daae9e965..c5ecc9c5b7cf 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,12 @@ { "name": "taro", - "version": "3.6.34", + "version": "4.0.4", "description": "开放式跨端跨框架开发解决方案", "homepage": "https://github.com/NervJS/taro#readme", "author": "O2Team", "private": true, "license": "MIT", "keywords": [ - "nerv", "taro" ], "bugs": { @@ -21,7 +20,7 @@ "scripts": { "preinstall": "npx only-allow pnpm", "prepare": "husky install", - "build": "pnpm -r --aggregate-output --filter=./packages/* build", + "build": "pnpm -r --filter=./packages/* prod", "build:binding:debug": "pnpm --filter @tarojs/binding run build:debug", "build:binding:release": "pnpm --filter @tarojs/binding run build", "format::rs": "cargo fmt --all", @@ -32,7 +31,7 @@ "format:check": "prettier --check --cache .", "test": "pnpm --if-present -r --aggregate-output --filter=./packages/* test:ci", "test:binding": "pnpm --filter @tarojs/binding run test", - "updateSnapshot": "pnpm --if-present -r --aggregate-output --filter=./packages/* updateSnapshot", + "updateSnapshot": "pnpm --if-present -r --aggregate-output --filter=./tests --filter=./packages/* updateSnapshot", "version": "run-s version:*", "version:release": "pnpm --parallel -r --aggregate-output --filter=./{npm/**,crates/native_binding,packages/*} exec npm version ${npm_package_version}", "version:git": "git add . && git commit -m \"chore(release): publish ${npm_package_version}\"", @@ -47,81 +46,74 @@ "prettier --write" ] }, + "taroTemp": [ + "@types/babel__core", + "@types/babel-types", + "@types/babel__traverse", + "@babel/cli", + "@babel/core", + "@babel/helper-plugin-utils", + "@babel/parser", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-decorators", + "@babel/plugin-proposal-do-expressions", + "@babel/plugin-proposal-object-rest-spread", + "@babel/plugin-syntax-dynamic-import", + "@babel/plugin-transform-react-jsx", + "@babel/plugin-transform-runtime", + "@babel/preset-env", + "@babel/preset-react", + "@babel/preset-typescript", + "@babel/runtime", + "@babel/traverse", + "@babel/types", + "babel-plugin-syntax-jsx", + "babel-preset-power-assert", + "'@types/react': '^18.0.0',", + "'@types/react-dom': '^18.0.0',", + "'@types/react-reconciler': '0.28.1'," + ], "devDependencies": { - "@babel/cli": "^7.14.5", - "@babel/core": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/plugin-proposal-class-properties": "^7.14.5", - "@babel/plugin-proposal-decorators": "^7.14.5", - "@babel/plugin-proposal-do-expressions": "^7.14.5", - "@babel/plugin-proposal-object-rest-spread": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "7.8.3", - "@babel/plugin-transform-react-jsx": "^7.14.5", - "@babel/plugin-transform-runtime": "^7.14.5", - "@babel/preset-env": "^7.14.5", - "@babel/preset-react": "^7.14.5", - "@babel/preset-typescript": "^7.14.5", - "@babel/runtime": "^7.14.5", - "@babel/traverse": "^7.14.5", + "@babel/cli": "^7.24.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.24.4", + "@babel/runtime": "^7.24.4", "@commitlint/cli": "^17.6.6", "@commitlint/config-conventional": "^17.6.6", - "@tarojs/components": "workspace:*", - "@tarojs/helper": "workspace:*", - "@tarojs/mini-runner": "workspace:*", - "@tarojs/plugin-framework-react": "workspace:*", - "@tarojs/plugin-framework-vue2": "workspace:*", - "@tarojs/plugin-framework-vue3": "workspace:*", - "@tarojs/plugin-platform-alipay": "workspace:*", - "@tarojs/plugin-platform-h5": "workspace:*", - "@tarojs/plugin-platform-jd": "workspace:*", - "@tarojs/plugin-platform-qq": "workspace:*", - "@tarojs/plugin-platform-swan": "workspace:*", - "@tarojs/plugin-platform-tt": "workspace:*", - "@tarojs/plugin-platform-weapp": "workspace:*", - "@tarojs/plugin-platform-harmony-hybrid": "workspace:*", - "@tarojs/router": "workspace:*", - "@tarojs/runner-utils": "workspace:*", - "@tarojs/runtime": "workspace:*", - "@tarojs/shared": "workspace:*", - "@tarojs/taro": "workspace:*", - "@tarojs/taro-h5": "workspace:*", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^4.0.0", + "@rollup/plugin-typescript": "^11.1.6", + "@rollup/plugin-alias": "^5.1.0", "@tarojs/taro-loader": "workspace:*", - "@tarojs/taro-rn": "workspace:*", - "@tarojs/webpack-runner": "workspace:*", - "@tarojs/webpack5-runner": "workspace:*", - "@types/babel-types": "^7.0.7", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.7", "@types/debug": "^4.1.5", - "@types/detect-port": "1.3.0", "@types/fs-extra": "^8.0.1", "@types/history": "^4.7.5", "@types/inquirer": "^8.2.1", - "@types/jest": "^29.4.0", + "@types/jest": "^29.5.0", "@types/less": "^3.0.2", - "@types/lodash": "^4.14.142", - "@types/lodash-es": "^4.17.6", - "@types/node": "^14.14.31", + "@types/node": "^18", "@types/postcss-import": "^14.0.0", - "@types/react": "^18.0.0", - "@types/react-reconciler": "0.28.1", - "@types/request": "^2.48.1", + "@types/postcss-url": "^10.0.0", "@types/resolve": "^1.20.6", "@types/sass": "1.43.1", "@types/tapable": "^1", "@types/webpack": "^4.41.26", "@types/webpack-dev-server": "^3.11.3", - "@typescript-eslint/eslint-plugin": "^6.2.0", - "@typescript-eslint/parser": "^6.2.0", + "@typescript-eslint/eslint-plugin": "^7.8.0", + "@typescript-eslint/parser": "^7.8.0", + "@vitest/coverage-v8": "^1.6.0", + "babel-jest": "^29.7.0", "babel-plugin-syntax-jsx": "6.18.0", "babel-preset-power-assert": "3.0.0", "babel-preset-taro": "workspace:*", "conventional-changelog-cli": "^2.0.1", "core-js": "^3.6.5", - "cpy-cli": "^4.1.0", "cross-env": "^7.0.2", - "eslint": "^8.12.0", + "escodegen": "^2.0.0", + "eslint": "^8.57.0", "eslint-config-prettier": "^6.4.0", "eslint-config-standard": "^14.1.1", "eslint-config-taro": "workspace:*", @@ -130,40 +122,39 @@ "eslint-plugin-node": "^10.0.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-react": "^7.4.0", + "eslint-plugin-react": "^7.33.2", "eslint-plugin-simple-import-sort": "^7.0.0", "eslint-plugin-standard": "^4.0.1", "husky": "^8.0.1", - "jest": "^27.4.5", - "jest-cli": "^27.4.5", - "jest-environment-node": "^27.4.4", + "jest": "^29.7.0", + "jest-cli": "^29.7.0", + "jest-environment-jsdom": "^29.6.4", + "jest-environment-node": "^29.7.0", + "jest-light-runner": "^0.6.0", + "jest-preset-stylelint": "^7.0.0", + "jest-taro-helper": "workspace:*", + "jsdom": "^24.0.0", "lint-staged": "^13.0.2", - "nervjs": "^1.4.6", + "mkdirp": "^3.0.1", "npm-run-all": "^4.1.2", - "postcss": "^8.4.18", + "postcss": "^8.4.38", + "postcss-less": "^6.0.0", "postcss-scss": "^4.0.3", "power-assert": "^1.6.1", - "preact": "^10.5.15", "prettier": "^2.7.1", "prop-types": "^15.7.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-test-renderer": "^18.2.0", "rimraf": "^5.0.0", - "stylelint": "^14.6.1", - "stylelint-config-standard-scss": "^3.0.0", - "stylelint-config-taro-rn": "workspace:*", - "stylelint-order": "^6.0.3", - "stylelint-taro-rn": "workspace:*", + "rollup": "^4.16.4", + "rollup-plugin-node-externals": "^7.1.1", + "rollup-plugin-postcss": "^4.0.2", + "stylelint": "^16.4.0", + "stylelint-config-standard-scss": "^13.1.0", + "stylelint-order": "^6.0.4", + "ts-jest": "^29.1.2", "tslib": "^2.6.2", - "typescript": "^4.7.4", - "vue": "^2.6.11", - "vue-loader": "^17.0.0", - "vue-template-compiler": "^2.6.11", - "webpack": "5.78.0", - "webpack-chain": "6.5.1", - "webpack-dev-server": "4.11.1", - "webpack-sources": "^3.2.3" + "ts-node": "^10.9.1", + "typescript": "^5.4.5", + "vitest": "^1.6.0" }, "pnpm": { "packageExtensions": { @@ -176,7 +167,7 @@ }, "react-native-root-siblings": { "peerDependencies": { - "react": "18.1.0" + "react": "18.2.0" }, "peerDependenciesMeta": { "react": { @@ -184,6 +175,9 @@ } } } + }, + "overrides": { + "browserslist": "^4.23.0" } } } diff --git a/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/package.json b/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/package.json index 6ef282c5548c..3285f2f2ab10 100644 --- a/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/package.json +++ b/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/package.json @@ -1,14 +1,14 @@ { "name": "babel-plugin-transform-react-jsx-to-rn-stylesheet", - "version": "3.6.34", + "version": "4.0.4", "description": "Transform stylesheet selector to style in JSX Elements.", + "author": "O2Team", "license": "MIT", "main": "dist/index.js", - "repository": { - "type": "git", - "url": "git+https://github.com/NervJS/taro.git" - }, "scripts": { + "prod": "pnpm run build", + "prebuild": "pnpm run clean", + "clean": "rimraf --impl=move-remove ./dist", "test": "jest", "test:ci": "jest --ci -i --coverage --silent", "dev": "tsc -w", @@ -17,21 +17,24 @@ "bugs": { "url": "https://github.com/NervJS/taro/issues" }, + "repository": { + "type": "git", + "url": "git+https://github.com/NervJS/taro.git" + }, "homepage": "https://github.com/NervJS/taro#readme", "engines": { - "node": ">=12.0.0", - "npm": ">=6.0.0" + "node": ">= 18" }, "dependencies": { - "camelize": "^1.0.0", + "camelize": "^1.0.1", "taro-css-to-react-native": "workspace:*" }, "devDependencies": { - "@babel/core": "^7.14.5", - "babel-jest": "^29.5.0", - "jest": "^29.3.1", - "jest-cli": "^29.3.1", - "ts-jest": "^29.0.5", - "typescript": "^4.7.4" + "@babel/core": "7.24.4", + "@types/babel__core": "7.20.5", + "babel-plugin-syntax-jsx": "6.18.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } } diff --git a/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/src/index.ts b/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/src/index.ts index 7205f4adb9b3..9ed0ae424eb8 100644 --- a/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/src/index.ts +++ b/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/src/index.ts @@ -1,9 +1,10 @@ -import { PluginObj, template as Template, types as Types } from 'babel__core' +import * as path from 'node:path' + import camelize from 'camelize' -import * as path from 'path' import { transformCSS } from 'taro-css-to-react-native' -import { ConvertPluginPass as PluginPass } from './types' +import type { PluginObj, template as Template, types as Types } from '@babel/core' +import type { ConvertPluginPass as PluginPass } from './types' const STYLE_SHEET_NAME = '_styleSheet' const GET_STYLE_FUNC_NAME = '_getStyle' @@ -140,11 +141,10 @@ export default function (babel: { const getModuleClassNameFunctionStmt = template(getModuleClassNameFunction)() - function getMap (str) { return str.split(/\s+/).map((className) => { // return template(`${STYLE_SHEET_NAME}["${className}"]`)().expression - const stmt = template(`${STYLE_SHEET_NAME}["${className}"]`)() + const stmt = template(`${STYLE_SHEET_NAME}["${className}"]`)() as Types.Node if (t.isExpressionStatement(stmt)) { return stmt.expression } @@ -171,19 +171,149 @@ export default function (babel: { return str === '' ? [] : getMap(str) } + function getArrayExpressionFromObjectProperty (value) { + let str + + if (!value || value.value === '') { + // className: "" + return [] + } else if ((value.type === 'CallExpression' || value.type === 'Identifier') && typeof value.value !== 'string') { + // className: "".concat(classPrefix, "-left") + // className: classNames([ classPrefix, className ]) + // className: cls + return [t.callExpression(t.identifier(GET_STYLE_FUNC_NAME), [value])] + } + + return !str ? [] : getMap(str) + } + function getMatchRule (enableMultipleClassName: boolean) { if (enableMultipleClassName) { return { styleMatchRule: /[sS]tyle$/, - classNameMathRule: /[cC]lassName$/ + classNameMatchRule: /[cC]lassName$/ } } return { styleMatchRule: /^style$/, - classNameMathRule: /^className$/ + classNameMatchRule: /^className$/ + } + } + + function processStyleAndClassName ({ + attributes, + state, + existStyleImport, + isFromJSX, + }) { + const { file, opts = {} } = state + const { enableMultipleClassName = false } = opts + const { styleMatchRule, classNameMatchRule } = getMatchRule(enableMultipleClassName) + const styleNameMapping = {} + const DEFAULT_STYLE_KEY = 'style' + + attributes.forEach(attribute => { + if (isFromJSX && !t.isJSXAttribute(attribute)) return + const attrNameString = t.isJSXAttribute(attribute) ? attribute.name?.name : attribute.key?.name + if (!attrNameString || typeof attrNameString !== 'string') return + if (attrNameString.match(styleMatchRule)) { + const prefix = attrNameString.replace(styleMatchRule, '') || DEFAULT_STYLE_KEY + styleNameMapping[prefix] = { + ...styleNameMapping[prefix] || {}, + hasStyleAttribute: true, + styleAttribute: attribute + } + } + if (attrNameString.match(classNameMatchRule)) { + const prefix = attrNameString.replace(classNameMatchRule, '') || DEFAULT_STYLE_KEY + styleNameMapping[prefix] = { + ...styleNameMapping[prefix] || {}, + hasClassName: true, + classNameAttribute: attribute + } + } + }) + + for (const key in styleNameMapping) { + const { hasClassName, classNameAttribute, hasStyleAttribute, styleAttribute } = styleNameMapping[key] + + if (!(hasClassName && existStyleImport) && hasStyleAttribute && t.isStringLiteral(styleAttribute?.value)) { + const cssObject = string2Object(styleAttribute?.value?.value) + styleAttribute.value = isFromJSX + ? t.jSXExpressionContainer(object2Expression(template, cssObject)) + : object2Expression(template, cssObject) + } + + if (hasClassName && existStyleImport) { + attributes.splice(attributes.indexOf(classNameAttribute), 1) + + if (isFromJSX) { + if ( + classNameAttribute.value && + t.isJSXExpressionContainer(classNameAttribute.value) && + typeof classNameAttribute.value.expression.value !== 'string'// not like className={'container'} + ) { + file.set('injectGetStyle', true) + } + } else { + if (classNameAttribute.value) { + file.set('injectGetStyle', true) + } + } + + const arrayExpression = isFromJSX + ? getArrayExpression(classNameAttribute.value) + : getArrayExpressionFromObjectProperty(classNameAttribute.value) + + if (arrayExpression.length === 0) return + if (arrayExpression.length > 1) file.set('hasMultiStyle', true) + + if (hasStyleAttribute && styleAttribute?.value) { + file.set('hasMultiStyle', true) + let expression + // 支持 行内 style 转成oject:style="width:100;height:100;" => style={{width:'100',height:'100'}} + if (t.isStringLiteral(styleAttribute.value)) { + const cssObject = string2Object(styleAttribute.value.value) + expression = object2Expression(template, cssObject) + } else { + expression = isFromJSX ? styleAttribute.value.expression : styleAttribute.value + } + const expressionType = expression.type + + let _arrayExpression + // 非rn场景,style不支持数组,因此需要将数组转换为对象 + // style={[styles.a, styles.b]} ArrayExpression + if (expressionType === 'ArrayExpression') { + _arrayExpression = arrayExpression.concat(expression.elements) + // style={styles.a} MemberExpression + // style={{ height: 100 }} ObjectExpression + // style={{ ...custom }} ObjectExpression + // style={custom} Identifier + // style={getStyle()} CallExpression + // style={this.props.useCustom ? custom : null} ConditionalExpression + // style={custom || other} LogicalExpression + } else { + _arrayExpression = arrayExpression.concat(expression) + } + + styleAttribute.value = isFromJSX + ? t.jSXExpressionContainer(t.callExpression(t.identifier(MERGE_ELE_STYLES_FUNC_NAME), _arrayExpression)) + : t.callExpression(t.identifier(MERGE_ELE_STYLES_FUNC_NAME), _arrayExpression) + } else { + const expression = arrayExpression.length > 1 + ? t.callExpression(t.identifier(MERGE_ELE_STYLES_FUNC_NAME), arrayExpression) + : arrayExpression[0] + + const newAttribute = isFromJSX + ? t.jSXAttribute(t.jSXIdentifier(key === DEFAULT_STYLE_KEY ? key : `${key}Style`), t.jSXExpressionContainer(expression)) + : t.objectProperty(t.identifier(key === DEFAULT_STYLE_KEY ? key : (key + 'Style')), expression) + attributes.push(newAttribute) + } + } } } + let existStyleImport = false return { @@ -255,106 +385,49 @@ export default function (babel: { }, JSXOpeningElement (astPath, state: PluginPass) { const { node } = astPath - const { file, opts = {} } = state - const { enableMultipleClassName = false } = opts - const { styleMatchRule, classNameMathRule } = getMatchRule(enableMultipleClassName) - - const styleNameMapping: any = {} - const DEFAULT_STYLE_KEY = 'style' const attributes = node.attributes - for (let i = 0; i < attributes.length; i++) { - const attribute = attributes[i] - if (!t.isJSXAttribute(attribute)) continue - const name = attribute.name - if (!name || typeof name.name !== 'string') continue - const attrNameString = name.name - - if (attrNameString.match(styleMatchRule)) { - const prefix = attrNameString.replace(styleMatchRule, '') || DEFAULT_STYLE_KEY - styleNameMapping[prefix] = Object.assign(styleNameMapping[prefix] || {}, { - hasStyleAttribute: true, - styleAttribute: attribute - }) - } - // 以className结尾的时候 - if (attrNameString.match(classNameMathRule)) { - const prefix = attrNameString.replace(classNameMathRule, '') || DEFAULT_STYLE_KEY - styleNameMapping[prefix] = Object.assign(styleNameMapping[prefix] || {}, { - hasClassName: true, - classNameAttribute: attribute - }) - } - } + processStyleAndClassName({ + attributes, + state, + existStyleImport, + isFromJSX: true + }) + }, + CallExpression (astPath, state: PluginPass) { + // 支持编译后代码的 style 替换 + // React.createElement 参数 - for (const key in styleNameMapping) { - const { hasClassName, classNameAttribute, hasStyleAttribute, styleAttribute } = styleNameMapping[key] - if (!(hasClassName && existStyleImport) && hasStyleAttribute) { - if (t.isStringLiteral(styleAttribute.value)) { - const cssObject = string2Object(styleAttribute.value.value) - styleAttribute.value = t.jSXExpressionContainer(object2Expression(template, cssObject)) - } + const { node } = astPath + // 检查是否是 React.createElement 调用 + if ( + t.isMemberExpression(node.callee) && + t.isIdentifier(node.callee.object, { name: 'React' }) && + t.isIdentifier(node.callee.property, { name: 'createElement' }) + ) { + const args = node.arguments + if (args.length <= 1) return + + let attributes + + if (t.isCallExpression(args[1]) && t.isObjectExpression(args[1].arguments[0])) { + // _object_spread({ className: rootClass(), style: rootStyle() }, rest) + attributes = args[1].arguments[0].properties } - - if (hasClassName && existStyleImport) { - // Remove origin className - attributes.splice(attributes.indexOf(classNameAttribute), 1) - - if ( - classNameAttribute.value && - classNameAttribute.value.type === 'JSXExpressionContainer' && - typeof classNameAttribute.value.expression.value !== 'string'// not like className={'container'} - ) { - file.set('injectGetStyle', true) - } - - const arrayExpression = getArrayExpression(classNameAttribute.value) - - if (arrayExpression.length === 0) { - return - } - - if (arrayExpression.length > 1) { - file.set('hasMultiStyle', true) - } - - if (hasStyleAttribute && styleAttribute.value) { - file.set('hasMultiStyle', true) - let expression - // 支持 行内 style 转成oject:style="width:100;height:100;" => style={{width:'100',height:'100'}} - if (t.isStringLiteral(styleAttribute.value)) { - const cssObject = string2Object(styleAttribute.value.value) - expression = object2Expression(template, cssObject) - } else { - expression = styleAttribute.value.expression - } - const expressionType = expression.type - - let _arrayExpression - // 非rn场景,style不支持数组,因此需要将数组转换为对象 - // style={[styles.a, styles.b]} ArrayExpression - if (expressionType === 'ArrayExpression') { - _arrayExpression = arrayExpression.concat(expression.elements) - // style={styles.a} MemberExpression - // style={{ height: 100 }} ObjectExpression - // style={{ ...custom }} ObjectExpression - // style={custom} Identifier - // style={getStyle()} CallExpression - // style={this.props.useCustom ? custom : null} ConditionalExpression - // style={custom || other} LogicalExpression - } else { - _arrayExpression = arrayExpression.concat(expression) - } - styleAttribute.value = t.jSXExpressionContainer(t.callExpression(t.identifier(MERGE_ELE_STYLES_FUNC_NAME), _arrayExpression)) - } else { - const expression = arrayExpression.length > 1 - ? t.callExpression(t.identifier(MERGE_ELE_STYLES_FUNC_NAME), arrayExpression) - : arrayExpression[0] - attributes.push(t.jSXAttribute(t.jSXIdentifier(key === DEFAULT_STYLE_KEY ? key : (key + 'Style')), t.jSXExpressionContainer(expression))) - } + if (t.isObjectExpression(args[1])) { + // { className: ..., style: ... } + attributes = args[1].properties } + if (!attributes) return + + processStyleAndClassName({ + attributes, + state, + existStyleImport, + isFromJSX: false + }) } - } + }, } } } diff --git a/packages/babel-plugin-transform-solid-jsx/.eslintrc.js b/packages/babel-plugin-transform-solid-jsx/.eslintrc.js new file mode 100644 index 000000000000..895898e9bc44 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + ignorePatterns: ['/test'], +} diff --git a/packages/babel-plugin-transform-solid-jsx/README.md b/packages/babel-plugin-transform-solid-jsx/README.md new file mode 100644 index 000000000000..d642f3b2c323 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/README.md @@ -0,0 +1,51 @@ +# babel-plugin-transform-solid-jsx + +fork from [babel-plugin-jsx-dom-expressions](https://github.com/ryansolid/dom-expressions/blob/main/packages/babel-plugin-jsx-dom-expressions) version: 0.37.19 + +This package is a JSX compiler built for [DOM Expressions](https://github.com/ryansolid/dom-expressions) to provide a general JSX to DOM transformation for reactive libraries that do fine grained change detection. This package aims to convert JSX statements to native DOM statements and wrap JSX expressions with functions that can be implemented with the library of your choice. Sort of like a JSX to Hyperscript for fine change detection. + +## What Has Been Modified? +- Added uniqueTransform configuration, defaulting to false, indicating that the following processing should not be performed. +- Within the transformElement function of the universal module, perform matching against components from @tarojs/components. Modify the transformation of these components such that they are instead invoked via createElement calls. + +### Example +```jsx +import { View, Text, Button } from '@tarojs/components'; + +const Component = () => { + return ( + + + Hello world! + + + + ); +}; +``` + +Compiles to: +```jsx +import { createTextNode as _$createTextNode } from "@tarojs/plugin-framework-react/dist/reconciler"; +import { insertNode as _$insertNode } from "@tarojs/plugin-framework-react/dist/reconciler"; +import { setProp as _$setProp } from "@tarojs/plugin-framework-react/dist/reconciler"; +import { createElement as _$createElement } from "@tarojs/plugin-framework-react/dist/reconciler"; +import { View, Text, Button } from "@tarojs/components"; +export default function Index() { + return function () { + var _el$ = _$createElement("view"), + _el$2 = _$createElement("view"), + _el$3 = _$createElement("text"), + _el$5 = _$createElement("button"); + _$insertNode(_el$, _el$2); + _$insertNode(_el$, _el$5); + _$setProp(_el$, "class", "index"); + _$insertNode(_el$2, _el$3); + _$insertNode(_el$3, _$createTextNode("Hello world! ")); + _$insertNode(_el$5, _$createTextNode("set class")); + return _el$; + }(); +} +``` + +> The purpose of doing so is to ensure compatibility by aligning the compilation results of Taro components within mini programs with those of original tags. diff --git a/packages/babel-plugin-transform-solid-jsx/index.js b/packages/babel-plugin-transform-solid-jsx/index.js new file mode 100644 index 000000000000..84aba041ed97 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/index.js @@ -0,0 +1,35 @@ +const jsxTransform = require('./dist') + +module.exports = function (context, options = {}) { + const plugins = [ + [ + jsxTransform, + Object.assign( + { + moduleName: 'solid-js/web', + builtIns: [ + 'For', + 'Show', + 'Switch', + 'Match', + 'Suspense', + 'SuspenseList', + 'Portal', + 'Index', + 'Dynamic', + 'ErrorBoundary', + ], + contextToCustomElements: true, + wrapConditionals: true, + generate: 'dom', + uniqueTransform: false, + }, + options + ), + ], + ] + + return { + plugins, + } +} diff --git a/packages/babel-plugin-transform-solid-jsx/jest.config.js b/packages/babel-plugin-transform-solid-jsx/jest.config.js new file mode 100644 index 000000000000..8691d79ed951 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + moduleDirectories: ['node_modules', 'packages'], + testEnvironment: 'jsdom', + collectCoverageFrom: ['./index.js'], + transform: { + '^.+\\.jsx?$': 'babel-jest', + }, +} diff --git a/packages/babel-plugin-transform-solid-jsx/package.json b/packages/babel-plugin-transform-solid-jsx/package.json new file mode 100644 index 000000000000..c812d706f3a9 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/package.json @@ -0,0 +1,40 @@ +{ + "name": "babel-plugin-transform-solid-jsx", + "description": "A JSX to DOM plugin that wraps expressions for fine grained change detection", + "version": "4.0.4", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/NervJS/taro.git" + }, + "bugs": { + "url": "https://github.com/NervJS/taro/issues" + }, + "readmeFilename": "README.md", + "main": "index.js", + "files": [ + "index.js", + "dist" + ], + "sideEffects": false, + "scripts": { + "prod": "pnpm run build", + "prebuild": "pnpm run clean", + "build": "rollup -c", + "clean": "rimraf ./dist", + "test:ci": "cross-env NODE_ENV=test jest --ci -i", + "test": "pnpm run build && jest --no-cache", + "test:coverage": "pnpm run build && jest --coverage --no-cache", + "prepublishOnly": "pnpm run build", + "prepare": "pnpm run build" + }, + "dependencies": { + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "html-entities": "2.3.3", + "validate-html-nesting": "^1.2.1" + }, + "devDependencies": { + "babel-plugin-tester": "^11.0.4" + } +} diff --git a/packages/babel-plugin-transform-solid-jsx/rollup.config.mjs b/packages/babel-plugin-transform-solid-jsx/rollup.config.mjs new file mode 100644 index 000000000000..8e9f2f01c8bc --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/rollup.config.mjs @@ -0,0 +1,31 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import nodeResolve from '@rollup/plugin-node-resolve' + +const __filename = fileURLToPath(new URL(import.meta.url)) +const cwd = path.dirname(__filename) + +const plugins = [ + nodeResolve({ + rootDir: path.join(cwd, '../..'), + moduleDirectories: ['node_modules', 'packages'], + }), +] + +export default { + input: 'src/index.js', + external: [ + '@babel/plugin-syntax-jsx', + '@babel/helper-module-imports', + '@babel/types', + 'html-entities', + 'validate-html-nesting', + ], + output: { + file: 'dist/index.js', + format: 'cjs', + exports: 'auto', + }, + plugins, +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/VoidElements.js b/packages/babel-plugin-transform-solid-jsx/src/VoidElements.js new file mode 100644 index 000000000000..ad9cd5302e84 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/VoidElements.js @@ -0,0 +1,18 @@ +export default [ + 'area', + 'base', + 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'keygen', + 'link', + 'menuitem', + 'meta', + 'param', + 'source', + 'track', + 'wbr' +] diff --git a/packages/babel-plugin-transform-solid-jsx/src/config.js b/packages/babel-plugin-transform-solid-jsx/src/config.js new file mode 100644 index 000000000000..67ba9aa693d3 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/config.js @@ -0,0 +1,16 @@ +export default { + moduleName: 'dom', + generate: 'dom', + hydratable: false, + delegateEvents: true, + delegatedEvents: [], + builtIns: [], + requireImportSource: false, + wrapConditionals: true, + omitNestedClosingTags: false, + contextToCustomElements: false, + staticMarker: '@once', + effectWrapper: 'effect', + memoWrapper: 'memo', + validate: true +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/constants.js b/packages/babel-plugin-transform-solid-jsx/src/constants.js new file mode 100644 index 000000000000..c5f19a5a1a45 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/constants.js @@ -0,0 +1,488 @@ +const booleans = [ + 'allowfullscreen', + 'async', + 'autofocus', + 'autoplay', + 'checked', + 'controls', + 'default', + 'disabled', + 'formnovalidate', + 'hidden', + 'indeterminate', + 'inert', + 'ismap', + 'loop', + 'multiple', + 'muted', + 'nomodule', + 'novalidate', + 'open', + 'playsinline', + 'readonly', + 'required', + 'reversed', + 'seamless', + 'selected', +] + +const BooleanAttributes = /* #__PURE__ */ new Set(booleans) + +const Properties = /* #__PURE__ */ new Set([ + 'className', + 'value', + 'readOnly', + 'formNoValidate', + 'isMap', + 'noModule', + 'playsInline', + ...booleans, +]) + +const ChildProperties = /* #__PURE__ */ new Set(['innerHTML', 'textContent', 'innerText', 'children']) + +// React Compat +const Aliases = /* #__PURE__ */ Object.assign(Object.create(null), { + className: 'class', + htmlFor: 'for', +}) + +const PropAliases = /* #__PURE__ */ Object.assign(Object.create(null), { + class: 'className', + formnovalidate: { + $: 'formNoValidate', + BUTTON: 1, + INPUT: 1, + }, + ismap: { + $: 'isMap', + IMG: 1, + }, + nomodule: { + $: 'noModule', + SCRIPT: 1, + }, + playsinline: { + $: 'playsInline', + VIDEO: 1, + }, + readonly: { + $: 'readOnly', + INPUT: 1, + TEXTAREA: 1, + }, +}) + +function getPropAlias(prop, tagName) { + const a = PropAliases[prop] + return typeof a === 'object' ? (a[tagName] ? a.$ : undefined) : a +} + +// list of Element events that will be delegated +const DelegatedEvents = /* #__PURE__ */ new Set([ + 'beforeinput', + 'click', + 'dblclick', + 'contextmenu', + 'focusin', + 'focusout', + 'input', + 'keydown', + 'keyup', + 'mousedown', + 'mousemove', + 'mouseout', + 'mouseover', + 'mouseup', + 'pointerdown', + 'pointermove', + 'pointerout', + 'pointerover', + 'pointerup', + 'touchend', + 'touchmove', + 'touchstart', +]) + +const SVGElements = /* #__PURE__ */ new Set([ + // "a", + 'altGlyph', + 'altGlyphDef', + 'altGlyphItem', + 'animate', + 'animateColor', + 'animateMotion', + 'animateTransform', + 'circle', + 'clipPath', + 'color-profile', + 'cursor', + 'defs', + 'desc', + 'ellipse', + 'feBlend', + 'feColorMatrix', + 'feComponentTransfer', + 'feComposite', + 'feConvolveMatrix', + 'feDiffuseLighting', + 'feDisplacementMap', + 'feDistantLight', + 'feDropShadow', + 'feFlood', + 'feFuncA', + 'feFuncB', + 'feFuncG', + 'feFuncR', + 'feGaussianBlur', + 'feImage', + 'feMerge', + 'feMergeNode', + 'feMorphology', + 'feOffset', + 'fePointLight', + 'feSpecularLighting', + 'feSpotLight', + 'feTile', + 'feTurbulence', + 'filter', + 'font', + 'font-face', + 'font-face-format', + 'font-face-name', + 'font-face-src', + 'font-face-uri', + 'foreignObject', + 'g', + 'glyph', + 'glyphRef', + 'hkern', + 'image', + 'line', + 'linearGradient', + 'marker', + 'mask', + 'metadata', + 'missing-glyph', + 'mpath', + 'path', + 'pattern', + 'polygon', + 'polyline', + 'radialGradient', + 'rect', + // "script", + 'set', + 'stop', + // "style", + 'svg', + 'switch', + 'symbol', + 'text', + 'textPath', + // "title", + 'tref', + 'tspan', + 'use', + 'view', + 'vkern', +]) + +const SVGNamespace = { + xlink: 'http://www.w3.org/1999/xlink', + xml: 'http://www.w3.org/XML/1998/namespace', +} + +const DOMElements = /* #__PURE__ */ new Set([ + 'html', + 'base', + 'head', + 'link', + 'meta', + 'style', + 'title', + 'body', + 'address', + 'article', + 'aside', + 'footer', + 'header', + 'main', + 'nav', + 'section', + 'body', + 'blockquote', + 'dd', + 'div', + 'dl', + 'dt', + 'figcaption', + 'figure', + 'hr', + 'li', + 'ol', + 'p', + 'pre', + 'ul', + 'a', + 'abbr', + 'b', + 'bdi', + 'bdo', + 'br', + 'cite', + 'code', + 'data', + 'dfn', + 'em', + 'i', + 'kbd', + 'mark', + 'q', + 'rp', + 'rt', + 'ruby', + 's', + 'samp', + 'small', + 'span', + 'strong', + 'sub', + 'sup', + 'time', + 'u', + 'var', + 'wbr', + 'area', + 'audio', + 'img', + 'map', + 'track', + 'video', + 'embed', + 'iframe', + 'object', + 'param', + 'picture', + 'portal', + 'source', + 'svg', + 'math', + 'canvas', + 'noscript', + 'script', + 'del', + 'ins', + 'caption', + 'col', + 'colgroup', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr', + 'button', + 'datalist', + 'fieldset', + 'form', + 'input', + 'label', + 'legend', + 'meter', + 'optgroup', + 'option', + 'output', + 'progress', + 'select', + 'textarea', + 'details', + 'dialog', + 'menu', + 'summary', + 'details', + 'slot', + 'template', + 'acronym', + 'applet', + 'basefont', + 'bgsound', + 'big', + 'blink', + 'center', + 'content', + 'dir', + 'font', + 'frame', + 'frameset', + 'hgroup', + 'image', + 'keygen', + 'marquee', + 'menuitem', + 'nobr', + 'noembed', + 'noframes', + 'plaintext', + 'rb', + 'rtc', + 'shadow', + 'spacer', + 'strike', + 'tt', + 'xmp', + 'a', + 'abbr', + 'acronym', + 'address', + 'applet', + 'area', + 'article', + 'aside', + 'audio', + 'b', + 'base', + 'basefont', + 'bdi', + 'bdo', + 'bgsound', + 'big', + 'blink', + 'blockquote', + 'body', + 'br', + 'button', + 'canvas', + 'caption', + 'center', + 'cite', + 'code', + 'col', + 'colgroup', + 'content', + 'data', + 'datalist', + 'dd', + 'del', + 'details', + 'dfn', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'em', + 'embed', + 'fieldset', + 'figcaption', + 'figure', + 'font', + 'footer', + 'form', + 'frame', + 'frameset', + 'head', + 'header', + 'hgroup', + 'hr', + 'html', + 'i', + 'iframe', + 'image', + 'img', + 'input', + 'ins', + 'kbd', + 'keygen', + 'label', + 'legend', + 'li', + 'link', + 'main', + 'map', + 'mark', + 'marquee', + 'menu', + 'menuitem', + 'meta', + 'meter', + 'nav', + 'nobr', + 'noembed', + 'noframes', + 'noscript', + 'object', + 'ol', + 'optgroup', + 'option', + 'output', + 'p', + 'param', + 'picture', + 'plaintext', + 'portal', + 'pre', + 'progress', + 'q', + 'rb', + 'rp', + 'rt', + 'rtc', + 'ruby', + 's', + 'samp', + 'script', + 'section', + 'select', + 'shadow', + 'slot', + 'small', + 'source', + 'spacer', + 'span', + 'strike', + 'strong', + 'style', + 'sub', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'template', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'time', + 'title', + 'tr', + 'track', + 'tt', + 'u', + 'ul', + 'var', + 'video', + 'wbr', + 'xmp', + 'input', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', +]) + +export { + Aliases, + BooleanAttributes, + ChildProperties, + DelegatedEvents, + DOMElements, + getPropAlias, + Properties, + SVGElements, + SVGNamespace, +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/dom/constants.js b/packages/babel-plugin-transform-solid-jsx/src/dom/constants.js new file mode 100644 index 000000000000..cd4c6e48169c --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/dom/constants.js @@ -0,0 +1,92 @@ +export const InlineElements = [ + 'a', + 'abbr', + 'acronym', + 'b', + 'bdi', + 'bdo', + 'big', + 'br', + 'button', + 'canvas', + 'cite', + 'code', + 'data', + 'datalist', + 'del', + 'dfn', + 'em', + 'embed', + 'i', + 'iframe', + 'img', + 'input', + 'ins', + 'kbd', + 'label', + 'map', + 'mark', + 'meter', + 'noscript', + 'object', + 'output', + 'picture', + 'progress', + 'q', + 'ruby', + 's', + 'samp', + 'script', + 'select', + 'slot', + 'small', + 'span', + 'strong', + 'sub', + 'sup', + 'svg', + 'template', + 'textarea', + 'time', + 'u', + 'tt', + 'var', + 'video' +] + +export const BlockElements = [ + 'address', + 'article', + 'aside', + 'blockquote', + 'dd', + 'details', + 'dialog', + 'div', + 'dl', + 'dt', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'header', + 'hgroup', + 'hr', + 'li', + 'main', + 'menu', + 'nav', + 'ol', + 'p', + 'pre', + 'section', + 'table', + 'ul' +] diff --git a/packages/babel-plugin-transform-solid-jsx/src/dom/element.js b/packages/babel-plugin-transform-solid-jsx/src/dom/element.js new file mode 100644 index 000000000000..60d0e5c681ec --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/dom/element.js @@ -0,0 +1,977 @@ +import * as t from '@babel/types' + +import { + Aliases, + ChildProperties, + DelegatedEvents, + getPropAlias, + Properties, + SVGElements, + SVGNamespace, +} from '../constants' +import { transformNode } from '../shared/transform' +import { + canNativeSpread, + checkLength, + convertJSXIdentifier, + escapeHTML, + filterChildren, + getConfig, + getRendererConfig, + getStaticExpression, + getTagName, + isComponent, + isDynamic, + registerImportMethod, + reservedNameSpaces, + toEventName, + toPropertyName, + transformCondition, + trimWhitespace, + wrappedByText, +} from '../shared/utils' +import VoidElements from '../VoidElements' +import { BlockElements, InlineElements } from './constants' + +const alwaysClose = [ + 'title', + 'style', + 'a', + 'strong', + 'small', + 'b', + 'u', + 'i', + 'em', + 's', + 'code', + 'object', + 'table', + 'button', + 'textarea', + 'select', + 'iframe', + 'script', + 'template', + 'fieldset', +] + +export function transformElement(path, info) { + const tagName = getTagName(path.node) + const config = getConfig(path) + const wrapSVG = info.topLevel && tagName !== 'svg' && SVGElements.has(tagName) + const voidTag = VoidElements.indexOf(tagName) > -1 + const isCustomElement = tagName.indexOf('-') > -1 + const results = { + template: `<${tagName}`, + declarations: [], + exprs: [], + dynamics: [], + postExprs: [], + isSVG: wrapSVG, + hasCustomElement: isCustomElement, + tagName, + renderer: 'dom', + skipTemplate: false, + } + if (config.hydratable && (tagName === 'html' || tagName === 'head' || tagName === 'body')) { + results.skipTemplate = true + if (tagName === 'head' && info.topLevel) { + const createComponent = registerImportMethod(path, 'createComponent', getRendererConfig(path, 'dom').moduleName) + const NoHydration = registerImportMethod(path, 'NoHydration', getRendererConfig(path, 'dom').moduleName) + results.exprs.push( + t.expressionStatement(t.callExpression(createComponent, [NoHydration, t.objectExpression([])])) + ) + return results + } + } + if (wrapSVG) results.template = '' + results.template + if (!info.skipId) results.id = path.scope.generateUidIdentifier('el$') + transformAttributes(path, results) + if (config.contextToCustomElements && (tagName === 'slot' || isCustomElement)) { + contextToCustomElement(path, results) + } + results.template += '>' + if (!voidTag) { + // always close tags can still be skipped if they have no closing parents and are the last element + const toBeClosed = + !info.lastElement || (info.toBeClosed && (!config.omitNestedClosingTags || info.toBeClosed.has(tagName))) + if (toBeClosed) { + results.toBeClosed = new Set(info.toBeClosed || alwaysClose) + results.toBeClosed.add(tagName) + if (InlineElements.includes(tagName)) BlockElements.forEach((i) => results.toBeClosed.add(i)) + } else results.toBeClosed = info.toBeClosed + transformChildren(path, results, config) + if (toBeClosed) results.template += `` + } + if (info.topLevel && config.hydratable && results.hasHydratableEvent) { + const runHydrationEvents = registerImportMethod( + path, + 'runHydrationEvents', + getRendererConfig(path, 'dom').moduleName + ) + results.postExprs.push(t.expressionStatement(t.callExpression(runHydrationEvents, []))) + } + if (wrapSVG) results.template += '' + return results +} + +export function setAttr(path, elem, name, value, { isSVG, dynamic, prevId, isCE, tagName }) { + // pull out namespace + const config = getConfig(path) + let parts, namespace + if ((parts = name.split(':')) && parts[1] && reservedNameSpaces.has(parts[0])) { + name = parts[1] + namespace = parts[0] + } + + // TODO: consider moving to a helper + if (namespace === 'style') { + if (t.isStringLiteral(value)) { + return t.callExpression( + t.memberExpression(t.memberExpression(elem, t.identifier('style')), t.identifier('setProperty')), + [t.stringLiteral(name), value] + ) + } + if (t.isNullLiteral(value) || t.isIdentifier(value, { name: 'undefined' })) { + return t.callExpression( + t.memberExpression(t.memberExpression(elem, t.identifier('style')), t.identifier('removeProperty')), + [t.stringLiteral(name)] + ) + } + return t.conditionalExpression( + t.binaryExpression('!=', value, t.nullLiteral()), + t.callExpression( + t.memberExpression(t.memberExpression(elem, t.identifier('style')), t.identifier('setProperty')), + [t.stringLiteral(name), prevId || value] + ), + t.callExpression( + t.memberExpression(t.memberExpression(elem, t.identifier('style')), t.identifier('removeProperty')), + [t.stringLiteral(name)] + ) + ) + } + + if (namespace === 'class') { + return t.callExpression( + t.memberExpression(t.memberExpression(elem, t.identifier('classList')), t.identifier('toggle')), + [t.stringLiteral(name), dynamic ? value : t.unaryExpression('!', t.unaryExpression('!', value))] + ) + } + + if (name === 'style') { + return t.callExpression( + registerImportMethod(path, 'style', getRendererConfig(path, 'dom').moduleName), + prevId ? [elem, value, prevId] : [elem, value] + ) + } + + if (!isSVG && name === 'class') { + return t.callExpression(registerImportMethod(path, 'className', getRendererConfig(path, 'dom').moduleName), [ + elem, + value, + ]) + } + + if (name === 'classList') { + return t.callExpression( + registerImportMethod(path, 'classList', getRendererConfig(path, 'dom').moduleName), + prevId ? [elem, value, prevId] : [elem, value] + ) + } + + if (dynamic && name === 'textContent') { + if (config.hydratable) { + return t.callExpression(registerImportMethod(path, 'setProperty'), [elem, t.stringLiteral('data'), value]) + } + return t.assignmentExpression('=', t.memberExpression(elem, t.identifier('data')), value) + } + + const isChildProp = ChildProperties.has(name) + const isProp = Properties.has(name) + const alias = getPropAlias(name, tagName.toUpperCase()) + if (namespace !== 'attr' && (isChildProp || (!isSVG && isProp) || isCE || namespace === 'prop')) { + if (isCE && !isChildProp && !isProp && namespace !== 'prop') name = toPropertyName(name) + if (config.hydratable && namespace !== 'prop') { + return t.callExpression(registerImportMethod(path, 'setProperty'), [elem, t.stringLiteral(name), value]) + } + return t.assignmentExpression('=', t.memberExpression(elem, t.identifier(alias || name)), value) + } + + const isNameSpaced = name.indexOf(':') > -1 + name = Aliases[name] || name + !isSVG && (name = name.toLowerCase()) + const ns = isNameSpaced && SVGNamespace[name.split(':')[0]] + if (ns) { + return t.callExpression(registerImportMethod(path, 'setAttributeNS', getRendererConfig(path, 'dom').moduleName), [ + elem, + t.stringLiteral(ns), + t.stringLiteral(name), + value, + ]) + } else { + return t.callExpression(registerImportMethod(path, 'setAttribute', getRendererConfig(path, 'dom').moduleName), [ + elem, + t.stringLiteral(name), + value, + ]) + } +} + +function detectResolvableEventHandler(attribute, handler) { + while (t.isIdentifier(handler)) { + const lookup = attribute.scope.getBinding(handler.name) + if (lookup) { + if (t.isVariableDeclarator(lookup.path.node)) { + handler = lookup.path.node.init + } else if (t.isFunctionDeclaration(lookup.path.node)) { + return true + } else return false + } else return false + } + return t.isFunction(handler) +} + +function transformAttributes(path, results) { + const elem = results.id + let hasHydratableEvent = false + let children + let spreadExpr + let attributes = path.get('openingElement').get('attributes') + const tagName = getTagName(path.node) + const isSVG = SVGElements.has(tagName) + const isCE = tagName.includes('-') + const hasChildren = path.node.children.length > 0 + const config = getConfig(path) + + // preprocess spreads + if (attributes.some((attribute) => t.isJSXSpreadAttribute(attribute.node))) { + [attributes, spreadExpr] = processSpreads(path, attributes, { + elem, + isSVG, + hasChildren, + wrapConditionals: config.wrapConditionals, + }) + path.get('openingElement').set( + 'attributes', + attributes.map((a) => a.node) + ) + // NOTE: can't be checked at compile time so add to compiled output + hasHydratableEvent = true + } + + // preprocess styles + const styleAttribute = path + .get('openingElement') + .get('attributes') + .find( + (a) => + a.node.name && + a.node.name.name === 'style' && + t.isJSXExpressionContainer(a.node.value) && + t.isObjectExpression(a.node.value.expression) && + !a.node.value.expression.properties.some((p) => t.isSpreadElement(p)) + ) + if (styleAttribute) { + let i = 0 + const leading = styleAttribute.node.value.expression.leadingComments + styleAttribute.node.value.expression.properties.slice().forEach((p, index) => { + if (!p.computed) { + if (leading) p.value.leadingComments = leading + path + .get('openingElement') + .node.attributes.splice( + styleAttribute.key + ++i, + 0, + t.jsxAttribute( + t.jsxNamespacedName( + t.jsxIdentifier('style'), + t.jsxIdentifier(t.isIdentifier(p.key) ? p.key.name : p.key.value) + ), + t.jsxExpressionContainer(p.value) + ) + ) + styleAttribute.node.value.expression.properties.splice(index - i - 1, 1) + } + }) + if (!styleAttribute.node.value.expression.properties.length) { + path.get('openingElement').node.attributes.splice(styleAttribute.key, 1) + } + } + + // preprocess classList + attributes = path.get('openingElement').get('attributes') + const classListAttribute = attributes.find( + (a) => + a.node.name && + a.node.name.name === 'classList' && + t.isJSXExpressionContainer(a.node.value) && + t.isObjectExpression(a.node.value.expression) && + !a.node.value.expression.properties.some( + (p) => + t.isSpreadElement(p) || + p.computed || + (t.isStringLiteral(p.key) && (p.key.value.includes(' ') || p.key.value.includes(':'))) + ) + ) + if (classListAttribute) { + let i = 0 + const leading = classListAttribute.node.value.expression.leadingComments + const classListProperties = classListAttribute.get('value').get('expression').get('properties') + classListProperties.slice().forEach((propPath, index) => { + const p = propPath.node + const { confident, value: computed } = propPath.get('value').evaluate() + if (leading) p.value.leadingComments = leading + if (!confident) { + path + .get('openingElement') + .node.attributes.splice( + classListAttribute.key + ++i, + 0, + t.jsxAttribute( + t.jsxNamespacedName( + t.jsxIdentifier('class'), + t.jsxIdentifier(t.isIdentifier(p.key) ? p.key.name : p.key.value) + ), + t.jsxExpressionContainer(p.value) + ) + ) + } else if (computed) { + path + .get('openingElement') + .node.attributes.splice( + classListAttribute.key + ++i, + 0, + t.jsxAttribute(t.jsxIdentifier('class'), t.stringLiteral(t.isIdentifier(p.key) ? p.key.name : p.key.value)) + ) + } + classListProperties.splice(index - i - 1, 1) + }) + if (!classListProperties.length) path.get('openingElement').node.attributes.splice(classListAttribute.key, 1) + } + + // combine class properties + attributes = path.get('openingElement').get('attributes') + const classAttributes = attributes.filter( + (a) => a.node.name && (a.node.name.name === 'class' || a.node.name.name === 'className') + ) + if (classAttributes.length > 1) { + const first = classAttributes[0].node + const values = [] + const quasis = [t.templateElement({ raw: '' })] + for (let i = 0; i < classAttributes.length; i++) { + const attr = classAttributes[i].node + const isLast = i === classAttributes.length - 1 + if (!t.isJSXExpressionContainer(attr.value)) { + const prev = quasis.pop() + quasis.push( + t.templateElement({ + raw: (prev ? prev.value.raw : '') + `${attr.value.value}` + (isLast ? '' : ' '), + }) + ) + } else { + values.push(t.logicalExpression('||', attr.value.expression, t.stringLiteral(''))) + quasis.push(t.templateElement({ raw: isLast ? '' : ' ' })) + } + i && attributes.splice(attributes.indexOf(classAttributes[i]), 1) + } + if (values.length) first.value = t.jsxExpressionContainer(t.templateLiteral(quasis, values)) + else first.value = t.stringLiteral(quasis[0].value.raw) + } + path.get('openingElement').set( + 'attributes', + attributes.map((a) => a.node) + ) + + let needsSpacing = true + + path + .get('openingElement') + .get('attributes') + .forEach((attribute) => { + const node = attribute.node + let value = node.value + let key = t.isJSXNamespacedName(node.name) ? `${node.name.namespace.name}:${node.name.name.name}` : node.name.name + const reservedNameSpace = t.isJSXNamespacedName(node.name) && reservedNameSpaces.has(node.name.namespace.name) + if (t.isJSXExpressionContainer(value) && !key.startsWith('use:')) { + const evaluated = attribute.get('value').get('expression').evaluate().value + let type + if (evaluated !== undefined && ((type = typeof evaluated) === 'string' || type === 'number')) { + if (type === 'number' && (Properties.has(key) || key.startsWith('prop:'))) { + value = t.jsxExpressionContainer(t.numericLiteral(evaluated)) + } else value = t.stringLiteral(String(evaluated)) + } + } + if (t.isJSXNamespacedName(node.name) && reservedNameSpace && !t.isJSXExpressionContainer(value)) { + node.value = value = t.jsxExpressionContainer(value || t.jsxEmptyExpression()) + } + if ( + t.isJSXExpressionContainer(value) && + (reservedNameSpace || !(t.isStringLiteral(value.expression) || t.isNumericLiteral(value.expression))) + ) { + if (key === 'ref') { + // Normalize expressions for non-null and type-as + while (t.isTSNonNullExpression(value.expression) || t.isTSAsExpression(value.expression)) { + value.expression = value.expression.expression + } + let binding + const isFunction = + t.isIdentifier(value.expression) && + (binding = path.scope.getBinding(value.expression.name)) && + binding.kind === 'const' + if (!isFunction && t.isLVal(value.expression)) { + const refIdentifier = path.scope.generateUidIdentifier('_ref$') + results.exprs.unshift( + t.variableDeclaration('var', [t.variableDeclarator(refIdentifier, value.expression)]), + t.expressionStatement( + t.conditionalExpression( + t.binaryExpression('===', t.unaryExpression('typeof', refIdentifier), t.stringLiteral('function')), + t.callExpression(registerImportMethod(path, 'use', getRendererConfig(path, 'dom').moduleName), [ + refIdentifier, + elem, + ]), + t.assignmentExpression('=', value.expression, elem) + ) + ) + ) + } else if (isFunction || t.isFunction(value.expression)) { + results.exprs.unshift( + t.expressionStatement( + t.callExpression(registerImportMethod(path, 'use', getRendererConfig(path, 'dom').moduleName), [ + value.expression, + elem, + ]) + ) + ) + } else if (t.isCallExpression(value.expression)) { + const refIdentifier = path.scope.generateUidIdentifier('_ref$') + results.exprs.unshift( + t.variableDeclaration('var', [t.variableDeclarator(refIdentifier, value.expression)]), + t.expressionStatement( + t.logicalExpression( + '&&', + t.binaryExpression('===', t.unaryExpression('typeof', refIdentifier), t.stringLiteral('function')), + t.callExpression(registerImportMethod(path, 'use', getRendererConfig(path, 'dom').moduleName), [ + refIdentifier, + elem, + ]) + ) + ) + ) + } + } else if (key.startsWith('use:')) { + // Some trick to treat JSXIdentifier as Identifier + node.name.name.type = 'Identifier' + results.exprs.unshift( + t.expressionStatement( + t.callExpression(registerImportMethod(path, 'use', getRendererConfig(path, 'dom').moduleName), [ + node.name.name, + elem, + t.arrowFunctionExpression( + [], + t.isJSXEmptyExpression(value.expression) ? t.booleanLiteral(true) : value.expression + ), + ]) + ) + ) + } else if (key === 'children') { + children = value + } else if (key.startsWith('on')) { + const ev = toEventName(key) + if (key.startsWith('on:') || key.startsWith('oncapture:')) { + const listenerOptions = [t.stringLiteral(key.split(':')[1]), value.expression] + results.exprs.push( + t.expressionStatement( + t.callExpression( + t.memberExpression(elem, t.identifier('addEventListener')), + key.startsWith('oncapture:') ? listenerOptions.concat(t.booleanLiteral(true)) : listenerOptions + ) + ) + ) + } else if (config.delegateEvents && (DelegatedEvents.has(ev) || config.delegatedEvents.indexOf(ev) !== -1)) { + // can only hydrate delegated events + hasHydratableEvent = true + const events = + attribute.scope.getProgramParent().data.events || + (attribute.scope.getProgramParent().data.events = new Set()) + events.add(ev) + let handler = value.expression + const resolveable = detectResolvableEventHandler(attribute, handler) + if (t.isArrayExpression(handler)) { + if (handler.elements.length > 1) { + results.exprs.unshift( + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression(elem, t.identifier(`$$${ev}Data`)), + handler.elements[1] + ) + ) + ) + } + handler = handler.elements[0] + results.exprs.unshift( + t.expressionStatement( + t.assignmentExpression('=', t.memberExpression(elem, t.identifier(`$$${ev}`)), handler) + ) + ) + } else if (t.isFunction(handler) || resolveable) { + results.exprs.unshift( + t.expressionStatement( + t.assignmentExpression('=', t.memberExpression(elem, t.identifier(`$$${ev}`)), handler) + ) + ) + } else { + results.exprs.unshift( + t.expressionStatement( + t.callExpression( + registerImportMethod(path, 'addEventListener', getRendererConfig(path, 'dom').moduleName), + [elem, t.stringLiteral(ev), handler, t.booleanLiteral(true)] + ) + ) + ) + } + } else { + let handler = value.expression + const resolveable = detectResolvableEventHandler(attribute, handler) + if (t.isArrayExpression(handler)) { + if (handler.elements.length > 1) { + handler = t.arrowFunctionExpression( + [t.identifier('e')], + t.callExpression(handler.elements[0], [handler.elements[1], t.identifier('e')]) + ) + } else handler = handler.elements[0] + results.exprs.unshift( + t.expressionStatement( + t.callExpression(t.memberExpression(elem, t.identifier('addEventListener')), [ + t.stringLiteral(ev), + handler, + ]) + ) + ) + } else if (t.isFunction(handler) || resolveable) { + results.exprs.unshift( + t.expressionStatement( + t.callExpression(t.memberExpression(elem, t.identifier('addEventListener')), [ + t.stringLiteral(ev), + handler, + ]) + ) + ) + } else { + results.exprs.unshift( + t.expressionStatement( + t.callExpression( + registerImportMethod(path, 'addEventListener', getRendererConfig(path, 'dom').moduleName), + [elem, t.stringLiteral(ev), handler] + ) + ) + ) + } + } + } else if ( + config.effectWrapper && + (isDynamic(attribute.get('value').get('expression'), { + checkMember: true, + }) || + ((key === 'classList' || key === 'style') && + !attribute.get('value').get('expression').evaluate().confident)) + ) { + let nextElem = elem + if (key === 'value' || key === 'checked') { + const effectWrapperId = registerImportMethod(path, config.effectWrapper) + results.postExprs.push( + t.expressionStatement( + t.callExpression(effectWrapperId, [ + t.arrowFunctionExpression( + [], + setAttr(path, elem, key, value.expression, { + tagName, + isSVG, + isCE, + }) + ), + ]) + ) + ) + return + } + if (key === 'textContent') { + nextElem = attribute.scope.generateUidIdentifier('el$') + children = t.jsxText(' ') + children.extra = { raw: ' ', rawValue: ' ' } + results.declarations.push( + t.variableDeclarator(nextElem, t.memberExpression(elem, t.identifier('firstChild'))) + ) + } + results.dynamics.push({ + elem: nextElem, + key, + value: value.expression, + isSVG, + isCE, + tagName, + }) + } else { + results.exprs.push( + t.expressionStatement(setAttr(attribute, elem, key, value.expression, { isSVG, isCE, tagName })) + ) + } + } else { + if (config.hydratable && key === '$ServerOnly') { + results.skipTemplate = true + return + } + if (t.isJSXExpressionContainer(value)) value = value.expression + key = Aliases[key] || key + if (value && ChildProperties.has(key)) { + results.exprs.push(t.expressionStatement(setAttr(attribute, elem, key, value, { isSVG, isCE, tagName }))) + } else { + !isSVG && (key = key.toLowerCase()) + results.template += `${needsSpacing ? ' ' : ''}${key}` + if (!value) { + needsSpacing = true + return + } + + let text = value.value + let needsQuoting = false + + if (key === 'style' || key === 'class') { + text = trimWhitespace(text) + if (key === 'style') { + text = text.replace(/; /g, ';').replace(/: /g, ':') + } + } + + if (!text.length) { + needsSpacing = false + results.template += `=""` + return + } + + for (let i = 0, len = text.length; i < len; i++) { + const char = text[i] + + if ( + char === "'" || + char === '"' || + char === ' ' || + char === '\t' || + char === '\n' || + char === '\r' || + char === '`' || + char === '=' || + char === '<' || + char === '>' + ) { + needsQuoting = true + } + } + + if (needsQuoting) { + needsSpacing = false + results.template += `="${escapeHTML(text, true)}"` + } else { + needsSpacing = true + results.template += `=${escapeHTML(text, true)}` + } + } + } + }) + if (!hasChildren && children) { + path.node.children.push(children) + } + if (spreadExpr) results.exprs.push(spreadExpr) + + results.hasHydratableEvent = results.hasHydratableEvent || hasHydratableEvent +} + +function findLastElement(children, hydratable) { + let lastElement = -1 + let tagName + for (let i = children.length - 1; i >= 0; i--) { + const node = children[i].node + if ( + hydratable || + t.isJSXText(node) || + getStaticExpression(children[i]) !== false || + (t.isJSXElement(node) && (tagName = getTagName(node)) && !isComponent(tagName)) + ) { + lastElement = i + break + } + } + return lastElement +} + +function transformChildren(path, results, config) { + let tempPath = results.id && results.id.name + const tagName = getTagName(path.node) + let nextPlaceholder + let i = 0 + const filteredChildren = filterChildren(path.get('children')) + const lastElement = findLastElement(filteredChildren, config.hydratable) + const childNodes = filteredChildren.reduce((memo, child, index) => { + if (child.isJSXFragment()) { + throw new Error(`Fragments can only be used top level in JSX. Not used under a <${tagName}>.`) + } + const transformed = transformNode(child, { + toBeClosed: results.toBeClosed, + lastElement: index === lastElement, + skipId: !results.id || !detectExpressions(filteredChildren, index, config), + }) + if (!transformed) return memo + const i = memo.length + if (transformed.text && i && memo[i - 1].text) { + memo[i - 1].template += transformed.template + } else memo.push(transformed) + return memo + }, []) + + childNodes.forEach((child, index) => { + if (!child) return + if (child.tagName && child.renderer !== 'dom') { + throw new Error(`<${child.tagName}> is not supported in <${tagName}>. + Wrap the usage with a component that would render this element, eg. Canvas`) + } + + results.template += child.template + if (child.id) { + if (child.tagName === 'head') { + if (config.hydratable) { + const createComponent = registerImportMethod( + path, + 'createComponent', + getRendererConfig(path, 'dom').moduleName + ) + const NoHydration = registerImportMethod(path, 'NoHydration', getRendererConfig(path, 'dom').moduleName) + results.exprs.push( + t.expressionStatement(t.callExpression(createComponent, [NoHydration, t.objectExpression([])])) + ) + } + return + } + + let getNextMatch + if (config.hydratable && tagName === 'html') { + getNextMatch = registerImportMethod(path, 'getNextMatch', getRendererConfig(path, 'dom').moduleName) + } + const walk = t.memberExpression(t.identifier(tempPath), t.identifier(i === 0 ? 'firstChild' : 'nextSibling')) + results.declarations.push( + t.variableDeclarator( + child.id, + config.hydratable && tagName === 'html' + ? t.callExpression(getNextMatch, [walk, t.stringLiteral(child.tagName)]) + : walk + ) + ) + results.declarations.push(...child.declarations) + results.exprs.push(...child.exprs) + results.dynamics.push(...child.dynamics) + results.postExprs.push(...child.postExprs) + results.hasHydratableEvent = results.hasHydratableEvent || child.hasHydratableEvent + results.hasCustomElement = results.hasCustomElement || child.hasCustomElement + tempPath = child.id.name + nextPlaceholder = null + i++ + } else if (child.exprs.length) { + const insert = registerImportMethod(path, 'insert', getRendererConfig(path, 'dom').moduleName) + const multi = checkLength(filteredChildren) + const markers = config.hydratable && multi + // boxed by textNodes + if (markers || wrappedByText(childNodes, index)) { + let exprId, contentId + if (markers) tempPath = createPlaceholder(path, results, tempPath, i++, '$')[0].name + if (nextPlaceholder) { + exprId = nextPlaceholder + } else { + [exprId, contentId] = createPlaceholder(path, results, tempPath, i++, markers ? '/' : '') + } + if (!markers) nextPlaceholder = exprId + results.exprs.push( + t.expressionStatement( + t.callExpression( + insert, + contentId ? [results.id, child.exprs[0], exprId, contentId] : [results.id, child.exprs[0], exprId] + ) + ) + ) + tempPath = exprId.name + } else if (multi) { + results.exprs.push( + t.expressionStatement( + t.callExpression(insert, [results.id, child.exprs[0], nextChild(childNodes, index) || t.nullLiteral()]) + ) + ) + } else { + results.exprs.push(t.expressionStatement(t.callExpression(insert, [results.id, child.exprs[0]]))) + } + } else nextPlaceholder = null + }) +} + +function createPlaceholder(path, results, tempPath, i, char) { + const exprId = path.scope.generateUidIdentifier('el$') + const config = getConfig(path) + let contentId + results.template += `` + if (config.hydratable && char === '/') { + contentId = path.scope.generateUidIdentifier('co$') + results.declarations.push( + t.variableDeclarator( + t.arrayPattern([exprId, contentId]), + t.callExpression(registerImportMethod(path, 'getNextMarker', getRendererConfig(path, 'dom').moduleName), [ + t.memberExpression(t.identifier(tempPath), t.identifier('nextSibling')), + ]) + ) + ) + } else { + results.declarations.push( + t.variableDeclarator( + exprId, + t.memberExpression(t.identifier(tempPath), t.identifier(i === 0 ? 'firstChild' : 'nextSibling')) + ) + ) + } + return [exprId, contentId] +} + +function nextChild(children, index) { + return children[index + 1] && (children[index + 1].id || nextChild(children, index + 1)) +} + +// reduce unnecessary refs +function detectExpressions(children, index, config) { + if (children[index - 1]) { + const node = children[index - 1].node + if ( + t.isJSXExpressionContainer(node) && + !t.isJSXEmptyExpression(node.expression) && + getStaticExpression(children[index - 1]) === false + ) { + return true + } + let tagName + if (t.isJSXElement(node) && (tagName = getTagName(node)) && isComponent(tagName)) return true + } + for (let i = index; i < children.length; i++) { + const child = children[i].node + if (t.isJSXExpressionContainer(child)) { + if (!t.isJSXEmptyExpression(child.expression) && getStaticExpression(children[i]) === false) return true + } else if (t.isJSXElement(child)) { + const tagName = getTagName(child) + if (isComponent(tagName)) return true + if (config.contextToCustomElements && (tagName === 'slot' || tagName.indexOf('-') > -1)) return true + if ( + child.openingElement.attributes.some( + (attr) => + t.isJSXSpreadAttribute(attr) || + ['textContent', 'innerHTML', 'innerText'].includes(attr.name.name) || + (attr.name.namespace && attr.name.namespace.name === 'use') || + (t.isJSXExpressionContainer(attr.value) && + !(t.isStringLiteral(attr.value.expression) || t.isNumericLiteral(attr.value.expression))) + ) + ) { + return true + } + const nextChildren = filterChildren(children[i].get('children')) + if (nextChildren.length) if (detectExpressions(nextChildren, 0, config)) return true + } + } +} + +function contextToCustomElement(path, results) { + results.exprs.push( + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression(results.id, t.identifier('_$owner')), + t.callExpression(registerImportMethod(path, 'getOwner', getRendererConfig(path, 'dom').moduleName), []) + ) + ) + ) +} + +function processSpreads(path, attributes, { elem, isSVG, hasChildren, wrapConditionals }) { + // TODO: skip but collect the names of any properties after the last spread to not overwrite them + const filteredAttributes = [] + const spreadArgs = [] + let runningObject = [] + let dynamicSpread = false + let firstSpread = false + attributes.forEach((attribute) => { + const node = attribute.node + const key = + !t.isJSXSpreadAttribute(node) && + (t.isJSXNamespacedName(node.name) ? `${node.name.namespace.name}:${node.name.name.name}` : node.name.name) + if (t.isJSXSpreadAttribute(node)) { + firstSpread = true + if (runningObject.length) { + spreadArgs.push(t.objectExpression(runningObject)) + runningObject = [] + } + spreadArgs.push( + isDynamic(attribute.get('argument'), { + checkMember: true, + }) && (dynamicSpread = true) + ? t.isCallExpression(node.argument) && + !node.argument.arguments.length && + !t.isCallExpression(node.argument.callee) && + !t.isMemberExpression(node.argument.callee) + ? node.argument.callee + : t.arrowFunctionExpression([], node.argument) + : node.argument + ) + } else if ( + (firstSpread || + (t.isJSXExpressionContainer(node.value) && + isDynamic(attribute.get('value').get('expression'), { checkMember: true }))) && + canNativeSpread(key, { checkNameSpaces: true }) + ) { + const isContainer = t.isJSXExpressionContainer(node.value) + const dynamic = isContainer && isDynamic(attribute.get('value').get('expression'), { checkMember: true }) + if (dynamic) { + const id = convertJSXIdentifier(node.name) + const expr = + wrapConditionals && + (t.isLogicalExpression(node.value.expression) || t.isConditionalExpression(node.value.expression)) + ? transformCondition(attribute.get('value').get('expression'), true) + : t.arrowFunctionExpression([], node.value.expression) + runningObject.push( + t.objectMethod('get', id, [], t.blockStatement([t.returnStatement(expr.body)]), !t.isValidIdentifier(key)) + ) + } else { + runningObject.push( + t.objectProperty( + t.stringLiteral(key), + isContainer + ? node.value.expression + : node.value || (Properties.has(key) ? t.booleanLiteral(true) : t.stringLiteral('')) + ) + ) + } + } else filteredAttributes.push(attribute) + }) + + if (runningObject.length) { + spreadArgs.push(t.objectExpression(runningObject)) + } + + const props = + spreadArgs.length === 1 && !dynamicSpread + ? spreadArgs[0] + : t.callExpression(registerImportMethod(path, 'mergeProps'), spreadArgs) + + return [ + filteredAttributes, + t.expressionStatement( + t.callExpression(registerImportMethod(path, 'spread', getRendererConfig(path, 'dom').moduleName), [ + elem, + props, + t.booleanLiteral(isSVG), + t.booleanLiteral(hasChildren), + ]) + ), + ] +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/dom/template.js b/packages/babel-plugin-transform-solid-jsx/src/dom/template.js new file mode 100644 index 000000000000..162ea9175c5b --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/dom/template.js @@ -0,0 +1,211 @@ +import * as t from '@babel/types' + +import { escapeStringForTemplate, getConfig, getNumberedId, getRendererConfig, registerImportMethod } from '../shared/utils' +import { setAttr } from './element' + +export function createTemplate(path, result, wrap) { + const config = getConfig(path) + if (result.id) { + registerTemplate(path, result) + if ( + !(result.exprs.length || result.dynamics.length || result.postExprs.length) && + result.decl.declarations.length === 1 + ) { + return result.decl.declarations[0].init + } else { + return t.callExpression( + t.arrowFunctionExpression( + [], + t.blockStatement([ + result.decl, + ...result.exprs.concat( + wrapDynamics(path, result.dynamics) || [], + result.postExprs || [] + ), + t.returnStatement(result.id) + ]) + ), + [] + ) + } + } + if (wrap && result.dynamic && config.memoWrapper) { + return t.callExpression(registerImportMethod(path, config.memoWrapper), [result.exprs[0]]) + } + return result.exprs[0] +} + +export function appendTemplates(path, templates) { + const declarators = templates.map(template => { + const tmpl = { + cooked: template.template, + raw: escapeStringForTemplate(template.template) + } + return t.variableDeclarator( + template.id, + t.addComment( + t.callExpression( + registerImportMethod(path, 'template', getRendererConfig(path, 'dom').moduleName), + [t.templateLiteral([t.templateElement(tmpl, true)], [])].concat( + template.isSVG || template.isCE + ? [t.booleanLiteral(template.isCE), t.booleanLiteral(template.isSVG)] + : [] + ) + ), + 'leading', + '#__PURE__' + ) + ) + }) + path.node.body.unshift(t.variableDeclaration('var', declarators)) +} + +function registerTemplate(path, results) { + const { hydratable } = getConfig(path) + let decl + if (results.template.length) { + let templateDef, templateId + if (!results.skipTemplate) { + const templates = + path.scope.getProgramParent().data.templates || + (path.scope.getProgramParent().data.templates = []) + if ((templateDef = templates.find(t => t.template === results.template))) { + templateId = templateDef.id + } else { + templateId = path.scope.generateUidIdentifier('tmpl$') + templates.push({ + id: templateId, + template: results.template, + isSVG: results.isSVG, + isCE: results.hasCustomElement, + renderer: 'dom' + }) + } + } + decl = t.variableDeclarator( + results.id, + hydratable + ? t.callExpression( + registerImportMethod(path, 'getNextElement', getRendererConfig(path, 'dom').moduleName), + templateId ? [templateId] : [] + ) + : t.callExpression(templateId, []) + ) + } + results.declarations.unshift(decl) + results.decl = t.variableDeclaration('var', results.declarations) +} + +function wrapDynamics(path, dynamics) { + if (!dynamics.length) return + const config = getConfig(path) + + const effectWrapperId = registerImportMethod(path, config.effectWrapper) + + if (dynamics.length === 1) { + const prevValue = + dynamics[0].key === 'classList' || dynamics[0].key === 'style' + ? t.identifier('_$p') + : undefined + if ( + dynamics[0].key.startsWith('class:') && + !t.isBooleanLiteral(dynamics[0].value) && + !t.isUnaryExpression(dynamics[0].value) + ) { + dynamics[0].value = t.unaryExpression('!', t.unaryExpression('!', dynamics[0].value)) + } + + return t.expressionStatement( + t.callExpression(effectWrapperId, [ + t.arrowFunctionExpression( + prevValue ? [prevValue] : [], + setAttr(path, dynamics[0].elem, dynamics[0].key, dynamics[0].value, { + isSVG: dynamics[0].isSVG, + isCE: dynamics[0].isCE, + tagName: dynamics[0].tagName, + dynamic: true, + prevId: prevValue + }) + ) + ]) + ) + } + + const prevId = t.identifier('_p$') + + /** @type {t.VariableDeclarator[]} */ + const declarations = [] + /** @type {t.ExpressionStatement[]} */ + const statements = [] + /** @type {t.Identifier[]} */ + const properties = [] + + dynamics.forEach(({ elem, key, value, isSVG, isCE, tagName }, index) => { + const varIdent = path.scope.generateUidIdentifier('v$') + + const propIdent = t.identifier(getNumberedId(index)) + const propMember = t.memberExpression(prevId, propIdent) + + if ( + key.startsWith('class:') && + !t.isBooleanLiteral(value) && + !t.isUnaryExpression(value) + ) { + value = t.unaryExpression('!', t.unaryExpression('!', value)) + } + + properties.push(propIdent) + declarations.push(t.variableDeclarator(varIdent, value)) + + if (key === 'classList' || key === 'style') { + statements.push( + t.expressionStatement( + t.assignmentExpression( + '=', + propMember, + setAttr(path, elem, key, varIdent, { + isSVG, + isCE, + tagName, + dynamic: true, + prevId: propMember, + }), + ), + ), + ) + } else { + const prev = key.startsWith('style:') ? varIdent : undefined + statements.push( + t.expressionStatement( + t.logicalExpression( + '&&', + t.binaryExpression('!==', varIdent, propMember), + setAttr( + path, + elem, + key, + t.assignmentExpression('=', propMember, varIdent), + { isSVG, isCE, tagName, dynamic: true, prevId: prev }, + ), + ), + ), + ) + } + }) + + return t.expressionStatement( + t.callExpression(effectWrapperId, [ + t.arrowFunctionExpression( + [prevId], + t.blockStatement([ + t.variableDeclaration('var', declarations), + ...statements, + t.returnStatement(prevId), + ]), + ), + t.objectExpression( + properties.map((id) => t.objectProperty(id, t.identifier('undefined'))), + ), + ]), + ) +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/index.js b/packages/babel-plugin-transform-solid-jsx/src/index.js new file mode 100644 index 000000000000..afeb0ed2199b --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/index.js @@ -0,0 +1,20 @@ +import SyntaxJSX from '@babel/plugin-syntax-jsx' + +import postprocess from './shared/postprocess' +import preprocess from './shared/preprocess' +import { transformJSX } from './shared/transform' + +export default () => { + return { + name: 'JSX DOM Expressions', + inherits: SyntaxJSX.default, + visitor: { + JSXElement: transformJSX, + JSXFragment: transformJSX, + Program: { + enter: preprocess, + exit: postprocess + } + } + } +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/shared/component.js b/packages/babel-plugin-transform-solid-jsx/src/shared/component.js new file mode 100644 index 000000000000..8a093af65007 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/shared/component.js @@ -0,0 +1,262 @@ +import * as t from '@babel/types' +import { decode } from 'html-entities' + +import { getCreateTemplate, transformNode } from './transform' +import { + convertJSXIdentifier, + filterChildren, + getConfig, + isDynamic, + registerImportMethod, + transformCondition, + trimWhitespace, +} from './utils' + +function convertComponentIdentifier(node) { + if (t.isJSXIdentifier(node)) { + if (node.name === 'this') return t.thisExpression() + if (t.isValidIdentifier(node.name)) node.type = 'Identifier' + else return t.stringLiteral(node.name) + } else if (t.isJSXMemberExpression(node)) { + const prop = convertComponentIdentifier(node.property) + const computed = t.isStringLiteral(prop) + return t.memberExpression(convertComponentIdentifier(node.object), prop, computed) + } + + return node +} + +export default function transformComponent(path) { + let exprs = [] + const config = getConfig(path) + const tagId = convertComponentIdentifier(path.node.openingElement.name) + let props = [] + let runningObject = [] + let dynamicSpread = false + const hasChildren = path.node.children.length > 0 + + if (config.builtIns.indexOf(tagId.name) > -1 && !path.scope.hasBinding(tagId.name)) { + const newTagId = registerImportMethod(path, tagId.name) + tagId.name = newTagId.name + } + + path + .get('openingElement') + .get('attributes') + .forEach((attribute) => { + const node = attribute.node + if (t.isJSXSpreadAttribute(node)) { + if (runningObject.length) { + props.push(t.objectExpression(runningObject)) + runningObject = [] + } + props.push( + isDynamic(attribute.get('argument'), { + checkMember: true, + }) && (dynamicSpread = true) + ? t.isCallExpression(node.argument) && + !node.argument.arguments.length && + !t.isCallExpression(node.argument.callee) && + !t.isMemberExpression(node.argument.callee) + ? node.argument.callee + : t.arrowFunctionExpression([], node.argument) + : node.argument + ) + } else { + // handle weird babel bug around HTML entities + const value = + (t.isStringLiteral(node.value) ? t.stringLiteral(node.value.value) : node.value) || t.booleanLiteral(true) + const id = convertJSXIdentifier(node.name) + const key = id.name + if (hasChildren && key === 'children') return + if (t.isJSXExpressionContainer(value)) { + if (key === 'ref') { + if (config.generate === 'ssr') return + // Normalize expressions for non-null and type-as + while ( + t.isTSNonNullExpression(value.expression) || + t.isTSAsExpression(value.expression) || + t.isTSSatisfiesExpression(value.expression) + ) { + value.expression = value.expression.expression + } + let binding + const isFunction = + t.isIdentifier(value.expression) && + (binding = path.scope.getBinding(value.expression.name)) && + binding.kind === 'const' + if (!isFunction && t.isLVal(value.expression)) { + const refIdentifier = path.scope.generateUidIdentifier('_ref$') + runningObject.push( + t.objectMethod( + 'method', + t.identifier('ref'), + [t.identifier('r$')], + t.blockStatement([ + t.variableDeclaration('var', [t.variableDeclarator(refIdentifier, value.expression)]), + t.expressionStatement( + t.conditionalExpression( + t.binaryExpression( + '===', + t.unaryExpression('typeof', refIdentifier), + t.stringLiteral('function') + ), + t.callExpression(refIdentifier, [t.identifier('r$')]), + t.assignmentExpression('=', value.expression, t.identifier('r$')) + ) + ), + ]) + ) + ) + } else if (isFunction || t.isFunction(value.expression)) { + runningObject.push(t.objectProperty(t.identifier('ref'), value.expression)) + } else if (t.isCallExpression(value.expression)) { + const refIdentifier = path.scope.generateUidIdentifier('_ref$') + runningObject.push( + t.objectMethod( + 'method', + t.identifier('ref'), + [t.identifier('r$')], + t.blockStatement([ + t.variableDeclaration('var', [t.variableDeclarator(refIdentifier, value.expression)]), + t.expressionStatement( + t.logicalExpression( + '&&', + t.binaryExpression( + '===', + t.unaryExpression('typeof', refIdentifier), + t.stringLiteral('function') + ), + t.callExpression(refIdentifier, [t.identifier('r$')]) + ) + ), + ]) + ) + ) + } + } else if ( + isDynamic(attribute.get('value').get('expression'), { + checkMember: true, + checkTags: true, + }) + ) { + if ( + config.wrapConditionals && + config.generate !== 'ssr' && + (t.isLogicalExpression(value.expression) || t.isConditionalExpression(value.expression)) + ) { + const expr = transformCondition(attribute.get('value').get('expression'), true) + + runningObject.push( + t.objectMethod( + 'get', + id, + [], + t.blockStatement([t.returnStatement(expr.body)]), + !t.isValidIdentifier(key) + ) + ) + } else if (t.isCallExpression(value.expression) && t.isArrowFunctionExpression(value.expression.callee)) { + const callee = value.expression.callee + const body = t.isBlockStatement(callee.body) + ? callee.body + : t.blockStatement([t.returnStatement(callee.body)]) + + runningObject.push(t.objectMethod('get', id, [], body, !t.isValidIdentifier(key))) + } else { + runningObject.push( + t.objectMethod( + 'get', + id, + [], + t.blockStatement([t.returnStatement(value.expression)]), + !t.isValidIdentifier(key) + ) + ) + } + } else runningObject.push(t.objectProperty(id, value.expression)) + } else runningObject.push(t.objectProperty(id, value)) + } + }) + + const childResult = transformComponentChildren(path.get('children'), config) + if (childResult && childResult[0]) { + if (childResult[1]) { + const body = + t.isCallExpression(childResult[0]) && t.isFunction(childResult[0].arguments[0]) + ? childResult[0].arguments[0].body + : childResult[0].body + ? childResult[0].body + : childResult[0] + runningObject.push( + t.objectMethod( + 'get', + t.identifier('children'), + [], + t.isExpression(body) ? t.blockStatement([t.returnStatement(body)]) : body + ) + ) + } else runningObject.push(t.objectProperty(t.identifier('children'), childResult[0])) + } + if (runningObject.length || !props.length) props.push(t.objectExpression(runningObject)) + + if (props.length > 1 || dynamicSpread) { + props = [t.callExpression(registerImportMethod(path, 'mergeProps'), props)] + } + const componentArgs = [tagId, props[0]] + exprs.push(t.callExpression(registerImportMethod(path, 'createComponent'), componentArgs)) + + // handle hoisting conditionals + if (exprs.length > 1) { + const ret = exprs.pop() + exprs = [t.callExpression(t.arrowFunctionExpression([], t.blockStatement([...exprs, t.returnStatement(ret)])), [])] + } + return { exprs, template: '', component: true } +} + +function transformComponentChildren(children, config) { + const filteredChildren = filterChildren(children) + if (!filteredChildren.length) return + let dynamic = false + const pathNodes = [] + + let transformedChildren = filteredChildren.reduce((memo, path) => { + if (t.isJSXText(path.node)) { + const v = decode(trimWhitespace(path.node.extra.raw)) + if (v.length) { + pathNodes.push(path.node) + memo.push(t.stringLiteral(v)) + } + } else { + const child = transformNode(path, { + topLevel: true, + componentChild: true, + lastElement: true, + }) + dynamic = dynamic || child.dynamic + if (config.generate === 'ssr' && filteredChildren.length > 1 && child.dynamic && t.isFunction(child.exprs[0])) { + child.exprs[0] = child.exprs[0].body + } + pathNodes.push(path.node) + memo.push(getCreateTemplate(config, path, child)(path, child, filteredChildren.length > 1)) + } + return memo + }, []) + + if (transformedChildren.length === 1) { + transformedChildren = transformedChildren[0] + if (!t.isJSXExpressionContainer(pathNodes[0]) && !t.isJSXSpreadChild(pathNodes[0]) && !t.isJSXText(pathNodes[0])) { + transformedChildren = + t.isCallExpression(transformedChildren) && + !transformedChildren.arguments.length && + !t.isIdentifier(transformedChildren.callee) + ? transformedChildren.callee + : t.arrowFunctionExpression([], transformedChildren) + dynamic = true + } + } else { + transformedChildren = t.arrowFunctionExpression([], t.arrayExpression(transformedChildren)) + dynamic = true + } + return [transformedChildren, dynamic] +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/shared/fragment.js b/packages/babel-plugin-transform-solid-jsx/src/shared/fragment.js new file mode 100644 index 000000000000..1d8c295daead --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/shared/fragment.js @@ -0,0 +1,20 @@ +import * as t from '@babel/types' +import { decode } from 'html-entities' + +import { getCreateTemplate, transformNode } from './transform' +import { filterChildren, trimWhitespace } from './utils' + +export default function transformFragmentChildren(children, results, config) { + const filteredChildren = filterChildren(children) + const childNodes = filteredChildren.reduce((memo, path) => { + if (t.isJSXText(path.node)) { + const v = decode(trimWhitespace(path.node.extra.raw)) + if (v.length) memo.push(t.stringLiteral(v)) + } else { + const child = transformNode(path, { topLevel: true, fragmentChild: true, lastElement: true }) + memo.push(getCreateTemplate(config, path, child)(path, child, true)) + } + return memo + }, []) + results.exprs.push(childNodes.length === 1 ? childNodes[0] : t.arrayExpression(childNodes)) +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/shared/postprocess.js b/packages/babel-plugin-transform-solid-jsx/src/shared/postprocess.js new file mode 100644 index 000000000000..b3cc54ad5607 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/shared/postprocess.js @@ -0,0 +1,27 @@ +import * as t from '@babel/types' + +import { appendTemplates as appendTemplatesDOM } from '../dom/template' +import { appendTemplates as appendTemplatesSSR } from '../ssr/template' +import { getRendererConfig, getTaroComponentsMap, registerImportMethod } from './utils' + +// add to the top/bottom of the module. +export default (path) => { + if (path.scope.data.events) { + path.node.body.push( + t.expressionStatement( + t.callExpression(registerImportMethod(path, 'delegateEvents', getRendererConfig(path, 'dom').moduleName), [ + t.arrayExpression(Array.from(path.scope.data.events).map((e) => t.stringLiteral(e))), + ]) + ) + ) + } + if (path.scope.data.templates?.length) { + const domTemplates = path.scope.data.templates.filter((temp) => temp.renderer === 'dom') + const ssrTemplates = path.scope.data.templates.filter((temp) => temp.renderer === 'ssr') + domTemplates.length > 0 && appendTemplatesDOM(path, domTemplates) + ssrTemplates.length > 0 && appendTemplatesSSR(path, ssrTemplates) + } + + const taroComponentsMap = getTaroComponentsMap(path) + taroComponentsMap.clear() +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/shared/preprocess.js b/packages/babel-plugin-transform-solid-jsx/src/shared/preprocess.js new file mode 100644 index 000000000000..56efb758ae9e --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/shared/preprocess.js @@ -0,0 +1,62 @@ +import * as t from '@babel/types' + +import config from '../config' +import { isComponent, isTaroComponent } from './utils' + +const { isValidHTMLNesting } = require('validate-html-nesting') + +// From https://github.com/MananTank/babel-plugin-validate-jsx-nesting/blob/main/src/index.js +const JSXValidator = { + JSXElement(path) { + const elName = path.node.openingElement.name + const parent = path.parent + if (!t.isJSXElement(parent) || !t.isJSXIdentifier(elName)) return + const elTagName = elName.name + if (isComponent(elTagName)) return + const parentElName = parent.openingElement.name + if (!t.isJSXIdentifier(parentElName)) return + const parentElTagName = parentElName.name + if (!isComponent(parentElTagName)) { + if (!isValidHTMLNesting(parentElTagName, elTagName)) { + throw path.buildCodeFrameError(`Invalid JSX: <${elTagName}> cannot be child of <${parentElTagName}>`) + } + } + }, +} + +export default (path, { opts }) => { + const merged = (path.hub.file.metadata.config = Object.assign({}, config, opts)) + const taroComponentsMap = (path.hub.file.metadata.taroComponentsMap ||= new Map()) + const lib = merged.requireImportSource + if (lib) { + const comments = path.hub.file.ast.comments + let process = false + for (let i = 0; i < comments.length; i++) { + const comment = comments[i] + const index = comment.value.indexOf('@jsxImportSource') + if (index > -1 && comment.value.slice(index).includes(lib)) { + process = true + break + } + } + if (!process) { + path.skip() + return + } + } + for (const stmt of path.get('body')) { + if (t.isImportDeclaration(stmt.node)) { + if (isTaroComponent(stmt.node.source.value)) { + stmt.node.specifiers.forEach((specifier) => { + // 包体导出的变量名 + const importedName = specifier.imported.name + // 当前使用的变量名 防止别名 + // import { Button as MyButton } from '@tarojs/components' + const localName = specifier.local.name + taroComponentsMap.set(localName, importedName) + }) + } + } + } + if (merged.validate) path.traverse(JSXValidator) +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/shared/transform.js b/packages/babel-plugin-transform-solid-jsx/src/shared/transform.js new file mode 100644 index 000000000000..91fa2f84aba7 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/shared/transform.js @@ -0,0 +1,210 @@ +import * as t from '@babel/types' + +import { transformElement as transformElementDOM } from '../dom/element' +import { createTemplate as createTemplateDOM } from '../dom/template' +import { transformElement as transformElementSSR } from '../ssr/element' +import { createTemplate as createTemplateSSR } from '../ssr/template' +import { transformElement as transformElementUniversal } from '../universal/element' +import { createTemplate as createTemplateUniversal } from '../universal/template' +import transformComponent from './component' +import transformFragmentChildren from './fragment' +import { + convertCamelToKebabCase, + escapeHTML, + getConfig, + getStaticExpression, + getTagName, + getTaroComponentsMap, + isComponent, + isDynamic, + transformCondition, + trimWhitespace, +} from './utils' + +export function transformJSX(path) { + const config = getConfig(path) + const replace = transformThis(path) + const result = transformNode( + path, + t.isJSXFragment(path.node) + ? {} + : { + topLevel: true, + lastElement: true, + } + ) + + const template = getCreateTemplate(config, path, result) + + path.replaceWith(replace(template(path, result, false))) +} + +function getTargetFunctionParent(path, parent) { + let current = path.scope.getFunctionParent() + while (current !== parent && current.path.isArrowFunctionExpression()) { + current = current.path.parentPath.scope.getFunctionParent() + } + return current +} + +export function transformThis(path) { + const parent = path.scope.getFunctionParent() + let thisId + path.traverse({ + ThisExpression(path) { + const current = getTargetFunctionParent(path, parent) + if (current === parent) { + thisId || (thisId = path.scope.generateUidIdentifier('self$')) + path.replaceWith(thisId) + } + }, + JSXElement(path) { + let source = path.get('openingElement').get('name') + while (source.isJSXMemberExpression()) { + source = source.get('object') + } + if (source.isJSXIdentifier() && source.node.name === 'this') { + const current = getTargetFunctionParent(path, parent) + if (current === parent) { + thisId || (thisId = path.scope.generateUidIdentifier('self$')) + source.replaceWith(t.jsxIdentifier(thisId.name)) + + if (path.node.closingElement) { + path.node.closingElement.name = path.node.openingElement.name + } + } + } + }, + }) + return (node) => { + if (thisId) { + parent.push({ + id: thisId, + init: t.thisExpression(), + kind: 'const', + }) + } + return node + } +} + +export function transformNode(path, info = {}) { + const config = getConfig(path) + const node = path.node + let staticValue + if (t.isJSXElement(node)) { + return transformElement(config, path, info) + } else if (t.isJSXFragment(node)) { + const results = { template: '', declarations: [], exprs: [], dynamics: [] } + // <>
+ transformFragmentChildren(path.get('children'), results, config) + return results + } else if (t.isJSXText(node) || (staticValue = getStaticExpression(path)) !== false) { + const text = + staticValue !== undefined + ? info.doNotEscape + ? staticValue.toString() + : escapeHTML(staticValue.toString()) + : trimWhitespace(node.extra.raw) + if (!text.length) return null + const results = { + template: text, + declarations: [], + exprs: [], + dynamics: [], + postExprs: [], + text: true, + } + if (!info.skipId && config.generate !== 'ssr') results.id = path.scope.generateUidIdentifier('el$') + return results + } else if (t.isJSXExpressionContainer(node)) { + if (t.isJSXEmptyExpression(node.expression)) return null + if ( + !isDynamic(path.get('expression'), { + checkMember: true, + checkTags: !!info.componentChild, + native: !info.componentChild, + }) + ) { + return { exprs: [node.expression], template: '' } + } + const expr = + config.wrapConditionals && + config.generate !== 'ssr' && + (t.isLogicalExpression(node.expression) || t.isConditionalExpression(node.expression)) + ? transformCondition(path.get('expression'), info.componentChild || info.fragmentChild) + : !info.componentChild && + (config.generate !== 'ssr' || info.fragmentChild) && + t.isCallExpression(node.expression) && + !t.isMemberExpression(node.expression.callee) && + node.expression.arguments.length === 0 + ? node.expression.callee + : t.arrowFunctionExpression([], node.expression) + return { + exprs: + expr.length > 1 + ? [ + t.callExpression( + t.arrowFunctionExpression([], t.blockStatement([expr[0], t.returnStatement(expr[1])])), + [] + ), + ] + : [expr], + template: '', + dynamic: true, + } + } else if (t.isJSXSpreadChild(node)) { + if ( + !isDynamic(path.get('expression'), { + checkMember: true, + native: !info.componentChild, + }) + ) { return { exprs: [node.expression], template: '' } } + const expr = t.arrowFunctionExpression([], node.expression) + return { + exprs: [expr], + template: '', + dynamic: true, + } + } +} + +export function getCreateTemplate(config, path, result) { + if ((result.tagName && result.renderer === 'dom') || config.generate === 'dom') { + return createTemplateDOM + } + + if (result.renderer === 'ssr' || config.generate === 'ssr') { + return createTemplateSSR + } + + return createTemplateUniversal +} + +export function transformElement(config, path, info = {}) { + const node = path.node + let tagName = getTagName(node) + if (config.uniqueTransform) { + const taroComponent = getTaroComponentsMap(path).get(tagName) + if (taroComponent) { + tagName = convertCamelToKebabCase(taroComponent) + } + } + // + if (isComponent(tagName)) return transformComponent(path) + + //
+ // const element = getTransformElemet(config, path, tagName); + + const tagRenderer = (config.renderers ?? []).find((renderer) => renderer.elements.includes(tagName)) + + if (tagRenderer?.name === 'dom' || getConfig(path).generate === 'dom') { + return transformElementDOM(path, info) + } + + if (getConfig(path).generate === 'ssr') { + return transformElementSSR(path, info) + } + + return transformElementUniversal(path, info) +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/shared/utils.js b/packages/babel-plugin-transform-solid-jsx/src/shared/utils.js new file mode 100644 index 000000000000..f95cb65232a8 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/shared/utils.js @@ -0,0 +1,419 @@ +import { addNamed } from '@babel/helper-module-imports' +import * as t from '@babel/types' + +export const reservedNameSpaces = new Set(['class', 'on', 'oncapture', 'style', 'use', 'prop', 'attr']) + +export const nonSpreadNameSpaces = new Set(['class', 'style', 'use', 'prop', 'attr']) + +export function getConfig(path) { + return path.hub.file.metadata.config +} + +export function getTaroComponentsMap(path) { + return path.hub.file.metadata.taroComponentsMap || new Map() +} + +export const getRendererConfig = (path, renderer) => { + const config = getConfig(path) + return config?.renderers?.find((r) => r.name === renderer) ?? config +} + +export function registerImportMethod(path, name, moduleName) { + const imports = path.scope.getProgramParent().data.imports || (path.scope.getProgramParent().data.imports = new Map()) + moduleName = moduleName || getConfig(path).moduleName + if (!imports.has(`${moduleName}:${name}`)) { + const id = addNamed(path, name, moduleName, { + nameHint: `_$${name}`, + }) + imports.set(`${moduleName}:${name}`, id) + return id + } else { + const iden = imports.get(`${moduleName}:${name}`) + // the cloning is required to play well with babel-preset-env which is + // transpiling import as we add them and using the same identifier causes + // problems with the multiple identifiers of the same thing + return t.cloneNode(iden) + } +} + +function jsxElementNameToString(node) { + if (t.isJSXMemberExpression(node)) { + return `${jsxElementNameToString(node.object)}.${node.property.name}` + } + if (t.isJSXIdentifier(node) || t.isIdentifier(node)) { + return node.name + } + return `${node.namespace.name}:${node.name.name}` +} + +export function tagNameToIdentifier(name) { + const parts = name.split('.') + if (parts.length === 1) return t.identifier(name) + let part + let base = t.identifier(parts.shift()) + while ((part = parts.shift())) { + base = t.memberExpression(base, t.identifier(part)) + } + return base +} + +export function getTagName(tag) { + const jsxName = tag.openingElement.name + return jsxElementNameToString(jsxName) +} + +export function isComponent(tagName) { + return ( + (tagName[0] && tagName[0].toLowerCase() !== tagName[0]) || tagName.includes('.') || /[^a-zA-Z]/.test(tagName[0]) + ) +} + +export function isDynamic(path, { checkMember, checkTags, checkCallExpressions = true, native }) { + const config = getConfig(path) + if (config.generate === 'ssr' && native) { + checkMember = false + checkCallExpressions = false + } + const expr = path.node + if (t.isFunction(expr)) return false + if (expr.leadingComments && expr.leadingComments[0] && expr.leadingComments[0].value.trim() === config.staticMarker) { + expr.leadingComments.shift() + return false + } + + if (checkCallExpressions && (t.isCallExpression(expr) || t.isOptionalCallExpression(expr))) { + return true + } + + if (checkMember && t.isMemberExpression(expr)) { + // Do not assume property access on namespaced imports as dynamic. + const object = path.get('object').node + + if ( + t.isIdentifier(object) && + (!expr.computed || + !isDynamic(path.get('property'), { + checkMember, + checkTags, + checkCallExpressions, + native, + })) + ) { + const binding = path.scope.getBinding(object.name) + + if (binding && binding.path.isImportNamespaceSpecifier()) { + return false + } + } + + return true + } + + if ( + checkMember && + (t.isOptionalMemberExpression(expr) || + t.isSpreadElement(expr) || + (t.isBinaryExpression(expr) && expr.operator === 'in')) + ) { + return true + } + + if (checkTags && (t.isJSXElement(expr) || (t.isJSXFragment(expr) && expr.children.length))) { + return true + } + + let dynamic + path.traverse({ + Function(p) { + if (t.isObjectMethod(p.node) && p.node.computed) { + dynamic = isDynamic(p.get('key'), { checkMember, checkTags, checkCallExpressions, native }) + } + p.skip() + }, + CallExpression(p) { + checkCallExpressions && (dynamic = true) && p.stop() + }, + OptionalCallExpression(p) { + checkCallExpressions && (dynamic = true) && p.stop() + }, + MemberExpression(p) { + checkMember && (dynamic = true) && p.stop() + }, + OptionalMemberExpression(p) { + checkMember && (dynamic = true) && p.stop() + }, + SpreadElement(p) { + checkMember && (dynamic = true) && p.stop() + }, + BinaryExpression(p) { + checkMember && p.node.operator === 'in' && (dynamic = true) && p.stop() + }, + JSXElement(p) { + checkTags ? (dynamic = true) && p.stop() : p.skip() + }, + JSXFragment(p) { + checkTags && p.node.children.length ? (dynamic = true) && p.stop() : p.skip() + }, + }) + return dynamic +} + +export function getStaticExpression(path) { + const node = path.node + let value, type + return ( + t.isJSXExpressionContainer(node) && + t.isJSXElement(path.parent) && + !isComponent(getTagName(path.parent)) && + !t.isSequenceExpression(node.expression) && + (value = path.get('expression').evaluate().value) !== undefined && + ((type = typeof value) === 'string' || type === 'number') && + value + ) +} + +// remove unnecessary JSX Text nodes +export function filterChildren(children) { + return children.filter( + ({ node: child }) => + !(t.isJSXExpressionContainer(child) && t.isJSXEmptyExpression(child.expression)) && + (!t.isJSXText(child) || !/^[\r\n]\s*$/.test(child.extra.raw)) + ) +} + +export function checkLength(children) { + let i = 0 + children.forEach((path) => { + const child = path.node + !(t.isJSXExpressionContainer(child) && t.isJSXEmptyExpression(child.expression)) && + (!t.isJSXText(child) || !/^\s*$/.test(child.extra.raw) || /^ *$/.test(child.extra.raw)) && + i++ + }) + return i > 1 +} + +export function trimWhitespace(text) { + text = text.replace(/\r/g, '') + if (/\n/g.test(text)) { + text = text + .split('\n') + .map((t, i) => (i ? t.replace(/^\s*/g, '') : t)) + .filter((s) => !/^\s*$/.test(s)) + .join(' ') + } + return text.replace(/\s+/g, ' ') +} + +export function toEventName(name) { + return name.slice(2).toLowerCase() +} + +export function toAttributeName(name) { + return name.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`) +} + +export function toPropertyName(name) { + return name.toLowerCase().replace(/-([a-z])/g, (_, w) => w.toUpperCase()) +} + +export function wrappedByText(list, startIndex) { + let index = startIndex + let wrapped + while (--index >= 0) { + const node = list[index] + if (!node) continue + if (node.text) { + wrapped = true + break + } + if (node.id) return false + } + if (!wrapped) return false + index = startIndex + while (++index < list.length) { + const node = list[index] + if (!node) continue + if (node.text) return true + if (node.id) return false + } + return false +} + +export function transformCondition(path, inline, deep) { + const config = getConfig(path) + const expr = path.node + const memo = registerImportMethod(path, config.memoWrapper) + let dTest, cond, id + if ( + t.isConditionalExpression(expr) && + (isDynamic(path.get('consequent'), { + checkTags: true, + }) || + isDynamic(path.get('alternate'), { checkTags: true })) + ) { + dTest = isDynamic(path.get('test'), { checkMember: true }) + if (dTest) { + cond = expr.test + if (!t.isBinaryExpression(cond)) cond = t.unaryExpression('!', t.unaryExpression('!', cond, true), true) + id = inline + ? t.callExpression(memo, [t.arrowFunctionExpression([], cond)]) + : path.scope.generateUidIdentifier('_c$') + expr.test = t.callExpression(id, []) + if (t.isConditionalExpression(expr.consequent) || t.isLogicalExpression(expr.consequent)) { + expr.consequent = transformCondition(path.get('consequent'), inline, true) + } + if (t.isConditionalExpression(expr.alternate) || t.isLogicalExpression(expr.alternate)) { + expr.alternate = transformCondition(path.get('alternate'), inline, true) + } + } + } else if (t.isLogicalExpression(expr)) { + let nextPath = path + // handle top-level or, ie cond && || + while (nextPath.node.operator !== '&&' && t.isLogicalExpression(nextPath.node.left)) { + nextPath = nextPath.get('left') + } + nextPath.node.operator === '&&' && + isDynamic(nextPath.get('right'), { checkTags: true }) && + (dTest = isDynamic(nextPath.get('left'), { + checkMember: true, + })) + if (dTest) { + cond = nextPath.node.left + if (!t.isBinaryExpression(cond)) cond = t.unaryExpression('!', t.unaryExpression('!', cond, true), true) + id = inline + ? t.callExpression(memo, [t.arrowFunctionExpression([], cond)]) + : path.scope.generateUidIdentifier('_c$') + nextPath.node.left = t.callExpression(id, []) + } + } + if (dTest && !inline) { + const statements = [ + t.variableDeclaration('var', [ + t.variableDeclarator( + id, + config.memoWrapper + ? t.callExpression(memo, [t.arrowFunctionExpression([], cond)]) + : t.arrowFunctionExpression([], cond) + ), + ]), + t.arrowFunctionExpression([], expr), + ] + return deep + ? t.callExpression( + t.arrowFunctionExpression([], t.blockStatement([statements[0], t.returnStatement(statements[1])])), + [] + ) + : statements + } + return deep ? expr : t.arrowFunctionExpression([], expr) +} + +export function escapeHTML(s, attr) { + if (typeof s !== 'string') return s + const delim = attr ? '"' : '<' + const escDelim = attr ? '"' : '<' + let iDelim = s.indexOf(delim) + let iAmp = s.indexOf('&') + + if (iDelim < 0 && iAmp < 0) return s + + let left = 0 + let out = '' + + while (iDelim >= 0 && iAmp >= 0) { + if (iDelim < iAmp) { + if (left < iDelim) out += s.substring(left, iDelim) + out += escDelim + left = iDelim + 1 + iDelim = s.indexOf(delim, left) + } else { + if (left < iAmp) out += s.substring(left, iAmp) + out += '&' + left = iAmp + 1 + iAmp = s.indexOf('&', left) + } + } + + if (iDelim >= 0) { + do { + if (left < iDelim) out += s.substring(left, iDelim) + out += escDelim + left = iDelim + 1 + iDelim = s.indexOf(delim, left) + } while (iDelim >= 0) + } else { + while (iAmp >= 0) { + if (left < iAmp) out += s.substring(left, iAmp) + out += '&' + left = iAmp + 1 + iAmp = s.indexOf('&', left) + } + } + + return left < s.length ? out + s.substring(left) : out +} + +export function convertJSXIdentifier(node) { + if (t.isJSXIdentifier(node)) { + if (t.isValidIdentifier(node.name)) { + node.type = 'Identifier' + } else { + return t.stringLiteral(node.name) + } + } else if (t.isJSXMemberExpression(node)) { + return t.memberExpression(convertJSXIdentifier(node.object), convertJSXIdentifier(node.property)) + } else if (t.isJSXNamespacedName(node)) { + return t.stringLiteral(`${node.namespace.name}:${node.name.name}`) + } + + return node +} + +export function canNativeSpread(key, { checkNameSpaces } = {}) { + if (checkNameSpaces && key.includes(':') && nonSpreadNameSpaces.has(key.split(':')[0])) return false + // TODO: figure out how to detect definitely function ref + if (key === 'ref') return false + return true +} + +const chars = 'etaoinshrdlucwmfygpbTAOISWCBvkxjqzPHFMDRELNGUKVYJQZX_$' +const base = chars.length + +export function getNumberedId(num) { + let out = '' + + do { + const digit = num % base + + num = Math.floor(num / base) + out = chars[digit] + out + } while (num !== 0) + + return out +} + +const templateEscapes = new Map([ + ['{', '\\{'], + ['`', '\\`'], + ['\\', '\\\\'], + ['\n', '\\n'], + ['\t', '\\t'], + ['\b', '\\b'], + ['\f', '\\f'], + ['\v', '\\v'], + ['\r', '\\r'], + ['\u2028', '\\u2028'], + ['\u2029', '\\u2029'], +]) + +export function escapeStringForTemplate(str) { + return str.replace(/[{\\`\n\t\b\f\v\r\u2028\u2029]/g, (ch) => templateEscapes.get(ch)) +} + +export function convertCamelToKebabCase(camelCaseStr) { + return camelCaseStr.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() +} + +export function isTaroComponent(packageName) { + return packageName === '@tarojs/components' +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/ssr/element.js b/packages/babel-plugin-transform-solid-jsx/src/ssr/element.js new file mode 100644 index 000000000000..436eab5054c6 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/ssr/element.js @@ -0,0 +1,546 @@ +import * as t from '@babel/types' +import { decode } from 'html-entities' + +import { Aliases, BooleanAttributes, ChildProperties, SVGElements } from '../constants' +import { getCreateTemplate, transformNode } from '../shared/transform' +import { + checkLength, + convertJSXIdentifier, + escapeHTML, + filterChildren, + getConfig, + getTagName, + isComponent, + isDynamic, + registerImportMethod, + reservedNameSpaces, + trimWhitespace, +} from '../shared/utils' +import VoidElements from '../VoidElements' +import { createTemplate } from './template' + +function appendToTemplate(template, value) { + let array + if (Array.isArray(value)) { + [value, ...array] = value + } + template[template.length - 1] += value + if (array && array.length) template.push.apply(template, array) +} + +export function transformElement(path, info) { + const config = getConfig(path) + // contains spread attributes + if (path.node.openingElement.attributes.some((a) => t.isJSXSpreadAttribute(a))) { + return createElement(path, { ...info, ...config }) + } + + const tagName = getTagName(path.node) + const voidTag = VoidElements.indexOf(tagName) > -1 + const results = { + template: [`<${tagName}`], + templateValues: [], + declarations: [], + exprs: [], + dynamics: [], + tagName, + wontEscape: path.node.wontEscape, + renderer: 'ssr', + } + if (tagName === 'script' || tagName === 'style') path.doNotEscape = true + + if (info.topLevel && config.hydratable) { + if (tagName === 'head') { + registerImportMethod(path, 'NoHydration') + registerImportMethod(path, 'createComponent') + const child = transformElement(path, { ...info, topLevel: false }) + results.template = '' + results.exprs.push( + t.callExpression(t.identifier('_$createComponent'), [ + t.identifier('_$NoHydration'), + t.objectExpression([ + t.objectMethod( + 'get', + t.identifier('children'), + [], + t.blockStatement([t.returnStatement(createTemplate(path, child))]) + ), + ]), + ]) + ) + return results + } + results.template.push('') + results.templateValues.push(t.callExpression(registerImportMethod(path, 'ssrHydrationKey'), [])) + } + transformAttributes(path, results, { ...config, ...info }) + appendToTemplate(results.template, '>') + if (!voidTag) { + transformChildren(path, results, { ...config, ...info }) + appendToTemplate(results.template, ``) + } + return results +} + +function toAttribute(key, isSVG) { + key = Aliases[key] || key + !isSVG && (key = key.toLowerCase()) + return key +} + +function setAttr(attribute, results, name, value, isSVG) { + // strip out namespaces for now, everything at this point is an attribute + let parts + if ((parts = name.split(':')) && parts[1] && reservedNameSpaces.has(parts[0])) { + name = parts[1] + } + + name = toAttribute(name, isSVG) + const attr = t.callExpression(registerImportMethod(attribute, 'ssrAttribute'), [ + t.stringLiteral(name), + value, + t.booleanLiteral(false), + ]) + if (results.template[results.template.length - 1].length) { + results.template.push('') + results.templateValues.push(attr) + } else { + const last = results.templateValues.length - 1 + results.templateValues[last] = t.binaryExpression('+', results.templateValues[last], attr) + } +} + +function escapeExpression(path, expression, attr, escapeLiterals) { + if ( + t.isStringLiteral(expression) || + t.isNumericLiteral(expression) || + (t.isTemplateLiteral(expression) && expression.expressions.length === 0) + ) { + if (escapeLiterals) { + if (t.isStringLiteral(expression)) return t.stringLiteral(escapeHTML(expression.value, attr)) + else if (t.isTemplateLiteral(expression)) return t.stringLiteral(escapeHTML(expression.quasis[0].value.raw, attr)) + } + return expression + } else if (t.isFunction(expression)) { + if (t.isBlockStatement(expression.body)) { + expression.body.body = expression.body.body.map((e) => { + if (t.isReturnStatement(e)) e.argument = escapeExpression(path, e.argument, attr, escapeLiterals) + return e + }) + } else expression.body = escapeExpression(path, expression.body, attr, escapeLiterals) + return expression + } else if (t.isTemplateLiteral(expression)) { + expression.expressions = expression.expressions.map((e) => escapeExpression(path, e, attr, escapeLiterals)) + return expression + } else if (t.isUnaryExpression(expression)) { + return expression + } else if (t.isBinaryExpression(expression)) { + expression.left = escapeExpression(path, expression.left, attr, escapeLiterals) + expression.right = escapeExpression(path, expression.right, attr, escapeLiterals) + return expression + } else if (t.isConditionalExpression(expression)) { + expression.consequent = escapeExpression(path, expression.consequent, attr, escapeLiterals) + expression.alternate = escapeExpression(path, expression.alternate, attr, escapeLiterals) + return expression + } else if (t.isLogicalExpression(expression)) { + expression.right = escapeExpression(path, expression.right, attr, escapeLiterals) + if (expression.operator !== '&&') { + expression.left = escapeExpression(path, expression.left, attr, escapeLiterals) + } + return expression + } else if (t.isCallExpression(expression) && t.isFunction(expression.callee)) { + if (t.isBlockStatement(expression.callee.body)) { + expression.callee.body.body = expression.callee.body.body.map((e) => { + if (t.isReturnStatement(e)) e.argument = escapeExpression(path, e.argument, attr, escapeLiterals) + return e + }) + } else expression.callee.body = escapeExpression(path, expression.callee.body, attr, escapeLiterals) + return expression + } else if (t.isJSXElement(expression) && !isComponent(getTagName(expression))) { + expression.wontEscape = true + return expression + } + + return t.callExpression( + registerImportMethod(path, 'escape'), + [expression].concat(attr ? [t.booleanLiteral(true)] : []) + ) +} + +function transformToObject(attrName, attributes, selectedAttributes) { + const properties = [] + const existingAttribute = attributes.find((a) => a.node.name.name === attrName) + for (let i = 0; i < selectedAttributes.length; i++) { + const attr = selectedAttributes[i].node + const computed = !t.isValidIdentifier(attr.name.name.name) + if (!computed) { + attr.name.name.type = 'Identifier' + } + properties.push( + t.objectProperty( + computed ? t.stringLiteral(attr.name.name.name) : attr.name.name, + t.isJSXExpressionContainer(attr.value) ? attr.value.expression : attr.value + ) + ) + ;(existingAttribute || i) && attributes.splice(selectedAttributes[i].key, 1) + } + if ( + existingAttribute && + t.isJSXExpressionContainer(existingAttribute.node.value) && + t.isObjectExpression(existingAttribute.node.value.expression) + ) { + existingAttribute.node.value.expression.properties.push(...properties) + } else { + selectedAttributes[0].node = t.jsxAttribute( + t.jsxIdentifier(attrName), + t.jsxExpressionContainer(t.objectExpression(properties)) + ) + } +} + +function normalizeAttributes(path) { + const attributes = path.get('openingElement').get('attributes') + const styleAttributes = attributes.filter( + (a) => t.isJSXNamespacedName(a.node.name) && a.node.name.namespace.name === 'style' + ) + const classNamespaceAttributes = attributes.filter( + (a) => t.isJSXNamespacedName(a.node.name) && a.node.name.namespace.name === 'class' + ) + if (classNamespaceAttributes.length) transformToObject('classList', attributes, classNamespaceAttributes) + const classAttributes = attributes.filter( + (a) => + a.node.name && + (a.node.name.name === 'class' || a.node.name.name === 'className' || a.node.name.name === 'classList') + ) + // combine class propertoes + if (classAttributes.length > 1) { + const first = classAttributes[0].node + const values = [] + const quasis = [t.templateElement({ raw: '' })] + for (let i = 0; i < classAttributes.length; i++) { + const attr = classAttributes[i].node + const isLast = i === classAttributes.length - 1 + if (!t.isJSXExpressionContainer(attr.value)) { + const prev = quasis.pop() + quasis.push( + t.templateElement({ + raw: (prev ? prev.value.raw : '') + `${attr.value.value}` + (isLast ? '' : ' '), + }) + ) + } else { + let expr = attr.value.expression + if (attr.name.name === 'classList') { + if (t.isObjectExpression(expr) && !expr.properties.some((p) => t.isSpreadElement(p))) { + transformClasslistObject(path, expr, values, quasis) + if (!isLast) quasis[quasis.length - 1].value.raw += ' ' + i && attributes.splice(attributes.indexOf(classAttributes[i]), 1) + continue + } + expr = t.callExpression(registerImportMethod(path, 'ssrClassList'), [expr]) + } + values.push(t.logicalExpression('||', expr, t.stringLiteral(''))) + quasis.push(t.templateElement({ raw: isLast ? '' : ' ' })) + } + i && attributes.splice(attributes.indexOf(classAttributes[i]), 1) + } + first.name = t.jsxIdentifier('class') + first.value = t.jsxExpressionContainer(t.templateLiteral(quasis, values)) + } + if (styleAttributes.length) transformToObject('style', attributes, styleAttributes) + return attributes +} + +function transformAttributes(path, results, info) { + const tagName = getTagName(path.node) + const isSVG = SVGElements.has(tagName) + const hasChildren = path.node.children.length > 0 + const attributes = normalizeAttributes(path) + let children + + attributes.forEach((attribute) => { + const node = attribute.node + + let value = node.value + let key = t.isJSXNamespacedName(node.name) ? `${node.name.namespace.name}:${node.name.name.name}` : node.name.name + const reservedNameSpace = t.isJSXNamespacedName(node.name) && reservedNameSpaces.has(node.name.namespace.name) + if ( + ((t.isJSXNamespacedName(node.name) && reservedNameSpace) || ChildProperties.has(key)) && + !t.isJSXExpressionContainer(value) + ) { + node.value = value = t.jsxExpressionContainer(value || t.jsxEmptyExpression()) + } + + if ( + t.isJSXExpressionContainer(value) && + (reservedNameSpace || + ChildProperties.has(key) || + !( + t.isStringLiteral(value.expression) || + t.isNumericLiteral(value.expression) || + t.isBooleanLiteral(value.expression) + )) + ) { + if (key === 'ref' || key.startsWith('use:') || key.startsWith('prop:') || key.startsWith('on')) return false + else if (ChildProperties.has(key)) { + if (info.hydratable && key === 'textContent' && value && value.expression) { + value.expression = t.logicalExpression('||', value.expression, t.stringLiteral(' ')) + } + if (key === 'innerHTML') path.doNotEscape = true + children = value + } else { + let doEscape = true + if (BooleanAttributes.has(key)) { + results.template.push('') + const fn = t.callExpression(registerImportMethod(attribute, 'ssrAttribute'), [ + t.stringLiteral(key), + value.expression, + t.booleanLiteral(true), + ]) + results.templateValues.push(fn) + return + } + if (key === 'style') { + if ( + t.isJSXExpressionContainer(value) && + t.isObjectExpression(value.expression) && + !value.expression.properties.some((p) => t.isSpreadElement(p)) + ) { + const props = value.expression.properties.map((p, i) => + t.binaryExpression( + '+', + t.stringLiteral((i ? ';' : '') + (t.isIdentifier(p.key) ? p.key.name : p.key.value) + ':'), + escapeExpression(path, p.value, true, true) + ) + ) + let res = props[0] + for (let i = 1; i < props.length; i++) { + res = t.binaryExpression('+', res, props[i]) + } + value.expression = res + } else { + value.expression = t.callExpression(registerImportMethod(path, 'ssrStyle'), [value.expression]) + } + doEscape = false + } + if (key === 'classList') { + if ( + t.isObjectExpression(value.expression) && + !value.expression.properties.some((p) => t.isSpreadElement(p)) + ) { + const values = [] + const quasis = [t.templateElement({ raw: '' })] + transformClasslistObject(path, value.expression, values, quasis) + if (!values.length) value.expression = t.stringLiteral(quasis[0].value.raw) + else if (values.length === 1 && !quasis[0].value.raw && !quasis[1].value.raw) { + value.expression = values[0] + } else value.expression = t.templateLiteral(quasis, values) + } else { + value.expression = t.callExpression(registerImportMethod(path, 'ssrClassList'), [value.expression]) + } + key = 'class' + doEscape = false + } + if (doEscape) value.expression = escapeExpression(path, value.expression, true) + + if (!doEscape || t.isLiteral(value.expression)) { + key = toAttribute(key, isSVG) + appendToTemplate(results.template, ` ${key}="`) + results.template.push(`"`) + results.templateValues.push(value.expression) + } else setAttr(attribute, results, key, value.expression, isSVG) + } + } else { + if (key === '$ServerOnly') return + if (t.isJSXExpressionContainer(value)) value = value.expression + key = toAttribute(key, isSVG) + const isBoolean = BooleanAttributes.has(key) + if (isBoolean && value && value.value !== '' && !value.value) return + appendToTemplate(results.template, ` ${key}`) + if (!value) return + let text = isBoolean ? '' : value.value + if (key === 'style' || key === 'class') { + text = trimWhitespace(text) + if (key === 'style') { + text = text.replace(/; /g, ';').replace(/: /g, ':') + } + } + appendToTemplate(results.template, `="${escapeHTML(text, true)}"`) + } + }) + if (!hasChildren && children) { + path.node.children.push(children) + } +} + +function transformClasslistObject(path, expr, values, quasis) { + expr.properties.forEach((prop, i) => { + const isLast = expr.properties.length - 1 === i + let key = prop.key + if (t.isIdentifier(prop.key) && !prop.computed) key = t.stringLiteral(key.name) + else if (prop.computed) { + key = t.callExpression(registerImportMethod(path, 'escape'), [prop.key, t.booleanLiteral(true)]) + } else key = t.stringLiteral(escapeHTML(prop.key.value)) + if (t.isBooleanLiteral(prop.value)) { + if (prop.value.value === true) { + if (!prop.computed) { + const prev = quasis.pop() + quasis.push( + t.templateElement({ + raw: (prev ? prev.value.raw : '') + (i ? ' ' : '') + `${key.value}` + (isLast ? '' : ' '), + }) + ) + } else { + values.push(key) + quasis.push(t.templateElement({ raw: isLast ? '' : ' ' })) + } + } + } else { + values.push(t.conditionalExpression(prop.value, key, t.stringLiteral(''))) + quasis.push(t.templateElement({ raw: isLast ? '' : ' ' })) + } + }) +} + +function transformChildren(path, results, { hydratable }) { + const doNotEscape = path.doNotEscape + const filteredChildren = filterChildren(path.get('children')) + const multi = checkLength(filteredChildren) + const markers = hydratable && multi + filteredChildren.forEach((node) => { + if (t.isJSXElement(node.node) && getTagName(node.node) === 'head') { + const child = transformNode(node, { doNotEscape, hydratable: false }) + registerImportMethod(path, 'NoHydration') + registerImportMethod(path, 'createComponent') + results.template.push('') + results.templateValues.push( + t.callExpression(t.identifier('_$createComponent'), [ + t.identifier('_$NoHydration'), + t.objectExpression([ + t.objectMethod( + 'get', + t.identifier('children'), + [], + t.blockStatement([t.returnStatement(createTemplate(path, child))]) + ), + ]), + ]) + ) + return + } + const child = transformNode(node, { doNotEscape }) + if (!child) return + appendToTemplate(results.template, child.template) + results.templateValues.push.apply(results.templateValues, child.templateValues || []) + if (child.exprs.length) { + if (!doNotEscape && !child.spreadElement) child.exprs[0] = escapeExpression(path, child.exprs[0]) + + // boxed by textNodes + if (markers && !child.spreadElement) { + appendToTemplate(results.template, ``) + results.template.push('') + results.templateValues.push(child.exprs[0]) + appendToTemplate(results.template, ``) + } else { + results.template.push('') + results.templateValues.push(child.exprs[0]) + } + } + }) +} + +function createElement(path, { topLevel, hydratable }) { + const tagName = getTagName(path.node) + const config = getConfig(path) + const attributes = normalizeAttributes(path) + + const filteredChildren = filterChildren(path.get('children')) + const multi = checkLength(filteredChildren) + const markers = hydratable && multi + const childNodes = filteredChildren.reduce((memo, path) => { + if (t.isJSXText(path.node)) { + const v = decode(trimWhitespace(path.node.extra.raw)) + if (v.length) memo.push(t.stringLiteral(v)) + } else { + const child = transformNode(path) + if (markers && child.exprs.length && !child.spreadElement) memo.push(t.stringLiteral('')) + if (child.exprs.length && !child.spreadElement) child.exprs[0] = escapeExpression(path, child.exprs[0]) + memo.push(getCreateTemplate(config, path, child)(path, child, true)) + if (markers && child.exprs.length && !child.spreadElement) memo.push(t.stringLiteral('')) + } + return memo + }, []) + + let props + if (attributes.length === 1) { + props = [attributes[0].node.argument] + } else { + props = [] + let runningObject = [] + let dynamicSpread = false + const hasChildren = path.node.children.length > 0 + + attributes.forEach((attribute) => { + const node = attribute.node + if (t.isJSXSpreadAttribute(node)) { + if (runningObject.length) { + props.push(t.objectExpression(runningObject)) + runningObject = [] + } + props.push( + isDynamic(attribute.get('argument'), { + checkMember: true, + }) && (dynamicSpread = true) + ? t.isCallExpression(node.argument) && + !node.argument.arguments.length && + !t.isCallExpression(node.argument.callee) && + !t.isMemberExpression(node.argument.callee) + ? node.argument.callee + : t.arrowFunctionExpression([], node.argument) + : node.argument + ) + } else { + const value = node.value || t.booleanLiteral(true) + const id = convertJSXIdentifier(node.name) + const key = t.isJSXNamespacedName(node.name) ? `${node.name.namespace.name}:${node.name.name.name}` : node.name.name + + if (hasChildren && key === 'children') return + if (key === 'ref' || key.startsWith('use:') || key.startsWith('prop:') || key.startsWith('on')) return + if (t.isJSXExpressionContainer(value)) { + if ( + isDynamic(attribute.get('value').get('expression'), { + checkMember: true, + checkTags: true, + }) + ) { + const expr = t.arrowFunctionExpression([], value.expression) + runningObject.push( + t.objectMethod('get', id, [], t.blockStatement([t.returnStatement(expr.body)]), !t.isValidIdentifier(key)) + ) + } else runningObject.push(t.objectProperty(id, value.expression)) + } else runningObject.push(t.objectProperty(id, value)) + } + }) + + if (runningObject.length || !props.length) props.push(t.objectExpression(runningObject)) + + if (props.length > 1 || dynamicSpread) { + props = [t.callExpression(registerImportMethod(path, 'mergeProps'), props)] + } + } + + const exprs = [ + t.callExpression(registerImportMethod(path, 'ssrElement'), [ + t.stringLiteral(tagName), + props[0], + childNodes.length + ? hydratable + ? t.arrowFunctionExpression([], childNodes.length === 1 ? childNodes[0] : t.arrayExpression(childNodes)) + : childNodes.length === 1 + ? childNodes[0] + : t.arrayExpression(childNodes) + : t.identifier('undefined'), + t.booleanLiteral(Boolean(topLevel && config.hydratable)), + ]), + ] + return { exprs, template: '', spreadElement: true } +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/ssr/template.js b/packages/babel-plugin-transform-solid-jsx/src/ssr/template.js new file mode 100644 index 000000000000..1679883e54b8 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/ssr/template.js @@ -0,0 +1,74 @@ +import * as t from '@babel/types' + +import { registerImportMethod } from '../shared/utils' + +export function createTemplate(path, result) { + if (!result.template) { + return result.exprs[0] + } + + let template, id + + if (!Array.isArray(result.template)) { + template = t.stringLiteral(result.template) + } else if (result.template.length === 1) { + template = t.stringLiteral(result.template[0]) + } else { + const strings = result.template.map(tmpl => t.stringLiteral(tmpl)) + template = t.arrayExpression(strings) + } + + const templates = + path.scope.getProgramParent().data.templates || + (path.scope.getProgramParent().data.templates = []) + const found = templates.find(tmp => { + if (t.isArrayExpression(tmp.template) && t.isArrayExpression(template)) { + return tmp.template.elements.every( + (el, i) => template.elements[i] && el.value === template.elements[i].value + ) + } + return tmp.template.value === template.value + }) + if (!found) { + id = path.scope.generateUidIdentifier('tmpl$') + templates.push({ + id, + template, + renderer: 'ssr' + }) + } else id = found.id + + if (result.wontEscape) { + if (!Array.isArray(result.template) || result.template.length === 1) return id + else if ( + Array.isArray(result.template) && + result.template.length === 2 && + result.templateValues[0].type === 'CallExpression' && + result.templateValues[0].callee.name === '_$ssrHydrationKey' + ) { + // remove unnecessary ssr call when only hydration key is used + return t.binaryExpression( + '+', + t.binaryExpression( + '+', + t.memberExpression(id, t.numericLiteral(0), true), + result.templateValues[0] + ), + t.memberExpression(id, t.numericLiteral(1), true) + ) + } + } + return t.callExpression( + registerImportMethod(path, 'ssr'), + Array.isArray(result.template) && result.template.length > 1 + ? [id, ...result.templateValues] + : [id] + ) +} + +export function appendTemplates(path, templates) { + const declarators = templates.map(template => { + return t.variableDeclarator(template.id, template.template) + }) + path.node.body.unshift(t.variableDeclaration('var', declarators)) +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/universal/element.js b/packages/babel-plugin-transform-solid-jsx/src/universal/element.js new file mode 100644 index 000000000000..bd37f00edb3c --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/universal/element.js @@ -0,0 +1,324 @@ +import * as t from '@babel/types' + +import { transformNode } from '../shared/transform' +import { + canNativeSpread, + checkLength, + convertCamelToKebabCase, + convertJSXIdentifier, + escapeStringForTemplate, + filterChildren, + getConfig, + getRendererConfig, + getTagName, + getTaroComponentsMap, + isDynamic, + registerImportMethod, + transformCondition, +} from '../shared/utils' + +export function transformElement(path) { + let tagName = getTagName(path.node) + const config = getConfig(path) + if (config.uniqueTransform) { + const taroComponent = getTaroComponentsMap(path).get(tagName) + if (taroComponent) { + tagName = convertCamelToKebabCase(taroComponent) + } + } + const results = { + id: path.scope.generateUidIdentifier('el$'), + declarations: [], + exprs: [], + dynamics: [], + postExprs: [], + tagName, + renderer: 'universal', + } + + results.declarations.push( + t.variableDeclarator( + results.id, + t.callExpression(registerImportMethod(path, 'createElement', getRendererConfig(path, 'universal').moduleName), [ + t.stringLiteral(tagName), + ]) + ) + ) + + transformAttributes(path, results) + transformChildren(path, results) + + return results +} + +function transformAttributes(path, results) { + let children, spreadExpr + let attributes = path.get('openingElement').get('attributes') + const elem = results.id + const hasChildren = path.node.children.length > 0 + const config = getConfig(path) + + // preprocess spreads + if (attributes.some((attribute) => t.isJSXSpreadAttribute(attribute.node))) { + [attributes, spreadExpr] = processSpreads(path, attributes, { + elem, + hasChildren, + wrapConditionals: config.wrapConditionals, + }) + path.get('openingElement').set( + 'attributes', + attributes.map((a) => a.node) + ) + } + + path + .get('openingElement') + .get('attributes') + .forEach((attribute) => { + const node = attribute.node + + let value = node.value + const key = t.isJSXNamespacedName(node.name) + ? `${node.name.namespace.name}:${node.name.name.name}` + : node.name.name + const reservedNameSpace = t.isJSXNamespacedName(node.name) && node.name.namespace.name === 'use' + if (t.isJSXNamespacedName(node.name) && reservedNameSpace && !t.isJSXExpressionContainer(value)) { + node.value = value = t.jsxExpressionContainer(value || t.jsxEmptyExpression()) + } + if (t.isJSXExpressionContainer(value)) { + if (key === 'ref') { + // Normalize expressions for non-null and type-as + while (t.isTSNonNullExpression(value.expression) || t.isTSAsExpression(value.expression)) { + value.expression = value.expression.expression + } + if (t.isLVal(value.expression)) { + const refIdentifier = path.scope.generateUidIdentifier('_ref$') + results.exprs.unshift( + t.variableDeclaration('var', [t.variableDeclarator(refIdentifier, value.expression)]), + t.expressionStatement( + t.conditionalExpression( + t.binaryExpression('===', t.unaryExpression('typeof', refIdentifier), t.stringLiteral('function')), + t.callExpression(registerImportMethod(path, 'use', getRendererConfig(path, 'universal').moduleName), [ + refIdentifier, + elem, + ]), + t.assignmentExpression('=', value.expression, elem) + ) + ) + ) + } else if (t.isFunction(value.expression)) { + results.exprs.unshift( + t.expressionStatement( + t.callExpression(registerImportMethod(path, 'use', getRendererConfig(path, 'universal').moduleName), [ + value.expression, + elem, + ]) + ) + ) + } else if (t.isCallExpression(value.expression)) { + const refIdentifier = path.scope.generateUidIdentifier('_ref$') + results.exprs.unshift( + t.variableDeclaration('var', [t.variableDeclarator(refIdentifier, value.expression)]), + t.expressionStatement( + t.logicalExpression( + '&&', + t.binaryExpression('===', t.unaryExpression('typeof', refIdentifier), t.stringLiteral('function')), + t.callExpression(registerImportMethod(path, 'use', getRendererConfig(path, 'universal').moduleName), [ + refIdentifier, + elem, + ]) + ) + ) + ) + } + } else if (key.startsWith('use:')) { + // Some trick to treat JSXIdentifier as Identifier + node.name.name.type = 'Identifier' + results.exprs.unshift( + t.expressionStatement( + t.callExpression(registerImportMethod(path, 'use', getRendererConfig(path, 'universal').moduleName), [ + node.name.name, + elem, + t.arrowFunctionExpression( + [], + t.isJSXEmptyExpression(value.expression) ? t.booleanLiteral(true) : value.expression + ), + ]) + ) + ) + } else if (key === 'children') { + children = value + } else if ( + config.effectWrapper && + isDynamic(attribute.get('value').get('expression'), { + checkMember: true, + }) + ) { + results.dynamics.push({ elem, key, value: value.expression }) + } else { + results.exprs.push(t.expressionStatement(setAttr(attribute, elem, key, value.expression))) + } + } else { + results.exprs.push(t.expressionStatement(setAttr(attribute, elem, key, value))) + } + }) + if (spreadExpr) results.exprs.push(spreadExpr) + if (!hasChildren && children) { + path.node.children.push(children) + } +} + +export function setAttr(path, elem, name, value, { prevId } = {}) { + if (!value) value = t.booleanLiteral(true) + return t.callExpression( + registerImportMethod(path, 'setProp', getRendererConfig(path, 'universal').moduleName), + prevId ? [elem, t.stringLiteral(name), value, prevId] : [elem, t.stringLiteral(name), value] + ) +} + +function transformChildren(path, results) { + const filteredChildren = filterChildren(path.get('children')) + const multi = checkLength(filteredChildren) + const childNodes = filteredChildren.map(transformNode).reduce((memo, child) => { + if (!child) return memo + const i = memo.length + if (child.text && i && memo[i - 1].text) { + memo[i - 1].template += child.template + } else memo.push(child) + return memo + }, []) + + const appends = [] + childNodes.forEach((child, index) => { + if (!child) return + if (child.tagName && child.renderer !== 'universal') { + throw new Error(`<${child.tagName}> is not supported in <${getTagName(path.node)}>. + Wrap the usage with a component that would render this element, eg. Canvas`) + } + if (child.id) { + const insertNode = registerImportMethod(path, 'insertNode', getRendererConfig(path, 'universal').moduleName) + let insert = child.id + if (child.text) { + const createTextNode = registerImportMethod( + path, + 'createTextNode', + getRendererConfig(path, 'universal').moduleName + ) + if (multi) { + results.declarations.push( + t.variableDeclarator( + child.id, + t.callExpression(createTextNode, [ + t.templateLiteral([t.templateElement({ raw: escapeStringForTemplate(child.template) })], []), + ]) + ) + ) + } else { + insert = t.callExpression(createTextNode, [ + t.templateLiteral([t.templateElement({ raw: escapeStringForTemplate(child.template) })], []), + ]) + } + } + appends.push(t.expressionStatement(t.callExpression(insertNode, [results.id, insert]))) + results.declarations.push(...child.declarations) + results.exprs.push(...child.exprs) + results.dynamics.push(...child.dynamics) + } else if (child.exprs.length) { + const insert = registerImportMethod(path, 'insert', getRendererConfig(path, 'universal').moduleName) + if (multi) { + results.exprs.push( + t.expressionStatement( + t.callExpression(insert, [results.id, child.exprs[0], nextChild(childNodes, index) || t.nullLiteral()]) + ) + ) + } else { + results.exprs.push(t.expressionStatement(t.callExpression(insert, [results.id, child.exprs[0]]))) + } + } + }) + results.exprs.unshift(...appends) +} + +function nextChild(children, index) { + return children[index + 1] && (children[index + 1].id || nextChild(children, index + 1)) +} + +function processSpreads(path, attributes, { elem, hasChildren, wrapConditionals }) { + // TODO: skip but collect the names of any properties after the last spread to not overwrite them + const filteredAttributes = [] + const spreadArgs = [] + let runningObject = [] + let dynamicSpread = false + let firstSpread = false + attributes.forEach((attribute) => { + const node = attribute.node + const key = + !t.isJSXSpreadAttribute(node) && + (t.isJSXNamespacedName(node.name) ? `${node.name.namespace.name}:${node.name.name.name}` : node.name.name) + if (t.isJSXSpreadAttribute(node)) { + firstSpread = true + if (runningObject.length) { + spreadArgs.push(t.objectExpression(runningObject)) + runningObject = [] + } + spreadArgs.push( + isDynamic(attribute.get('argument'), { + checkMember: true, + }) && (dynamicSpread = true) + ? t.isCallExpression(node.argument) && + !node.argument.arguments.length && + !t.isCallExpression(node.argument.callee) && + !t.isMemberExpression(node.argument.callee) + ? node.argument.callee + : t.arrowFunctionExpression([], node.argument) + : node.argument + ) + } else if ( + (firstSpread || + (t.isJSXExpressionContainer(node.value) && + isDynamic(attribute.get('value').get('expression'), { checkMember: true }))) && + canNativeSpread(key, { checkNameSpaces: true }) + ) { + const isContainer = t.isJSXExpressionContainer(node.value) + const dynamic = isContainer && isDynamic(attribute.get('value').get('expression'), { checkMember: true }) + if (dynamic) { + const id = convertJSXIdentifier(node.name) + const expr = + wrapConditionals && + (t.isLogicalExpression(node.value.expression) || t.isConditionalExpression(node.value.expression)) + ? transformCondition(attribute.get('value').get('expression'), true) + : t.arrowFunctionExpression([], node.value.expression) + runningObject.push( + t.objectMethod('get', id, [], t.blockStatement([t.returnStatement(expr.body)]), !t.isValidIdentifier(key)) + ) + } else { + runningObject.push( + t.objectProperty( + t.stringLiteral(key), + isContainer ? node.value.expression : node.value || t.booleanLiteral(true) + ) + ) + } + } else filteredAttributes.push(attribute) + }) + + if (runningObject.length) { + spreadArgs.push(t.objectExpression(runningObject)) + } + + const props = + spreadArgs.length === 1 && !dynamicSpread + ? spreadArgs[0] + : t.callExpression(registerImportMethod(path, 'mergeProps'), spreadArgs) + + return [ + filteredAttributes, + t.expressionStatement( + t.callExpression(registerImportMethod(path, 'spread', getRendererConfig(path, 'universal').moduleName), [ + elem, + props, + t.booleanLiteral(hasChildren), + ]) + ), + ] +} diff --git a/packages/babel-plugin-transform-solid-jsx/src/universal/template.js b/packages/babel-plugin-transform-solid-jsx/src/universal/template.js new file mode 100644 index 000000000000..6aeac7f21465 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/src/universal/template.js @@ -0,0 +1,108 @@ +import * as t from '@babel/types' + +import { getConfig, getNumberedId, registerImportMethod } from '../shared/utils' +import { setAttr } from './element' + +export function createTemplate(path, result, wrap) { + const config = getConfig(path) + if (result.id) { + result.decl = t.variableDeclaration('var', result.declarations) + if ( + !(result.exprs.length || result.dynamics.length || result.postExprs.length) && + result.decl.declarations.length === 1 + ) { + return result.decl.declarations[0].init + } else { + return t.callExpression( + t.arrowFunctionExpression( + [], + t.blockStatement([ + result.decl, + ...result.exprs.concat( + wrapDynamics(path, result.dynamics) || [], + result.postExprs || [] + ), + t.returnStatement(result.id) + ]) + ), + [] + ) + } + } + if (wrap && result.dynamic && config.memoWrapper) { + return t.callExpression(registerImportMethod(path, config.memoWrapper), [result.exprs[0]]) + } + return result.exprs[0] +} + +function wrapDynamics(path, dynamics) { + if (!dynamics.length) return + const config = getConfig(path) + + const effectWrapperId = registerImportMethod(path, config.effectWrapper) + + if (dynamics.length === 1) { + const prevValue = t.identifier('_$p') + + return t.expressionStatement( + t.callExpression(effectWrapperId, [ + t.arrowFunctionExpression( + [prevValue], + setAttr(path, dynamics[0].elem, dynamics[0].key, dynamics[0].value, { + dynamic: true, + prevId: prevValue + }) + ) + ]) + ) + } + + const prevId = t.identifier('_p$') + + /** @type {t.VariableDeclarator[]} */ + const declarations = [] + /** @type {t.ExpressionStatement[]} */ + const statements = [] + /** @type {t.Identifier[]} */ + const properties = [] + + dynamics.forEach(({ elem, key, value }, index) => { + const varIdent = path.scope.generateUidIdentifier('v$') + + const propIdent = t.identifier(getNumberedId(index)) + const propMember = t.memberExpression(prevId, propIdent) + + properties.push(propIdent) + declarations.push(t.variableDeclarator(varIdent, value)) + + statements.push( + t.expressionStatement( + t.logicalExpression( + '&&', + t.binaryExpression('!==', varIdent, propMember), + t.assignmentExpression( + '=', + propMember, + setAttr(path, elem, key, varIdent, { dynamic: true, prevId: propMember }), + ), + ), + ), + ) + }) + + return t.expressionStatement( + t.callExpression(effectWrapperId, [ + t.arrowFunctionExpression( + [prevId], + t.blockStatement([ + t.variableDeclaration('var', declarations), + ...statements, + t.returnStatement(prevId), + ]), + ), + t.objectExpression( + properties.map((id) => t.objectProperty(id, t.identifier('undefined'))), + ), + ]), + ) +} diff --git a/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/aliasOrSameNameImport/code.js b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/aliasOrSameNameImport/code.js new file mode 100644 index 000000000000..01f1ab60dc09 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/aliasOrSameNameImport/code.js @@ -0,0 +1,43 @@ +import { Button, Icon, Text, View as MyView } from '@tarojs/components' +import { Input, RenderWithChildren, RenderWithSlot } from 'somewhere' + +export default function Index() { + return ( + + + Hello world! + + + Hello world2! + + + + {Math.random()} + + }> + button}> + }> + + + + + RenderWithChildren2 + + + + + + + + + + + + + + + + ) +} diff --git a/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/aliasOrSameNameImport/output.js b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/aliasOrSameNameImport/output.js new file mode 100644 index 000000000000..d731d088e247 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/aliasOrSameNameImport/output.js @@ -0,0 +1,125 @@ +import { insert as _$insert } from 'r-custom' +import { createComponent as _$createComponent } from 'r-custom' +import { createTextNode as _$createTextNode } from 'r-custom' +import { insertNode as _$insertNode } from 'r-custom' +import { setProp as _$setProp } from 'r-custom' +import { createElement as _$createElement } from 'r-custom' +import { Button, Icon, Text, View as MyView } from '@tarojs/components' +import { Input, RenderWithChildren, RenderWithSlot } from 'somewhere' +export default function Index() { + return (() => { + var _el$ = _$createElement('view'), + _el$2 = _$createElement('view'), + _el$3 = _$createElement('text'), + _el$5 = _$createElement('view'), + _el$6 = _$createElement('text'), + _el$8 = _$createElement('button'), + _el$10 = _$createElement('view'), + _el$11 = _$createElement('icon') + _$insertNode(_el$, _el$2) + _$insertNode(_el$, _el$5) + _$insertNode(_el$, _el$8) + _$insertNode(_el$, _el$10) + _$insertNode(_el$, _el$11) + _$setProp(_el$, 'class', 'index') + _$insertNode(_el$2, _el$3) + _$insertNode(_el$3, _$createTextNode(`Hello world! `)) + _$insertNode(_el$5, _el$6) + _$insertNode(_el$6, _$createTextNode(`Hello world2! `)) + _$insertNode(_el$8, _$createTextNode(`set class`)) + _$insert(_el$, _$createComponent(Input, {}), _el$10) + _$insert(_el$10, () => Math.random()) + _$setProp(_el$11, 'type', 'success') + _$insert( + _el$, + _$createComponent(RenderWithSlot, { + get header() { + return _$createElement('view') + }, + }), + null + ) + _$insert( + _el$, + _$createComponent(RenderWithSlot, { + get header() { + return (() => { + var _el$24 = _$createElement('button') + _$insertNode(_el$24, _$createTextNode(`button`)) + return _el$24 + })() + }, + }), + null + ) + _$insert( + _el$, + _$createComponent(RenderWithSlot, { + get header() { + return _$createComponent(Input, {}) + }, + }), + null + ) + _$insert( + _el$, + _$createComponent(RenderWithChildren, { + get children() { + var _el$12 = _$createElement('button') + _$insertNode(_el$12, _$createTextNode(`RenderWithChildren1`)) + return _el$12 + }, + }), + null + ) + _$insert( + _el$, + _$createComponent(RenderWithChildren, { + get children() { + var _el$14 = _$createElement('view') + _$insertNode(_el$14, _$createTextNode(`RenderWithChildren2`)) + return _el$14 + }, + }), + null + ) + _$insert( + _el$, + _$createComponent(RenderWithChildren, { + get children() { + var _el$16 = _$createElement('button'), + _el$17 = _$createElement('view') + _$insertNode(_el$16, _el$17) + _$insertNode(_el$17, _$createTextNode(`variant1`)) + return _el$16 + }, + }), + null + ) + _$insert( + _el$, + _$createComponent(RenderWithChildren, { + get children() { + var _el$19 = _$createElement('view'), + _el$20 = _$createElement('button') + _$insertNode(_el$19, _el$20) + _$insertNode(_el$20, _$createTextNode(`variant2`)) + return _el$19 + }, + }), + null + ) + _$insert( + _el$, + _$createComponent(RenderWithChildren, { + get children() { + var _el$22 = _$createElement('view') + _$insert(_el$22, _$createComponent(Input, {})) + return _el$22 + }, + }), + null + ) + return _el$ + })() +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/options.json b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/options.json new file mode 100644 index 000000000000..de4498d6a0bd --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/options.json @@ -0,0 +1,9 @@ +{ + "pluginOptions": { + "moduleName": "r-custom", + "builtIns": ["For", "Show"], + "generate": "universal", + "staticMarker": "@once", + "uniqueTransform": true + } +} diff --git a/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/simpleElements/code.js b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/simpleElements/code.js new file mode 100644 index 000000000000..59db47cecb08 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/simpleElements/code.js @@ -0,0 +1,18 @@ +import { Button, Icon, Text, View } from '@tarojs/components' + +export default function Index() { + return ( + + + Hello world! + + + Hello world2! + + + + {Math.random()} + + + ) +} diff --git a/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/simpleElements/output.js b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/simpleElements/output.js new file mode 100644 index 000000000000..f8b1934103b7 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/simpleElements/output.js @@ -0,0 +1,32 @@ +import { insert as _$insert } from 'r-custom' +import { createTextNode as _$createTextNode } from 'r-custom' +import { insertNode as _$insertNode } from 'r-custom' +import { setProp as _$setProp } from 'r-custom' +import { createElement as _$createElement } from 'r-custom' +import { Button, Icon, Text, View } from '@tarojs/components' +export default function Index() { + return (() => { + var _el$ = _$createElement('view'), + _el$2 = _$createElement('view'), + _el$3 = _$createElement('text'), + _el$5 = _$createElement('view'), + _el$6 = _$createElement('text'), + _el$8 = _$createElement('button'), + _el$10 = _$createElement('view'), + _el$11 = _$createElement('icon') + _$insertNode(_el$, _el$2) + _$insertNode(_el$, _el$5) + _$insertNode(_el$, _el$8) + _$insertNode(_el$, _el$10) + _$insertNode(_el$, _el$11) + _$setProp(_el$, 'class', 'index') + _$insertNode(_el$2, _el$3) + _$insertNode(_el$3, _$createTextNode(`Hello world! `)) + _$insertNode(_el$5, _el$6) + _$insertNode(_el$6, _$createTextNode(`Hello world2! `)) + _$insertNode(_el$8, _$createTextNode(`set class`)) + _$insert(_el$10, () => Math.random()) + _$setProp(_el$11, 'type', 'success') + return _el$ + })() +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/unTransformElements/code.js b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/unTransformElements/code.js new file mode 100644 index 000000000000..59db47cecb08 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/unTransformElements/code.js @@ -0,0 +1,18 @@ +import { Button, Icon, Text, View } from '@tarojs/components' + +export default function Index() { + return ( + + + Hello world! + + + Hello world2! + + + + {Math.random()} + + + ) +} diff --git a/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/unTransformElements/options.js b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/unTransformElements/options.js new file mode 100644 index 000000000000..339dbca19e9f --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/unTransformElements/options.js @@ -0,0 +1,5 @@ +module.exports = { + pluginOptions: { + uniqueTransform: false + } +} diff --git a/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/unTransformElements/output.js b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/unTransformElements/output.js new file mode 100644 index 000000000000..f4a449364457 --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/test/__unique_transform_fixtures__/unTransformElements/output.js @@ -0,0 +1,36 @@ +import { createComponent as _$createComponent } from 'r-custom' +import { Button, Icon, Text, View } from '@tarojs/components' +export default function Index() { + return _$createComponent(View, { + class: 'index', + get children() { + return [ + _$createComponent(View, { + get children() { + return _$createComponent(Text, { + children: 'Hello world! ', + }) + }, + }), + _$createComponent(View, { + get children() { + return _$createComponent(Text, { + children: 'Hello world2! ', + }) + }, + }), + _$createComponent(Button, { + children: 'set class', + }), + _$createComponent(View, { + get children() { + return Math.random() + }, + }), + _$createComponent(Icon, { + type: 'success', + }), + ] + }, + }) +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-solid-jsx/test/unique-transform.spec.js b/packages/babel-plugin-transform-solid-jsx/test/unique-transform.spec.js new file mode 100644 index 000000000000..04e1b3ff2c6a --- /dev/null +++ b/packages/babel-plugin-transform-solid-jsx/test/unique-transform.spec.js @@ -0,0 +1,10 @@ +const path = require('path') +const pluginTester = require('babel-plugin-tester').default +const plugin = require('../dist') + +pluginTester({ + plugin, + title: 'Convert JSX', + fixtures: path.join(__dirname, '__unique_transform_fixtures__'), + snapshot: true +}) diff --git a/packages/babel-plugin-transform-taroapi/__tests__/index.spec.ts b/packages/babel-plugin-transform-taroapi/__tests__/index.spec.ts index feecca114cce..834ecdde1d88 100644 --- a/packages/babel-plugin-transform-taroapi/__tests__/index.spec.ts +++ b/packages/babel-plugin-transform-taroapi/__tests__/index.spec.ts @@ -1,7 +1,7 @@ import * as babel from '@babel/core' import * as t from '@babel/types' -import * as definition from '@tarojs/plugin-platform-h5/dist/definition.json' +import * as definition from '../../taro-platform-h5/dist/definition.json' import plugin from '../src' type ImportType = babel.types.ImportSpecifier | babel.types.ImportDefaultSpecifier | babel.types.ImportNamespaceSpecifier diff --git a/packages/babel-plugin-transform-taroapi/babel.config.json b/packages/babel-plugin-transform-taroapi/babel.config.json index 52bbf45dfe07..6117dbb1e9d8 100644 --- a/packages/babel-plugin-transform-taroapi/babel.config.json +++ b/packages/babel-plugin-transform-taroapi/babel.config.json @@ -4,8 +4,7 @@ "@babel/preset-env", { "targets": { - "ios": "9", - "android": "4.4" + "node": "current" } } ] diff --git a/packages/babel-plugin-transform-taroapi/package.json b/packages/babel-plugin-transform-taroapi/package.json index 00b4deebebe5..9a0b05c8989b 100644 --- a/packages/babel-plugin-transform-taroapi/package.json +++ b/packages/babel-plugin-transform-taroapi/package.json @@ -1,8 +1,13 @@ { "name": "babel-plugin-transform-taroapi", - "version": "3.6.34", + "version": "4.0.4", + "author": "O2Team", + "license": "MIT", "main": "dist/index.js", "scripts": { + "prod": "pnpm run build", + "prebuild": "pnpm run clean", + "clean": "rimraf --impl=move-remove ./dist", "build": "tsc", "test": "cross-env NODE_ENV=jest jest", "test:ci": "cross-env NODE_ENV=jest jest --ci -i --coverage --silent", @@ -10,17 +15,18 @@ "test:coverage": "cross-env NODE_ENV=jest jest --coverage", "updateSnapshot": "cross-env NODE_ENV=jest jest --updateSnapshot" }, + "engines": { + "node": ">= 18" + }, "dependencies": { "lodash": "^4.17.21" }, "devDependencies": { - "@babel/core": "^7.14.5", - "@babel/types": "^7.14.5", - "babel-jest": "^29.5.0", - "jest": "^29.3.1", - "jest-cli": "^29.3.1", - "ts-jest": "^29.0.5", - "typescript": "^4.7.4" + "@babel/core": "7.24.4", + "@babel/types": "7.24.0", + "@types/babel__core": "7.20.5" }, - "license": "MIT" + "peerDependencies": { + "@babel/core": "^7.0.0" + } } diff --git a/packages/babel-plugin-transform-taroapi/src/index.ts b/packages/babel-plugin-transform-taroapi/src/index.ts index c0c9a848db70..f43e91fb737a 100644 --- a/packages/babel-plugin-transform-taroapi/src/index.ts +++ b/packages/babel-plugin-transform-taroapi/src/index.ts @@ -60,7 +60,7 @@ const plugin = function (babel: typeof BabelCore): BabelCore.PluginObj { this.apis = apis }, visitor: { - ImportDeclaration (ast) { + ImportDeclaration (ast: BabelCore.NodePath) { if (ast.node.source.value !== this.packageName) return ast.node.specifiers.forEach(node => { @@ -91,7 +91,7 @@ const plugin = function (babel: typeof BabelCore): BabelCore.PluginObj { } }) }, - MemberExpression (ast) { + MemberExpression (ast: BabelCore.NodePath) { /* 处理Taro.xxx */ const isTaro = t.isIdentifier(ast.node.object, { name: taroName }) const property = ast.node.property @@ -110,7 +110,7 @@ const plugin = function (babel: typeof BabelCore): BabelCore.PluginObj { // 同一api使用多次, 读取变量名 if (this.apis.has(propertyName)) { - const parentNode = ast.parent + const parentNode = ast.parent as BabelCore.types.AssignmentExpression const isAssignment = t.isAssignmentExpression(parentNode) && parentNode.left === ast.node if (!isAssignment) { @@ -129,7 +129,7 @@ const plugin = function (babel: typeof BabelCore): BabelCore.PluginObj { needDefault = true } }, - CallExpression (ast) { + CallExpression (ast: BabelCore.NodePath) { if (!ast.scope.hasReference(this.canIUse)) return const callee = ast.node.callee if (t.isMemberExpression(callee) && t.isIdentifier(callee.object, { name: taroName })) { @@ -188,7 +188,7 @@ const plugin = function (babel: typeof BabelCore): BabelCore.PluginObj { ast.node.specifiers = namedImports } }, - CallExpression (ast) { + CallExpression (ast: BabelCore.NodePath) { if (!invokedApis.has(that.canIUse)) return const callee = ast.node.callee const { name } = t.identifier(invokedApis.get(that.canIUse)!) diff --git a/packages/babel-preset-taro/README.md b/packages/babel-preset-taro/README.md index 6f528de67734..33fd18f1d0cf 100644 --- a/packages/babel-preset-taro/README.md +++ b/packages/babel-preset-taro/README.md @@ -43,13 +43,7 @@ module.exports = { - `react-refresh/babel` -#### 3. Vue - -##### presetes - -- `@vue/babel-preset-jsx` - -#### 4. Vue3 +#### 3. Vue3 ##### plugins @@ -80,16 +74,16 @@ module.exports = { ### vueJsx :::note -只在使用 **Vue/Vue3** 时生效。 +只在使用 **Vue3** 时生效。 ::: **默认值**:`true` **类型**:`true` | `false` | `object` -是否使用 `@vue/babel-preset-jsx`(Vue)或 `@vue/babel-plugin-jsx`(Vue3)来支持使用 `jsx`。 +是否使用 `@vue/babel-plugin-jsx` 来支持使用 `jsx`。 -当传入一个 `object` 时,等同于设置为 `true`,且该 `object` 将会作为 `@vue/babel-preset-jsx`(Vue)或 `@vue/babel-plugin-jsx`(Vue3)的参数。 +当传入一个 `object` 时,等同于设置为 `true`,且该 `object` 将会作为 `@vue/babel-plugin-jsx` 的参数。 ### targets diff --git a/packages/babel-preset-taro/__tests__/index.spec.js b/packages/babel-preset-taro/__tests__/index.spec.js index 9ebfffff16dd..6702adb31a30 100644 --- a/packages/babel-preset-taro/__tests__/index.spec.js +++ b/packages/babel-preset-taro/__tests__/index.spec.js @@ -2,20 +2,6 @@ const babelPresetTaro = require('..') describe('babel-preset-taro', () => { - it('nerv', () => { - const config = babelPresetTaro({}, { - framework: 'nerv' - }) - - expect(config.sourceType).toBe('unambiguous') - - const [override] = config.overrides - - const [, [_, reactConfig]] = override.presets - expect(reactConfig.pragma).toBe('Nerv.createElement') - expect(reactConfig.pragmaFrag).toBe('Nerv.Fragment') - }) - it('react', () => { const config = babelPresetTaro({}, { framework: 'react' @@ -24,20 +10,6 @@ describe('babel-preset-taro', () => { expect(config.sourceType).toBe('unambiguous') }) - it('vue', () => { - const config = babelPresetTaro({}, { - framework: 'vue' - }) - - expect(config.sourceType).toBe('unambiguous') - - const [override] = config.overrides - - const [, [jsxPreset, jsxOptions]] = override.presets - expect(jsxPreset === require('@vue/babel-preset-jsx')).toBeTruthy() - expect(jsxOptions).toEqual({}) - }) - it('vue3', () => { const config = babelPresetTaro({}, { framework: 'vue3' @@ -52,20 +24,6 @@ describe('babel-preset-taro', () => { expect(jsxOptions).toEqual({}) }) - it('vue without jsx', () => { - const config = babelPresetTaro({}, { - framework: 'vue', - vueJsx: false - }) - - expect(config.sourceType).toBe('unambiguous') - - const [override] = config.overrides - - const [, jsxPreset] = override.presets - expect(jsxPreset).toBeUndefined() - }) - it('vue3 without jsx', () => { const config = babelPresetTaro({}, { framework: 'vue3', @@ -95,30 +53,19 @@ describe('babel-preset-taro', () => { expect(tsconfig.jsxPragma === 'React').toBeTruthy() }) - it('typescript nerv', () => { - const config = babelPresetTaro({}, { - framework: 'nerv', - ts: true - }) - - const [override] = config.overrides - - const [, , [ts, tsconfig]] = override.presets - expect(typeof ts.default === 'function').toBeTruthy() - expect(tsconfig.jsxPragma === 'Nerv').toBeTruthy() - }) - - it('typescript vue', () => { + it('typescript vue3', () => { const config = babelPresetTaro({}, { - framework: 'vue', + framework: 'vue3', ts: true }) - const [override, vueOverride] = config.overrides - const [, , [ts, tsconfig]] = override.presets + const [, vueOverride] = config.overrides + const [[ts, tsConfig]] = vueOverride.presets expect(typeof ts.default === 'function').toBeTruthy() - expect(tsconfig.hasOwnProperty('jsxPragma') === false).toBeTruthy() + expect(tsConfig.hasOwnProperty('jsxPragma') === false).toBeTruthy() + expect(tsConfig.allExtensions).toBeTruthy() + expect(tsConfig.isTSX).toBeTruthy() expect(vueOverride.include.test('a.vue')).toBeTruthy() }) diff --git a/packages/babel-preset-taro/index.js b/packages/babel-preset-taro/index.js index df04463e4bd8..98c36bb90e6b 100644 --- a/packages/babel-preset-taro/index.js +++ b/packages/babel-preset-taro/index.js @@ -1,6 +1,6 @@ const path = require('path') -function hasBrowserslist () { +function hasBrowserslist() { const fs = require('@tarojs/helper').fs const root = process.cwd() try { @@ -28,64 +28,78 @@ module.exports = (_, options = {}) => { const presets = [] const plugins = [] const overrides = [] - const isReact = options.framework === 'react' || options.framework === 'preact' - const isNerv = options.framework === 'nerv' - const isVue = options.framework === 'vue' - const isVue3 = options.framework === 'vue3' + const isVite = options.compiler === 'vite' + // vite 不需要 react 的 preset,在内部已经处理了 + const isReact = options.framework === 'react' || options.framework === 'preact' && !isVite + // vite 不需要 solid 的 preset,在内部已经处理了 + const isSolid = options.framework === 'solid' && !isVite + // vite 不需要 vue 的 preset,在内部已经处理了 + const isVue3 = options.framework === 'vue3' && !isVite + // TODO:后续改为在 vite harmony 中实现对 ts 的支持 + const isHarmony = process.env.TARO_PLATFORM === 'harmony' + // vite 不需要使用 babel 处理 ts,在 esbuild 中处理了 + const isTs = options.ts && (!isVite || isHarmony) const moduleName = options.framework.charAt(0).toUpperCase() + options.framework.slice(1) const presetReactConfig = options.react || {} - if (isNerv) { - presets.push([require('@babel/preset-react'), { - pragma: `${moduleName}.createElement`, - pragmaFrag: `${moduleName}.Fragment`, - ...presetReactConfig - }]) - } - if (isReact) { - presets.push([require('@babel/preset-react'), { - runtime: options.reactJsxRuntime || 'automatic', - ...presetReactConfig - }]) + presets.push([ + require('@babel/preset-react'), + { + runtime: options.reactJsxRuntime || 'automatic', + ...presetReactConfig, + }, + ]) if (process.env.TARO_PLATFORM === 'web' && process.env.NODE_ENV !== 'production' && options.hot !== false) { if (options.framework === 'react') { plugins.push([require('react-refresh/babel'), { skipEnvCheck: true }]) } else if (options.framework === 'preact') { overrides.push({ include: /\.[jt]sx$/, - plugins: [require('@prefresh/babel-plugin')] + plugins: [require('@prefresh/babel-plugin')], }) } } + } else if (isSolid) { + const solidOptions = {} + if (process.env.TARO_PLATFORM !== 'web') { + Object.assign(solidOptions, { + moduleName: '@tarojs/plugin-framework-solid/dist/reconciler', + generate: 'universal', + uniqueTransform: true, + }) + } + presets.push([ + require('babel-plugin-transform-solid-jsx'), + solidOptions, + ]) } - if (isVue || isVue3) { + if (isVue3) { if (options.vueJsx !== false) { const jsxOptions = typeof options.vueJsx === 'object' ? options.vueJsx : {} - if (isVue) { - presets.push([require('@vue/babel-preset-jsx'), jsxOptions]) - } else { - plugins.push([require('@vue/babel-plugin-jsx'), jsxOptions]) - } + plugins.push([require('@vue/babel-plugin-jsx'), jsxOptions]) } } - if (options.ts) { + if (isTs) { const config = typeof options.ts === 'object' ? options.ts : {} - if (isNerv || isReact) { + if (isReact) { config.jsxPragma = moduleName } - if (isVue || isVue3) { + if (isVue3) { overrides.push({ include: /\.vue$/, - presets: [[require('@babel/preset-typescript'), { allExtensions: true, isTSX: true }]] + presets: [[require('@babel/preset-typescript'), { allExtensions: true, isTSX: true }]], }) } presets.push([require('@babel/preset-typescript'), config]) } - const runtimePath = process.env.NODE_ENV === 'jest' || process.env.NODE_ENV === 'test' ? false : path.dirname(require.resolve('@babel/runtime/package.json')) + const runtimePath = + process.env.NODE_ENV === 'jest' || process.env.NODE_ENV === 'test' + ? false + : path.dirname(require.resolve('@babel/runtime/package.json')) const runtimeVersion = require('@babel/runtime/package.json').version const { loose = false, @@ -115,7 +129,7 @@ module.exports = (_, options = {}) => { // By default transform-runtime assumes that @babel/runtime@7.0.0-beta.0 is installed, which means helpers introduced later than 7.0.0-beta.0 will be inlined instead of imported. // See https://github.com/babel/babel/issues/10261 // And https://github.com/facebook/docusaurus/pull/2111 - version = runtimeVersion + version = runtimeVersion, } = options // resolve targets @@ -127,7 +141,7 @@ module.exports = (_, options = {}) => { } else if (!hasBrowserslist()) { targets = { ios: '9', - android: '5' + android: '5', } } @@ -142,7 +156,7 @@ module.exports = (_, options = {}) => { include, exclude, shippedProposals, - forceAllTransforms + forceAllTransforms, } let transformRuntimeCorejs = false @@ -162,23 +176,33 @@ module.exports = (_, options = {}) => { presets.unshift([require('@babel/preset-env'), envOptions]) plugins.push( - [require('@babel/plugin-proposal-decorators'), { - decoratorsBeforeExport, - legacy: decoratorsLegacy !== false - }], + [ + require('@babel/plugin-proposal-decorators'), + { + decoratorsBeforeExport, + legacy: decoratorsLegacy !== false, + }, + ], [require('@babel/plugin-proposal-class-properties'), { loose }] ) - plugins.push([require('@babel/plugin-transform-runtime'), { - regenerator: true, - corejs: transformRuntimeCorejs, - helpers: true, - useESModules: process.env.NODE_ENV !== 'test', - absoluteRuntime, - version - }]) - - if (typeof options['dynamic-import-node'] === 'boolean' ? options['dynamic-import-node'] : process.env.TARO_PLATFORM !== 'web') { + plugins.push([ + require('@babel/plugin-transform-runtime'), + { + regenerator: true, + corejs: transformRuntimeCorejs, + helpers: true, + useESModules: process.env.NODE_ENV !== 'test', + absoluteRuntime, + version, + }, + ]) + + if ( + typeof options['dynamic-import-node'] === 'boolean' + ? options['dynamic-import-node'] + : process.env.TARO_PLATFORM !== 'web' + ) { plugins.push([require('babel-plugin-dynamic-import-node')]) } @@ -186,10 +210,13 @@ module.exports = (_, options = {}) => { return { sourceType: 'unambiguous', - overrides: [{ - exclude: [/@babel[/|\\\\]runtime/, /core-js/, /\bwebpack\/buildin\b/], - presets, - plugins - }, ...overrides] + overrides: [ + { + exclude: [/@babel[/|\\\\]runtime/, /core-js/, /\bwebpack\/buildin\b/], + presets, + plugins, + }, + ...overrides, + ], } } diff --git a/packages/babel-preset-taro/package.json b/packages/babel-preset-taro/package.json index 39d20f15f09f..8f5972dee07a 100644 --- a/packages/babel-preset-taro/package.json +++ b/packages/babel-preset-taro/package.json @@ -1,10 +1,10 @@ { "name": "babel-preset-taro", - "version": "3.6.34", + "version": "4.0.4", "description": "Taro babel preset", - "author": "yuche ", - "homepage": "https://github.com/nervjs/taro/tree/master/packages/babel-preset-taro#readme", + "author": "O2Team", "license": "MIT", + "homepage": "https://github.com/nervjs/taro/tree/main/packages/babel-preset-taro#readme", "main": "index.js", "files": [ "rn/", @@ -16,44 +16,67 @@ "url": "git+https://github.com/NervJS/taro.git" }, "scripts": { - "test": "jest --collectCoverage", + "test": "jest", "test:ci": "jest --ci -i --coverage --silent" }, "bugs": { "url": "https://github.com/NervJS/taro/issues" }, + "engines": { + "node": ">= 18" + }, "dependencies": { - "@babel/plugin-proposal-class-properties": "^7.14.5", - "@babel/plugin-proposal-decorators": "^7.14.5", - "@babel/plugin-syntax-jsx": "^7.14.5", - "@babel/plugin-transform-runtime": "^7.14.5", - "@babel/preset-env": "^7.14.5", - "@babel/preset-react": "^7.14.5", - "@babel/preset-typescript": "^7.14.5", - "@babel/runtime": "^7.14.5", - "@babel/runtime-corejs3": "^7.14.5", + "@babel/plugin-proposal-decorators": "^7.24.1", + "@babel/plugin-transform-class-properties": "^7.24.1", + "@babel/plugin-transform-runtime": "^7.24.3", + "@babel/preset-env": "^7.24.4", + "@babel/preset-typescript": "^7.24.1", + "@babel/runtime": "^7.24.4", + "@babel/runtime-corejs3": "^7.24.4", + "@rnx-kit/babel-preset-metro-react-native": "^1.1.8", "@tarojs/helper": "workspace:*", - "babel-plugin-dynamic-import-node": "2.3.3", + "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-minify-dead-code-elimination": "^0.5.2", "babel-plugin-transform-imports-api": "1.0.0", - "core-js": "^3.6.5", - "lodash": "^4.17.21", - "metro-react-native-babel-preset": "^0.72.1", - "react-refresh": "^0.11.0" + "babel-plugin-transform-solid-jsx": "workspace:*", + "core-js": "^3.36.1" }, "devDependencies": { - "@babel/core": "^7.14.5", - "@prefresh/babel-plugin": "^0.4.1", - "@tarojs/shared": "workspace:*", - "@vue/babel-plugin-jsx": "^1.0.6", - "@vue/babel-preset-jsx": "^1.2.4", - "jest": "^29.3.1", - "jest-cli": "^29.3.1", - "jest-environment-node": "^29.5.0", - "ts-jest": "^29.0.5", - "typescript": "^4.7.4" + "@babel/core": "^7.24.4", + "@babel/preset-react": "^7.24.1", + "@prefresh/babel-plugin": "^0.5.1", + "@tarojs/taro-rn": "workspace:*", + "@vue/babel-plugin-jsx": "^1.2.2", + "babel-preset-solid": "^1.8.16", + "react-refresh": "^0.14.0" }, "peerDependencies": { - "@babel/core": "*" + "@babel/core": "^7.0.0", + "@babel/preset-react": "^7.24.1", + "@prefresh/babel-plugin": "^0.5.1", + "@tarojs/taro-rn": "workspace:*", + "@vue/babel-plugin-jsx": "^1.2.2", + "babel-preset-solid": "^1.8.16", + "react-refresh": "^0.14.0" + }, + "peerDependenciesMeta": { + "@babel/preset-react": { + "optional": true + }, + "react-refresh": { + "optional": true + }, + "@prefresh/babel-plugin": { + "optional": true + }, + "babel-preset-solid": { + "optional": true + }, + "@vue/babel-plugin-jsx": { + "optional": true + }, + "@tarojs/taro-rn": { + "optional": true + } } } diff --git a/packages/babel-preset-taro/rn/index.js b/packages/babel-preset-taro/rn/index.js index 42ef78216ed3..6c27266d05e6 100644 --- a/packages/babel-preset-taro/rn/index.js +++ b/packages/babel-preset-taro/rn/index.js @@ -1,6 +1,9 @@ -const reactNativeBabelPreset = require('metro-react-native-babel-preset') +const reactNativeBabelPreset = require('@rnx-kit/babel-preset-metro-react-native') module.exports = (_, options = {}) => { + if (!process.env.NODE_ENV) { + process.env.NODE_ENV = 'development' + } const { decoratorsBeforeExport, decoratorsLegacy diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 9f1887c445e9..8919b7b31a80 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,16 +1,16 @@ { "name": "@tarojs/create-app", - "version": "3.6.34", + "version": "4.0.4", "description": "create taro app with one command", - "author": "VincentW ", - "homepage": "https://github.com/nervjs/taro/tree/master/packages/create-app#readme", + "author": "O2Team", "license": "MIT", "main": "./dist/index.js", "scripts": { + "prod": "pnpm run build", + "prebuild": "pnpm run clean", + "clean": "rimraf --impl=move-remove ./dist", "dev": "tsc -w", - "prod": "tsc", - "build": "run-s clean prod", - "clean": "rimraf dist" + "build": "tsc" }, "files": [ "dist" @@ -27,7 +27,7 @@ "cli" ], "engines": { - "node": ">=12" + "node": ">= 18" }, "dependencies": { "@tarojs/cli": "workspace:*", diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts old mode 100755 new mode 100644 diff --git a/packages/create-app/src/index.ts b/packages/create-app/src/index.ts old mode 100755 new mode 100644 diff --git a/packages/create-app/src/util/index.ts b/packages/create-app/src/util/index.ts index 7efd5e0052c5..ebff9b4ffe18 100644 --- a/packages/create-app/src/util/index.ts +++ b/packages/create-app/src/util/index.ts @@ -1,4 +1,4 @@ -import * as path from 'path' +import * as path from 'node:path' export function getRootPath (): string { return path.resolve(__dirname, '../../') diff --git a/packages/css-to-react-native/package.json b/packages/css-to-react-native/package.json index 61caa42a316f..360035e1f558 100644 --- a/packages/css-to-react-native/package.json +++ b/packages/css-to-react-native/package.json @@ -1,17 +1,20 @@ { "name": "taro-css-to-react-native", "description": "Convert CSS text to a React Native stylesheet object", - "version": "3.6.34", - "main": "dist/index.js", + "version": "4.0.4", + "author": "O2Team", "license": "MIT", + "main": "dist/index.js", + "types": "index.d.ts", "files": [ "dist", "src", - "index.d.ts", - "CHANGELOG.md", - "README.md" + "index.d.ts" ], "scripts": { + "prod": "pnpm run build", + "prebuild": "pnpm run clean", + "clean": "rimraf --impl=move-remove ./dist", "build": "babel src --ignore *.spec.js --out-dir ./dist", "test": "jest" }, @@ -47,17 +50,14 @@ "/node_modules" ] }, + "engines": { + "node": ">= 18" + }, "dependencies": { - "camelize": "^1.0.0", + "camelize": "^1.0.1", "css": "^3.0.0", "css-color-keywords": "^1.0.0", "css-mediaquery": "^0.1.2", - "postcss-value-parser": "^3.3.0" - }, - "devDependencies": { - "@babel/core": "^7.14.5", - "babel-jest": "^29.5.0", - "jest": "^29.3.1", - "jest-cli": "^29.3.1" + "postcss-value-parser": "^4.2.0" } } diff --git a/packages/eslint-config-taro/nerv.js b/packages/eslint-config-taro/nerv.js deleted file mode 100644 index d7c3858e5527..000000000000 --- a/packages/eslint-config-taro/nerv.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = Object.assign({}, require('./index'), { - settings: { - react: { - pragma: 'Nerv', - version: '15.0' - } - } -}) diff --git a/packages/eslint-config-taro/package.json b/packages/eslint-config-taro/package.json index f63ed60268ae..cc2abd7de9f1 100644 --- a/packages/eslint-config-taro/package.json +++ b/packages/eslint-config-taro/package.json @@ -1,7 +1,9 @@ { "name": "eslint-config-taro", - "version": "3.6.34", + "version": "4.0.4", "description": "Taro specific linting rules for ESLint", + "author": "O2Team", + "license": "MIT", "main": "index.js", "files": [ "rules", @@ -9,10 +11,7 @@ "html-tags.js", "react.js", "preact.js", - "nerv.js", - "vue.js", - "vue3.js", - "README.md" + "vue3.js" ], "repository": { "type": "git", @@ -23,23 +22,30 @@ "react", "jsx" ], - "dependencies": { - "@babel/eslint-parser": "^7.17.0", - "@typescript-eslint/parser": "^6.2.0" + "engines": { + "node": ">= 18" }, - "devDependencies": { - "@babel/core": "^7.14.5", + "dependencies": { + "@babel/eslint-parser": "^7.24.1", + "@typescript-eslint/parser": "^6.2.0", "@typescript-eslint/eslint-plugin": "^6.2.0", - "eslint": "^8.12.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-react": "^7.4.0", - "eslint-plugin-react-hooks": "^4.4.0", - "eslint-plugin-vue": "^8.6.0", - "typescript": "^4.7.4" + "eslint-plugin-import": "^2.29.1" }, "peerDependencies": { - "eslint": "*" + "eslint": "^8", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.4.0", + "eslint-plugin-vue": "^9.17.0" }, - "author": "O2Team", - "license": "MIT" + "peerDependenciesMeta": { + "eslint-plugin-react": { + "optional": true + }, + "eslint-plugin-react-hooks": { + "optional": true + }, + "eslint-plugin-vue": { + "optional": true + } + } } diff --git a/packages/eslint-config-taro/vue.js b/packages/eslint-config-taro/vue.js deleted file mode 100644 index 1fe40f9a77bf..000000000000 --- a/packages/eslint-config-taro/vue.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - extends: [ - // add more generic rulesets here, such as: - // 'eslint:recommended', - 'plugin:vue/recommended' - ], - parserOptions: { - parser: require.resolve('@babel/eslint-parser') - } -} diff --git a/packages/jest-helper/package.json b/packages/jest-helper/package.json new file mode 100644 index 000000000000..c4e3e8257eb9 --- /dev/null +++ b/packages/jest-helper/package.json @@ -0,0 +1,28 @@ +{ + "name": "jest-taro-helper", + "version": "4.0.4", + "description": "jest helper for taro", + "private": true, + "author": "O2Team", + "license": "MIT", + "main": "index.js", + "files": [ + "lib" + ], + "scripts": { + "prod": "pnpm run build", + "prebuild": "pnpm run clean", + "clean": "rimraf --impl=move-remove ./lib", + "build": "tsc", + "dev": "tsc -w" + }, + "keywords": [], + "engines": { + "node": ">= 18" + }, + "dependencies": { + "@jest/test-sequencer": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0" + } +} diff --git a/packages/jest-helper/src/resolver.ts b/packages/jest-helper/src/resolver.ts new file mode 100644 index 000000000000..571ab332e5d9 --- /dev/null +++ b/packages/jest-helper/src/resolver.ts @@ -0,0 +1,14 @@ +import type { ResolverOptions } from 'jest-resolve' + +export = (path: string, options: ResolverOptions) => { + return options.defaultResolver(path, { + ...options, + packageFilter: (pkg) => { + const field = ['module', 'main'].find((e) => pkg[e] && typeof pkg[e] === 'string') + if (field) { + pkg.main = pkg[field] || pkg.main + } + return pkg + }, + }) +} diff --git a/packages/jest-helper/src/sequencer.ts b/packages/jest-helper/src/sequencer.ts new file mode 100644 index 000000000000..cddc40cb46b2 --- /dev/null +++ b/packages/jest-helper/src/sequencer.ts @@ -0,0 +1,10 @@ +import type { Test } from 'jest-runner' + +const Sequencer = require('@jest/test-sequencer').default + +export default class CustomSequencer extends Sequencer { + sort (tests: Test[]) { + const copyTests = Array.from(tests) + return copyTests.sort((testA, testB) => testA.path.localeCompare(testB.path)) + } +} diff --git a/packages/jest-helper/src/snapshot/resolver.ts b/packages/jest-helper/src/snapshot/resolver.ts new file mode 100644 index 000000000000..2ec43f1fd6b5 --- /dev/null +++ b/packages/jest-helper/src/snapshot/resolver.ts @@ -0,0 +1,20 @@ +import * as path from 'node:path' + +const TEST_DIR = '__tests__' +const SNAPSHOT_DIR = '__snapshots__' + +export const resolveSnapshotPath = (testPath: string, snapshotExtension: string) => { + return testPath.replace(TEST_DIR, SNAPSHOT_DIR) + snapshotExtension +} + +export const resolveTestPath = (snapshotPath: string, snapshotExtension: string) => { + return snapshotPath.replace(snapshotExtension, '').replace(SNAPSHOT_DIR, TEST_DIR) +} + +export const testPathForConsistencyCheck = path.posix.join('consistency_check', TEST_DIR, 'example.test.js') + +export default { + resolveSnapshotPath, + resolveTestPath, + testPathForConsistencyCheck, +} diff --git a/packages/jest-helper/src/snapshot/serializers.ts b/packages/jest-helper/src/snapshot/serializers.ts new file mode 100644 index 000000000000..7145d6145e5d --- /dev/null +++ b/packages/jest-helper/src/snapshot/serializers.ts @@ -0,0 +1,58 @@ +import * as os from 'node:os' +// import type { Config, Refs, Printer } from 'pretty-format' + +export const print = (val: string) => { + // Note: 对齐各平台的路径分隔符 + return val.replace(/\\*\*\sfilePath:\s(.*)\s\*\*\//g, (replaceValue) => replaceValue.replace(/\\/g, '/')) +} + +const mockFilePath = '/** filePath:mockFilePath **/' +export const parseSnapshotByFilePath = (val: string) => { + const arr = val.split(new RegExp(os.EOL + '|\n')) + let key = mockFilePath + return arr.reduce((acc, cur) => { + if (cur.startsWith('/** filePath:')) { + key = cur.replace(/\\/g, '/') + acc[key] = '' + } else { + acc[key] ||= '' + if (acc[key] !== '') { + acc[key] += '\n' + } + acc[key] += cur + } + return acc + }, {}) +} + +export const snapshotObject2String = (val: Record) => { + const arr = Object.entries(val) + const hasMockFilePath = arr.some(([key]) => key === mockFilePath) && arr.length <= 1 + return `${hasMockFilePath ? '' : '"\n'}${arr + .sort(([key1], [key2]) => key1.localeCompare(key2)) + .filter(([key, value]) => (!!key && key !== mockFilePath) || (key === mockFilePath && !!value)) + .map(([key, value]) => `${key === mockFilePath ? '' : `${key}\n`}${value}`) + .join('\n')}${hasMockFilePath ? '' : '"'}` +} + +export const serialize = ( + val: any, + // config: Config, + // indentation: string, + // depth: number, + // refs: Refs, + // printer: Printer, +) => { + if (typeof val === 'string') { + return snapshotObject2String(parseSnapshotByFilePath(val)) + } + return val +} + +export const test = (val: unknown) => typeof val === 'string' + +export default { + // print, + serialize, + test, +} diff --git a/packages/jest-helper/tsconfig.json b/packages/jest-helper/tsconfig.json new file mode 100644 index 000000000000..90ec8083ddee --- /dev/null +++ b/packages/jest-helper/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.root.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "lib", + "rootDir": "./src", + "module": "commonjs" + }, + "include": ["./src"], + "exclude": ["./src/__tests__"] +} diff --git a/packages/postcss-html-transform/package.json b/packages/postcss-html-transform/package.json index b56c89378ab2..20b0e98f1a9f 100644 --- a/packages/postcss-html-transform/package.json +++ b/packages/postcss-html-transform/package.json @@ -1,10 +1,10 @@ { "name": "postcss-html-transform", - "version": "3.6.34", + "version": "4.0.4", "description": "transform html tag name selector", - "main": "index.js", - "author": "drchan", + "author": "O2Team", "license": "MIT", + "main": "index.js", "files": [ "index.js", "dist" @@ -14,16 +14,19 @@ "url": "git+https://github.com/NervJS/taro.git" }, "scripts": { + "prod": "pnpm run build", + "prebuild": "pnpm run clean", + "clean": "rimraf --impl=move-remove ./dist", "build": "tsc", "dev": "tsc -w" }, "bugs": { "url": "https://github.com/NervJS/taro/issues" }, - "devDependencies": { - "postcss": "^8.4.18" + "engines": { + "node": ">= 18" }, "peerDependencies": { - "postcss": "^8.4.18" + "postcss": "^8" } } diff --git a/packages/postcss-plugin-constparse/package.json b/packages/postcss-plugin-constparse/package.json index c6fb2cc0e60b..36c602aa1e57 100644 --- a/packages/postcss-plugin-constparse/package.json +++ b/packages/postcss-plugin-constparse/package.json @@ -1,14 +1,14 @@ { "name": "postcss-plugin-constparse", - "version": "3.6.34", + "version": "4.0.4", "description": "parse constants defined in config", - "main": "index.js", - "author": "Simba", + "author": "O2Team", "license": "MIT", - "devDependencies": { - "postcss": "^8.4.18" + "main": "index.js", + "engines": { + "node": ">= 18" }, "peerDependencies": { - "postcss": "^8.4.18" + "postcss": "^8" } } diff --git a/packages/postcss-pxtransform/__tests__/index.test.js b/packages/postcss-pxtransform/__tests__/index.test.js index d025877ffaf5..2da7ac304296 100644 --- a/packages/postcss-pxtransform/__tests__/index.test.js +++ b/packages/postcss-pxtransform/__tests__/index.test.js @@ -865,6 +865,6 @@ describe('platform 为 harmony,适配', () => { } } const processed = postcss(px2rem(options)).process(rules).css - expect(processed).toBe('view { width: 100vp; }') + expect(processed).toBe('view { width: 100ch; }') }) }) diff --git a/packages/postcss-pxtransform/index.js b/packages/postcss-pxtransform/index.js index 150c69be556a..93610d9be814 100644 --- a/packages/postcss-pxtransform/index.js +++ b/packages/postcss-pxtransform/index.js @@ -1,8 +1,8 @@ const pxRegex = require('./lib/pixel-unit-regex') -const PXRegex = require('./lib/pixel-upper-unit-regex') const filterPropList = require('./lib/filter-prop-list') const defaults = { + methods: ['platform', 'size'], rootValue: 16, unitPrecision: 5, selectorBlackList: [], @@ -36,32 +36,41 @@ const DEFAULT_WEAPP_OPTIONS = { const processed = Symbol('processed') - let targetUnit +const SPECIAL_PIXEL = ['Px', 'PX', 'pX'] +let unConvertTargetUnit +let platform + module.exports = (options = {}) => { options = Object.assign({}, DEFAULT_WEAPP_OPTIONS, options) - + const exclude = options.exclude const transUnits = ['px'] const baseFontSize = options.baseFontSize || (options.minRootSize >= 1 ? options.minRootSize : 20) const designWidth = (input) => typeof options.designWidth === 'function' ? options.designWidth(input) : options.designWidth + platform = options.platform switch (options.platform) { case 'h5': { targetUnit = options.targetUnit ?? 'rem' - if (targetUnit === 'vw') { - options.rootValue = (input) => { - return designWidth(input) / 100 - } - } else if (targetUnit === 'px') { - options.rootValue = (input) => (1 / options.deviceRatio[designWidth(input)]) * 2 - } else { - // rem - options.rootValue = (input) => { - return (baseFontSize / options.deviceRatio[designWidth(input)]) * 2 - } + switch (targetUnit) { + case 'vw': + case 'vmin': + options.rootValue = (input) => { + return designWidth(input) / 100 + } + break + case 'px': + options.rootValue = (input) => (1 / options.deviceRatio[designWidth(input)]) * 2 + break + default: + // rem + options.rootValue = (input) => { + return (baseFontSize / options.deviceRatio[designWidth(input)]) * 2 + } + break } transUnits.push('rpx') @@ -80,6 +89,8 @@ module.exports = (options = {}) => { case 'harmony': { options.rootValue = (input) => 1 / options.deviceRatio[designWidth(input)] targetUnit = 'px' + unConvertTargetUnit = 'ch' // harmony对于大小写的PX转换成其他单位,用于rust解析 + transUnits.push(...SPECIAL_PIXEL) break } default: { @@ -118,6 +129,10 @@ module.exports = (options = {}) => { /** 是否跳过当前文件不处理 */ let skip = false + if (exclude && exclude?.(result.opts.from)) { + return null + } + return { // 注意:钩子在节点变动时会重新执行,Once,OnceExit只执行一次,https://github.com/NervJS/taro/issues/13238 Comment (comment) { @@ -126,6 +141,8 @@ module.exports = (options = {}) => { return } + if (!opts.methods.includes('platform')) return + // delete code between comment in RN // 有死循环的问题 if (options.platform === 'rn') { @@ -183,47 +200,46 @@ module.exports = (options = {}) => { }, Declaration (decl) { if (skip) return + if (!opts.methods.includes('size')) return if (decl[processed]) return // 标记当前 node 已处理 decl[processed] = true - if (options.platform === 'harmony') { - if (decl.value.indexOf('PX') === -1) return - const value = decl.value.replace(PXRegex, function (m, _$1, $2) { - return m.replace($2, 'vp') - }) - decl.value = value - } else { - if (decl.value.indexOf('px') === -1) return + if (!/px/i.test(decl.value)) return - if (!satisfyPropList(decl.prop)) return + if (!satisfyPropList(decl.prop)) return - if (blacklistedSelector(opts.selectorBlackList, decl.parent.selector)) return - const value = decl.value.replace(pxRgx, pxReplace) - // if rem unit already exists, do not add or replace - if (declarationExists(decl.parent, decl.prop, value)) return - if (opts.replace) { - decl.value = value + const isBlacklisted = blacklistedSelector(opts.selectorBlackList, decl.parent.selector) + if (isBlacklisted && platform !== 'harmony') return + let value + if (isBlacklisted) { + // 如果是harmony平台,黑名单的样式单位做特殊处理 + if (platform === 'harmony') { + value = decl.value.replace(pxRgx, (m, $1) => $1 ? $1 + unConvertTargetUnit : m) } else { - decl.cloneAfter({ value: value }) + // 如果是其他平台,黑名单的样式单位不做处理 + return } + } else { + value = decl.value.replace(pxRgx, pxReplace) + } + // if rem unit already exists, do not add or replace + if (declarationExists(decl.parent, decl.prop, value)) return + if (opts.replace) { + decl.value = value + } else { + decl.cloneAfter({ value: value }) } }, AtRule: { media: (rule) => { if (skip) return - if (options.platform === 'harmony') { - if (rule.params.indexOf('PX') === -1) return - const value = rule.params.replace(PXRegex, function (m, _$1, $2) { - return m.replace($2, 'vp') - }) - rule.params = value - } else { - if (rule.params.indexOf('px') === -1) return - rule.params = rule.params.replace(pxRgx, pxReplace) - } + if (!opts.methods.includes('size')) return + + if (!/px/i.test(rule.params)) return + rule.params = rule.params.replace(pxRgx, pxReplace) }, }, } @@ -255,14 +271,28 @@ function convertLegacyOptions (options) { } function createPxReplace (rootValue, unitPrecision, minPixelValue, onePxTransform) { + const specialPxRgx = pxRegex(SPECIAL_PIXEL) return function (input) { return function (m, $1) { if (!$1) return m + + if (platform === 'harmony' && specialPxRgx.test(m)) { + // harmony对大小写的PX转换成其他单位,用于rust解析 + return $1 + unConvertTargetUnit + } + if (!onePxTransform && parseInt($1, 10) === 1) { + if (platform === 'harmony') { return $1 + unConvertTargetUnit } return m } const pixels = parseFloat($1) - if (pixels < minPixelValue) return m + if (pixels < minPixelValue) { + if (platform === 'harmony') { return $1 + unConvertTargetUnit } + return m + } + + // 转换工作,如果是harmony的话不转换 + if (platform === 'harmony') { return m } let val = pixels / rootValue(input, m, $1) if (unitPrecision >= 0 && unitPrecision <= 100) { val = toFixed(val, unitPrecision) diff --git a/packages/postcss-pxtransform/package.json b/packages/postcss-pxtransform/package.json index c83b9ad10b2e..d68f16ae992c 100644 --- a/packages/postcss-pxtransform/package.json +++ b/packages/postcss-pxtransform/package.json @@ -1,7 +1,9 @@ { "name": "postcss-pxtransform", - "version": "3.6.34", + "version": "4.0.4", "description": "PostCSS plugin px 转小程序 rpx及h5 rem 单位", + "author": "O2Team", + "license": "MIT", "main": "index.js", "keywords": [ "postcss", @@ -9,8 +11,10 @@ "postcss-plugin", "pxtransform" ], - "author": "Pines-Cheng ", - "license": "MIT", + "files": [ + "lib", + "index.js" + ], "repository": { "type": "git", "url": "git+https://github.com/NervJS/taro.git" @@ -18,7 +22,7 @@ "bugs": { "url": "https://github.com/NervJS/taro/issues" }, - "homepage": "https://github.com/NervJS/taro/tree/next/packages/postcss-pxtransform#readme", + "homepage": "https://github.com/NervJS/taro/tree/main/packages/postcss-pxtransform#readme", "scripts": { "test": "jest" }, @@ -26,12 +30,10 @@ "testEnvironment": "node", "testEnvironmentOptions": {} }, - "devDependencies": { - "jest": "^29.3.1", - "jest-cli": "^29.3.1", - "postcss": "^8.4.18" + "engines": { + "node": ">= 18" }, "peerDependencies": { - "postcss": "^8.4.18" + "postcss": "^8" } } diff --git a/packages/postcss-unit-transform/.eslintrc.js b/packages/postcss-unit-transform/.eslintrc.js deleted file mode 100644 index e26be85ae2b8..000000000000 --- a/packages/postcss-unit-transform/.eslintrc.js +++ /dev/null @@ -1,19 +0,0 @@ -const config = require('../../.eslintrc.js') - -module.exports = { - parser: config.parser, - plugins: [ - '@typescript-eslint' - ], - parserOptions: { }, - extends: [ - 'eslint:recommended', - 'standard', - 'plugin:@typescript-eslint/recommended', - 'prettier' - ], - rules: { - '@typescript-eslint/no-unused-vars': 0, - '@typescript-eslint/no-var-requires': 0 - } -} diff --git a/packages/postcss-unit-transform/__tests__/index.test.ts b/packages/postcss-unit-transform/__tests__/index.test.ts index 3b4bec9a31c0..a11b421b78c5 100644 --- a/packages/postcss-unit-transform/__tests__/index.test.ts +++ b/packages/postcss-unit-transform/__tests__/index.test.ts @@ -23,4 +23,4 @@ describe('wxss解析', () => { font-size: 0px; }`) }) -}) \ No newline at end of file +}) diff --git a/packages/postcss-unit-transform/index.js b/packages/postcss-unit-transform/index.js index c2f2edd85cf4..679baabfb4a6 100644 --- a/packages/postcss-unit-transform/index.js +++ b/packages/postcss-unit-transform/index.js @@ -1,17 +1,19 @@ -const postcss = require('postcss') - -module.exports = postcss.plugin('postcss-taro-unit-transform', plugin) - -function plugin (opts) { - return function (root) { - root.walkDecls(function (decl) { - let value = decl.value - value = value.replace(/\b-?(\d+(\.\d+)?)px\b/ig, function (match, size) { - return Number(size) === 0 ? '0px': parseFloat(size) * 2 + 'px' - }).replace(/\b-?(\d+(\.\d+)?)rpx\b/ig, function (match, size) { - return size + 'px' +function plugin () { + return { + postcssPlugin: 'postcss-taro-unit-transform', + Once (root) { + root.walkDecls(decl => { + let value = decl.value + value = value.replace(/\b-?(\d+(\.\d+)?)px\b/ig, function (_match, size) { + return Number(size) === 0 ? '0px' : parseFloat(size) * 2 + 'px' + }).replace(/\b-?(\d+(\.\d+)?)rpx\b/ig, function (_match, size) { + return size + 'px' + }) + decl.value = value }) - decl.value = value - }) + } } } +plugin.postcss = true + +module.exports = plugin diff --git a/packages/postcss-unit-transform/package.json b/packages/postcss-unit-transform/package.json index 3e7f42d44e02..07ab8bb12363 100644 --- a/packages/postcss-unit-transform/package.json +++ b/packages/postcss-unit-transform/package.json @@ -1,7 +1,9 @@ { "name": "postcss-taro-unit-transform", - "version": "3.6.34", + "version": "4.0.4", "description": "小程序单位转换", + "author": "O2Team", + "license": "MIT", "main": "index.js", "scripts": { "test": "jest", @@ -28,14 +30,7 @@ "/node_modules/" ] }, - "author": "luckyadam", - "license": "MIT", - "dependencies": { - "postcss": "^6.0.21", - "typescript": "^4.7.4" - }, - "devDependencies": { - "jest": "^29.7.0", - "ts-jest": "^29.0.5" + "peerDependencies": { + "postcss": "^8" } } diff --git a/packages/rollup-plugin-copy/index.js b/packages/rollup-plugin-copy/index.js new file mode 100644 index 000000000000..c368c43314c0 --- /dev/null +++ b/packages/rollup-plugin-copy/index.js @@ -0,0 +1,3 @@ +module.exports = require('./lib/index.js').default + +module.exports.default = module.exports diff --git a/packages/rollup-plugin-copy/package.json b/packages/rollup-plugin-copy/package.json new file mode 100644 index 000000000000..e6cd44cfdd94 --- /dev/null +++ b/packages/rollup-plugin-copy/package.json @@ -0,0 +1,29 @@ +{ + "name": "rollup-plugin-copy", + "version": "4.0.4", + "description": "rollup-plugin-copy for taro", + "private": true, + "author": "O2Team", + "license": "MIT", + "main": "index.js", + "files": [ + "lib" + ], + "scripts": { + "prod": "pnpm run build", + "prebuild": "pnpm run clean", + "clean": "rimraf --impl=move-remove ./lib", + "build": "tsc", + "dev": "tsc -w" + }, + "keywords": [], + "engines": { + "node": ">= 18" + }, + "dependencies": { + "@tarojs/helper": "workspace:*" + }, + "peerDependencies": { + "rollup": "^4" + } +} diff --git a/packages/rollup-plugin-copy/src/index.ts b/packages/rollup-plugin-copy/src/index.ts new file mode 100644 index 000000000000..ef66d2e49575 --- /dev/null +++ b/packages/rollup-plugin-copy/src/index.ts @@ -0,0 +1,65 @@ +import path from 'node:path' + +import { fs } from '@tarojs/helper' + +const cwd = process.cwd() + +interface ITarget { + readonly src: string + readonly dest: string +} + +interface IOptions { + hook?: string + targets?: ITarget[] +} + +export default function handleRollupCopy ({ + targets = [], + hook = 'buildEnd', +}: IOptions) { + // Note: 因为 rollup 的 watch 模式下,hook 会被多次调用,所以这里需要做一个标记 + let hasWatched = false + const isWatchMode = process.argv.includes('-w') || process.argv.includes('--watch') + return { + name: 'rollup-plugin:taro-copy', + async [hook] () { + if (hasWatched) return + + for (const item of targets) { + try { + let src = item.src + let dest = item.dest + const { base, dir } = path.parse(src) + dest = !dir ? dest : path.join(dest, base) + + src = path.join(cwd, src) + dest = path.join(cwd, dest) + if (path.extname(src)) { + fs.ensureDirSync(path.dirname(dest)) + } else { + fs.ensureDirSync(dest) + } + + const stat = fs.statSync(src) + if (stat.isDirectory()) { + fs.copySync(src, dest, { recursive: true }) + isWatchMode && fs.watch(src, { recursive: true }, (_event, filename) => { + if (!filename) return + fs.copyFileSync(path.join(src, filename), path.join(dest, filename)) + }) + } else if (stat.isFile()) { + fs.copyFileSync(src, dest) + isWatchMode && fs.watchFile(src, () => { + fs.copyFileSync(path.join(src), dest) + }) + } + } catch (error) { + console.error(error) + } + } + + hasWatched = true + } + } +} diff --git a/packages/rollup-plugin-copy/tsconfig.json b/packages/rollup-plugin-copy/tsconfig.json new file mode 100644 index 000000000000..e934ffe0043f --- /dev/null +++ b/packages/rollup-plugin-copy/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.root.json", + "compilerOptions": { + "baseUrl": ".", + "esModuleInterop": true, + "outDir": "lib", + "rootDir": "./src", + "module": "commonjs" + }, + "include": ["./src"], + "exclude": ["./src/__tests__"] +} diff --git a/packages/shared/package.json b/packages/shared/package.json index 2f25692cfb90..bda29860dd88 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,9 +1,8 @@ { "name": "@tarojs/shared", - "version": "3.6.34", + "version": "4.0.4", "description": "Taro utils internal use.", - "author": "yuche ", - "homepage": "https://github.com/nervjs/taro/tree/master/packages/shared#readme", + "author": "O2Team", "license": "MIT", "browser": "dist/index.js", "main:h5": "dist/shared.esm.js", @@ -19,22 +18,21 @@ "url": "git+https://github.com/NervJS/taro.git" }, "scripts": { + "prod": "pnpm run build", + "prebuild": "pnpm run clean", + "clean": "rimraf --impl=move-remove ./dist", "build": "pnpm run rollup --environment NODE_ENV:production", "dev": "pnpm run rollup --environment NODE_ENV:development -w", - "rollup": "rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs", + "rollup": "rollup --config rollup.config.ts --configPlugin typescript", "test": "jest" }, "bugs": { "url": "https://github.com/NervJS/taro/issues" }, + "engines": { + "node": ">= 18" + }, "devDependencies": { - "@rollup/plugin-typescript": "^11.1.0", - "jest": "^29.3.1", - "jest-cli": "^29.3.1", - "lodash": "^4.17.21", - "rollup": "^3.8.1", - "rollup-plugin-ts": "^3.0.2", - "ts-jest": "^29.0.5", - "typescript": "^4.7.4" + "lodash": "^4.17.21" } } diff --git a/packages/shared/rollup.config.ts b/packages/shared/rollup.config.ts index 4ae7217ef295..ff358506033c 100644 --- a/packages/shared/rollup.config.ts +++ b/packages/shared/rollup.config.ts @@ -1,6 +1,6 @@ -import { mergeWith } from 'lodash' +import typescript from '@rollup/plugin-typescript' +import _ from 'lodash' import { defineConfig } from 'rollup' -import ts from 'rollup-plugin-ts' import type { RollupOptions } from 'rollup' @@ -10,11 +10,11 @@ const baseConfig: RollupOptions = { exports: 'named' }, plugins: [ - ts({ - tsconfig: e => ({ - ...e, + typescript({ + compilerOptions: { preserveConstEnums: true, - }) + }, + include: ['src/**/*'] }) ] } @@ -54,8 +54,8 @@ export default defineConfig(variesConfig.map(v => { return objValue.concat(srcValue) } if (typeof objValue === 'object') { - return mergeWith({}, objValue, srcValue, customizer) + return _.mergeWith({}, objValue, srcValue, customizer) } } - return mergeWith({}, baseConfig, v, customizer) + return _.mergeWith({}, baseConfig, v, customizer) })) diff --git a/packages/shared/src/runtime-hooks.ts b/packages/shared/src/runtime-hooks.ts index 89b317880a43..756a23adaec7 100644 --- a/packages/shared/src/runtime-hooks.ts +++ b/packages/shared/src/runtime-hooks.ts @@ -196,7 +196,6 @@ type ITaroHooks = { getEventCenter: (EventsClass: typeof Events) => Events isBubbleEvents: (eventName: string) => boolean getSpecialNodes: () => string[] - /** 解决 Vue2 布尔值属性值的设置问题 */ onRemoveAttribute: (element, qualifiedName: string) => boolean /** 用于把 React 同一事件回调中的所有 setState 合并到同一个更新处理中 */ batchedEventUpdates: (cb: TFunc) => void @@ -253,6 +252,8 @@ type ITaroHooks = { modifyAddEventListener: (element, sideEffect: boolean, getComponentsAlias: () => Record) => void /** 元素删除事件监听钩子 */ modifyRemoveEventListener: (element, sideEffect: boolean, getComponentsAlias: () => Record) => void + /** 鸿蒙用于监听 memory 等级的钩子 */ + getMemoryLevel: (level: { level: number }) => void } export const hooks = new TaroHooks({ @@ -353,4 +354,6 @@ export const hooks = new TaroHooks({ modifyAddEventListener: TaroHook(HOOK_TYPE.SINGLE), modifyRemoveEventListener: TaroHook(HOOK_TYPE.SINGLE), + + getMemoryLevel: TaroHook(HOOK_TYPE.SINGLE), }) diff --git a/packages/shared/src/template.ts b/packages/shared/src/template.ts index f927d3b1c340..3096365be60f 100644 --- a/packages/shared/src/template.ts +++ b/packages/shared/src/template.ts @@ -494,7 +494,7 @@ export class BaseTemplate { return '' } - public buildPageTemplate = (baseTempPath: string, _page: { content: Record, path: string }) => { + public buildPageTemplate = (baseTempPath: string, _page?: { content: Record, path: string }) => { const template = `