├── src ├── components.ts ├── apis-list.ts ├── runtime.ts └── index.ts ├── .gitignore ├── index.js ├── tsconfig.json ├── package.json └── README.md /src/components.ts: -------------------------------------------------------------------------------- 1 | export const components = {} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | .DS_Store 4 | dist 5 | yarn.lock 6 | package-lock.json -------------------------------------------------------------------------------- /src/apis-list.ts: -------------------------------------------------------------------------------- 1 | export const noPromiseApis = new Set([]) 2 | export const needPromiseApis = new Set([]) -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js').default 2 | module.exports.default = module.exports 3 | -------------------------------------------------------------------------------- /src/runtime.ts: -------------------------------------------------------------------------------- 1 | import { mergeReconciler, mergeInternalComponents, processApis } from '@tarojs/shared' 2 | import { components } from './components' 3 | import { noPromiseApis, needPromiseApis } from './apis-list' 4 | 5 | const hostConfig = { 6 | initNativeApi (taro) { 7 | const global = taro.miniGlobal 8 | processApis(taro, global, { 9 | noPromiseApis, 10 | needPromiseApis, 11 | isOnlyPromisify: true 12 | }) 13 | } 14 | } 15 | 16 | 17 | mergeReconciler(hostConfig) 18 | mergeInternalComponents(components) 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "commonjs", 6 | "removeComments": false, 7 | "preserveConstEnums": false, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "noImplicitAny": false, 11 | "allowSyntheticDefaultImports": true, 12 | "outDir": "dist", 13 | "rootDir": "./src", 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "strictNullChecks": true, 17 | "resolveJsonModule": true, 18 | "sourceMap": true, 19 | "baseUrl": ".", 20 | "skipLibCheck": true 21 | }, 22 | "include": [ 23 | "./src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tarojs/plugin-inject", 3 | "version": "1.0.2", 4 | "description": "Taro 小程序端平台中间层插件", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npm run clean && npm run prod", 8 | "dev": "tsc -w", 9 | "prod": "tsc", 10 | "clean": "rimraf dist", 11 | "test": "jest", 12 | "test:dev": "jest --watch" 13 | }, 14 | "files": [ 15 | "src", 16 | "dist", 17 | "index.js", 18 | "package.json" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/NervJS/taro-plugin-inject.git" 23 | }, 24 | "keywords": [ 25 | "Taro" 26 | ], 27 | "author": "luckyadam", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/NervJS/tarojs-plugin-inject/issues" 31 | }, 32 | "homepage": "https://github.com/NervJS/tarojs-plugin-inject#readme", 33 | "peerDependencies": { 34 | "@tarojs/shared": "^3.1.3" 35 | }, 36 | "devDependencies": { 37 | "@tarojs/service": "^3.1.3", 38 | "@types/node": "^13.9.8", 39 | "typescript": "^3.8.3", 40 | "webpack": "^4.41.0", 41 | "webpack-dev-server": "^3.8.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { IPluginContext, TaroPlatformBase } from '@tarojs/service' 3 | import { isObject, isArray, isFunction, isString } from '@tarojs/shared' 4 | import * as path from 'path' 5 | 6 | type VoidComponents = Set 7 | type NestElements = Map 8 | 9 | interface IOptions { 10 | voidComponents: string[] | ((list: VoidComponents) => VoidComponents) 11 | nestElements: Record | ((elem: NestElements) => NestElements) 12 | components: Record> 13 | componentsMap: Record 14 | syncApis: string[] 15 | asyncApis: string[] 16 | thirdPartyComponents: Record> 17 | } 18 | 19 | export default (ctx: IPluginContext, options: IOptions) => { 20 | const fs = ctx.helper.fs 21 | 22 | ctx.registerMethod({ 23 | name: 'onSetupClose', 24 | fn(platform: TaroPlatformBase) { 25 | const { 26 | voidComponents, 27 | nestElements, 28 | components, 29 | syncApis, 30 | asyncApis, 31 | componentsMap, 32 | thirdPartyComponents 33 | } = options 34 | 35 | const template = platform.template 36 | 37 | if (isArray(voidComponents)) { 38 | voidComponents.forEach(el => template.voidElements.add(el)) 39 | } else if (isFunction(voidComponents)) { 40 | template.voidElements = voidComponents(template.voidElements) 41 | } 42 | 43 | if (isObject(nestElements)) { 44 | for (const key in nestElements) { 45 | template.nestElements.set(key, nestElements[key]) 46 | } 47 | } else if (isFunction(nestElements)) { 48 | template.nestElements = nestElements(template.nestElements) 49 | } 50 | 51 | if (components || syncApis || asyncApis || componentsMap) { 52 | injectRuntimePath(platform) 53 | 54 | if (components) { 55 | template.mergeComponents(ctx, components) 56 | } 57 | 58 | if (componentsMap) { 59 | injectComponentsReact(fs, platform.taroComponentsPath, componentsMap) 60 | platform.taroComponentsPath = `@tarojs/plugin-inject/dist/components-react` 61 | } 62 | 63 | injectComponents(fs, components) 64 | injectApis(fs, syncApis, asyncApis) 65 | } 66 | 67 | if (thirdPartyComponents) { 68 | template.mergeThirdPartyComponents(thirdPartyComponents) 69 | } 70 | } 71 | }) 72 | } 73 | 74 | function injectRuntimePath(platform: TaroPlatformBase) { 75 | const injectedPath = `@tarojs/plugin-inject/dist/runtime` 76 | if (isArray(platform.runtimePath)) { 77 | platform.runtimePath.push(injectedPath) 78 | } else if (isString(platform.runtimePath)) { 79 | platform.runtimePath = [platform.runtimePath, injectedPath] 80 | } 81 | } 82 | 83 | function injectComponentsReact(fs, taroComponentsPath, componentsMap) { 84 | fs.writeFileSync(path.resolve(__dirname, '../dist/components-react.js'), ` 85 | export * from '${taroComponentsPath}' 86 | ${Object.keys(componentsMap).map((key) => `export const ${key} = '${componentsMap[key]}'`).join('\n')} 87 | `) 88 | } 89 | 90 | function injectComponents(fs, components) { 91 | fs.writeFileSync(path.resolve(__dirname, '../dist/components.js'), ` 92 | export const components = ${components ? JSON.stringify(components) : JSON.stringify({})}; 93 | `) 94 | } 95 | 96 | function injectApis(fs, syncApis, asyncApis) { 97 | fs.writeFileSync(path.resolve(__dirname, '../dist/apis-list.js'), ` 98 | export const noPromiseApis = new Set(${syncApis ? JSON.stringify(syncApis) : JSON.stringify([])}); 99 | export const needPromiseApis = new Set(${asyncApis ? JSON.stringify(asyncApis) : JSON.stringify([])}); 100 | `) 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @tarojs/plugin-inject 2 | 3 | > 可以为小程序平台注入公共的组件、API 等逻辑 4 | 5 | ## 版本要求 6 | 7 | ### Taro 3.3+ 8 | 9 | 请使用本插件的 `1.0` 或以上版本 10 | 11 | ### Taro 3.1/3.2 12 | 13 | 请使用本插件的 `0.0.2` 或以上版本 14 | 15 | ## 安装 16 | 17 | 在 Taro 项目根目录下安装 18 | 19 | ```bash 20 | $ npm i @tarojs/plugin-inject --save 21 | ``` 22 | 23 | ## 使用 24 | 25 | ### 引入插件 26 | 27 | 请确保 Taro CLI 已升级至 Taro `3.1.0` 的最新版本。 28 | 29 | 修改项目 `config/index.js` 中的 plugins 配置为如下 30 | 31 | ```js 32 | const config = { 33 | ... 34 | plugins: [ 35 | ['@tarojs/plugin-inject', { 36 | // 配置项 37 | }] 38 | ] 39 | ... 40 | } 41 | ``` 42 | 43 | ### 配置项 44 | 45 | 插件可以接受如下参数: 46 | 47 | | 参数项 | 类型 | 用途 | 48 | | :-----| :---- | :---- | 49 | | syncApis | array | 新增同步 API | 50 | | asyncApis | array | 新增异步 API | 51 | | components | object | 修改、新增组件的属性 | 52 | | componentsMap | object | 新增组件时的名称映射 | 53 | | voidComponents | array, function | 设置组件是否可以渲染子元素 | 54 | | nestElements | object, function | 设置组件模版的循环次数 | 55 | | thirdPartyComponents | object | 设置第三方自定义组件的属性的默认值 | 56 | 57 | #### 1. syncApis 58 | 59 | > Deprecated 60 | > v1.0.0+ 不再需要此属性 61 | 62 | 插件支持为小程序新增**同步的** API。 63 | 64 | 用法: 65 | 66 | ```js 67 | const config = { 68 | plugins: [ 69 | ['@tarojs/plugin-inject', { 70 | // 配置需要新增的 API 71 | syncApis: ['a'] 72 | }] 73 | ] 74 | } 75 | ``` 76 | 77 | 运行时即可调用此新增的 API: 78 | 79 | ```js 80 | Taro.a() 81 | ``` 82 | 83 | #### 2. asyncApis 84 | 85 | 插件支持为小程序新增**异步的** API。 86 | 87 | 用法: 88 | 89 | ```js 90 | const config = { 91 | plugins: [ 92 | ['@tarojs/plugin-inject', { 93 | // 配置需要新增的 API 94 | asyncApis: ['b'] 95 | }] 96 | ] 97 | } 98 | ``` 99 | 100 | 运行时即可调用此新增的 API: 101 | 102 | ```js 103 | Taro.b() 104 | .then(() => {}) 105 | .catch(() => {}) 106 | ``` 107 | 108 | #### 3. components 109 | 110 | 插件支持为小程序的组件**修改属性默认值**或**新增属性**。 111 | 112 | `components` 属性的[设置规范](https://taro-docs.jd.com/taro/docs/next/platform-plugin-base#31-%E7%BC%96%E5%86%99-componentsts) 113 | 114 | 用法: 115 | 116 | ```js 117 | const config = { 118 | plugins: [ 119 | ['@tarojs/plugin-inject', { 120 | components: { 121 | // 为 Text 组件新增了 'x-props' 属性和 'bindYEvent' 事件 122 | Text: { 123 | 'x-props': "'hello'", 124 | bindYEvent: '' 125 | }, 126 | // 新增一个组件 127 | ShareElement: { 128 | key: "", 129 | transform: "true", 130 | duration: "300", 131 | "easing-function": "" 132 | } 133 | }, 134 | // 新增的组件需要写映射 135 | componentsMap: { 136 | ShareElement: 'share-element' 137 | } 138 | }] 139 | ] 140 | } 141 | ``` 142 | 143 | 新增事件属性: 144 | > 新增一个事件属性有两种方式,一种是像上面例子那样属性以`bind`开头,一种是像下面例子这样将属性值设为`eh` [查看详细PR](https://github.com/NervJS/taro/pull/11478) 145 | ```js 146 | const config = { 147 | plugins: [ 148 | [ 149 | '@tarojs/plugin-inject', 150 | { 151 | components: { 152 | // 新增一个 'CustomComponent' 组件并支持 'catchtouchend' 事件 153 | CustomComponent: { 154 | catchtouchend: 'eh', 155 | }, 156 | }, 157 | componentsMap: { 158 | CustomComponent: 'custom-component', 159 | }, 160 | }, 161 | ], 162 | ], 163 | } 164 | ``` 165 | 166 | #### 4. voidComponents 167 | 168 | 在 `voidComponents` 里的组件**不可以渲染子组件**。 169 | 170 | Taro3 默认下述组件**不渲染子组件**以节省模板体积: 171 | 172 | ```js 173 | export const voidElements = new Set([ 174 | 'progress', 175 | 'icon', 176 | 'rich-text', 177 | 'input', 178 | 'textarea', 179 | 'slider', 180 | 'switch', 181 | 'audio', 182 | 'ad', 183 | 'official-account', 184 | 'open-data', 185 | 'navigation-bar' 186 | ]) 187 | ``` 188 | 189 | 但是我们可以通过配置进行修改: 190 | 191 | ```js 192 | const config = { 193 | plugins: [ 194 | ['@tarojs/plugin-inject', { 195 | // array:新增 View 组件也不需要渲染子组件 196 | voidComponents: ['view'], 197 | // function:直接修改 voidComponents 198 | voidComponents (origin) { 199 | // 现在 audio 能渲染子组件了 200 | origin.delete('audio') 201 | return origin 202 | }, 203 | }] 204 | ] 205 | } 206 | ``` 207 | 208 | #### 5. nestElements 209 | 210 | 对于不支持模板递归的小程序(如微信、QQ、京东小程序),Taro3 默认下述组件的模板能递归自身: 211 | 212 | ```js 213 | // 正数代表最多可递归 N 次 214 | // -1 代表最多可递归 config.baseLevel 次 215 | export const nestElements = new Map([ 216 | ['view', -1], 217 | ['catch-view', -1], 218 | ['cover-view', -1], 219 | ['static-view', -1], 220 | ['pure-view', -1], 221 | ['block', -1], 222 | ['text', -1], 223 | ['static-text', 6], 224 | ['slot', 8], 225 | ['slot-view', 8], 226 | ['label', 6], 227 | ['form', 4], 228 | ['scroll-view', 4], 229 | ['swiper', 4], 230 | ['swiper-item', 4] 231 | ]) 232 | ``` 233 | 234 | 默认原生自定义组件可递归 `config.baseLevel` 次,因为 Taro 不清楚原生自定义组件是否存在可递归自身的情况。例如 `vant-weapp` 中,`van-image` 组件不存在递归自身的情况,而 `van-cell` 这种容器类组件可能递归自身。 235 | 236 | 但是对 `van-image` 组件循环 `config.baseLevel` 次是不必要的,会增大小程序包体积,针对这种情况我们可以通过配置进行修改: 237 | 238 | ```js 239 | const config = { 240 | plugins: [ 241 | ['@tarojs/plugin-inject', { 242 | // object:修改 swiper、swiper-item 组件的最大循环次数 243 | nestElements: { 244 | 'swiper': 2, 245 | 'swiper-item': 2 246 | }, 247 | // function:直接修改 nestElements 248 | nestElements (origin) { 249 | // 现在 van-image 只能循环一次了 250 | origin.set('van-image', 1) 251 | return origin 252 | }, 253 | }] 254 | ] 255 | } 256 | ``` 257 | 258 | #### 6. thirdPartyComponents 259 | 260 | > v1.0.2+ 开始支持,且需要 Taro v3.4.10+ 261 | 262 | 在默认情况下,第三方自定义组件的属性会被编译为形如:``。 263 | 264 | 这时自定义组件声明的默认值会失效(详情请浏览 [#11575](https://github.com/NervJS/taro/issues/11575))。 265 | 266 | ```js 267 | Component({ 268 | props: { 269 | image: { 270 | type: String, 271 | value: 'default' 272 | } 273 | } 274 | }) 275 | ``` 276 | 277 | 所以我们需要为此属性增加默认值,把它编译为形如:``。 278 | 279 | 用法: 280 | 281 | ```js 282 | const config = { 283 | plugins: [ 284 | ['@tarojs/plugin-inject', { 285 | thirdPartyComponents: { 286 | // 为 `van-empty` 组件的 image 属性设置默认值 'default' 287 | 'van-empty': { 288 | 'image': "'default'" 289 | } 290 | } 291 | }] 292 | ] 293 | } 294 | ``` 295 | 296 | ### 模块补充 297 | 298 | 在前面的 components 示例中,给Text组件添加了新属性x-props后,@tarojs/components的类型文件中没有新定义的属性,typescript无法识别Text组件的x-props属性,导致vscode提示属性不存在,可以通过[模块补充](https://docs.taro.zone/docs/platform-plugin-how#%E7%B1%BB%E5%9E%8B)的方式来修正 299 | 300 | ```typescript 301 | //tsconfig.json 302 | { 303 | "compilerOptions": { 304 | "typeRoots": ["src/global.d.ts"], 305 | } 306 | } 307 | 308 | // global.d.ts 309 | declare module '@tarojs/components' { 310 | export * from '@tarojs/components/types/index'; 311 | 312 | // 下面示例是react的定义,vue下可能有所不同,原理是一样的 313 | import { ComponentType } from 'react'; 314 | import { TextProps as OldTextProps } from '@tarojs/components/types/Text'; 315 | 316 | // 修改的Props 317 | interface TextProps extends OldTextProps { 318 | xProps?: string; 319 | } 320 | 321 | export const Text: ComponentType; 322 | } 323 | ``` 324 | 325 | 326 | ## License 327 | 328 | MIT License 329 | 330 | Copyright (c) O2Team 331 | 332 | Permission is hereby granted, free of charge, to any person obtaining a copy 333 | of this software and associated documentation files (the "Software"), to deal 334 | in the Software without restriction, including without limitation the rights 335 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 336 | copies of the Software, and to permit persons to whom the Software is 337 | furnished to do so, subject to the following conditions: 338 | 339 | The above copyright notice and this permission notice shall be included in all 340 | copies or substantial portions of the Software. 341 | 342 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 343 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 344 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 345 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 346 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 347 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 348 | SOFTWARE. 349 | --------------------------------------------------------------------------------