diff --git a/.eslintrc.js b/.eslintrc.js
index 5487bda8a0..917518ca30 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,11 +1,15 @@
module.exports = {
root: true,
- parser: '@babel/eslint-parser',
+ parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module'
},
- extends: 'standard',
+ extends: [
+ 'plugin:@typescript-eslint/recommended',
+ 'standard'
+ ],
plugins: [
+ '@typescript-eslint',
'html',
'jest'
],
@@ -27,10 +31,18 @@ module.exports = {
getCurrentPages: 'readonly'
},
rules: {
+ '@typescript-eslint/ban-ts-comment': 0,
+ '@typescript-eslint/no-empty-function': 0,
+ '@typescript-eslint/no-this-alias': 0,
+ '@typescript-eslint/no-var-requires': 0,
+ '@typescript-eslint/explicit-module-boundary-types': 0,
'no-cond-assign': 0,
camelcase: 0
},
env: {
- 'jest/globals': true
+ 'jest/globals': true,
+ es6: true,
+ browser: true,
+ node: true
}
}
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index b97ed05560..c3d25b06ce 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -22,6 +22,7 @@ jobs:
- name: generate docs file
if: env.GIT_DIFF
run: |
+ cd docs-vueporess
npm i
npm run docs:build
diff --git a/.gitignore b/.gitignore
index 124586563b..4050ceff10 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,5 +8,7 @@ package-lock.json
pnpm-lock.yaml
yarn.lock
.DS_Store
-docs-vuepress/.vuepress/dist
+docs-vuepress/docs/.vuepress/dist
elevate/
+.yarn/
+packages/**/dist/
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000000..a8a5904397
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+registry = https://registry.npmjs.org/
+auto-install-peers = true
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000000..50cf8e67e4
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v14.21.1
\ No newline at end of file
diff --git a/docs-vuepress/.vuepress/config.js b/docs-vuepress/docs/.vuepress/config.js
similarity index 100%
rename from docs-vuepress/.vuepress/config.js
rename to docs-vuepress/docs/.vuepress/config.js
diff --git a/docs-vuepress/.vuepress/enhanceApp.js b/docs-vuepress/docs/.vuepress/enhanceApp.js
similarity index 100%
rename from docs-vuepress/.vuepress/enhanceApp.js
rename to docs-vuepress/docs/.vuepress/enhanceApp.js
diff --git a/docs-vuepress/.vuepress/headerMdPlugin.js b/docs-vuepress/docs/.vuepress/headerMdPlugin.js
similarity index 100%
rename from docs-vuepress/.vuepress/headerMdPlugin.js
rename to docs-vuepress/docs/.vuepress/headerMdPlugin.js
diff --git a/docs-vuepress/.vuepress/public/favicon.ico b/docs-vuepress/docs/.vuepress/public/favicon.ico
similarity index 100%
rename from docs-vuepress/.vuepress/public/favicon.ico
rename to docs-vuepress/docs/.vuepress/public/favicon.ico
diff --git a/docs-vuepress/.vuepress/public/logo.png b/docs-vuepress/docs/.vuepress/public/logo.png
similarity index 100%
rename from docs-vuepress/.vuepress/public/logo.png
rename to docs-vuepress/docs/.vuepress/public/logo.png
diff --git a/docs-vuepress/.vuepress/public/manifest.webmanifest b/docs-vuepress/docs/.vuepress/public/manifest.webmanifest
similarity index 100%
rename from docs-vuepress/.vuepress/public/manifest.webmanifest
rename to docs-vuepress/docs/.vuepress/public/manifest.webmanifest
diff --git a/docs-vuepress/.vuepress/styles/index.styl b/docs-vuepress/docs/.vuepress/styles/index.styl
similarity index 100%
rename from docs-vuepress/.vuepress/styles/index.styl
rename to docs-vuepress/docs/.vuepress/styles/index.styl
diff --git a/docs-vuepress/.vuepress/styles/pallete.styl b/docs-vuepress/docs/.vuepress/styles/pallete.styl
similarity index 100%
rename from docs-vuepress/.vuepress/styles/pallete.styl
rename to docs-vuepress/docs/.vuepress/styles/pallete.styl
diff --git a/docs-vuepress/.vuepress/theme/components/AlgoliaSearchBox.vue b/docs-vuepress/docs/.vuepress/theme/components/AlgoliaSearchBox.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/components/AlgoliaSearchBox.vue
rename to docs-vuepress/docs/.vuepress/theme/components/AlgoliaSearchBox.vue
diff --git a/docs-vuepress/.vuepress/theme/components/CodeList.vue b/docs-vuepress/docs/.vuepress/theme/components/CodeList.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/components/CodeList.vue
rename to docs-vuepress/docs/.vuepress/theme/components/CodeList.vue
diff --git a/docs-vuepress/.vuepress/theme/components/MobileSwiper.vue b/docs-vuepress/docs/.vuepress/theme/components/MobileSwiper.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/components/MobileSwiper.vue
rename to docs-vuepress/docs/.vuepress/theme/components/MobileSwiper.vue
diff --git a/docs-vuepress/.vuepress/theme/components/MobileView.vue b/docs-vuepress/docs/.vuepress/theme/components/MobileView.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/components/MobileView.vue
rename to docs-vuepress/docs/.vuepress/theme/components/MobileView.vue
diff --git a/docs-vuepress/.vuepress/theme/components/Navbar.vue b/docs-vuepress/docs/.vuepress/theme/components/Navbar.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/components/Navbar.vue
rename to docs-vuepress/docs/.vuepress/theme/components/Navbar.vue
diff --git a/docs-vuepress/.vuepress/theme/components/Popover.vue b/docs-vuepress/docs/.vuepress/theme/components/Popover.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/components/Popover.vue
rename to docs-vuepress/docs/.vuepress/theme/components/Popover.vue
diff --git a/docs-vuepress/.vuepress/theme/components/SlideItem.vue b/docs-vuepress/docs/.vuepress/theme/components/SlideItem.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/components/SlideItem.vue
rename to docs-vuepress/docs/.vuepress/theme/components/SlideItem.vue
diff --git a/docs-vuepress/.vuepress/theme/components/Swiper.vue b/docs-vuepress/docs/.vuepress/theme/components/Swiper.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/components/Swiper.vue
rename to docs-vuepress/docs/.vuepress/theme/components/Swiper.vue
diff --git a/docs-vuepress/.vuepress/theme/components/SwiperImg.vue b/docs-vuepress/docs/.vuepress/theme/components/SwiperImg.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/components/SwiperImg.vue
rename to docs-vuepress/docs/.vuepress/theme/components/SwiperImg.vue
diff --git a/docs-vuepress/.vuepress/theme/enhanceApp.js b/docs-vuepress/docs/.vuepress/theme/enhanceApp.js
similarity index 100%
rename from docs-vuepress/.vuepress/theme/enhanceApp.js
rename to docs-vuepress/docs/.vuepress/theme/enhanceApp.js
diff --git a/docs-vuepress/.vuepress/theme/global-components/Content.vue b/docs-vuepress/docs/.vuepress/theme/global-components/Content.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/global-components/Content.vue
rename to docs-vuepress/docs/.vuepress/theme/global-components/Content.vue
diff --git a/docs-vuepress/.vuepress/theme/global-components/Footer.vue b/docs-vuepress/docs/.vuepress/theme/global-components/Footer.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/global-components/Footer.vue
rename to docs-vuepress/docs/.vuepress/theme/global-components/Footer.vue
diff --git a/docs-vuepress/.vuepress/theme/global-components/Header.vue b/docs-vuepress/docs/.vuepress/theme/global-components/Header.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/global-components/Header.vue
rename to docs-vuepress/docs/.vuepress/theme/global-components/Header.vue
diff --git a/docs-vuepress/.vuepress/theme/index.js b/docs-vuepress/docs/.vuepress/theme/index.js
similarity index 100%
rename from docs-vuepress/.vuepress/theme/index.js
rename to docs-vuepress/docs/.vuepress/theme/index.js
diff --git a/docs-vuepress/.vuepress/theme/layouts/GlobalLayout.vue b/docs-vuepress/docs/.vuepress/theme/layouts/GlobalLayout.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/layouts/GlobalLayout.vue
rename to docs-vuepress/docs/.vuepress/theme/layouts/GlobalLayout.vue
diff --git a/docs-vuepress/.vuepress/theme/layouts/HomepageLayout.vue b/docs-vuepress/docs/.vuepress/theme/layouts/HomepageLayout.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/layouts/HomepageLayout.vue
rename to docs-vuepress/docs/.vuepress/theme/layouts/HomepageLayout.vue
diff --git a/docs-vuepress/.vuepress/theme/layouts/Layout.vue b/docs-vuepress/docs/.vuepress/theme/layouts/Layout.vue
similarity index 100%
rename from docs-vuepress/.vuepress/theme/layouts/Layout.vue
rename to docs-vuepress/docs/.vuepress/theme/layouts/Layout.vue
diff --git a/docs-vuepress/.vuepress/theme/vuepress-format-header-slug-plugin.js b/docs-vuepress/docs/.vuepress/theme/vuepress-format-header-slug-plugin.js
similarity index 100%
rename from docs-vuepress/.vuepress/theme/vuepress-format-header-slug-plugin.js
rename to docs-vuepress/docs/.vuepress/theme/vuepress-format-header-slug-plugin.js
diff --git a/docs-vuepress/README.md b/docs-vuepress/docs/README.md
similarity index 100%
rename from docs-vuepress/README.md
rename to docs-vuepress/docs/README.md
diff --git a/docs-vuepress/api/ApiIndex.vue b/docs-vuepress/docs/api/ApiIndex.vue
similarity index 100%
rename from docs-vuepress/api/ApiIndex.vue
rename to docs-vuepress/docs/api/ApiIndex.vue
diff --git a/docs-vuepress/api/app-config.md b/docs-vuepress/docs/api/app-config.md
similarity index 100%
rename from docs-vuepress/api/app-config.md
rename to docs-vuepress/docs/api/app-config.md
diff --git a/docs-vuepress/api/builtIn.md b/docs-vuepress/docs/api/builtIn.md
similarity index 100%
rename from docs-vuepress/api/builtIn.md
rename to docs-vuepress/docs/api/builtIn.md
diff --git a/docs-vuepress/api/compile.md b/docs-vuepress/docs/api/compile.md
similarity index 100%
rename from docs-vuepress/api/compile.md
rename to docs-vuepress/docs/api/compile.md
diff --git a/docs-vuepress/api/composition-api.md b/docs-vuepress/docs/api/composition-api.md
similarity index 100%
rename from docs-vuepress/api/composition-api.md
rename to docs-vuepress/docs/api/composition-api.md
diff --git a/docs-vuepress/api/directives.md b/docs-vuepress/docs/api/directives.md
similarity index 100%
rename from docs-vuepress/api/directives.md
rename to docs-vuepress/docs/api/directives.md
diff --git a/docs-vuepress/api/extend.md b/docs-vuepress/docs/api/extend.md
similarity index 100%
rename from docs-vuepress/api/extend.md
rename to docs-vuepress/docs/api/extend.md
diff --git a/docs-vuepress/api/global-api.md b/docs-vuepress/docs/api/global-api.md
similarity index 100%
rename from docs-vuepress/api/global-api.md
rename to docs-vuepress/docs/api/global-api.md
diff --git a/docs-vuepress/api/index.md b/docs-vuepress/docs/api/index.md
similarity index 100%
rename from docs-vuepress/api/index.md
rename to docs-vuepress/docs/api/index.md
diff --git a/docs-vuepress/api/instance-api.md b/docs-vuepress/docs/api/instance-api.md
similarity index 100%
rename from docs-vuepress/api/instance-api.md
rename to docs-vuepress/docs/api/instance-api.md
diff --git a/docs-vuepress/api/reactivity-api.md b/docs-vuepress/docs/api/reactivity-api.md
similarity index 100%
rename from docs-vuepress/api/reactivity-api.md
rename to docs-vuepress/docs/api/reactivity-api.md
diff --git a/docs-vuepress/api/store-api.md b/docs-vuepress/docs/api/store-api.md
similarity index 100%
rename from docs-vuepress/api/store-api.md
rename to docs-vuepress/docs/api/store-api.md
diff --git a/docs-vuepress/articles/1.0.md b/docs-vuepress/docs/articles/1.0.md
similarity index 100%
rename from docs-vuepress/articles/1.0.md
rename to docs-vuepress/docs/articles/1.0.md
diff --git a/docs-vuepress/articles/2.0.md b/docs-vuepress/docs/articles/2.0.md
similarity index 100%
rename from docs-vuepress/articles/2.0.md
rename to docs-vuepress/docs/articles/2.0.md
diff --git a/docs-vuepress/articles/2.7-release.md b/docs-vuepress/docs/articles/2.7-release.md
similarity index 100%
rename from docs-vuepress/articles/2.7-release.md
rename to docs-vuepress/docs/articles/2.7-release.md
diff --git a/docs-vuepress/articles/2.8-release.md b/docs-vuepress/docs/articles/2.8-release.md
similarity index 100%
rename from docs-vuepress/articles/2.8-release.md
rename to docs-vuepress/docs/articles/2.8-release.md
diff --git a/docs-vuepress/articles/index.md b/docs-vuepress/docs/articles/index.md
similarity index 100%
rename from docs-vuepress/articles/index.md
rename to docs-vuepress/docs/articles/index.md
diff --git a/docs-vuepress/articles/mpx-cli-next.md b/docs-vuepress/docs/articles/mpx-cli-next.md
similarity index 100%
rename from docs-vuepress/articles/mpx-cli-next.md
rename to docs-vuepress/docs/articles/mpx-cli-next.md
diff --git a/docs-vuepress/articles/mpx1.md b/docs-vuepress/docs/articles/mpx1.md
similarity index 100%
rename from docs-vuepress/articles/mpx1.md
rename to docs-vuepress/docs/articles/mpx1.md
diff --git a/docs-vuepress/articles/mpx2.md b/docs-vuepress/docs/articles/mpx2.md
similarity index 100%
rename from docs-vuepress/articles/mpx2.md
rename to docs-vuepress/docs/articles/mpx2.md
diff --git a/docs-vuepress/articles/performance.md b/docs-vuepress/docs/articles/performance.md
similarity index 100%
rename from docs-vuepress/articles/performance.md
rename to docs-vuepress/docs/articles/performance.md
diff --git a/docs-vuepress/articles/size-control.md b/docs-vuepress/docs/articles/size-control.md
similarity index 100%
rename from docs-vuepress/articles/size-control.md
rename to docs-vuepress/docs/articles/size-control.md
diff --git a/docs-vuepress/articles/ts-derivation.md b/docs-vuepress/docs/articles/ts-derivation.md
similarity index 100%
rename from docs-vuepress/articles/ts-derivation.md
rename to docs-vuepress/docs/articles/ts-derivation.md
diff --git a/docs-vuepress/articles/unit-test.md b/docs-vuepress/docs/articles/unit-test.md
similarity index 100%
rename from docs-vuepress/articles/unit-test.md
rename to docs-vuepress/docs/articles/unit-test.md
diff --git a/docs-vuepress/assets/images/1666074957603.jpg b/docs-vuepress/docs/assets/images/1666074957603.jpg
similarity index 100%
rename from docs-vuepress/assets/images/1666074957603.jpg
rename to docs-vuepress/docs/assets/images/1666074957603.jpg
diff --git a/docs-vuepress/assets/images/cloud.png b/docs-vuepress/docs/assets/images/cloud.png
similarity index 100%
rename from docs-vuepress/assets/images/cloud.png
rename to docs-vuepress/docs/assets/images/cloud.png
diff --git a/docs-vuepress/assets/images/select-ts-version.png b/docs-vuepress/docs/assets/images/select-ts-version.png
similarity index 100%
rename from docs-vuepress/assets/images/select-ts-version.png
rename to docs-vuepress/docs/assets/images/select-ts-version.png
diff --git a/docs-vuepress/assets/images/start-tips1.png b/docs-vuepress/docs/assets/images/start-tips1.png
similarity index 100%
rename from docs-vuepress/assets/images/start-tips1.png
rename to docs-vuepress/docs/assets/images/start-tips1.png
diff --git a/docs-vuepress/assets/images/start-tips2.png b/docs-vuepress/docs/assets/images/start-tips2.png
similarity index 100%
rename from docs-vuepress/assets/images/start-tips2.png
rename to docs-vuepress/docs/assets/images/start-tips2.png
diff --git a/docs-vuepress/desc.md b/docs-vuepress/docs/desc.md
similarity index 100%
rename from docs-vuepress/desc.md
rename to docs-vuepress/docs/desc.md
diff --git a/docs-vuepress/guide/advance/ability-compatible.md b/docs-vuepress/docs/guide/advance/ability-compatible.md
similarity index 100%
rename from docs-vuepress/guide/advance/ability-compatible.md
rename to docs-vuepress/docs/guide/advance/ability-compatible.md
diff --git a/docs-vuepress/guide/advance/async-subpackage.md b/docs-vuepress/docs/guide/advance/async-subpackage.md
similarity index 100%
rename from docs-vuepress/guide/advance/async-subpackage.md
rename to docs-vuepress/docs/guide/advance/async-subpackage.md
diff --git a/docs-vuepress/guide/advance/custom-output-path.md b/docs-vuepress/docs/guide/advance/custom-output-path.md
similarity index 100%
rename from docs-vuepress/guide/advance/custom-output-path.md
rename to docs-vuepress/docs/guide/advance/custom-output-path.md
diff --git a/docs-vuepress/guide/advance/dll-plugin.md b/docs-vuepress/docs/guide/advance/dll-plugin.md
similarity index 100%
rename from docs-vuepress/guide/advance/dll-plugin.md
rename to docs-vuepress/docs/guide/advance/dll-plugin.md
diff --git a/docs-vuepress/guide/advance/i18n.md b/docs-vuepress/docs/guide/advance/i18n.md
similarity index 100%
rename from docs-vuepress/guide/advance/i18n.md
rename to docs-vuepress/docs/guide/advance/i18n.md
diff --git a/docs-vuepress/guide/advance/image-process.md b/docs-vuepress/docs/guide/advance/image-process.md
similarity index 100%
rename from docs-vuepress/guide/advance/image-process.md
rename to docs-vuepress/docs/guide/advance/image-process.md
diff --git a/docs-vuepress/guide/advance/mixin.md b/docs-vuepress/docs/guide/advance/mixin.md
similarity index 100%
rename from docs-vuepress/guide/advance/mixin.md
rename to docs-vuepress/docs/guide/advance/mixin.md
diff --git a/docs-vuepress/guide/advance/npm.md b/docs-vuepress/docs/guide/advance/npm.md
similarity index 100%
rename from docs-vuepress/guide/advance/npm.md
rename to docs-vuepress/docs/guide/advance/npm.md
diff --git a/docs-vuepress/guide/advance/pinia.md b/docs-vuepress/docs/guide/advance/pinia.md
similarity index 100%
rename from docs-vuepress/guide/advance/pinia.md
rename to docs-vuepress/docs/guide/advance/pinia.md
diff --git a/docs-vuepress/guide/advance/platform.md b/docs-vuepress/docs/guide/advance/platform.md
similarity index 100%
rename from docs-vuepress/guide/advance/platform.md
rename to docs-vuepress/docs/guide/advance/platform.md
diff --git a/docs-vuepress/guide/advance/plugin.md b/docs-vuepress/docs/guide/advance/plugin.md
similarity index 100%
rename from docs-vuepress/guide/advance/plugin.md
rename to docs-vuepress/docs/guide/advance/plugin.md
diff --git a/docs-vuepress/guide/advance/progressive.md b/docs-vuepress/docs/guide/advance/progressive.md
similarity index 100%
rename from docs-vuepress/guide/advance/progressive.md
rename to docs-vuepress/docs/guide/advance/progressive.md
diff --git a/docs-vuepress/guide/advance/resource-resolve.md b/docs-vuepress/docs/guide/advance/resource-resolve.md
similarity index 100%
rename from docs-vuepress/guide/advance/resource-resolve.md
rename to docs-vuepress/docs/guide/advance/resource-resolve.md
diff --git a/docs-vuepress/guide/advance/size-report.md b/docs-vuepress/docs/guide/advance/size-report.md
similarity index 100%
rename from docs-vuepress/guide/advance/size-report.md
rename to docs-vuepress/docs/guide/advance/size-report.md
diff --git a/docs-vuepress/guide/advance/store.md b/docs-vuepress/docs/guide/advance/store.md
similarity index 100%
rename from docs-vuepress/guide/advance/store.md
rename to docs-vuepress/docs/guide/advance/store.md
diff --git a/docs-vuepress/guide/advance/subpackage.md b/docs-vuepress/docs/guide/advance/subpackage.md
similarity index 100%
rename from docs-vuepress/guide/advance/subpackage.md
rename to docs-vuepress/docs/guide/advance/subpackage.md
diff --git a/docs-vuepress/guide/basic/class-style-binding.md b/docs-vuepress/docs/guide/basic/class-style-binding.md
similarity index 100%
rename from docs-vuepress/guide/basic/class-style-binding.md
rename to docs-vuepress/docs/guide/basic/class-style-binding.md
diff --git a/docs-vuepress/guide/basic/component.md b/docs-vuepress/docs/guide/basic/component.md
similarity index 100%
rename from docs-vuepress/guide/basic/component.md
rename to docs-vuepress/docs/guide/basic/component.md
diff --git a/docs-vuepress/guide/basic/conditional-render.md b/docs-vuepress/docs/guide/basic/conditional-render.md
similarity index 100%
rename from docs-vuepress/guide/basic/conditional-render.md
rename to docs-vuepress/docs/guide/basic/conditional-render.md
diff --git a/docs-vuepress/guide/basic/css.md b/docs-vuepress/docs/guide/basic/css.md
similarity index 100%
rename from docs-vuepress/guide/basic/css.md
rename to docs-vuepress/docs/guide/basic/css.md
diff --git a/docs-vuepress/guide/basic/event.md b/docs-vuepress/docs/guide/basic/event.md
similarity index 100%
rename from docs-vuepress/guide/basic/event.md
rename to docs-vuepress/docs/guide/basic/event.md
diff --git a/docs-vuepress/guide/basic/ide.md b/docs-vuepress/docs/guide/basic/ide.md
similarity index 100%
rename from docs-vuepress/guide/basic/ide.md
rename to docs-vuepress/docs/guide/basic/ide.md
diff --git a/docs-vuepress/guide/basic/intro.md b/docs-vuepress/docs/guide/basic/intro.md
similarity index 100%
rename from docs-vuepress/guide/basic/intro.md
rename to docs-vuepress/docs/guide/basic/intro.md
diff --git a/docs-vuepress/guide/basic/list-render.md b/docs-vuepress/docs/guide/basic/list-render.md
similarity index 100%
rename from docs-vuepress/guide/basic/list-render.md
rename to docs-vuepress/docs/guide/basic/list-render.md
diff --git a/docs-vuepress/guide/basic/reactive.md b/docs-vuepress/docs/guide/basic/reactive.md
similarity index 100%
rename from docs-vuepress/guide/basic/reactive.md
rename to docs-vuepress/docs/guide/basic/reactive.md
diff --git a/docs-vuepress/guide/basic/refs.md b/docs-vuepress/docs/guide/basic/refs.md
similarity index 100%
rename from docs-vuepress/guide/basic/refs.md
rename to docs-vuepress/docs/guide/basic/refs.md
diff --git a/docs-vuepress/guide/basic/single-file.md b/docs-vuepress/docs/guide/basic/single-file.md
similarity index 100%
rename from docs-vuepress/guide/basic/single-file.md
rename to docs-vuepress/docs/guide/basic/single-file.md
diff --git a/docs-vuepress/guide/basic/start.md b/docs-vuepress/docs/guide/basic/start.md
similarity index 100%
rename from docs-vuepress/guide/basic/start.md
rename to docs-vuepress/docs/guide/basic/start.md
diff --git a/docs-vuepress/guide/basic/template.md b/docs-vuepress/docs/guide/basic/template.md
similarity index 100%
rename from docs-vuepress/guide/basic/template.md
rename to docs-vuepress/docs/guide/basic/template.md
diff --git a/docs-vuepress/guide/basic/two-way-binding.md b/docs-vuepress/docs/guide/basic/two-way-binding.md
similarity index 100%
rename from docs-vuepress/guide/basic/two-way-binding.md
rename to docs-vuepress/docs/guide/basic/two-way-binding.md
diff --git a/docs-vuepress/guide/composition-api/composition-api.md b/docs-vuepress/docs/guide/composition-api/composition-api.md
similarity index 100%
rename from docs-vuepress/guide/composition-api/composition-api.md
rename to docs-vuepress/docs/guide/composition-api/composition-api.md
diff --git a/docs-vuepress/guide/composition-api/reactive-api.md b/docs-vuepress/docs/guide/composition-api/reactive-api.md
similarity index 100%
rename from docs-vuepress/guide/composition-api/reactive-api.md
rename to docs-vuepress/docs/guide/composition-api/reactive-api.md
diff --git a/docs-vuepress/guide/extend/api-proxy.md b/docs-vuepress/docs/guide/extend/api-proxy.md
similarity index 100%
rename from docs-vuepress/guide/extend/api-proxy.md
rename to docs-vuepress/docs/guide/extend/api-proxy.md
diff --git a/docs-vuepress/guide/extend/fetch.md b/docs-vuepress/docs/guide/extend/fetch.md
similarity index 100%
rename from docs-vuepress/guide/extend/fetch.md
rename to docs-vuepress/docs/guide/extend/fetch.md
diff --git a/docs-vuepress/guide/extend/index.md b/docs-vuepress/docs/guide/extend/index.md
similarity index 100%
rename from docs-vuepress/guide/extend/index.md
rename to docs-vuepress/docs/guide/extend/index.md
diff --git a/docs-vuepress/guide/extend/mock.md b/docs-vuepress/docs/guide/extend/mock.md
similarity index 100%
rename from docs-vuepress/guide/extend/mock.md
rename to docs-vuepress/docs/guide/extend/mock.md
diff --git a/docs-vuepress/docs/guide/extend/request.md b/docs-vuepress/docs/guide/extend/request.md
new file mode 100644
index 0000000000..e49d226a90
--- /dev/null
+++ b/docs-vuepress/docs/guide/extend/request.md
@@ -0,0 +1,131 @@
+# 网络请求
+
+
+Mpx 提供了网络请求库 fetch,抹平了微信,阿里等平台请求参数及响应数据的差异;同时支持请求拦截器,请求取消等
+
+
+## 使用说明
+
+```js
+import mpx from '@mpxjs/core'
+import mpxFetch from '@mpxjs/fetch'
+mpx.use(mpxFetch)
+// 第一种访问形式
+mpx.xfetch.fetch({
+ url: 'http://xxx.com'
+}).then(res => {
+ console.log(res.data)
+})
+
+mpx.createApp({
+ onLaunch() {
+ // 第二种访问形式
+ this.$xfetch.fetch({url: 'http://test.com'})
+ }
+})
+```
+
+## 导出说明
+
+mpx-fetch提供了一个实例 **xfetch** ,该实例包含以下api
+
+- fetch(config), 正常的promisify风格的请求方法
+- CancelToken,实例属性,用于创建一个取消请求的凭证。
+- interceptors,实例属性,用于添加拦截器,包含两个属性,request & response
+
+## 请求拦截器
+
+```js
+mpx.xfetch.interceptors.request.use(function(config) {
+ console.log(config)
+ // 也可以返回promise
+ return config
+})
+mpx.xfetch.interceptors.response.use(function(res) {
+ console.log(res)
+ // 也可以返回promise
+ return res
+})
+```
+
+## 请求中断
+
+```js
+const cancelToken = new mpx.xfetch.CancelToken()
+mpx.xfetch.fetch({
+ url: 'http://xxx.com',
+ data: {
+ name: 'test'
+ },
+ cancelToken: cancelToken.token
+})
+cancelToken.exec('手动取消请求') // 执行后请求中断,返回abort fail
+```
+
+## 设置请求参数
+```js
+mpx.xfetch.fetch({
+ url: 'http://xxx.com',
+ params: {
+ name: 'test'
+ }
+})
+
+mpx.xfetch.fetch({
+ url: 'http://xxx.com',
+ method: 'POST',
+ // params 参数等价于 url query
+ params: {
+ age: 10
+ },
+ data: {
+ name: 'test'
+ },
+ // 设置参数序列化方式,等价于header = {'content-type': 'application/x-www-form-urlencoded'}
+ emulateJSON: true
+})
+```
+
+## 设置请求 timeout
+
+```js
+mpx.xfetch.fetch({
+ url: 'http://xxx.com',
+ method: 'POST',
+ data: {
+ name: 'test'
+ },
+ timeout: 10000 // 超时时间
+})
+```
+
+## 在组合式 API 中使用 {#composition-api-usage}
+在组合式 API 中我们提供了 [useFetch](/api/extend.html#usefetch) 方法来访问 `xfetch` 实例对象
+
+```js
+// app.mpx
+import mpx, { createComponent } from '@mpxjs/core'
+import { useFetch } from '@mpxjs/fetch'
+
+createComponent({
+ setup() {
+ useFetch().fetch({
+ url: 'http://xxx.com',
+ method: 'POST',
+ params: {
+ age: 10
+ },
+ data: {
+ name: 'test'
+ },
+ emulateJSON: true,
+ usePre: true,
+ cacheInvalidationTime: 3000,
+ ignorePreParamKeys: ['timestamp']
+ }).then(res => {
+ console.log(res.data)
+ })
+ }
+})
+
+```
diff --git a/docs-vuepress/guide/migrate/2.7.md b/docs-vuepress/docs/guide/migrate/2.7.md
similarity index 100%
rename from docs-vuepress/guide/migrate/2.7.md
rename to docs-vuepress/docs/guide/migrate/2.7.md
diff --git a/docs-vuepress/guide/migrate/2.8.md b/docs-vuepress/docs/guide/migrate/2.8.md
similarity index 100%
rename from docs-vuepress/guide/migrate/2.8.md
rename to docs-vuepress/docs/guide/migrate/2.8.md
diff --git a/docs-vuepress/guide/migrate/mpx-cli-3.md b/docs-vuepress/docs/guide/migrate/mpx-cli-3.md
similarity index 100%
rename from docs-vuepress/guide/migrate/mpx-cli-3.md
rename to docs-vuepress/docs/guide/migrate/mpx-cli-3.md
diff --git a/docs-vuepress/guide/tool/e2e-test.md b/docs-vuepress/docs/guide/tool/e2e-test.md
similarity index 100%
rename from docs-vuepress/guide/tool/e2e-test.md
rename to docs-vuepress/docs/guide/tool/e2e-test.md
diff --git a/docs-vuepress/guide/tool/ts.md b/docs-vuepress/docs/guide/tool/ts.md
similarity index 100%
rename from docs-vuepress/guide/tool/ts.md
rename to docs-vuepress/docs/guide/tool/ts.md
diff --git a/docs-vuepress/guide/tool/unit-test.md b/docs-vuepress/docs/guide/tool/unit-test.md
similarity index 100%
rename from docs-vuepress/guide/tool/unit-test.md
rename to docs-vuepress/docs/guide/tool/unit-test.md
diff --git a/docs-vuepress/guide/understand/compile.md b/docs-vuepress/docs/guide/understand/compile.md
similarity index 100%
rename from docs-vuepress/guide/understand/compile.md
rename to docs-vuepress/docs/guide/understand/compile.md
diff --git a/docs-vuepress/guide/understand/runtime.md b/docs-vuepress/docs/guide/understand/runtime.md
similarity index 100%
rename from docs-vuepress/guide/understand/runtime.md
rename to docs-vuepress/docs/guide/understand/runtime.md
diff --git a/docs-vuepress/package.json b/docs-vuepress/package.json
new file mode 100644
index 0000000000..72362c53e8
--- /dev/null
+++ b/docs-vuepress/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mpx-docs",
+ "description": "mpx docs",
+ "private": true,
+ "scripts": {
+ "docs:dev": "vuepress dev docs",
+ "docs:build": "vuepress build docs"
+ },
+ "devDependencies": {
+ "@vuepress/plugin-back-to-top": "^1.8.2",
+ "@vuepress/plugin-pwa": "^1.8.0",
+ "vuepress": "^1.9.7"
+ }
+}
diff --git a/examples/mpx-cloud/babel.config.json b/examples/mpx-cloud/babel.config.json
index 6be2f424a9..3aad4a5b59 100644
--- a/examples/mpx-cloud/babel.config.json
+++ b/examples/mpx-cloud/babel.config.json
@@ -13,7 +13,7 @@
"@babel/transform-runtime",
{
"corejs": 3,
- "version": "^7.12.5"
+ "version": "^7.20.13"
}
],
[
diff --git a/examples/mpx-cloud/build/package.json b/examples/mpx-cloud/build/package.json
index a84ff3ca30..894f7fbe22 100644
--- a/examples/mpx-cloud/build/package.json
+++ b/examples/mpx-cloud/build/package.json
@@ -1,5 +1,5 @@
{
- "name": "build",
+ "name": "mpx-cloud-build",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-cloud/config/package.json b/examples/mpx-cloud/config/package.json
index a59b4221e8..c266f7fe89 100644
--- a/examples/mpx-cloud/config/package.json
+++ b/examples/mpx-cloud/config/package.json
@@ -1,5 +1,5 @@
{
- "name": "config",
+ "name": "mpx-cloud-config",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-cloud/package.json b/examples/mpx-cloud/package.json
index 3e42ce5884..d67a217a74 100644
--- a/examples/mpx-cloud/package.json
+++ b/examples/mpx-cloud/package.json
@@ -1,5 +1,5 @@
{
- "name": "mpx-cloud",
+ "name": "mpx-cloud-demo",
"version": "1.0.0",
"description": "A mpx project",
"main": "index.js",
@@ -19,16 +19,16 @@
"author": "sky-admin <546485299@qq.com>",
"license": "ISC",
"dependencies": {
- "@mpxjs/api-proxy": "^2.7.28",
- "@mpxjs/core": "^2.7.26"
+ "@mpxjs/api-proxy": "workspace:*",
+ "@mpxjs/core": "workspace:*"
},
"devDependencies": {
"@babel/core": "7.12.10",
"@babel/eslint-parser": "7.16.0",
"@babel/plugin-transform-runtime": "7.12.10",
"@babel/preset-env": "7.12.11",
- "@babel/runtime-corejs3": "7.12.5",
- "@mpxjs/webpack-plugin": "^2.7.27",
+ "@babel/runtime-corejs3": "7.20.13",
+ "@mpxjs/webpack-plugin": "workspace:*",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"babel-jest": "^25.3.0",
diff --git a/examples/mpx-i18n/babel.config.json b/examples/mpx-i18n/babel.config.json
index 6be2f424a9..3aad4a5b59 100644
--- a/examples/mpx-i18n/babel.config.json
+++ b/examples/mpx-i18n/babel.config.json
@@ -13,7 +13,7 @@
"@babel/transform-runtime",
{
"corejs": 3,
- "version": "^7.12.5"
+ "version": "^7.20.13"
}
],
[
diff --git a/examples/mpx-i18n/build/package.json b/examples/mpx-i18n/build/package.json
index a84ff3ca30..a89946d72b 100644
--- a/examples/mpx-i18n/build/package.json
+++ b/examples/mpx-i18n/build/package.json
@@ -1,5 +1,5 @@
{
- "name": "build",
+ "name": "mpx-i18n-build",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-i18n/config/package.json b/examples/mpx-i18n/config/package.json
index a59b4221e8..ae7527e23b 100644
--- a/examples/mpx-i18n/config/package.json
+++ b/examples/mpx-i18n/config/package.json
@@ -1,5 +1,5 @@
{
- "name": "config",
+ "name": "mpx-i18n-config",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-i18n/package.json b/examples/mpx-i18n/package.json
index 241eeca038..8a1703ed49 100644
--- a/examples/mpx-i18n/package.json
+++ b/examples/mpx-i18n/package.json
@@ -1,5 +1,5 @@
{
- "name": "mpx-subpackage-demo",
+ "name": "mpx-i18n-demo",
"version": "1.0.0",
"description": "A mpx project",
"main": "index.js",
@@ -19,16 +19,16 @@
"author": "sky-admin <546485299@qq.com>",
"license": "ISC",
"dependencies": {
- "@mpxjs/api-proxy": "^2.7.28",
- "@mpxjs/core": "^2.7.26"
+ "@mpxjs/api-proxy": "workspace:*",
+ "@mpxjs/core": "workspace:*"
},
"devDependencies": {
"@babel/core": "7.12.10",
"@babel/eslint-parser": "7.16.0",
"@babel/plugin-transform-runtime": "7.12.10",
"@babel/preset-env": "7.12.11",
- "@babel/runtime-corejs3": "7.12.5",
- "@mpxjs/webpack-plugin": "^2.7.27",
+ "@babel/runtime-corejs3": "7.20.13",
+ "@mpxjs/webpack-plugin": "workspace:*",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"babel-jest": "^25.3.0",
@@ -59,7 +59,7 @@
"vue-loader": "^15.9.3",
"vue-router": "^3.1.3",
"vue-style-loader": "^4.1.2",
- "vue-template-compiler": "^2.6.10",
+ "vue-template-compiler": "~2.6.10",
"ts-loader": "9.2.6",
"typescript": "^4.1.3",
"webpack": "^5.72.0",
diff --git a/examples/mpx-progressive/babel.config.json b/examples/mpx-progressive/babel.config.json
index 6be2f424a9..3aad4a5b59 100644
--- a/examples/mpx-progressive/babel.config.json
+++ b/examples/mpx-progressive/babel.config.json
@@ -13,7 +13,7 @@
"@babel/transform-runtime",
{
"corejs": 3,
- "version": "^7.12.5"
+ "version": "^7.20.13"
}
],
[
diff --git a/examples/mpx-progressive/build/package.json b/examples/mpx-progressive/build/package.json
index a84ff3ca30..c214b883a2 100644
--- a/examples/mpx-progressive/build/package.json
+++ b/examples/mpx-progressive/build/package.json
@@ -1,5 +1,5 @@
{
- "name": "build",
+ "name": "mpx-progressive-build",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-progressive/config/package.json b/examples/mpx-progressive/config/package.json
index a59b4221e8..ef5dd4236f 100644
--- a/examples/mpx-progressive/config/package.json
+++ b/examples/mpx-progressive/config/package.json
@@ -1,5 +1,5 @@
{
- "name": "config",
+ "name": "mpx-progressive-config",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-progressive/package.json b/examples/mpx-progressive/package.json
index b1021c92ac..a2a69f58dd 100644
--- a/examples/mpx-progressive/package.json
+++ b/examples/mpx-progressive/package.json
@@ -1,5 +1,5 @@
{
- "name": "mpx-subpackage-demo",
+ "name": "mpx-progressive-demo",
"version": "1.0.0",
"description": "A mpx project",
"main": "index.js",
@@ -19,8 +19,8 @@
"author": "sky-admin <546485299@qq.com>",
"license": "ISC",
"dependencies": {
- "@mpxjs/api-proxy": "^2.7.28",
- "@mpxjs/core": "^2.7.26"
+ "@mpxjs/api-proxy": "workspace:*",
+ "@mpxjs/core": "workspace:*"
},
"devDependencies": {
"@babel/core": "7.12.10",
@@ -28,8 +28,8 @@
"@babel/plugin-syntax-typescript": "^7.16.7",
"@babel/plugin-transform-runtime": "7.12.10",
"@babel/preset-env": "7.12.11",
- "@babel/runtime-corejs3": "7.12.5",
- "@mpxjs/webpack-plugin": "^2.7.27",
+ "@babel/runtime-corejs3": "7.20.13",
+ "@mpxjs/webpack-plugin": "workspace:*",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"babel-jest": "^25.3.0",
diff --git a/examples/mpx-subpackage/babel.config.json b/examples/mpx-subpackage/babel.config.json
index 6be2f424a9..3aad4a5b59 100644
--- a/examples/mpx-subpackage/babel.config.json
+++ b/examples/mpx-subpackage/babel.config.json
@@ -13,7 +13,7 @@
"@babel/transform-runtime",
{
"corejs": 3,
- "version": "^7.12.5"
+ "version": "^7.20.13"
}
],
[
diff --git a/examples/mpx-subpackage/build/package.json b/examples/mpx-subpackage/build/package.json
index a84ff3ca30..aca8f718a9 100644
--- a/examples/mpx-subpackage/build/package.json
+++ b/examples/mpx-subpackage/build/package.json
@@ -1,5 +1,5 @@
{
- "name": "build",
+ "name": "mpx-subpackage-build",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-subpackage/config/package.json b/examples/mpx-subpackage/config/package.json
index a59b4221e8..a13c9d6ca8 100644
--- a/examples/mpx-subpackage/config/package.json
+++ b/examples/mpx-subpackage/config/package.json
@@ -1,5 +1,5 @@
{
- "name": "config",
+ "name": "mpx-subpackage-config",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-subpackage/package.json b/examples/mpx-subpackage/package.json
index ec23a2c0ec..8472248f89 100644
--- a/examples/mpx-subpackage/package.json
+++ b/examples/mpx-subpackage/package.json
@@ -19,16 +19,16 @@
"author": "sky-admin <546485299@qq.com>",
"license": "ISC",
"dependencies": {
- "@mpxjs/api-proxy": "^2.7.28",
- "@mpxjs/core": "^2.7.26"
+ "@mpxjs/api-proxy": "workspace:*",
+ "@mpxjs/core": "workspace:*"
},
"devDependencies": {
"@babel/core": "7.12.10",
"@babel/eslint-parser": "7.16.0",
"@babel/plugin-transform-runtime": "7.12.10",
"@babel/preset-env": "7.12.11",
- "@babel/runtime-corejs3": "7.12.5",
- "@mpxjs/webpack-plugin": "^2.7.27",
+ "@babel/runtime-corejs3": "7.20.13",
+ "@mpxjs/webpack-plugin": "workspace:*",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"babel-jest": "^25.3.0",
diff --git a/examples/mpx-todoMVC/babel.config.json b/examples/mpx-todoMVC/babel.config.json
index 24f07ee7d1..9ab394b138 100644
--- a/examples/mpx-todoMVC/babel.config.json
+++ b/examples/mpx-todoMVC/babel.config.json
@@ -13,7 +13,7 @@
"@babel/transform-runtime",
{
"corejs": 3,
- "version": "^7.12.5"
+ "version": "^7.20.13"
}
],
"lodash"
diff --git a/examples/mpx-todoMVC/build/package.json b/examples/mpx-todoMVC/build/package.json
index a84ff3ca30..298b32e405 100644
--- a/examples/mpx-todoMVC/build/package.json
+++ b/examples/mpx-todoMVC/build/package.json
@@ -1,5 +1,5 @@
{
- "name": "build",
+ "name": "mpx-todoMVC-build",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-todoMVC/config/package.json b/examples/mpx-todoMVC/config/package.json
index a59b4221e8..2a2d17a918 100644
--- a/examples/mpx-todoMVC/config/package.json
+++ b/examples/mpx-todoMVC/config/package.json
@@ -1,5 +1,5 @@
{
- "name": "config",
+ "name": "mpx-todoMVC-config",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-todoMVC/package.json b/examples/mpx-todoMVC/package.json
index 88a51becf3..f513c27ec5 100644
--- a/examples/mpx-todoMVC/package.json
+++ b/examples/mpx-todoMVC/package.json
@@ -1,5 +1,5 @@
{
- "name": "mpx-subpackage-demo",
+ "name": "mpx-todo-mvc-demo",
"version": "1.0.0",
"description": "A mpx project",
"main": "index.js",
@@ -19,16 +19,16 @@
"author": "sky-admin <546485299@qq.com>",
"license": "ISC",
"dependencies": {
- "@mpxjs/api-proxy": "^2.8.0-beta.2",
- "@mpxjs/core": "^2.8.0-beta.3"
+ "@mpxjs/api-proxy": "workspace:*",
+ "@mpxjs/core": "workspace:*"
},
"devDependencies": {
"@babel/core": "7.12.10",
"@babel/eslint-parser": "7.16.0",
"@babel/plugin-transform-runtime": "7.12.10",
"@babel/preset-env": "7.12.11",
- "@babel/runtime-corejs3": "7.12.5",
- "@mpxjs/webpack-plugin": "^2.8.0-beta.2",
+ "@babel/runtime-corejs3": "7.20.13",
+ "@mpxjs/webpack-plugin": "workspace:*",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"babel-jest": "^25.3.0",
diff --git a/examples/mpx-transform-web/.gitignore b/examples/mpx-transform-web/.gitignore
new file mode 100644
index 0000000000..53f7466aca
--- /dev/null
+++ b/examples/mpx-transform-web/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local
\ No newline at end of file
diff --git a/examples/mpx-transform-web/babel.config.json b/examples/mpx-transform-web/babel.config.json
new file mode 100644
index 0000000000..58b76917fa
--- /dev/null
+++ b/examples/mpx-transform-web/babel.config.json
@@ -0,0 +1,33 @@
+{
+ "presets": [
+ [
+ "@babel/env",
+ {
+ "modules": false,
+ "shippedProposals": true
+ }
+ ]
+ ],
+ "plugins": [
+ [
+ "@babel/transform-runtime",
+ {
+ "corejs": 3,
+ "version": "^7.10.4"
+ }
+ ]
+ ],
+ "sourceType": "unambiguous",
+ "env": {
+ "test": {
+ "presets": [
+ [
+ "@babel/env",
+ {
+ "shippedProposals": true
+ }
+ ]
+ ]
+ }
+ }
+}
diff --git a/examples/mpx-transform-web/build/build.js b/examples/mpx-transform-web/build/build.js
new file mode 100644
index 0000000000..f2d5e04844
--- /dev/null
+++ b/examples/mpx-transform-web/build/build.js
@@ -0,0 +1,127 @@
+const rm = require('rimraf')
+const chalk = require('chalk')
+const webpack = require('webpack')
+const program = require('commander')
+const { userConf, supportedModes } = require('../config/index')
+const getWebpackConf = require('./getWebpackConf')
+const { resolveDist, getRootPath } = require('./utils')
+
+program
+ .option('-w, --watch', 'watch mode')
+ .option('-p, --production', 'production release')
+ .parse(process.argv)
+
+const env = process.env
+
+const modeStr = env.npm_config_mode || env.npm_config_modes || ''
+
+const report = env.npm_config_report
+
+const modes = modeStr.split(/[,|]/)
+ .map((mode) => {
+ const modeArr = mode.split(':')
+ if (supportedModes.includes(modeArr[0])) {
+ return {
+ mode: modeArr[0],
+ env: modeArr[1]
+ }
+ }
+ }).filter((item) => item)
+
+if (!modes.length) {
+ modes.push({
+ mode: userConf.srcMode
+ })
+}
+
+// 开启子进程
+if (userConf.openChildProcess && modes.length > 1) {
+ let scriptType = ''
+ const isProduct = program.production
+ const isWatch = program.watch
+ if (!isProduct && isWatch) scriptType = 'watch'
+ if (isProduct && !isWatch) scriptType = 'build'
+ if (isProduct && isWatch) scriptType = 'watch:prod'
+ if (!isProduct && !isWatch) scriptType = 'build:dev'
+
+ const spawn = require('child_process').spawn
+ while (modes.length > 1) {
+ const modeObj = modes.pop()
+ const modeAndEnv = modeObj.env ? `${modeObj.mode}:${modeObj.env}` : modeObj.mode
+ const ls = spawn('npm', ['run', scriptType, `--modes=${modeAndEnv}`, `--mode=${modeAndEnv}`], { stdio: 'inherit' })
+ ls.on('close', (code) => {
+ process.exitCode = code
+ })
+ }
+}
+
+let webpackConfs = []
+
+modes.forEach(({ mode, env }) => {
+ const options = Object.assign({}, userConf, {
+ mode,
+ env,
+ production: program.production,
+ watch: program.watch,
+ report,
+ subDir: (userConf.isPlugin || userConf.cloudFunc) ? 'miniprogram' : ''
+ })
+ webpackConfs.push(getWebpackConf(options))
+})
+
+if (webpackConfs.length === 1) {
+ webpackConfs = webpackConfs[0]
+}
+
+
+try {
+ modes.forEach(({ mode, env }) => {
+ rm.sync(resolveDist(getRootPath(mode, env), '*'))
+ })
+} catch (e) {
+ console.error(e)
+ console.log('\n\n删除dist文件夹遇到了一些问题,如果遇到问题请手工删除dist重来\n\n')
+}
+
+if (program.watch) {
+ webpack(webpackConfs).watch(undefined, callback)
+} else {
+ webpack(webpackConfs, callback)
+}
+
+function callback (err, stats) {
+ if (err) {
+ process.exitCode = 1
+ return console.error(err)
+ }
+ if (Array.isArray(stats.stats)) {
+ stats.stats.forEach(item => {
+ console.log(item.compilation.name + '打包结果:')
+ process.stdout.write(item.toString({
+ colors: true,
+ modules: false,
+ children: false,
+ chunks: false,
+ chunkModules: false,
+ entrypoints: false
+ }) + '\n\n')
+ })
+ } else {
+ process.stdout.write(stats.toString({
+ colors: true,
+ modules: false,
+ children: false,
+ chunks: false,
+ chunkModules: false,
+ entrypoints: false
+ }) + '\n\n')
+ }
+
+ if (stats.hasErrors()) {
+ console.log(chalk.red(' Build failed with errors.\n'))
+ } else if (program.watch) {
+ console.log(chalk.cyan(` Build complete at ${new Date()}.\n Still watching...\n`))
+ } else {
+ console.log(chalk.cyan(' Build complete.\n'))
+ }
+}
diff --git a/examples/mpx-transform-web/build/getPlugins.js b/examples/mpx-transform-web/build/getPlugins.js
new file mode 100644
index 0000000000..8ce6bdf931
--- /dev/null
+++ b/examples/mpx-transform-web/build/getPlugins.js
@@ -0,0 +1,44 @@
+let { mpxPluginConf } = require('../config/index')
+const MpxWebpackPlugin = require('@mpxjs/web-plugin/webpack')
+const { resolveSrc, getConf } = require('./utils')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+const VueLoaderPlugin = require('vue-loader').VueLoaderPlugin
+const webpack = require('webpack')
+module.exports = function getPlugins (options) {
+ const { mode, srcMode, env, subDir, production, report } = options
+ const plugins = []
+ const currentMpxPluginConf = getConf(mpxPluginConf, options)
+ plugins.push(new MpxWebpackPlugin(Object.assign({}, currentMpxPluginConf, {
+ mode,
+ srcMode,
+ env: 'didi',
+ // webConfig: {
+ // routeMode: 'history'
+ // },
+ externalClasses: ['list-class'],
+ }, env && { env })))
+
+ plugins.push(new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: production ? '"production"' : '"development"'
+ }
+ }))
+
+ if (mode === 'web') {
+ plugins.push(new VueLoaderPlugin())
+ plugins.push(new HtmlWebpackPlugin({
+ filename: 'index.html',
+ template: resolveSrc('index.html', subDir),
+ inject: true
+ }))
+ }
+
+ plugins.push(new webpack.ProgressPlugin())
+
+ if (report) {
+ plugins.push(new BundleAnalyzerPlugin())
+ }
+
+ return plugins
+}
diff --git a/examples/mpx-transform-web/build/getRules.js b/examples/mpx-transform-web/build/getRules.js
new file mode 100644
index 0000000000..27909baca5
--- /dev/null
+++ b/examples/mpx-transform-web/build/getRules.js
@@ -0,0 +1,122 @@
+let { mpxLoaderConf } = require('../config/index')
+const MpxWebpackPlugin = require('@mpxjs/web-plugin/webpack')
+// const MpxWebpackPlugin = require('@mpxjs/webpack-plugin')
+const { resolve, getConf } = require('./utils')
+
+const baseRules = [
+ {
+ test: /\.js$/,
+ loader: 'babel-loader',
+ include: [/\.mpx\.js/, resolve('src'), resolve('test'), resolve('node_modules/@mpxjs')]
+ },
+ {
+ test: /\.json$/,
+ resourceQuery: /asScript/,
+ type: 'javascript/auto'
+ },
+ {
+ test: /\.(wxs|qs|sjs|qjs|jds|dds|filter\.js)$/,
+ use: [
+ MpxWebpackPlugin.wxsPreLoader()
+ ],
+ enforce: 'pre'
+ },
+ {
+ test: /\.(png|jpe?g|gif|svg)$/,
+ use: [
+ MpxWebpackPlugin.urlLoader({
+ name: 'img/[name][hash].[ext]'
+ })
+ ]
+ }
+]
+
+const tsRule = {
+ test: /\.ts$/,
+ use: [
+ 'babel-loader',
+ {
+ loader: 'ts-loader',
+ options: {
+ appendTsSuffixTo: [/\.(mpx|vue)$/]
+ }
+ }
+ ]
+}
+
+module.exports = function getRules (options) {
+ const { mode, tsSupport } = options
+
+ let rules = baseRules.slice()
+
+ if (tsSupport) {
+ rules.push(tsRule)
+ }
+
+ const currentMpxLoaderConf = getConf(mpxLoaderConf, options)
+
+ if (mode === 'web') {
+ rules = rules.concat([
+ {
+ test: /\.mpx$/,
+ use: [
+ {
+ loader: 'vue-loader',
+ options: {
+ transformToRequire: {
+ 'mpx-image': 'src',
+ 'mpx-audio': 'src',
+ 'mpx-video': 'src'
+ }
+ }
+ },
+ MpxWebpackPlugin.loader(currentMpxLoaderConf)
+ ]
+ },
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader'
+ },
+ // 如输出web时需要支持其他预编译语言,可以在此添加rule配置
+ {
+ test: /\.styl(us)?$/,
+ use: [
+ 'vue-style-loader',
+ 'css-loader',
+ 'stylus-loader'
+ ]
+ },
+ {
+ test: /\.css$/,
+ use: [
+ 'vue-style-loader',
+ 'css-loader'
+ ]
+ }
+ ])
+ } else {
+ rules = rules.concat([
+ {
+ test: /\.mpx$/,
+ use: MpxWebpackPlugin.loader(currentMpxLoaderConf)
+ },
+ {
+ test: /\.styl(us)?$/,
+ use: [
+ MpxWebpackPlugin.wxssLoader(),
+ 'stylus-loader'
+ ]
+ },
+ {
+ test: /\.(wxss|acss|css|qss|ttss|jxss|ddss)$/,
+ use: MpxWebpackPlugin.wxssLoader()
+ },
+ {
+ test: /\.(wxml|axml|swan|qml|ttml|qxml|jxml|ddml)$/,
+ use: MpxWebpackPlugin.wxmlLoader()
+ }
+ ])
+ }
+
+ return rules
+}
diff --git a/examples/mpx-transform-web/build/getWebpackConf.js b/examples/mpx-transform-web/build/getWebpackConf.js
new file mode 100644
index 0000000000..4f7b971ca1
--- /dev/null
+++ b/examples/mpx-transform-web/build/getWebpackConf.js
@@ -0,0 +1,53 @@
+const webpackBaseConf = require('./webpack.base.conf')
+const { mergeWithCustomize, customizeObject } = require('webpack-merge')
+const getRules = require('./getRules')
+const getPlugins = require('./getPlugins')
+const { resolveSrc, resolveDist, getRootPath } = require('./utils')
+const MpxWebpackPlugin = require('@mpxjs/web-plugin/webpack')
+// const MpxWebpackPlugin = require('@mpxjs/webpack-plugin')
+
+module.exports = function getWebpackConfs (options) {
+ const { plugin, subDir, mode, env, production, watch } = options
+ const entry = plugin
+ ? { plugin: MpxWebpackPlugin.getPluginEntry(resolveSrc('plugin.json', subDir)) }
+ : { app: resolveSrc('app.mpx', subDir) }
+ const rootPath = getRootPath(mode, env)
+ const output = {
+ path: resolveDist(rootPath, subDir),
+ publicPath: '/',
+ filename: '[name].js',
+ crossOriginLoading: 'anonymous'
+ }
+ const name = plugin ? `${rootPath}-plugin-compiler` : `${rootPath}-compiler`
+ const rules = getRules(options)
+ // console.log(rules);
+ const plugins = getPlugins(options)
+ const extendConfs = {}
+ if (production) {
+ extendConfs.mode = 'production'
+ }
+ extendConfs.optimization = {
+ nodeEnv: production ? 'production' : 'development'
+ }
+ if (watch) {
+ // 仅在watch模式下生产sourcemap
+ // 百度小程序不开启sourcemap,开启会有模板渲染问题
+ if (mode !== 'swan') {
+ extendConfs.devtool = 'source-map'
+ }
+ }
+
+ return mergeWithCustomize({
+ customizeObject: customizeObject({
+ snapshot: 'replace'
+ })
+ })(webpackBaseConf, {
+ name,
+ entry,
+ output,
+ module: {
+ rules
+ },
+ plugins
+ }, extendConfs)
+}
diff --git a/examples/mpx-transform-web/build/package.json b/examples/mpx-transform-web/build/package.json
new file mode 100644
index 0000000000..b76afc0e66
--- /dev/null
+++ b/examples/mpx-transform-web/build/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "mpx-transform-web-build",
+ "version": "1.0.0",
+ "description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
+}
diff --git a/examples/mpx-transform-web/build/utils.js b/examples/mpx-transform-web/build/utils.js
new file mode 100644
index 0000000000..5334f26415
--- /dev/null
+++ b/examples/mpx-transform-web/build/utils.js
@@ -0,0 +1,39 @@
+const path = require('path')
+
+function resolveSrc (file, subDir = '') {
+ return path.join(__dirname, '../src', subDir, file || '')
+}
+
+function resolveDist (platform, subDir = '') {
+ return path.join(__dirname, '../dist', platform, subDir)
+}
+
+function resolve (file) {
+ return path.join(__dirname, '..', file || '')
+}
+
+function normalizeArr (arrCfg) {
+ if (Array.isArray(arrCfg) && arrCfg.length) {
+ return arrCfg
+ } else if (arrCfg) {
+ return [arrCfg]
+ }
+ return []
+}
+
+function getRootPath (...args) {
+ return args.filter(item => item).join('_')
+}
+
+function getConf (conf, options) {
+ return typeof conf === 'function' ? conf(options) : conf
+}
+
+module.exports = {
+ resolve,
+ resolveSrc,
+ resolveDist,
+ normalizeArr,
+ getRootPath,
+ getConf
+}
diff --git a/examples/mpx-transform-web/build/webpack.base.conf.js b/examples/mpx-transform-web/build/webpack.base.conf.js
new file mode 100644
index 0000000000..84ea2119c9
--- /dev/null
+++ b/examples/mpx-transform-web/build/webpack.base.conf.js
@@ -0,0 +1,43 @@
+const { resolve } = require('./utils')
+
+module.exports = {
+ performance: {
+ hints: false
+ },
+ mode: 'none',
+ resolve: {
+ extensions: ['.mpx', '.js', '.wxml', '.vue', '.ts'],
+ modules: ['node_modules']
+ },
+ // cache: {
+ // type: 'filesystem',
+ // buildDependencies: {
+ // build: [resolve('build/')],
+ // config: [resolve('config/')]
+ // },
+ // cacheDirectory: resolve('.cache/')
+ // },
+ snapshot: {
+ // 如果希望修改node_modules下的文件时对应的缓存可以失效,可以将此处的配置改为 managedPaths: []
+ managedPaths: []
+ },
+ optimization: {
+ minimizer: [
+ {
+ apply: compiler => {
+ // Lazy load the Terser plugin
+ const TerserPlugin = require('terser-webpack-plugin')
+ new TerserPlugin({
+ // terserOptions参考 https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
+ terserOptions: {
+ // terser的默认行为会把某些对象方法转为箭头函数,导致ios9等不支持箭头函数的环境白屏,详情见 https://github.com/terser/terser#compress-options
+ compress: {
+ arrows: false
+ }
+ }
+ }).apply(compiler)
+ }
+ }
+ ]
+ }
+}
diff --git a/examples/mpx-transform-web/config/dll.conf.js b/examples/mpx-transform-web/config/dll.conf.js
new file mode 100644
index 0000000000..69a0b8285d
--- /dev/null
+++ b/examples/mpx-transform-web/config/dll.conf.js
@@ -0,0 +1,16 @@
+const { resolve, resolveSrc } = require('../build/utils')
+module.exports = {
+ path: resolve('dll'),
+ context: resolveSrc(),
+ groups: {
+ cacheGroups: [
+ {
+ entries: [resolveSrc('lib/dll')],
+ name: 'dll',
+ }
+ ],
+ webpackCfg: {
+ mode: 'none'
+ }
+ }
+}
diff --git a/examples/mpx-transform-web/config/index.js b/examples/mpx-transform-web/config/index.js
new file mode 100644
index 0000000000..9f84ab52c3
--- /dev/null
+++ b/examples/mpx-transform-web/config/index.js
@@ -0,0 +1,22 @@
+const mpxLoaderConf = require('./mpxLoader.conf')
+const mpxPluginConf = require('./mpxPlugin.conf')
+const userConf = require('./user.conf')
+
+const supportedModes = ['wx', 'ali', 'swan', 'qq', 'tt', 'qa', 'jd', 'dd']
+
+if (userConf.transWeb) {
+ supportedModes.push('web')
+}
+
+const options = {
+ userConf,
+ mpxLoaderConf,
+ mpxPluginConf,
+ supportedModes
+}
+
+if (userConf.needDll) {
+ options.dllConf = require('./dll.conf')
+}
+
+module.exports = options
diff --git a/examples/mpx-transform-web/config/mpxLoader.conf.js b/examples/mpx-transform-web/config/mpxLoader.conf.js
new file mode 100644
index 0000000000..36bbfe2cf2
--- /dev/null
+++ b/examples/mpx-transform-web/config/mpxLoader.conf.js
@@ -0,0 +1,3 @@
+// mpx的loader配置在这里传入
+// 配置项文档:https://www.mpxjs.cn/api/compile.html#mpxwebpackplugin-loader
+module.exports = {}
diff --git a/examples/mpx-transform-web/config/mpxPlugin.conf.js b/examples/mpx-transform-web/config/mpxPlugin.conf.js
new file mode 100644
index 0000000000..71e185b0f9
--- /dev/null
+++ b/examples/mpx-transform-web/config/mpxPlugin.conf.js
@@ -0,0 +1,65 @@
+const resolve = require('../build/utils').resolve
+const path = require('path')
+// 可以在此配置mpx webpack plugin
+// 配置项文档: https://www.mpxjs.cn/api/compile.html#mpxwebpackplugin-options
+module.exports = {
+ mode: 'web',
+ srcMode: 'wx',
+ // env为mpx编译的目标环境,需自定义
+ // env: 'didi',
+ // resolve的模式
+ resolveMode: 'webpack', // 可选值 webpack / native,默认是webpack,原生迁移建议使用native
+
+ // 当resolveMode为native时可通过该字段指定项目根目录
+ // projectRoot: resolve('src'),
+
+ // 可选值 full / changed,不传默认为change,当设置为changed时在watch模式下将只会对内容发生变化的文件进行写入,以提升小程序开发者工具编译性能
+ writeMode: 'changed',
+
+ // 是否需要对样式加scope,因为只有ali平台没有样式隔离,只对ali平台生效,提供include和exclude,和webpack的rules规则相同
+ // autoScopeRules: {
+ // include: [resolve('src')]
+ // },
+
+ // 批量指定文件mode,用法如下,指定平台,提供include/exclude指定文件,即include的文件会默认被认为是该平台的,include/exclude的规则和webpack的rules的相同
+ modeRules: {
+ // ali: {
+ // include: [resolve('node_modules/vant-aliapp')]
+ // }
+ },
+
+ // 定义一些全局环境变量,可在JS/模板/样式/JSON中使用
+ defs: {
+ __application_name__: 'dd'
+ },
+
+ // 是否转换px到rpx
+ transRpxRules: [
+ {
+ mode: 'only',
+ comment: 'use rpx',
+ include: resolve('src')
+ }
+ ],
+
+ // 多语言i18n能力 以下是简单示例,更多详情请参考文档:https://didi.github.io/mpx/i18n.html
+ i18n: {
+ locale: 'en-US',
+ // messages既可以通过对象字面量传入,也可以通过messagesPath指定一个js模块路径,在该模块中定义配置并导出,dateTimeFormats/dateTimeFormatsPath和numberFormats/numberFormatsPath同理
+ messagesPath: path.resolve(__dirname, '../src/i18n/index.js')
+ // messages: {
+ // 'en-US': {
+ // message: {
+ // title: 'test2344',
+ // hello: '{msg} world'
+ // }
+ // },
+ // 'zh-CN': {
+ // message: {
+ // title: '中文',
+ // hello: '{msg} 世界'
+ // }
+ // }
+ // }
+ }
+}
diff --git a/examples/mpx-transform-web/config/package.json b/examples/mpx-transform-web/config/package.json
new file mode 100644
index 0000000000..1c834f8878
--- /dev/null
+++ b/examples/mpx-transform-web/config/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "mpx-transform-web-config",
+ "version": "1.0.0",
+ "description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
+}
diff --git a/examples/mpx-transform-web/config/user.conf.js b/examples/mpx-transform-web/config/user.conf.js
new file mode 100644
index 0000000000..34ac93e3a4
--- /dev/null
+++ b/examples/mpx-transform-web/config/user.conf.js
@@ -0,0 +1,21 @@
+function formatOption (option) {
+ if (option === 'true') return true
+ if (option === 'false') return false
+ return option
+}
+
+// 根据创建项目时的问题生成的
+// 改动需谨慎,有的选项存在互斥关系,比如跨平台,则无法使用云函数
+// 若需修改以启用新的能力,建议试试新建项目按问题生成模板后把这部分内容拷贝过来
+module.exports = {
+ srcMode: formatOption('wx'),
+ cross: formatOption('true'),
+ openChildProcess: formatOption('true'),
+ transWeb: formatOption('true'),
+ cloudFunc: formatOption('false'),
+ isPlugin: formatOption('false'),
+ tsSupport: formatOption('false'),
+ needEslint: formatOption('false'),
+ needDll: formatOption('true'),
+ needUnitTest: formatOption('false')
+}
diff --git a/examples/mpx-transform-web/index.html b/examples/mpx-transform-web/index.html
new file mode 100644
index 0000000000..352793a15b
--- /dev/null
+++ b/examples/mpx-transform-web/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ Vite App
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/package.json b/examples/mpx-transform-web/package.json
new file mode 100644
index 0000000000..410e78ca0b
--- /dev/null
+++ b/examples/mpx-transform-web/package.json
@@ -0,0 +1,96 @@
+{
+ "name": "mpx-transform-web-demo",
+ "version": "1.0.0",
+ "description": "A mpx project",
+ "main": "index.js",
+ "scripts": {
+ "vite:dev": "vite",
+ "vite:build": "vite build",
+ "watch": "node build/build.js -w",
+ "watch:prod": "node build/build.js -w -p",
+ "build": "npm run prod --modes=web",
+ "prod": "node build/build.js -p",
+ "watch:web:serve": "npx npm-run-all --parallel watch:web serve",
+ "watch:web": "npm run watch --mode=web",
+ "serve": "npx http-server dist/web",
+ "lint": "eslint --ext .js,.ts,.mpx src/"
+ },
+ "author": "yandadaFreedom <525966780@qq.com>",
+ "license": "ISC",
+ "dependencies": {
+ "@mpxjs/api-proxy": "workspace:*",
+ "@mpxjs/core": "workspace:*",
+ "@mpxjs/web-plugin": "workspace:*",
+ "@mpxjs/webpack-plugin": "workspace:*",
+ "@mpxjs/loaders": "workspace:*",
+ "@mpxjs/store": "workspace:*",
+ "mime": "^2.6.0",
+ "vue": "~2.7.10",
+ "vue-demi": "^0.13.11",
+ "vue-i18n": "^8.27.2",
+ "vue-i18n-bridge": "^9.2.2",
+ "@intlify/core-base": "9.2.2",
+ "@intlify/shared": "9.2.2",
+ "@intlify/vue-devtools": "9.2.2",
+ "@intlify/devtools-if": "9.2.2",
+ "@intlify/message-compiler": "9.2.2",
+ "core-js-pure": "^3.28.0"
+ },
+ "browserslist": [
+ "ios >= 9",
+ "chrome >= 47"
+ ],
+ "devDependencies": {
+ "@babel/core": "^7.10.4",
+ "@babel/eslint-parser": "^7.16.0",
+ "@babel/plugin-transform-runtime": "^7.10.4",
+ "@babel/plugin-transform-typescript": "^7.16.1",
+ "@babel/preset-env": "^7.10.4",
+ "@babel/runtime-corejs3": "^7.20.13",
+ "@better-scroll/core": "^2.2.1",
+ "@better-scroll/movable": "^2.2.1",
+ "@better-scroll/observe-dom": "^2.2.1",
+ "@better-scroll/pull-down": "^2.2.1",
+ "@better-scroll/slide": "^2.2.1",
+ "@better-scroll/wheel": "^2.2.1",
+ "@better-scroll/zoom": "^2.2.1",
+ "@originjs/vite-plugin-commonjs": "^1.0.3",
+ "@typescript-eslint/eslint-plugin": "^5.2.0",
+ "@typescript-eslint/parser": "^5.2.0",
+ "@vitejs/plugin-legacy": "4.0.0",
+ "autoprefixer": "^6.3.1",
+ "babel-jest": "^27.4.5",
+ "babel-loader": "^8.1.0",
+ "chalk": "^2.3.2",
+ "commander": "^6.0.0",
+ "copy-webpack-plugin": "^9.0.1",
+ "css-loader": "^0.28.11",
+ "eslint": "^7.32.0",
+ "html-loader": "^3.0.1",
+ "html-webpack-plugin": "^5.3.2",
+ "http-server": "^0.12.0",
+ "jest": "^27.4.5",
+ "npm-run-all": "^4.1.5",
+ "path": "^0.12.7",
+ "postcss": "^8.4.5",
+ "rimraf": "^2.6.2",
+ "stylus": "^0.54.5",
+ "stylus-loader": "^3.0.2",
+ "terser": "^5.0.0",
+ "terser-webpack-plugin": "^5.3.6",
+ "ts-jest": "^27.1.2",
+ "ts-loader": "^9.2.6",
+ "typescript": "^4.1.2",
+ "vconsole": "^3.2.0",
+ "vite": "^4.0.0",
+ "vue-loader": "^15.9.3",
+ "vue-router": "^3.5.4",
+ "vue-style-loader": "^4.1.2",
+ "vue-template-compiler": "~2.6.10",
+ "webpack": "^5.69.1",
+ "webpack-bundle-analyzer": "^4.5.0",
+ "webpack-merge": "^5.8.0",
+ "lodash": "^4.1.1",
+ "esbuild": "^0.16.17"
+ }
+}
diff --git a/examples/mpx-transform-web/postcss.config.js b/examples/mpx-transform-web/postcss.config.js
new file mode 100644
index 0000000000..e66012e084
--- /dev/null
+++ b/examples/mpx-transform-web/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: [
+ require('autoprefixer')({ remove: false })
+
+ ]
+}
diff --git a/examples/mpx-transform-web/src/app.json b/examples/mpx-transform-web/src/app.json
new file mode 100644
index 0000000000..9c886b1f98
--- /dev/null
+++ b/examples/mpx-transform-web/src/app.json
@@ -0,0 +1,33 @@
+{
+ "pages": [
+ "./pages/wxs?async=app_pages2",
+ "./pages/index",
+ "./pages/index2?async=app_pages1"
+ ],
+ "packages": ["./packages/index"],
+ "subpackages": [
+ {
+ "root": "packageA",
+ "pages": [
+ "./pages/picker",
+ "./pages/swiper"
+ ],
+ "independent": true
+ }
+ ],
+ "tabBar": {
+ "custom": false,
+ "color": "red",
+ "selectedColor": "black",
+ "backgroundColor": "white",
+ "list": [{
+ "pagePath": "pages/index",
+ "text": "page10",
+ "iconPath": "./images/icon1.png"
+ }, {
+ "pagePath": "pages/wxs",
+ "text": "page2",
+ "iconPath": "https://ut-static.udache.com/webx/mini-pics/pZ0qkcV-yweUQ644F5pVp.png"
+ }]
+ }
+}
diff --git a/examples/mpx-transform-web/src/app.mpx b/examples/mpx-transform-web/src/app.mpx
new file mode 100644
index 0000000000..74a8d304a4
--- /dev/null
+++ b/examples/mpx-transform-web/src/app.mpx
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/common/index.js b/examples/mpx-transform-web/src/common/index.js
new file mode 100644
index 0000000000..26e3333d23
--- /dev/null
+++ b/examples/mpx-transform-web/src/common/index.js
@@ -0,0 +1,26 @@
+import mpx, { createPage } from '@mpxjs/core'
+import swiperPage from '../packageA/pages/swiper.mpx?resolve'
+createPage({
+ data: {
+ a: 1,
+ index: 0,
+ array: ['美国', '中国', '巴西', '日本']
+ },
+ onLoad () {
+ console.log('load index')
+ },
+ onShow () {
+ console.log('show index')
+ // console.log(111, swiperPage)
+ },
+ methods: {
+ reload () {
+ window.location.reload()
+ },
+ jumpPage () {
+ mpx.navigateTo({
+ url: swiperPage
+ })
+ }
+ }
+})
diff --git a/examples/mpx-transform-web/src/common/js/index.js b/examples/mpx-transform-web/src/common/js/index.js
new file mode 100644
index 0000000000..0b787b82dd
--- /dev/null
+++ b/examples/mpx-transform-web/src/common/js/index.js
@@ -0,0 +1,28 @@
+import mpx, { createPage } from '@mpxjs/core'
+import swiperPage from '../../packageA/pages/swiper.mpx?resolve'
+// import img from '../../images/icon1.png'
+createPage({
+ data: {
+ // img,
+ a: 1,
+ index: 0,
+ array: ['美国', '中国', '巴西', '日本']
+ },
+ onLoad () {
+ console.log('load index')
+ },
+ onShow () {
+ console.log('show index123')
+ console.log('test', swiperPage, mpx.i18n.t('message.title'))
+ },
+ methods: {
+ reload () {
+ window.location.reload()
+ },
+ jumpPage () {
+ mpx.navigateTo({
+ // url: swiperPage
+ })
+ }
+ }
+})
diff --git a/examples/mpx-transform-web/src/common/js/list.ts b/examples/mpx-transform-web/src/common/js/list.ts
new file mode 100644
index 0000000000..14f24edbe0
--- /dev/null
+++ b/examples/mpx-transform-web/src/common/js/list.ts
@@ -0,0 +1,11 @@
+import { createComponent } from '@mpxjs/core'
+
+createComponent({
+ externalClasses: ['list-class'],
+ data: {
+ listData: ['手机', '电视', '电脑']
+ },
+ onShow () {
+ console.log('list')
+ }
+})
diff --git a/examples/mpx-transform-web/src/common/js/list2.ts b/examples/mpx-transform-web/src/common/js/list2.ts
new file mode 100644
index 0000000000..847b6ced66
--- /dev/null
+++ b/examples/mpx-transform-web/src/common/js/list2.ts
@@ -0,0 +1,11 @@
+import { createComponent } from '@mpxjs/core'
+
+createComponent({
+ externalClasses: ['list-class'],
+ data: {
+ listData: ['手机', '电视']
+ },
+ onShow () {
+ console.log('list')
+ }
+})
diff --git a/examples/mpx-transform-web/src/components/custom-tab-bar/index.mpx b/examples/mpx-transform-web/src/components/custom-tab-bar/index.mpx
new file mode 100644
index 0000000000..99993ede3e
--- /dev/null
+++ b/examples/mpx-transform-web/src/components/custom-tab-bar/index.mpx
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+
+
+
+
+ {{ item.text_active }}
+
+
+
+ {{ item.text }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/components/hello.wxs b/examples/mpx-transform-web/src/components/hello.wxs
new file mode 100644
index 0000000000..147c69d865
--- /dev/null
+++ b/examples/mpx-transform-web/src/components/hello.wxs
@@ -0,0 +1,10 @@
+var foo = "'hello world' from hello.wxs"
+var bar = function (d) {
+ return d
+}
+console.log(123)
+module.exports = {
+ FOO: foo,
+ bar: bar
+}
+module.exports.msg = 'some msg'
diff --git a/examples/mpx-transform-web/src/components/hello2.wxs b/examples/mpx-transform-web/src/components/hello2.wxs
new file mode 100644
index 0000000000..eee66da1a9
--- /dev/null
+++ b/examples/mpx-transform-web/src/components/hello2.wxs
@@ -0,0 +1,10 @@
+var foo = "'hello world' from hello.wxs2"
+var bar = function (d) {
+ return d
+}
+console.log(123)
+module.exports = {
+ FOO: foo,
+ bar: bar
+}
+module.exports.msg = 'some msg'
diff --git a/examples/mpx-transform-web/src/components/list.mpx b/examples/mpx-transform-web/src/components/list.mpx
new file mode 100644
index 0000000000..9aa0eabedd
--- /dev/null
+++ b/examples/mpx-transform-web/src/components/list.mpx
@@ -0,0 +1,18 @@
+
+
+ {{item}}
+
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/components/list.web.didi.mpx b/examples/mpx-transform-web/src/components/list.web.didi.mpx
new file mode 100644
index 0000000000..9980a6779b
--- /dev/null
+++ b/examples/mpx-transform-web/src/components/list.web.didi.mpx
@@ -0,0 +1,19 @@
+
+
+
web环境加载list组件
+
{{item}}
+
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/components/list1.mpx b/examples/mpx-transform-web/src/components/list1.mpx
new file mode 100644
index 0000000000..3b770a918b
--- /dev/null
+++ b/examples/mpx-transform-web/src/components/list1.mpx
@@ -0,0 +1,18 @@
+
+
+ 22343434344
+
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/components/list2.mpx b/examples/mpx-transform-web/src/components/list2.mpx
new file mode 100644
index 0000000000..29de6dc909
--- /dev/null
+++ b/examples/mpx-transform-web/src/components/list2.mpx
@@ -0,0 +1,19 @@
+
+
+ {{item}}
+ by list2
+
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/custom-tab-bar/index.mpx b/examples/mpx-transform-web/src/custom-tab-bar/index.mpx
new file mode 100644
index 0000000000..6e4e27cca1
--- /dev/null
+++ b/examples/mpx-transform-web/src/custom-tab-bar/index.mpx
@@ -0,0 +1,217 @@
+
+
+
+
+
+
+
+
+ {{ item.text }}
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/i18n/cn.js b/examples/mpx-transform-web/src/i18n/cn.js
new file mode 100644
index 0000000000..e8dddd3f34
--- /dev/null
+++ b/examples/mpx-transform-web/src/i18n/cn.js
@@ -0,0 +1,5 @@
+/* eslint-disable */
+const cn = {
+ 'page_title': '测试'
+}
+module.exports = cn
diff --git a/examples/mpx-transform-web/src/i18n/en.js b/examples/mpx-transform-web/src/i18n/en.js
new file mode 100644
index 0000000000..2291f2903d
--- /dev/null
+++ b/examples/mpx-transform-web/src/i18n/en.js
@@ -0,0 +1,5 @@
+/* eslint-disable */
+const en = {
+ 'page_title': 'i18n test==='
+}
+module.exports = en
diff --git a/examples/mpx-transform-web/src/i18n/index.js b/examples/mpx-transform-web/src/i18n/index.js
new file mode 100644
index 0000000000..9e0a050347
--- /dev/null
+++ b/examples/mpx-transform-web/src/i18n/index.js
@@ -0,0 +1,17 @@
+// log MPX i18n 因为是在node环境下引入文件 所以需要使用commonjs规范
+// import cn from './cn'
+// import en from './en'
+const en = require('./en')
+const cn = require('./cn')
+/**
+ * 语言类型由端传来,key为lang,可能的值有:zh-CN, en-US, pt-BR, en-BR, zh-HK, zh-TW
+ */
+
+// export default {
+// 'zh-CN': cn,
+// 'en-US': en
+// }
+module.exports = {
+ 'zh-CN': cn,
+ 'en-US': en
+}
diff --git a/examples/mpx-transform-web/src/images/icon1.png b/examples/mpx-transform-web/src/images/icon1.png
new file mode 100644
index 0000000000..bb2201d300
Binary files /dev/null and b/examples/mpx-transform-web/src/images/icon1.png differ
diff --git a/examples/mpx-transform-web/src/images/icon2.png b/examples/mpx-transform-web/src/images/icon2.png
new file mode 100644
index 0000000000..93ddfb2149
Binary files /dev/null and b/examples/mpx-transform-web/src/images/icon2.png differ
diff --git a/examples/mpx-transform-web/src/index.html b/examples/mpx-transform-web/src/index.html
new file mode 100644
index 0000000000..8fee0722b2
--- /dev/null
+++ b/examples/mpx-transform-web/src/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+ mpx-test
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/packageA/pages/picker.mpx b/examples/mpx-transform-web/src/packageA/pages/picker.mpx
new file mode 100644
index 0000000000..87f4410361
--- /dev/null
+++ b/examples/mpx-transform-web/src/packageA/pages/picker.mpx
@@ -0,0 +1,99 @@
+
+
+ jump to swiper
+
+
+ 当前选择:{{array[index]}}
+
+
+
+
+ {{item}}年
+
+
+ {{item}}月
+
+
+ {{item}}日
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/packageA/pages/swiper.mpx b/examples/mpx-transform-web/src/packageA/pages/swiper.mpx
new file mode 100644
index 0000000000..b6bb2f1d4d
--- /dev/null
+++ b/examples/mpx-transform-web/src/packageA/pages/swiper.mpx
@@ -0,0 +1,108 @@
+
+
+ 1111
+
+
+
+ {{item}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/packages/index.mpx b/examples/mpx-transform-web/src/packages/index.mpx
new file mode 100644
index 0000000000..946640da06
--- /dev/null
+++ b/examples/mpx-transform-web/src/packages/index.mpx
@@ -0,0 +1,7 @@
+
diff --git a/examples/mpx-transform-web/src/packages/pages/other.mpx b/examples/mpx-transform-web/src/packages/pages/other.mpx
new file mode 100644
index 0000000000..0bcaab04cf
--- /dev/null
+++ b/examples/mpx-transform-web/src/packages/pages/other.mpx
@@ -0,0 +1,3 @@
+
+ other.mpx
+
diff --git a/examples/mpx-transform-web/src/pages/config/index.json b/examples/mpx-transform-web/src/pages/config/index.json
new file mode 100644
index 0000000000..ba8ec758c7
--- /dev/null
+++ b/examples/mpx-transform-web/src/pages/config/index.json
@@ -0,0 +1,5 @@
+{
+ "usingComponents": {
+ "list": "../../components/list"
+ }
+}
\ No newline at end of file
diff --git a/examples/mpx-transform-web/src/pages/index.mpx b/examples/mpx-transform-web/src/pages/index.mpx
new file mode 100644
index 0000000000..8e9cd00a14
--- /dev/null
+++ b/examples/mpx-transform-web/src/pages/index.mpx
@@ -0,0 +1,67 @@
+
+ __UNPLUGIN__1111
+ test24
+ {{$t('page_title')}}
+
+ {{$t('message.title')}}
+
+
+
+ 123
+
+
+
+
+ 当前选择:{{array[index]}}
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/pages/index2.mpx b/examples/mpx-transform-web/src/pages/index2.mpx
new file mode 100644
index 0000000000..706673b945
--- /dev/null
+++ b/examples/mpx-transform-web/src/pages/index2.mpx
@@ -0,0 +1,37 @@
+
+ index2
+
+
+
+
+
diff --git a/examples/mpx-transform-web/src/pages/wxs.mpx b/examples/mpx-transform-web/src/pages/wxs.mpx
new file mode 100644
index 0000000000..748745ec07
--- /dev/null
+++ b/examples/mpx-transform-web/src/pages/wxs.mpx
@@ -0,0 +1,51 @@
+
+
+
+ var some_msg1 = "=====";
+ module.exports = {
+ msg : some_msg1,
+ }
+
+ wxs1: {{foo.msg}}
+
+ wxs2: {{hello.FOO}}
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/mpx-transform-web/tsconfig.json b/examples/mpx-transform-web/tsconfig.json
new file mode 100644
index 0000000000..2104f8944b
--- /dev/null
+++ b/examples/mpx-transform-web/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "esnext",
+ "noImplicitThis": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "esModuleInterop": true,
+ "moduleResolution": "node",
+ "lib": [
+ "esnext",
+ "dom",
+ "dom.iterable"
+ ]
+ },
+ "include": ["src", "vite.config.ts"],
+ "exclude": [
+ "build",
+ "dist",
+ "config"
+ ]
+}
diff --git a/examples/mpx-transform-web/vite.config.ts b/examples/mpx-transform-web/vite.config.ts
new file mode 100644
index 0000000000..dda23944fb
--- /dev/null
+++ b/examples/mpx-transform-web/vite.config.ts
@@ -0,0 +1,77 @@
+import mpx from '@mpxjs/web-plugin/vite'
+import path from 'path'
+import { defineConfig, splitVendorChunkPlugin } from 'vite'
+// import legacy from '@vitejs/plugin-legacy'
+
+export default defineConfig({
+ optimizeDeps:{
+ include: ['@mpxjs/api-proxy', '@mpxjs/core']
+ },
+ plugins: [
+ mpx({
+ env: 'didi',
+ mode: 'web',
+ externalClasses: ['list-class'],
+ // 定义一些全局环境变量,可在JS/模板/样式/JSON中使用
+ defs: {
+ // eslint-disable-next-line camelcase
+ __application_name__: 'dd'
+ },
+
+ // 是否转换px到rpx
+ transRpxRules: [
+ {
+ mode: 'only',
+ comment: 'use rpx',
+ include: path.join(__dirname, '..', 'src')
+ }
+ ],
+ i18n: {
+ locale: 'en-US',
+ // messages既可以通过对象字面量传入,也可以通过messagesPath指定一个js模块路径,在该模块中定义配置并导出,dateTimeFormats/dateTimeFormatsPath和numberFormats/numberFormatsPath同理
+ // messagesPath: path.resolve('./src/i18n/index.js'),
+ messages: {
+ 'en-US': {
+ message: {
+ title: 'test',
+ hello: '{msg} world'
+ }
+ },
+ 'zh-CN': {
+ message: {
+ title: '中文',
+ hello: '{msg} 世界'
+ }
+ }
+ }
+ }
+ }),
+ // test with split chunk
+ splitVendorChunkPlugin()
+ // test with legency
+ // legacy()
+ ],
+ resolve: {
+ alias: {
+ '@': path.resolve('.')
+ },
+ extensions: ['.mpx', '.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']
+ },
+ build: {
+ target: ['es2015'],
+ sourcemap: !(process.env.NODE_ENV === 'production'),
+ minify: false,
+ rollupOptions: {
+ output: {
+ manualChunks (id) {
+ if (id.includes('vueComponentNormalizer')) {
+ return 'vendor'
+ }
+ }
+ }
+ }
+ },
+ css: {
+ devSourcemap: !(process.env.NODE_ENV === 'production')
+ }
+})
diff --git a/examples/mpx-use-iconfont/build/package.json b/examples/mpx-use-iconfont/build/package.json
index a84ff3ca30..6a50995ce9 100644
--- a/examples/mpx-use-iconfont/build/package.json
+++ b/examples/mpx-use-iconfont/build/package.json
@@ -1,5 +1,5 @@
{
- "name": "build",
+ "name": "mpx-use-iconfont-build",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-use-iconfont/config/package.json b/examples/mpx-use-iconfont/config/package.json
index a59b4221e8..a879e1e2f5 100644
--- a/examples/mpx-use-iconfont/config/package.json
+++ b/examples/mpx-use-iconfont/config/package.json
@@ -1,5 +1,5 @@
{
- "name": "config",
+ "name": "mpx-use-iconfont-config",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-use-iconfont/package.json b/examples/mpx-use-iconfont/package.json
index c26a7fce0f..9c91835048 100644
--- a/examples/mpx-use-iconfont/package.json
+++ b/examples/mpx-use-iconfont/package.json
@@ -1,5 +1,5 @@
{
- "name": "mpx-template-demo-iconfont",
+ "name": "mpx-use-iconfont-demo",
"version": "1.0.0",
"description": "A mpx project",
"main": "index.js",
@@ -19,10 +19,10 @@
"author": "xuegan ",
"license": "ISC",
"dependencies": {
- "@mpxjs/api-proxy": "^2.7.28",
- "vue": "^2.6.10",
+ "@mpxjs/api-proxy": "workspace:*",
+ "vue": "~2.6.10",
"vue-i18n": "^8.15.3",
- "@mpxjs/core": "^2.7.37"
+ "@mpxjs/core": "workspace:*"
},
"browserslist": [
"ios >= 9",
@@ -30,13 +30,13 @@
],
"devDependencies": {
"copy-webpack-plugin": "^9.0.1",
- "@mpxjs/webpack-plugin": "^2.7.38",
+ "@mpxjs/webpack-plugin": "workspace:*",
"http-server": "^0.12.0",
"npm-run-all": "^4.1.5",
"html-webpack-plugin": "^5.3.2",
"vue-loader": "^15.9.3",
"vue-router": "^3.1.3",
- "vue-template-compiler": "^2.6.10",
+ "vue-template-compiler": "~2.6.10",
"vue-style-loader": "^4.1.2",
"jest": "^27.4.5",
"@mpxjs/miniprogram-simulate": "^1.4.12",
@@ -58,7 +58,7 @@
"@babel/core": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.10.4",
"@babel/preset-env": "^7.10.4",
- "@babel/runtime-corejs3": "^7.10.4",
+ "@babel/runtime-corejs3": "^7.20.13",
"@babel/eslint-parser": "^7.16.0",
"babel-loader": "^8.1.0",
"chalk": "^2.3.2",
diff --git a/examples/mpx-useuilib/babel.config.json b/examples/mpx-useuilib/babel.config.json
index 6be2f424a9..3aad4a5b59 100644
--- a/examples/mpx-useuilib/babel.config.json
+++ b/examples/mpx-useuilib/babel.config.json
@@ -13,7 +13,7 @@
"@babel/transform-runtime",
{
"corejs": 3,
- "version": "^7.12.5"
+ "version": "^7.20.13"
}
],
[
diff --git a/examples/mpx-useuilib/build/package.json b/examples/mpx-useuilib/build/package.json
index a84ff3ca30..de0c91edb9 100644
--- a/examples/mpx-useuilib/build/package.json
+++ b/examples/mpx-useuilib/build/package.json
@@ -1,5 +1,5 @@
{
- "name": "build",
+ "name": "mpx-useuilib-build",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-useuilib/config/package.json b/examples/mpx-useuilib/config/package.json
index a59b4221e8..3753582642 100644
--- a/examples/mpx-useuilib/config/package.json
+++ b/examples/mpx-useuilib/config/package.json
@@ -1,5 +1,5 @@
{
- "name": "config",
+ "name": "mpx-useuilib-config",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-useuilib/package.json b/examples/mpx-useuilib/package.json
index 58e35a36cd..ee192ff4fa 100644
--- a/examples/mpx-useuilib/package.json
+++ b/examples/mpx-useuilib/package.json
@@ -1,5 +1,5 @@
{
- "name": "mpx-subpackage-demo",
+ "name": "mpx-useuilib-demo",
"version": "1.0.0",
"description": "A mpx project",
"main": "index.js",
@@ -19,21 +19,21 @@
"author": "sky-admin <546485299@qq.com>",
"license": "ISC",
"dependencies": {
- "@mpxjs/api-proxy": "^2.7.28",
- "@mpxjs/core": "^2.7.33",
+ "@mpxjs/api-proxy": "workspace:*",
+ "@mpxjs/core": "workspace:*",
"html-webpack-plugin": "^5.5.0",
"iview-weapp": "^2.0.0",
"vant": "^2.12.47",
"vant-weapp": "^0.5.14",
- "vue": "^2.6.10"
+ "vue": "~2.6.10"
},
"devDependencies": {
"@babel/core": "7.12.10",
"@babel/eslint-parser": "7.16.0",
"@babel/plugin-transform-runtime": "7.12.10",
"@babel/preset-env": "7.12.11",
- "@babel/runtime-corejs3": "7.12.5",
- "@mpxjs/webpack-plugin": "^2.7.33",
+ "@babel/runtime-corejs3": "7.20.13",
+ "@mpxjs/webpack-plugin": "workspace:*",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"babel-jest": "^25.3.0",
diff --git a/examples/mpx-webview/babel.config.json b/examples/mpx-webview/babel.config.json
index 6be2f424a9..3aad4a5b59 100644
--- a/examples/mpx-webview/babel.config.json
+++ b/examples/mpx-webview/babel.config.json
@@ -13,7 +13,7 @@
"@babel/transform-runtime",
{
"corejs": 3,
- "version": "^7.12.5"
+ "version": "^7.20.13"
}
],
[
diff --git a/examples/mpx-webview/build/package.json b/examples/mpx-webview/build/package.json
index a84ff3ca30..c0c29e3924 100644
--- a/examples/mpx-webview/build/package.json
+++ b/examples/mpx-webview/build/package.json
@@ -1,5 +1,5 @@
{
- "name": "build",
+ "name": "mpx-webview-build",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-webview/config/package.json b/examples/mpx-webview/config/package.json
index a59b4221e8..0829abfee6 100644
--- a/examples/mpx-webview/config/package.json
+++ b/examples/mpx-webview/config/package.json
@@ -1,5 +1,5 @@
{
- "name": "config",
+ "name": "mpx-webview-config",
"version": "1.0.0",
"description": "避免webpack通过分析目录依赖错误地将项目package.json中声明的dependencies作为buildDependencies,请勿删除该文件!"
}
diff --git a/examples/mpx-webview/package.json b/examples/mpx-webview/package.json
index 04b31e8d73..8e33537cac 100644
--- a/examples/mpx-webview/package.json
+++ b/examples/mpx-webview/package.json
@@ -1,5 +1,5 @@
{
- "name": "mpx-subpackage-demo",
+ "name": "mpx-webview-demo",
"version": "1.0.0",
"description": "A mpx project",
"main": "index.js",
@@ -18,16 +18,16 @@
"author": "sky-admin <546485299@qq.com>",
"license": "ISC",
"dependencies": {
- "@mpxjs/api-proxy": "^2.7.28",
- "@mpxjs/core": "^2.7.26"
+ "@mpxjs/api-proxy": "workspace:*",
+ "@mpxjs/core": "workspace:*"
},
"devDependencies": {
"@babel/core": "7.12.10",
"@babel/eslint-parser": "7.16.0",
"@babel/plugin-transform-runtime": "7.12.10",
"@babel/preset-env": "7.12.11",
- "@babel/runtime-corejs3": "7.12.5",
- "@mpxjs/webpack-plugin": "^2.7.27",
+ "@babel/runtime-corejs3": "7.20.13",
+ "@mpxjs/webpack-plugin": "workspace:*",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"babel-jest": "^25.3.0",
diff --git a/lerna.json b/lerna.json
index 91ae09e47f..b1079583c2 100644
--- a/lerna.json
+++ b/lerna.json
@@ -2,5 +2,7 @@
"packages": [
"packages/*"
],
+ "useWorkspaces": "true",
+ "npmClient": "pnpm",
"version": "2.8.24"
}
diff --git a/package.json b/package.json
index 56a3d469fd..fe091bd265 100644
--- a/package.json
+++ b/package.json
@@ -8,8 +8,7 @@
"fix": "eslint --fix --ext .js packages/",
"test": "jest",
"release": "npm run lint && npm run test && npx lerna version",
- "docs:dev": "vuepress dev docs-vuepress",
- "docs:build": "vuepress build docs-vuepress"
+ "dev": "pnpm -r --parallel --filter=./packages/* run dev"
},
"devDependencies": {
"@babel/core": "^7.8.7",
@@ -17,20 +16,43 @@
"@docsearch/css": "^3.0.0",
"@docsearch/js": "^3.0.0",
"@testing-library/jest-dom": "^4.2.4",
+ "@types/async": "^2.4.2",
+ "@types/babel__code-frame": "^7.0.3",
+ "@types/babel__generator": "^7.6.4",
+ "@types/babel__traverse": "^7.18.3",
+ "@types/babel-traverse": "^6.25.4",
+ "@types/babel-types": "^7.0.4",
+ "@types/debug": "^4.1.7",
+ "@types/hash-sum": "^1.0.0",
+ "@types/loader-utils": "^2.0.3",
+ "@types/lodash": "^4.14.191",
+ "@types/lodash-es": "^4.17.6",
+ "@types/lru-cache": "^7.10.10",
+ "@types/node": "^16.11.6",
+ "@types/mime": "^3.0.1",
+ "@types/qs": "^6.9.7",
"@types/jest": "^27.0.1",
- "@vuepress/plugin-back-to-top": "^1.8.2",
- "@vuepress/plugin-pwa": "^1.8.0",
+ "@typescript-eslint/eslint-plugin": "^5.17.0",
+ "@typescript-eslint/parser": "^5.17.0",
+ "babel-eslint": "^10.0.1",
"eslint": "^7.32.0",
+ "eslint-config-babel": "^8.0.2",
"eslint-config-standard": "^16.0.3",
+ "eslint-friendly-formatter": "^4.0.1",
"eslint-plugin-html": "^6.2.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-jest": "^27.0.1",
+ "eslint-plugin-local-rules": "^0.1.0",
"eslint-plugin-node": "^11.1.0",
+ "eslint-plugin-prettier": "^2.6.2",
"eslint-plugin-promise": "^5.1.1",
+ "eslint-plugin-standard": "^4.0.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.2.0",
- "lerna": "^3.4.3",
- "typescript": "^4.1.3",
- "vuepress": "^1.9.7"
+ "lerna": "^6.5.0",
+ "rimraf": "^3.0.2",
+ "typescript": "^4.7.4",
+ "vue": "^2.7.10",
+ "webpack": "^5.48.0"
}
}
diff --git a/packages/api-proxy/package.json b/packages/api-proxy/package.json
index 10564c74b8..050d8f59b1 100644
--- a/packages/api-proxy/package.json
+++ b/packages/api-proxy/package.json
@@ -38,5 +38,8 @@
"homepage": "https://github.com/didi/mpx#readme",
"dependencies": {
"axios": "^0.21.1"
+ },
+ "peerDependencies": {
+ "@mpxjs/core": "workspace:*"
}
}
diff --git a/packages/api-proxy/src/common/js/utils.js b/packages/api-proxy/src/common/js/utils.js
index 455646cf80..a0d7888b61 100644
--- a/packages/api-proxy/src/common/js/utils.js
+++ b/packages/api-proxy/src/common/js/utils.js
@@ -88,8 +88,7 @@ function error (msg) {
console.error && console.error(`[@mpxjs/api-proxy error]:\n ${msg}`)
}
-function noop () {
-}
+function noop () {}
function makeMap (arr) {
return arr.reduce((obj, item) => {
diff --git a/packages/compile-utils/README.md b/packages/compile-utils/README.md
new file mode 100644
index 0000000000..4029717c9e
--- /dev/null
+++ b/packages/compile-utils/README.md
@@ -0,0 +1,10 @@
+# `mpx-utils`
+
+> A toolkit for mpx framework
+
+## Usage
+
+
+```js
+import { xxx } from '@mpxjs/utils'
+```
\ No newline at end of file
diff --git a/packages/compile-utils/package.json b/packages/compile-utils/package.json
new file mode 100644
index 0000000000..6bdc700b46
--- /dev/null
+++ b/packages/compile-utils/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@mpxjs/compile-utils",
+ "version": "2.7.40",
+ "description": "mpx compile utils",
+ "main": "dist/index.js",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "keywords": [
+ "mpx"
+ ],
+ "author": "lareinayanyu",
+ "license": "ISC",
+ "scripts": {
+ "dev": "tsc -w",
+ "build": "rimraf ./dist && tsc"
+ },
+ "dependencies": {
+ "json5": "^2.1.3",
+ "loader-utils": "^2.0.2"
+ },
+ "publishConfig": {
+ "registry": "https://registry.npmjs.org"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git@github.com:didi/mpx.git"
+ },
+ "homepage": "https://didi.github.io/mpx/",
+ "bugs": {
+ "url": "https://github.com/didi/mpx/issues"
+ }
+}
diff --git a/packages/compile-utils/src/add-infix.ts b/packages/compile-utils/src/add-infix.ts
new file mode 100644
index 0000000000..19bc59c854
--- /dev/null
+++ b/packages/compile-utils/src/add-infix.ts
@@ -0,0 +1,15 @@
+import path from 'path'
+
+export function addInfix (
+ resourcePath: string,
+ infix: string,
+ extname: string | any[]
+) {
+ extname = extname || path.extname(resourcePath)
+ return (
+ resourcePath.substring(0, resourcePath.length - extname.length) +
+ '.' +
+ infix +
+ extname
+ )
+}
diff --git a/packages/compile-utils/src/add-query.ts b/packages/compile-utils/src/add-query.ts
new file mode 100644
index 0000000000..700e008dc4
--- /dev/null
+++ b/packages/compile-utils/src/add-query.ts
@@ -0,0 +1,43 @@
+import { parseRequest } from './parse-request'
+import { stringifyQuery } from './stringify-query'
+import { type as t } from './type'
+import { hasOwn } from './has-own'
+
+// 默认为非强行覆盖原query,如需强行覆盖传递force为true
+export function addQuery (
+ request: any,
+ data: any = {},
+ force?: any,
+ removeKeys?: any[]
+) {
+ const {
+ rawResourcePath: resourcePath,
+ loaderString,
+ queryObj: queryObjRaw
+ } = parseRequest(request)
+ const queryObj = Object.assign({}, queryObjRaw)
+ if (force) {
+ Object.assign(queryObj, data)
+ } else {
+ Object.keys(data).forEach((key) => {
+ if (!hasOwn(queryObj, key)) {
+ queryObj[key] = data[key]
+ }
+ })
+ }
+
+ if (removeKeys) {
+ if (t(removeKeys) === 'String') {
+ removeKeys = [removeKeys]
+ }
+ removeKeys.forEach(key => {
+ delete queryObj[key]
+ })
+ }
+
+ return (
+ (loaderString ? `${loaderString}!` : '') +
+ resourcePath +
+ stringifyQuery(queryObj)
+ )
+}
diff --git a/packages/compile-utils/src/ensure-array.ts b/packages/compile-utils/src/ensure-array.ts
new file mode 100644
index 0000000000..82b482525d
--- /dev/null
+++ b/packages/compile-utils/src/ensure-array.ts
@@ -0,0 +1,16 @@
+/**
+ * forked from https://github.com/rollup/plugins/blob/master/packages/pluginutils/src/utils/ensureArray.ts
+ * Helper since Typescript can't detect readonly arrays with Array.isArray
+ */
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function isArray(arg: unknown): arg is any[] | readonly any[] {
+ return Array.isArray(arg)
+}
+
+export function ensureArray(
+ thing: readonly T[] | T | undefined | null
+): readonly T[] {
+ if (isArray(thing)) return thing
+ if (thing == null) return []
+ return [thing]
+}
diff --git a/packages/compile-utils/src/env.js b/packages/compile-utils/src/env.js
new file mode 100644
index 0000000000..fe424ef88b
--- /dev/null
+++ b/packages/compile-utils/src/env.js
@@ -0,0 +1,24 @@
+export function getEnvObj () {
+ switch (__mpx_mode__) {
+ case 'wx':
+ return wx
+ case 'ali':
+ return my
+ case 'swan':
+ return swan
+ case 'qq':
+ return qq
+ case 'tt':
+ return tt
+ case 'jd':
+ return jd
+ case 'qa':
+ return qa
+ case 'dd':
+ return dd
+ }
+}
+
+export const isBrowser = typeof window !== 'undefined'
+
+export const isDev = process.env.NODE_ENV !== 'production'
diff --git a/packages/compile-utils/src/env.ts b/packages/compile-utils/src/env.ts
new file mode 100644
index 0000000000..5b1beac49b
--- /dev/null
+++ b/packages/compile-utils/src/env.ts
@@ -0,0 +1,4 @@
+/**
+ * 判断当前环境是否是浏览器环境
+ */
+export const inBrowser = typeof window !== 'undefined'
diff --git a/packages/compile-utils/src/gen-code.ts b/packages/compile-utils/src/gen-code.ts
new file mode 100644
index 0000000000..6c48b4b523
--- /dev/null
+++ b/packages/compile-utils/src/gen-code.ts
@@ -0,0 +1,13 @@
+import { stringify } from './stringify'
+
+export const genImport = (importer: string, name?: string): string => {
+ return `import ${name ? `${name} from` : ''} ${stringify(importer)}`
+}
+
+export const genAsyncImport = (
+ importer: string,
+ name?: string,
+ callback?: string
+): string => {
+ return `import(${importer})${name ? `.then((${name}) => ${callback})` : ''}`
+}
diff --git a/packages/compile-utils/src/gen-component-tag.ts b/packages/compile-utils/src/gen-component-tag.ts
new file mode 100644
index 0000000000..0f43d1ba66
--- /dev/null
+++ b/packages/compile-utils/src/gen-component-tag.ts
@@ -0,0 +1,64 @@
+import { type as t } from './type'
+
+function stringifyAttr (val: string) {
+ if (typeof val === 'string') {
+ const hasSingle = val.indexOf("'") > -1
+ const hasDouble = val.indexOf('"') > -1
+ // 移除属性中换行
+ val = val.replace(/\n/g, '')
+
+ if (hasSingle && hasDouble) {
+ val = val.replace(/'/g, '"')
+ }
+ if (hasDouble) {
+ return `'${val}'`
+ } else {
+ return `"${val}"`
+ }
+ }
+}
+
+function stringifyAttrs (attrs: { [x: string]: any }) {
+ let result = ''
+ Object.keys(attrs).forEach(function (name) {
+ result += ' ' + name
+ const value = attrs[name]
+ if (value != null && value !== true) {
+ result += '=' + stringifyAttr(value)
+ }
+ })
+ return result
+}
+
+export function genComponentTag (
+ part: { content: string; tag: string; attrs: any },
+ processor: any = {}
+) {
+ // normalize
+ if (t(processor) === 'Function') {
+ processor = {
+ content: processor
+ }
+ }
+ if (part.content) {
+ // unpad
+ // part.content = '\n' + part.content.replace(/^\n*/m, '')
+ }
+
+ const tag = processor.tag ? processor.tag(part) : part.tag
+ const attrs = processor.attrs ? processor.attrs(part) : part.attrs
+ const content = processor.content ? processor.content(part) : part.content
+ let result = ''
+ if (tag) {
+ result += `<${tag}`
+ if (attrs) {
+ result += stringifyAttrs(attrs)
+ }
+ if (content) {
+ result += `>${content}${tag}>`
+ } else {
+ result += '/>'
+ }
+ }
+ return result
+}
diff --git a/packages/compile-utils/src/get-entry-name.ts b/packages/compile-utils/src/get-entry-name.ts
new file mode 100644
index 0000000000..c4efa304a0
--- /dev/null
+++ b/packages/compile-utils/src/get-entry-name.ts
@@ -0,0 +1,15 @@
+import { LoaderContext, NormalModule } from 'webpack'
+
+export function getEntryName (loaderContext: LoaderContext): string {
+ if (!loaderContext._compilation) return ''
+ const moduleGraph = loaderContext._compilation.moduleGraph
+ let entryName = ''
+ for (const [name, { dependencies }] of loaderContext._compilation.entries) {
+ const entryModule = moduleGraph.getModule(dependencies[0]) as NormalModule
+ if (entryModule && entryModule.resource === loaderContext.resource) {
+ entryName = name
+ break
+ }
+ }
+ return entryName
+}
diff --git a/packages/compile-utils/src/has-own.ts b/packages/compile-utils/src/has-own.ts
new file mode 100644
index 0000000000..8c9c37f891
--- /dev/null
+++ b/packages/compile-utils/src/has-own.ts
@@ -0,0 +1,5 @@
+const hasOwnProperty = Object.prototype.hasOwnProperty
+
+export function hasOwn (obj: Record, key: string) {
+ return hasOwnProperty.call(obj, key)
+}
diff --git a/packages/compile-utils/src/helpers.ts b/packages/compile-utils/src/helpers.ts
new file mode 100644
index 0000000000..c10259da94
--- /dev/null
+++ b/packages/compile-utils/src/helpers.ts
@@ -0,0 +1,130 @@
+import loaderUtils from 'loader-utils'
+import { addQuery, parseRequest } from './index'
+
+const selectorPath = '@mpxjs/loaders/selector-loader'
+const scriptSetupPath = '@mpxjs/loaders/script-setup-loader'
+
+interface defaultLangType {
+ template: 'wxml'
+ styles: 'wxss'
+ script: 'js'
+ json: 'json'
+ wxs: 'wxs'
+ [key: string]: string
+}
+
+interface Options {
+ mpx: boolean
+ type: string
+ index: number
+ extract?: boolean
+ mode?: string
+ [key: string]: any
+}
+
+const defaultLang: defaultLangType = {
+ template: 'wxml',
+ styles: 'wxss',
+ script: 'js',
+ json: 'json',
+ wxs: 'wxs'
+}
+
+export function createHelpers (loaderContext: any) {
+ const rawRequest = loaderUtils.getRemainingRequest(loaderContext)
+ const { resourcePath, queryObj } = parseRequest(loaderContext.resource)
+ // @ts-ignore
+ const { mode, env } = loaderContext.getMpx() || {}
+
+ function getRequire (
+ type: string,
+ part: Record,
+ extraOptions: Record,
+ index?: number
+ ) {
+ return 'require(' + getRequestString(type, part, extraOptions, index) + ')'
+ }
+
+ function getImport (
+ type: string,
+ part: Record,
+ extraOptions: Record,
+ index: number
+ ) {
+ return (
+ 'import __' +
+ type +
+ '__ from ' +
+ getRequestString(type, part, extraOptions, index)
+ )
+ }
+
+ function getNamedExports (
+ type: string,
+ part: Record,
+ extraOptions: Record,
+ index: number
+ ) {
+ return 'export * from ' + getRequestString(type, part, extraOptions, index)
+ }
+
+ function getFakeRequest (type: string, part: Record) {
+ const lang = part.lang || defaultLang[type] || type
+ const options = { ...queryObj }
+ if (lang === 'json') options.asScript = true
+ return addQuery(`${resourcePath}.${lang}`, options)
+ }
+
+ function getRequestString (
+ type: string,
+ part: Record,
+ extraOptions: Record,
+ index = 0
+ ) {
+ const src = part.src
+ const options: Options = {
+ mpx: true,
+ type,
+ index,
+ ...extraOptions
+ }
+
+ switch (type) {
+ case 'json':
+ options.asScript = true
+ if (part.useJSONJS) options.useJSONJS = true
+ // eslint-disable-next-line no-fallthrough
+ case 'styles':
+ case 'template':
+ options.extract = true
+ }
+
+ if (part.mode) options.mode = part.mode
+
+ if (src) {
+ return loaderUtils.stringifyRequest(
+ loaderContext,
+ addQuery(src, options, true)
+ )
+ } else {
+ const fakeRequest = getFakeRequest(type, part)
+ let request = `${selectorPath}?mode=${mode}&env=${env}!${addQuery(
+ rawRequest,
+ options,
+ true
+ )}`
+ if (part.setup && type === 'script') request = scriptSetupPath + '!' + request
+ return loaderUtils.stringifyRequest(
+ loaderContext,
+ `${fakeRequest}!=!${request}`
+ )
+ }
+ }
+
+ return {
+ getRequire,
+ getImport,
+ getNamedExports,
+ getRequestString
+ }
+}
diff --git a/packages/compile-utils/src/index.ts b/packages/compile-utils/src/index.ts
new file mode 100644
index 0000000000..887def2997
--- /dev/null
+++ b/packages/compile-utils/src/index.ts
@@ -0,0 +1,28 @@
+export * from './add-infix'
+export * from './add-query'
+export * from './ensure-array'
+export * from './env'
+export * from './gen-code'
+export * from './gen-component-tag'
+export * from './get-entry-name'
+export * from './has-own'
+export * from './helpers'
+export * from './is-empty-object'
+export * from './is-url-request'
+export * from './is-valid-identifier-str'
+export * from './match-condition'
+export * from './mpx-json'
+export * from './noop'
+export * from './normalize'
+export * from './omit'
+export * from './parse-request'
+export * from './pre-process-defs'
+export * from './resolve'
+export * from './resolve-module-context'
+export * from './string'
+export * from './stringify'
+export * from './stringify-loaders-resource'
+export * from './stringify-query'
+export * from './to-posix'
+export * from './ts-loader-watch-run-loader-filter'
+export * from './type'
diff --git a/packages/compile-utils/src/is-empty-object.ts b/packages/compile-utils/src/is-empty-object.ts
new file mode 100644
index 0000000000..0f6f19b6dd
--- /dev/null
+++ b/packages/compile-utils/src/is-empty-object.ts
@@ -0,0 +1,11 @@
+export function isEmptyObject (obj: any) {
+ if (!obj) {
+ return true
+ }
+ // @ts-ignore
+ // eslint-disable-next-line no-unreachable-loop
+ for (const key in obj) {
+ return false
+ }
+ return true
+}
diff --git a/packages/compile-utils/src/is-url-request.ts b/packages/compile-utils/src/is-url-request.ts
new file mode 100644
index 0000000000..69a1f87bf0
--- /dev/null
+++ b/packages/compile-utils/src/is-url-request.ts
@@ -0,0 +1,22 @@
+import loaderUtils from 'loader-utils'
+const tagRE = /\{\{((?:.|\n|\r)+?)\}\}(?!})/
+
+export function isUrlRequest (url: string, root: any, externals: any[]) {
+ // 对于非字符串或空字符串url直接返回false
+ if (!url || typeof url !== 'string') return false
+ // 对于@开头且后续字符串为合法标识符的情况也返回false,识别为theme变量
+ if (/^@[A-Za-z_$][A-Za-z0-9_$]*$/.test(url)) return false
+ if (/^.+:\/\//.test(url)) return false
+ // 对于url中存在Mustache插值的情况也返回false
+ if (tagRE.test(url)) return false
+ // url存在于externals中也返回false
+ if (externals && externals.some((external) => {
+ if (typeof external === 'string') {
+ return external === url
+ } else if (external instanceof RegExp) {
+ return external.test(url)
+ }
+ return false
+ })) return false
+ return loaderUtils.isUrlRequest(url, root)
+}
diff --git a/packages/compile-utils/src/is-valid-identifier-str.ts b/packages/compile-utils/src/is-valid-identifier-str.ts
new file mode 100644
index 0000000000..d7f27a7869
--- /dev/null
+++ b/packages/compile-utils/src/is-valid-identifier-str.ts
@@ -0,0 +1,3 @@
+export function isValidIdentifierStr (str: string) {
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(str)
+}
diff --git a/packages/compile-utils/src/match-condition.ts b/packages/compile-utils/src/match-condition.ts
new file mode 100644
index 0000000000..3ca0fa8037
--- /dev/null
+++ b/packages/compile-utils/src/match-condition.ts
@@ -0,0 +1,50 @@
+const orMatcher = (items: string | any[]) => {
+ return (str: any) => {
+ for (let i = 0; i < items.length; i++) {
+ if (items[i](str)) return true
+ }
+ return false
+ }
+}
+
+const normalizeCondition = (condition: any): any => {
+ if (!condition) throw new Error('Expected condition but got falsy value')
+ if (typeof condition === 'string') {
+ return (str: string | string[]) => str.indexOf(condition) !== -1
+ }
+ if (typeof condition === 'function') {
+ return condition
+ }
+ if (condition instanceof RegExp) {
+ return condition.test.bind(condition)
+ }
+ if (Array.isArray(condition)) {
+ const items = condition.map(c => normalizeCondition(c))
+ return orMatcher(items)
+ }
+ throw Error(
+ 'Unexcepted ' +
+ typeof condition +
+ ' when condition was expected (' +
+ condition +
+ ')'
+ )
+}
+
+// 匹配规则为include匹配到且未被exclude匹配到的资源为true,其余资源全部为false,如果需要实现不传include为全部匹配的话可以将include的默认值设置为()=>true进行传入
+const matchCondition = (resourcePath: any, condition: any = {}) => {
+ let matched = false
+ const includeMatcher =
+ condition.include && normalizeCondition(condition.include)
+ const excludeMatcher =
+ condition.exclude && normalizeCondition(condition.exclude)
+ if (includeMatcher && includeMatcher(resourcePath)) {
+ matched = true
+ }
+ if (excludeMatcher && excludeMatcher(resourcePath)) {
+ matched = false
+ }
+ return matched
+}
+
+export { matchCondition, normalizeCondition }
diff --git a/packages/compile-utils/src/mpx-json.ts b/packages/compile-utils/src/mpx-json.ts
new file mode 100644
index 0000000000..f0b86a8f63
--- /dev/null
+++ b/packages/compile-utils/src/mpx-json.ts
@@ -0,0 +1,50 @@
+import path from 'path'
+
+// 将JS生成JSON
+function compileMPXJSON ({ source, defs, filePath }: any) {
+ const defKeys = Object.keys(defs)
+ const defValues = defKeys.map(key => {
+ return defs[key]
+ })
+ // eslint-disable-next-line no-new-func
+ const func = new Function(
+ 'exports',
+ 'require',
+ 'module',
+ '__filename',
+ '__dirname',
+ ...defKeys,
+ source
+ )
+ // 模拟commonJS执行
+ // support exports
+ const e = {}
+ const m = {
+ exports: e
+ }
+ const dirname = path.dirname(filePath)
+ func(
+ e,
+ function (modulePath: string) {
+ if (!path.isAbsolute(modulePath)) {
+ if (modulePath.indexOf('.') === 0) {
+ modulePath = path.resolve(dirname, modulePath)
+ }
+ }
+ return require(modulePath)
+ },
+ m,
+ filePath,
+ dirname,
+ ...defValues
+ )
+ return m.exports
+}
+
+function compileMPXJSONText (opts: any) {
+ return JSON.stringify(compileMPXJSON(opts), null, 2)
+}
+
+export {
+ compileMPXJSONText
+}
diff --git a/packages/compile-utils/src/noop.ts b/packages/compile-utils/src/noop.ts
new file mode 100644
index 0000000000..e643899bad
--- /dev/null
+++ b/packages/compile-utils/src/noop.ts
@@ -0,0 +1,2 @@
+// eslint-disable-next-line @typescript-eslint/no-empty-function
+export const NOOP = () => {}
diff --git a/packages/compile-utils/src/normalize.ts b/packages/compile-utils/src/normalize.ts
new file mode 100644
index 0000000000..ab2191a9e9
--- /dev/null
+++ b/packages/compile-utils/src/normalize.ts
@@ -0,0 +1,5 @@
+export const normalize = {
+ lib: (file: string) => '@mpxjs/webpack-plugin/lib/' + file,
+ runtime: (file: string) => '@mpxjs/web-plugin/src/runtime/' + file,
+ utils: (file: string) => '@mpxjs/compile-utils/' + file
+}
diff --git a/packages/compile-utils/src/omit.ts b/packages/compile-utils/src/omit.ts
new file mode 100644
index 0000000000..599743eec7
--- /dev/null
+++ b/packages/compile-utils/src/omit.ts
@@ -0,0 +1,8 @@
+export function omit(
+ obj: T,
+ omitKeys: K[]
+): Omit {
+ const result = { ...obj }
+ omitKeys.forEach((key) => delete result[key])
+ return result
+}
diff --git a/packages/compile-utils/src/parse-request.ts b/packages/compile-utils/src/parse-request.ts
new file mode 100644
index 0000000000..662834cfaa
--- /dev/null
+++ b/packages/compile-utils/src/parse-request.ts
@@ -0,0 +1,56 @@
+import path from 'path'
+import { OptionObject, parseQuery } from 'loader-utils'
+const seen = new Map()
+
+export interface Result {
+ resource: string
+ loaderString: string
+ resourcePath: string
+ resourceQuery: string
+ rawResourcePath: string
+ queryObj: OptionObject
+}
+
+function genQueryObj (result: Result) {
+ // 避免外部修改queryObj影响缓存
+ result.queryObj = parseQuery(result.resourceQuery || '?')
+ return result
+}
+
+export function parseRequest (request: string) {
+ if (seen.has(request)) {
+ return genQueryObj(seen.get(request))
+ }
+ const elements = request.split('!')
+ const resource = elements.pop() as string
+ const loaderString = elements.join('!')
+ let resourcePath = resource
+ let resourceQuery = ''
+ const queryIndex = resource.indexOf('?')
+ if (queryIndex >= 0) {
+ resourcePath = resource.slice(0, queryIndex)
+ resourceQuery = resource.slice(queryIndex)
+ }
+ const queryObj = parseQuery(resourceQuery || '?')
+ const rawResourcePath = resourcePath
+ if (queryObj.resourcePath) {
+ resourcePath = queryObj.resourcePath as string
+ } else if (queryObj.infix) {
+ const resourceDir = path.dirname(resourcePath)
+ const resourceBase = path.basename(resourcePath)
+ resourcePath = path.join(
+ resourceDir,
+ resourceBase.replace(queryObj.infix as string, '')
+ )
+ }
+ const result = {
+ resource,
+ loaderString,
+ resourcePath,
+ resourceQuery,
+ rawResourcePath,
+ queryObj
+ }
+ seen.set(request, result)
+ return result
+}
diff --git a/packages/compile-utils/src/pre-process-defs.ts b/packages/compile-utils/src/pre-process-defs.ts
new file mode 100644
index 0000000000..b797ba9b29
--- /dev/null
+++ b/packages/compile-utils/src/pre-process-defs.ts
@@ -0,0 +1,31 @@
+/**
+ * @file common util methods
+ */
+
+/**
+ * 预处理一次常量对象,处理掉不符合标识符规则的变量,目前只处理'.',用于在JSON/style中使用的场景
+ * @param {object} defs 待处理的常量
+ * @returns {object} 处理完毕的常量对象
+ */
+function preProcessDefs (defs: any) {
+ const newDefs: any = {}
+ Object.keys(defs).forEach(key => {
+ if (typeof key === 'string' && key.indexOf('.') !== -1) {
+ key.split('.').reduce((prev: any, curr, index, arr) => {
+ if (index === arr.length - 1) {
+ prev[curr] = defs[key]
+ } else {
+ prev[curr] = prev[curr] || {}
+ }
+ return prev[curr]
+ }, newDefs)
+ } else {
+ newDefs[key] = defs[key]
+ }
+ })
+ return newDefs
+}
+
+export {
+ preProcessDefs
+}
diff --git a/packages/compile-utils/src/query.d.ts b/packages/compile-utils/src/query.d.ts
new file mode 100644
index 0000000000..bf34d7eda4
--- /dev/null
+++ b/packages/compile-utils/src/query.d.ts
@@ -0,0 +1,6 @@
+import 'loader-utils'
+declare module 'loader-utils' {
+ export interface OptionObject {
+ context?: string
+ }
+}
diff --git a/packages/compile-utils/src/resolve-module-context.ts b/packages/compile-utils/src/resolve-module-context.ts
new file mode 100644
index 0000000000..186b7e9de8
--- /dev/null
+++ b/packages/compile-utils/src/resolve-module-context.ts
@@ -0,0 +1,5 @@
+import path from 'path'
+
+export function resolveModuleContext (moduleId: string): string {
+ return path.dirname(moduleId)
+}
diff --git a/packages/compile-utils/src/resolve.ts b/packages/compile-utils/src/resolve.ts
new file mode 100644
index 0000000000..a217eccd73
--- /dev/null
+++ b/packages/compile-utils/src/resolve.ts
@@ -0,0 +1,21 @@
+import { parseRequest } from './parse-request'
+import { LoaderContext } from 'webpack'
+
+type LoaderContextResolveCallback = Parameters<
+ LoaderContext['resolve']
+>[2]
+// todo 提供不记录dependency的resolve方法,非必要的情况下不记录dependency,提升缓存利用率
+export function resolve (
+ context: string,
+ request: string,
+ loaderContext: LoaderContext,
+ callback: LoaderContextResolveCallback
+) {
+ const { queryObj } = parseRequest(request)
+ context = queryObj.context || context
+ return loaderContext.resolve(context, request, (err, resource, info) => {
+ if (err) return callback(err)
+ if (resource === false) return callback(new Error('Resolve ignored!'))
+ callback(null, resource, info)
+ })
+}
diff --git a/packages/compile-utils/src/set.ts b/packages/compile-utils/src/set.ts
new file mode 100644
index 0000000000..7fa6bc297a
--- /dev/null
+++ b/packages/compile-utils/src/set.ts
@@ -0,0 +1,51 @@
+export type ItemType = string | Record
+export type SetType = Set
+export type FnType = (item: ItemType) => unknown
+
+export default {
+ every (set: SetType, fn: FnType) {
+ for (const item of set) {
+ if (!fn(item)) return false
+ }
+ return true
+ },
+ has (set: SetType, fn: FnType) {
+ for (const item of set) {
+ if (fn(item)) return true
+ }
+ return false
+ },
+ map (set: SetType, fn: FnType) {
+ const result = new Set()
+ set.forEach((item: ItemType) => {
+ result.add(fn(item))
+ })
+ return result
+ },
+ filter (set: SetType, fn: FnType) {
+ const result = new Set()
+ set.forEach((item: ItemType) => {
+ if (fn(item)) {
+ result.add(item)
+ }
+ })
+ return result
+ },
+ concat (setA: SetType, setB: SetType) {
+ const result = new Set()
+ setA.forEach((item: ItemType) => {
+ result.add(item)
+ })
+ setB.forEach((item: ItemType) => {
+ result.add(item)
+ })
+ return result
+ },
+ mapToArr (set: SetType, fn: FnType) {
+ const result: Array = []
+ set.forEach((item: ItemType) => {
+ result.push(fn(item))
+ })
+ return result
+ }
+}
diff --git a/packages/compile-utils/src/string.ts b/packages/compile-utils/src/string.ts
new file mode 100644
index 0000000000..dfe51179e2
--- /dev/null
+++ b/packages/compile-utils/src/string.ts
@@ -0,0 +1,30 @@
+function isCapital (c: string) {
+ return /[A-Z]/.test(c)
+}
+
+function isMustache (str: string) {
+ return /\{\{((?:.|\n|\r)+?)\}\}(?!})/.test(str)
+}
+
+// WordExample/wordExample -> word-example
+function capitalToHyphen (v: string) {
+ let ret = ''
+ for (let c, i = 0; i < v.length; i++) {
+ c = v[i]
+ if (isCapital(c)) {
+ if (i === 0) {
+ c = c.toLowerCase()
+ } else {
+ c = '-' + c.toLowerCase()
+ }
+ }
+ ret += c
+ }
+ return ret
+}
+
+export {
+ isCapital,
+ isMustache,
+ capitalToHyphen
+}
diff --git a/packages/compile-utils/src/stringify-loaders-resource.ts b/packages/compile-utils/src/stringify-loaders-resource.ts
new file mode 100644
index 0000000000..e4fc357e61
--- /dev/null
+++ b/packages/compile-utils/src/stringify-loaders-resource.ts
@@ -0,0 +1,23 @@
+const loaderToIdent = (data: { options: string; loader: string; ident: string }) => {
+ if (!data.options) {
+ return data.loader
+ }
+ if (typeof data.options === 'string') {
+ return data.loader + '?' + data.options
+ }
+ if (typeof data.options !== 'object') {
+ throw new Error('loader options must be string or object')
+ }
+ if (data.ident) {
+ return data.loader + '??' + data.ident
+ }
+ return data.loader + '?' + JSON.stringify(data.options)
+}
+
+export const stringifyLoadersAndResource = (loaders: any, resource: string) => {
+ let str = ''
+ for (const loader of loaders) {
+ str += loaderToIdent(loader) + '!'
+ }
+ return str + resource
+}
diff --git a/packages/compile-utils/src/stringify-query.ts b/packages/compile-utils/src/stringify-query.ts
new file mode 100644
index 0000000000..6ff24bcf01
--- /dev/null
+++ b/packages/compile-utils/src/stringify-query.ts
@@ -0,0 +1,44 @@
+/**
+ * stringify object to query string, started with '?'
+ * @param {Object} obj
+ * @param {boolean} useJSON
+ * @return {string} queryString
+ */
+import JSON5 from 'json5'
+
+export function stringifyQuery (obj: any, useJSON?: boolean) {
+ if (useJSON) return `?${JSON5.stringify(obj)}`
+
+ const res = obj
+ ? Object.keys(obj)
+ .sort()
+ .map(key => {
+ const val = obj[key]
+
+ if (val === undefined) {
+ return val
+ }
+
+ if (val === true) {
+ return key
+ }
+
+ if (Array.isArray(val)) {
+
+ const key2 = `${key}[]`
+ const result: string[] = []
+ val.slice().forEach(val2 => {
+ if (val2 === undefined) {
+ return
+ }
+ result.push(`${key2}=${encodeURIComponent(val2)}`)
+ })
+ return result.join('&')
+ }
+ return `${key}=${encodeURIComponent(val)}`
+ })
+ .filter(x => x)
+ .join('&')
+ : null
+ return res ? `?${res}` : ''
+}
diff --git a/packages/compile-utils/src/stringify.ts b/packages/compile-utils/src/stringify.ts
new file mode 100644
index 0000000000..23cf0aa634
--- /dev/null
+++ b/packages/compile-utils/src/stringify.ts
@@ -0,0 +1,30 @@
+import { hasOwn } from './has-own'
+const stringify = JSON.stringify.bind(JSON)
+
+export { stringify }
+
+export function stringifyObject (
+ obj?: Record
+): Record {
+ const result: Record = {}
+ if (obj) {
+ Object.keys(obj).forEach(key => {
+ result[key] = stringify(obj[key])
+ })
+ }
+ return result
+}
+
+export function shallowStringify (obj: Record): string {
+ const arr = []
+ for (const key in obj) {
+ if (hasOwn(obj, key)) {
+ let value = obj[key]
+ if (Array.isArray(value)) {
+ value = `[${value.join(',')}]`
+ }
+ arr.push(`'${key}':${value}`)
+ }
+ }
+ return `{${arr.join(',')}}`
+}
diff --git a/packages/compile-utils/src/to-posix.ts b/packages/compile-utils/src/to-posix.ts
new file mode 100644
index 0000000000..1beb15ca67
--- /dev/null
+++ b/packages/compile-utils/src/to-posix.ts
@@ -0,0 +1,3 @@
+export function toPosix (path: string) {
+ return path.replace(/\\/g, '/')
+}
diff --git a/packages/compile-utils/src/ts-loader-watch-run-loader-filter.ts b/packages/compile-utils/src/ts-loader-watch-run-loader-filter.ts
new file mode 100644
index 0000000000..380f1f9cfe
--- /dev/null
+++ b/packages/compile-utils/src/ts-loader-watch-run-loader-filter.ts
@@ -0,0 +1,23 @@
+import set from './set'
+const selectorPath = '@mpxjs/loaders/selector-loader.js'
+const scriptSetupPath = '@mpxjs/loaders/script-setup-loader.js'
+const webMpxLoaderPath = '@mpxjs/web-plugin/webpack/loader/web-loader.js'
+const mpxLoaderPath = '@mpxjs/webpack-plugin/lib/loader.js'
+const tsLoaderWatchRunFilterLoaders = new Set([
+ selectorPath,
+ scriptSetupPath,
+ mpxLoaderPath,
+ webMpxLoaderPath,
+ 'node_modules/vue-loader/lib/index.js'
+])
+
+export function tsWatchRunLoaderFilter (loaders: Array>, loaderIndex: number) {
+ for (let len = loaders.length; len > 0; --len) {
+ const currentLoader = loaders[len - 1]
+ if (!set.has(tsLoaderWatchRunFilterLoaders, filterLoaderPath => currentLoader.path.endsWith(filterLoaderPath))) {
+ break
+ }
+ loaderIndex--
+ }
+ return loaderIndex
+}
diff --git a/packages/compile-utils/src/type.ts b/packages/compile-utils/src/type.ts
new file mode 100644
index 0000000000..d2dbea8a5c
--- /dev/null
+++ b/packages/compile-utils/src/type.ts
@@ -0,0 +1,3 @@
+export function type (n: any) {
+ return Object.prototype.toString.call(n).slice(8, -1)
+}
diff --git a/packages/compile-utils/tsconfig.json b/packages/compile-utils/tsconfig.json
new file mode 100644
index 0000000000..b091ad4c02
--- /dev/null
+++ b/packages/compile-utils/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.root.json",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "declaration": true,
+ "outDir": "dist",
+ "module": "commonjs"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/compiler/README.md b/packages/compiler/README.md
new file mode 100644
index 0000000000..29c2e4a0d7
--- /dev/null
+++ b/packages/compiler/README.md
@@ -0,0 +1 @@
+# `@mpxjs/compiler`
diff --git a/packages/compiler/package.json b/packages/compiler/package.json
new file mode 100644
index 0000000000..58815090b3
--- /dev/null
+++ b/packages/compiler/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "@mpxjs/compiler",
+ "version": "2.7.40",
+ "description": "mpx compile",
+ "keywords": [
+ "mpx"
+ ],
+ "homepage": "https://didi.github.io/mpx/",
+ "bugs": {
+ "url": "https://github.com/didi/mpx/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git@github.com:didi/mpx.git"
+ },
+ "license": "ISC",
+ "author": "lareinayanyu",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "scripts": {
+ "build": "rimraf ./dist && tsc",
+ "dev": "tsc -w"
+ },
+ "dependencies": {
+ "@babel/code-frame": "^7.16.0",
+ "@babel/generator": "^7.16.0",
+ "@babel/parser": "^7.16.2",
+ "@babel/traverse": "^7.16.0",
+ "@babel/types": "^7.16.0",
+ "@mpxjs/compile-utils": "workspace:*",
+ "@mpxjs/plugin-proxy": "workspace:*",
+ "hash-sum": "^1.0.2",
+ "he": "^1.1.1",
+ "json5": "^2.1.3",
+ "loader-utils": "^2.0.2",
+ "lru-cache": "^4.1.2",
+ "magic-string": "^0.26.2",
+ "postcss": "^8.4.5",
+ "postcss-load-config": "^3.1.1",
+ "postcss-selector-parser": "^6.0.8",
+ "source-map": "^0.6.1"
+ },
+ "publishConfig": {
+ "registry": "https://registry.npmjs.org"
+ }
+}
diff --git a/packages/compiler/src/global.d.ts b/packages/compiler/src/global.d.ts
new file mode 100644
index 0000000000..44d4b4a0b1
--- /dev/null
+++ b/packages/compiler/src/global.d.ts
@@ -0,0 +1,15 @@
+
+declare module 'lru-cache' {
+ class LruCache {
+ constructor(cache: number);
+
+ get(cacheKey: string): T;
+
+ set(cacheKey: string, content: string): void;
+ }
+ export default LruCache
+}
+
+declare module 'he' {}
+
+declare module 'consolidate' {}
\ No newline at end of file
diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts
new file mode 100644
index 0000000000..8a0183b984
--- /dev/null
+++ b/packages/compiler/src/index.ts
@@ -0,0 +1,11 @@
+import platform from './platform'
+import templateCompiler from './template-compiler'
+import scriptSetupCompiler from './script-setup-compiler/index'
+import styleCompiler from './style-compiler'
+import jsonCompiler from './json-compiler'
+
+export * from './template-compiler/index'
+export * from './style-compiler/index'
+export * from './json-compiler'
+
+export { templateCompiler, styleCompiler, jsonCompiler, platform, scriptSetupCompiler }
diff --git a/packages/compiler/src/json-compiler/index.ts b/packages/compiler/src/json-compiler/index.ts
new file mode 100644
index 0000000000..ebbbcfe195
--- /dev/null
+++ b/packages/compiler/src/json-compiler/index.ts
@@ -0,0 +1,155 @@
+import { parseRequest, stringify } from '@mpxjs/compile-utils'
+import { ProxyPluginContext } from '@mpxjs/plugin-proxy'
+import fs from 'fs'
+import json5 from 'json5'
+import path from 'path'
+import { promisify } from 'util'
+import { CompilerResult } from '../template-compiler'
+import { JsonConfig } from './json-config'
+
+export * from './json-config'
+
+export const DEFAULT_TAB_BAR_CONFIG = {
+ borderStyle: 'black',
+ position: 'bottom',
+ custom: false,
+ isShow: true
+}
+
+export const JSON_JS_EXT = '.json.js'
+
+function evalJSONJS(
+ source: string,
+ filename: string,
+ defs: Record,
+ fs: any,
+ callback: (filename: string) => void
+): Record {
+ const defKeys = Object.keys(defs)
+ const defValues = defKeys.map(key => {
+ return defs[key]
+ })
+ const dirname = path.dirname(filename)
+ // eslint-disable-next-line no-new-func
+ const func = new Function(
+ 'module',
+ 'exports',
+ 'require',
+ '__filename',
+ '__dirname',
+ ...defKeys,
+ source
+ )
+ const module = {
+ exports: {}
+ }
+ // 此处采用readFileSync+evalJSONJS而不直接使用require获取依赖内容有两个原因:
+ // 1. 支持依赖中正常访问defs变量
+ // 2. 避免对应的依赖文件被作为buildDependencies
+ func(
+ module,
+ module.exports,
+ function (request: string) {
+ if (request.startsWith('.')) {
+ request = path.join(dirname, request)
+ }
+ const filename = require.resolve(request)
+ callback(filename)
+ const source = fs.readFileSync(filename).toString('utf-8')
+ return evalJSONJS(source, filename, fs, defs || {}, callback)
+ },
+ filename,
+ dirname,
+ ...defValues
+ )
+
+ return module.exports
+}
+
+async function getJSONContent(
+ json: CompilerResult['json'],
+ filename: string,
+ pluginContext: ProxyPluginContext,
+ defs: Record | unknown,
+ fs: any
+) {
+ let jsonPath = filename
+ if (json) {
+ let jsonContent = json.content
+ let useJSONJS = json.useJSONJS
+ let resourcePath = ''
+ if (json.src) {
+ const resolvedJsonPath = await pluginContext.resolve(json.src, filename)
+ if (resolvedJsonPath) {
+ const { rawResourcePath } = parseRequest(resolvedJsonPath.id)
+ jsonPath = resolvedJsonPath.id
+ useJSONJS = rawResourcePath.endsWith(JSON_JS_EXT)
+ const readFile = promisify(fs.readFile)
+ jsonContent = await readFile(rawResourcePath, 'utf-8')
+ json.content = jsonContent
+ resourcePath = rawResourcePath
+ pluginContext.addDependency(resolvedJsonPath.id)
+ }
+ }
+ if (useJSONJS) {
+ return {
+ content: stringify(
+ evalJSONJS(jsonContent, resourcePath, defs || {}, fs, filename => {
+ pluginContext.addDependency(filename)
+ })
+ ),
+ path: jsonPath
+ }
+ }
+ return {
+ content: jsonContent,
+ path: jsonPath
+ }
+ }
+ return {
+ content: '{}',
+ path: jsonPath
+ }
+}
+
+/**
+ * resolve json content
+ * @param descriptor - SFCDescriptor
+ * @param pluginContext - TransformPluginContext
+ * @param options - ResolvedOptions
+ * @returns json config
+ */
+async function parse(
+ compilerResult: CompilerResult,
+ context: string,
+ pluginContext: ProxyPluginContext,
+ defs: any,
+ fsInfo?: any
+): Promise {
+ const { json } = compilerResult
+ const jsonContent = await getJSONContent(
+ json,
+ context,
+ pluginContext,
+ defs,
+ fsInfo || fs
+ )
+ const jsonResult = json5.parse(jsonContent.content)
+ if (jsonResult.tabBar) {
+ jsonResult.tabBar = {
+ ...DEFAULT_TAB_BAR_CONFIG,
+ ...jsonResult.tabBar
+ }
+ }
+ return {
+ ...jsonResult,
+ path: jsonContent.path
+ }
+}
+
+const jsonCompiler = {
+ parse
+}
+
+
+export default jsonCompiler
\ No newline at end of file
diff --git a/packages/compiler/src/json-compiler/json-config.ts b/packages/compiler/src/json-compiler/json-config.ts
new file mode 100644
index 0000000000..9aecea69a1
--- /dev/null
+++ b/packages/compiler/src/json-compiler/json-config.ts
@@ -0,0 +1,46 @@
+/**
+ * wechat miniprogram app/page/component config type
+ */
+export type TabBarItem = {
+ pagePath: string
+ text: string
+ iconPath?: string
+ selectedIconPath?: string
+}
+
+export interface JsonConfig {
+ path?: string,
+ component?: boolean
+ usingComponents?: Record
+ componentGenerics?: Record
+ packages?: string[]
+ pages?: (
+ | string
+ | {
+ src: string
+ path: string
+ }
+ )[]
+ tabBar?: {
+ custom?: boolean
+ color?: string
+ selectedColor?: string
+ backgroundColor?: string
+ list?: TabBarItem[]
+ }
+ networkTimeout?: {
+ request: number
+ connectSocket: number
+ uploadFile: number
+ downloadFile: number
+ }
+ subpackages?: {
+ root?: 'string'
+ pages: JsonConfig['pages']
+ }[]
+ window?: Record
+ style?: string
+ singlePage?: {
+ navigationBarFit: boolean
+ }
+}
\ No newline at end of file
diff --git a/packages/compiler/src/platform/index.ts b/packages/compiler/src/platform/index.ts
new file mode 100644
index 0000000000..1b7771ce9a
--- /dev/null
+++ b/packages/compiler/src/platform/index.ts
@@ -0,0 +1,25 @@
+// @ts-nocheck
+import runRules from './run-rules'
+import wxTemplate from './template/wx'
+import wxJson from './json/wx'
+
+export default function getRulesRunner ({ type, mode, srcMode, data, meta, testKey, mainKey, waterfall, warn, error }) {
+ const specMap = {
+ template: {
+ wx: wxTemplate({ warn, error })
+ },
+ json: {
+ wx: wxJson({ warn, error })
+ }
+ }
+ const spec = specMap[type] && specMap[type][srcMode]
+ if (spec && spec.supportedModes.indexOf(mode) > -1) {
+ const normalizeTest = spec.normalizeTest
+ const mainRules = mainKey ? spec[mainKey] : spec
+ if (mainRules) {
+ return function (input) {
+ return runRules(mainRules, input, { mode, data, meta, testKey, waterfall, normalizeTest })
+ }
+ }
+ }
+}
diff --git a/packages/compiler/src/platform/json/change-key.ts b/packages/compiler/src/platform/json/change-key.ts
new file mode 100644
index 0000000000..a51f667965
--- /dev/null
+++ b/packages/compiler/src/platform/json/change-key.ts
@@ -0,0 +1,6 @@
+export default function changeKey (input: Record, srcKey: string, targetKey: string) {
+ const value = input[srcKey]
+ delete input[srcKey]
+ input[targetKey] = value
+ return input
+}
diff --git a/packages/compiler/src/platform/json/normalize-test.ts b/packages/compiler/src/platform/json/normalize-test.ts
new file mode 100644
index 0000000000..1b2fa25bce
--- /dev/null
+++ b/packages/compiler/src/platform/json/normalize-test.ts
@@ -0,0 +1,20 @@
+import { hasOwn } from '@mpxjs/compile-utils'
+
+export default function normalizeTest (test: string) {
+ if (test) {
+ return (input: Record, meta: Record) => {
+ const pathArr = test.split('|')
+ meta.paths = []
+ let result = false
+ for (let i = 0; i < pathArr.length; i++) {
+ if (hasOwn(input, pathArr[i])) {
+ meta.paths.push(pathArr[i])
+ result = true
+ }
+ }
+ return result
+ }
+ } else {
+ return () => true
+ }
+}
diff --git a/packages/compiler/src/platform/json/wx/index.ts b/packages/compiler/src/platform/json/wx/index.ts
new file mode 100644
index 0000000000..578b9d499e
--- /dev/null
+++ b/packages/compiler/src/platform/json/wx/index.ts
@@ -0,0 +1,403 @@
+// @ts-nocheck
+import runRules from '../../run-rules'
+import normalizeTest from '../normalize-test'
+import changeKey from '../change-key'
+import { normalize } from '@mpxjs/compile-utils'
+const mpxViewPath = normalize.lib('runtime/components/ali/mpx-view.mpx')
+const mpxTextPath = normalize.lib('runtime/components/ali/mpx-text.mpx')
+
+export default function getSpec ({ warn, error }) {
+ function print (mode, path, isError) {
+ const msg = `Json path <${path}> is not supported in ${mode} environment!`
+ isError ? error(msg) : warn(msg)
+ }
+
+ function deletePath (opts) {
+ let isError = opts
+ let shouldLog = true
+ if (typeof opts === 'object') {
+ shouldLog = !opts.noLog
+ isError = opts.isError
+ }
+
+ return function (input, { mode, pathArr = [] }, meta) {
+ const currPath = meta.paths.join('|')
+ if (shouldLog) {
+ print(mode, pathArr.concat(currPath).join('.'), isError)
+ }
+ meta.paths.forEach((path) => {
+ delete input[path]
+ })
+ return input
+ }
+ }
+
+ /**
+ * @desc 在app.mpx里配置usingComponents作为全局组件
+ */
+ function addGlobalComponents (input, { globalComponents }) {
+ if (globalComponents) {
+ input.usingComponents = Object.assign({}, globalComponents, input.usingComponents)
+ }
+ return input
+ }
+
+ // 处理支付宝 componentPlaceholder 不支持 view、text 原生标签
+ function aliComponentPlaceholderFallback (input) {
+ const componentPlaceholder = input.componentPlaceholder
+ const usingComponents = input.usingComponents || (input.usingComponents = {})
+ for (const cph in componentPlaceholder) {
+ const cur = componentPlaceholder[cph]
+ const placeholderCompMatched = cur.match(/^(?:view|text)$/g)
+ if (!Array.isArray(placeholderCompMatched)) continue
+ let compName, compPath
+ switch (placeholderCompMatched[0]) {
+ case 'view':
+ compName = 'mpx-view'
+ compPath = mpxViewPath
+ break
+ case 'text':
+ compName = 'mpx-text'
+ compPath = mpxTextPath
+ }
+ usingComponents[compName] = compPath
+ componentPlaceholder[cph] = compName
+ }
+ return input
+ }
+
+ const spec = {
+ supportedModes: ['ali', 'swan', 'qq', 'tt', 'jd', 'qa', 'dd'],
+ normalizeTest,
+ page: [
+ {
+ test: 'navigationBarTitleText',
+ ali (input) {
+ return changeKey(input, this.test, 'defaultTitle')
+ }
+ },
+ {
+ test: 'enablePullDownRefresh',
+ ali (input) {
+ input = changeKey(input, this.test, 'pullRefresh')
+ if (input.pullRefresh) {
+ input.allowsBounceVertical = 'YES'
+ }
+ return input
+ },
+ jd: deletePath()
+ },
+ {
+ test: 'navigationBarBackgroundColor',
+ ali (input) {
+ return changeKey(input, this.test, 'titleBarColor')
+ }
+ },
+ {
+ test: 'disableSwipeBack',
+ ali: deletePath(),
+ qq: deletePath(),
+ jd: deletePath(),
+ swan: deletePath()
+ },
+ {
+ test: 'onReachBottomDistance',
+ qq: deletePath(),
+ jd: deletePath()
+ },
+ {
+ test: 'disableScroll',
+ ali: deletePath(),
+ qq: deletePath(),
+ jd: deletePath()
+ },
+ {
+ test: 'backgroundColorTop|backgroundColorBottom',
+ ali: deletePath(),
+ swan: deletePath()
+ },
+ {
+ test: 'navigationBarTextStyle|navigationStyle|backgroundTextStyle',
+ ali: deletePath()
+ },
+ {
+ test: 'pageOrientation',
+ ali: deletePath(),
+ swan: deletePath(),
+ tt: deletePath(),
+ jd: deletePath()
+ },
+ {
+ test: 'componentPlaceholder',
+ ali: aliComponentPlaceholderFallback
+ },
+ {
+ ali: addGlobalComponents,
+ swan: addGlobalComponents,
+ qq: addGlobalComponents,
+ tt: addGlobalComponents,
+ jd: addGlobalComponents
+ }
+ ],
+ component: [
+ {
+ test: 'componentGenerics',
+ ali: deletePath(true)
+ },
+ {
+ test: 'componentPlaceholder',
+ ali: aliComponentPlaceholderFallback
+ },
+ {
+ ali: addGlobalComponents,
+ swan: addGlobalComponents,
+ qq: addGlobalComponents,
+ tt: addGlobalComponents
+ }
+ ],
+ tabBar: {
+ list: [
+ {
+ test: 'text',
+ ali (input) {
+ return changeKey(input, this.test, 'name')
+ }
+ },
+ {
+ test: 'iconPath',
+ ali (input) {
+ return changeKey(input, this.test, 'icon')
+ }
+ },
+ {
+ test: 'selectedIconPath',
+ ali (input) {
+ return changeKey(input, this.test, 'activeIcon')
+ }
+ }
+ ],
+ rules: [
+ {
+ test: 'color',
+ ali (input) {
+ return changeKey(input, this.test, 'textColor')
+ }
+ },
+ {
+ test: 'list',
+ ali (input) {
+ const value = input.list
+ delete input.list
+ input.items = value.map(item => {
+ return runRules(spec.tabBar.list, item, {
+ mode: 'ali',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['tabBar', 'list']
+ }
+ })
+ })
+ return input
+ }
+ },
+ {
+ test: 'position',
+ ali: deletePath(),
+ swan: deletePath()
+ },
+ {
+ test: 'borderStyle',
+ ali: deletePath()
+ },
+ {
+ test: 'custom',
+ ali: deletePath(),
+ swan: deletePath(),
+ tt: deletePath(),
+ jd: deletePath()
+ }
+ ]
+ },
+ rules: [
+ {
+ test: 'resizable',
+ ali: deletePath(),
+ qq: deletePath(),
+ swan: deletePath(),
+ tt: deletePath(),
+ jd: deletePath()
+ },
+ {
+ test: 'preloadRule',
+ tt: deletePath(),
+ jd: deletePath()
+ },
+ {
+ test: 'functionalPages',
+ ali: deletePath(true),
+ qq: deletePath(true),
+ swan: deletePath(true),
+ tt: deletePath(),
+ jd: deletePath(true)
+ },
+ {
+ test: 'plugins',
+ qq: deletePath(true),
+ swan: deletePath(true),
+ tt: deletePath(),
+ jd: deletePath(true)
+ },
+ {
+ test: 'usingComponents',
+ ali: deletePath({ noLog: true }),
+ qq: deletePath({ noLog: true }),
+ swan: deletePath({ noLog: true }),
+ tt: deletePath({ noLog: true }),
+ jd: deletePath({ noLog: true })
+ },
+ {
+ test: 'debug',
+ ali: deletePath(),
+ swan: deletePath()
+ },
+ {
+ test: 'requiredBackgroundModes',
+ ali: deletePath(),
+ tt: deletePath()
+ },
+ {
+ test: 'workers',
+ jd: deletePath(),
+ ali: deletePath(),
+ swan: deletePath(),
+ tt: deletePath()
+ },
+ {
+ test: 'subpackages|subPackages',
+ jd: deletePath(true)
+ },
+ {
+ test: 'packages',
+ jd: deletePath()
+ },
+ {
+ test: 'navigateToMiniProgramAppIdList|networkTimeout',
+ ali: deletePath(),
+ jd: deletePath()
+ },
+ {
+ test: 'tabBar',
+ ali (input) {
+ input.tabBar = runRules(spec.tabBar, input.tabBar, {
+ mode: 'ali',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['tabBar']
+ }
+ })
+ },
+ qq (input) {
+ input.tabBar = runRules(spec.tabBar, input.tabBar, {
+ mode: 'qq',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['tabBar']
+ }
+ })
+ },
+ swan (input) {
+ input.tabBar = runRules(spec.tabBar, input.tabBar, {
+ mode: 'swan',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['tabBar']
+ }
+ })
+ },
+ tt (input) {
+ input.tabBar = runRules(spec.tabBar, input.tabBar, {
+ mode: 'tt',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['tabBar']
+ }
+ })
+ },
+ jd (input) {
+ input.tabBar = runRules(spec.tabBar, input.tabBar, {
+ mode: 'jd',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['tabBar']
+ }
+ })
+ }
+ },
+ {
+ test: 'window',
+ ali (input) {
+ input.window = runRules(spec.page, input.window, {
+ mode: 'ali',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['window']
+ }
+ })
+ return input
+ },
+ qq (input) {
+ input.window = runRules(spec.page, input.window, {
+ mode: 'qq',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['window']
+ }
+ })
+ return input
+ },
+ swan (input) {
+ input.window = runRules(spec.page, input.window, {
+ mode: 'swan',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['window']
+ }
+ })
+ return input
+ },
+ tt (input) {
+ input.window = runRules(spec.page, input.window, {
+ mode: 'tt',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['window']
+ }
+ })
+ return input
+ },
+ jd (input) {
+ input.window = runRules(spec.page, input.window, {
+ mode: 'jd',
+ normalizeTest,
+ waterfall: true,
+ data: {
+ pathArr: ['window']
+ }
+ })
+ return input
+ }
+ }
+ ]
+ }
+ return spec
+}
diff --git a/packages/compiler/src/platform/run-rules.ts b/packages/compiler/src/platform/run-rules.ts
new file mode 100644
index 0000000000..4f6fa4d64e
--- /dev/null
+++ b/packages/compiler/src/platform/run-rules.ts
@@ -0,0 +1,40 @@
+// @ts-nocheck
+import { type } from '@mpxjs/compile-utils'
+
+function defaultNormalizeTest (rawTest, context) {
+ const testType = type(rawTest)
+ switch (testType) {
+ case 'Function':
+ return rawTest.bind(context)
+ case 'RegExp':
+ return input => rawTest.test(input)
+ case 'String':
+ return input => rawTest === input
+ default:
+ return () => true
+ }
+}
+
+export default function runRules (rules = [], input, options = {}) {
+ const { mode, testKey, normalizeTest, data = {}, meta = {}, waterfall } = options
+ rules = rules.rules || rules
+ for (let i = 0; i < rules.length; i++) {
+ const rule = rules[i]
+ const tester = (normalizeTest || defaultNormalizeTest)(rule.test, rule)
+ const testInput = testKey ? input[testKey] : input
+ const processor = rule[mode]
+ // mode传入data中供processor使用
+ Object.assign(data, {
+ mode
+ })
+ if (tester(testInput, meta) && processor) {
+ const result = processor.call(rule, input, data, meta)
+ meta.processed = true
+ if (result !== undefined) {
+ input = result
+ }
+ if (!waterfall) break
+ }
+ }
+ return input
+}
diff --git a/packages/compiler/src/platform/template/normalize-component-rules.ts b/packages/compiler/src/platform/template/normalize-component-rules.ts
new file mode 100644
index 0000000000..a7b74aeb7b
--- /dev/null
+++ b/packages/compiler/src/platform/template/normalize-component-rules.ts
@@ -0,0 +1,68 @@
+// @ts-nocheck
+
+import runRules from '../run-rules'
+import templateCompiler from '../../template-compiler'
+import { Config } from './wx/component-config'
+
+/**
+ * @desc 针对每一个组件(属性,event,指令等)执行规则判断
+ * @params cfgs [{test: 'camera', props:[], event: []}] 组件配置列表
+ * @params spec ../index.js中公共的spec
+ */
+export default function normalizeComponentRules (cfgs: Config[], spec) {
+ return cfgs.map((cfg) => {
+ const result = {}
+ if (cfg.test) {
+ result.test = cfg.test
+ }
+ const supportedModes = cfg.supportedModes || spec.supportedModes
+ // 合并component-config中组件的event 与index中公共的event规则
+ const eventRules = (cfg.event || []).concat(spec.event.rules)
+ supportedModes.forEach((mode) => {
+ result[mode] = function (el, data) {
+ data = Object.assign({}, data, { el, eventRules })
+ const testKey = 'name'
+ let rAttrsList = []
+ const options = {
+ mode,
+ testKey,
+ data
+ }
+ el.attrsList.forEach((attr) => {
+ const meta = {}
+ let rAttr = runRules(spec.directive, attr, {
+ ...options,
+ meta
+ })
+ // 指令未匹配到时说明为props,因为目前所有的指令都需要转换
+ if (!meta.processed) {
+ rAttr = runRules(spec.preProps, rAttr, options)
+ rAttr = runRules(cfg.props, rAttr, options)
+ if (Array.isArray(rAttr)) {
+ rAttr = rAttr.map((attr) => {
+ return runRules(spec.postProps, attr, options)
+ })
+ } else if (rAttr !== false) {
+ rAttr = runRules(spec.postProps, rAttr, options)
+ }
+ }
+ // 生成目标attrsList
+ if (Array.isArray(rAttr)) {
+ rAttrsList = rAttrsList.concat(rAttr)
+ } else if (rAttr !== false) {
+ rAttrsList.push(rAttr)
+ }
+ })
+ el.attrsList = rAttrsList
+ el.attrsMap = templateCompiler.compiler.makeAttrsMap(rAttrsList)
+ // 前置处理attrs,便于携带信息用于tag的处理
+ const rTag = cfg[mode] && cfg[mode].call(this, el.tag, data)
+ if (rTag) {
+ el.tag = rTag
+ }
+ return el
+ }
+ })
+ return result
+ })
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/README.md b/packages/compiler/src/platform/template/wx/component-config/README.md
new file mode 100644
index 0000000000..f47e1244a3
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/README.md
@@ -0,0 +1,39 @@
+# 基于微信的模板转换规则
+
+> 每个文件是一个微信组件的规则。需要为mpx跨的所有平台编写对应的转换规则。
+
+## web
+
+web的额外逻辑,因为小程序组件和web存在差异,比如事件相关的东西,因此可能无法直接单纯地转换,因此会做额外判断决定是否注入一个内建组件来polyfill,这些内建组件在 `/lib/runtime/components/web` 。
+
+## 规则结构
+
+每个文件对应一个组件,里面要覆盖到Mpx支持的所有平台。
+
+每个平台不支持的组件写在 unsupported.js 中。
+
+Mpx的转换一个重要原则是转不了的东西通过控制台打印提示用户,要求用户提供一份符合对应平台的代码通过条件编译支持。因此错误输出格式保持一致是有必要的。
+
+在 index.js 中,会汇总每个组件的转换规则函数,为了使错误信息标准化,一致化,错误打印函数的生成函数实现在index.js里。
+
+每个组件文件是一个方法,接受错误打印生成方法,根据组件名生成对应的错误打印方法。
+
+在转换规则对应的平台处理方法里决定是否要进行错误打印。
+
+## 转换规则编写指南
+
+每一条规则其实是一个对象。主要的属性有:
+
+test: 用于匹配标签名,可以是字符串或正则
+
+\[平台(如wx/qq/swan/ali/tt/web)]: 一个方法,用于处理这个平台下的标签转换,如view在web下需要判断是否有事件决定转成普通的div还是内建组件mpx-view,参数待完善
+
+props和event: 都是数组,每一项都如大规则一样包含test用于命中要转换的项,然后是平台方法,用于处理转换。
+
+## 单元测试
+
+鉴于转换规则可能在不同时间被多人多次编辑,且规则繁杂,为了避免框架底层改动/他人规则改动导致之前编写的规则失败,建议为转换编写对应的单元测试case。
+
+同时,单元测试也可以帮助你更好更快编写转换规则,而无需找个真实的项目尝试。
+
+转换规则的单元测试放置在 webpack-plugin/test/platform/wx 下,util中提供了一个转换方法,可参考之前的示例进行。
diff --git a/packages/compiler/src/platform/template/wx/component-config/ad.ts b/packages/compiler/src/platform/template/wx/component-config/ad.ts
new file mode 100644
index 0000000000..75638cb536
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/ad.ts
@@ -0,0 +1,57 @@
+import { DefineConfig } from "."
+
+const TAG_NAME = 'ad'
+
+export default function ({ print }) {
+ const ttValueWarningLog = print({ platform: 'bytedance', type: 'value', tag: TAG_NAME, isError: false })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const baiduValueWarningLog = print({ platform: 'baidu', type: 'value', tag: TAG_NAME, isError: false })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const qqValueWarningLog = print({ platform: 'qq', type: 'value', tag: TAG_NAME, isError: false })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+ const qqEventLog = print({ platform: 'qq', tag: TAG_NAME, isError: false, type: 'event' })
+ return {
+ test: TAG_NAME,
+ props: [
+ {
+ test: /^ad-type$/,
+ tt (obj) {
+ obj.name = 'type'
+ if (obj.value === 'grid') {
+ ttValueWarningLog({ name: 'type', value: obj.value })
+ }
+ return obj
+ },
+ qq (obj) {
+ obj.name = 'type'
+ if (obj.value === 'grid' || obj.value === 'video') {
+ qqValueWarningLog({ name: 'type', value: obj.value })
+ }
+ return obj
+ },
+ swan (obj) {
+ obj.name = 'type'
+ if (obj.value === 'grid' || obj.value === 'video') {
+ baiduValueWarningLog({ name: 'type', value: obj.value })
+ }
+ return obj
+ }
+ },
+ {
+ test: /^ad-theme$/,
+ tt: ttPropLog
+ },
+ {
+ test: /^(ad-intervals|ad-theme)$/,
+ qq: qqPropLog,
+ swan: baiduPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(close)$/,
+ qq: qqEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/block.ts b/packages/compiler/src/platform/template/wx/component-config/block.ts
new file mode 100644
index 0000000000..84d67e790b
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/block.ts
@@ -0,0 +1,12 @@
+import { DefineConfig } from "."
+
+const TAG_NAME = 'block'
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ web () {
+ return 'template'
+ }
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/button.ts b/packages/compiler/src/platform/template/wx/component-config/button.ts
new file mode 100644
index 0000000000..1d8c9824c7
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/button.ts
@@ -0,0 +1,182 @@
+import { isMustache } from '@mpxjs/compile-utils'
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'button'
+
+// 微信支持的属性及其值
+const wxSupportPropsValue = {
+ 'open-type': ['contact', 'share', 'getPhoneNumber', 'getUserInfo', 'launchApp', 'openSetting', 'feedback']
+}
+
+export default function ({ print }) {
+ const aliValueLogError = print({ platform: 'ali', tag: TAG_NAME, isError: true, type: 'value' })
+ const aliValueLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'value' })
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' })
+ const baiduValueLogError = print({ platform: 'baidu', tag: TAG_NAME, isError: true, type: 'value' })
+ const baiduValueLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false, type: 'value' })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const baiduEventLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const qqValueLogError = print({ platform: 'qq', tag: TAG_NAME, isError: true, type: 'value' })
+ const qqValueLog = print({ platform: 'qq', tag: TAG_NAME, isError: false, type: 'value' })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+ const qqEventLog = print({ platform: 'qq', tag: TAG_NAME, isError: false, type: 'event' })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+ const jdEventLog = print({ platform: 'jd', tag: TAG_NAME, isError: false, type: 'event' })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const ttValueLogError = print({ platform: 'bytedance', tag: TAG_NAME, isError: true, type: 'value' })
+ const ttValueLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'value' })
+ const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' })
+ const webPropLog = print({ platform: 'web', tag: TAG_NAME, isError: false })
+ const webEventLog = print({ platform: 'web', tag: TAG_NAME, isError: false, type: 'event' })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ const wxPropValueLog = print({ platform: 'wx', tag: TAG_NAME, isError: false, type: 'value' })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-button'
+ },
+ props: [
+ {
+ test: 'open-type',
+ ali ({ name, value }) {
+ const notSupported = ['contact', 'launchApp', 'openSetting', 'feedback']
+ if (notSupported.indexOf(value) > -1) {
+ aliValueLogError({ name, value })
+ } else if (value === 'getPhoneNumber') {
+ return [
+ {
+ name: 'open-type',
+ value: 'getAuthorize'
+ },
+ {
+ name: 'scope',
+ value: 'phoneNumber'
+ }
+ ]
+ } else if (value === 'getUserInfo') {
+ return [
+ {
+ name: 'open-type',
+ value: 'getAuthorize'
+ },
+ {
+ name: 'scope',
+ value: 'userInfo'
+ }
+ ]
+ } else if (isMustache(value)) {
+ // 如果是个变量,报warning
+ aliValueLog({ name, value })
+ }
+ },
+ swan ({ name, value }) {
+ const supportList = ['contact', 'share', 'getUserInfo', 'getPhoneNumber', 'openSetting', 'chooseAddress', 'chooseInvoiceTitle', 'login']
+ if (name === 'open-type' && wxSupportPropsValue[name] && wxSupportPropsValue[name].indexOf(value) === -1) {
+ wxPropValueLog({ name, value })
+ }
+ if (isMustache(value)) {
+ // 如果是个变量,报warning
+ baiduValueLog({ name, value })
+ } else if (value && supportList.indexOf(value) === -1) {
+ baiduValueLogError({ name, value })
+ }
+ },
+ qq ({ name, value }) {
+ const supportList = ['share', 'getUserInfo', 'getPhoneNumber', 'launchApp', 'openSetting', 'contact', 'feedback', 'openGroupProfile', 'addFriend', 'addColorSign', 'openPublicProfile', 'addGroupApp', 'shareMessageToFriend', 'addToFavorites']
+ if (name === 'open-type' && wxSupportPropsValue[name] && wxSupportPropsValue[name].indexOf(value) === -1) {
+ wxPropValueLog({ name, value })
+ }
+ if (isMustache(value)) {
+ // 如果是个变量,报warning
+ qqValueLog({ name, value })
+ } else if (value && supportList.indexOf(value) === -1) {
+ qqValueLogError({ name, value })
+ }
+ },
+ tt ({ name, value }) {
+ if (name === 'open-type' && wxSupportPropsValue[name] && wxSupportPropsValue[name].indexOf(value) === -1) {
+ wxPropValueLog({ name, value })
+ }
+ if (isMustache(value)) {
+ ttValueLog({ name, value })
+ } else {
+ const supportList = ['share', 'getPhoneNumber', 'contact']
+ if (value && supportList.indexOf(value) === -1) {
+ ttValueLogError({ name, value })
+ }
+ }
+ }
+ },
+ {
+ test: /^(lang|session-from|send-message-title|send-message-path|send-message-img|show-message-card)$/,
+ ali: aliPropLog
+ },
+ {
+ test: /^(lang|session-from|send-message-title|send-message-path|send-message-img|show-message-card|app-parameter)$/,
+ swan: baiduPropLog
+ },
+ {
+ test: /^(session-from|send-message-title|send-message-path|send-message-img|show-message-card)$/,
+ qq: qqPropLog
+ },
+ {
+ test: /^(session-from|send-message-title|send-message-path|send-message-img|show-message-card|bindcontact|bindlaunchapp)$/,
+ jd: jdPropLog
+ },
+ {
+ test: /^(plain|lang|session-from|send-message-title|send-message-path|send-message-img|app-parameter|show-message-card)$/,
+ tt: ttPropLog
+ },
+ {
+ test: /^(open-type|lang|session-from|send-message-title|send-message-path|send-message-img|show-message-card|app-parameter)$/,
+ web: webPropLog
+ },
+ {
+ test: /^(size|type|plain|loading|form-type|hover-class|hover-stop-propagation|hover-start-time|hover-stay-time|use-built-in)$/,
+ web (_prop, { el }) {
+ // todo 这部分能力基于内部封装实现
+ el.isBuiltIn = true
+ }
+ },
+ {
+ test: /^(open-type|lang|session-from|send-message-title|send-message-path|send-message-img|app-parameter|show-message-card|bindgetuserinfo|bindcontact|bindgetphonenumber|binderror|bindopensetting|bindlaunchapp)$/,
+ qa: qaPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(getphonenumber|getuserinfo)$/,
+ ali () {
+ return 'getAuthorize'
+ }
+ },
+ {
+ test: /^(contact|launchapp|opensetting)$/,
+ ali: aliEventLog
+ },
+ {
+ test: /^(error|launchapp)$/,
+ swan: baiduEventLog
+ },
+ {
+ test: /^(getphonenumber)$/,
+ qq: qqEventLog
+ },
+ {
+ test: /^(contact|feedback)$/,
+ jd: jdEventLog
+ },
+ {
+ test: /^(getuserinfo|contact|error|launchapp|opensetting)$/,
+ tt: ttEventLog
+ },
+ {
+ test: /^(getuserinfo|contact|error|launchapp|opensetting|getphonenumber)$/,
+ web: webEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/camera.ts b/packages/compiler/src/platform/template/wx/component-config/camera.ts
new file mode 100644
index 0000000000..348a103ed2
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/camera.ts
@@ -0,0 +1,122 @@
+import { isMustache } from '@mpxjs/compile-utils'
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'camera'
+
+export default function ({ print }) {
+ const ttValueLogError = print({
+ platform: 'bytedance',
+ tag: TAG_NAME,
+ isError: true,
+ type: 'value'
+ })
+ const ttEventLog = print({
+ platform: 'bytedance',
+ tag: TAG_NAME,
+ isError: false
+ })
+ const ttPropLog = print({
+ platform: 'bytedance',
+ tag: TAG_NAME,
+ isError: false
+ })
+ const baiduValueLogError = print({
+ platform: 'baidu',
+ tag: TAG_NAME,
+ isError: true,
+ type: 'value'
+ })
+ const baiduEventLog = print({
+ platform: 'baidu',
+ tag: TAG_NAME,
+ isError: false
+ })
+ const qqValueLog = print({
+ platform: 'qq',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'value'
+ })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+ const qqEventLog = print({
+ platform: 'qq',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ const qaEventLog = print({
+ platform: 'qa',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ return {
+ test: TAG_NAME,
+ props: [
+ {
+ test: 'mode',
+ swan({ name, value }) {
+ // 百度只有相机模式,也就是微信的mode=normal
+ if (value !== 'normal') {
+ baiduValueLogError({ name, value })
+ }
+ return false
+ },
+ tt({ name, value }) {
+ if (value !== 'normal') {
+ ttValueLogError({ name, value })
+ }
+ return false
+ },
+ qa: qaPropLog
+ },
+ {
+ test: 'flash',
+ qq({ name, value }) {
+ const supportList = ['auto', 'on', 'off']
+ if (isMustache(value) || supportList.indexOf(value) === -1) {
+ // 如果是个变量,或者是不支持的属性值,报warning
+ qqValueLog({ name, value })
+ }
+ },
+ tt: ttPropLog
+ },
+ {
+ test: /^(resolution|frame-size)$/,
+ qq: qqPropLog
+ },
+ {
+ test: /^(frame-size|device-position)$/,
+ qa(prop) {
+ const propsMap = {
+ 'device-position': 'deviceposition',
+ 'frame-size': 'framesize'
+ }
+ prop.name = propsMap[prop.name as keyof typeof propsMap]
+ if (prop.name === 'framesize') {
+ const valueMap = {
+ small: 'low',
+ medium: 'medium',
+ large: 'high'
+ }
+ prop.value = valueMap[prop.value as keyof typeof valueMap]
+ }
+ return prop
+ }
+ }
+ ],
+ event: [
+ {
+ test: /^(scancode)$/,
+ swan: baiduEventLog,
+ tt: ttEventLog,
+ qa: qaEventLog
+ },
+ {
+ test: /^(initdone)$/,
+ qq: qqEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/canvas.ts b/packages/compiler/src/platform/template/wx/component-config/canvas.ts
new file mode 100644
index 0000000000..4cb009af2f
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/canvas.ts
@@ -0,0 +1,60 @@
+import { DefineConfig } from "."
+
+const TAG_NAME = 'canvas'
+
+export default function ({ print }) {
+ const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+ const qaEventLog = print({ platform: 'qa', tag: TAG_NAME, isError: false, type: 'event' })
+ return {
+ test: TAG_NAME,
+ props: [
+ {
+ test: /^canvas-id$/,
+ ali ({ value }) {
+ return {
+ name: 'id',
+ value
+ }
+ }
+ },
+ {
+ test: 'disable-scroll',
+ tt: ttPropLog
+ },
+ {
+ test: 'type',
+ jd: jdPropLog
+ }
+ ],
+ // 组件事件中的差异部分
+ // 微信中基础事件有touchstart|touchmove|touchcancel|touchend|tap|longpress|longtap|transitionend|animationstart|animationiteration|animationend|touchforcechange
+ // 支付宝中的基础事件有touchStart|touchMove|touchEnd|touchCancel|tap|longTap
+ event: [
+ {
+ test: /^(touchstart|touchmove|touchend|touchcancel|longtap)$/,
+ ali (eventName) {
+ const eventMap = {
+ touchstart: 'touchStart',
+ touchmove: 'touchMove',
+ touchend: 'touchEnd',
+ touchcancel: 'touchCancel',
+ longtap: 'longTap'
+ }
+ return eventMap[eventName as keyof typeof eventMap]
+ }
+ },
+ {
+ test: /^(error)$/,
+ ali: aliEventLog
+ },
+ {
+ test: /^(longtap|error)$/,
+ tt: ttEventLog,
+ qa: qaEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/checkbox-group.ts b/packages/compiler/src/platform/template/wx/component-config/checkbox-group.ts
new file mode 100644
index 0000000000..8747cbc247
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/checkbox-group.ts
@@ -0,0 +1,13 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'checkbox-group'
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-checkbox-group'
+ }
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/checkbox.ts b/packages/compiler/src/platform/template/wx/component-config/checkbox.ts
new file mode 100644
index 0000000000..a9b7d5be66
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/checkbox.ts
@@ -0,0 +1,22 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'checkbox'
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-checkbox'
+ },
+ event: [
+ {
+ test: 'tap',
+ ali () {
+ // 支付宝checkbox上不支持tap事件,change事件的表现和tap类似所以替换
+ return 'change'
+ }
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/component.ts b/packages/compiler/src/platform/template/wx/component-config/component.ts
new file mode 100644
index 0000000000..2d40e84510
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/component.ts
@@ -0,0 +1,45 @@
+import { parseMustache } from '../../../../template-compiler/compiler'
+import { normalize } from '@mpxjs/compile-utils'
+import { DefineConfig, PropsTransformer } from '.'
+
+const TAG_NAME = 'component'
+
+/** is 属性格式化为中划线(-)连接 */
+const formatPropIs: PropsTransformer = (obj, data) => {
+ const parsed = parseMustache(obj.value)
+ let value = parsed.result
+ if (parsed.hasBinding) value = value.slice(1, -1)
+ const el = data.el
+ if (el) {
+ const injectWxsProp = {
+ injectWxsPath: '~' + normalize.lib('runtime/utils.wxs'),
+ injectWxsModuleName: '__wxsUtils__'
+ }
+ if (el.injectWxsProps && Array.isArray(el.injectWxsProps)) {
+ el.injectWxsProps.push(injectWxsProp)
+ } else {
+ el.injectWxsProps = [injectWxsProp]
+ }
+ }
+ return {
+ name: 'is',
+ value: `{{__wxsUtils__.humpToLine(${value})}}`
+ }
+}
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ props: [
+ {
+ test: 'is',
+ ali(obj, data) {
+ return formatPropIs(obj, data)
+ },
+ swan(obj, data) {
+ return formatPropIs(obj, data)
+ }
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/cover-image.ts b/packages/compiler/src/platform/template/wx/component-config/cover-image.ts
new file mode 100644
index 0000000000..9a2dcf5b3d
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/cover-image.ts
@@ -0,0 +1,31 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'cover-image'
+
+export default function ({ print }) {
+ const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' })
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-image'
+ },
+ tt () {
+ return 'image'
+ },
+ props: [
+ {
+ test: 'use-built-in',
+ web (_prop, { el }) {
+ el.isBuiltIn = true
+ }
+ }
+ ],
+ event: [
+ {
+ test: /^(load|error)$/,
+ ali: aliEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/cover-view.ts b/packages/compiler/src/platform/template/wx/component-config/cover-view.ts
new file mode 100644
index 0000000000..8024d8cb72
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/cover-view.ts
@@ -0,0 +1,43 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'cover-view'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const baiduValueLogError = print({ platform: 'baidu', tag: TAG_NAME, isError: true, type: 'value' })
+ const webPropLog = print({ platform: 'web', tag: TAG_NAME, isError: false })
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ if (el.hasEvent) {
+ el.isBuiltIn = true
+ }
+ if (el.isBuiltIn) {
+ return 'mpx-view'
+ } else {
+ return 'div'
+ }
+ },
+ tt () {
+ return 'view'
+ },
+ props: [
+ {
+ test: 'scroll-top',
+ ali: aliPropLog,
+ swan ({ name, value }) {
+ if (typeof value === 'string') {
+ baiduValueLogError({ name, value })
+ }
+ },
+ web: webPropLog
+ },
+ {
+ test: 'use-built-in',
+ web (_prop, { el }) {
+ el.isBuiltIn = true
+ }
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/form.ts b/packages/compiler/src/platform/template/wx/component-config/form.ts
new file mode 100644
index 0000000000..4de5899bd1
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/form.ts
@@ -0,0 +1,35 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'form'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+ const webPropLog = print({ platform: 'web', tag: TAG_NAME, isError: false })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ // form全量使用内建组件
+ el.isBuiltIn = true
+ return 'mpx-form'
+ },
+ props: [
+ {
+ test: /^(report-submit-timeout)$/,
+ ali: aliPropLog,
+ swan: baiduPropLog,
+ jd: jdPropLog,
+ qq: qqPropLog
+ },
+ {
+ test: /^(report-submit|report-submit-timeout)$/,
+ web: webPropLog,
+ qa: qaPropLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/hypen-tag-name.ts b/packages/compiler/src/platform/template/wx/component-config/hypen-tag-name.ts
new file mode 100644
index 0000000000..effaaa94d8
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/hypen-tag-name.ts
@@ -0,0 +1,15 @@
+import { capitalToHyphen } from '@mpxjs/compile-utils'
+import { DefineConfig } from '.'
+
+export default function () {
+ function convertTagName (name: string) {
+ return capitalToHyphen(name)
+ }
+
+ return {
+ // tag name contains capital letters
+ test: /[A-Z]/,
+ ali: convertTagName,
+ swan: convertTagName
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/icon.ts b/packages/compiler/src/platform/template/wx/component-config/icon.ts
new file mode 100644
index 0000000000..4e9805f5fa
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/icon.ts
@@ -0,0 +1,13 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'icon'
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-icon'
+ }
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/image.ts b/packages/compiler/src/platform/template/wx/component-config/image.ts
new file mode 100644
index 0000000000..a7b9328e95
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/image.ts
@@ -0,0 +1,42 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'image'
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-image'
+ },
+ props: [
+ {
+ test: /^show-menu-by-longpress$/,
+ ali: aliPropLog,
+ swan: baiduPropLog,
+ qq: qqPropLog,
+ tt: ttPropLog
+ },
+ {
+ test: /^webp|show-menu-by-longpress$/,
+ jd: jdPropLog
+ },
+ {
+ test: /^(mode|lazy-load|show-menu-by-longpress|webp|use-built-in)$/,
+ web (_prop, { el }) {
+ el.isBuiltIn = true
+ }
+ },
+ {
+ test: /^(show-menu-by-longpress|webp)$/,
+ qa: qaPropLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/index.ts b/packages/compiler/src/platform/template/wx/component-config/index.ts
new file mode 100644
index 0000000000..8c9c6cf89d
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/index.ts
@@ -0,0 +1,172 @@
+import ad from './ad'
+import block from './block'
+import button from './button'
+import camera from './camera'
+import canvas from './canvas'
+import checkboxGroup from './checkbox-group'
+import checkbox from './checkbox'
+import coverImage from './cover-image'
+import coverView from './cover-view'
+import form from './form'
+import HyphenTagName from './hypen-tag-name'
+import icon from './icon'
+import image from './image'
+import input from './input'
+import livePlayer from './live-player'
+import livePusher from './live-pusher'
+import map from './map'
+import movableArea from './movable-area'
+import movableView from './movable-view'
+import navigator from './navigator'
+import pickerViewColumn from './picker-view-column'
+import pickerView from './picker-view'
+import picker from './picker'
+import progress from './progress'
+import radioGroup from './radio-group'
+import radio from './radio'
+import richText from './rich-text'
+import scrollView from './scroll-view'
+import slider from './slider'
+import swiperItem from './swiper-item'
+import swiper from './swiper'
+import switchComponent from './switch'
+import template from './template'
+import text from './text'
+import textarea from './textarea'
+import Nonsupport from './unsupported'
+import video from './video'
+import view from './view'
+import webView from './web-view'
+import wxs from './wxs'
+import component from './component'
+
+interface Attr {
+ name: string
+ value: any
+}
+
+type Test = string | RegExp
+
+export type PropsTransformer = (attr: Attr, params: { el: any }) => any
+export type EventTransformer = (event: string, params: { el: any }) => any
+export type TagTransformer = (tag: string, params: { el: any }) => any
+export type PrintLog = (tag?: Attr | string) => void
+export interface Config {
+ test?: Test
+ supportedModes?: string[]
+ [key: string]:
+ | Config['props']
+ | Config['event']
+ | Config['test']
+ | TagTransformer
+ | string[]
+ props?: {
+ [key: string]: PropsTransformer | string | undefined | RegExp
+ test?: Test
+ }[]
+ event?: {
+ [key: string]: EventTransformer | string | undefined | RegExp
+ test?: Test
+ }[]
+}
+
+export type Print = (params: {
+ platform: any
+ tag?: any
+ type?: string | undefined
+ isError?: boolean | undefined
+}) => PrintLog
+
+export type DefineConfig = (params: { print: Print }) => Config
+export type DefineConfigs = (params: { print: Print }) => Config[]
+
+export default function getComponentConfigs({
+ warn,
+ error
+}: {
+ warn: any
+ error: any
+}) {
+ const print: Print =
+ ({ platform, tag, type = 'property', isError = false }) =>
+ arg => {
+ if (type === 'tag') {
+ error(`<${arg}> is not supported in ${platform} environment!`)
+ return
+ }
+ let msg
+ switch (type) {
+ case 'event':
+ msg = `<${tag}> does not support [bind${arg}] event in ${platform} environment!`
+ break
+ case 'property':
+ msg = `<${tag}> does not support [${
+ arg && (arg as Attr).name
+ }] property in ${platform} environment!`
+ break
+ case 'value':
+ msg = `<${tag}>'s property '${
+ arg && (arg as Attr).name
+ }' does not support '[${
+ arg && (arg as Attr).value
+ }]' value in ${platform} environment!`
+ break
+ case 'tagRequiredProps':
+ msg = `<${tag}> should have '${arg}' attr in ali environment!`
+ break
+ case 'value-attr-uniform':
+ msg = `The internal attribute name of the <${tag}>'s attribute '${
+ arg && (arg as Attr).value
+ }' is not supported in the ali environment, Please check!`
+ break
+ default:
+ msg = `<${tag}>'s transform has some error happened!`
+ }
+ isError ? error(msg) : warn(msg)
+ }
+
+ // 转换规则只需以微信为基准配置微信和支付宝的差异部分,比如微信和支付宝都支持但是写法不一致,或者微信支持而支付宝不支持的部分(抛出错误或警告)
+ return [
+ ...Nonsupport({ print }),
+ ad({ print }),
+ view({ print }),
+ scrollView({ print }),
+ swiper({ print }),
+ swiperItem({ print }),
+ movableView({ print }),
+ movableArea({ print }),
+ coverView({ print }),
+ coverImage({ print }),
+ text({ print }),
+ richText({ print }),
+ progress({ print }),
+ button({ print }),
+ checkboxGroup({ print }),
+ checkbox({ print }),
+ radioGroup({ print }),
+ radio({ print }),
+ form({ print }),
+ input({ print }),
+ picker({ print }),
+ pickerView({ print }),
+ pickerViewColumn({ print }),
+ slider({ print }),
+ switchComponent({ print }),
+ textarea({ print }),
+ navigator({ print }),
+ image({ print }),
+ map({ print }),
+ canvas({ print }),
+ wxs({ print }),
+ template({ print }),
+ block({ print }),
+ icon({ print }),
+ webView({ print }),
+ video({ print }),
+ camera({ print }),
+ livePlayer({ print }),
+ livePusher({ print }),
+ HyphenTagName({ print }),
+ component({ print })
+ ]
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/input.ts b/packages/compiler/src/platform/template/wx/component-config/input.ts
new file mode 100644
index 0000000000..f8dbc079e0
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/input.ts
@@ -0,0 +1,86 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'input'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const baiduEventLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false, type: 'event' })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+ const jdEventLog = print({ platform: 'jd', tag: TAG_NAME, isError: false, type: 'event' })
+ const webPropLog = print({ platform: 'web', tag: TAG_NAME, isError: false })
+ const webEventLog = print({ platform: 'web', tag: TAG_NAME, isError: false, type: 'event' })
+ const webValueLog = print({ platform: 'web', tag: TAG_NAME, isError: false, type: 'value' })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-input'
+ },
+ props: [
+ {
+ test: /^(cursor-spacing|auto-focus|adjust-position|hold-keyboard)$/,
+ ali: aliPropLog
+ },
+ {
+ test: /^(auto-focus|hold-keyboard)$/,
+ swan: baiduPropLog
+ },
+ {
+ test: /^(placeholder-class|auto-focus|confirm-type|confirm-hold|adjust-position|hold-keyboard)$/,
+ tt: ttPropLog
+ },
+ {
+ test: /^(hold-keyboard)$/,
+ jd: jdPropLog
+ },
+ {
+ test: 'type',
+ web (prop) {
+ let { value } = prop
+ if (value === 'idcard' || value === 'digit') {
+ webValueLog(prop)
+ value = 'text'
+ }
+ return {
+ name: prop.name,
+ value
+ }
+ }
+ },
+ {
+ test: /^(password|auto-focus|focus|cursor|selection-start|selection-end|use-built-in)$/,
+ web (_prop, { el }) {
+ el.isBuiltIn = true
+ }
+ },
+ {
+ test: /^(placeholder-style|placeholder-class|cursor-spacing|confirm-type|confirm-hold|adjust-position|hold-keyboard)$/,
+ web: webPropLog
+ },
+ {
+ test: /^(always-embed|bindkeyboardheightchange)$/,
+ qa: qaPropLog
+ }
+ ],
+ event: [
+ {
+ test: 'keyboardheightchange',
+ ali: aliEventLog,
+ swan: baiduEventLog,
+ tt: ttEventLog,
+ web: webEventLog,
+ jd: jdEventLog
+ },
+ {
+ test: 'confirm',
+ web: webEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/live-player.ts b/packages/compiler/src/platform/template/wx/component-config/live-player.ts
new file mode 100644
index 0000000000..b98f45f749
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/live-player.ts
@@ -0,0 +1,51 @@
+import { DefineConfig } from "."
+
+const TAG_NAME = 'live-player'
+
+export default function ({ print }) {
+ const baiduValueLogError = print({ platform: 'baidu', tag: TAG_NAME, isError: true, type: 'value' })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const baiduEventLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false, type: 'event' })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+ const qqEventLog = print({ platform: 'qq', tag: TAG_NAME, isError: false, type: 'event' })
+ const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ return {
+ test: TAG_NAME,
+ props: [
+ {
+ test: 'mode',
+ swan ({ name, value }) {
+ // 百度只有直播模式,也就是微信的mode=live
+ if (value !== 'live') {
+ baiduValueLogError({ name, value })
+ }
+ return false
+ }
+ },
+ {
+ test: /^(sound-mode|auto-pause-if-navigate|auto-pause-if-open-native)$/,
+ swan: baiduPropLog
+ },
+ {
+ test: /^(background-mute|picture-in-picture-mode)$/,
+ qq: qqPropLog
+ },
+ {
+ test: /^(mode|background-mute|min-cache|max-cache|sound-mode|auto-pause-if-navigate|auto-pause-if-open-native)$/,
+ tt: ttPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(audiovolumenotify|enterpictureinpicture|leavepictureinpicture)$/,
+ qq: qqEventLog,
+ swan: baiduEventLog
+ },
+ {
+ test: /^(netstatus|audiovolumenotify|enterpictureinpicture|leavepictureinpicture)$/,
+ tt: ttEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/live-pusher.ts b/packages/compiler/src/platform/template/wx/component-config/live-pusher.ts
new file mode 100644
index 0000000000..8f37d98289
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/live-pusher.ts
@@ -0,0 +1,23 @@
+import { DefineConfig } from "."
+
+const TAG_NAME = 'live-pusher'
+
+export default function ({ print }) {
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+ const qqEventLog = print({ platform: 'qq', tag: TAG_NAME, isError: false, type: 'event' })
+ return {
+ test: TAG_NAME,
+ props: [
+ {
+ test: /^(remote-mirror|local-mirror|audio-reverb-type|enable-mic|enable-agc|enable-ans|audio-volume-type|video-width|video-height|beauty-style|filter)$/,
+ qq: qqPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(audiovolumenotify)$/,
+ qq: qqEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/map.ts b/packages/compiler/src/platform/template/wx/component-config/map.ts
new file mode 100644
index 0000000000..1a81076398
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/map.ts
@@ -0,0 +1,101 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'map'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const aliEventLogError = print({ platform: 'ali', tag: TAG_NAME, isError: true, type: 'event' })
+ const aliPropValueWarningLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'value-attr-uniform' })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const baiduEventLogError = print({ platform: 'baidu', tag: TAG_NAME, isError: true, type: 'event' })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+ const jdEventLogError = print({ platform: 'jd', tag: TAG_NAME, isError: true, type: 'event' })
+ const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ const qaEventLogError = print({ platform: 'qa', tag: TAG_NAME, isError: true, type: 'event' })
+ return {
+ // 匹配标签名,可传递正则
+ test: TAG_NAME,
+ // 组件属性中的差异部分
+ props: [
+ {
+ test: /^(min-scale|max-scale|covers|subkey|layer-style|rotate|skew|enable-3D|show-compass|show-scale|enable-overlooking|enable-zoom|enable-scroll|enable-rotate|enable-satellite|enable-traffic|enable-poi|enable-building)$/,
+ ali: aliPropLog
+ },
+ {
+ test: /^polygons$/,
+ ali ({ name, value }) {
+ name = 'polygon'
+ // TODO 标签的属性名不一致,后续考虑通过wxs注入的方式实现转换
+ aliPropValueWarningLog()
+ return { name, value }
+ }
+ },
+ {
+ test: 'subkey',
+ swan: baiduPropLog
+ },
+ {
+ test: /^(covers|polygons|subkey|layer-style|rotate|skew|enable-3D|show-compass|show-scale|enable-overlooking|enable-zoom|enable-scroll|enable-rotate|enable-satellite|enable-traffic|setting)$/,
+ jd: jdPropLog
+ },
+ {
+ test: /^(include-points|show-location)$/,
+ jd ({ name }) {
+ const propsMap = {
+ 'include-points': 'includePoints',
+ 'show-location': 'showLocation'
+ }
+ return propsMap[name as keyof typeof propsMap]
+ }
+ },
+ {
+ test: /^(min-scale|max-scale|polyline|controls|polygons|subkey|layer-style|rotate|skew|enable-3D|show-compass|show-scale|enable-overlooking|enable-zoom|enable-scroll|enable-rotate|enable-satellite|enable-traffic|enable-poi|enable-building|setting)$/,
+ tt: ttPropLog
+ },
+ {
+ test: /^(min-scale|max-scale|covers|polyline|include-points|show-location|subkey|layer-style|skew|enable-3D|show-compass|show-scale|enable-overlooking|enable-zoom|enable-scroll|enable-rotate|enable-satellite|enable-traffic|enable-poi|enable-building|setting)$/,
+ qa: qaPropLog
+ }
+ ],
+ // 组件事件中的差异部分
+ // 微信中基础事件有touchstart|touchmove|touchcancel|touchend|tap|longpress|longtap|transitionend|animationstart|animationiteration|animationend|touchforcechange
+ // 支付宝中的基础事件有touchStart|touchMove|touchEnd|touchCancel|tap|longTap
+ event: [
+ {
+ test: /^(tap|markertap|callouttap|controltap|regionchange|)$/,
+ ali (eventName) {
+ const eventMap = {
+ tap: 'tap',
+ markertap: 'markerTap',
+ callouttap: 'calloutTap',
+ controltap: 'controlTap',
+ regionchange: 'regionChange'
+ }
+ return eventMap[eventName as keyof typeof eventMap]
+ }
+ },
+ {
+ test: /^(updated|poitap|anchorpointtap)$/,
+ ali: aliEventLogError
+ },
+ {
+ test: 'poitap',
+ swan: baiduEventLogError
+ },
+ {
+ test: /^(labeltap|updated|poitap)$/,
+ jd: jdEventLogError
+ },
+ {
+ test: /^(labeltap|controltap|updated|regionchange|poitap|anchorpointtap)$/,
+ tt: ttEventLog
+ },
+ {
+ test: /^(labeltap|anchorpointtap)$/,
+ qa: qaEventLogError
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/movable-area.ts b/packages/compiler/src/platform/template/wx/component-config/movable-area.ts
new file mode 100644
index 0000000000..6c77eab7cf
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/movable-area.ts
@@ -0,0 +1,13 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'movable-area'
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-movable-area'
+ }
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/movable-view.ts b/packages/compiler/src/platform/template/wx/component-config/movable-view.ts
new file mode 100644
index 0000000000..a467bca16e
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/movable-view.ts
@@ -0,0 +1,27 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'movable-view'
+
+export default function ({ print }) {
+ const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-movable-view'
+ },
+ props: [
+ {
+ test: /^(out-of-bounds)$/,
+ ali: qaPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(htouchmove|vtouchmove)$/,
+ ali: aliEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/navigator.ts b/packages/compiler/src/platform/template/wx/component-config/navigator.ts
new file mode 100644
index 0000000000..a71807b6bc
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/navigator.ts
@@ -0,0 +1,204 @@
+import { isMustache } from '@mpxjs/compile-utils'
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'navigator'
+
+// 微信支持的属性及其值
+const wxSupportPropsValue: {
+ [key: string]: any
+} = {
+ 'open-type': [
+ 'navigate',
+ 'redirect',
+ 'switchTab',
+ 'reLaunch',
+ 'navigateBack',
+ 'exit'
+ ]
+}
+export default function ({ print }) {
+ const aliValueLogError = print({
+ platform: 'ali',
+ tag: TAG_NAME,
+ isError: true,
+ type: 'value'
+ })
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const aliPropLogError = print({
+ platform: 'ali',
+ tag: TAG_NAME,
+ isError: true
+ })
+ const aliEventLog = print({
+ platform: 'ali',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ const ttValueLogError = print({
+ platform: 'bytedance',
+ tag: TAG_NAME,
+ isError: true,
+ type: 'value'
+ })
+ const ttPropLog = print({
+ platform: 'bytedance',
+ tag: TAG_NAME,
+ isError: false
+ })
+ const ttEventLog = print({
+ platform: 'bytedance',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ const webPropLog = print({ platform: 'web', tag: TAG_NAME, isError: false })
+ const webEventLog = print({ platform: 'web', tag: TAG_NAME, isError: false })
+ const webValueLogError = print({
+ platform: 'web',
+ tag: TAG_NAME,
+ isError: true,
+ type: 'value'
+ })
+ const wxPropValueLog = print({
+ platform: 'wx',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'value'
+ })
+ const qaEventLog = print({
+ platform: 'qa',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ const qaValueLogError = print({
+ platform: 'qa',
+ tag: TAG_NAME,
+ isError: true,
+ type: 'value'
+ })
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-navigator'
+ },
+ props: [
+ {
+ test: /^(target|delta|app-id|path|extra-data|version|hover-stop-propagation)$/,
+ ali: aliPropLogError,
+ qa: qaPropLog
+ },
+ {
+ test: 'open-type',
+ ali(attr) {
+ if (
+ wxSupportPropsValue[attr.name] &&
+ wxSupportPropsValue[attr.name].indexOf(attr.value) === -1
+ ) {
+ wxPropValueLog({ name: attr.name, value: attr.value })
+ }
+ if (isMustache(attr.value)) {
+ // 如果是个变量,报warning~
+ aliPropLog(attr)
+ } else {
+ const supportedList = [
+ 'navigate',
+ 'redirect',
+ 'switchTab',
+ 'navigateBack',
+ 'reLaunch',
+ 'exit'
+ ]
+ if (supportedList.indexOf(attr.value) === -1) {
+ aliValueLogError(attr)
+ }
+ }
+ },
+ tt(attr) {
+ if (
+ wxSupportPropsValue[attr.name] &&
+ wxSupportPropsValue[attr.name].indexOf(attr.value) === -1
+ ) {
+ wxPropValueLog({ name: attr.name, value: attr.value })
+ }
+ if (isMustache(attr.value)) {
+ // 如果是个变量,报warning~
+ ttPropLog(attr)
+ } else {
+ const supportedList = [
+ 'navigate',
+ 'redirect',
+ 'switchTab',
+ 'navigateBack',
+ 'reLaunch'
+ ]
+ if (supportedList.indexOf(attr.value) === -1) {
+ ttValueLogError(attr)
+ }
+ }
+ },
+ web(attr) {
+ const supportedList = [
+ 'navigate',
+ 'redirect',
+ 'navigateBack',
+ 'reLaunch'
+ ]
+ if (supportedList.indexOf(attr.value) === -1) {
+ webValueLogError(attr)
+ }
+ },
+ qa(attr) {
+ if (
+ wxSupportPropsValue[attr.name] &&
+ wxSupportPropsValue[attr.name].indexOf(attr.value) === -1
+ ) {
+ wxPropValueLog({ name: attr.name, value: attr.value })
+ }
+ if (isMustache(attr.value)) {
+ qaPropLog(attr)
+ } else {
+ const supportedList = [
+ 'navigate',
+ 'redirect',
+ 'switchTab',
+ 'navigateBack',
+ 'reLaunch'
+ ]
+ if (supportedList.indexOf(attr.value) === -1) {
+ qaValueLogError(attr)
+ }
+ }
+ }
+ },
+ {
+ test: /^(target|app-id|path|extra-data|version)$/,
+ tt: ttPropLog
+ },
+ {
+ test: /^(target|app-id|path|extra-data|version)$/,
+ web: webPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(success|fail|complete)$/,
+ ali: aliEventLog,
+ tt: ttEventLog,
+ web: webEventLog,
+ qa: qaEventLog,
+ jd(eventName) {
+ const eventMap = {
+ success: 'success',
+ fail: 'error',
+ complete: 'complete'
+ }
+ return eventMap[eventName as keyof typeof eventMap]
+ }
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/picker-view-column.ts b/packages/compiler/src/platform/template/wx/component-config/picker-view-column.ts
new file mode 100644
index 0000000000..8db2f2b7bf
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/picker-view-column.ts
@@ -0,0 +1,13 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'picker-view-column'
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-picker-view-column'
+ }
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/picker-view.ts b/packages/compiler/src/platform/template/wx/component-config/picker-view.ts
new file mode 100644
index 0000000000..265761e3aa
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/picker-view.ts
@@ -0,0 +1,34 @@
+import { DefineConfig } from "."
+
+const TAG_NAME = 'picker-view'
+
+export default function ({ print }) {
+ const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' })
+ const baiduEventLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' })
+ const jdEventLog = print({ platform: 'jd', tag: TAG_NAME, isError: false, type: 'event' })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-picker-view'
+ },
+ props: [
+ {
+ test: /^(indicator-class|mask-class)$/,
+ tt: ttPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(pickstart|pickend)$/,
+ ali: aliEventLog,
+ swan: baiduEventLog,
+ tt: ttEventLog,
+ jd: jdEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/picker.ts b/packages/compiler/src/platform/template/wx/component-config/picker.ts
new file mode 100644
index 0000000000..655bb2fc5a
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/picker.ts
@@ -0,0 +1,61 @@
+import { DefineConfig } from '.'
+const TAG_NAME = 'picker'
+
+export default function ({ print }) {
+ const aliPropLogError = print({
+ platform: 'ali',
+ tag: TAG_NAME,
+ isError: true
+ })
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const aliEventLog = print({
+ platform: 'ali',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: true })
+ const ttPropLog = print({
+ platform: 'bytedance',
+ tag: TAG_NAME,
+ isError: false
+ })
+ const baiduPropLog = print({
+ platform: 'baidu',
+ tag: TAG_NAME,
+ isError: false
+ })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-picker'
+ },
+ props: [
+ {
+ test: 'mode',
+ ali (attr) {
+ if (attr.value !== 'selector') {
+ aliPropLogError(attr)
+ }
+ return false
+ }
+ },
+ {
+ test: /^(header-text)$/,
+ tt: ttPropLog,
+ swan: baiduPropLog,
+ ali: aliPropLog,
+ jd: jdPropLog,
+ qa: qaPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(cancel)$/,
+ ali: aliEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/progress.ts b/packages/compiler/src/platform/template/wx/component-config/progress.ts
new file mode 100644
index 0000000000..800eefa073
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/progress.ts
@@ -0,0 +1,73 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'progress'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const baiduEventLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false, type: 'event' })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+ const jdEventLog = print({ platform: 'jd', tag: TAG_NAME, isError: false, type: 'event' })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-progress'
+ },
+ props: [
+ {
+ test: /^(border-radius|font-size|color|active-mode|duration)$/,
+ ali: aliPropLog
+ },
+ {
+ test: /^(border-radius|font-size)$/,
+ swan: baiduPropLog
+ },
+ {
+ test: /^(activeColor|backgroundColor)$/,
+ ali (obj) {
+ const propsMap = {
+ activeColor: 'active-color',
+ backgroundColor: 'background-color'
+ }
+ obj.name = propsMap[obj.name as keyof typeof propsMap]
+ return obj
+ },
+ tt (obj) {
+ const propsMap = {
+ activeColor: 'active-color',
+ backgroundColor: 'background-color'
+ }
+ obj.name = propsMap[obj.name as keyof typeof propsMap]
+ return obj
+ }
+ },
+ {
+ test: /^(show-info|border-radius|font-size|duration)$/,
+ tt: ttPropLog
+ },
+ {
+ test: /^(border-radius|font-size|duration|bindactiveend)$/,
+ jd: jdPropLog
+ },
+ {
+ test: /^(duration)$/,
+ qq: qqPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(activeend)$/,
+ ali: aliEventLog,
+ swan: baiduEventLog,
+ tt: ttEventLog,
+ jd: jdEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/radio-group.ts b/packages/compiler/src/platform/template/wx/component-config/radio-group.ts
new file mode 100644
index 0000000000..e66371b39f
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/radio-group.ts
@@ -0,0 +1,13 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'radio-group'
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-radio-group'
+ }
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/radio.ts b/packages/compiler/src/platform/template/wx/component-config/radio.ts
new file mode 100644
index 0000000000..e53bb79dc3
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/radio.ts
@@ -0,0 +1,22 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'radio'
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-radio'
+ },
+ event: [
+ {
+ test: 'tap',
+ ali () {
+ // 支付宝radio上不支持tap事件,change事件的表现和tap类似所以替换
+ return 'change'
+ }
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/rich-text.ts b/packages/compiler/src/platform/template/wx/component-config/rich-text.ts
new file mode 100644
index 0000000000..f3cf352ce4
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/rich-text.ts
@@ -0,0 +1,31 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'rich-text'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-rich-text'
+ },
+ props: [
+ {
+ test: /^(space)$/,
+ ali: aliPropLog,
+ swan: baiduPropLog,
+ tt: ttPropLog,
+ jd: jdPropLog
+ },
+ {
+ test: /^(nodes)$/,
+ jd: jdPropLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/scroll-view.ts b/packages/compiler/src/platform/template/wx/component-config/scroll-view.ts
new file mode 100644
index 0000000000..eabf725722
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/scroll-view.ts
@@ -0,0 +1,71 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'scroll-view'
+
+export default function ({ print }) {
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const baiduEventLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false, type: 'event' })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+ const jdEventLog = print({ platform: 'jd', tag: TAG_NAME, isError: false, type: 'event' })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' })
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' })
+ const qqEventLog = print({ platform: 'qq', tag: TAG_NAME, isError: false, type: 'event' })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-scroll-view'
+ },
+ props: [
+ {
+ test: /^(enable-flex|scroll-anchorin|refresher-enabled|refresher-threshold|refresher-default-style|refresher-background|refresher-triggered|enhanced|bounces|show-scrollbar|paging-enabled|fast-deceleratio)$/,
+ ali: aliPropLog,
+ tt: ttPropLog,
+ qq: qqPropLog,
+ swan: baiduPropLog
+ },
+ {
+ test: /^(enable-back-to-top)$/,
+ swan: baiduPropLog,
+ tt: ttPropLog
+ },
+ {
+ test: /^(enable-flex|scroll-anchoring|refresher-enabled|refresher-threshold|refresher-default-style|refresher-background|refresher-triggered)$/,
+ jd: jdPropLog
+ },
+ {
+ test: /^(enable-back-to-top|enable-flex|scroll-anchoring|enhanced|bounces|show-scrollbar|paging-enabled|fast-deceleration|binddragstart|binddragging|binddragend)$/,
+ qa: qaPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(scrolltoupper|scrolltolower|scroll)$/,
+ ali (eventName) {
+ const eventMap = {
+ scrolltoupper: 'scrollToUpper',
+ scrolltolower: 'scrollToLower',
+ scroll: 'scroll'
+ }
+ return eventMap[eventName as keyof typeof eventMap]
+ }
+ },
+ {
+ test: /^(refresherpulling|refresherrefresh|refresherrestore|refresherabort)$/,
+ jd: jdEventLog
+ },
+ {
+ test: /^(dragstart|dragging|dragend|refresherpulling|refresherrefresh|refresherrestore|refresherabort)$/,
+ ali: aliEventLog,
+ tt: ttEventLog,
+ qq: qqEventLog,
+ swan: baiduEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/slider.ts b/packages/compiler/src/platform/template/wx/component-config/slider.ts
new file mode 100644
index 0000000000..d060444251
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/slider.ts
@@ -0,0 +1,57 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'slider'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-slider'
+ },
+ props: [
+ {
+ test: /^color$/,
+ ali: aliPropLog
+ },
+ {
+ test: /^(color|selected-color|activeColor|backgroundColor|block-size|block-color)$/,
+ ali (obj) {
+ const propsMap = {
+ color: 'background-color',
+ 'selected-color': 'active-color',
+ activeColor: 'active-color',
+ backgroundColor: 'background-color',
+ 'block-size': 'handle-size',
+ 'block-color': 'handle-color'
+ }
+ obj.name = propsMap[obj.name as keyof typeof propsMap]
+ return obj
+ }
+ },
+ {
+ test: /^(color|selected-color)$/,
+ swan (obj) {
+ const propsMap = {
+ color: 'backgroundColor',
+ 'selected-color': 'activeColor'
+ }
+ obj.name = propsMap[obj.name as keyof typeof propsMap]
+ return obj
+ }
+ },
+ {
+ test: /^(activeColor|backgroundColor)$/,
+ tt (obj) {
+ const propsMap = {
+ activeColor: 'active-color',
+ backgroundColor: 'background-color'
+ }
+ obj.name = propsMap[obj.name as keyof typeof propsMap]
+ return obj
+ }
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/swiper-item.ts b/packages/compiler/src/platform/template/wx/component-config/swiper-item.ts
new file mode 100644
index 0000000000..d15049a2e0
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/swiper-item.ts
@@ -0,0 +1,33 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'swiper-item'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-swiper-item'
+ },
+ props: [
+ {
+ test: /^(item-id)$/,
+ ali: aliPropLog
+ },
+ {
+ test: /^(skip-hidden-item-layout)$/,
+ qa: qaPropLog,
+ ali: aliPropLog,
+ tt: ttPropLog,
+ swan: baiduPropLog,
+ qq: qqPropLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/swiper.ts b/packages/compiler/src/platform/template/wx/component-config/swiper.ts
new file mode 100644
index 0000000000..fc44e5401f
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/swiper.ts
@@ -0,0 +1,70 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'swiper'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const baiduEventLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false, type: 'event' })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const webPropLog = print({ platform: 'web', tag: TAG_NAME, isError: false })
+ const jdEventLog = print({ platform: 'jd', tag: TAG_NAME, isError: false, type: 'event' })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-swiper'
+ },
+ props: [
+ {
+ test: /^(display-multiple-items|skip-hidden-item-layout|easing-function)$/,
+ ali: aliPropLog
+ },
+ {
+ test: /^(skip-hidden-item-layout|easing-function|snap-to-edge)$/,
+ swan: baiduPropLog
+ },
+ {
+ test: /^(easing-function)$/,
+ jd: jdPropLog
+ },
+ {
+ test: /^(easing-function|snap-to-edge)$/,
+ qq: qqPropLog
+ },
+ {
+ test: /^(skip-hidden-item-layout|easing-function)$/,
+ tt: ttPropLog
+ },
+ {
+ test: /^(previous-margin|next-margin|display-multiple-items|skip-hidden-item-layout)$/,
+ web: webPropLog
+ },
+ {
+ test: /^(snap-to-edge|easing-function)$/,
+ qa: qaPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(change|animationfinish)$/,
+ ali (eventName) {
+ const eventMap = {
+ change: 'change',
+ animationfinish: 'animationEnd'
+ }
+ return eventMap[eventName as keyof typeof eventMap]
+ }
+ },
+ {
+ test: /^(transition)$/,
+ swan: baiduEventLog,
+ jd: jdEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/switch.ts b/packages/compiler/src/platform/template/wx/component-config/switch.ts
new file mode 100644
index 0000000000..55cd3a2ada
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/switch.ts
@@ -0,0 +1,26 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'switch'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-switch'
+ },
+ props: [
+ {
+ test: /^type$/,
+ ali: aliPropLog
+ },
+ {
+ test: /^disabled$/,
+ jd: jdPropLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/template.ts b/packages/compiler/src/platform/template/wx/component-config/template.ts
new file mode 100644
index 0000000000..07f1252a7f
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/template.ts
@@ -0,0 +1,20 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'template'
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ props: [
+ {
+ test: 'data',
+ swan ({ name, value }) {
+ return {
+ name,
+ value: `{${value}}`
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/text.ts b/packages/compiler/src/platform/template/wx/component-config/text.ts
new file mode 100644
index 0000000000..d00ab8cf2e
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/text.ts
@@ -0,0 +1,45 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'text'
+
+export default function ({ print }) {
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ if (el.hasEvent) {
+ el.isBuiltIn = true
+ }
+ if (el.isBuiltIn) {
+ return 'mpx-text'
+ } else {
+ return 'span'
+ }
+ },
+ props: [
+ {
+ test: /^(decode|user-select)$/,
+ swan: baiduPropLog
+ },
+ {
+ test: /^(user-select)$/,
+ ali: aliPropLog,
+ tt: ttPropLog,
+ qq: qqPropLog,
+ qa: qaPropLog
+ },
+ {
+ test: /^(selectable|space|decode|use-built-in)$/,
+ web (_prop, { el }) {
+ el.isBuiltIn = true
+ },
+ qa: qaPropLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/textarea.ts b/packages/compiler/src/platform/template/wx/component-config/textarea.ts
new file mode 100644
index 0000000000..25f0dad390
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/textarea.ts
@@ -0,0 +1,79 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'textarea'
+
+export default function ({ print }) {
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' })
+ const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
+ const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' })
+ const webPropLog = print({ platform: 'web', tag: TAG_NAME, isError: false })
+ const webEventLog = print({ platform: 'web', tag: TAG_NAME, isError: false, type: 'event' })
+ const jdPropLog = print({ platform: 'jd', tag: TAG_NAME, isError: false })
+ const jdEventLog = print({ platform: 'jd', tag: TAG_NAME, isError: false, type: 'event' })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ const qaEventLog = print({ platform: 'qa', tag: TAG_NAME, isError: false, type: 'event' })
+ const qqEventLog = print({ platform: 'qq', tag: TAG_NAME, isError: false, type: 'event' })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+ const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
+
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ // form全量使用内建组件
+ el.isBuiltIn = true
+ return 'mpx-textarea'
+ },
+ props: [
+ {
+ test: /^(auto-focus|fixed|cursor-spacing|cursor|show-confirm-bar|selection-start|selection-end|adjust-position|hold-keyboard|disable-default-padding|confirm-type)$/,
+ ali: aliPropLog
+ },
+ {
+ test: /^(hold-keyboard|disable-default-padding|confirm-type)$/,
+ qq: qqPropLog,
+ swan: baiduPropLog
+ },
+ {
+ test: /^(placeholder-class|auto-focus|show-confirm-bar|adjust-position|hold-keyboard|disable-default-padding|confirm-type)$/,
+ tt: ttPropLog
+ },
+ {
+ test: /^(placeholder-style|placeholder-class|fixed|cursor-spacing|show-confirm-bar|adjust-position|hold-keyboard|auto-height)$/,
+ web: webPropLog
+ },
+ {
+ test: /^(hold-keyboard|disable-default-padding)$/,
+ jd: jdPropLog
+ },
+ {
+ test: /^(fixed|cursor-spacing|show-confirm-bar|adjust-position|hold-keyboard|auto-height)$/,
+ qa: qaPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(confirm|linechange)$/,
+ web: webEventLog
+ },
+ {
+ test: /^keyboardheightchange$/,
+ ali: aliEventLog,
+ jd: jdEventLog,
+ qq: qqEventLog
+ },
+ {
+ test: 'confirm',
+ web: webEventLog
+ },
+ {
+ test: 'blur|input|confirm',
+ qa: qaEventLog
+ },
+ {
+ test: /^(linechange|keyboardheightchange)$/,
+ tt: ttEventLog
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/unsupported.ts b/packages/compiler/src/platform/template/wx/component-config/unsupported.ts
new file mode 100644
index 0000000000..f7b1d2bd80
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/unsupported.ts
@@ -0,0 +1,63 @@
+import { DefineConfigs } from '.'
+
+// 支付宝小程序不支持的标签集合
+const ALI_UNSUPPORTED_TAG_NAME_ARR = ['live-pusher', 'live-player', 'audio', 'functional-page-navigator', 'editor']
+// 百度小程序不支持的标签集合
+const BAIDU_UNSUPPORTED_TAG_NAME_ARR = ['functional-page-navigator', 'live-pusher', 'editor']
+// QQ小程序不支持的标签集合
+const QQ_UNSUPPORTED_TAG_NAME_ARR = ['functional-page-navigator', 'official-account', 'editor']
+// 头条小程序不支持的标签集合
+const TT_UNSUPPORTED_TAG_NAME_ARR = ['movable-view', 'cover-image', 'cover-view', 'movable-area', 'open-data', 'official-account', 'editor', 'functional-page-navigator', 'audio', 'live-pusher']
+// 京东小程序不支持的标签集合
+const JD_UNSUPPORTED_TAG_NAME_ARR = ['functional-page-navigator', 'live-pusher', 'live-player', 'rich-text', 'audio', 'video', 'camera']
+// 快应用不支持的标签集合
+const QA_UNSUPPORTED_TAG_NAME_ARR = ['movable-view', 'movable-area', 'open-data', 'official-account', 'editor', 'functional-page-navigator', 'live-player', 'live-pusher', 'ad', 'cover-image']
+
+export default function ({ print }) {
+ const aliUnsupportedTagError = print({ platform: 'ali', isError: true, type: 'tag' })
+ const baiduUnsupportedTagError = print({ platform: 'baidu', isError: true, type: 'tag' })
+ const qqUnsupportedTagError = print({ platform: 'qq', isError: true, type: 'tag' })
+ const ttUnsupportedTagError = print({ platform: 'bytedance', isError: true, type: 'tag' })
+ const jdUnsupportedTagError = print({ platform: 'jd', isError: true, type: 'tag' })
+ const qaUnsupportedTagError = print({ platform: 'qa', isError: true, type: 'tag' })
+
+ const aliUnsupportedExp = new RegExp('^(' + ALI_UNSUPPORTED_TAG_NAME_ARR.join('|') + ')$')
+ const baiduUnsupportedExp = new RegExp('^(' + BAIDU_UNSUPPORTED_TAG_NAME_ARR.join('|') + ')$')
+ const qqUnsupportedExp = new RegExp('^(' + QQ_UNSUPPORTED_TAG_NAME_ARR.join('|') + ')$')
+ const ttUnsupportedExp = new RegExp('^(' + TT_UNSUPPORTED_TAG_NAME_ARR.join('|') + ')$')
+ const jdUnsupportedExp = new RegExp('^(' + JD_UNSUPPORTED_TAG_NAME_ARR.join('|') + ')$')
+ const qaUnsupportedExp = new RegExp('^(' + QA_UNSUPPORTED_TAG_NAME_ARR.join('|') + ')$')
+
+ return [
+ {
+ supportedModes: ['swan'],
+ test: baiduUnsupportedExp,
+ swan: baiduUnsupportedTagError
+ },
+ {
+ supportedModes: ['ali'],
+ test: aliUnsupportedExp,
+ ali: aliUnsupportedTagError
+ },
+ {
+ supportedModes: ['qq'],
+ test: qqUnsupportedExp,
+ qq: qqUnsupportedTagError
+ },
+ {
+ supportedModes: ['tt'],
+ test: ttUnsupportedExp,
+ tt: ttUnsupportedTagError
+ },
+ {
+ supportedModes: ['jd'],
+ test: jdUnsupportedExp,
+ jd: jdUnsupportedTagError
+ },
+ {
+ supportedModes: ['qa'],
+ test: qaUnsupportedExp,
+ qa: qaUnsupportedTagError
+ }
+ ]
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/video.ts b/packages/compiler/src/platform/template/wx/component-config/video.ts
new file mode 100644
index 0000000000..d14e441394
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/video.ts
@@ -0,0 +1,121 @@
+import { DefineConfig } from '.'
+const TAG_NAME = 'video'
+
+export default function ({ print }) {
+ const baiduPropLog = print({
+ platform: 'baidu',
+ tag: TAG_NAME,
+ isError: false
+ })
+ const baiduEventLogError = print({
+ platform: 'baidu',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ const qqPropLog = print({ platform: 'qq', tag: TAG_NAME, isError: false })
+ const qqEventLogError = print({
+ platform: 'qq',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ const ttPropLog = print({
+ platform: 'bytedance',
+ tag: TAG_NAME,
+ isError: false
+ })
+ const ttEventLogError = print({
+ platform: 'bytedance',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ const aliPropLog = print({ platform: 'ali', tag: TAG_NAME, isError: false })
+ const aliEventLogError = print({
+ platform: 'ali',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ const qaEventLogError = print({
+ platform: 'qa',
+ tag: TAG_NAME,
+ isError: false,
+ type: 'event'
+ })
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-video'
+ },
+ props: [
+ {
+ test: /^(enable-danmu|danmu-btn|show-progress|play-btn-position|enable-play-gesture|auto-pause-if-navigate|auto-pause-if-open-native|vslide-gesture|vslide-gesture-in-fullscreen|ad-unit-id|poster-for-crawler|show-casting-button|picture-in-picture-mode|picture-in-picture-show-progress|enable-auto-rotation|show-snapshot-button|show-screen-lock-button)$/,
+ ali: aliPropLog
+ },
+ {
+ test: /^(duration|play-btn-position|auto-pause-if-navigate|auto-pause-if-open-native|ad-unit-id|poster-for-crawler|show-casting-button|picture-in-picture-mode|picture-in-picture-show-progress|enable-auto-rotation|show-snapshot-button|show-screen-lock-button)$/,
+ swan: baiduPropLog
+ },
+ {
+ test: /^(vslide-gesture)$/,
+ qq(obj) {
+ const propsMap = {
+ 'vslide-gesture': 'page-gesture'
+ }
+ obj.name = propsMap[obj.name as keyof typeof propsMap]
+ return obj
+ }
+ },
+ {
+ test: /^(vslide-gesture-in-fullscreen|ad-unit-id|show-screen-lock-button|enable-auto-rotation|show-snapshot-button|picture-in-picture-show-progress|picture-in-picture-mode|poster-for-crawler|show-casting-button|enable-play-gesture)$/,
+ qq: qqPropLog
+ },
+ {
+ test: /^(duration|danmu-list|danmu-btn|enable-danmu|muted|initial-time|page-gesture|direction|show-progress|show-center-play-btn|enable-progress-gesture|show-mute-btn|title|enable-play-gesture|auto-pause-if-navigate|auto-pause-if-open-native|vslide-gesture|vslide-gesture-in-fullscreen|ad-unit-id|poster-for-crawler|show-casting-button|picture-in-picture-mode|picture-in-picture-show-progress|enable-auto-rotation|show-screen-lock-button|show-snapshot-button)$/,
+ tt: ttPropLog
+ },
+ {
+ test: /^(duration|danmu-list|danmu-btn|enable-danmu|muted|initial-time|page-gesture|direction|show-progress|show-center-play-btn|enable-progress-gesture|show-mute-btn|enable-play-gesture|auto-pause-if-navigate|auto-pause-if-open-native|vslide-gesture|vslide-gesture-in-fullscreen|ad-unit-id|poster-for-crawler|show-casting-button|picture-in-picture-mode|picture-in-picture-show-progress|enable-auto-rotation|show-screen-lock-button|show-snapshot-button)$/,
+ qa: qaPropLog
+ }
+ ],
+ event: [
+ {
+ test: /^(timeupdate|fullscreenchange|waiting|loadedmetadata)$/,
+ ali(evtName) {
+ const eventMap = {
+ timeupdate: 'timeUpdate',
+ fullscreenchange: 'fullScreenChange',
+ waiting: 'loading',
+ loadedmetadata: 'renderStart'
+ }
+ return eventMap[evtName as keyof typeof eventMap]
+ }
+ },
+ {
+ test: /^(enterpictureinpicture|leavepictureinpicture|controlstoggle|seekcomplete)$/,
+ ali: aliEventLogError
+ },
+ {
+ test: /^(enterpictureinpicture|leavepictureinpicture|controlstoggle|loadedmetadata|seekcomplete)$/,
+ qq: qqEventLogError
+ },
+ {
+ test: /^(progress|enterpictureinpicture|leavepictureinpicture|controlstoggle|loadedmetadata|seekcomplete)$/,
+ swan: baiduEventLogError
+ },
+ {
+ test: /^(progress|enterpictureinpicture|leavepictureinpicture|controlstoggle|loadedmetadata|seekcomplete)$/,
+ tt: ttEventLogError
+ },
+ {
+ test: /^(progress|enterpictureinpicture|leavepictureinpicture|controlstoggle|loadedmetadata|seekcomplete)$/,
+ qa: qaEventLogError
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/view.ts b/packages/compiler/src/platform/template/wx/component-config/view.ts
new file mode 100644
index 0000000000..6a62557fbb
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/view.ts
@@ -0,0 +1,62 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'view'
+
+export default function ({ print }) {
+ const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
+ const qaEventLogError = print({ platform: 'qa', tag: TAG_NAME, isError: false, type: 'event' })
+
+ return {
+ // 匹配标签名,可传递正则
+ test: TAG_NAME,
+ // 支付宝标签名转换函数,如无差异可忽略
+ // ali () {
+ // return 'a:view'
+ // },
+ web (_tag, { el }) {
+ if (el.hasEvent) {
+ el.isBuiltIn = true
+ }
+ if (el.isBuiltIn) {
+ return 'mpx-view'
+ } else {
+ return 'div'
+ }
+ },
+ qa () {
+ return 'div'
+ },
+ // 组件属性中的差异部分
+ props: [
+ {
+ test: /^(hover-(class|stop-propagation|start-time|stay-time)|use-built-in)$/,
+ // 当遇到微信支持而支付宝不支持的特性时,转换函数可以只抛出错误或警告而不返回值
+ web (_prop, { el }) {
+ el.isBuiltIn = true
+ },
+ qa: qaPropLog
+ }
+ ],
+ // 组件事件中的差异部分
+ // 微信中基础事件有touchstart|touchmove|touchcancel|touchend|tap|longpress|longtap|transitionend|animationstart|animationiteration|animationend|touchforcechange
+ // 支付宝中的基础事件有touchStart|touchMove|touchEnd|touchCancel|tap|longTap
+ // 快应用通用事件有touchstart|touchmove|touchend|touchcancel|longpress|click|focus|blur
+ event: [
+ {
+ // 支付宝中的view组件额外支持了transitionEnd|animationStart|animationIteration|animationEnd,故在此声明了组件事件转换逻辑
+ test: /^(transitionend|animationstart|animationiteration|animationend)$/,
+ //
+ ali (eventName) {
+ const eventMap = {
+ transitionend: 'transitionEnd',
+ animationstart: 'animationStart',
+ animationiteration: 'animationIteration',
+ animationend: 'animationEnd'
+ }
+ return eventMap[eventName as keyof typeof eventMap]
+ },
+ qa: qaEventLogError
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/web-view.ts b/packages/compiler/src/platform/template/wx/component-config/web-view.ts
new file mode 100644
index 0000000000..a3009bf1fc
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/web-view.ts
@@ -0,0 +1,13 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'web-view'
+
+export default function () {
+ return {
+ test: TAG_NAME,
+ web (_tag, { el }) {
+ el.isBuiltIn = true
+ return 'mpx-web-view'
+ }
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/component-config/wxs.ts b/packages/compiler/src/platform/template/wx/component-config/wxs.ts
new file mode 100644
index 0000000000..cf6f9c48bd
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/component-config/wxs.ts
@@ -0,0 +1,56 @@
+import { DefineConfig } from '.'
+
+const TAG_NAME = 'wxs'
+
+export default function () {
+ return {
+ // 匹配标签名,可传递正则
+ test: TAG_NAME,
+ ali () {
+ return 'import-sjs'
+ },
+ swan () {
+ return 'import-sjs'
+ },
+ qq () {
+ return 'qs'
+ },
+ jd () {
+ return 'jds'
+ },
+ tt () {
+ return 'sjs'
+ },
+ qa () {
+ return 'qjs'
+ },
+ dd () {
+ return 'dds'
+ },
+ // 组件属性中的差异部分
+ props: [
+ {
+ test: 'src',
+ ali (obj) {
+ obj.name = 'from'
+ return obj
+ },
+ qa (obj) {
+ obj.name = 'from'
+ return obj
+ }
+ },
+ {
+ test: 'module',
+ ali (obj) {
+ obj.name = 'name'
+ return obj
+ },
+ qa (obj) {
+ obj.name = 'name'
+ return obj
+ }
+ }
+ ]
+ }
+}
diff --git a/packages/compiler/src/platform/template/wx/index.ts b/packages/compiler/src/platform/template/wx/index.ts
new file mode 100644
index 0000000000..ddc20c2f27
--- /dev/null
+++ b/packages/compiler/src/platform/template/wx/index.ts
@@ -0,0 +1,433 @@
+// @ts-nocheck
+import { parse } from 'json5'
+import { normalize, isValidIdentifierStr } from '@mpxjs/compile-utils'
+import runRules from '../../run-rules'
+import getComponentConfigs from './component-config'
+import normalizeComponentRules from '../normalize-component-rules'
+import { parseMustache as _parseMustache, stringifyWithResolveComputed as _stringifyWithResolveComputed } from '../../../template-compiler/compiler'
+const parseMustache = _parseMustache
+const stringifyWithResolveComputed = _stringifyWithResolveComputed
+
+export default function getSpec ({ warn, error }) {
+ const spec = {
+ supportedModes: ['ali', 'swan', 'qq', 'tt', 'web', 'qa', 'jd', 'dd'],
+ // props预处理
+ preProps: [],
+ // props后处理
+ postProps: [
+ {
+ web ({ name, value }) {
+ const parsed = parseMustache(value)
+ if (parsed.hasBinding) {
+ return {
+ name: name === 'animation' ? 'v-' + name : ':' + name,
+ value: parsed.result
+ }
+ }
+ }
+ }
+ ],
+ // 指令处理
+ directive: [
+ // 特殊指令
+ {
+ test: 'wx:for',
+ swan (obj, data) {
+ const attrsMap = data.el.attrsMap
+ const parsed = parseMustache(obj.value)
+ let listName = parsed.result
+ const el = data.el
+
+ const itemName = attrsMap['wx:for-item'] || 'item'
+ const indexName = attrsMap['wx:for-index'] || 'index'
+ const keyName = attrsMap['wx:key'] || null
+ let keyStr = ''
+
+ if (parsed.hasBinding) {
+ listName = listName.slice(1, -1)
+ }
+
+ if (keyName) {
+ const parsed = parseMustache(keyName)
+ if (parsed.hasBinding) {
+ // keyStr = ` trackBy ${parsed.result.slice(1, -1)}`
+ } else if (keyName === '*this') {
+ keyStr = ` trackBy ${itemName}`
+ } else {
+ if (!isValidIdentifierStr(keyName)) {
+ keyStr = ` trackBy ${itemName}['${keyName}']`
+ } else {
+ keyStr = ` trackBy ${itemName}.${keyName}`
+ }
+ }
+ }
+ if (el) {
+ const injectWxsProp = {
+ injectWxsPath: '~' + normalize.lib('runtime/swanHelper.wxs'),
+ injectWxsModuleName: '__swanHelper__'
+ }
+ if (el.injectWxsProps && Array.isArray(el.injectWxsProps)) {
+ el.injectWxsProps.push(injectWxsProp)
+ } else {
+ el.injectWxsProps = [injectWxsProp]
+ }
+ }
+ return {
+ name: 's-for',
+ value: `${itemName}, ${indexName} in __swanHelper__.processFor(${listName})${keyStr}`
+ }
+ },
+ web ({ value }, { el }) {
+ const parsed = parseMustache(value)
+ const attrsMap = el.attrsMap
+ const itemName = attrsMap['wx:for-item'] || 'item'
+ const indexName = attrsMap['wx:for-index'] || 'index'
+ return {
+ name: 'v-for',
+ value: `(${itemName}, ${indexName}) in ${parsed.result}`
+ }
+ }
+ },
+ {
+ test: 'wx:key',
+ swan () {
+ return false
+ },
+ web ({ value }, { el }) {
+ // vue的template中不能包含key,对应于小程序中的block
+ if (el.tag === 'block') return false
+ const itemName = el.attrsMap['wx:for-item'] || 'item'
+ const keyName = value
+ if (value === '*this') {
+ value = itemName
+ } else {
+ if (isValidIdentifierStr(keyName)) {
+ value = `${itemName}.${keyName}`
+ } else {
+ value = `${itemName}['${keyName}']`
+ }
+ }
+ return {
+ name: ':key',
+ value
+ }
+ }
+ },
+ {
+ // 在swan/web模式下删除for-index/for-item,转换为v/s-for表达式
+ test: /^wx:(for-item|for-index)$/,
+ swan () {
+ return false
+ },
+ web () {
+ return false
+ }
+ },
+ {
+ test: 'wx:model',
+ web ({ value }, { el }) {
+ el.hasEvent = true
+ const attrsMap = el.attrsMap
+ const tagRE = /\{\{((?:.|\n|\r)+?)\}\}(?!})/
+ const stringify = JSON.stringify
+ const match = tagRE.exec(value)
+ if (match) {
+ const modelProp = attrsMap['wx:model-prop'] || 'value'
+ const modelEvent = attrsMap['wx:model-event'] || 'input'
+ const modelValuePathRaw = attrsMap['wx:model-value-path']
+ const modelValuePath = modelValuePathRaw === undefined ? 'value' : modelValuePathRaw
+ const modelFilter = attrsMap['wx:model-filter']
+ let modelValuePathArr
+ try {
+ modelValuePathArr = parse(modelValuePath)
+ } catch (e) {
+ if (modelValuePath === '') {
+ modelValuePathArr = []
+ } else {
+ modelValuePathArr = modelValuePath.split('.')
+ }
+ }
+ const modelValue = match[1].trim()
+ return [
+ {
+ name: ':' + modelProp,
+ value: modelValue
+ },
+ {
+ name: 'mpxModelEvent',
+ value: modelEvent
+ },
+ {
+ name: '@mpxModel',
+ value: `__model(${stringifyWithResolveComputed(modelValue)}, $event, ${stringify(modelValuePathArr)}, ${stringify(modelFilter)})`
+ }
+ ]
+ }
+ }
+ },
+ {
+ test: /^wx:(model-prop|model-event|model-value-path|model-filter)$/,
+ web () {
+ return false
+ }
+ },
+ {
+ // ref只支持字符串字面量
+ test: 'wx:ref',
+ web ({ value }) {
+ return {
+ name: 'ref',
+ value: `__mpx_ref_${value}__`
+ }
+ }
+ },
+ {
+ // style样式绑定
+ test: /^(style|wx:style)$/,
+ web ({ value }, { el }) {
+ if (el.isStyleParsed) {
+ return false
+ }
+ const styleBinding = []
+ el.isStyleParsed = true
+ el.attrsList.forEach((item) => {
+ const parsed = parseMustache(item.value)
+ if (item.name === 'style') {
+ if (parsed.hasBinding || parsed.result.indexOf('rpx') > -1) {
+ styleBinding.push(parseMustache(item.value).result)
+ } else {
+ styleBinding.push(JSON.stringify(item.value))
+ }
+ } else if (item.name === 'wx:style') {
+ styleBinding.push(parseMustache(item.value).result)
+ }
+ })
+ return {
+ name: ':style',
+ value: `[${styleBinding}] | transRpxStyle`
+ }
+ }
+ },
+ {
+ // 样式类名绑定
+ test: /^wx:(class)$/,
+ web ({ value }) {
+ const parsed = parseMustache(value)
+ return {
+ name: ':class',
+ value: parsed.result
+ }
+ }
+ },
+ // 通用指令
+ {
+ test: /^wx:(.*)$/,
+ ali ({ name, value }) {
+ const dir = this.test.exec(name)[1]
+ return {
+ name: 'a:' + dir,
+ value
+ }
+ },
+ swan ({ name, value }) {
+ const dir = this.test.exec(name)[1]
+ return {
+ name: 's-' + dir,
+ value
+ }
+ },
+ qq ({ name, value }) {
+ const dir = this.test.exec(name)[1]
+ return {
+ name: 'qq:' + dir,
+ value
+ }
+ },
+ jd ({ name, value }) {
+ const dir = this.test.exec(name)[1]
+ return {
+ name: 'jd:' + dir,
+ value
+ }
+ },
+ tt ({ name, value }) {
+ const dir = this.test.exec(name)[1]
+ return {
+ name: 'tt:' + dir,
+ value
+ }
+ },
+ dd ({ name, value }) {
+ const dir = this.test.exec(name)[1]
+ return {
+ name: 'dd:' + dir,
+ value
+ }
+ },
+ web ({ name, value }) {
+ let dir = this.test.exec(name)[1]
+ const parsed = parseMustache(value)
+ if (dir === 'elif') {
+ dir = 'else-if'
+ }
+ return {
+ name: 'v-' + dir,
+ value: parsed.result
+ }
+ }
+ },
+ // 事件
+ {
+ test: /^(bind|catch|capture-bind|capture-catch):?(.*?)(\..*)?$/,
+ ali ({ name, value }, { eventRules }) {
+ const match = this.test.exec(name)
+ const prefix = match[1]
+ const eventName = match[2]
+ const modifierStr = match[3] || ''
+ const rPrefix = runRules(spec.event.prefix, prefix, { mode: 'ali' })
+ const rEventName = runRules(eventRules, eventName, { mode: 'ali' })
+ return {
+ name: rPrefix + rEventName.replace(/^./, (matched) => {
+ return matched.toUpperCase()
+ }) + modifierStr,
+ value
+ }
+ },
+ swan ({ name, value }, { eventRules }) {
+ const match = this.test.exec(name)
+ const eventName = match[2]
+ runRules(eventRules, eventName, { mode: 'swan' })
+ },
+ qq ({ name, value }, { eventRules }) {
+ const match = this.test.exec(name)
+ const eventName = match[2]
+ runRules(eventRules, eventName, { mode: 'qq' })
+ },
+ jd ({ name, value }, { eventRules }) {
+ const match = this.test.exec(name)
+ const eventName = match[2]
+ runRules(eventRules, eventName, { mode: 'jd' })
+ },
+ // tt ({ name, value }, { eventRules }) {
+ // const match = this.test.exec(name)
+ // const prefix = match[1]
+ // const eventName = match[2]
+ // const modifierStr = match[3] || ''
+ // const rEventName = runRules(eventRules, eventName, { mode: 'tt' })
+ // return {
+ // // 字节将所有事件转为小写
+ // name: prefix + rEventName.toLowerCase() + modifierStr,
+ // value
+ // }
+ // },
+ tt ({ name, value }, { eventRules }) {
+ const match = this.test.exec(name)
+ const eventName = match[2]
+ runRules(eventRules, eventName, { mode: 'tt' })
+ },
+ dd ({ name, value }, { eventRules }) {
+ const match = this.test.exec(name)
+ const eventName = match[2]
+ runRules(eventRules, eventName, { mode: 'dd' })
+ },
+ web ({ name, value }, { eventRules, el }) {
+ const match = this.test.exec(name)
+ const prefix = match[1]
+ const eventName = match[2]
+ const modifierStr = match[3] || ''
+ const meta = {
+ modifierStr
+ }
+ // 记录event监听信息用于后续判断是否需要使用内置基础组件
+ el.hasEvent = true
+ const rPrefix = runRules(spec.event.prefix, prefix, { mode: 'web', meta })
+ const rEventName = runRules(eventRules, eventName, { mode: 'web' })
+ return {
+ name: rPrefix + rEventName + meta.modifierStr,
+ value
+ }
+ }
+ },
+ // 无障碍
+ {
+ test: /^aria-(role|label)$/,
+ ali () {
+ warn('Ali environment does not support aria-role|label props!')
+ }
+ }
+ ],
+ event: {
+ prefix: [
+ {
+ ali (prefix) {
+ const prefixMap = {
+ bind: 'on',
+ catch: 'catch'
+ }
+ if (!prefixMap[prefix]) {
+ error(`Ali environment does not support [${prefix}] event handling!`)
+ return
+ }
+ return prefixMap[prefix]
+ },
+ // 通过meta将prefix转化为modifier
+ web (prefix, data, meta) {
+ const modifierStr = meta.modifierStr
+ const modifierMap = modifierStr.split('.').reduce((map, key) => {
+ if (key) {
+ map[key] = true
+ }
+ return map
+ }, {})
+ switch (prefix) {
+ case 'catch':
+ modifierMap.stop = true
+ break
+ case 'capture-bind':
+ modifierMap.capture = true
+ break
+ case 'capture-catch':
+ modifierMap.stop = true
+ modifierMap.capture = true
+ break
+ }
+ // web中不支持proxy modifier
+ delete modifierMap.proxy
+ const tempModifierStr = Object.keys(modifierMap).join('.')
+ meta.modifierStr = tempModifierStr ? '.' + tempModifierStr : ''
+ return '@'
+ }
+ }
+ ],
+ rules: [
+ // 通用冒泡事件
+ {
+ test: /^(touchstart|touchmove|touchcancel|touchend|tap|longpress|longtap|transitionend|animationstart|animationiteration|animationend|touchforcechange)$/,
+ ali (eventName) {
+ const eventMap = {
+ touchstart: 'touchStart',
+ touchmove: 'touchMove',
+ touchend: 'touchEnd',
+ touchcancel: 'touchCancel',
+ tap: 'tap',
+ longtap: 'longTap',
+ longpress: 'longTap'
+ }
+ if (eventMap[eventName]) {
+ return eventMap[eventName]
+ } else {
+ error(`Ali environment does not support [${eventName}] event!`)
+ }
+ },
+ web (eventName) {
+ if (eventName === 'touchforcechange') {
+ error(`Web environment does not support [${eventName}] event!`)
+ }
+ }
+ }
+ ]
+ }
+ }
+ spec.rules = normalizeComponentRules(getComponentConfigs({ warn, error }).concat({}), spec)
+ return spec
+}
diff --git a/packages/compiler/src/script-setup-compiler/index.ts b/packages/compiler/src/script-setup-compiler/index.ts
new file mode 100644
index 0000000000..0b251dc0c1
--- /dev/null
+++ b/packages/compiler/src/script-setup-compiler/index.ts
@@ -0,0 +1,1168 @@
+// @ts-nocheck
+import MagicString from 'magic-string'
+import * as babylon from '@babel/parser'
+import traverse from '@babel/traverse'
+import * as t from '@babel/types'
+import formatCodeFrame from '@babel/code-frame'
+
+// Special compiler macros
+const DEFINE_PROPS = 'defineProps'
+const DEFINE_OPTIONS = 'defineOptions'
+const DEFINE_EXPOSE = 'defineExpose'
+const WITH_DEFAULTS = 'withDefaults'
+
+const MPX_CORE = '@mpxjs/core'
+
+const BindingTypes = {
+ /**
+ * declared as a prop
+ */
+ PROPS: 'props',
+ /**
+ * a local alias of a `
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-checkbox-group.vue b/packages/web-plugin/src/runtime/components/web/mpx-checkbox-group.vue
new file mode 100644
index 0000000000..eb0b9fa007
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-checkbox-group.vue
@@ -0,0 +1,86 @@
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-checkbox.vue b/packages/web-plugin/src/runtime/components/web/mpx-checkbox.vue
new file mode 100644
index 0000000000..d2731f1002
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-checkbox.vue
@@ -0,0 +1,115 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-form.vue b/packages/web-plugin/src/runtime/components/web/mpx-form.vue
new file mode 100644
index 0000000000..788b15322f
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-form.vue
@@ -0,0 +1,87 @@
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-icon.vue b/packages/web-plugin/src/runtime/components/web/mpx-icon.vue
new file mode 100644
index 0000000000..5dbfe5ba34
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-icon.vue
@@ -0,0 +1,158 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-image.vue b/packages/web-plugin/src/runtime/components/web/mpx-image.vue
new file mode 100644
index 0000000000..f976057a04
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-image.vue
@@ -0,0 +1,113 @@
+
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-input.vue b/packages/web-plugin/src/runtime/components/web/mpx-input.vue
new file mode 100644
index 0000000000..9a0c143950
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-input.vue
@@ -0,0 +1,159 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-keep-alive.vue b/packages/web-plugin/src/runtime/components/web/mpx-keep-alive.vue
new file mode 100644
index 0000000000..5b99ea8b29
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-keep-alive.vue
@@ -0,0 +1,100 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-movable-area.vue b/packages/web-plugin/src/runtime/components/web/mpx-movable-area.vue
new file mode 100644
index 0000000000..c772364631
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-movable-area.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-movable-view.vue b/packages/web-plugin/src/runtime/components/web/mpx-movable-view.vue
new file mode 100644
index 0000000000..004c2c58f2
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-movable-view.vue
@@ -0,0 +1,308 @@
+
+
+
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-navigator.vue b/packages/web-plugin/src/runtime/components/web/mpx-navigator.vue
new file mode 100644
index 0000000000..b9e4e5e7e2
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-navigator.vue
@@ -0,0 +1,119 @@
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-picker-view-column.vue b/packages/web-plugin/src/runtime/components/web/mpx-picker-view-column.vue
new file mode 100644
index 0000000000..5218cc34f8
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-picker-view-column.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-picker-view.vue b/packages/web-plugin/src/runtime/components/web/mpx-picker-view.vue
new file mode 100644
index 0000000000..0a4e81f247
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-picker-view.vue
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-picker.vue b/packages/web-plugin/src/runtime/components/web/mpx-picker.vue
new file mode 100644
index 0000000000..5a22211928
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-picker.vue
@@ -0,0 +1,648 @@
+
+
+
+
+
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-progress.vue b/packages/web-plugin/src/runtime/components/web/mpx-progress.vue
new file mode 100644
index 0000000000..98156d820a
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-progress.vue
@@ -0,0 +1,173 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-radio-group.vue b/packages/web-plugin/src/runtime/components/web/mpx-radio-group.vue
new file mode 100644
index 0000000000..669a822984
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-radio-group.vue
@@ -0,0 +1,91 @@
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-radio.vue b/packages/web-plugin/src/runtime/components/web/mpx-radio.vue
new file mode 100644
index 0000000000..76e9530e08
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-radio.vue
@@ -0,0 +1,113 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-rich-text.vue b/packages/web-plugin/src/runtime/components/web/mpx-rich-text.vue
new file mode 100644
index 0000000000..09f4c92208
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-rich-text.vue
@@ -0,0 +1,31 @@
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-scroll-view.vue b/packages/web-plugin/src/runtime/components/web/mpx-scroll-view.vue
new file mode 100644
index 0000000000..d339788545
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-scroll-view.vue
@@ -0,0 +1,429 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-slider.vue b/packages/web-plugin/src/runtime/components/web/mpx-slider.vue
new file mode 100644
index 0000000000..2e49fd9f7d
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-slider.vue
@@ -0,0 +1,236 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-swiper-item.vue b/packages/web-plugin/src/runtime/components/web/mpx-swiper-item.vue
new file mode 100644
index 0000000000..64a939add7
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-swiper-item.vue
@@ -0,0 +1,20 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-swiper.vue b/packages/web-plugin/src/runtime/components/web/mpx-swiper.vue
new file mode 100644
index 0000000000..bf93eb441e
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-swiper.vue
@@ -0,0 +1,274 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-switch.vue b/packages/web-plugin/src/runtime/components/web/mpx-switch.vue
new file mode 100644
index 0000000000..a2f452065f
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-switch.vue
@@ -0,0 +1,173 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-tab-bar-container.vue b/packages/web-plugin/src/runtime/components/web/mpx-tab-bar-container.vue
new file mode 100644
index 0000000000..09d5ab7363
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-tab-bar-container.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-tab-bar.vue b/packages/web-plugin/src/runtime/components/web/mpx-tab-bar.vue
new file mode 100644
index 0000000000..74cd1a8274
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-tab-bar.vue
@@ -0,0 +1,87 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-text.vue b/packages/web-plugin/src/runtime/components/web/mpx-text.vue
new file mode 100644
index 0000000000..549052dbe4
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-text.vue
@@ -0,0 +1,81 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-textarea.vue b/packages/web-plugin/src/runtime/components/web/mpx-textarea.vue
new file mode 100644
index 0000000000..ae9f6cae4d
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-textarea.vue
@@ -0,0 +1,145 @@
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-video.vue b/packages/web-plugin/src/runtime/components/web/mpx-video.vue
new file mode 100755
index 0000000000..cc0c23058d
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-video.vue
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-view.vue b/packages/web-plugin/src/runtime/components/web/mpx-view.vue
new file mode 100644
index 0000000000..dffd361504
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-view.vue
@@ -0,0 +1,73 @@
+
diff --git a/packages/web-plugin/src/runtime/components/web/mpx-web-view.vue b/packages/web-plugin/src/runtime/components/web/mpx-web-view.vue
new file mode 100644
index 0000000000..d1ace84d4a
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/mpx-web-view.vue
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
diff --git a/packages/web-plugin/src/runtime/components/web/util.js b/packages/web-plugin/src/runtime/components/web/util.js
new file mode 100644
index 0000000000..a6655aa06b
--- /dev/null
+++ b/packages/web-plugin/src/runtime/components/web/util.js
@@ -0,0 +1,47 @@
+/**
+ @file web运行时组件抹平中需要用到的一些工具方法
+ */
+
+/**
+ * 处理字符串类型的宽高数值,兼容rpx
+ * @param {object | number} size 宽高
+ * @param {object} option 配置项,目前仅支持配置默认值
+ * @param {number} option.default 默认值,当传入的size有问题时返回
+ * @returns {number} 处理后的数字宽高,单位px
+ */
+export function processSize (size, option = {}) {
+ const defaultValue = option.default || 0
+ if (typeof size === 'number') {
+ return size
+ } else if (typeof size === 'string') {
+ const rs = parseInt(size, 10)
+ if (size.indexOf('rpx') !== -1) {
+ // 计算rpx折算回px
+ const width = window.screen.width
+ const finalRs = Math.floor(rs / 750 * width)
+ return finalRs
+ } else {
+ return isNaN(rs) ? defaultValue : rs
+ }
+ } else {
+ return defaultValue
+ }
+}
+
+// 判断对象类型
+export function type (n) {
+ return Object.prototype.toString.call(n).slice(8, -1)
+}
+
+export function isEmptyObject (obj) {
+ if (!obj) {
+ return true
+ }
+ // eslint-disable-next-line no-unreachable-loop
+ for (const key in obj) {
+ return false
+ }
+ return true
+}
+
+export const isBrowser = typeof window !== 'undefined'
diff --git a/packages/web-plugin/src/runtime/option-processor.d.ts b/packages/web-plugin/src/runtime/option-processor.d.ts
new file mode 100644
index 0000000000..fd17fd663c
--- /dev/null
+++ b/packages/web-plugin/src/runtime/option-processor.d.ts
@@ -0,0 +1,13 @@
+declare global {
+ namespace NodeJS {
+ interface Global {
+ [key: string]: any
+ }
+ }
+}
+
+export default function processOption (...args: any): object
+
+export function getComponent (...args: any): object
+
+export function getWxsMixin (...args: any): object
diff --git a/packages/web-plugin/src/runtime/option-processor.js b/packages/web-plugin/src/runtime/option-processor.js
new file mode 100644
index 0000000000..2ec6f83e77
--- /dev/null
+++ b/packages/web-plugin/src/runtime/option-processor.js
@@ -0,0 +1,319 @@
+/* eslint-disable */
+import { isBrowser, hasOwn } from './utils'
+import transRpxStyle from './trans-rpx-style'
+import animation from './animation'
+
+export default function processOption ({
+ option,
+ ctorType,
+ firstPage,
+ outputPath,
+ pageConfig,
+ pagesMap,
+ componentsMap,
+ tabBarMap,
+ componentGenerics,
+ genericsInfo,
+ mixin,
+ hasApp,
+ Vue,
+ VueRouter
+ }
+) {
+ if (ctorType === 'app') {
+ // 对于app中的组件需要全局注册
+ for (const componentName in componentsMap) {
+ if (hasOwn(componentsMap, componentName)) {
+ const component = componentsMap[componentName]
+ Vue.component(componentName, component)
+ }
+ }
+
+ Vue.directive('animation', animation)
+
+ Vue.filter('transRpxStyle', transRpxStyle)
+
+ const routes = []
+
+ for (const pagePath in pagesMap) {
+ if (hasOwn(pagesMap, pagePath)) {
+ const page = pagesMap[pagePath]
+ routes.push({
+ path: '/' + pagePath,
+ component: page
+ })
+ }
+ }
+
+ if (routes.length) {
+ if (firstPage) {
+ routes.push({
+ path: '/',
+ redirect: '/' + firstPage
+ })
+ }
+ const webRouteConfig = global.__mpx.config.webRouteConfig
+ global.__mpxRouter = option.router = new VueRouter({
+ ...webRouteConfig,
+ routes: routes
+ })
+ global.__mpxRouter.stack = []
+ global.__mpxRouter.needCache = null
+ global.__mpxRouter.needRemove = []
+ // 处理reLaunch中传递的url并非首页时的replace逻辑
+ global.__mpxRouter.beforeEach(function (to, from, next) {
+ let action = global.__mpxRouter.__mpxAction
+ const stack = global.__mpxRouter.stack
+
+ // 处理人为操作
+ if (!action) {
+ if (stack.length > 1 && stack[stack.length - 2].path === to.path) {
+ action = {
+ type: 'back',
+ delta: 1
+ }
+ } else {
+ action = {
+ type: 'to'
+ }
+ }
+ }
+
+ const pageInRoutes = routes.some(item => item.path === to.path)
+ if (!pageInRoutes) {
+ if (stack.length < 1) {
+ if (global.__mpxRouter.app.$options.onPageNotFound) {
+ // onPageNotFound,仅首次进入时生效
+ global.__mpxRouter.app.$options.onPageNotFound({
+ path: to.path,
+ query: to.query,
+ isEntryPage: true
+ })
+ return
+ } else {
+ console.warn(`[Mpx runtime warn]: the ${to.path} path does not exist in the application,will redirect to the home page path ${firstPage}`)
+ return next({
+ path: firstPage,
+ replace: true
+ })
+ }
+ } else {
+ let methods = ''
+ switch (action.type) {
+ case 'to':
+ methods = 'navigateTo'
+ break
+ case 'redirect':
+ methods = 'redirectTo'
+ break
+ case 'back':
+ methods = 'navigateBack'
+ break
+ case 'reLaunch':
+ methods = 'reLaunch'
+ break
+ default:
+ methods = 'navigateTo'
+ }
+ throw new Error(`${methods}:fail page "${to.path}" is not found`)
+ }
+ }
+
+ const insertItem = {
+ path: to.path
+ }
+ // 构建历史栈
+ switch (action.type) {
+ case 'to':
+ stack.push(insertItem)
+ global.__mpxRouter.needCache = insertItem
+ break
+ case 'back':
+ global.__mpxRouter.needRemove = stack.splice(stack.length - action.delta, action.delta)
+ break
+ case 'redirect':
+ global.__mpxRouter.needRemove = stack.splice(stack.length - 1, 1, insertItem)
+ global.__mpxRouter.needCache = insertItem
+ break
+ case 'switch':
+ if (!action.replaced) {
+ action.replaced = true
+ return next({
+ path: action.path,
+ replace: true
+ })
+ } else {
+ // 将非tabBar页面remove
+ let tabItem = null
+ global.__mpxRouter.needRemove = stack.filter((item) => {
+ if (tabBarMap[item.path.slice(1)]) {
+ tabItem = item
+ return false
+ }
+ return true
+ })
+ if (tabItem) {
+ global.__mpxRouter.stack = [tabItem]
+ } else {
+ global.__mpxRouter.stack = [insertItem]
+ global.__mpxRouter.needCache = insertItem
+ }
+ }
+ break
+ case 'reLaunch':
+ if (!action.replaced) {
+ action.replaced = true
+ return next({
+ path: action.path,
+ query: {
+ reLaunchCount: action.reLaunchCount
+ },
+ replace: true
+ })
+ } else {
+ global.__mpxRouter.needRemove = stack
+ global.__mpxRouter.stack = [insertItem]
+ global.__mpxRouter.needCache = insertItem
+ }
+ }
+ next()
+ })
+ // 处理visibilitychange时触发当前活跃页面组件的onshow/onhide
+ if (isBrowser) {
+ const errorHandler = function (args, fromVue) {
+ if (global.__mpxAppCbs && global.__mpxAppCbs.error && global.__mpxAppCbs.error.length) {
+ global.__mpxAppCbs.error.forEach((cb) => {
+ cb.apply(null, args)
+ })
+ } else if (fromVue) {
+ throw args[0]
+ }
+ }
+ Vue.config.errorHandler = (...args) => {
+ return errorHandler(args, true)
+ }
+ window.addEventListener('error', (event) => {
+ return errorHandler([event.error, event])
+ })
+ window.addEventListener('unhandledrejection', (event) => {
+ return errorHandler([event.reason, event])
+ })
+ document.addEventListener('visibilitychange', function () {
+ const vnode = global.__mpxRouter && global.__mpxRouter.__mpxActiveVnode
+ if (vnode && vnode.componentInstance) {
+ const currentPage = vnode.tag.endsWith('mpx-tab-bar-container') ? vnode.componentInstance.$refs.tabBarPage : vnode.componentInstance
+ if (document.hidden) {
+ if (global.__mpxAppCbs && global.__mpxAppCbs.hide) {
+ global.__mpxAppCbs.hide.forEach((cb) => {
+ cb()
+ })
+ }
+ if (currentPage) {
+ currentPage.mpxPageStatus = 'hide'
+ }
+ } else {
+ if (global.__mpxAppCbs && global.__mpxAppCbs.show) {
+ global.__mpxAppCbs.show.forEach((cb) => {
+ // todo 实现app.onShow参数
+ /* eslint-disable node/no-callback-literal */
+ cb({})
+ })
+ }
+ if (currentPage) {
+ currentPage.mpxPageStatus = 'show'
+ }
+ }
+ }
+ })
+ // 初始化length
+ global.__mpxRouter.__mpxHistoryLength = global.history.length
+ }
+ }
+
+ // 注入pinia
+ if (global.__mpxPinia) {
+ option.pinia = global.__mpxPinia
+ }
+ } else {
+ // 局部注册页面和组件中依赖的组件
+ for (const componentName in componentsMap) {
+ if (hasOwn(componentsMap, componentName)) {
+ const component = componentsMap[componentName]
+ if (!option.components) {
+ option.components = {}
+ }
+ option.components[componentName] = component
+ }
+ }
+
+ if (genericsInfo) {
+ const genericHash = genericsInfo.hash
+ global.__mpxGenericsMap[genericHash] = {}
+ Object.keys(genericsInfo.map).forEach((genericValue) => {
+ if (componentsMap[genericValue]) {
+ global.__mpxGenericsMap[genericHash][genericValue] = componentsMap[genericValue]
+ } else {
+ console.log(option)
+ console.warn(`[Mpx runtime warn]: generic value "${genericValue}" must be
+registered in parent context!`)
+ }
+ })
+ }
+
+ if (componentGenerics) {
+ option.props = option.props || {}
+ option.props.generichash = String
+ Object.keys(componentGenerics).forEach((genericName) => {
+ if (componentGenerics[genericName].default) {
+ option.props[`generic${genericName}`] = {
+ type: String,
+ default: `${genericName}default`
+ }
+ } else {
+ option.props[`generic${genericName}`] = String
+ }
+ })
+ }
+
+ if (ctorType === 'page') {
+ option.__mpxPageConfig = Object.assign({}, global.__mpxPageConfig, pageConfig)
+ }
+ if (!hasApp) {
+ option.directives = { animation }
+ option.filters = { transRpxStyle }
+ }
+ }
+
+ if (option.mixins) {
+ option.mixins.push(mixin)
+ } else {
+ option.mixins = [mixin]
+ }
+
+ if (outputPath) {
+ option.componentPath = '/' + outputPath
+ }
+ return option
+}
+
+export function getComponent (component, extendOptions) {
+ component = component.__esModule ? component.default : component
+ // eslint-disable-next-line
+ if (extendOptions) Object.assign(component, extendOptions)
+ return component
+}
+
+export function getWxsMixin (wxsModules) {
+ if (!wxsModules || !Object.keys(wxsModules).length) return {}
+ return {
+ created () {
+ Object.keys(wxsModules).forEach((key) => {
+ if (key in this) {
+ console.error(`[Mpx runtime error]: The wxs module key [${key}] exist in the component/page instance already, please check and rename it!`)
+ } else {
+ this[key] = wxsModules[key]
+ }
+ })
+ }
+ }
+}
diff --git a/packages/web-plugin/src/runtime/trans-rpx-style.js b/packages/web-plugin/src/runtime/trans-rpx-style.js
new file mode 100644
index 0000000000..34115f381f
--- /dev/null
+++ b/packages/web-plugin/src/runtime/trans-rpx-style.js
@@ -0,0 +1,44 @@
+export default function (style) {
+ const defaultTransRpxFn = function (match, $1) {
+ const rpx2vwRatio = +(100 / 750).toFixed(8)
+ return '' + ($1 * rpx2vwRatio) + 'vw'
+ }
+ const transRpxFn = global.__mpxTransRpxFn || defaultTransRpxFn
+ const parsedStyleObj = {}
+ const rpxRegExpG = /\b(\d+(\.\d+)?)rpx\b/g
+ const parseStyleText = (cssText) => {
+ const listDelimiter = /;(?![^(]*\))/g
+ const propertyDelimiter = /:(.+)/
+ if (typeof cssText === 'string') {
+ cssText.split(listDelimiter).forEach((item) => {
+ if (item) {
+ const tmp = item.split(propertyDelimiter)
+ tmp.length > 1 && (parsedStyleObj[tmp[0].trim()] = tmp[1].trim())
+ }
+ })
+ } else if (typeof cssText === 'object') {
+ if (Array.isArray(cssText)) {
+ cssText.forEach(cssItem => {
+ parseStyleText(cssItem)
+ })
+ } else {
+ Object.assign(parsedStyleObj, cssText)
+ }
+ }
+ }
+ const transRpxStyleFn = (val) => {
+ if (typeof val === 'string' && val.indexOf('rpx') > 0) {
+ return val.replace(rpxRegExpG, transRpxFn).replace(/"/g, '')
+ }
+ return val
+ }
+ if (style) {
+ style.forEach(item => {
+ parseStyleText(item)
+ for (const key in parsedStyleObj) {
+ parsedStyleObj[key] = transRpxStyleFn(parsedStyleObj[key])
+ }
+ })
+ }
+ return parsedStyleObj
+}
diff --git a/packages/web-plugin/src/runtime/utils.js b/packages/web-plugin/src/runtime/utils.js
new file mode 100644
index 0000000000..ac0786506e
--- /dev/null
+++ b/packages/web-plugin/src/runtime/utils.js
@@ -0,0 +1,7 @@
+const hasOwnProperty = Object.prototype.hasOwnProperty
+
+export function hasOwn (obj, key) {
+ return hasOwnProperty.call(obj, key)
+}
+
+export const isBrowser = typeof window !== 'undefined'
diff --git a/packages/web-plugin/src/types/compilation.d.ts b/packages/web-plugin/src/types/compilation.d.ts
new file mode 100644
index 0000000000..dd01ccdc98
--- /dev/null
+++ b/packages/web-plugin/src/types/compilation.d.ts
@@ -0,0 +1,12 @@
+import { MpxWithOptions } from '../mpx'
+import 'webpack'
+
+declare module 'webpack' {
+ interface Compilation {
+ __mpx__: MpxWithOptions
+ }
+
+ interface NormalModule {
+ wxs: boolean
+ }
+}
diff --git a/packages/web-plugin/src/types/patch.d.ts b/packages/web-plugin/src/types/patch.d.ts
new file mode 100644
index 0000000000..c4873f375f
--- /dev/null
+++ b/packages/web-plugin/src/types/patch.d.ts
@@ -0,0 +1,122 @@
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+declare module 'webpack/lib/InitFragment' {
+ export default class InitFragment {
+ static STAGE_CONSTANTS: any
+ constructor(...args: any[])
+ }
+}
+declare module 'webpack/lib/util/makeSerializable' {
+ export default function makeSerializable(...args: any[]): any
+}
+declare module 'webpack/lib/dependencies/ModuleDependency' {}
+declare module '@mpxjs/compiler/template-compiler/parser' {
+ import { CompilerResult } from "@mpxjs/compiler";
+ export default function parser(...args: any[]): CompilerResult
+}
+declare module 'webpack/lib/NullFactory' {
+ export default class NullFactory {
+ constructor()
+ }
+}
+declare module 'webpack/lib/dependencies/HarmonyImportDependencyParserPlugin' {}
+declare module 'webpack/lib/FlagEntryExportAsUsedPlugin' {
+ export default class FlagEntryExportAsUsedPlugin {
+ constructor(nsObjectUsed:boolean, explanation:string)
+ apply(compiler: any):this
+ }
+}
+declare module 'webpack/lib/FileSystemInfo' {
+ export default function FileSystemInfo(): any
+}
+
+declare module '@mpxjs/webpack-plugin/lib/dependencies/ResolveDependency' {
+ export default class ResolveDependency{
+ static Template: typeof ResolveDependencyTemplate
+ constructor(resource:string, packageName:string, issuerResource:string, range: number[])
+ }
+}
+declare class InjectDependencyTemplate {
+ constructor();
+}
+declare module '@mpxjs/webpack-plugin/lib/dependencies/InjectDependency' {
+ export default class InjectDependency {
+ static Template: typeof InjectDependencyTemplate
+ constructor(options : { content: string; index: number } )
+ }
+}
+declare class CommonJsVariableDependencyTemplate {
+ constructor();
+}
+declare module '@mpxjs/webpack-plugin/lib/dependencies/CommonJsVariableDependency' {
+ export default class CommonJsVariableDependency {
+ name: string
+ static Template: typeof CommonJsVariableDependencyTemplate
+ constructor(request: string, name: string)
+ }
+}
+declare class ReplaceDependencyTemplate {
+ constructor();
+}
+declare module '@mpxjs/webpack-plugin/lib/dependencies/ReplaceDependency' {
+ export default class ReplaceDependency {
+ static Template: typeof ReplaceDependencyTemplate
+ constructor(replacement: string, range: number[])
+ }
+}
+declare class RecordResourceMapDependencyTemplate {
+ constructor();
+}
+declare module '@mpxjs/webpack-plugin/lib/dependencies/RecordResourceMapDependency' {
+ export default class RecordResourceMapDependency {
+ static Template: typeof RecordResourceMapDependencyTemplate
+ constructor(resourcePath: string, resourceType: string, outputPath: string, packageRoot: string)
+ }
+}
+declare class RecordVueContentDependencyTemplate {
+ constructor();
+}
+declare module '@mpxjs/webpack-plugin/lib/dependencies/RecordVueContentDependency' {
+ export default class RecordVueContentDependency {
+ static Template: typeof RecordVueContentDependencyTemplate
+ constructor(resourcePath: string, content: string)
+ }
+}
+
+declare module '@mpxjs/webpack-plugin/lib/resolver/AddModePlugin' {
+ export default class AddModePlugin {
+ constructor(source: string, env: string, fileConditionRules, target)
+ apply(...args: any): void
+ }
+}
+
+declare module '@mpxjs/webpack-plugin/lib/resolver/AddEnvPlugin' {
+ export default class AddEnvPlugin {
+ constructor(source: string, mode: string, fileConditionRules, target)
+ apply(...args: any): void
+ }
+}
+
+declare interface DepConstructor {
+ new (...args: any[]): any;
+}
+
+declare class DependencyTemplate {
+ constructor();
+ apply(...args: any[]): void;
+}
+
+declare abstract class ModuleFactory {
+ create(...args: any[]): void;
+}
+
+declare module 'lru-cache' {
+ class LruCache {
+ constructor(cache: number);
+
+ get(cacheKey: string): T;
+
+ set(cacheKey: string, content: string): void;
+ }
+ export default LruCache
+}
diff --git a/packages/web-plugin/src/types/query.d.ts b/packages/web-plugin/src/types/query.d.ts
new file mode 100644
index 0000000000..fe33284dae
--- /dev/null
+++ b/packages/web-plugin/src/types/query.d.ts
@@ -0,0 +1,34 @@
+import 'loader-utils'
+declare module 'loader-utils' {
+ export interface OptionObject {
+ vue?: boolean
+ mpx?: boolean
+ app?: boolean
+ page?: boolean
+ component?: boolean
+ resolve?: boolean
+ src?: string
+ type?:
+ | 'script'
+ | 'template'
+ | 'style'
+ | 'custom'
+ | 'global'
+ | 'main'
+ | 'globalDefine'
+ | 'hot'
+ index?: string
+ lang?: string
+ raw?: string
+ componentId?: string
+ async?: boolean
+ root?: string
+ outputPath?: string
+ mpxStyleOptions?: string
+ isPage?: boolean
+ isComponent?: boolean
+ isApp?: boolean
+ mode?: string
+ packageRoot?: string
+ }
+}
diff --git a/packages/web-plugin/src/utils/get-output-path.ts b/packages/web-plugin/src/utils/get-output-path.ts
new file mode 100644
index 0000000000..d5651a9763
--- /dev/null
+++ b/packages/web-plugin/src/utils/get-output-path.ts
@@ -0,0 +1,17 @@
+import path from 'path'
+import { Options } from '../options'
+import pathHash from './path-hash'
+
+export default function getOutputPath (
+ resourcePath:string,
+ type: string,
+ options: Options,
+ { ext = '', conflictPath = '' } = {}): string {
+ const name = path.parse(resourcePath).name
+ const hash = pathHash(resourcePath)
+ const customOutputPath = options.customOutputPath
+ if (conflictPath) return conflictPath.replace(/(\.[^\\/]+)?$/, match => hash + match)
+ if (typeof customOutputPath === 'function') return customOutputPath(type, name, hash, ext).replace(/^\//, '')
+ if (type === 'component' || type === 'page') return path.join(type + 's', name + hash, 'index' + ext)
+ return path.join(type, name + hash + ext)
+}
diff --git a/packages/web-plugin/src/utils/path-hash.ts b/packages/web-plugin/src/utils/path-hash.ts
new file mode 100644
index 0000000000..31d9a77523
--- /dev/null
+++ b/packages/web-plugin/src/utils/path-hash.ts
@@ -0,0 +1,19 @@
+import hash from 'hash-sum'
+import path from 'path'
+import { Options } from '../options'
+
+export default function pathHash (
+ resourcePath: string,
+ options?: Options
+): string {
+ let hashPath = resourcePath
+ const pathHashMode = options?.pathHashMode
+ const projectRoot = options?.projectRoot || ''
+ if (pathHashMode === 'relative') {
+ hashPath = path.relative(projectRoot, resourcePath)
+ }
+ if (typeof pathHashMode === 'function') {
+ hashPath = pathHashMode(resourcePath, projectRoot) || resourcePath
+ }
+ return hash(hashPath)
+}
diff --git a/packages/web-plugin/src/vite.ts b/packages/web-plugin/src/vite.ts
new file mode 100644
index 0000000000..e46ea88c4b
--- /dev/null
+++ b/packages/web-plugin/src/vite.ts
@@ -0,0 +1,2 @@
+export * from './vite/index'
+export { default } from './vite/index'
diff --git a/packages/web-plugin/src/vite/config.ts b/packages/web-plugin/src/vite/config.ts
new file mode 100644
index 0000000000..e31daf32b6
--- /dev/null
+++ b/packages/web-plugin/src/vite/config.ts
@@ -0,0 +1,9 @@
+export type ResolvedConfig = {
+ sourceMap?: boolean
+ isProduction: boolean
+ base?: string
+}
+
+export const resolvedConfig: ResolvedConfig = {
+ isProduction: true
+}
diff --git a/packages/web-plugin/src/vite/handle-hot-update.ts b/packages/web-plugin/src/vite/handle-hot-update.ts
new file mode 100644
index 0000000000..62849219e8
--- /dev/null
+++ b/packages/web-plugin/src/vite/handle-hot-update.ts
@@ -0,0 +1,27 @@
+import { addQuery } from '@mpxjs/compile-utils'
+import { HmrContext } from 'vite'
+import { getDescriptor, setPrevDescriptor } from './utils/descriptor-cache'
+
+export default async function handleHotUpdate (ctx: HmrContext) {
+ const prevDescriptor = getDescriptor(ctx.file)
+ if (!prevDescriptor) return
+ // 有descriptor缓存的是mpx文件或者外联json文件
+ setPrevDescriptor(ctx.file, prevDescriptor)
+
+ // 改写read方法,vue内部热更新会调用
+ ctx.read = async function () {
+ // 增加type令mpx转换为一个默认的空的js并跳过vue插件转换
+ const id = addQuery(ctx.file, {
+ type: 'hot',
+ vue: true,
+ isPage: prevDescriptor?.isPage,
+ app: prevDescriptor?.app,
+ isComponent: prevDescriptor?.isComponent
+ })
+ // 插件转换mpx文件并缓存代码到vueSfc
+ await ctx.server.transformRequest(id)
+ const descriptor = getDescriptor(ctx.file)
+ // 给vue热更新返回转换后的代码,让其对比
+ return descriptor?.vueSfc || ''
+ }
+}
diff --git a/packages/web-plugin/src/vite/helper.ts b/packages/web-plugin/src/vite/helper.ts
new file mode 100644
index 0000000000..c776cb8c37
--- /dev/null
+++ b/packages/web-plugin/src/vite/helper.ts
@@ -0,0 +1,238 @@
+import {
+ addQuery,
+ genImport,
+ isUrlRequest,
+ parseRequest,
+ shallowStringify,
+ stringify
+} from '@mpxjs/compile-utils'
+import { PluginContext } from 'rollup'
+import { OPTION_PROCESSOR_PATH, TAB_BAR_PATH } from '../constants'
+import { Options } from '../options'
+import { TabBarItem } from '@mpxjs/compiler'
+import { resolvedConfig } from './config'
+import mpxGlobal from './mpx'
+import { genComponentCode } from './transformer/script'
+import { SFCDescriptor } from './utils/descriptor-cache'
+
+export const ENTRY_HELPER_CODE = '\0/vite/mpx-entry-helper'
+export const APP_HELPER_CODE = '\0/vite/mpx-app-helper'
+export const I18N_HELPER_CODE = '\0/vite/mpx-i18n-helper'
+export const TAB_BAR_PAGE_HELPER_CODE = '\0/vite/mpx-tab-bar-page-helper'
+
+export const renderPageRouteCode = (
+ _options: Options,
+ importer: string
+): string => {
+ return `export default ${stringify(
+ resolvedConfig.base + mpxGlobal.pagesMap[importer]
+ )}`
+}
+
+export const renderEntryCode = async (
+ importer: string,
+ options: Options
+): Promise => {
+ return `
+ ${genImport(addQuery(importer, { app: true }), 'App')}
+ ${genImport('@mpxjs/web-plugin/src/runtime/base.styl')}
+ ${genImport('vue', 'Vue')}
+ ${genImport('vue-router', 'VueRouter')}
+ ${genImport('@better-scroll/core', 'BScroll')}
+ ${genImport('@better-scroll/pull-down', 'PullDown')}
+ ${genImport('@better-scroll/observe-dom', 'ObserveDOM')}
+ Vue.use(VueRouter)
+ BScroll.use(ObserveDOM)
+ BScroll.use(PullDown)
+ global.BScroll = BScroll
+ new Vue({
+ el: ${options.webConfig?.el || '"#app"'},
+ render: function(h){
+ return h(App)
+ }
+ })
+ `
+}
+
+export function renderI18nCode (options: Options): string {
+ const content = []
+ const { i18n } = options
+ if (i18n) {
+ content.push(
+ genImport('vue', 'Vue'),
+ genImport('vue-i18n', 'VueI18n'),
+ genImport('vue-i18n-bridge', '{ createI18n }'),
+ genImport('@mpxjs/core', 'Mpx'),
+ 'Vue.use(VueI18n, { bridge: true })'
+ )
+ const i18nObj = { ...i18n }
+ const requestObj: Record = {}
+ const i18nKeys = ['messages', 'dateTimeFormats', 'numberFormats']
+ i18nKeys.forEach(key => {
+ const keyPath = `${key}Path` as keyof typeof i18nObj
+ if (i18nObj[keyPath]) {
+ requestObj[key] = stringify(i18nObj[keyPath])
+ delete i18nObj[keyPath]
+ }
+ })
+ Object.keys(requestObj).forEach(key => {
+ content.push(`import __mpx__i18n__${key} from ${requestObj[key]}`)
+ })
+ content.push(`const i18nCfg = ${stringify(i18nObj)}`)
+ Object.keys(requestObj).forEach(key => {
+ content.push(`i18nCfg.${key} = __mpx__i18n__${key}`)
+ })
+ content.push(
+ 'i18nCfg.legacy = false',
+ 'const i18n = createI18n(i18nCfg, VueI18n)',
+ 'Vue.use(i18n)',
+ 'Mpx.i18n = i18n'
+ )
+ }
+ return content.join('\n')
+}
+
+/**
+ * app初始化代码,主要是初始化所有的global对象
+ * @param descriptor - SFCDescriptor
+ * @returns
+ */
+export async function renderAppHelpCode (
+ options: Options,
+ descriptor: SFCDescriptor,
+ pluginContext: PluginContext
+) {
+ const { jsonConfig } = descriptor
+ const content = []
+ const tabBar = {
+ ...jsonConfig.tabBar
+ }
+ const list = tabBar.list || []
+ const newList = []
+ const needReplaceName: string[] = []
+ async function resolveTabBarItemIcon (
+ item: TabBarItem,
+ key: 'iconPath' | 'selectedIconPath'
+ ) {
+ const iconPath = item[key]
+ if (
+ iconPath &&
+ isUrlRequest(iconPath, jsonConfig.path, options.externals)
+ ) {
+ const varName = `__mpx_icon_${needReplaceName.length}__`
+ needReplaceName.push(varName)
+ const resolveIcon = await pluginContext.resolve(iconPath, jsonConfig.path)
+ if (resolveIcon) {
+ item[key] = varName
+ content.push(`import ${varName} from "${resolveIcon.id}"`)
+ }
+ }
+ }
+ for (let i = 0; i < list.length; i++) {
+ const item = {
+ ...list[i]
+ }
+ await resolveTabBarItemIcon(item, 'iconPath')
+ await resolveTabBarItemIcon(item, 'selectedIconPath')
+ newList.push(item)
+ }
+ tabBar.list = newList
+ let tabBarStr = stringify(tabBar)
+ needReplaceName.forEach(v => {
+ tabBarStr = tabBarStr.replace(`"${v}"`, v)
+ })
+ content.push(
+ `global.__networkTimeout = ${stringify(jsonConfig.networkTimeout)}`,
+ `global.__style = ${stringify(jsonConfig.style || 'v1')}`,
+ `global.__mpxPageConfig = ${stringify(jsonConfig.window || {})}`,
+ `global.__tabBar = ${tabBarStr}`,
+ `global.currentSrcMode = "${options.srcMode}"`,
+ 'global.getApp = function(){ return {} }',
+ `global.getCurrentPages = function(){
+ if(!global.__mpxRouter) return []
+ // @ts-ignore
+ return global.__mpxRouter.stack.map(item => {
+ let page
+ const vnode = item.vnode
+ if(vnode && vnode.componentInstance) {
+ page = vnode.tag.endsWith('mpx-tab-bar-container') ? vnode.componentInstance.$refs.tabBarPage : vnode.componentInstance
+ }
+ return page || { route: item.path.slice(1) }
+ })
+ }`,
+ 'global.__mpxGenericsMap = {}'
+ )
+ return content.join('\n')
+}
+
+/**
+ * TabBar,mpx-tab-bar-container依赖global.__tabBarPagesMap
+ * @param options -
+ * @param descriptor -
+ * @param pluginContext -
+ * @returns
+ */
+export const renderTabBarPageCode = async (
+ _options: Options,
+ descriptor: SFCDescriptor,
+ pluginContext: PluginContext
+): Promise => {
+ const customBarPath = './custom-tab-bar/index?isComponent'
+ const tabBars: string[] = []
+ const { filename, jsonConfig, tabBarMap, localPagesMap } = descriptor
+ const { tabBar } = jsonConfig
+
+ const tabBarPagesMap: Record = {}
+
+ const emitWarning = (msg: string) => {
+ pluginContext.warn('[script processor]: ' + msg)
+ }
+
+ if (tabBar && tabBarMap) {
+ const varName = '__mpxTabBar'
+ let tabBarPath = TAB_BAR_PATH
+ if (tabBar.custom) {
+ const customBarPathResolved = await pluginContext.resolve(
+ customBarPath,
+ filename
+ )
+ tabBarPath = customBarPathResolved?.id || TAB_BAR_PATH
+ }
+ tabBars.push(genImport(tabBarPath, varName))
+ tabBarPagesMap['mpx-tab-bar'] = genComponentCode(varName, tabBarPath)
+ Object.keys(tabBarMap).forEach((tarbarName, index) => {
+ const tabBarId = localPagesMap[tarbarName].resource
+ if (tabBarId) {
+ const varName = `__mpx_tabBar__${index}`
+ const { queryObj: query } = parseRequest(tabBarId)
+ const async = !!query.async
+ !async && tabBars.push(genImport(tabBarId, varName))
+ tabBarPagesMap[tarbarName] = genComponentCode(
+ varName,
+ tabBarId,
+ {
+ async
+ },
+ {
+ __mpxPageroute: tarbarName
+ }
+ )
+ } else {
+ emitWarning(
+ `TabBar page path ${tarbarName} is not exist in local page map, please check!`
+ )
+ }
+ })
+ }
+
+ const content = [
+ genImport('vue', 'Vue'),
+ genImport(OPTION_PROCESSOR_PATH, 'processOption, { getComponent }'),
+ tabBars.join('\n'),
+ 'global.__tabBar && Vue.observable(global.__tabBar)',
+ tabBarPagesMap &&
+ `// @ts-ignore
+ global.__tabBarPagesMap = ${shallowStringify(tabBarPagesMap)}`
+ ]
+ return content.join('\n')
+}
diff --git a/packages/web-plugin/src/vite/index.ts b/packages/web-plugin/src/vite/index.ts
new file mode 100644
index 0000000000..fccace0e50
--- /dev/null
+++ b/packages/web-plugin/src/vite/index.ts
@@ -0,0 +1,163 @@
+import { parseRequest, stringify, stringifyObject } from '@mpxjs/compile-utils'
+import createVuePlugin from '@vitejs/plugin-vue2'
+import { createFilter, Plugin, UserConfig } from 'vite'
+import { Options, processOptions } from '../options'
+import { resolvedConfig } from './config'
+import handleHotUpdate from './handle-hot-update'
+import {
+ APP_HELPER_CODE,
+ I18N_HELPER_CODE,
+ renderAppHelpCode,
+ renderI18nCode,
+ renderTabBarPageCode,
+ TAB_BAR_PAGE_HELPER_CODE
+} from './helper'
+import mpxGlobal from './mpx'
+import {
+ customExtensionsPlugin,
+ esbuildCustomExtensionsPlugin
+} from './plugins/add-extensions-plugin'
+import { createMpxOutSideJsPlugin } from './plugins/outside-js'
+import { createResolveEntryPlugin } from './plugins/resolve-entry-plugin'
+import { createSplitPackageChunkPlugin } from './plugins/split-package-chunk-plugin'
+import { createWxsPlugin } from './plugins/wxs-plugin'
+import { transformMain } from './transformer/main'
+import { transformStyle } from './transformer/style'
+import { getDescriptor } from './utils/descriptor-cache'
+
+function createMpxWebPlugin(options: Options, userConfig?: UserConfig): Plugin {
+ const { include, exclude } = options
+ const filter = createFilter(include, exclude)
+
+ return {
+ name: 'vite:mpx',
+
+ config() {
+ return {
+ ...userConfig,
+ define: {
+ global: 'globalThis', // polyfill node global
+ 'process.env.NODE_ENV': stringify(
+ resolvedConfig.isProduction ? '"production"' : '"development"'
+ ),
+ ...userConfig?.define,
+ ...stringifyObject(options.defs)
+ }
+ }
+ },
+
+ configResolved(c) {
+ Object.assign(resolvedConfig, {
+ base: c.base,
+ sourceMap: c.command === 'build' ? !!c.build.sourcemap : true,
+ isProduction: c.isProduction
+ })
+ },
+
+ handleHotUpdate(ctx) {
+ return handleHotUpdate(ctx)
+ },
+
+ async resolveId(id) {
+ if (
+ id === APP_HELPER_CODE ||
+ id === I18N_HELPER_CODE ||
+ id === TAB_BAR_PAGE_HELPER_CODE
+ ) {
+ return id
+ }
+ },
+
+ load(id) {
+ if (id === APP_HELPER_CODE && mpxGlobal.entry) {
+ const { resourcePath: filename } = parseRequest(mpxGlobal.entry)
+ const descriptor = getDescriptor(filename)
+ if (descriptor) {
+ return renderAppHelpCode(options, descriptor, this)
+ }
+ }
+ if (id === TAB_BAR_PAGE_HELPER_CODE && mpxGlobal.entry) {
+ const { resourcePath: filename } = parseRequest(mpxGlobal.entry)
+ const descriptor = getDescriptor(filename)
+ if (descriptor) {
+ return renderTabBarPageCode(options, descriptor, this)
+ }
+ }
+ if (id === I18N_HELPER_CODE) {
+ return renderI18nCode(options)
+ }
+ },
+
+ async transform(code, id) {
+ const { queryObj: query, resourcePath: filename } = parseRequest(id)
+ if (!filter(filename)) return
+ if (!!query.resolve) return
+ if (query.vue === undefined) {
+ // mpx file => vue file
+ return await transformMain(code, filename, query, options, this)
+ } else {
+ if (query.type === 'style') {
+ // mpx style => vue style
+ const descriptor = getDescriptor(filename)
+ if (descriptor) {
+ return await transformStyle(
+ code,
+ filename,
+ descriptor,
+ options,
+ this
+ )
+ }
+ }
+ if (query.type === 'hot') {
+ // 来自于热更新的请求,转换新的代码并缓存vueSfc到descriptor
+ await transformMain(code, filename, query, options, this)
+ return 'export default {}'
+ }
+ }
+ }
+ }
+}
+
+export default function mpx (options: Partial = {}): Plugin[] {
+ const baseOptions = processOptions({ ...options })
+ const { mode = '', env = '', fileConditionRules } = baseOptions
+ const customExtensions = [mode, env, env && `${mode}.${env}`].filter(Boolean)
+ const plugins = [
+ // split subpackage chunk
+ createSplitPackageChunkPlugin(),
+ // add custom extensions
+ customExtensionsPlugin({
+ include: /@mpxjs|\.mpx/,
+ fileConditionRules,
+ extensions: customExtensions
+ }),
+ // ensure mpx entry point
+ createResolveEntryPlugin(baseOptions),
+ // wxs => js
+ createWxsPlugin(),
+ // 外联js/ts增加globalDefine
+ createMpxOutSideJsPlugin(),
+ // mpx => vue
+ createMpxWebPlugin(baseOptions, {
+ optimizeDeps: {
+ esbuildOptions: {
+ plugins: [
+ // prebuild for addExtensions
+ esbuildCustomExtensionsPlugin({
+ include: /@mpxjs|api-proxy|core/,
+ fileConditionRules,
+ extensions: customExtensions
+ })
+ ]
+ }
+ }
+ }),
+ // vue support for mpxjs/rumtime
+ createVuePlugin({
+ include: /\.vue|\.mpx$/
+ })
+ ]
+
+ return plugins
+}
diff --git a/packages/web-plugin/src/vite/mpx.ts b/packages/web-plugin/src/vite/mpx.ts
new file mode 100644
index 0000000000..b08ce47b41
--- /dev/null
+++ b/packages/web-plugin/src/vite/mpx.ts
@@ -0,0 +1,15 @@
+interface Mpx {
+ entry?: string
+ pagesMap: Record
+ componentsMap: Record
+ pagesEntryMap: Record
+}
+
+const mpx: Mpx = {
+ entry: undefined,
+ pagesMap: {},
+ componentsMap: {},
+ pagesEntryMap: {}
+}
+
+export default mpx
diff --git a/packages/web-plugin/src/vite/plugins/add-extensions-plugin.ts b/packages/web-plugin/src/vite/plugins/add-extensions-plugin.ts
new file mode 100644
index 0000000000..06f2592efb
--- /dev/null
+++ b/packages/web-plugin/src/vite/plugins/add-extensions-plugin.ts
@@ -0,0 +1,75 @@
+import { matchCondition, parseRequest } from '@mpxjs/compile-utils'
+import { Plugin as EsbuildPlugin } from 'esbuild'
+import fs from 'fs'
+import path from 'path'
+import { createFilter, Plugin } from 'vite'
+import { Options } from '../../options'
+
+export interface CustomExtensionsOptions {
+ include: RegExp
+ extensions: string[]
+ fileConditionRules: Options['fileConditionRules']
+}
+
+/**
+ * generate file path with mode
+ * @param originPath - path/to/index.js
+ * @param extendsion - string
+ * @returns path/to/index.extendsion.js
+ */
+function genExtensionsFilePath (filename: string, extendsion: string): string {
+ const parseResult = path.parse(filename)
+ return path.format({
+ ...parseResult,
+ name: `${parseResult.name}.${extendsion}`,
+ base: undefined
+ })
+}
+
+export function esbuildCustomExtensionsPlugin(
+ options: CustomExtensionsOptions
+): EsbuildPlugin {
+ return {
+ name: 'esbuild:mpx-custom-estensions',
+ setup(build) {
+ build.onLoad({ filter: options.include }, async args => {
+ if (!matchCondition(args.path, options.fileConditionRules)) return
+ for (const extendsion of options.extensions) {
+ try {
+ const filePath = genExtensionsFilePath(args.path, extendsion)
+ await fs.promises.access(filePath)
+ return {
+ contents: await fs.promises.readFile(filePath, 'utf-8')
+ }
+ } catch {}
+ }
+ })
+ }
+ }
+}
+
+/**
+ * add custom extensions plugin
+ * @param options - options
+ * @returns vite plugin options
+ */
+export function customExtensionsPlugin (
+ options: CustomExtensionsOptions
+): Plugin {
+ const filter = createFilter(options.include)
+ return {
+ name: 'vite:mpx-custom-estensions',
+ async load (id) {
+ if (!filter(id) || !matchCondition(id, options.fileConditionRules)) return
+ const { resourcePath: filename, queryObj: query } = parseRequest(id)
+ if (query.vue) return
+ for (const extendsion of options.extensions) {
+ try {
+ const filePath = genExtensionsFilePath(filename, extendsion)
+ await fs.promises.access(filePath)
+ return await fs.promises.readFile(filePath, 'utf-8')
+ } catch {}
+ }
+ }
+ }
+}
diff --git a/packages/web-plugin/src/vite/plugins/outside-js.ts b/packages/web-plugin/src/vite/plugins/outside-js.ts
new file mode 100644
index 0000000000..ca66e9d689
--- /dev/null
+++ b/packages/web-plugin/src/vite/plugins/outside-js.ts
@@ -0,0 +1,33 @@
+import { parseRequest, stringify } from '@mpxjs/compile-utils'
+import MagicString from 'magic-string'
+import { createFilter, Plugin } from 'vite'
+import { resolvedConfig } from '../config'
+import { getDescriptor } from '../utils/descriptor-cache'
+
+/**
+ * 给外联的js加上global配置
+ * @returns
+ */
+export function createMpxOutSideJsPlugin (): Plugin {
+ const filter = createFilter([/\.(js|ts)$/])
+ return {
+ name: 'vite:mpx-outside-js',
+ async transform (code, id) {
+ const { resourcePath: filename } = parseRequest(id)
+ if (!filter(filename)) return
+ const descriptor = getDescriptor(filename)
+ if (!descriptor) return
+ const s = new MagicString(code)
+ !resolvedConfig.isProduction &&
+ s.prepend(`global.currentResource = ${stringify(filename)}\n`)
+ s.prepend(`global.currentModuleId = ${stringify(descriptor.id)}\n`)
+ return {
+ code: s.toString(),
+ map: s.generateMap({
+ file: filename + '.map',
+ source: filename
+ })
+ }
+ }
+ }
+}
diff --git a/packages/web-plugin/src/vite/plugins/resolve-entry-plugin.ts b/packages/web-plugin/src/vite/plugins/resolve-entry-plugin.ts
new file mode 100644
index 0000000000..2eb69593d8
--- /dev/null
+++ b/packages/web-plugin/src/vite/plugins/resolve-entry-plugin.ts
@@ -0,0 +1,66 @@
+import { createFilter, Plugin } from 'vite'
+import { Options } from '../../options'
+import { addQuery, parseRequest } from '@mpxjs/compile-utils'
+import { ENTRY_HELPER_CODE, renderEntryCode, renderPageRouteCode } from '../helper'
+import mpxGlobal from '../mpx'
+
+/**
+ * 推断mpx入口文件并记录的插件
+ * @param options - Options
+ * @returns
+ */
+export function createResolveEntryPlugin (options: Options): Plugin {
+ const filter = createFilter([/\.mpx$/])
+ return {
+ name: 'vite:mpx-resolve-entry',
+ enforce: 'pre',
+ async resolveId (source, importer, options) {
+ const { queryObj: query, resourcePath: filename } = parseRequest(source)
+ if (!filter(filename)) return
+ if (
+ query.resolve === undefined &&
+ query.vue === undefined &&
+ query.app === undefined &&
+ query.isPage === undefined &&
+ query.isComponent === undefined
+ ) {
+ // entry mpx
+ const resolution = await this.resolve(source, importer, {
+ skipSelf: true,
+ ...options
+ })
+ if (resolution) {
+ if (mpxGlobal.entry === undefined) {
+ mpxGlobal.entry = resolution.id
+ }
+ if (mpxGlobal.entry === resolution.id) {
+ return ENTRY_HELPER_CODE
+ }
+ }
+ }
+ if (query.resolve) {
+ const resolution = await this.resolve(source, importer, {
+ skipSelf: true,
+ ...options
+ })
+ if (resolution) {
+ // 跳过vue-plugin
+ return addQuery(resolution.id, {
+ raw: true
+ })
+ }
+ }
+ },
+ load (id) {
+ if (id === ENTRY_HELPER_CODE && mpxGlobal.entry) {
+ return renderEntryCode(mpxGlobal.entry, options)
+ }
+ const { resourcePath: filename, queryObj: query } = parseRequest(id)
+ if (!filter(filename)) return
+ if (!!query.resolve) {
+ // 强制改raw
+ return renderPageRouteCode(options, filename)
+ }
+ }
+ }
+}
diff --git a/packages/web-plugin/src/vite/plugins/split-package-chunk-plugin.ts b/packages/web-plugin/src/vite/plugins/split-package-chunk-plugin.ts
new file mode 100644
index 0000000000..10d9fd97f3
--- /dev/null
+++ b/packages/web-plugin/src/vite/plugins/split-package-chunk-plugin.ts
@@ -0,0 +1,65 @@
+import { ManualChunksOption } from 'rollup'
+import { Plugin } from 'vite'
+import mpxGlobal from '../mpx'
+import { getDescriptor } from '../utils/descriptor-cache'
+
+/**
+ * 将分包分离到额外的chunk里
+ * @returns
+ */
+function createSplitPackageChunk () {
+ const manualChunksOption: ManualChunksOption = (id: string) => {
+ if (/plugin-vue2:normalizer/.test(id)) {
+ // 强制将normalizer分到vendor里去,否则会引起TDZ
+ return 'vendor'
+ }
+ if (mpxGlobal.entry) {
+ const descriptor = getDescriptor(mpxGlobal.entry)
+ if (descriptor) {
+ const { jsonConfig } = descriptor
+ const { subpackages = [] } = jsonConfig
+ for (const { root } of subpackages) {
+ if (root && (id.includes(root))) {
+ return root
+ }
+ }
+ }
+ }
+ }
+ return manualChunksOption
+}
+
+export function createSplitPackageChunkPlugin (): Plugin {
+ return {
+ name: 'vite:mpx-split-package-chunk',
+ config (config) {
+ const output = config?.build?.rollupOptions?.output
+ if (output) {
+ const outputs = Array.isArray(output) ? output : [output]
+ for (const output of outputs) {
+ const splitPackageChunk = createSplitPackageChunk()
+ if (output && output.manualChunks) {
+ if (typeof output.manualChunks === 'function') {
+ const userManualChunks = output.manualChunks
+ output.manualChunks = (...args) => {
+ return userManualChunks(...args) ?? splitPackageChunk(...args)
+ }
+ }
+ } else {
+ output.manualChunks = splitPackageChunk
+ }
+ }
+ } else {
+ return {
+ build: {
+ rollupOptions: {
+ output: {
+ manualChunks: createSplitPackageChunk()
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/web-plugin/src/vite/plugins/wxs-plugin.ts b/packages/web-plugin/src/vite/plugins/wxs-plugin.ts
new file mode 100644
index 0000000000..02ab5400db
--- /dev/null
+++ b/packages/web-plugin/src/vite/plugins/wxs-plugin.ts
@@ -0,0 +1,21 @@
+import { createFilter, Plugin, transformWithEsbuild } from 'vite'
+import { parseRequest } from '@mpxjs/compile-utils'
+
+/**
+ * wxs文件支持
+ * @returns
+ */
+export function createWxsPlugin (): Plugin {
+ const filter = createFilter([/\.wxs$/])
+ return {
+ name: 'vite:mpx-wxs',
+ async transform (code, id) {
+ const { resourcePath: filename } = parseRequest(id)
+ if (!filter(filename)) return
+ return await transformWithEsbuild(code, '', {
+ format: 'esm',
+ sourcefile: filename
+ })
+ }
+ }
+}
diff --git a/packages/web-plugin/src/vite/transformer/json.ts b/packages/web-plugin/src/vite/transformer/json.ts
new file mode 100644
index 0000000000..3944cbdd40
--- /dev/null
+++ b/packages/web-plugin/src/vite/transformer/json.ts
@@ -0,0 +1,35 @@
+import { TransformPluginContext } from 'rollup'
+import { Options } from '../../options'
+import { jsonProcess } from '../../processor/json-process'
+import { jsonCompiler } from '@mpxjs/compiler'
+import mpx from '../mpx'
+import { SFCDescriptor } from '../utils/descriptor-cache'
+import { proxyPluginContext } from '@mpxjs/plugin-proxy'
+
+export async function processJSON (
+ descriptor: SFCDescriptor,
+ options: Options,
+ pluginContext: TransformPluginContext
+): Promise {
+ const jsonConfig = (descriptor.jsonConfig = await jsonCompiler.parse(
+ descriptor,
+ descriptor.filename,
+ proxyPluginContext(pluginContext),
+ options
+ ))
+ try {
+ const jsonResult = await jsonProcess({
+ jsonConfig,
+ pluginContext,
+ context: jsonConfig.path || descriptor.filename,
+ options,
+ mode: 'vite',
+ mpx
+ })
+ descriptor.localPagesMap = jsonResult.localPagesMap
+ descriptor.localComponentsMap = jsonResult.localComponentsMap
+ descriptor.tabBarMap = jsonResult.tabBarMap
+ } catch (error) {
+ pluginContext.error(`[mpx] process json error: ${error}`)
+ }
+}
diff --git a/packages/web-plugin/src/vite/transformer/main.ts b/packages/web-plugin/src/vite/transformer/main.ts
new file mode 100644
index 0000000000..a08b7955cd
--- /dev/null
+++ b/packages/web-plugin/src/vite/transformer/main.ts
@@ -0,0 +1,44 @@
+import { OptionObject } from 'loader-utils'
+import { TransformPluginContext, TransformResult } from 'rollup'
+import { Options } from '../../options'
+import { createDescriptor } from '../utils/descriptor-cache'
+import { processJSON } from './json'
+import { genScriptBlock, transformScript } from './script'
+import { genStylesBlock } from './style'
+import { genTemplateBlock } from './template'
+
+export async function transformMain (
+ code: string,
+ filename: string,
+ query: OptionObject,
+ options: Options,
+ pluginContext: TransformPluginContext
+): Promise {
+ const descriptor = createDescriptor(filename, code, query, options)
+ if (descriptor) {
+ // set pages/component to descriptor
+ await processJSON(descriptor, options, pluginContext)
+ // generate template block, delay transform template
+ const templateBlock = await genTemplateBlock(
+ descriptor,
+ options,
+ pluginContext
+ )
+ // transform script
+ const { code, map } = await transformScript(descriptor, options, pluginContext)
+ // generate script block
+ const scriptBlock = await genScriptBlock(descriptor, code)
+ // generate styles block, delay transform style
+ const stylesBlock = await genStylesBlock(descriptor)
+ const vueSfc = genVueSfc(templateBlock, scriptBlock, stylesBlock)
+ if (query.type === 'hot') descriptor.vueSfc = vueSfc
+ return {
+ code: vueSfc,
+ map: map
+ }
+ }
+}
+
+function genVueSfc (...args: { output: string }[]) {
+ return args.map(v => v.output).join()
+}
diff --git a/packages/web-plugin/src/vite/transformer/script.ts b/packages/web-plugin/src/vite/transformer/script.ts
new file mode 100644
index 0000000000..cc94b95a2b
--- /dev/null
+++ b/packages/web-plugin/src/vite/transformer/script.ts
@@ -0,0 +1,290 @@
+import {
+ genComponentTag,
+ genImport,
+ omit,
+ parseRequest,
+ shallowStringify,
+ stringify
+} from '@mpxjs/compile-utils'
+import { scriptSetupCompiler } from '@mpxjs/compiler'
+import MagicString from 'magic-string'
+import { SourceMap, TransformPluginContext } from 'rollup'
+import { Options } from 'src/options'
+import remapping, { SourceMapInput } from '@ampproject/remapping'
+import { transformWithEsbuild } from 'vite'
+import { OPTION_PROCESSOR_PATH, TAB_BAR_CONTAINER_PATH } from '../../constants'
+import { resolvedConfig } from '../config'
+import {
+ APP_HELPER_CODE,
+ I18N_HELPER_CODE,
+ TAB_BAR_PAGE_HELPER_CODE
+} from '../helper'
+import { setDescriptor, SFCDescriptor } from '../utils/descriptor-cache'
+
+export const genComponentCode = (
+ varName: string,
+ resource: string,
+ { async = false } = {},
+ params: unknown = {}
+): string => {
+ if (!async) {
+ return `getComponent(${varName}, ${stringify(params)})`
+ } else {
+ return `() => import(${stringify(resource)}).then(${varName} =>
+ getComponent(${varName}.default, ${stringify(params)})
+ )`
+ }
+}
+
+/**
+ * transform mpx script
+ * @param code - mpx script content
+ * @param descriptor - SFCDescriptor
+ * @param options - ResolvedOptions
+ * @param pluginContext - TransformPluginContext
+ * @returns script content
+ */
+export async function transformScript (
+ descriptor: SFCDescriptor,
+ options: Options,
+ pluginContext: TransformPluginContext
+): Promise<{
+ code: string
+ map?: SourceMap
+}> {
+ const {
+ id: componentId,
+ filename,
+ app,
+ isPage,
+ jsonConfig,
+ script,
+ wxsModuleMap,
+ wxsContentMap,
+ tabBarMap,
+ builtInComponentsMap,
+ genericsInfo,
+ localPagesMap,
+ localComponentsMap
+ } = descriptor
+
+ if (!script?.content) {
+ return {
+ code: ''
+ }
+ }
+
+ const mappings: SourceMapInput[] = []
+ const ctorType = app ? 'app' : isPage ? 'page' : 'component'
+ const { i18n } = options
+ const componentGenerics = jsonConfig.componentGenerics
+ const pagesMap: Record = {}
+ const componentsMap: Record = {}
+
+ if (script.setup) {
+ const res = scriptSetupCompiler(
+ script,
+ descriptor.app ? 'app' : descriptor.isPage ? 'page' : '',
+ descriptor.filename
+ )
+ script.content = res.content
+ mappings.push(res.map as SourceMapInput)
+ }
+
+ const s = new MagicString(script.content)
+
+ if (script.src) {
+ s.prepend(`${genImport(script.src)}\n`)
+ const resolvedId = await pluginContext.resolve(script.src, filename)
+ if (resolvedId?.id) setDescriptor(resolvedId.id, descriptor)
+ }
+
+ !resolvedConfig.isProduction &&
+ s.prepend(`global.currentResource = ${stringify(filename)}\n`)
+ s.prepend(`global.currentModuleId = ${stringify(descriptor.id)}\n`)
+
+ // import page by page json config
+ Object.keys(localPagesMap).forEach((pageName, index) => {
+ const pageCfg = localPagesMap[pageName]
+ const varName = `__mpx__page__${index}`
+ const isTabBar = tabBarMap && tabBarMap[pageName]
+ const newPagePath = isTabBar ? TAB_BAR_CONTAINER_PATH : pageCfg.resource
+ const async = pageCfg.async
+ !async && s.prepend(`${genImport(newPagePath, varName)}\n`)
+ pagesMap[pageName] = genComponentCode(
+ varName,
+ newPagePath,
+ {
+ async
+ },
+ isTabBar
+ ? { __mpxBuiltIn: true }
+ : {
+ __mpxPageRoute: pageName
+ }
+ )
+ })
+ // import component by component json config
+ Object.keys(localComponentsMap).forEach((componentName, index) => {
+ const componentCfg: {
+ resource: string
+ async: boolean
+ } = localComponentsMap[componentName]
+ const componentId = componentCfg.resource
+ const varName = `__mpx__component__${index}`
+ const async = componentCfg.async
+ !async && s.prepend(`${genImport(componentId, varName)}\n`)
+ componentsMap[componentName] = genComponentCode(varName, componentId, {
+ async
+ })
+ })
+
+ // import runtime component
+ Object.keys(builtInComponentsMap).forEach((componentName, index) => {
+ const componentCfg = builtInComponentsMap[componentName]
+ const varName = `__mpx__builtInComponent__${index}`
+ s.prepend(`${genImport(componentCfg.resource, varName)}\n`)
+ componentsMap[componentName] = genComponentCode(
+ varName,
+ componentCfg.resource,
+ {},
+ { __mpxBuiltIn: true }
+ )
+ })
+
+ s.prepend(
+ `${genImport(
+ OPTION_PROCESSOR_PATH,
+ 'processOption, { getComponent, getWxsMixin }'
+ )}\n`
+ )
+
+ if (i18n) {
+ s.prepend(`${genImport(I18N_HELPER_CODE, '')}\n`)
+ }
+
+ if (app) {
+ s.prepend(
+ `${genImport(APP_HELPER_CODE)}
+ ${genImport(TAB_BAR_PAGE_HELPER_CODE)}
+ ${genImport('vue', 'Vue')}
+ ${genImport('vue-router', 'VueRouter')}\n`
+ )
+ }
+
+ // after source code
+ s.append('\nconst wxsModules = {}\n')
+
+ if (wxsModuleMap) {
+ const wxsModuleKeys = Object.keys(wxsModuleMap)
+ for (let i = 0; i < wxsModuleKeys.length; i++) {
+ const key = wxsModuleKeys[i]
+ const wxsModuleId = wxsModuleMap[key]
+ // inline wxs module, transform to iife
+ if (wxsModuleId.startsWith('~')) {
+ const mpxWxsPath = wxsModuleId.split('!=!')[1]
+ const { resourcePath: filename, queryObj: query } =
+ parseRequest(mpxWxsPath)
+ const wxsContent = wxsContentMap[`${filename}~${query.wxsModule}`]
+ if (wxsContent) {
+ const varName = `__mpx__wxs__${i}`
+ const result = await transformWithEsbuild(wxsContent, '', {
+ globalName: varName,
+ format: 'iife'
+ })
+ s.append(`${result.code}\n`)
+ s.append(`wxsModules.${key} = ${varName}\n`)
+ }
+ } else {
+ // wxs file, tranfrom to esm with wxsPlugin
+ const varName = `__mpx__wxs__${i}`
+ s.append(`${genImport(wxsModuleId, varName)}\n`)
+ s.append(`wxsModules.${key} = ${varName}\n`)
+ }
+ }
+ }
+
+ s.append(
+ `const currentOption = global.__mpxOptionsMap[${stringify(
+ descriptor.id
+ )}]\n`
+ )
+
+ s.append(
+ `export default processOption({
+ option: currentOption,
+ ctorType: ${stringify(ctorType)},
+ firstPage: ${stringify(Object.keys(localPagesMap)[0])},
+ outputPath: ${stringify(componentId)},
+ pageConfig: ${stringify(
+ isPage
+ ? omit(jsonConfig, ['usingComponents', 'style', 'singlePage'])
+ : {}
+ )},
+ pagesMap: ${shallowStringify(pagesMap)},
+ componentsMap: ${shallowStringify(componentsMap)},
+ tabBarMap: ${stringify(tabBarMap)},
+ componentGenerics: ${stringify(componentGenerics)},
+ genericsInfo: ${stringify(genericsInfo)},
+ mixin: getWxsMixin(wxsModules),
+ ...${app ? `{ Vue: Vue, VueRouter: VueRouter }` : '{}'}
+ })\n`
+ )
+
+ // transform ts
+ if (script?.attrs.lang === 'ts' && !script.src && !script.setup) {
+ const result = transformWithEsbuild(
+ s.toString(),
+ filename,
+ { loader: 'ts' },
+ s.generateMap({
+ file: filename + '.map',
+ source: filename
+ })
+ )
+ return result
+ }
+
+ let index = 0
+ return {
+ code: s.toString(),
+ map: remapping(
+ [
+ s.generateMap({
+ file: filename + '.map',
+ source: filename
+ }) as SourceMapInput
+ ],
+ () => {
+ return mappings[index++] || null
+ },
+ true
+ ) as SourceMap
+ }
+}
+
+/**
+ * generate script block and transform script content
+ * @param descriptor - SFCDescriptor
+ * @param options - ResolvedOptions
+ * @param pluginContext - TransformPluginContext
+ */
+export async function genScriptBlock (
+ descriptor: SFCDescriptor,
+ code: string
+): Promise<{ output: string }> {
+ return {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ output: genComponentTag(descriptor.script!, {
+ attrs (script: { attrs: { src?: string; setup?: boolean } }) {
+ const attrs = Object.assign({}, script.attrs)
+ delete attrs.src
+ delete attrs.setup
+ return attrs
+ },
+ content () {
+ return code
+ }
+ })
+ }
+}
diff --git a/packages/web-plugin/src/vite/transformer/style.ts b/packages/web-plugin/src/vite/transformer/style.ts
new file mode 100644
index 0000000000..c22330f540
--- /dev/null
+++ b/packages/web-plugin/src/vite/transformer/style.ts
@@ -0,0 +1,48 @@
+import { genComponentTag } from '@mpxjs/compile-utils'
+import { styleCompiler } from '@mpxjs/compiler'
+import { proxyPluginContext } from '@mpxjs/plugin-proxy'
+import { TransformPluginContext } from 'rollup'
+import { Options } from '../../options'
+import { TransformResult } from 'vite'
+import pathHash from '../../utils/path-hash'
+import { SFCDescriptor } from '../utils/descriptor-cache'
+import { resolvedConfig } from '../config'
+import mpx from '../mpx'
+
+/**
+ * transform style
+ * @param code - style code
+ * @param filename - filename
+ * @param descriptor - SFCDescriptor
+ * @param options - ResolvedOptions
+ * @param pluginContext - TransformPluginContext
+ */
+export async function transformStyle (
+ code: string,
+ filename: string,
+ descriptor: SFCDescriptor,
+ options: Options,
+ pluginContext: TransformPluginContext
+): Promise {
+ return styleCompiler.transform(code, proxyPluginContext(pluginContext), {
+ sourceMap: resolvedConfig.sourceMap,
+ map: pluginContext.getCombinedSourcemap(),
+ resource: filename,
+ mpx: {
+ ...mpx,
+ ...options,
+ pathHash: pathHash,
+ isApp: descriptor.app
+ }
+ })
+}
+
+/**
+ * generate style block
+ * @param descriptor - SFCDescriptor
+ * @returns
+ */
+export function genStylesBlock (descriptor: SFCDescriptor): { output: string } {
+ const { styles } = descriptor
+ return { output: styles.map(style => genComponentTag(style)).join('\n') }
+}
diff --git a/packages/web-plugin/src/vite/transformer/template.ts b/packages/web-plugin/src/vite/transformer/template.ts
new file mode 100644
index 0000000000..2f61aa24fa
--- /dev/null
+++ b/packages/web-plugin/src/vite/transformer/template.ts
@@ -0,0 +1,80 @@
+import { genComponentTag } from '@mpxjs/compile-utils'
+import { TransformPluginContext } from 'rollup'
+import { TransformResult } from 'vite'
+import { Options } from '../../options'
+import { SFCDescriptor } from '../utils/descriptor-cache'
+import { templateProcess } from '../../processor/template-process'
+
+/**
+ * transform mpx template to vue template
+ * @param code - mpx template code
+ * @param filename - filename
+ * @param descriptor - SFCDescriptor
+ * @param options - ResolvedOptions
+ * @param pluginContext - TransformPluginContext
+ */
+
+export async function transformTemplate (
+ descriptor: SFCDescriptor,
+ options: Options,
+ pluginContext?: TransformPluginContext
+): Promise {
+ const { id, filename, jsonConfig, app, template } = descriptor
+ let builtInComponentsMap: SFCDescriptor['builtInComponentsMap'] = {}
+ let genericsInfo: SFCDescriptor['genericsInfo']
+ let wxsContentMap: SFCDescriptor['wxsContentMap'] = {}
+ let wxsModuleMap: SFCDescriptor['wxsModuleMap'] = {}
+ let templateContent = ''
+ if (template) {
+ ({
+ wxsModuleMap,
+ wxsContentMap,
+ genericsInfo,
+ builtInComponentsMap,
+ templateContent
+ } = templateProcess({
+ template,
+ options,
+ pluginContext,
+ jsonConfig,
+ app,
+ resource: filename,
+ moduleId: id
+ }))
+ }
+ descriptor.wxsModuleMap = wxsModuleMap
+ descriptor.wxsContentMap = wxsContentMap
+ descriptor.genericsInfo = genericsInfo
+ descriptor.builtInComponentsMap = builtInComponentsMap
+
+ return {
+ code: templateContent,
+ map: null
+ }
+}
+
+/**
+ * gen template block
+ * @param descriptor - SFCDescriptor
+ * @returns descriptor.template.content
+ */
+export async function genTemplateBlock (
+ descriptor: SFCDescriptor,
+ options: Options,
+ pluginContext?: TransformPluginContext
+): Promise<{
+ output: string
+}> {
+ const templateContent = await transformTemplate(
+ descriptor,
+ options,
+ pluginContext
+ )
+ return {
+ output: genComponentTag({
+ content: templateContent?.code || '',
+ tag: 'template',
+ attrs: descriptor.template?.attrs
+ })
+ }
+}
diff --git a/packages/web-plugin/src/vite/utils/descriptor-cache.ts b/packages/web-plugin/src/vite/utils/descriptor-cache.ts
new file mode 100644
index 0000000000..eeaae51f0e
--- /dev/null
+++ b/packages/web-plugin/src/vite/utils/descriptor-cache.ts
@@ -0,0 +1,147 @@
+import { CompilerResult, templateCompiler } from '@mpxjs/compiler'
+import path from 'path'
+import slash from 'slash'
+import { Options } from '../../options'
+import { JsonProcessResult } from '../../processor/json-process'
+import { TemplateProcessResult } from '../../processor/template-process'
+import { OptionObject } from 'loader-utils'
+import pathHash from '../../utils/path-hash'
+import { resolvedConfig } from '../config'
+
+export interface SFCDescriptor
+ extends CompilerResult,
+ Omit,
+ JsonProcessResult {
+ id: string
+ filename: string
+ app: boolean
+ isPage: boolean
+ isComponent: boolean
+ vueSfc?: string
+}
+
+const cache = new Map()
+const prevCache = new Map()
+
+function genDescriptorTemplate () {
+ const template: SFCDescriptor['template'] = {
+ tag: 'template',
+ type: 'template',
+ content:
+ '
',
+ attrs: {},
+ start: 0,
+ end: 0
+ }
+ return template
+}
+
+function genDescriptorScript (descriptor: SFCDescriptor) {
+ const script: SFCDescriptor['script'] = {
+ tag: 'script',
+ type: 'script',
+ content: '',
+ attrs: {},
+ start: 0,
+ end: 0
+ }
+ if (descriptor.app) {
+ script.content = `
+import { createApp } from "@mpxjs/core"
+createApp({})`
+ }
+ if (descriptor.isPage) {
+ script.content = `
+import { createPage } from "@mpxjs/core"
+createPage({})`
+ }
+ if (descriptor.isComponent) {
+ script.content = `
+import { createComponent } from "@mpxjs/core"
+createComponent({})`
+ }
+ return script
+}
+
+export function createDescriptor (
+ filename: string,
+ code: string,
+ query: OptionObject,
+ options: Options
+): SFCDescriptor {
+ const { projectRoot = '', mode = 'web', defs, env } = options
+ const { isProduction, sourceMap } = resolvedConfig
+ const normalizedPath = slash(
+ path.normalize(path.relative(projectRoot, filename))
+ )
+ const isPage = !!query.isPage
+ const isComponent = !!query.isComponent
+ const compilerResult = templateCompiler.compiler.parseComponent(code, {
+ mode,
+ defs,
+ env,
+ filePath: filename,
+ pad: 'line',
+ needMap: sourceMap
+ })
+ if (compilerResult.script && compilerResult.script.map) {
+ const sources = compilerResult.script.map.sources || []
+ compilerResult.script.map.sources = sources.map(
+ (v: string) => v.split('?')[0]
+ )
+ }
+ const descriptor: SFCDescriptor = {
+ ...compilerResult,
+ id: pathHash(normalizedPath + (isProduction ? code : '')),
+ filename,
+ isPage,
+ isComponent,
+ app: !(isPage || isComponent),
+ wxsModuleMap: {},
+ wxsContentMap: {},
+ builtInComponentsMap: {},
+ genericsInfo: undefined,
+ jsonConfig: {},
+ localPagesMap: {},
+ localComponentsMap: {},
+ tabBarMap: {}
+ }
+ if (descriptor.app) {
+ descriptor.template = genDescriptorTemplate()
+ }
+ if (!descriptor.script) {
+ descriptor.script = genDescriptorScript(descriptor)
+ }
+ setDescriptor(filename, descriptor)
+ return descriptor
+}
+
+export function getPrevDescriptor (filename: string): SFCDescriptor | undefined {
+ return prevCache.get(filename)
+}
+
+export function setPrevDescriptor (
+ filename: string,
+ entry: SFCDescriptor
+): void {
+ prevCache.set(filename, entry)
+}
+
+export function getDescriptor (
+ filename: string,
+ code?: string,
+ query?: OptionObject,
+ options?: Options,
+ createIfNotFound = true
+): SFCDescriptor | undefined {
+ if (cache.has(filename)) {
+ return cache.get(filename)
+ }
+ if (createIfNotFound && code && query && options) {
+ return createDescriptor(filename, code, query, options)
+ }
+}
+
+export function setDescriptor (filename: string, entry: SFCDescriptor): void {
+ cache.set(filename, entry)
+}
diff --git a/packages/web-plugin/src/webpack.ts b/packages/web-plugin/src/webpack.ts
new file mode 100644
index 0000000000..c9f7a09671
--- /dev/null
+++ b/packages/web-plugin/src/webpack.ts
@@ -0,0 +1,2 @@
+export * from './webpack/index'
+export { default } from './webpack/index'
diff --git a/packages/web-plugin/src/webpack/index.ts b/packages/web-plugin/src/webpack/index.ts
new file mode 100644
index 0000000000..0ce9e4ed08
--- /dev/null
+++ b/packages/web-plugin/src/webpack/index.ts
@@ -0,0 +1,720 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+'use strict'
+
+import {
+ addQuery,
+ matchCondition,
+ parseRequest,
+ stringify,
+ stringifyLoadersAndResource,
+ toPosix
+} from '@mpxjs/compile-utils'
+import CommonJsVariableDependency from '@mpxjs/webpack-plugin/lib/dependencies/CommonJsVariableDependency'
+import InjectDependency from '@mpxjs/webpack-plugin/lib/dependencies/InjectDependency'
+import RecordResourceMapDependency from '@mpxjs/webpack-plugin/lib/dependencies/RecordResourceMapDependency'
+import RecordVueContentDependency from '@mpxjs/webpack-plugin/lib/dependencies/RecordVueContentDependency'
+import ReplaceDependency from '@mpxjs/webpack-plugin/lib/dependencies/ReplaceDependency'
+import ResolveDependency from '@mpxjs/webpack-plugin/lib/dependencies/ResolveDependency'
+import AddEnvPlugin from '@mpxjs/webpack-plugin/lib/resolver/AddEnvPlugin'
+import AddModePlugin from '@mpxjs/webpack-plugin/lib/resolver/AddModePlugin'
+import async from 'async'
+import {
+ Compiler,
+ DefinePlugin,
+ Dependency,
+ ExternalsPlugin,
+ Module,
+ NormalModule,
+ WebpackError
+} from 'webpack'
+import harmonySpecifierTag from 'webpack/lib/dependencies/HarmonyImportDependencyParserPlugin'
+import FileSystemInfo from 'webpack/lib/FileSystemInfo'
+import FlagEntryExportAsUsedPlugin from 'webpack/lib/FlagEntryExportAsUsedPlugin'
+import NullFactory from 'webpack/lib/NullFactory'
+import { Options, processOptions } from '../options'
+import getOutputPath from '../utils/get-output-path'
+import mpx, { Mpx } from './mpx'
+
+const styleCompilerPath = require.resolve('@mpxjs/loaders/style-loader.js')
+const isProductionLikeMode = (options: {
+ mode?: 'production' | 'development' | 'none' | undefined
+}) => {
+ return options.mode === 'production' || !options.mode
+}
+
+const warnings: Array = []
+const errors: Array = []
+
+class MpxWebpackPlugin {
+ options: Options
+
+ constructor (options: Partial) {
+ options = options || {}
+ this.options = processOptions(options)
+ // Hack for buildDependencies
+ const rawResolveBuildDependencies =
+ FileSystemInfo.prototype.resolveBuildDependencies
+ FileSystemInfo.prototype.resolveBuildDependencies = function (
+ context: string,
+ deps: Dependency,
+ rawCallback: (err: string, result: string) => void
+ ) {
+ return rawResolveBuildDependencies.call(
+ this,
+ context,
+ deps,
+ (err: string, result: string) => {
+ if (
+ result &&
+ typeof options.hackResolveBuildDependencies === 'function'
+ ){
+ options.hackResolveBuildDependencies(result)
+ }
+ return rawCallback(err, result)
+ }
+ )
+ }
+ }
+
+ static loader (options: { [k: string]: unknown }) {
+ if (options.transRpx) {
+ warnings.push(
+ 'Mpx loader option [transRpx] is deprecated now, please use mpx webpack plugin config [transRpxRules] instead!'
+ )
+ }
+ return {
+ loader: '@mpxjs/web-plugin/dist/webpack/loader/web-loader',
+ options
+ }
+ }
+
+ static wxsPreLoader (options = {}) {
+ return {
+ loader: '@mpxjs/loaders/pre-loader',
+ options
+ }
+ }
+
+ static urlLoader (options = {}) {
+ return {
+ loader: '@mpxjs/loaders/url-loader',
+ options
+ }
+ }
+
+ static fileLoader (options = {}) {
+ return {
+ loader: '@mpxjs/loaders/file-loader',
+ options
+ }
+ }
+
+ runModeRules (data: { [k: string]: any }) {
+ const { resourcePath, queryObj } = parseRequest(data.resource)
+ if (queryObj.mode) {
+ return
+ }
+ const mode = this.options.mode
+ const modeRule = this.options?.modeRules?.mode
+ if (!modeRule) {
+ return
+ }
+ if (matchCondition(resourcePath, modeRule)) {
+ data.resource = addQuery(data.resource, { mode })
+ data.request = addQuery(data.request, { mode })
+ }
+ }
+
+ apply (compiler: { [k: string]: unknown } & Compiler) {
+ if (!compiler.__mpx__) {
+ compiler.__mpx__ = true
+ } else {
+ errors.push(
+ 'Multiple MpxWebpackPlugin instances exist in webpack compiler, please check webpack plugins config!'
+ )
+ }
+
+ // 将entry export标记为used且不可mangle,避免require.async生成的js chunk在生产环境下报错
+ new FlagEntryExportAsUsedPlugin(true, 'entry').apply(compiler)
+ if (!compiler.options.node || !compiler.options.node.global) {
+ compiler.options.node = compiler.options.node || {}
+ compiler.options.node.global = true
+ }
+
+ const addModePlugin = new AddModePlugin(
+ 'before-file',
+ this.options.mode || 'web',
+ this.options.fileConditionRules,
+ 'file'
+ )
+ const addEnvPlugin = new AddEnvPlugin(
+ 'before-file',
+ this.options.env || '',
+ this.options.fileConditionRules,
+ 'file'
+ )
+ if (Array.isArray(compiler.options.resolve.plugins)) {
+ compiler.options.resolve.plugins.push(addModePlugin)
+ } else {
+ compiler.options.resolve.plugins = [addModePlugin]
+ }
+ if (this.options.env) {
+ compiler.options.resolve.plugins.push(addEnvPlugin)
+ }
+ // 代理writeFile
+ if (this.options.writeMode === 'changed') {
+ const writedFileContentMap = new Map()
+ const originalWriteFile = compiler.outputFileSystem.writeFile
+ compiler.outputFileSystem.writeFile = (
+ filePath: string,
+ content: any,
+ callback: () => void
+ ) => {
+ const lastContent = writedFileContentMap.get(filePath)
+ if (
+ Buffer.isBuffer(lastContent)
+ ? lastContent.equals(content)
+ : lastContent === content
+ ) {
+ return callback()
+ }
+ writedFileContentMap.set(filePath, content)
+ originalWriteFile(filePath, content, callback)
+ return ''
+ }
+ }
+
+ const defs = this.options.defs || {}
+
+ const defsOpt: { [k: string]: any } = {
+ __mpx_wxs__: DefinePlugin.runtimeValue(({ module }) => {
+ return stringify(!!module.wxs)
+ })
+ }
+
+ Object.keys(defs).forEach(key => {
+ defsOpt[key] = stringify(defs[key])
+ })
+ // define mode & defs
+ new DefinePlugin(defsOpt).apply(compiler)
+
+ new ExternalsPlugin('commonjs2', this.options.externals || []).apply(
+ compiler
+ )
+
+ compiler.hooks.compilation.tap(
+ 'MpxWebpackPlugin ',
+ (compilation, { normalModuleFactory }) => {
+ NormalModule.getCompilationHooks(compilation).loader.tap(
+ 'MpxWebpackPlugin',
+ (loaderContext: any) => {
+ // 设置loaderContext的minimize
+ if (isProductionLikeMode(compiler.options)) {
+ mpx.minimize = true
+ }
+ loaderContext.getMpx = () => {
+ return mpx
+ }
+ }
+ )
+ compilation.dependencyFactories.set(
+ ResolveDependency,
+ new NullFactory()
+ )
+ compilation.dependencyTemplates.set(
+ ResolveDependency,
+ new ResolveDependency.Template()
+ )
+
+ compilation.dependencyFactories.set(
+ InjectDependency,
+ new NullFactory()
+ )
+ compilation.dependencyTemplates.set(
+ InjectDependency,
+ new InjectDependency.Template()
+ )
+
+ compilation.dependencyFactories.set(
+ ReplaceDependency,
+ new NullFactory()
+ )
+ compilation.dependencyTemplates.set(
+ ReplaceDependency,
+ new ReplaceDependency.Template()
+ )
+ compilation.dependencyFactories.set(
+ CommonJsVariableDependency,
+ normalModuleFactory
+ )
+ compilation.dependencyTemplates.set(
+ CommonJsVariableDependency,
+ new CommonJsVariableDependency.Template()
+ )
+ compilation.dependencyFactories.set(
+ RecordResourceMapDependency,
+ new NullFactory()
+ )
+ compilation.dependencyTemplates.set(
+ RecordResourceMapDependency,
+ new RecordResourceMapDependency.Template()
+ )
+ compilation.dependencyFactories.set(
+ RecordVueContentDependency,
+ new NullFactory()
+ )
+ compilation.dependencyTemplates.set(
+ RecordVueContentDependency,
+ new RecordVueContentDependency.Template()
+ )
+ }
+ )
+
+ compiler.hooks.thisCompilation.tap(
+ 'MpxWebpackPlugin',
+ (compilation, { normalModuleFactory }) => {
+ compilation.warnings = compilation.warnings.concat(
+ warnings
+ )
+ compilation.errors = compilation.errors.concat(errors)
+ const moduleGraph = compilation.moduleGraph
+ if (!compilation.__mpx__) {
+ Object.assign(mpx, {
+ ...this.options,
+ appInfo: {},
+ // pages全局记录,无需区分主包分包
+ pagesMap: {},
+ // 组件资源记录,依照所属包进行记录
+ componentsMap: {
+ main: {}
+ },
+ staticResourcesMap: {
+ main: {}
+ },
+ usingComponents: {},
+ currentPackageRoot: '',
+ wxsContentMap: {},
+ minimize: false,
+ // 输出web专用配置
+ appTitle: 'Index homepage',
+ vueContentCache: new Map(),
+ recordResourceMap: ({
+ resourcePath,
+ resourceType,
+ outputPath,
+ packageRoot = '',
+ recordOnly,
+ warn,
+ error
+ }: {
+ resourcePath: string
+ resourceType: string
+ outputPath: string
+ packageRoot: string
+ recordOnly: boolean
+ warn: (warn?: Error | string) => void
+ error: (error?: Error | string) => void
+ }) => {
+ const packageName = packageRoot || 'main'
+ const resourceMap = mpx[`${resourceType}sMap` as keyof Mpx]
+ const currentResourceMap = resourceMap.main
+ ? (resourceMap[packageName] = resourceMap[packageName] || {})
+ : resourceMap
+ let alreadyOutputted = false
+ if (outputPath) {
+ if (
+ !currentResourceMap[resourcePath] ||
+ currentResourceMap[resourcePath] === true
+ ) {
+ if (!recordOnly) {
+ // 在非recordOnly的模式下,进行输出路径冲突检测,如果存在输出路径冲突,则对输出路径进行重命名
+ for (const key in currentResourceMap) {
+ // todo 用outputPathMap来检测输出路径冲突
+ if (
+ currentResourceMap[key] === outputPath &&
+ key !== resourcePath
+ ) {
+ outputPath =
+ getOutputPath(resourcePath, resourceType, mpx, {
+ conflictPath: outputPath
+ }) || ''
+ warn &&
+ warn(
+ new Error(
+ `Current ${resourceType} [${resourcePath}] is registered with conflicted outputPath [${currentResourceMap[key]}] which is already existed in system, will be renamed with [${outputPath}], use ?resolve to get the real outputPath!`
+ )
+ )
+ break
+ }
+ }
+ }
+ currentResourceMap[resourcePath] = outputPath
+ } else {
+ if (currentResourceMap[resourcePath] === outputPath) {
+ alreadyOutputted = true
+ } else {
+ error &&
+ error(
+ new Error(
+ `Current ${resourceType} [${resourcePath}] is already registered with outputPath [${currentResourceMap[resourcePath]}], you can not register it with another outputPath [${outputPath}]!`
+ )
+ )
+ }
+ }
+ } else if (!currentResourceMap[resourcePath]) {
+ currentResourceMap[resourcePath] = true
+ }
+
+ return {
+ outputPath,
+ alreadyOutputted
+ }
+ }
+ })
+ compilation.__mpx__ = mpx
+ }
+ const rawProcessModuleDependencies =
+ compilation.processModuleDependencies
+ compilation.processModuleDependencies = (module, callback) => {
+ const presentationalDependencies =
+ module.presentationalDependencies || []
+ async.forEach(
+ presentationalDependencies.filter(
+ (dep: Dependency & { [k: string]: any }) => dep.mpxAction
+ ),
+ (dep: Dependency & { [k: string]: any }, callback) => {
+ dep.mpxAction(module, compilation, callback)
+ },
+ err => {
+ rawProcessModuleDependencies.call(
+ compilation,
+ module,
+ innerErr => {
+ const cbError: any = err || innerErr
+ return callback(cbError)
+ }
+ )
+ }
+ )
+ }
+ const normalModuleFactoryParserCallback = (
+ parser: Record
+ ) => {
+ parser.hooks.call
+ .for('__mpx_resolve_path__')
+ .tap('MpxWebpackPlugin', (expr: Record) => {
+ if (expr.arguments[0]) {
+ const resource = expr.arguments[0].value
+ const packageName = mpx.currentPackageRoot || 'main'
+ const module: Module = parser.state.module
+ const moduleGraphReturn = moduleGraph.getIssuer(
+ module
+ ) as Module & { resource: any }
+ const resource1: string = moduleGraphReturn.resource
+ const issuerResource = resource1
+ const range = expr.range
+ const dep = new ResolveDependency(
+ resource,
+ packageName,
+ issuerResource,
+ range
+ )
+ parser.state.current.addPresentationalDependency(dep)
+ return true
+ }
+ })
+
+ // hack babel polyfill global
+ parser.hooks.statementIf.tap(
+ 'MpxWebpackPlugin',
+ (expr: Record) => {
+ if (/core-js.+microtask/.test(parser.state.module.resource)) {
+ if (
+ expr.test.left &&
+ (expr.test.left.name === 'Observer' ||
+ expr.test.left.name === 'MutationObserver')
+ ) {
+ const current = parser.state.current
+ current.addPresentationalDependency(
+ new InjectDependency({
+ content: 'document && ',
+ index: expr.test.range[0]
+ })
+ )
+ }
+ }
+ }
+ )
+
+ parser.hooks.evaluate
+ .for('CallExpression')
+ .tap('MpxWebpackPlugin', (expr: Record) => {
+ const current = parser.state.current
+ const arg0 = expr.arguments[0]
+ const arg1 = expr.arguments[1]
+ const callee = expr.callee
+ // todo 该逻辑在corejs3中不需要,等corejs3比较普及之后可以干掉
+ if (/core-js.+global/.test(parser.state.module.resource)) {
+ if (
+ callee.name === 'Function' &&
+ arg0 &&
+ arg0.value === 'return this'
+ ) {
+ current.addPresentationalDependency(
+ new InjectDependency({
+ content: '(function() { return this })() || ',
+ index: expr.range[0]
+ })
+ )
+ }
+ }
+ if (/regenerator/.test(parser.state.module.resource)) {
+ if (
+ callee.name === 'Function' &&
+ arg0 &&
+ arg0.value === 'r' &&
+ arg1 &&
+ arg1.value === 'regeneratorRuntime = r'
+ ) {
+ current.addPresentationalDependency(
+ new ReplaceDependency('(function () {})', expr.range)
+ )
+ }
+ }
+ })
+
+ // 处理跨平台转换
+ if (mpx.srcMode !== mpx.mode) {
+ // 处理跨平台全局对象转换
+ const transGlobalObject = (expr: Record) => {
+ const module = parser.state.module
+ const current = parser.state.current
+ const { queryObj, resourcePath } = parseRequest(module.resource)
+ const localSrcMode = queryObj.mode
+ const globalSrcMode = mpx.srcMode
+ const srcMode = localSrcMode || globalSrcMode
+ const mode = mpx.mode
+
+ let target
+ if (expr.type === 'Identifier') {
+ target = expr
+ } else if (expr.type === 'MemberExpression') {
+ target = expr.object
+ }
+
+ if (
+ !matchCondition(resourcePath, this.options.transMpxRules) ||
+ resourcePath.indexOf('@mpxjs') !== -1 ||
+ !target ||
+ mode === srcMode
+ ) {
+ return
+ }
+
+ const type = target.name
+ const name = type === 'wx' ? 'mpx' : 'createFactory'
+ const replaceContent =
+ type === 'wx' ? 'mpx' : `createFactory(${stringify(type)})`
+
+ const dep = new ReplaceDependency(replaceContent, target.range)
+ current.addPresentationalDependency(dep)
+
+ let needInject = true
+ for (const dep of module.dependencies) {
+ if (
+ dep instanceof CommonJsVariableDependency &&
+ dep.name === name
+ ) {
+ needInject = false
+ break
+ }
+ }
+ if (needInject) {
+ const dep = new CommonJsVariableDependency(
+ `@mpxjs/core/src/runtime/${name}`,
+ name
+ )
+ module.addDependency(dep)
+ }
+ }
+
+ // 转换wx全局对象
+ parser.hooks.expression
+ .for('wx')
+ .tap('MpxWebpackPlugin', transGlobalObject)
+
+ // 为跨平台api调用注入srcMode参数指导api运行时转换
+ const apiBlackListMap = [
+ 'createApp',
+ 'createPage',
+ 'createComponent',
+ 'createStore',
+ 'createStoreWithThis',
+ 'mixin',
+ 'injectMixins',
+ 'toPureObject',
+ 'observable',
+ 'watch',
+ 'use',
+ 'set',
+ 'remove',
+ 'delete',
+ 'setConvertRule',
+ 'getMixin',
+ 'getComputed',
+ 'implement'
+ ].reduce((map: Record, api: string) => {
+ map[api] = true
+ return map
+ }, {})
+
+ const injectSrcModeForTransApi = (
+ expr: Record,
+ members: Array
+ ) => {
+ // members为空数组时,callee并不是memberExpression
+ if (!members.length) return
+ const callee = expr.callee
+ const args = expr.arguments
+ const name = callee.object.name
+ const { queryObj, resourcePath } = parseRequest(
+ parser.state.module.resource
+ )
+ const localSrcMode = queryObj.mode
+ const globalSrcMode = mpx.srcMode
+ const srcMode = localSrcMode || globalSrcMode
+
+ if (
+ srcMode === globalSrcMode ||
+ apiBlackListMap[
+ callee.property.name || callee.property.value
+ ] ||
+ (name !== 'mpx' && name !== 'wx') ||
+ (name === 'wx' &&
+ !matchCondition(resourcePath, this.options.transMpxRules))
+ ){
+ return
+ }
+
+ const srcModeString = `__mpx_src_mode_${srcMode}__`
+ const dep = new InjectDependency({
+ content: args.length
+ ? `, ${stringify(srcModeString)}`
+ : stringify(srcModeString),
+ index: expr.end - 1
+ })
+ parser.state.current.addPresentationalDependency(dep)
+ }
+
+ parser.hooks.callMemberChain
+ .for(harmonySpecifierTag)
+ .tap('MpxWebpackPlugin', injectSrcModeForTransApi)
+ parser.hooks.callMemberChain
+ .for('mpx')
+ .tap('MpxWebpackPlugin', injectSrcModeForTransApi)
+ parser.hooks.callMemberChain
+ .for('wx')
+ .tap('MpxWebpackPlugin', injectSrcModeForTransApi)
+ }
+ }
+ normalModuleFactory.hooks.parser
+ .for('javascript/auto')
+ .tap('MpxWebpackPlugin', normalModuleFactoryParserCallback)
+ normalModuleFactory.hooks.parser
+ .for('javascript/dynamic')
+ .tap('MpxWebpackPlugin', normalModuleFactoryParserCallback)
+ normalModuleFactory.hooks.parser
+ .for('javascript/esm')
+ .tap('MpxWebpackPlugin', normalModuleFactoryParserCallback)
+ }
+ )
+
+ compiler.hooks.normalModuleFactory.tap(
+ 'MpxWebpackPlugin',
+ normalModuleFactory => {
+ // resolve前修改原始request
+ normalModuleFactory.hooks.beforeResolve.tap(
+ 'MpxWebpackPlugin',
+ data => {
+ const request = data.request
+ const { queryObj, resource } = parseRequest(request)
+ if (queryObj.resolve) {
+ // 此处的query用于将资源引用的当前包信息传递给resolveDependency
+ const resolveLoaderPath = '@mpxjs/loaders/resolve-loader'
+ data.request = `!!${resolveLoaderPath}!${resource}`
+ }
+ }
+ )
+ // 应用过rules后,注入mpx相关资源编译loader
+ normalModuleFactory.hooks.afterResolve.tap(
+ 'MpxWebpackPlugin',
+ ({ createData }) => {
+ const { queryObj } = parseRequest(createData.request || '')
+ const loaders = createData.loaders
+ const mpxStyleOptions = queryObj.mpxStyleOptions
+ const firstLoader =
+ loaders && loaders[0] ? toPosix(loaders[0].loader) : ''
+ const isPitcherRequest = firstLoader.includes(
+ 'vue-loader/lib/loaders/pitcher'
+ )
+ let cssLoaderIndex = -1
+ let vueStyleLoaderIndex = -1
+ let mpxStyleLoaderIndex = -1
+ loaders &&
+ loaders.forEach((loader, index) => {
+ const currentLoader = toPosix(loader.loader)
+ if (
+ currentLoader.includes('css-loader') &&
+ cssLoaderIndex === -1
+ ) {
+ cssLoaderIndex = index
+ } else if (
+ currentLoader.includes(
+ 'vue-loader/lib/loaders/stylePostLoader'
+ ) &&
+ vueStyleLoaderIndex === -1
+ ) {
+ vueStyleLoaderIndex = index
+ } else if (
+ currentLoader.includes(styleCompilerPath) &&
+ mpxStyleLoaderIndex === -1
+ ) {
+ mpxStyleLoaderIndex = index
+ }
+ })
+ if (mpxStyleLoaderIndex === -1) {
+ let loaderIndex = -1
+ if (cssLoaderIndex > -1 && vueStyleLoaderIndex === -1) {
+ loaderIndex = cssLoaderIndex
+ } else if (
+ cssLoaderIndex > -1 &&
+ vueStyleLoaderIndex > -1 &&
+ !isPitcherRequest
+ ) {
+ loaderIndex = vueStyleLoaderIndex
+ }
+ if (loaderIndex > -1) {
+ // @ts-ignore
+ loaders &&
+ loaders.splice(loaderIndex + 1, 0, {
+ loader: styleCompilerPath,
+ options:
+ (mpxStyleOptions && JSON.parse(mpxStyleOptions)) || {}
+ } as any)
+ }
+ }
+
+ createData.request = stringifyLoadersAndResource(
+ loaders,
+ createData.resource || ''
+ )
+ // 根据用户传入的modeRules对特定资源添加mode query
+ this.runModeRules(createData)
+ }
+ )
+ }
+ )
+ }
+}
+
+export default MpxWebpackPlugin
diff --git a/packages/web-plugin/src/webpack/loader/web-loader.ts b/packages/web-plugin/src/webpack/loader/web-loader.ts
new file mode 100644
index 0000000000..fc541f2654
--- /dev/null
+++ b/packages/web-plugin/src/webpack/loader/web-loader.ts
@@ -0,0 +1,230 @@
+import { templateCompiler, jsonCompiler } from '@mpxjs/compiler'
+import {
+ addQuery,
+ matchCondition,
+ parseRequest,
+ getEntryName,
+ tsWatchRunLoaderFilter
+} from '@mpxjs/compile-utils'
+import RecordResourceMapDependency from '@mpxjs/webpack-plugin/lib/dependencies/RecordResourceMapDependency'
+import RecordVueContentDependency from '@mpxjs/webpack-plugin/lib/dependencies/RecordVueContentDependency'
+import async from 'async'
+import loaderUtils from 'loader-utils'
+import path from 'path'
+import { MPX_APP_MODULE_ID } from '../../constants'
+import mpx, { getOptions } from '../mpx'
+import processJSON from '../web/process-json'
+import processScript from '../web/process-script'
+import processStyles from '../web/process-styles'
+import processTemplate from '../web/process-template'
+import pathHash from '../../utils/path-hash'
+import getOutputPath from '../../utils/get-output-path'
+import { Dependency, LoaderContext } from 'webpack'
+import { proxyPluginContext } from '@mpxjs/plugin-proxy'
+
+export default function (
+ this: LoaderContext,
+ content: string
+): string | undefined {
+ this.cacheable()
+
+ // 兼容处理处理ts-loader中watch-run/updateFile逻辑,直接跳过当前loader及后续的loader返回内容
+ const pathExtname = path.extname(this.resourcePath)
+ if (!['.vue', '.mpx'].includes(pathExtname)) {
+ this.loaderIndex = tsWatchRunLoaderFilter(this.loaders, this.loaderIndex)
+ return content
+ }
+
+ if (!mpx) {
+ return content
+ }
+ const { resourcePath, queryObj } = parseRequest(this.resource)
+
+ const packageRoot = queryObj.packageRoot || mpx.currentPackageRoot
+ const packageName = packageRoot || 'main'
+ const pagesMap = mpx.pagesMap
+ const componentsMap = mpx.componentsMap[packageName]
+ const mode = mpx.mode
+ const env = mpx.env
+ const autoScope = matchCondition(resourcePath, mpx.autoScopeRules)
+
+ let ctorType = 'app'
+ if (pagesMap[resourcePath]) {
+ // page
+ ctorType = 'page'
+ } else if (componentsMap[resourcePath]) {
+ // component
+ ctorType = 'component'
+ }
+ // 支持资源query传入isPage或isComponent支持页面/组件单独编译
+ if (ctorType === 'app' && (queryObj.isComponent || queryObj.isPage)) {
+ const entryName =
+ getEntryName(this) ||
+ getOutputPath(
+ resourcePath,
+ queryObj.isComponent ? 'component' : 'page',
+ mpx
+ ) ||
+ ''
+ ctorType = queryObj.isComponent ? 'component' : 'page'
+ this._module?.addPresentationalDependency(
+ (
+ new RecordResourceMapDependency(
+ resourcePath,
+ ctorType,
+ entryName,
+ packageRoot!
+ )
+ )
+ )
+ }
+
+ if (ctorType === 'app') {
+ if (!mpx.appInfo?.name) {
+ mpx.appInfo = {
+ resourcePath,
+ name: getEntryName(this)
+ }
+ }
+ }
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const loaderContext: any = this
+ const stringifyRequest = (r: string) => loaderUtils.stringifyRequest(loaderContext, r)
+ const filePath = this.resourcePath
+ const moduleId = ctorType === 'app' ? MPX_APP_MODULE_ID : 'm' + ((pathHash && pathHash(filePath)) || '')
+
+ // 将mpx文件 分成四部分
+ const parts = templateCompiler.parser(content, {
+ filePath,
+ needMap: this.sourceMap,
+ mode,
+ env
+ })
+
+ let output = ''
+ const callback = this.async()
+
+ jsonCompiler
+ .parse(
+ parts,
+ loaderContext.context,
+ proxyPluginContext(loaderContext),
+ getOptions(),
+ loaderContext._compilation?.inputFileSystem
+ )
+ .then(jsonConfig => {
+ let componentGenerics = {}
+ if (jsonConfig.componentGenerics) {
+ componentGenerics = Object.assign({}, jsonConfig.componentGenerics)
+ }
+ // 处理mode为web时输出vue格式文件
+ if (ctorType === 'app' && !queryObj.isApp) {
+ const request = addQuery(this.resource, { isApp: true })
+ const el = (mpx.webConfig && mpx.webConfig.el) || '#app'
+ output += `
+import App from ${stringifyRequest(request)}
+import Vue from 'vue'
+new Vue({
+ el: '${el}',
+ render: function(h){
+ return h(App)
+ }
+})\n
+`
+ // 直接结束loader进入parse
+ this.loaderIndex = -1
+ return callback(null, output)
+ }
+ // 通过RecordVueContentDependency和vueContentCache确保子request不再重复生成vueContent
+ const cacheContent = mpx.vueContentCache && mpx.vueContentCache.get(filePath)
+ if (cacheContent) return callback(null, cacheContent)
+ return async.waterfall(
+ [
+ (callback: (err?: Error | null, result?: any) => void) => {
+ async.parallel(
+ [
+ callback => {
+ processTemplate(
+ parts.template,
+ {
+ loaderContext,
+ moduleId,
+ ctorType,
+ jsonConfig
+ },
+ callback
+ )
+ },
+ callback => {
+ processStyles(
+ parts.styles,
+ {
+ ctorType,
+ autoScope,
+ moduleId
+ },
+ callback
+ )
+ },
+ callback => {
+ processJSON(
+ jsonConfig,
+ {
+ loaderContext
+ },
+ callback
+ )
+ }
+ ],
+ (err, res) => {
+ callback(err, res)
+ }
+ )
+ },
+ (
+ [templateRes, stylesRes, jsonRes]: any,
+ callback: (err?: Error | null, result?: any) => void
+ ) => {
+ output += templateRes.output
+ output += stylesRes.output
+ output += jsonRes.output
+ if (
+ ctorType === 'app' &&
+ jsonRes.jsonConfig.window &&
+ jsonRes.jsonConfig.window.navigationBarTitleText
+ ) {
+ mpx.appTitle = jsonRes.jsonConfig.window.navigationBarTitleText
+ }
+
+ processScript(
+ parts.script!,
+ {
+ loaderContext,
+ ctorType,
+ moduleId,
+ componentGenerics,
+ jsonConfig: jsonRes.jsonConfig,
+ outputPath: queryObj.outputPath || '',
+ tabBarMap: jsonRes.tabBarMap,
+ builtInComponentsMap: templateRes.builtInComponentsMap,
+ genericsInfo: templateRes.genericsInfo,
+ wxsModuleMap: templateRes.wxsModuleMap,
+ localComponentsMap: jsonRes.localComponentsMap,
+ localPagesMap: jsonRes.localPagesMap
+ },
+ callback
+ )
+ }
+ ],
+ (err, scriptRes: any) => {
+ if (err) return callback(err)
+ output += scriptRes.output
+ this._module &&
+ this._module.addPresentationalDependency(
+ new RecordVueContentDependency(filePath, output)
+ )
+ callback(null, output)
+ }
+ )
+ })
+}
diff --git a/packages/web-plugin/src/webpack/mpx.ts b/packages/web-plugin/src/webpack/mpx.ts
new file mode 100644
index 0000000000..9483a64530
--- /dev/null
+++ b/packages/web-plugin/src/webpack/mpx.ts
@@ -0,0 +1,34 @@
+import pick from 'lodash/pick'
+import { Options, optionKeys } from '../options'
+
+export type Mpx = {
+ appInfo?: Record
+ pagesMap: any
+ componentsMap: any
+ usingComponents?: Record
+ currentPackageRoot?: string
+ wxsContentMap?: any
+ minimize?: boolean
+ staticResourcesMap?: Record
+ vueContentCache?: Map
+ appTitle?: string
+ recordResourceMap?(record: {
+ resourcePath: string
+ resourceType: 'page' | 'component'
+ outputPath: string
+ packageRoot: string
+ recordOnly: boolean
+ warn(e: Error): void
+ error(e: Error): void
+ }): void
+}
+
+export type MpxWithOptions = Mpx & Options
+
+const mpx: MpxWithOptions = {} as MpxWithOptions
+
+export function getOptions (): Options {
+ return pick(mpx, optionKeys)
+}
+
+export default mpx
diff --git a/packages/web-plugin/src/webpack/web/process-json.ts b/packages/web-plugin/src/webpack/web/process-json.ts
new file mode 100644
index 0000000000..cd5729e6fa
--- /dev/null
+++ b/packages/web-plugin/src/webpack/web/process-json.ts
@@ -0,0 +1,42 @@
+import { LoaderContext } from 'webpack'
+import { jsonProcess } from '../../processor/json-process'
+import { JsonConfig } from '@mpxjs/compiler'
+import mpx, { getOptions } from '../mpx'
+
+export default async function (
+ jsonConfig: JsonConfig,
+ {
+ loaderContext
+ }: {
+ loaderContext: LoaderContext
+ },
+ rawCallback: (err?: Error | null, result?: any) => void
+) {
+ const output = '/* json */\n'
+ let localPagesMap = {}
+ let localComponentsMap = {}
+ let tabBarMap = {}
+
+ const context = loaderContext.context
+
+ const callback = (err?: Error) => {
+ return rawCallback(err, {
+ output,
+ jsonConfig,
+ localPagesMap,
+ localComponentsMap,
+ tabBarMap
+ })
+ }
+
+ ({ jsonConfig, localPagesMap, localComponentsMap, tabBarMap } =
+ await jsonProcess({
+ jsonConfig,
+ pluginContext: loaderContext,
+ context,
+ options: getOptions(),
+ mode: 'webpack',
+ mpx
+ }))
+ callback()
+}
diff --git a/packages/web-plugin/src/webpack/web/process-script.ts b/packages/web-plugin/src/webpack/web/process-script.ts
new file mode 100644
index 0000000000..163f278f60
--- /dev/null
+++ b/packages/web-plugin/src/webpack/web/process-script.ts
@@ -0,0 +1,335 @@
+import {
+ addQuery,
+ createHelpers,
+ genComponentTag,
+ genImport,
+ isUrlRequest,
+ shallowStringify,
+ stringify
+} from '@mpxjs/compile-utils'
+import { CompilerResult, JsonConfig } from '@mpxjs/compiler'
+import { proxyPluginContext } from '@mpxjs/plugin-proxy'
+import {
+ stringifyRequest as _stringifyRequest,
+ urlToRequest
+} from 'loader-utils'
+import MagicString from 'magic-string'
+import { LoaderContext } from 'webpack'
+import { Options } from '../../options'
+import { JsonProcessResult } from '../../processor/json-process'
+import { TemplateProcessResult } from '../../processor/template-process'
+import mpx, { getOptions } from '../mpx'
+
+const optionProcessorPath = '@mpxjs/web-plugin/src/runtime/option-processor'
+const tabBarContainerPath = '@mpxjs/web-plugin/src/runtime/components/web/mpx-tab-bar-container.vue'
+const tabBarPath = '@mpxjs/web-plugin/src/runtime/components/web/mpx-tab-bar.vue'
+
+function getAsyncChunkName (chunkName: boolean | string) {
+ if (chunkName && typeof chunkName !== 'boolean') {
+ return `/* webpackChunkName: "${chunkName}" */`
+ }
+ return ''
+}
+
+export default function (
+ script: CompilerResult['script'] | null,
+ {
+ loaderContext,
+ ctorType,
+ moduleId,
+ componentGenerics,
+ jsonConfig,
+ outputPath,
+ tabBarMap,
+ builtInComponentsMap,
+ genericsInfo,
+ wxsModuleMap,
+ localComponentsMap,
+ localPagesMap
+ }: {
+ loaderContext: LoaderContext | any
+ moduleId: string
+ ctorType: string
+ outputPath: string
+ componentGenerics: JsonConfig['componentGenerics']
+ tabBarMap: JsonProcessResult['tabBarMap']
+ jsonConfig: JsonConfig
+ builtInComponentsMap: TemplateProcessResult['builtInComponentsMap']
+ genericsInfo: TemplateProcessResult['genericsInfo']
+ wxsModuleMap: TemplateProcessResult['wxsModuleMap']
+ localComponentsMap: JsonProcessResult['localPagesMap']
+ localPagesMap: JsonProcessResult['localPagesMap']
+ },
+ callback: (err?: Error | null, result?: any) => void
+) {
+ const { i18n, projectRoot, webConfig = {}, appInfo, srcMode, minimize } = mpx
+
+ const mpxPluginContext = proxyPluginContext(loaderContext)
+ const { getRequire } = createHelpers(loaderContext)
+ const tabBar = jsonConfig.tabBar
+ const tabBarPagesMap: Record = {}
+ const isProduction = minimize || process.env.NODE_ENV === 'production'
+ const stringifyRequest = (r: string) => _stringifyRequest(loaderContext, r)
+ const genComponentCode = (
+ resource: string,
+ { async = false } = {},
+ params = {}
+ ) => {
+ const resourceRequest = stringifyRequest(resource)
+ if (!async) {
+ return `getComponent(require(${resourceRequest}), ${stringify(params)})`
+ } else {
+ return `()=>import(${getAsyncChunkName(
+ async
+ )}${resourceRequest}).then(res => getComponent(res, ${stringify(
+ params
+ )}))`
+ }
+ }
+
+ if (tabBar && tabBarMap) {
+ // 挂载tabBar组件
+ const tabBarRequest = addQuery(
+ tabBar.custom ? './custom-tab-bar/index' : tabBarPath,
+ { isComponent: true }
+ )
+ tabBarPagesMap['mpx-tab-bar'] = genComponentCode(tabBarRequest)
+ // 挂载tabBar页面
+ Object.keys(tabBarMap).forEach(pagePath => {
+ const pageCfg = localPagesMap[pagePath]
+ const { resource, async } = pageCfg
+ if (pageCfg) {
+ tabBarPagesMap[pagePath] = genComponentCode(
+ resource,
+ { async },
+ { __mpxPageRoute: stringify(pagePath) }
+ )
+ } else {
+ mpxPluginContext.warn(
+ new Error(
+ `[script processor][${loaderContext.resource}]: TabBar page path ${pagePath} is not exist in local page map, please check!`
+ )
+ )
+ }
+ })
+ }
+
+ let output = '/* script */\n'
+
+ let scriptSrcMode = srcMode
+ if (script) {
+ scriptSrcMode = script.mode || scriptSrcMode
+ } else {
+ script = {
+ tag: 'script',
+ type: 'script',
+ content: '',
+ attrs: {},
+ start: 0,
+ end: 0
+ }
+ }
+ // @ts-ignore
+ output += genComponentTag(script, {
+ attrs (script: { attrs: { src?: string; setup?: boolean } }) {
+ const attrs = Object.assign({}, script.attrs)
+ // src改为内联require,删除
+ delete attrs.src
+ // script setup通过mpx处理,删除该属性避免vue报错
+ delete attrs.setup
+ return attrs
+ },
+ content: function (script: { content?: string }) {
+ const content = new MagicString(
+ `\n import processOption, { getComponent, getWxsMixin } from ${stringifyRequest(
+ optionProcessorPath
+ )}\n`
+ )
+ // add import
+ if (ctorType === 'app') {
+ content.append(
+ `${genImport('@mpxjs/web-plugin/src/runtime/base.styl')}
+ ${genImport('vue', 'Vue')}
+ ${genImport('vue-router', 'VueRouter')}
+ ${genImport('@mpxjs/core', 'Mpx')}
+ Vue.use(VueRouter)
+ global.getApp = function(){}
+ global.getCurrentPages = function(){
+ if(!global.__mpxRouter) return []
+ // @ts-ignore
+ return global.__mpxRouter.stack.map(item => {
+ let page
+ const vnode = item.vnode
+ if(vnode && vnode.componentInstance) {
+ page = vnode.tag.endsWith('mpx-tab-bar-container') ? vnode.componentInstance.$refs.tabBarPage : vnode.componentInstance
+ }
+ return page || { route: item.path.slice(1) }
+ })
+ }
+ global.__networkTimeout = ${stringify(jsonConfig.networkTimeout)}
+ global.__mpxGenericsMap = {}
+ global.__mpxOptionsMap = {}
+ global.__style = ${stringify(jsonConfig.style || 'v1')}
+ global.__mpxPageConfig = ${stringify(jsonConfig.window)}
+ global.__mpxTransRpxFn = ${webConfig.transRpxFn}\n`
+ )
+ if (i18n) {
+ const i18nObj = Object.assign({}, i18n)
+ content.append(
+ `${genImport('vue-i18n', 'VueI18n')}
+ import { createI18n } from 'vue-i18n-bridge'
+ Vue.use(VueI18n , { bridge: true })\n
+ `
+ )
+ const requestObj: Record = {}
+ const i18nKeys = ['messages', 'dateTimeFormats', 'numberFormats']
+ i18nKeys.forEach(key => {
+ const i18nKey = `${key}Path` as keyof Options['i18n']
+ if (i18nObj[i18nKey]) {
+ requestObj[key] = stringifyRequest(i18nObj[i18nKey])
+ delete i18nObj[i18nKey]
+ }
+ })
+ content.append(` const i18nCfg = ${stringify(i18nObj)}\n`)
+ Object.keys(requestObj).forEach(key => {
+ content.append(` i18nCfg.${key} = require(${requestObj[key]})\n`)
+ })
+ content.append(' i18nCfg.legacy = false\n')
+ content.append(` const i18n = createI18n(i18nCfg, VueI18n)
+ Vue.use(i18n)
+ Mpx.i18n = i18n
+ \n`)
+ }
+ }
+ let hasApp = true
+ if (!appInfo || !appInfo.name) {
+ hasApp = false
+ }
+ // 注入wxs模块
+ content.append(' const wxsModules = {}\n')
+ if (wxsModuleMap) {
+ Object.keys(wxsModuleMap).forEach(module => {
+ const src = urlToRequest(wxsModuleMap[module], projectRoot)
+ content.append(
+ ` wxsModules.${module} = require(${stringifyRequest(src)})\n`
+ )
+ })
+ }
+ const pagesMap: Record = {}
+ const componentsMap: Record = {}
+ Object.keys(localPagesMap).forEach(pagePath => {
+ const pageCfg = localPagesMap[pagePath]
+ const { resource, async } = pageCfg
+ const isTabBar = tabBarMap && tabBarMap[pagePath]
+ pagesMap[pagePath] = genComponentCode(
+ isTabBar ? tabBarContainerPath : resource,
+ {
+ async: isTabBar ? false : async
+ },
+ isTabBar
+ ? { __mpxBuiltIn: true }
+ : { __mpxPageRoute: stringify(pagePath) }
+ )
+ })
+ Object.keys(localComponentsMap).forEach(componentName => {
+ const componentCfg = localComponentsMap[componentName]
+ const { resource, async } = componentCfg
+ componentsMap[componentName] = genComponentCode(resource, { async })
+ })
+
+ Object.keys(builtInComponentsMap).forEach(componentName => {
+ const componentCfg = builtInComponentsMap[componentName]
+ componentsMap[componentName] = genComponentCode(
+ componentCfg.resource,
+ { async: false },
+ { __mpxBuiltIn: true }
+ )
+ })
+ content.append(
+ ` global.currentModuleId = ${stringify(moduleId)}\n
+ global.currentSrcMode = ${stringify(scriptSrcMode)}\n
+ `
+ )
+ if (!isProduction) {
+ content.append(
+ ` global.currentResource = ${stringify(
+ loaderContext.resourcePath
+ )}\n`
+ )
+ }
+ content.append(' /** script content **/\n')
+ // 传递ctorType以补全js内容
+ const extraOptions = { ctorType }
+ // todo 仅靠vueContentCache保障模块唯一性还是不够严谨,后续需要考虑去除原始query后构建request
+ // createApp/Page/Component执行完成后立刻获取当前的option并暂存
+ content.append(
+ ` ${getRequire('script', script, extraOptions)}\n
+ const currentOption = global.__mpxOptionsMap[${stringify(moduleId)}]\n
+ `
+ )
+ // 获取pageConfig
+ const pageConfig: Record> = {}
+ if (ctorType === 'page') {
+ const uselessOptions = new Set([
+ 'usingComponents',
+ 'style',
+ 'singlePage'
+ ])
+ Object.keys(jsonConfig)
+ .filter(key => !uselessOptions.has(key))
+ .forEach(key => {
+ // @ts-ignore
+ pageConfig[key] = jsonConfig[key]
+ })
+ }
+ // 为了执行顺序正确,tabBarPagesMap在app逻辑执行完成后注入,保障小程序中app->page->component的js执行顺序
+ let tabBarStr = stringify(jsonConfig.tabBar)
+ if (tabBarStr && tabBarPagesMap) {
+ tabBarStr = tabBarStr.replace(
+ /"(iconPath|selectedIconPath)":"([^"]+)"/g,
+ function (matched, $1, $2) {
+ // vite 引用本地路径无法识别
+ if (isUrlRequest($2, projectRoot, getOptions().externals)) {
+ return `"${$1}":require(${stringifyRequest(
+ urlToRequest($2, projectRoot)
+ )})`
+ }
+ return matched
+ }
+ )
+ content.append(
+ ` global.__tabBar = ${tabBarStr}
+ Vue.observable(global.__tabBar)
+ // @ts-ignore
+ global.__tabBarPagesMap = ${shallowStringify(tabBarPagesMap)}\n
+ `
+ )
+ }
+
+ // 配置平台转换通过createFactory在core中convertor中定义和进行
+ // 通过processOption进行组件注册和路由注入
+ content.append(` export default processOption({
+ option: currentOption,
+ ctorType: ${stringify(ctorType)},
+ firstPage: ${stringify(Object.keys(localPagesMap)[0])},
+ outputPath: ${stringify(outputPath)},
+ pageConfig: ${stringify(pageConfig)},
+ // @ts-ignore
+ pagesMap: ${shallowStringify(pagesMap)},
+ // @ts-ignore
+ componentsMap: ${shallowStringify(componentsMap)},
+ tabBarMap: ${stringify(tabBarMap)},
+ componentGenerics: ${stringify(componentGenerics)},
+ genericsInfo: ${stringify(genericsInfo)},
+ mixin: getWxsMixin(wxsModules),
+ hasApp: ${hasApp}
+ ${ctorType === 'app' ? ',Vue, VueRouter' : ''}
+ })`)
+ return content.toString()
+ }
+ })
+ output += '\n'
+ callback(null, {
+ output
+ })
+}
diff --git a/packages/web-plugin/src/webpack/web/process-styles.ts b/packages/web-plugin/src/webpack/web/process-styles.ts
new file mode 100644
index 0000000000..13a36aa18a
--- /dev/null
+++ b/packages/web-plugin/src/webpack/web/process-styles.ts
@@ -0,0 +1,30 @@
+import { genComponentTag, stringify } from '@mpxjs/compile-utils'
+
+export default function (
+ styles: Array<{ content: string; tag: string; attrs: Record }>,
+ options: { autoScope?: boolean; moduleId?: string; ctorType: string },
+ callback: (err?: Error | null, result?: Record) => void
+) {
+ let output = '/* styles */\n'
+ if (styles.length) {
+ styles.forEach(style => {
+ output += genComponentTag(style, {
+ attrs (style: { attrs: Record }) {
+ const attrs = Object.assign({}, style.attrs)
+ if (options.autoScope) attrs.scoped = true
+ attrs.mpxStyleOptions = stringify({
+ // scoped: !!options.autoScope,
+ // query中包含module字符串会被新版vue-cli中的默认rules当做css-module处理
+ mid: options.moduleId
+ })
+ return attrs
+ }
+ })
+ output += '\n'
+ })
+ output += '\n'
+ }
+ callback(null, {
+ output
+ })
+}
diff --git a/packages/web-plugin/src/webpack/web/process-template.ts b/packages/web-plugin/src/webpack/web/process-template.ts
new file mode 100644
index 0000000000..b609f63ee3
--- /dev/null
+++ b/packages/web-plugin/src/webpack/web/process-template.ts
@@ -0,0 +1,87 @@
+import { genComponentTag, parseRequest } from '@mpxjs/compile-utils'
+import { CompilerResult, JsonConfig } from '@mpxjs/compiler'
+import { LoaderContext } from 'webpack'
+import { templateProcess } from '../../processor/template-process'
+import mpx, { getOptions } from '../mpx'
+
+export default function (
+ template: CompilerResult['template'],
+ {
+ loaderContext,
+ moduleId,
+ ctorType,
+ jsonConfig
+ }: {
+ loaderContext: LoaderContext
+ moduleId: string
+ ctorType: string
+ jsonConfig: JsonConfig
+ },
+ callback: (err?: Error | null, result?: any) => void
+) {
+ const { resourcePath } = parseRequest(loaderContext.resource)
+ let builtInComponentsMap = {}
+ let wxsModuleMap
+ let genericsInfo
+ let templateContent
+ let wxsContentMap
+ let output = '/* template */\n'
+ const app = ctorType === 'app'
+
+ if (app) {
+ template = {
+ type: 'template',
+ attrs: {},
+ tag: 'template',
+ content:
+ '
'
+ }
+ }
+ if (template) {
+ // 由于远端src template资源引用的相对路径可能发生变化,暂时不支持。
+ if (template.src) {
+ return callback(
+ new Error(
+ '[mpx loader][' +
+ loaderContext.resource +
+ ']: ' +
+ 'template content must be inline in .mpx files!'
+ )
+ )
+ }
+ if (template.lang) {
+ return callback(
+ new Error(
+ '[mpx loader][' +
+ loaderContext.resource +
+ ']: ' +
+ 'template lang is not supported in trans web mode temporarily, we will support it in the future!'
+ )
+ )
+ }
+ ({
+ wxsModuleMap,
+ genericsInfo,
+ builtInComponentsMap,
+ templateContent,
+ wxsContentMap
+ } = templateProcess({
+ template,
+ options: getOptions(),
+ pluginContext: loaderContext,
+ jsonConfig,
+ app,
+ resource: resourcePath,
+ moduleId
+ }))
+ Object.assign(mpx.wxsContentMap, wxsContentMap)
+ template.content = templateContent
+ output += `${genComponentTag(template)}\n\n`
+ }
+ callback(null, {
+ output,
+ builtInComponentsMap,
+ genericsInfo,
+ wxsModuleMap
+ })
+}
diff --git a/packages/web-plugin/tsconfig.json b/packages/web-plugin/tsconfig.json
new file mode 100644
index 0000000000..1cca5ad9f2
--- /dev/null
+++ b/packages/web-plugin/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.root.json",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "declaration": true,
+ "outDir": "dist",
+ "module": "commonjs"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000000..0249556127
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+packages:
+ - 'packages/*'
+ - 'examples/**'
diff --git a/test/e2e/miniprogram-project/package.json b/test/e2e/miniprogram-project/package.json
index 86eb70131c..c5f2f4d353 100644
--- a/test/e2e/miniprogram-project/package.json
+++ b/test/e2e/miniprogram-project/package.json
@@ -24,7 +24,7 @@
"license": "ISC",
"dependencies": {
"@mpxjs/api-proxy": "^2.7.1",
- "vue": "^2.6.10",
+ "vue": "~2.6.10",
"vue-i18n": "^8.15.3",
"@mpxjs/core": "^2.7.2"
},
@@ -36,7 +36,7 @@
"@babel/core": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.10.4",
"@babel/preset-env": "^7.10.4",
- "@babel/runtime-corejs3": "^7.10.4",
+ "@babel/runtime-corejs3": "^7.20.13",
"@mpxjs/miniprogram-simulate": "1.4.9",
"@mpxjs/mpx-jest": "0.0.16",
"@mpxjs/webpack-plugin": "^2.7.2",
@@ -79,7 +79,7 @@
"vue-loader": "^15.9.3",
"vue-router": "^3.1.3",
"vue-style-loader": "^4.1.2",
- "vue-template-compiler": "^2.6.10",
+ "vue-template-compiler": "~2.6.10",
"webpack": "^5.48.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.2",
diff --git a/test/e2e/miniprogram-wxss-loader/package.json b/test/e2e/miniprogram-wxss-loader/package.json
index 7fa4cedc20..daa6ca3ace 100644
--- a/test/e2e/miniprogram-wxss-loader/package.json
+++ b/test/e2e/miniprogram-wxss-loader/package.json
@@ -38,7 +38,7 @@
"@babel/core": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.10.4",
"@babel/preset-env": "^7.10.4",
- "@babel/runtime-corejs3": "^7.10.4",
+ "@babel/runtime-corejs3": "^7.20.13",
"@mpxjs/miniprogram-simulate": "1.4.9",
"@mpxjs/mpx-jest": "0.0.16",
"@mpxjs/webpack-plugin": "^2.7.55",
diff --git a/test/e2e/plugin-project/package.json b/test/e2e/plugin-project/package.json
index c458781bb3..ded56c019e 100644
--- a/test/e2e/plugin-project/package.json
+++ b/test/e2e/plugin-project/package.json
@@ -23,7 +23,7 @@
"license": "ISC",
"dependencies": {
"@mpxjs/api-proxy": "^2.7.1",
- "vue": "^2.6.10",
+ "vue": "~2.6.10",
"vue-i18n": "^8.15.3",
"@mpxjs/core": "^2.7.2"
},
@@ -37,7 +37,7 @@
"@babel/plugin-transform-runtime": "^7.10.4",
"@babel/plugin-transform-typescript": "^7.16.1",
"@babel/preset-env": "^7.10.4",
- "@babel/runtime-corejs3": "^7.10.4",
+ "@babel/runtime-corejs3": "^7.20.13",
"@mpxjs/miniprogram-simulate": "1.4.9",
"@mpxjs/mpx-jest": "0.0.16",
"@mpxjs/webpack-plugin": "^2.7.2",
@@ -75,7 +75,7 @@
"vue-loader": "^15.9.3",
"vue-router": "^3.1.3",
"vue-style-loader": "^4.1.2",
- "vue-template-compiler": "^2.6.10",
+ "vue-template-compiler": "~2.6.10",
"webpack": "^5.43.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-merge": "^5.8.0"
diff --git a/tsconfig.root.json b/tsconfig.root.json
new file mode 100644
index 0000000000..74092c3d1a
--- /dev/null
+++ b/tsconfig.root.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "allowJs": true,
+ "moduleResolution": "node",
+ "strict": true,
+ "sourceMap": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitOverride": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "removeComments": false,
+ "target": "ES2015"
+ }
+}