Skip to content

Commit

Permalink
feat: support rn (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
mater1996 authored Nov 19, 2024
1 parent 9d634c7 commit 843ddca
Show file tree
Hide file tree
Showing 17 changed files with 542 additions and 259 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ mpx-cli-service build --targets=wx,ali
| QQ | qq |
| 头条 | tt |
| 浏览器 | web |
| react-native(安卓) | android |
| react-native(IOS) | ios |

#### serve

Expand Down
2 changes: 1 addition & 1 deletion packages/cli-shared-utils/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

/**
* @typedef { 'wx' | 'ali' | 'swan' | 'qq' | 'tt' | 'dd' | 'web' } Mode
* @typedef { 'wx' | 'ali' | 'swan' | 'qq' | 'tt' | 'dd' | 'web' | 'android' | 'ios' } Mode
* @typedef { { mode: Mode, env: 'development' | 'production' } } Target
*/

Expand Down
6 changes: 4 additions & 2 deletions packages/cli-shared-utils/lib/target.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { makeMap } = require('./utils')
/**
* @type { Mode[] }
*/
const SUPPORT_MODE = ['wx', 'ali', 'swan', 'qq', 'tt', 'dd', 'web', 'tenon']
const SUPPORT_MODE = ['wx', 'ali', 'swan', 'qq', 'tt', 'dd', 'web', 'tenon', 'android', 'ios']

/**
* @type { Object.<Mode, string[]> }
Expand All @@ -21,7 +21,9 @@ const MODE_CONFIG_FILES_MAP = {
tt: ['project.config.json'],
dd: ['project.config.json'],
web: [],
tenon: []
tenon: [],
ios: [],
android: []
}

const DEFAULT_MODE = 'wx'
Expand Down
25 changes: 25 additions & 0 deletions packages/mpx-cli/__tests__/preset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,28 @@ test('test-ssr', async () => {
const pkg = require(path.resolve(cwd, name, 'package.json'))
expect(pkg.devDependencies).toHaveProperty('@mpxjs/vue-cli-plugin-mpx-ssr')
})

test('test-rn', async () => {
const cwd = path.resolve(__dirname, '../../test')
const name = 'test-rn'
await create(
name,
{
force: true,
git: false,
cwd
},
{
srcMode: 'wx',
appid: 'test',
description: 'test',
needRn: true,
cross: true,
plugins: {},
useConfigFiles: true
}
)

const pkg = require(path.resolve(cwd, name, 'package.json'))
expect(pkg.devDependencies).toHaveProperty('@mpxjs/vue-cli-plugin-mpx')
})
14 changes: 8 additions & 6 deletions packages/mpx-cli/lib/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const { clearConsole } = require('@vue/cli/lib/util/clearConsole')
const merge = require('lodash.merge')
const prompts = require('./prompts')
const builtInPreset = require('./preset')
const { createRnProject } = require('./createRn')

async function resolvePreset (args = {}) {
const { p, preset, c, clone } = args
Expand Down Expand Up @@ -81,14 +82,10 @@ async function create (projectName, options, preset = null) {
preset.cssPreprocessor = 'stylus'

// mpx cli 插件
preset.plugins = Object.assign(
{},
preset.plugins,
builtInPreset.plugins
)
preset.plugins = Object.assign({}, preset.plugins, builtInPreset.plugins)

// 合并问答中的preset
prompts.forEach(v => {
prompts.forEach((v) => {
if (preset[v.name]) {
merge(preset, v.preset)
}
Expand Down Expand Up @@ -172,6 +169,7 @@ async function create (projectName, options, preset = null) {
cloudFunc: preset.cloudFunc,
cross: preset.cross,
needSSR: preset.needSSR,
needRn: preset.needRn,
name
})
})
Expand All @@ -196,6 +194,10 @@ async function create (projectName, options, preset = null) {
preset: undefined,
inlinePreset: JSON.stringify(preset)
})

if (!process.env.VUE_CLI_TEST && preset.needRn) {
await createRnProject(targetDir, options)
}
}

module.exports = function (...args) {
Expand Down
80 changes: 80 additions & 0 deletions packages/mpx-cli/lib/createRn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const { hasYarn, hasPnpm3OrLater, execa } = require('@vue/cli-shared-utils')
const path = require('path')
const fs = require('fs-extra')
const { loadOptions } = require('@vue/cli/lib/options')
const PackageManager = require('@vue/cli/lib/util/ProjectPackageManager')

const RN_DEP = {
'@ant-design/icons-react-native': '^2.3.2',
'@ant-design/react-native': '^5.2.2',
'@react-native-async-storage/async-storage': '^1.24.0',
'@react-native-clipboard/clipboard': '^1.14.2',
'@react-native-community/netinfo': '^11.3.2',
'@react-native-masked-view/masked-view': '^0.3.1',
'@react-native/assets-registry': '^0.75.2',
'@react-native/gradle-plugin': '^0.75.2',
'@react-navigation/elements': '^1.3.31',
'@react-navigation/native': '^6.1.18',
'@react-navigation/native-stack': '^6.11.0',
expo: '^51.0.32',
'expo-brightness': '~12.0.1',
'expo-clipboard': '~6.0.3',
react: '18.3.1',
'react-native': '0.75.2',
'react-native-collapsible': '^1.6.1',
'react-native-device-info': '^11.1.0',
'react-native-gesture-handler': '^2.18.1',
'react-native-get-location': '^5.0.0',
'react-native-haptic-feedback': '^2.3.3',
'react-native-linear-gradient': '^2.8.3',
'react-native-maps': '^1.18.0',
'react-native-modal-popover': '^2.1.3',
'react-native-reanimated': '3.15.0',
'react-native-root-siblings': '^5.0.1',
'react-native-safe-area-context': '^4.10.9',
'react-native-screens': '^3.34.0',
'react-native-webview': '^13.12.1'
}

async function createRnProject (targetDir, options) {
const rnProjectPath = path.resolve(targetDir, 'ReactNativeProject')
const packageManager =
options.packageManager ||
loadOptions().packageManager ||
(hasYarn() ? 'yarn' : null) ||
(hasPnpm3OrLater() ? 'pnpm' : 'npm')
const pm = new PackageManager({
context: rnProjectPath,
forcePackageManager: packageManager
})
await execa(
'npx',
[
'@react-native-community/cli',
'init',
'ReactNativeProject',
'--pm',
pm.bin,
'--skip-install',
true,
'--skip-git-init',
true
],
{ stdio: 'inherit', cwd: targetDir }
)
const pkgPath = path.resolve(targetDir, 'ReactNativeProject', 'package.json')
const pkg = require(pkgPath)
Object.assign(pkg.dependencies, RN_DEP)
Object.assign(pkg.scripts, {
'bundle:ios': 'react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ./ios/main.jsbundle --assets-dest ./ios',
'bundle:android': 'react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/'
})
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
await pm.install()
await execa('npx', ['install-expo-modules'], {
stdio: 'inherit',
cwd: rnProjectPath
})
}

module.exports.createRnProject = createRnProject
7 changes: 7 additions & 0 deletions packages/mpx-cli/lib/prompts.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ module.exports = [
type: 'confirm',
default: true
},
{
name: 'needRn',
when: ({ srcMode }) => srcMode === 'wx',
message: '是否需要输出react-native',
type: 'confirm',
default: false
},
{
name: 'needSSR',
when: ({ srcMode, cross }) => srcMode === 'wx' && cross === true,
Expand Down
2 changes: 1 addition & 1 deletion packages/vue-cli-plugin-mpx-typescript/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module.exports = function (api, options) {
api.chainWebpack(webpackConfig => {
webpackConfig.module
.rule('ts')
.test(/\.ts$/)
.test(/\.(ts|tsx)$/)
.use('babel-loader')
.loader(require.resolve('babel-loader'))
.end()
Expand Down
86 changes: 77 additions & 9 deletions packages/vue-cli-plugin-mpx/config/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,6 @@ function addMpWebpackConfig (api, options, config, target) {
// alias config
config.resolve.alias.set('@', api.resolve('src'))

// defind config
config.plugin('mpx-provide-plugin').use(webpack.ProvidePlugin, [
{
process: 'process/browser'
}
])
// 和vue-cli保持同名,方便一次性修改mp和web版本的define参数
config.plugin('define').use(webpack.DefinePlugin, [resolveClientEnv(options)])

Expand Down Expand Up @@ -376,6 +370,54 @@ function addWebWebpackConfig (api, options, config, target) {
config.module.rules.delete('svg')
}

/**
* cli注入的基础RN配置
* @param { import('@vue/cli-service').PluginAPI } api
* @param { import('@vue/cli-service').ProjectOptions } options
* @param { import('webpack-chain') } config
* @param { import('@mpxjs/cli-shared-utils').Target } target
*/
function addRnWebpackConfig (api, options, config, target) {
config.resolve.extensions.add('.tsx').add('.jsx')
config.output.publicPath('/')
config.output.filename('[name].js')
config.optimization.splitChunks(false)
config.externals({
'react-native': 'react-native',
react: 'react',
'@react-native-masked-view/masked-view':
'@react-native-masked-view/masked-view',
'react-native-reanimated': 'react-native-reanimated',
'react-native-gesture-handler': 'react-native-gesture-handler',
'react-native-gesture-handler/DrawerLayout':
'react-native-gesture-handler/DrawerLayout',
'react-native-gesture-handler/Swipeable':
'react-native-gesture-handler/Swipeable',
'@ant-design/icons-react-native': '@ant-design/icons-react-native',
'react-native-safe-area-context': 'react-native-safe-area-context',
'react-native-collapsible': 'react-native-collapsible',
'react-native-modal-popover': 'react-native-modal-popover',
'react/jsx-runtime': 'react/jsx-runtime',
'@react-navigation/native': '@react-navigation/native',
'@react-navigation/native-stack': '@react-navigation/native-stack',
'@react-navigation/elements': '@react-navigation/elements',
'@react-native-async-storage/async-storage':
'@react-native-async-storage/async-storage',
'@react-native-clipboard/clipboard': '@react-native-clipboard/clipboard',
'@react-native-community/netinfo': '@react-native-community/netinfo',
'react-native-device-info': 'react-native-device-info',
'react-native-root-siblings': 'react-native-root-siblings',
'react-native-maps': 'react-native-maps',
'@ant-design/react-native': '@ant-design/react-native',
'expo-brightness': 'expo-brightness',
'expo-clipboard': 'expo-clipboard',
'react-native-webview': 'react-native-webview',
'react-native-get-location': 'react-native-get-location',
'react-native-linear-gradient': 'react-native-linear-gradient',
'react-native-haptic-feedback': 'react-native-haptic-feedback'
})
}

/**
* cli注入的基础配置
* @param { import('@vue/cli-service').PluginAPI } api
Expand All @@ -385,6 +427,8 @@ function addWebWebpackConfig (api, options, config, target) {
*/
module.exports.addBaseConfig = function (api, options, config, target) {
const isWeb = target.mode === 'web'
const isRn = (target.mode === 'android') | (target.mode === 'ios')

config.module
.rule('json')
.test(/\.json$/)
Expand Down Expand Up @@ -482,6 +526,10 @@ module.exports.addBaseConfig = function (api, options, config, target) {
addMpWebpackConfig(api, options, config, target)
}

if (isRn) {
addRnWebpackConfig(api, options, config, target)
}

transformEntry(api, options, config, target)

updateWebpackName(api, config)
Expand All @@ -490,9 +538,14 @@ module.exports.addBaseConfig = function (api, options, config, target) {
/**
* cli注入的基础配置以config方式
* @param { import('@vue/cli-service').PluginAPI } api
* @returns { import('@vue/cli-service').ProjectOptions['configureWebpack'] }
*/
module.exports.resolveBaseRawWebpackConfig = function (api) {
return () => ({
module.exports.resolveBaseRawWebpackConfig = function (api, options, target) {
const isRn = (target.mode === 'android') | (target.mode === 'ios')
/**
* @type { import('@vue/cli-service').ProjectOptions['configureWebpack'] }
*/
const config = {
snapshot: {
managedPaths: [api.resolve('node_modules/')]
},
Expand All @@ -501,8 +554,23 @@ module.exports.resolveBaseRawWebpackConfig = function (api) {
},
infrastructureLogging: {
level: 'none'
},
resolve: {
fallback: {
process: require.resolve('process/browser')
}
}
})
}
if (isRn) {
config.externalsType = 'commonjs'
config.output = {
library: {
type: 'commonjs2',
export: 'default'
}
}
}
return () => config
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/vue-cli-plugin-mpx/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function validWebConfig (webpackConfigs, api, options) {
*/
function resolveBuildWebpackConfigByTarget (api, options, target, args) {
// 强制添加一个修改webpack配置的方法,因为webpack-chain不支持webpack5
addRawConfigBeforeUserConfig(api, resolveBaseRawWebpackConfig(api))
addRawConfigBeforeUserConfig(api, resolveBaseRawWebpackConfig(api, options, target))
let webpackConfigs
if (target.mode === 'web') {
// web配置,使用vue-cli内置的方法获取配置 + mpx-cli 修改后的配置
Expand All @@ -65,7 +65,7 @@ function resolveBuildWebpackConfigByTarget (api, options, target, args) {
*/
function resolveServeWebpackConfigByTarget (api, options, target, args) {
// 强制添加一个修改webpack配置的方法,因为webpack-chain不支持webpack5
addRawConfigBeforeUserConfig(api, resolveBaseRawWebpackConfig(api))
addRawConfigBeforeUserConfig(api, resolveBaseRawWebpackConfig(api, options, target))
const webpackConfigs = [api.resolveWebpackConfig()]
if (target.mode === 'web') {
validWebConfig(webpackConfigs, api, options)
Expand Down
13 changes: 11 additions & 2 deletions packages/vue-cli-plugin-mpx/generator/babel/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module.exports = function (api, options) {
api.render('./template')

const needRn = options.needRn
api.render('./template', {
needRn
})
api.extendPackage({
devDependencies: {
'@babel/core': '^7.10.4',
Expand All @@ -9,4 +11,11 @@ module.exports = function (api, options) {
'@babel/runtime-corejs3': '^7.10.4'
}
})
if (needRn) {
api.extendPackage({
devDependencies: {
'@babel/preset-react': '^7.24.7'
}
})
}
}
Loading

0 comments on commit 843ddca

Please sign in to comment.