一、添加项目依赖
yarn add react-native-web
yarn add babel-plugin-react-native-web css-loader style-loader ts-loader url-loader clean-webpack-plugin html-webpack-plugin file-loader webpack webpack-cli webpack-dev-server --dev
二、package.json 添加启动命令
"web": "webpack-dev-server --mode=development --open"
三、添加webpack.config.js文件
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const {DefinePlugin} = require('webpack');
const appDirectory = path.resolve(__dirname, '../');
const appDirectoryWeb = path.resolve(__dirname, './');
const defaultSetting = require('./setting')
const { cdnUrl, title, outputDir, enableSentry } = defaultSetting
// const { NODE_ENV, port, npm_config_port, VUE_APP_BASE_API, ANALYZE } = process.env
const IS_DEV = (process.argv || []).includes('--mode=development')
console.log(`process--`, IS_DEV)
const babelLoaderConfig = {
test: /\.js$|.ts$|.tsx?$/,
include: [
path.resolve(appDirectory),
path.resolve(appDirectoryWeb, 'index.web.js'),
path.resolve(appDirectory, 'app.tsx'),
path.resolve(appDirectory, 'screens'),
path.resolve(appDirectory, 'node_modules/react-native-uncompiled'),
],
use: {
loader: 'babel-loader',
options: {
cacheDirectory: false,
presets: ['module:metro-react-native-babel-preset'],
plugins: ['react-native-web'],
},
},
};
const imageLoaderConfig = {
test: /\.(gif|jpe?g|png|svg)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[hash:8].[ext]',
esModule: false,
},
},
};
const ttcLoaderConfig = {
// test: /\.(woff2?|eot|ttf|otf|ttc)(\?.*)?$/,
test: /\.ttf$/,
type: 'asset/inline',
// use: {
// loader: 'url-loader',
// options: {
// name: '[name].[hash:8].[ext]',
// esModule: false,
// },
// },
};
const cssLoaderConfig = {
test: /\.css$/,
use: ['style-loader', 'css-loader'],
};
const fileLoaderConfig = {
test: /(postMock|video).html$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
},
},
};
console.log(`wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww---------22`)
module.exports = (_, argv) => ({
mode: 'production',
entry: path.join(appDirectoryWeb, 'index.web.js'),
output: {
path: path.join(appDirectory, IS_DEV ? 'build' : outputDir),
filename: '[name].[contenthash:8].js',
publicPath: IS_DEV ? '/' : cdnUrl,
},
devtool: argv.mode === 'development' ? 'source-map' : undefined,
module: {
rules: [
babelLoaderConfig,
imageLoaderConfig,
ttcLoaderConfig,
cssLoaderConfig,
fileLoaderConfig,
],
},
resolve: {
alias: {
'react-native$': 'react-native-web',
'react-native-svg': 'react-native-svg-web'
},
extensions: ['.web.ts', '.web.tsx', '.web.js', '.ts', '.tsx', '.js'],
},
devServer: {
static: {
directory: path.join(appDirectory, 'public'),
},
historyApiFallback: true,
compress: true,
port: 3001,
},
plugins: [
new HTMLWebpackPlugin({
template: path.join(appDirectoryWeb, './index.html'),
}),
new CleanWebpackPlugin(),
new DefinePlugin({__DEV__: argv.mode === 'development'}),
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'initial',
priority: 20,
},
react: {
test: /[\\/]node_modules[\\/]?react(.*)/,
name: 'react',
priority: 30,
},
commons: {
name: 'chunks-commons',
test: path.join(__dirname, 'componnets'),
minChunks: 3,
priority: 5,
reuseExistingChunk: true,
},
},
},
},
});
四、添加index.web.js项目入口文件
···
import {AppRegistry} from 'react-native';
import App from '../App';
import {name as appMame} from '../app.json';
import './index.css';
AppRegistry.registerComponent(appMame, () => App);
AppRegistry.runApplication(appMame, {
initialProps: {},
rootTag: document.getElementById('root'),
});
···
五、添加index.css文件
/* These styles make the body full-height */
html, body { height: 100%; }
/* These styles disable body scrolling if you are using <ScrollView> */
body { overflow: hidden; }
/* These styles make the root element full-height */
#root { display:flex; height:100%; }
六、新增public文件夹
添加favicon.icon和index.html文件
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>react-native for web</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
七 添加 setting.js文件
module.exports = {
title: 'bnd-saas-approval-h5', // 应用的title
showVconsole: false, // 是否显示console (仅开发环境生效)
cdnUrl: '/xbx/app-h5/', // cdn地址
outputDir: 'saas-xbx-web-app-rn', // 打包输出文件夹名称
pageTrans: true, // 页面切换是否需要转场动画
topProgress: true,
appId: '', // 微信appid
enableSentry: false,
enableMock: false,
SentryDSN: 'https://f7c88eb50e5c4b6a9d69010f4bba512b@o350779.ingest.sentry.io/5225764'
}
八. 兼容多端的代码
获取头部高度问题
const StatusBarManager = Platform.select({
web: () => {
return {
HEIGHT: 44,
};
},
default: () => NativeModules.StatusBarManager,
})();input 样式问题
input focus的时候 带有样式 获取焦点的时候 有黄色的框
以及scroll样式滚动问题
import React from 'react';
import {Platform} from 'react-native';
const WebStyle = {
input: {
outline: 'none',
},
nowrap: {
textWrap: 'noWrap',
},
scrollViewAuto: {
overflow: 'auto',
},
};
const defaultWebStyle = {
input: {},
nowrap: {},
scrollViewAuto: {},
};
const WebPropsStyles = Platform.select({
web: () => WebStyle,
default: () => defaultWebStyle,
})();
export default WebPropsStyles;
- 原生方法和h5方法兼容问题
/**
* @description 关闭当前rn窗口
*/
export const ExitApp = debounce(
(origin?: string) => {
Platform.select({
web: () => {
if (origin === 'APP') {
onBackHome()
} else {
window.history.go(-1)
}
},
default: () => {
NativeModules.BndCommonModule.goBack()
}
})()
},
300,
{
leading: true,
trailing: false
}
)
- 原生拍照和h5拍照兼容问题
const getInputFile = ({ accept, multiple, captrure }: { accept?: string; multiple?: string; captrure?: string }, handle: Function) => {
const inputuploadidDom = document.querySelector('#inputuploadid')
if (inputuploadidDom) {
// inputuploadidDom.removeEventListerAll()
inputuploadidDom.value = null
inputuploadidDom.remove()
}
const InputDom = document.createElement('input')
InputDom.setAttribute('type', 'file')
console.log(`accept`, accept)
if (accept) {
const { isWebAndroid } = getWebPlatform()
const origin = handlerQuery('origin', window.location.href)
console.log(`origin`, origin)
console.log(`window.location.href`, window.location.href, window && window.location && window.location.href)
console.log(`accept === '*`, accept === '*')
console.log(`isWebAndroid`, isWebAndroid)
console.log(`origin === 'APP'`, origin === 'APP')
if (accept === '*' && window && window.location && window.location.href && isWebAndroid && origin === 'APP') {
accept = 'file/*'
}
InputDom.setAttribute('accept', accept)
}
InputDom.setAttribute('id', 'inputuploadid')
if (!InputDom.style) {
InputDom.style = {}
}
InputDom.style.width = 0
InputDom.style.height = 0
InputDom.style.visibility = 'hidden'
InputDom.style.opacity = 0
InputDom.width = 0
InputDom.height = 0
InputDom.value = null
if (multiple === 'multiple') {
InputDom.setAttribute('multiple', multiple)
}
if (captrure) {
InputDom.setAttribute('captrure', captrure)
}
InputDom.addEventListener('change', handle)
console.log(`InputDom`, InputDom)
console.log(`{ accept, multiple, captrure }`, { accept, multiple, captrure })
return InputDom
}
const WebImagePicker = ({ max }) => {
return new Promise(resolve => {
const InputDom = getInputFile({ accept: 'image/*', multiple: max > 1 ? 'multiple' : '' }, (e: any) => {
console.log(`e--`, e)
const files = Array.from(e.target.files)
.slice(0, max)
.map(file => {
file.type = uploadUtils.getFileSuffixType(file.name)
file.fileName = file.name
file.fileSize = file.size
file.path = URL.createObjectURL(file)
file.filePath = file.path
file.fileType = file.type
file.uri = file.path
return file
})
console.log('file----', files)
resolve(files)
})
document.body.append(InputDom)
InputDom.click()
// InputDom.remove()
})
}
const webFilePicker = (data, accept = '*', multiple = 'multiple') => {
return new Promise(resolve => {
console.log(`data-`, data)
const InputDom = getInputFile({ accept, multiple }, (e: any) => {
console.log(`e--`, e)
const files = Array.from(e.target.files).map(file => {
file.type = uploadUtils.getFileSuffixType(file.name)
file.fileName = file.name
file.fileSize = file.size
file.path = URL.createObjectURL(file)
file.filePath = file.path
file.fileType = file.type
file.uri = file.path
return file
})
console.log('file----', files)
resolve(files)
})
document.body.append(InputDom)
InputDom.click()
})
}
export const ImagePicker = ({ max } = { max: 9 }) => {
return Platform.select({
web: () => {
return WebImagePicker({ max })
},
default: () => {
returnNativeModules.BndCommonModule.bndSaasSelectPhoto({ max })
}
})()
}