├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── example ├── js-taro-react │ ├── .editorconfig │ ├── .eslintrc │ ├── .npmrc │ ├── babel.config.js │ ├── config │ │ ├── dev.js │ │ ├── index.js │ │ └── prod.js │ ├── package-lock.json │ ├── package.json │ ├── project.config.json │ ├── src │ │ ├── app.config.js │ │ ├── app.css │ │ ├── app.js │ │ ├── components │ │ │ └── BaseComponent.jsx │ │ ├── index.html │ │ ├── package-test │ │ │ └── pages │ │ │ │ └── test │ │ │ │ ├── index.config.js │ │ │ │ └── index.jsx │ │ └── pages │ │ │ ├── arrowa │ │ │ └── index.jsx │ │ │ ├── arrowb │ │ │ └── index.jsx │ │ │ ├── arrowc │ │ │ └── index.jsx │ │ │ ├── classa │ │ │ └── index.jsx │ │ │ ├── classb │ │ │ └── index.jsx │ │ │ ├── classc │ │ │ └── index.jsx │ │ │ ├── connectarrow │ │ │ └── index.jsx │ │ │ ├── connectclass │ │ │ └── index.jsx │ │ │ ├── connectfunction │ │ │ └── index.jsx │ │ │ ├── functiona │ │ │ └── index.jsx │ │ │ ├── functionb │ │ │ └── index.jsx │ │ │ ├── functionc │ │ │ └── index.jsx │ │ │ └── index │ │ │ ├── index.config.js │ │ │ ├── index.css │ │ │ └── index.jsx │ └── yarn.lock └── ts-taro-react │ ├── .editorconfig │ ├── .eslintrc │ ├── .npmrc │ ├── babel.config.js │ ├── config │ ├── dev.js │ ├── index.js │ └── prod.js │ ├── global.d.ts │ ├── log.js │ ├── package-lock.json │ ├── package.json │ ├── project.config.json │ ├── src │ ├── app.config.ts │ ├── app.scss │ ├── app.ts │ ├── components │ │ └── BaseComponent.tsx │ ├── index.html │ ├── package-test │ │ └── pages │ │ │ └── test │ │ │ ├── index.config.ts │ │ │ └── index.tsx │ └── pages │ │ └── index │ │ ├── index.config.ts │ │ ├── index.scss │ │ └── index.tsx │ ├── tsconfig.json │ └── yarn.lock ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | yarn.lock -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | test 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "trailingComma": "all", 6 | "printWidth": 100 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Thomas Trainset 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taro React 小程序注入全局组件 2 | 3 | ## 缘由 4 | 5 | 众所周知,小程序中不能定义全局组件,如果我们想自定义一个 modal 弹窗,来替代 **wx.showModal** 的话,则需要在每个页面手动的引入组件。随着项目越来越大,手动引入组件无疑是繁琐和低效的,因而开发了这个 webpack-loader 代替手动操作。 6 | 7 | ## 作用 8 | 9 | 为每个页面注入全局组件。支持主包和分包,支持类写法和函数写法。 10 | 11 | ## 环境 12 | 13 | `taro react` 14 | 15 | ## 安装 16 | 17 | `npm install taro-inject-component-loader -D` 。 18 | 19 | ## 配置 20 | 21 | ### Webpack 配置 22 | 23 | ```ts 24 | webpackChain(chain) { 25 | chain.merge({ 26 | module: { 27 | rule: { 28 | injectBaseComponentLoader: { 29 | test: /\.tsx$/, 30 | use: [ 31 | { 32 | loader: 'taro-inject-component-loader', 33 | logError: true, 34 | options: { 35 | importPath: '@components/BaseComponent', 36 | isPage(filePath) { 37 | // 兼容 windows 38 | const formatFilePath = filePath.replace(/\\/g, '/') 39 | return /(package-.+\/)?pages\/[A-Za-z0-9-]+\/index\.[tj]sx\$/.test(filePath) 40 | } 41 | }, 42 | }, 43 | ], 44 | }, 45 | }, 46 | }, 47 | }); 48 | }, 49 | ``` 50 | 51 | ### 配置项 52 | 53 | | 字段 | 必填 | 默认 | 含义 | 54 | | ---------- | ---- | --------------------------------------------------------------------------- | ---------------------- | 55 | | importPath | 是 | 无 | 导入路径 | 56 | | logError | 否 | true | 控制台打印错误 | 57 | | isPage | 否 | (path) => /(package-.+\/)?pages\/[A-Za-z0-9-]+\/index\.[tj]sx\$/.test(path) | 判断当前文件是不是页面 | 58 | 59 | isPage 不传的情况下,默认会将 `src/pages/页面名称/index.[tj]sx` 和 `src/package-模块名称/pages/页面名称/index.[tj]sx` 这两种情形下的文件识别为页面。 60 | 61 | ## 效果 62 | 63 | ### 源代码 64 | 65 | 页面组件 66 | 67 | ```tsx 68 | 69 | import { View } from '@taro/components' 70 | 71 | export default function Index() { 72 | return 哈哈哈哈哈 73 | } 74 | ``` 75 | 76 | 要注入的组件 77 | 78 | ```tsx 79 | 80 | import { View } from '@taro/components' 81 | 82 | export default function () { 83 | return WebpackInject 84 | } 85 | ``` 86 | 87 | ### 注入后的代码 88 | 89 | 会自动注入为页面根节点的最后一个子元素 90 | 91 | ```tsx 92 | 93 | import { View } from '@taro/components' 94 | import WebpackInject from '@components/BaseComponent' 95 | 96 | export default function Index() { 97 | return ( 98 | 99 | 哈哈哈哈哈 100 | 101 | 102 | ) 103 | } 104 | ``` 105 | 106 | ## 语法支持 107 | 108 | 下面提到的写法中,都支持注入组件。对于识别为页面,但由于语法不支持,导致注入失败的页面,loader 会在控制台抛出警告,请注意查看。 109 | 110 | ```tsx 111 | // 导出匿名函数 112 | export default function() { 113 | return 114 | } 115 | 116 | // 导出具名函数 117 | export default function A() { 118 | return 119 | } 120 | 121 | // 导出匿名箭头函数 122 | export default () => { 123 | return 124 | } 125 | 126 | export default () => 127 | 128 | // 导出匿名类 129 | export default class { 130 | render() { 131 | return 132 | } 133 | } 134 | 135 | // 导出具名类 136 | export default class A { 137 | render() { 138 | return 139 | } 140 | } 141 | ``` 142 | 143 | 此外,还可以使用表达式导出 144 | 145 | ```tsx 146 | // 导出普通函数 147 | function A() { 148 | return 149 | } 150 | 151 | const A = function() { 152 | return 153 | } 154 | 155 | // 导出箭头函数 156 | const A = () => { 157 | return 158 | } 159 | const A = () => 160 | 161 | // 导出类 162 | class A { 163 | render() { 164 | return 165 | } 166 | } 167 | 168 | const A = class { 169 | render() { 170 | return 171 | } 172 | } 173 | 174 | const A = class extends Component { 175 | render() { 176 | return 177 | } 178 | } 179 | 180 | export default A 181 | ``` 182 | 183 | ## 注意事项 184 | 185 | ### 要注入的组件需要默认导出 186 | 187 | ```tsx 188 | import { View } from '@taro/components' 189 | 190 | // 默认导出 191 | export default () => 192 | ``` 193 | 194 | ### 跳过代码注入 195 | 196 | 只要检测到页面里有 importPath 的路径,无论代码中有没有用到该组件,都不会自动注入。 197 | 198 | ```tsx 199 | import { View } from '@taro/components' 200 | // 因为检测到了手动从 importPath 路径导入组件, 所以不会注入组件 201 | import WebpackInject from 'importPath' 202 | 203 | export default () => 204 | ``` 205 | 206 | ### 高阶组件代码注入 207 | 208 | loader 不支持高阶组件的代码注入 209 | 210 | ```tsx 211 | const connect = () => { 212 | return CComponent => CComponent 213 | } 214 | 215 | const CustomComponent = () => { 216 | return export default connect arrow function Component 217 | } 218 | 219 | export default memo(connect()(CustomComponent)) 220 | ``` 221 | 222 | 建议手动引入要注入的组件,或者更改代码写法: 223 | 224 | ```tsx 225 | const connect = () => { 226 | return CComponent => CComponent 227 | } 228 | 229 | const CustomComponent = () => { 230 | return export default connect arrow function Component 231 | } 232 | 233 | const HigherComponent = memo(connect()(CustomComponent)) 234 | 235 | export default props => { 236 | return ( 237 | 238 | 239 | 240 | ) 241 | } 242 | ``` 243 | 244 | ### 自闭合标签 245 | 246 | 自闭合标签组件不支持组件注入。 247 | 248 | ```tsx 249 | export default () => 274 | 275 | ) 276 | ) 277 | ``` 278 | 279 | 建议手动引入组件,或者将代码改为: 280 | 281 | ```tsx 282 | export default () => ( 283 | return ( 284 | 285 | 286 | 287 | 288 | 289 | 290 | ) 291 | ) 292 | ``` 293 | 294 | ## 代码示例 295 | 296 | [ts 版本](example/ts-taro-react/config/index.js) 297 | 298 | [js 版本](example/js-taro-react/config/index.js) 299 | -------------------------------------------------------------------------------- /example/js-taro-react/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /example/js-taro-react/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["taro/react"] 3 | } 4 | -------------------------------------------------------------------------------- /example/js-taro-react/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npm.taobao.org 2 | disturl=https://npm.taobao.org/dist 3 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ 4 | phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/ 5 | electron_mirror=https://npm.taobao.org/mirrors/electron/ 6 | chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver 7 | operadriver_cdnurl=https://npm.taobao.org/mirrors/operadriver 8 | selenium_cdnurl=https://npm.taobao.org/mirrors/selenium 9 | node_inspector_cdnurl=https://npm.taobao.org/mirrors/node-inspector 10 | fsevents_binary_host_mirror=http://npm.taobao.org/mirrors/fsevents/ 11 | -------------------------------------------------------------------------------- /example/js-taro-react/babel.config.js: -------------------------------------------------------------------------------- 1 | // babel-preset-taro 更多选项和默认值: 2 | // https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md 3 | module.exports = { 4 | presets: [ 5 | ['taro', { 6 | framework: 'react', 7 | ts: false 8 | }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /example/js-taro-react/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"development"' 4 | }, 5 | defineConstants: { 6 | }, 7 | mini: {}, 8 | h5: {} 9 | } 10 | -------------------------------------------------------------------------------- /example/js-taro-react/config/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const config = { 4 | projectName: 'js-taro-react', 5 | date: '2021-2-16', 6 | designWidth: 750, 7 | deviceRatio: { 8 | 640: 2.34 / 2, 9 | 750: 1, 10 | 828: 1.81 / 2, 11 | }, 12 | sourceRoot: 'src', 13 | outputRoot: 'dist', 14 | plugins: [], 15 | defineConstants: {}, 16 | copy: { 17 | patterns: [], 18 | options: {}, 19 | }, 20 | alias: { 21 | '@components': path.resolve(__dirname, '..', 'src/components'), 22 | }, 23 | framework: 'react', 24 | mini: { 25 | postcss: { 26 | pxtransform: { 27 | enable: true, 28 | config: {}, 29 | }, 30 | url: { 31 | enable: true, 32 | config: { 33 | limit: 1024, // 设定转换尺寸上限 34 | }, 35 | }, 36 | cssModules: { 37 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 38 | config: { 39 | namingPattern: 'module', // 转换模式,取值为 global/module 40 | generateScopedName: '[name]__[local]___[hash:base64:5]', 41 | }, 42 | }, 43 | }, 44 | webpackChain(chain) { 45 | chain.merge({ 46 | module: { 47 | rule: { 48 | injectBaseComponentLoader: { 49 | test: /\.jsx$/, 50 | use: [ 51 | { 52 | loader: path.resolve(__dirname, '../../../dist/index.js'), 53 | options: { 54 | importPath: '@components/BaseComponent', 55 | }, 56 | }, 57 | ], 58 | }, 59 | }, 60 | }, 61 | }) 62 | }, 63 | }, 64 | h5: { 65 | publicPath: '/', 66 | staticDirectory: 'static', 67 | postcss: { 68 | autoprefixer: { 69 | enable: true, 70 | config: {}, 71 | }, 72 | cssModules: { 73 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 74 | config: { 75 | namingPattern: 'module', // 转换模式,取值为 global/module 76 | generateScopedName: '[name]__[local]___[hash:base64:5]', 77 | }, 78 | }, 79 | }, 80 | webpackChain(chain) { 81 | chain.merge({ 82 | module: { 83 | rule: { 84 | injectBaseComponentLoader: { 85 | test: /\.jsx$/, 86 | use: [ 87 | { 88 | loader: path.resolve(__dirname, '../../../dist/index.js'), 89 | options: { 90 | importSpecifier: '@components/BaseComponent', 91 | componentName: 'BaseComponent', 92 | isPage(filePath) { 93 | return /(package-.+\/)?pages\/.+\/index\.jsx$/.test(filePath) 94 | }, 95 | }, 96 | }, 97 | ], 98 | }, 99 | }, 100 | }, 101 | }) 102 | }, 103 | }, 104 | } 105 | 106 | module.exports = function(merge) { 107 | if (process.env.NODE_ENV === 'development') { 108 | return merge({}, config, require('./dev')) 109 | } 110 | return merge({}, config, require('./prod')) 111 | } 112 | -------------------------------------------------------------------------------- /example/js-taro-react/config/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"production"' 4 | }, 5 | defineConstants: { 6 | }, 7 | mini: {}, 8 | h5: { 9 | /** 10 | * 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。 11 | * 参考代码如下: 12 | * webpackChain (chain) { 13 | * chain.plugin('analyzer') 14 | * .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, []) 15 | * } 16 | */ 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/js-taro-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-taro-react", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "js-taro-react", 6 | "templateInfo": { 7 | "name": "default", 8 | "typescript": false, 9 | "css": "none" 10 | }, 11 | "scripts": { 12 | "build:weapp": "taro build --type weapp", 13 | "build:swan": "taro build --type swan", 14 | "build:alipay": "taro build --type alipay", 15 | "build:tt": "taro build --type tt", 16 | "build:h5": "taro build --type h5", 17 | "build:rn": "taro build --type rn", 18 | "build:qq": "taro build --type qq", 19 | "build:jd": "taro build --type jd", 20 | "build:quickapp": "taro build --type quickapp", 21 | "dev:weapp": "npm run build:weapp -- --watch", 22 | "dev:swan": "npm run build:swan -- --watch", 23 | "dev:alipay": "npm run build:alipay -- --watch", 24 | "dev:tt": "npm run build:tt -- --watch", 25 | "dev:h5": "npm run build:h5 -- --watch", 26 | "dev:rn": "npm run build:rn -- --watch", 27 | "dev:qq": "npm run build:qq -- --watch", 28 | "dev:jd": "npm run build:jd -- --watch", 29 | "dev:quickapp": "npm run build:quickapp -- --watch" 30 | }, 31 | "browserslist": [ 32 | "last 3 versions", 33 | "Android >= 4.1", 34 | "ios >= 8" 35 | ], 36 | "author": "", 37 | "dependencies": { 38 | "@babel/runtime": "^7.7.7", 39 | "@styli/taro": "0.22.0", 40 | "@tarojs/components": "3.1.0-beta.4", 41 | "@tarojs/react": "3.1.0-beta.4", 42 | "@tarojs/runtime": "3.1.0-beta.4", 43 | "@tarojs/taro": "3.1.0-beta.4", 44 | "react": "^16.10.0", 45 | "react-dom": "^16.10.0" 46 | }, 47 | "devDependencies": { 48 | "@babel/core": "^7.8.0", 49 | "@tarojs/mini-runner": "3.1.0-beta.4", 50 | "@tarojs/webpack-runner": "3.1.0-beta.4", 51 | "@types/react": "^16.0.0", 52 | "@types/webpack-env": "^1.13.6", 53 | "babel-preset-taro": "3.1.0-beta.4", 54 | "eslint": "^6.8.0", 55 | "eslint-config-taro": "3.1.0-beta.4", 56 | "eslint-plugin-import": "^2.12.0", 57 | "eslint-plugin-react": "^7.8.2", 58 | "eslint-plugin-react-hooks": "^1.6.1", 59 | "stylelint": "9.3.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /example/js-taro-react/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "dist/", 3 | "projectname": "js-taro-react", 4 | "description": "js-taro-react", 5 | "appid": "wx86d4ff7b691abb62", 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": false, 9 | "enhance": false, 10 | "postcss": false, 11 | "preloadBackgroundData": false, 12 | "minified": false, 13 | "newFeature": false, 14 | "coverView": true, 15 | "nodeModules": false, 16 | "autoAudits": false, 17 | "showShadowRootInWxmlPanel": true, 18 | "scopeDataCheck": false, 19 | "uglifyFileName": false, 20 | "checkInvalidKey": true, 21 | "checkSiteMap": true, 22 | "uploadWithSourceMap": true, 23 | "compileHotReLoad": false, 24 | "useMultiFrameRuntime": true, 25 | "useApiHook": true, 26 | "useApiHostProcess": false, 27 | "babelSetting": { 28 | "ignore": [], 29 | "disablePlugins": [], 30 | "outputPath": "" 31 | }, 32 | "enableEngineNative": false, 33 | "bundle": false, 34 | "useIsolateContext": true, 35 | "useCompilerModule": true, 36 | "userConfirmedUseCompilerModuleSwitch": false, 37 | "userConfirmedBundleSwitch": false, 38 | "packNpmManually": false, 39 | "packNpmRelationList": [], 40 | "minifyWXSS": true 41 | }, 42 | "compileType": "miniprogram", 43 | "condition": {} 44 | } -------------------------------------------------------------------------------- /example/js-taro-react/src/app.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | pages: [ 3 | 'pages/index/index', 4 | 'pages/classa/index', 5 | 'pages/classb/index', 6 | 'pages/classc/index', 7 | 'pages/functiona/index', 8 | 'pages/functionb/index', 9 | 'pages/functionc/index', 10 | 'pages/arrowa/index', 11 | 'pages/arrowb/index', 12 | 'pages/connectclass/index', 13 | 'pages/connectfunction/index', 14 | 'pages/connectarrow/index', 15 | ], 16 | subPackages: [ 17 | { 18 | root: 'package-test', 19 | name: 'test', 20 | pages: ['pages/test/index'], 21 | }, 22 | ], 23 | window: { 24 | backgroundTextStyle: 'light', 25 | navigationBarBackgroundColor: '#fff', 26 | navigationBarTitleText: 'WeChat', 27 | navigationBarTextStyle: 'black', 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /example/js-taro-react/src/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdoer/taro-inject-component-loader/30354b13da365152c67b6bf383eb2b32e23dbd27/example/js-taro-react/src/app.css -------------------------------------------------------------------------------- /example/js-taro-react/src/app.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | import './app.css' 3 | 4 | class App extends Component { 5 | 6 | componentDidMount () {} 7 | 8 | componentDidShow () {} 9 | 10 | componentDidHide () {} 11 | 12 | componentDidCatchError () {} 13 | 14 | // this.props.children 是将要会渲染的页面 15 | render () { 16 | return this.props.children 17 | } 18 | } 19 | 20 | export default App 21 | -------------------------------------------------------------------------------- /example/js-taro-react/src/components/BaseComponent.jsx: -------------------------------------------------------------------------------- 1 | import { View } from '@styli/taro' 2 | import React, { FC, memo } from 'react' 3 | 4 | export default memo(({}) => { 5 | console.log(1111) 6 | return ( 7 | 8 | 9 | BaseComponent 10 | 11 | 12 | webpack inject 13 | 14 | 15 | ) 16 | }) 17 | -------------------------------------------------------------------------------- /example/js-taro-react/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /example/js-taro-react/src/package-test/pages/test/index.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '分包子页面-函数式组件', 3 | } 4 | -------------------------------------------------------------------------------- /example/js-taro-react/src/package-test/pages/test/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | export default memo(function() { 5 | return ( 6 | 7 | 8 | ) 9 | }) 10 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/arrowa/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | class CustomComponent3 extends Component { 5 | render() { 6 | return Component C 7 | } 8 | } 9 | 10 | const CustomComponent2 = () => Component B 11 | 12 | const CustomComponent = () => { 13 | return ( 14 | 15 | export default arrow function Component A 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default CustomComponent 23 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/arrowb/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | class CustomComponent3 extends Component { 5 | render() { 6 | return Component C 7 | } 8 | } 9 | 10 | const CustomComponent2 = () => Component B 11 | 12 | export default () => { 13 | return ( 14 | 15 | export default arrow function Component A 16 | 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/arrowc/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | const CustomComponent = () => { 5 | return export default arrow function Component A 6 | } 7 | 8 | export default CustomComponent 9 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/classa/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | class CustomComponent extends Component { 5 | render() { 6 | return export default class Component A 7 | } 8 | } 9 | 10 | export default CustomComponent 11 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/classb/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | export default class CustomComponent extends Component { 5 | render() { 6 | return export default class Component B 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/classc/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | export default class extends Component { 5 | render() { 6 | return export default class Component C 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/connectarrow/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | const connect = () => { 5 | return CComponent => CComponent 6 | } 7 | 8 | const CustomComponent = () => { 9 | return export default connect arrow function Component 10 | } 11 | 12 | export default connect()(CustomComponent) 13 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/connectclass/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | const connect = () => { 5 | return CComponent => CComponent 6 | } 7 | 8 | class CustomComponent extends Component { 9 | render() { 10 | return export default connect class Component 11 | } 12 | } 13 | 14 | export default connect()(CustomComponent) 15 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/connectfunction/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | const connect = () => { 5 | return CComponent => CComponent 6 | } 7 | 8 | function CustomComponent() { 9 | return export default connect function Component 10 | } 11 | 12 | export default connect()(CustomComponent) 13 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/functiona/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | function CustomComponent() { 5 | return ( 6 | 7 | export default function Component A 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | const CustomComponent2 = function() { 15 | return Component B 16 | } 17 | 18 | class CustomComponent3 extends Component { 19 | render() { 20 | return Component C 21 | } 22 | } 23 | 24 | export default CustomComponent 25 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/functionb/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | const CustomComponent2 = function() { 5 | return Component B 6 | } 7 | 8 | class CustomComponent3 extends Component { 9 | render() { 10 | return Component C 11 | } 12 | } 13 | 14 | export default function CustomComponent() { 15 | return ( 16 | 17 | export default function Component B 18 | 19 | 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/functionc/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | const CustomComponent2 = function() { 5 | return Component B 6 | } 7 | 8 | class CustomComponent3 extends Component { 9 | render() { 10 | return Component C 11 | } 12 | } 13 | 14 | export default function() { 15 | return ( 16 | 17 | export default function Component C 18 | 19 | 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/index/index.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '首页' 3 | } 4 | -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/index/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdoer/taro-inject-component-loader/30354b13da365152c67b6bf383eb2b32e23dbd27/example/js-taro-react/src/pages/index/index.css -------------------------------------------------------------------------------- /example/js-taro-react/src/pages/index/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | import { navigateTo } from '@tarojs/taro' 4 | import './index.css' 5 | 6 | const data = [ 7 | { 8 | title: 'Class A', 9 | path: '/pages/classa/index', 10 | }, 11 | { 12 | title: 'Class B', 13 | path: '/pages/classb/index', 14 | }, 15 | { 16 | title: 'Class C', 17 | path: '/pages/classc/index', 18 | }, 19 | { 20 | title: 'Function A', 21 | path: '/pages/functiona/index', 22 | }, 23 | { 24 | title: 'Function B', 25 | path: '/pages/functionb/index', 26 | }, 27 | { 28 | title: 'Function C', 29 | path: '/pages/functionc/index', 30 | }, 31 | { 32 | title: 'Arrow Function A', 33 | path: '/pages/arrowa/index', 34 | }, 35 | { 36 | title: 'Arrow Function b', 37 | path: '/pages/arrowb/index', 38 | }, 39 | { 40 | title: 'connect class', 41 | path: '/pages/connectclass/index', 42 | }, 43 | { 44 | title: 'connect function', 45 | path: '/pages/connectfunction/index', 46 | }, 47 | { 48 | title: 'connect arrow', 49 | path: '/pages/connectarrow/index', 50 | }, 51 | ] 52 | 53 | export default class Index extends Component { 54 | componentWillMount() {} 55 | 56 | componentDidMount() {} 57 | 58 | componentWillUnmount() {} 59 | 60 | componentDidShow() {} 61 | 62 | componentDidHide() {} 63 | 64 | render() { 65 | return ( 66 | 67 | {data.map((item, index) => ( 68 | navigateTo({ url: item.path })} 81 | > 82 | {item.title} 83 | 84 | ))} 85 | 86 | ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /example/ts-taro-react/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /example/ts-taro-react/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["taro/react"] 3 | } 4 | -------------------------------------------------------------------------------- /example/ts-taro-react/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npm.taobao.org 2 | disturl=https://npm.taobao.org/dist 3 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ 4 | phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/ 5 | electron_mirror=https://npm.taobao.org/mirrors/electron/ 6 | chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver 7 | operadriver_cdnurl=https://npm.taobao.org/mirrors/operadriver 8 | selenium_cdnurl=https://npm.taobao.org/mirrors/selenium 9 | node_inspector_cdnurl=https://npm.taobao.org/mirrors/node-inspector 10 | fsevents_binary_host_mirror=http://npm.taobao.org/mirrors/fsevents/ 11 | -------------------------------------------------------------------------------- /example/ts-taro-react/babel.config.js: -------------------------------------------------------------------------------- 1 | // babel-preset-taro 更多选项和默认值: 2 | // https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md 3 | module.exports = { 4 | presets: [ 5 | ['taro', { 6 | framework: 'react', 7 | ts: true 8 | }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /example/ts-taro-react/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"development"' 4 | }, 5 | defineConstants: { 6 | }, 7 | mini: {}, 8 | h5: {} 9 | } 10 | -------------------------------------------------------------------------------- /example/ts-taro-react/config/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const config = { 4 | projectName: 'taro-example', 5 | date: '2020-12-31', 6 | designWidth: 750, 7 | deviceRatio: { 8 | 640: 2.34 / 2, 9 | 750: 1, 10 | 828: 1.81 / 2, 11 | }, 12 | sourceRoot: 'src', 13 | outputRoot: 'dist', 14 | plugins: [], 15 | defineConstants: {}, 16 | copy: { 17 | patterns: [], 18 | options: {}, 19 | }, 20 | framework: 'react', 21 | alias: { 22 | '@components': path.resolve(__dirname, '..', 'src/components'), 23 | }, 24 | mini: { 25 | postcss: { 26 | pxtransform: { 27 | enable: true, 28 | config: {}, 29 | }, 30 | url: { 31 | enable: true, 32 | config: { 33 | limit: 1024, // 设定转换尺寸上限 34 | }, 35 | }, 36 | cssModules: { 37 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 38 | config: { 39 | namingPattern: 'module', // 转换模式,取值为 global/module 40 | generateScopedName: '[name]__[local]___[hash:base64:5]', 41 | }, 42 | }, 43 | }, 44 | webpackChain(chain) { 45 | chain.merge({ 46 | module: { 47 | rule: { 48 | injectBaseComponentLoader: { 49 | test: /\.tsx$/, 50 | use: [ 51 | { 52 | loader: path.resolve(__dirname, '../../../dist/index.js'), 53 | options: { 54 | importPath: '@components/BaseComponent', 55 | }, 56 | }, 57 | ], 58 | }, 59 | }, 60 | }, 61 | }) 62 | }, 63 | }, 64 | h5: { 65 | publicPath: '/', 66 | staticDirectory: 'static', 67 | postcss: { 68 | autoprefixer: { 69 | enable: true, 70 | config: {}, 71 | }, 72 | cssModules: { 73 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 74 | config: { 75 | namingPattern: 'module', // 转换模式,取值为 global/module 76 | generateScopedName: '[name]__[local]___[hash:base64:5]', 77 | }, 78 | }, 79 | }, 80 | webpackChain(chain) { 81 | chain.merge({ 82 | module: { 83 | rule: { 84 | injectBaseComponentLoader: { 85 | test: /\.tsx$/, 86 | use: [ 87 | { 88 | loader: path.resolve(__dirname, '../../../dist/index.js'), 89 | options: { 90 | importSpecifier: '@components/BaseComponent', 91 | componentName: 'BaseComponent', 92 | isPage(filePath) { 93 | return /(package-.+\/)?pages\/.+\/index\.tsx$/.test(filePath) 94 | }, 95 | }, 96 | }, 97 | ], 98 | }, 99 | }, 100 | }, 101 | }) 102 | }, 103 | }, 104 | } 105 | 106 | module.exports = function(merge) { 107 | if (process.env.NODE_ENV === 'development') { 108 | return merge({}, config, require('./dev')) 109 | } 110 | return merge({}, config, require('./prod')) 111 | } 112 | -------------------------------------------------------------------------------- /example/ts-taro-react/config/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"production"' 4 | }, 5 | defineConstants: { 6 | }, 7 | mini: {}, 8 | h5: { 9 | /** 10 | * 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。 11 | * 参考代码如下: 12 | * webpackChain (chain) { 13 | * chain.plugin('analyzer') 14 | * .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, []) 15 | * } 16 | */ 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/ts-taro-react/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png'; 2 | declare module '*.gif'; 3 | declare module '*.jpg'; 4 | declare module '*.jpeg'; 5 | declare module '*.svg'; 6 | declare module '*.css'; 7 | declare module '*.less'; 8 | declare module '*.scss'; 9 | declare module '*.sass'; 10 | declare module '*.styl'; 11 | 12 | declare namespace NodeJS { 13 | interface ProcessEnv { 14 | TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq' | 'jd' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/ts-taro-react/log.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | export default function() { 5 | return ( 6 | 7 | 8 | ) 9 | } 10 | o' 11 | 12 | export default class Index extends Component { 13 | 14 | componentWillMount() { } 15 | 16 | componentDidMount() { } 17 | 18 | componentWillUnmount() { } 19 | 20 | componentDidShow() { } 21 | 22 | componentDidHide() { } 23 | 24 | render() { 25 | return ( 26 | 27 | 28 | navigateTo({ url: '/package-test/pages/test/index' })} 38 | > 39 | 点击 40 | 41 | 42 | 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /example/ts-taro-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taro-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "loader example", 6 | "templateInfo": { 7 | "name": "default", 8 | "typescript": true, 9 | "css": "sass" 10 | }, 11 | "scripts": { 12 | "build:weapp": "taro build --type weapp", 13 | "build:swan": "taro build --type swan", 14 | "build:alipay": "taro build --type alipay", 15 | "build:tt": "taro build --type tt", 16 | "build:h5": "taro build --type h5", 17 | "build:rn": "taro build --type rn", 18 | "build:qq": "taro build --type qq", 19 | "build:jd": "taro build --type jd", 20 | "build:quickapp": "taro build --type quickapp", 21 | "dev:weapp": "npm run build:weapp -- --watch", 22 | "dev:swan": "npm run build:swan -- --watch", 23 | "dev:alipay": "npm run build:alipay -- --watch", 24 | "dev:tt": "npm run build:tt -- --watch", 25 | "dev:h5": "npm run build:h5 -- --watch", 26 | "dev:rn": "npm run build:rn -- --watch", 27 | "dev:qq": "npm run build:qq -- --watch", 28 | "dev:jd": "npm run build:jd -- --watch", 29 | "dev:quickapp": "npm run build:quickapp -- --watch" 30 | }, 31 | "browserslist": [ 32 | "last 3 versions", 33 | "Android >= 4.1", 34 | "ios >= 8" 35 | ], 36 | "author": "", 37 | "dependencies": { 38 | "@babel/runtime": "^7.7.7", 39 | "@styli/taro": "0.22.0", 40 | "@tarojs/components": "3.1.0-beta.4", 41 | "@tarojs/react": "3.1.0-beta.4", 42 | "@tarojs/runtime": "3.1.0-beta.4", 43 | "@tarojs/taro": "3.1.0-beta.4", 44 | "react": "^16.10.0", 45 | "react-dom": "^16.10.0" 46 | }, 47 | "devDependencies": { 48 | "@babel/core": "^7.8.0", 49 | "@tarojs/mini-runner": "3.1.0-beta.4", 50 | "@tarojs/webpack-runner": "3.1.0-beta.4", 51 | "@types/react": "^16.0.0", 52 | "@types/webpack-env": "^1.13.6", 53 | "@typescript-eslint/eslint-plugin": "^2.x", 54 | "@typescript-eslint/parser": "^2.x", 55 | "babel-preset-taro": "3.1.0-beta.4", 56 | "eslint": "^6.8.0", 57 | "eslint-config-taro": "3.1.0-beta.4", 58 | "eslint-plugin-import": "^2.12.0", 59 | "eslint-plugin-react": "^7.8.2", 60 | "eslint-plugin-react-hooks": "^1.6.1", 61 | "stylelint": "9.3.0", 62 | "typescript": "^3.7.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /example/ts-taro-react/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "dist/", 3 | "projectname": "taro-example", 4 | "description": "loader example", 5 | "appid": "touristappid", 6 | "setting": { 7 | "urlCheck": true, 8 | "scopeDataCheck": false, 9 | "coverView": true, 10 | "es6": false, 11 | "postcss": true, 12 | "compileHotReLoad": false, 13 | "lazyloadPlaceholderEnable": false, 14 | "preloadBackgroundData": false, 15 | "minified": true, 16 | "autoAudits": false, 17 | "newFeature": false, 18 | "uglifyFileName": false, 19 | "uploadWithSourceMap": true, 20 | "useIsolateContext": true, 21 | "nodeModules": false, 22 | "enhance": true, 23 | "useMultiFrameRuntime": true, 24 | "useApiHook": true, 25 | "useApiHostProcess": true, 26 | "showShadowRootInWxmlPanel": true, 27 | "packNpmManually": false, 28 | "packNpmRelationList": [], 29 | "minifyWXSS": true 30 | }, 31 | "compileType": "miniprogram", 32 | "condition": {} 33 | } -------------------------------------------------------------------------------- /example/ts-taro-react/src/app.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | pages: ['pages/index/index'], 3 | subPackages: [ 4 | { 5 | root: 'package-test', 6 | name: 'test', 7 | pages: ['pages/test/index'], 8 | }, 9 | ], 10 | window: { 11 | backgroundTextStyle: 'light', 12 | navigationBarBackgroundColor: '#fff', 13 | navigationBarTitleText: 'WeChat', 14 | navigationBarTextStyle: 'black' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/ts-taro-react/src/app.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdoer/taro-inject-component-loader/30354b13da365152c67b6bf383eb2b32e23dbd27/example/ts-taro-react/src/app.scss -------------------------------------------------------------------------------- /example/ts-taro-react/src/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | import './app.scss' 3 | 4 | class App extends Component { 5 | 6 | componentDidMount () {} 7 | 8 | componentDidShow () {} 9 | 10 | componentDidHide () {} 11 | 12 | componentDidCatchError () {} 13 | 14 | // this.props.children 是将要会渲染的页面 15 | render () { 16 | return this.props.children 17 | } 18 | } 19 | 20 | export default App 21 | -------------------------------------------------------------------------------- /example/ts-taro-react/src/components/BaseComponent.tsx: -------------------------------------------------------------------------------- 1 | import { View } from '@styli/taro' 2 | import React, { memo } from 'react' 3 | 4 | export default memo(({ }) => { 5 | return ( 6 | 7 | BaseComponent 8 | webpack inject 9 | 10 | ) 11 | }) 12 | -------------------------------------------------------------------------------- /example/ts-taro-react/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /example/ts-taro-react/src/package-test/pages/test/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '分包子页面-函数式组件', 3 | } 4 | -------------------------------------------------------------------------------- /example/ts-taro-react/src/package-test/pages/test/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | 4 | 5 | function a() { 6 | const Index222 = 1 7 | 8 | console.log(Index222) 9 | } 10 | 11 | const Index222 = class extends Component { 12 | 13 | render() { 14 | return ( 15 | 16 | 哈哈哈哈哈哈哈 17 | 18 | ) 19 | } 20 | } 21 | 22 | const X = () => 哈哈哈 23 | 24 | export default () => 哈哈哈 25 | -------------------------------------------------------------------------------- /example/ts-taro-react/src/pages/index/index.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | navigationBarTitleText: '主包-类组件' 3 | } 4 | -------------------------------------------------------------------------------- /example/ts-taro-react/src/pages/index/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdoer/taro-inject-component-loader/30354b13da365152c67b6bf383eb2b32e23dbd27/example/ts-taro-react/src/pages/index/index.scss -------------------------------------------------------------------------------- /example/ts-taro-react/src/pages/index/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from '@styli/taro' 3 | import './index.scss' 4 | import { navigateTo } from '@tarojs/taro' 5 | 6 | class A extends Component { 7 | render() { 8 | return 哈哈哈哈哈啊哈哈哈哈哈=========A组件 9 | } 10 | } 11 | 12 | export default class Index extends Component { 13 | 14 | componentWillMount() { } 15 | 16 | componentDidMount() { } 17 | 18 | componentWillUnmount() { } 19 | 20 | componentDidShow() { } 21 | 22 | componentDidHide() { } 23 | 24 | render() { 25 | return ( 26 | 27 | 28 | navigateTo({ url: '/package-test/pages/test/index' })} 38 | > 39 | 点击 40 | 41 | 42 | 43 | 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/ts-taro-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "removeComments": false, 6 | "preserveConstEnums": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "outDir": "lib", 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "strictNullChecks": true, 15 | "sourceMap": true, 16 | "baseUrl": ".", 17 | "rootDir": ".", 18 | "jsx": "react", 19 | "jsxFactory": "React.createElement", 20 | "allowJs": true, 21 | "resolveJsonModule": true, 22 | "typeRoots": [ 23 | "node_modules/@types", 24 | "global.d.ts" 25 | ], 26 | "paths": { 27 | "@components/*": ["src/components/*"] 28 | } 29 | }, 30 | "exclude": [ 31 | "node_modules", 32 | "dist" 33 | ], 34 | "compileOnSave": false 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.1.1", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist" 8 | ], 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "homepage": "https://github.com/xdoer/taro-inject-component-loader", 13 | "bugs": { 14 | "url": "https://github.com/xdoer/taro-inject-component-loader/issues", 15 | "email": "gotoanything@foxmail.com" 16 | }, 17 | "keywords": [ 18 | "taro", 19 | "react", 20 | "loader", 21 | "webpack", 22 | "inject" 23 | ], 24 | "scripts": { 25 | "start": "tsdx watch --format cjs", 26 | "build": "tsdx build --format cjs", 27 | "test": "tsdx test", 28 | "lint": "tsdx lint", 29 | "prepare": "tsdx build --format cjs", 30 | "size": "size-limit", 31 | "analyze": "size-limit --why" 32 | }, 33 | "husky": { 34 | "hooks": { 35 | "pre-commit": "tsdx lint" 36 | } 37 | }, 38 | "name": "taro-inject-component-loader", 39 | "author": "xdoer", 40 | "module": "dist/taro-inject-component-loader.esm.js", 41 | "size-limit": [ 42 | { 43 | "path": "dist/taro-inject-component-loader.cjs.production.min.js", 44 | "limit": "10 KB" 45 | }, 46 | { 47 | "path": "dist/taro-inject-component-loader.esm.js", 48 | "limit": "10 KB" 49 | } 50 | ], 51 | "peerDependencies": { 52 | "webpack": "^4.0.0 || ^5.0.0" 53 | }, 54 | "dependencies": { 55 | "loader-utils": "^2.0.0", 56 | "schema-utils": "^3.0.0" 57 | }, 58 | "devDependencies": { 59 | "@size-limit/preset-small-lib": "^4.9.1", 60 | "@types/loader-utils": "^2.0.1", 61 | "@types/schema-utils": "^2.4.0", 62 | "husky": "^4.3.6", 63 | "size-limit": "^4.9.1", 64 | "tsdx": "^0.14.1", 65 | "tslib": "^2.0.3", 66 | "typescript": "^4.1.3" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import generate from '@babel/generator' 2 | import traverse from '@babel/traverse' 3 | import utils from '@babel/types' 4 | import { parse } from '@babel/parser' 5 | import { getOptions } from 'loader-utils' 6 | import { validate } from 'schema-utils' 7 | 8 | const schema = { 9 | type: 'object', 10 | properties: { 11 | importPath: { 12 | type: 'string', 13 | }, 14 | logError: { 15 | type: 'boolean' 16 | }, 17 | isPage: { 18 | instanceof: 'Function', 19 | }, 20 | }, 21 | additionalProperties: false, 22 | } 23 | 24 | export default function (source: string) { 25 | // @ts-ignore 26 | const webpackEnv = this 27 | 28 | const options = getOptions(webpackEnv) 29 | 30 | validate(schema as any, options, { name: 'taro-inject-component-loader' }) 31 | 32 | const { importPath = '', componentName = 'WebpackInjected', logError = true, isPage = defaultJudgePage } = options || {} 33 | 34 | // 获取原始文件地址 35 | const filePath = webpackEnv.resourcePath 36 | 37 | if (typeof isPage === 'function' && isPage(filePath)) { 38 | // 生成 AST 39 | const ast: any = parse(source, { 40 | sourceType: 'module', 41 | plugins: ['jsx', 'typescript', 'classProperties'], 42 | }) 43 | 44 | // 如果有导入申明,则默认表示已手动导入了组件 45 | let insert = false 46 | 47 | // 保存所有顶层的声明 48 | const declarations = new Map() 49 | 50 | traverse(ast, { 51 | // 查找是否有导入 52 | ImportDeclaration(path) { 53 | if (path.node.source.value === importPath) { 54 | insert = true 55 | } 56 | }, 57 | 58 | // 收集页面文件里的所有申明 59 | // 类组件 60 | ClassDeclaration(path) { 61 | // 如果不是顶层的申明,则直接返回 62 | if (path.parent.type !== 'Program') return 63 | 64 | const type = path.node.type 65 | const name = path.node.id.name 66 | declarations.set(name, type) 67 | }, 68 | 69 | // 函数申明 70 | FunctionDeclaration(path) { 71 | // 如果不是顶层的申明,则直接返回 72 | if (path.parent.type !== 'Program') return 73 | 74 | const type = path.node.type 75 | const name = path.node.id?.name 76 | if (!name) return 77 | 78 | declarations.set(name, type) 79 | }, 80 | 81 | // 表达式申明 82 | VariableDeclaration(path) { 83 | // 如果不是顶层的申明,则直接返回 84 | if (path.parent.type !== 'Program') return 85 | 86 | path.node.declarations.forEach((declaration: any) => { 87 | 88 | // const a = () => {} 89 | if (declaration.init?.type === 'ArrowFunctionExpression') { 90 | const type = declaration.init?.type 91 | const name = declaration.id?.name 92 | declarations.set(name, type) 93 | } 94 | 95 | // const a = function(){} 96 | if (declaration.init?.type === 'FunctionExpression') { 97 | const type = declaration.init.type 98 | const name = declaration.id.name 99 | declarations.set(name, type) 100 | } 101 | 102 | // const a = class {} 103 | if (declaration.init?.type === 'ClassExpression') { 104 | const type = declaration.init.type 105 | const name = declaration.id.name 106 | declarations.set(name, type) 107 | } 108 | }) 109 | }, 110 | }) 111 | 112 | if (!insert) { 113 | // 记录组件插入状态 114 | const state = { 115 | importedDeclaration: false, 116 | importedComponent: false, 117 | } 118 | 119 | traverse(ast, { 120 | // 添加申明 121 | ImportDeclaration(path) { 122 | if (!state.importedDeclaration) { 123 | state.importedDeclaration = true 124 | path.insertBefore( 125 | utils.importDeclaration( 126 | [ 127 | utils.importDefaultSpecifier(utils.identifier('' + componentName)), 128 | ], 129 | utils.stringLiteral('' + importPath), 130 | ), 131 | ) 132 | } 133 | }, 134 | 135 | // 默认导出的为页面组件 136 | ExportDefaultDeclaration(path) { 137 | 138 | // 如果默认导出的是函数 139 | if (path.node.declaration.type === 'FunctionDeclaration') { 140 | const mainFnBody = path.node.declaration.body.body 141 | const length = mainFnBody.length 142 | const last = mainFnBody[length - 1] 143 | insertComponent(last, '' + componentName, state) 144 | } 145 | 146 | // 默认导出箭头函数 147 | if (path.node.declaration.type === 'ArrowFunctionExpression') { 148 | // export default () => { return } 149 | if (path.node.declaration.body.type === 'BlockStatement') { 150 | const mainFnBody = path.node.declaration.body.body 151 | const length = mainFnBody.length 152 | const last = mainFnBody[length - 1] 153 | insertComponent(last, '' + componentName, state) 154 | } else { 155 | // export default () => 156 | insertComponent(path.node.declaration.body, '' + componentName, state) 157 | } 158 | } 159 | 160 | // 默认导出类 161 | if (path.node.declaration.type === 'ClassDeclaration') { 162 | traverse(path.node, { 163 | ClassMethod(path) { 164 | if ((path.node.key as any).name === 'render') { 165 | const body = path.node.body.body || [] 166 | const last = body[body.length - 1] 167 | insertComponent(last, '' + componentName, state) 168 | return 169 | } 170 | }, 171 | }, path.scope, path) 172 | } 173 | 174 | // 如果默认导出的是一个申明 175 | if (path.node.declaration.type === "Identifier") { 176 | const name = path.node.declaration.name 177 | const componentType = declarations.get(name) 178 | 179 | traverse(path.parent, { 180 | FunctionDeclaration(path) { 181 | if (path.node.id?.name !== name) return 182 | const mainFnBody = path.node?.body?.body 183 | const length = mainFnBody.length 184 | const last = mainFnBody[length - 1] 185 | insertComponent(last, '' + componentName, state) 186 | }, 187 | ClassDeclaration(path) { 188 | if (path.node.id.name !== name) return 189 | traverse(path.node, { 190 | ClassMethod(path) { 191 | if ((path.node.key as any)?.name !== 'render') return 192 | const body = path.node.body.body || [] 193 | const last = body[body.length - 1] 194 | insertComponent(last, '' + componentName, state) 195 | }, 196 | }, path.scope, path) 197 | }, 198 | VariableDeclarator(path) { 199 | if (path.node.id.type !== 'Identifier') return 200 | if (path.node.id.name !== name) return 201 | if (!path.node.init) return 202 | 203 | if (path.node.init.type !== componentType) return 204 | 205 | if (path.node.init.type === 'FunctionExpression') { 206 | const mainFnBody = path.node.init.body.body 207 | const length = mainFnBody.length 208 | const last = mainFnBody[length - 1] 209 | insertComponent(last, '' + componentName, state) 210 | } 211 | 212 | if (path.node.init.type === 'ClassExpression') { 213 | traverse(path.node, { 214 | ClassMethod(path) { 215 | if ((path.node.key as any).name !== 'render') return 216 | const body = path.node.body.body || [] 217 | const last = body[body.length - 1] 218 | insertComponent(last, '' + componentName, state) 219 | }, 220 | }, path.scope, path) 221 | } 222 | 223 | if (path.node.init.type === 'ArrowFunctionExpression') { 224 | // const A = () => {} 225 | // export default A 226 | if (path.node.init.body.type == 'BlockStatement') { 227 | const mainFnBody = path.node.init.body.body 228 | const length = mainFnBody.length 229 | const last = mainFnBody[length - 1] 230 | insertComponent(last, '' + componentName, state) 231 | } else { 232 | // const A = () =>
233 | // export default A 234 | insertComponent(path.node.init.body, '' + componentName, state) 235 | } 236 | } 237 | } 238 | }) 239 | } 240 | }, 241 | }) 242 | 243 | if (!state.importedComponent && logError) { 244 | webpackEnv.emitWarning(`页面: ${filePath} 注入组件失败,建议手动引入组件。组件注入限制请查阅: https://github.com/xdoer/taro-inject-component-loader`) 245 | 246 | } 247 | if (!state.importedDeclaration && logError) { 248 | webpackEnv.emitWarning(`页面: ${filePath} 注入导入申明失败,建议手动引入组件。组件注入限制请查阅: https://github.com/xdoer/taro-inject-component-loader`) 249 | } 250 | 251 | source = generate(ast).code 252 | } 253 | } 254 | 255 | return source 256 | } 257 | 258 | 259 | function createElement(name: string) { 260 | const reactIdentifier = utils.identifier('React') 261 | const createElementIdentifier = utils.identifier('createElement') 262 | const callee = utils.memberExpression(reactIdentifier, createElementIdentifier) 263 | return utils.callExpression(callee, [utils.identifier(name)]) 264 | } 265 | 266 | function createJSX(name: string) { 267 | return utils.jSXElement( 268 | utils.jSXOpeningElement(utils.jsxIdentifier('' + name), [], true), 269 | null, 270 | [], 271 | true, 272 | ) 273 | } 274 | 275 | function wrapJSX(name: string, temp: any) { 276 | return utils.jSXElement( 277 | utils.jSXOpeningElement(utils.jsxIdentifier('' + name), [], false), 278 | utils.jSXClosingElement(utils.jsxIdentifier('' + name)), 279 | [temp], 280 | false, 281 | ) 282 | } 283 | 284 | function insertComponent(node: any, componentName: string, state: any) { 285 | if (node?.type === 'ReturnStatement') { 286 | // createElement 287 | if (node.argument?.callee?.property?.name === 'createElement' && !state.importedComponent) { 288 | state.importedComponent = true 289 | const reactCreateArguments = node.argument.arguments 290 | reactCreateArguments.push(createElement(componentName)) 291 | } 292 | // JSX 293 | if (node.argument?.type === 'JSXElement' && !state.importedComponent) { 294 | state.importedComponent = true 295 | // @caiminchao 开源方案,只修改了这个方法,就是将node.argument塞入import的组件children的位置 296 | const temp = node.argument; 297 | node.argument = wrapJSX(componentName, temp); 298 | } 299 | } 300 | if (node.type === 'JSXElement' && !state.importedComponent) { 301 | node.children.push(createJSX(componentName)) 302 | } 303 | } 304 | 305 | function defaultJudgePage(filePath: string) { 306 | // 兼容 windows 路径 307 | const formatFilePath = filePath.replace(/\\/g, '/') 308 | return /(package-.+\/)?pages\/[A-Za-z0-9-]+\/index\.[tj]sx$/.test(formatFilePath) 309 | } 310 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | } 35 | } 36 | --------------------------------------------------------------------------------