feat: support use react write miniapp (#3393) (#3344)

This commit is contained in:
chenbin92 2020-08-06 17:48:19 +08:00 committed by GitHub
parent 105a496f68
commit 974e0a0152
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
190 changed files with 7299 additions and 4558 deletions

View File

@ -7,9 +7,13 @@ const commonRules = {
"no-param-reassign": 0,
"comma-dangle": 0,
"prefer-object-spread": 0,
"import/named": 0,
// TODO: open rule indent, consider of MemberExpression
"indent": 0,
'semi': 2,
"semi": 2,
"react/react-in-jsx-scope": 0,
"jsx-a11y/html-has-lang": 0,
"react/static-property-placement": 0
};
const jsRules = deepmerge(eslint, {

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ packages/*/lib/
# temp folder .ice
examples/*/.ice
examples/*/.rax
.eslintcache
docs/.vuepress/dist/

View File

@ -298,22 +298,24 @@ icejs 中一般不允许修改该配置。
注意devServer 不支持 port 属性配置,如需改变端口,请通过命令行参数传入。
### targets
### browserslist
- 类型: `string` | `object` 
- 默认值:`last 2 versions, Firefox ESR, > 1%, ie >= 9, iOS >= 8, Android >= 4`
配置 @babel/preset-env 的 [targets](https://babeljs.io/docs/en/babel-preset-env#targets),配置浏览器最低版本,新配置的 `targets` 会覆盖默认值。
配置 @babel/preset-env 的浏览器最低版本(https://babeljs.io/docs/en/babel-preset-env#targets),新配置的 `browserslist` 会覆盖默认值。
```json
{
"targets": {
"browserslist": {
"chrome": 49,
"ie": 11,
}
}
```
> 注: 因 targets 字段被使用,这里使用 browserslist 字段替代 @babel/preset-env 的 targets 字段。
### vendor
- 类型:`boolean`

View File

@ -5,7 +5,13 @@ const appConfig: IAppConfig = {
app: {
rootId: 'ice-container',
errorBoundary: true,
parseSearchParams: true
parseSearchParams: true,
onShow() {
console.log('app show...');
},
onHide() {
console.log('app hide...');
},
},
logger: {
level: APP_MODE === 'build' ? 'error' : 'debug',

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Link, helpers, logger, config } from 'ice';
import { Link, usePageShow, usePageHide, helpers, logger, config } from 'ice';
logger.debug('helpers from ice', helpers.urlParse);
logger.debug('logger from ice', logger.debug);
@ -15,6 +15,14 @@ export default function Home(props) {
logger.info('Home props', props);
logger.info('render home config.appId', config.appId);
usePageShow(() => {
console.log('page show....');
});
usePageHide(() => {
console.log('page hide...');
});
return (
<>
<h2>Home Page...{props.count}</h2>

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Link } from 'ice';
const Home = (props) => {
const NotFound = (props) => {
console.log('render 404', props);
return (
@ -14,4 +14,4 @@ const Home = (props) => {
);
};
export default Home;
export default NotFound;

View File

@ -2,7 +2,7 @@
"name": "example-hello-world",
"description": "hello world",
"dependencies": {
"ice.js": "^1.0.0",
"ice.js": "1.7.0-7",
"react": "^16.4.1",
"react-dom": "^16.4.1"
},

View File

@ -0,0 +1,3 @@
# with rax web
https://github.com/ice-lab/icejs/tree/master/examples

View File

@ -0,0 +1,4 @@
{
"plugins": [],
"targets": ["miniapp"]
}

View File

@ -0,0 +1,24 @@
{
"name": "with-rax-web",
"description": "rax web example",
"dependencies": {
"rax": "^1.1.0",
"rax-app": "^2.0.0",
"rax-document": "^0.1.0",
"rax-image": "^2.0.0",
"rax-link": "^1.0.1",
"rax-text": "^1.0.0",
"rax-view": "^1.0.0"
},
"devDependencies": {
"miniapp-render": "1.2.0-0",
"@types/rax": "^1.0.0"
},
"scripts": {
"start": "raxjs start",
"build": "raxjs build"
},
"engines": {
"node": ">=8.0.0"
}
}

View File

@ -0,0 +1,15 @@
{
"routes": [
{
"path": "/",
"source": "pages/Home/index"
},
{
"path": "/about",
"source": "pages/About/index"
}
],
"window": {
"title": "Rax App"
}
}

View File

@ -0,0 +1,12 @@
import { runApp } from 'rax-app';
runApp({
app: {
onShow() {
console.log('app show...');
},
onHide() {
console.log('app hide...');
}
}
});

View File

@ -0,0 +1,5 @@
.logo {
width: 200rpx;
height: 180rpx;
margin-bottom: 20rpx;
}

View File

@ -0,0 +1,16 @@
import { createElement } from 'rax';
import Image from 'rax-image';
import './index.css';
export default () => {
const source = {
uri: '//gw.alicdn.com/tfs/TB1MRC_cvb2gK0jSZK9XXaEgFXa-1701-1535.png',
};
return (
<Image
className="logo"
source={source}
/>
);
};

View File

@ -0,0 +1,21 @@
import { createElement } from 'rax';
import { Root, Style, Script } from 'rax-document';
function Document() {
return (
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover" />
<title>@ali/demo-app</title>
<Style />
</head>
<body>
{/* root container */}
<Root />
<Script />
</body>
</html>
);
}
export default Document;

View File

@ -0,0 +1,16 @@
.about {
align-items: center;
margin-top: 200rpx;
}
.title {
font-size: 45rpx;
font-weight: bold;
margin: 20rpx 0;
}
.info {
font-size: 36rpx;
margin: 8rpx 0;
color: #555;
}

View File

@ -0,0 +1,27 @@
import { createElement, PureComponent } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import { withPageLifeCycle } from 'rax-app';
import './index.css';
class About extends PureComponent {
public onShow() {
console.log('about show...');
}
public onHide() {
console.log('about hide...');
}
public render() {
return (
<View className="about">
<Text className="title">About Page!!!</Text>
<Text className="info" onClick={() => this.props.history.push('/')}>Go Home</Text>
</View>
);
}
}
export default withPageLifeCycle(About);

View File

@ -0,0 +1,16 @@
.home {
align-items: center;
margin-top: 200rpx;
}
.title {
font-size: 45rpx;
font-weight: bold;
margin: 20rpx 0;
}
.info {
font-size: 36rpx;
margin: 8rpx 0;
color: #555;
}

View File

@ -0,0 +1,28 @@
import { createElement } from 'rax';
import { usePageShow, usePageHide } from 'rax-app';
import View from 'rax-view';
import Text from 'rax-text';
import Logo from '@/components/Logo';
import './index.css';
export default function Home(props) {
const { history } = props;
usePageShow(() => {
console.log('home show...');
});
usePageHide(() => {
console.log('home hide...');
});
return (
<View className="home">
<Logo />
<Text className="title">Welcome to Your Rax App!!!</Text>
<Text className="info">More information about Rax</Text>
<Text className="info" onClick={() => history.push('/about')}>Go About</Text>
</View>
);
}

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"module": "esNext",
"target": "es2015",
"outDir": "build",
"jsx": "preserve",
"jsxFactory": "createElement",
"moduleResolution": "node",
"sourceMap": true,
"alwaysStrict": true,
"baseUrl": "."
},
"include": ["src/*", ".rax"],
"exclude": ["build"]
}

View File

@ -0,0 +1,3 @@
# with react miniapp
https://github.com/ice-lab/icejs/tree/master/examples

View File

@ -0,0 +1,3 @@
{
"targets": ["miniapp", "wechat-miniprogram"]
}

View File

@ -0,0 +1,20 @@
{
"name": "example-with-miniapp",
"description": "miniapp example",
"dependencies": {
"react": "^16.4.1",
"react-dom": "^16.4.1",
"universal-request": "^2.2.0"
},
"devDependencies": {
"miniapp-history": "^0.1.2",
"miniapp-render": "1.2.0-1"
},
"scripts": {
"start": "icejs start",
"build": "icejs build"
},
"engines": {
"node": ">=8.0.0"
}
}

View File

@ -0,0 +1,15 @@
{
"routes": [
{
"path": "/home",
"source": "pages/Home/index"
},
{
"path": "/about",
"source": "pages/About/index"
}
],
"window": {
"title": "Mini App"
}
}

View File

@ -0,0 +1,12 @@
import { runApp } from 'ice';
runApp({
app: {
onShow() {
console.log('app show...');
},
onHide() {
console.log('app hide...');
}
}
});

View File

@ -0,0 +1,30 @@
import React, { useEffect } from 'react';
import request from 'universal-request';
function getRepo(){
request({
url: 'https://ice.alicdn.com/assets/materials/react-materials.json',
}).then(res => {
console.log('request res:', res);
}).catch(err => {
console.log(err);
});
}
const About = (props) => {
useEffect(() => {
getRepo();
});
const { history } = props;
return (
<div>
<h2>About Page</h2>
<p onClick={() => history.push('/home')}>go home</p>
</div>
);
};
export default About;

View File

@ -0,0 +1,19 @@
.container {
min-height: 600px;
overflow: hidden;
text-align: center;
background-color: #fff;
}
.title {
text-align: center;
color: red;
}
.description {
margin-top: 40px;
}
.action {
margin-top: 40px;
}

View File

@ -0,0 +1,27 @@
import * as React from 'react';
import { usePageShow, usePageHide } from 'ice';
import styles from './index.module.scss';
const Home = (props) => {
usePageShow(() => {
console.log('page show...');
});
usePageHide(() => {
console.log('page hide...');
});
const { history } = props;
return (
<div className={styles.container}>
<h2 className={styles.title}>Welcome to icejs miniapp!</h2>
<view className={styles.description}>This is a awesome project, enjoy it!</view>
<view onClick={() => {
console.log('Click');
history.push('/about');
}}>go about</view>
</div>
);
};
export default Home;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,33 @@
{
"compileOnSave": false,
"buildOnSave": false,
"compilerOptions": {
"baseUrl": ".",
"outDir": "build",
"module": "esnext",
"target": "es6",
"jsx": "react",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"rootDir": "./",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": false,
"importHelpers": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"],
"ice": [".ice/index.ts"],
"ice/*": [".ice/pages/*"]
}
},
"include": ["src/*", ".ice"],
"exclude": ["node_modules", "build", "public"]
}

View File

@ -11,7 +11,7 @@ module.exports = {
'testPathIgnorePatterns': [
'/node_modules/',
'/lib/',
'icejs/bin/'
'create-cli-utils/'
],
'preset': 'ts-jest'
};

View File

@ -57,6 +57,7 @@
"npm-run-all": "^4.1.5",
"nsfw": "1.2.6",
"pify": "^4.0.1",
"rax": "^1.1.4",
"react-test-renderer": "^16.13.1",
"rimraf": "^3.0.0",
"simple-git": "^1.132.0",

View File

@ -0,0 +1 @@
# `create-app-shared`

View File

@ -0,0 +1,36 @@
{
"name": "create-app-shared",
"version": "0.1.5",
"description": "",
"author": "ice-admin@alibaba-inc.com",
"homepage": "https://github.com/alibaba/ice#readme",
"license": "MIT",
"main": "lib/index.js",
"dependencies": {
"deepmerge": "^4.2.2",
"history": "^4.9.0",
"miniapp-history": "^0.1.0",
"query-string": "^6.13.1",
"universal-env": "^3.0.0"
},
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"publishConfig": {
"registry": "http://registry.npmjs.com/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/alibaba/ice.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"bugs": {
"url": "https://github.com/alibaba/ice/issues"
}
}

View File

@ -0,0 +1,118 @@
import { isWeex } from 'universal-env';
import { isMiniAppPlatform } from './env';
import { SHOW, HIDE, ERROR, LAUNCH, NOT_FOUND, SHARE, TAB_ITEM_CLICK } from './constants';
import { isFunction } from './utils';
import { getHistory } from './history';
import router from './router';
import { emit as pageEmit } from './pageLifeCycles';
export const appCycles = {};
// eslint-disable-next-line
declare var __weex_require__: any;
/**
* Emit life cycle callback
* @param {string} cycle cycle name
* @param {object} context callback's context when executed
* @param {...any} args callback params
*/
export function emit(cycle: any, context?: any, ...args) {
if (Object.prototype.hasOwnProperty.call(appCycles, cycle)) {
const cycles = appCycles[cycle];
if (cycle === SHARE) {
// In MiniApp, it need return callback result as share info, like { title, desc, path }
args[0].content = context ? cycles[0].call(context, args[1]) : cycles[0](args[1]);
} else {
cycles.forEach(fn => {
// eslint-disable-next-line
context ? fn.apply(context, args) : fn(...args);
});
}
}
}
/**
* Add app lifecycle callback
* @param {string} cycle cycle name
* @param {function} callback cycle callback
*/
export function addAppLifeCycle(cycle, callback) {
if (isFunction(callback)) {
// eslint-disable-next-line
const cycles = appCycles[cycle] = appCycles[cycle] || [];
cycles.push(callback);
}
}
// Emit MiniApp App lifeCycles
if (isMiniAppPlatform) {
window.addEventListener(LAUNCH, ({ options, context }: any) => {
emit(LAUNCH, context, options);
});
window.addEventListener('appshow', ({ options, context }: any) => {
emit(SHOW, context, options);
});
window.addEventListener('apphide', ({ context }: any) => {
emit(HIDE, context);
});
window.addEventListener('apperror', ({ context, error }: any) => {
emit(ERROR, context, error);
});
window.addEventListener('pagenotfound', ({ context }: any) => {
emit(NOT_FOUND, context);
});
window.addEventListener('appshare', ({ context, shareInfo, options }: any) => {
emit(SHARE, context, shareInfo, options);
});
window.addEventListener('tabitemclick', ({ options, context }: any) => {
emit(TAB_ITEM_CLICK, context, options);
});
} else if (isWeex) {
try {
// https://weex.apache.org/docs/modules/globalEvent.html#addeventlistener
// Use __weex_require__ in Rax project.
const globalEvent = __weex_require__('@weex-module/globalEvent');
globalEvent.addEventListener('WXApplicationDidBecomeActiveEvent', function() {
router.current.visibiltyState = true;
// Emit app show
emit(SHOW);
// Emit page show
pageEmit(SHOW, router.current.pathname);
});
globalEvent.addEventListener('WXApplicationWillResignActiveEvent', function() {
router.current.visibiltyState = false;
// Emit page hide
pageEmit(HIDE, router.current.pathname);
// Emit app hide
emit(HIDE);
});
} catch (err) {
console.log(`@weex-module/globalEvent error: ${ err}`);
}
} else if (typeof document !== 'undefined' && typeof window !== 'undefined') {
document.addEventListener('visibilitychange', function() {
// Get history
const history = getHistory();
const currentPathName = history ? history.location.pathname : router.current.pathname;
// The app switches from foreground to background
if (currentPathName === router.current.pathname) {
router.current.visibiltyState = !router.current.visibiltyState;
if (router.current.visibiltyState) {
// Emit app show
emit(SHOW);
// Emit page show
pageEmit(SHOW, router.current.pathname);
} else {
// Emit page hide
pageEmit(HIDE, router.current.pathname);
// Emit app hide
emit(HIDE);
}
}
});
// Emit error lifeCycles
window.addEventListener('error', event => {
emit(ERROR, null, event.error);
});
}

View File

@ -0,0 +1,34 @@
import { addAppLifeCycle } from './appLifeCycles';
import { SHOW, LAUNCH, ERROR, HIDE, TAB_ITEM_CLICK, NOT_FOUND, SHARE, UNHANDLED_REJECTION } from './constants';
import { isMiniAppPlatform, isWeChatMiniProgram, isByteDanceMicroApp } from './env';
export default function collectAppLifeCycle(appConfig) {
const { onLaunch, onShow, onError, onHide, onTabItemClick } = appConfig.app;
// multi-end valid lifecycle
// Add app lanuch callback
addAppLifeCycle(LAUNCH, onLaunch);
// Add app show callback
addAppLifeCycle(SHOW, onShow);
// Add app error callback
addAppLifeCycle(ERROR, onError);
// Add app hide callback
addAppLifeCycle(HIDE, onHide);
// Add tab bar item click callback
addAppLifeCycle(TAB_ITEM_CLICK, onTabItemClick);
// Add lifecycle callbacks which only valid in Wechat MiniProgram and ByteDance MicroApp
if (isWeChatMiniProgram || isByteDanceMicroApp) {
const { onPageNotFound, onShareAppMessage } = appConfig;
// Add global share callback
addAppLifeCycle(SHARE, onShareAppMessage);
// Add page not found callback
addAppLifeCycle(NOT_FOUND, onPageNotFound);
}
// Add lifecycle callbacks which only valid in Alibaba MiniApp
if (isMiniAppPlatform) {
const { onShareAppMessage, onUnhandledRejection } = appConfig;
// Add global share callback
addAppLifeCycle(SHARE, onShareAppMessage);
// Add unhandledrejection callback
addAppLifeCycle(UNHANDLED_REJECTION, onUnhandledRejection);
}
}

View File

@ -0,0 +1,8 @@
export const SHOW = 'show';
export const HIDE = 'hide';
export const LAUNCH = 'launch';
export const ERROR = 'error';
export const NOT_FOUND = 'notfound';
export const SHARE = 'share';
export const TAB_ITEM_CLICK = 'tabitemclick';
export const UNHANDLED_REJECTION = 'unhandledrejection';

View File

@ -0,0 +1,52 @@
import RuntimeModule from './runtimeModule';
import { createHistory } from './history';
import { isMiniAppPlatform } from './env';
import collectAppLifeCycle from './collectAppLifeCycle';
// eslint-disable-next-line
const deepmerge = require('deepmerge');
const DEFAULE_APP_CONFIG = {
app: {
rootId: 'root'
},
router: {
type: 'hash'
}
};
export default ({ loadRuntimeModules, loadStaticModules, createElement }) => {
const createBaseApp = (appConfig, buildConfig, context: any = {}) => {
appConfig = deepmerge(DEFAULE_APP_CONFIG, appConfig);
// load module to run before createApp ready
loadStaticModules(appConfig);
// Set history
let history = {};
if (!isMiniAppPlatform) {
const { router } = appConfig;
const { type, basename } = router;
history = createHistory({ type, basename });
appConfig.router.history = history;
}
context.createElement = createElement;
// Load runtime modules
const runtime = new RuntimeModule(appConfig, buildConfig, context);
loadRuntimeModules(runtime);
// Collect app lifeCyle
collectAppLifeCycle(appConfig);
return {
history,
runtime,
appConfig
};
};
return createBaseApp;
};

View File

@ -0,0 +1,41 @@
import { getHistory } from './history';
import router from './router';
import { LAUNCH, SHOW, HIDE } from './constants';
import { emit as appEmit } from './appLifeCycles';
import { emit as pageEmit } from './pageLifeCycles';
import { isMiniAppPlatform } from './env';
function emitLifeCycles() {
// Get history
const history = getHistory();
const pathname = history.location.pathname;
// Set current router
router.current = {
pathname,
visibiltyState: true
};
if (!isMiniAppPlatform) {
// Emit app lifecycle
appEmit(LAUNCH);
appEmit(SHOW);
// Listen history change
history.listen((location) => {
if (location.pathname !== router.current.pathname) {
// Flow router info
router.prev = router.current;
router.current = {
pathname: location.pathname,
visibiltyState: true
};
router.prev.visibiltyState = false;
pageEmit(HIDE, router.prev.pathname);
pageEmit(SHOW, router.current.pathname);
}
});
}
}
export default emitLifeCycles;

View File

@ -0,0 +1,27 @@
import { isMiniAppPlatform } from './env';
function enhanceWithRouter({ withRouter, createElement }) {
if (isMiniAppPlatform) {
withRouter = function (Component) {
function Wrapper(props) {
// eslint-disable-next-line
const history = window.history;
return createElement(
Component,
Object.assign({}, props, {
history,
location: (history as any).location,
})
);
}
// eslint-disable-next-line
Wrapper.displayName = 'withRouter(' + (Component.displayName || Component.name) + ')';
Wrapper.WrappedComponent = Component;
return Wrapper;
};
}
return withRouter;
}
export default enhanceWithRouter;

View File

@ -0,0 +1,4 @@
import { isMiniApp, isWeChatMiniProgram, isByteDanceMicroApp } from 'universal-env';
export const isMiniAppPlatform = isMiniApp || isWeChatMiniProgram || isByteDanceMicroApp;
export * from 'universal-env';

View File

@ -0,0 +1,32 @@
import {
createBrowserHistory,
createHashHistory,
createMemoryHistory
} from 'history';
import { createMiniAppHistory } from 'miniapp-history';
import { isMiniAppPlatform } from './env';
let history;
function createHistory({ routes, customHistory, type, basename }: any) {
if (customHistory) {
history = customHistory;
} else if (type === 'hash') {
history = createHashHistory({ basename });
} else if (type === 'browser') {
history = createBrowserHistory({ basename });
} else if (isMiniAppPlatform) {
(window as any).history = createMiniAppHistory(routes);
window.location = (window.history as any).location;
history = window.history;
} else {
history = createMemoryHistory();
}
return history;
}
function getHistory() {
return isMiniAppPlatform ? window.history : history;
}
export { getHistory, createHistory };

View File

@ -0,0 +1,43 @@
import enhanceWithRouter from './enhanceWithRouter';
import { withPageLifeCycle, createUsePageLifeCycle } from './pageLifeCycles';
import emitLifeCycles from './emitLifeCycles';
import createBaseApp from './createBaseApp';
import { createHistory, getHistory } from './history';
import { pathRedirect } from './utils';
import {
registerNativeEventListeners,
addNativeEventListener,
removeNativeEventListener
} from './nativeEventListener';
import useSearchParams from './useSearchParams';
import withSearchParams from './withSearchParams';
import collectAppLifeCycle from './collectAppLifeCycle';
function createShareAPI({ withRouter, createElement, useEffect }, loadRuntimeModules, loadStaticModules) {
const { usePageShow, usePageHide } = createUsePageLifeCycle({ useEffect });
return {
createBaseApp: createBaseApp({ loadRuntimeModules, loadStaticModules, createElement }),
// history api
withRouter: enhanceWithRouter({ withRouter, createElement }),
createHistory,
getHistory,
useSearchParams,
withSearchParams: withSearchParams(createElement),
// lifeCycle api
emitLifeCycles,
collectAppLifeCycle,
usePageShow,
usePageHide,
withPageLifeCycle,
// utils api
pathRedirect,
registerNativeEventListeners,
addNativeEventListener,
removeNativeEventListener
};
};
export default createShareAPI;

View File

@ -0,0 +1,12 @@
// eslint-disable-next-line
export function registerNativeEventListeners(Klass, events) {
// For rax miniapp runtime babel plugins prev compile
}
export function addNativeEventListener(eventName, callback) {
window.addEventListener(eventName, callback);
}
export function removeNativeEventListener(evetName, callback) {
window.removeEventListener(evetName, callback);
}

View File

@ -0,0 +1,108 @@
import { getHistory } from './history';
import { isMiniAppPlatform } from './env';
import { SHOW, HIDE } from './constants';
import router from './router';
// visibleListeners => { [pathname]: { show: [], hide: [] } }
const visibleListeners = {};
function addPageLifeCycle(cycle, callback) {
const pathname = router.current.pathname;
if (!visibleListeners[pathname]) {
visibleListeners[pathname] = {
[SHOW]: [],
[HIDE]: []
};
}
visibleListeners[pathname][cycle].push(callback);
}
export function emit(cycle: any, pathname?: string, ...args) {
// Ensure queue exists
if (visibleListeners[pathname] && visibleListeners[pathname][cycle]) {
for (let i = 0, l = visibleListeners[pathname][cycle].length; i < l; i++) {
visibleListeners[pathname][cycle][i](...args);
}
}
}
function createPageLifeCycle(useEffect) {
return (cycle, callback) => {
useEffect(() => {
// When component did mount, it will trigger usePageShow callback
if (cycle === SHOW) {
callback();
}
const pathname = router.current.pathname;
addPageLifeCycle(cycle, callback);
return () => {
if (visibleListeners[pathname]) {
const index = visibleListeners[pathname][cycle].indexOf(callback);
if (index > -1) {
visibleListeners[pathname][cycle].splice(index, 1);
}
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};
}
export function withPageLifeCycle(Component) {
class Wrapper extends Component {
constructor() {
super();
if (this.onShow) {
if (!isMiniAppPlatform) {
// In MiniApp platform show event will trigger after addPageLifeCycle, so it needn't be execute in constructor
this.onShow();
}
addPageLifeCycle(SHOW, this.onShow.bind(this));
}
if (this.onHide) {
addPageLifeCycle(HIDE, this.onHide.bind(this));
}
// Keep the path name corresponding to current page component
this.pathname = router.current.pathname;
}
private componentWillUnmount() {
visibleListeners[this.pathname] = null;
}
}
Wrapper.displayName = `withPageLifeCycle(${ Component.displayName || Component.name })`;
return Wrapper as any;
}
if (isMiniAppPlatform) {
// eslint-disable-next-line
window.addEventListener('pageshow', () => {
// Get history
const history = getHistory();
emit(SHOW, history.location.pathname);
});
// eslint-disable-next-line
window.addEventListener('pagehide', () => {
// Get history
const history = getHistory();
emit(HIDE, history.location.pathname);
});
}
export function createUsePageLifeCycle({ useEffect }) {
const usePageShow = (callback) => {
createPageLifeCycle(useEffect)(SHOW, callback);
};
const usePageHide = (callback) => {
createPageLifeCycle(useEffect)(HIDE, callback);
};
return {
usePageShow,
usePageHide
};
}

View File

@ -0,0 +1,29 @@
import { isMiniAppPlatform } from './env';
import { getHistory } from './history';
const current = {
pathname: '/',
visibiltyState: true
};
const router = {
prev: null,
current
};
Object.defineProperty(router, 'current', {
get() {
if (!isMiniAppPlatform) {
return current;
}
const history = getHistory();
return Object.assign(current, {
pathname: history.location.pathname
});
},
set(value) {
Object.assign(current, value);
}
});
export default router;

View File

@ -1,66 +1,25 @@
import * as React from 'react';
import { createHistory } from './history';
interface IRenderParams {
App: React.ComponentType;
appMountNode: HTMLElement;
}
interface IDOMRender {
(data: IRenderParams): void;
}
interface IGetAppRouter {
(): React.ComponentType;
}
interface RouteItemProps {
children?: RouteItemProps[];
path?: string;
redirect?: string;
component?: React.ComponentType;
RouteWrapper?: React.ComponentType;
}
interface IModifyRoutes {
(modifyFn: IModifyFn): void;
}
interface IModifyFn {
(routes: RouteItemProps[]): RouteItemProps[];
}
interface IAppRouter {
(routes: RouteItemProps[]): React.ComponentType;
}
interface IWrapperRoute {
(RouteComponent: React.ComponentType): React.ComponentType;
}
interface IWrapperRouteComponent {
(wrapperRoute: IWrapperRoute): void;
}
class RuntimeModule {
private AppProvider: React.ComponentType[];
private modifyRoutesRegistration: IModifyFn[];
private renderRouter: any;
private wrapperRouteRegistration: IWrapperRoute[];
private AppProvider: any;
public renderRouter: IAppRouter;
private appConfig: any;
public appConfig: any;
private buildConfig: any;
public context: any;
private context: any;
public buildConfig: any;
private modifyDOMRender: any;
public modifyDOMRender: IDOMRender|null;
private modifyRoutesRegistration: any;
private wrapperRouteRegistration: any;
constructor(appConfig, buildConfig, context) {
this.renderRouter = () => () => <div>No route</div>;
this.renderRouter = () => () => context.createElement('div', null, 'No route');
this.AppProvider = [];
this.appConfig = appConfig;
this.buildConfig = buildConfig;
@ -78,7 +37,7 @@ class RuntimeModule {
modifyRoutes: this.modifyRoutes,
wrapperRouteComponent: this.wrapperRouteComponent,
createHistory
}
};
if (module) module.default({
...runtimeAPI,
@ -100,28 +59,31 @@ class RuntimeModule {
if (!this.AppProvider.length) return null;
return this.AppProvider.reduce((ProviderComponent, CurrentProvider) => {
return ({ children, ...rest }) => {
return (
<ProviderComponent {...rest}>
{CurrentProvider ? <CurrentProvider {...rest}>{children}</CurrentProvider> : children}
</ProviderComponent>
)
}
const element = CurrentProvider
? this.context.createElement(CurrentProvider, {...rest})
: this.context.createElement(children);
return this.context.createElement(
ProviderComponent,
{...rest},
element
);
};
});
}
public addDOMRender = (render: IDOMRender) => {
public addDOMRender = (render) => {
this.modifyDOMRender = render;
}
public modifyRoutes: IModifyRoutes = (modifyFn) => {
public modifyRoutes = (modifyFn) => {
this.modifyRoutesRegistration.push(modifyFn);
}
public wrapperRouteComponent: IWrapperRouteComponent = (wrapperRoute) => {
public wrapperRouteComponent = (wrapperRoute) => {
this.wrapperRouteRegistration.push(wrapperRoute);
}
private wrapperRoutes = (routes) => {
public wrapperRoutes = (routes) => {
return routes.map((item) => {
if (item.children) {
item.children = this.wrapperRoutes(item.children);
@ -132,7 +94,7 @@ class RuntimeModule {
});
}
public getAppRouter: IGetAppRouter = () => {
public getAppRouter = () => {
const routes = this.wrapperRoutes(this.modifyRoutesRegistration.reduce((acc, curr) => {
return curr(acc);
}, []));

View File

@ -0,0 +1,9 @@
import * as queryString from 'query-string';
import { getHistory } from './history';
const useSearchParams = () => {
const history = getHistory();
return queryString.parse(history.location.search);
};
export default useSearchParams;

View File

@ -0,0 +1,7 @@
import { isFunction } from './type';
import pathRedirect from './pathRedirect';
export {
isFunction,
pathRedirect
};

View File

@ -0,0 +1,55 @@
/* eslint no-undef:0 */
// In a Single-Page Application, sometimes we need to jump to a specific route.
// It is very simple in the Web application, url like #/xxx can jump to the corresponding page.
// Things seem to be very complicated in Weex, because using `MemoryHistory`,
// which is used as a reference implementation and may also be used in non-DOM environments.
// We cannot jump directly through url.
import { isWeex, isWeb } from 'universal-env';
// We want to control applications on different platforms to jump to specific pages through unified parameters.
// Like https://xxx.com?_path=/page1, use `_path` to jump to the specific route.
const TARGET_PATH_REG = /[?&]_path=([^&#]+)/i;
export default function pathRedirect(history, routes) {
let targetPath = '';
let targetQuery = null;
// In Web, use location.search first
if (isWeb && TARGET_PATH_REG.test(window.location.search)) {
targetQuery = window.location.search.match(TARGET_PATH_REG);
}
// In Weex, use location.href first. Support by rax-weex framework
if (isWeex && TARGET_PATH_REG.test(window.location.href)) {
targetQuery = window.location.href.match(TARGET_PATH_REG);
}
// If there is no `_path` in url search, try history.location.
if (!targetQuery && TARGET_PATH_REG.test(history.location.search)) {
targetQuery = history.location.search.match(TARGET_PATH_REG);
}
let isConfirmed = false;
targetPath = targetQuery ? targetQuery[1] : '';
for (let i = 0, l = routes.length; i < l; i++) {
if (targetPath === routes[i].path) {
isConfirmed = true;
break;
}
}
if (targetPath && !isConfirmed) {
console.warn(
'Warning: url query `_path` should be an exist path in app.json, see: https://rax.js.org/docs/guide/routes '
);
return false;
}
// If `targetPath` exists, jump to the specific route.
if (targetPath) {
history.replace(targetPath + history.location.search);
}
}

View File

@ -0,0 +1 @@
export const isFunction = target => typeof target === 'function';

View File

@ -0,0 +1,20 @@
import * as queryString from 'query-string';
import { getHistory } from './history';
export default (createElement) => {
const withSearchParams = Component => {
const WrapperedSearchParams = props => {
const history = getHistory();
const searchParams = queryString.parse(history.location.search);
return createElement(
Component,
{
...props,
searchParams
}
);
};
return WrapperedSearchParams;
};
return withSearchParams;
};

View File

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.settings.json",
"compilerOptions": {
"baseUrl": "./",
"rootDir": "src",
"outDir": "lib"
},
}

View File

@ -0,0 +1 @@
# `create-cli-utils`

View File

@ -0,0 +1,37 @@
{
"name": "create-cli-utils",
"version": "0.1.1",
"description": "",
"author": "ice-admin@alibaba-inc.com",
"homepage": "https://github.com/alibaba/ice#readme",
"license": "MIT",
"main": "lib/index.js",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"dependencies": {
"@alib/build-scripts": "^0.1.24",
"chokidar": "^3.3.1",
"commander": "^5.0.0",
"detect-port": "^1.3.0",
"inquirer": "^7.1.0",
"yargs-parser": "^18.1.2"
},
"publishConfig": {
"registry": "http://registry.npmjs.com/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/alibaba/ice.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"bugs": {
"url": "https://github.com/alibaba/ice/issues"
}
}

View File

@ -2,9 +2,8 @@
const parse = require('yargs-parser');
const { build } = require('@alib/build-scripts');
const log = require('@alib/build-scripts/lib/utils/log');
const getBuiltInPlugins = require('../lib/index');
module.exports = async () => {
module.exports = async (getBuiltInPlugins) => {
process.env.NODE_ENV = 'production';
const rawArgv = parse(process.argv.slice(2), {
configuration: { 'strip-dashed': true },

View File

@ -0,0 +1,55 @@
#!/usr/bin/env node
const detect = require('detect-port');
const inquirer = require('inquirer');
const parse = require('yargs-parser');
const { start } = require('@alib/build-scripts');
const log = require('@alib/build-scripts/lib/utils/log');
const rawArgv = parse(process.argv.slice(2), {
configuration: { 'strip-dashed': true }
});
const DEFAULT_PORT = rawArgv.port || process.env.PORT || 3333;
const defaultPort = parseInt(DEFAULT_PORT, 10);
module.exports = async (getBuiltInPlugins) => {
let newPort = await detect(defaultPort);
if (newPort !== defaultPort) {
const question = {
type: 'confirm',
name: 'shouldChangePort',
message: `${defaultPort} 端口已被占用,是否使用 ${newPort} 端口启动?`,
default: true
};
const answer = await inquirer.prompt(question);
if (!answer.shouldChangePort) {
newPort = null;
}
}
if (newPort === null) {
process.exit(1);
}
process.env.NODE_ENV = 'development';
rawArgv.port = parseInt(newPort, 10);
// ignore _ in rawArgv
delete rawArgv._;
try {
const devServer = await start({
args: { ...rawArgv },
getBuiltInPlugins
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
devServer.close();
process.exit(0);
});
});
} catch (err) {
log.error(err.message);
console.error(err);
process.exit(1);
}
};

View File

@ -0,0 +1,61 @@
#!/usr/bin/env node
const program = require('commander');
const checkNodeVersion = require('@alib/build-scripts/lib/utils/checkNodeVersion');
const start = require('./start');
const build = require('./build');
const test = require('./test');
module.exports = async (getBuiltInPlugins, forkChildProcessPath, packageInfo) => {
console.log(packageInfo.name, packageInfo.version);
// finish check before run command
checkNodeVersion(packageInfo.engines.node);
program
.version(packageInfo.version)
.usage('<command> [options]');
program
.command('build')
.description('build project')
.allowUnknownOption()
.option('--config <config>', 'use custom config')
.action(async function() {
await build(getBuiltInPlugins);
});
program
.command('start')
.description('start server')
.allowUnknownOption()
.option('--config <config>', 'use custom config')
.option('-h, --host <host>', 'dev server host', '0.0.0.0')
.option('-p, --port <port>', 'dev server port')
.action(async function() {
await start(getBuiltInPlugins, forkChildProcessPath);
});
program
.command('test')
.description('run tests with jest')
.allowUnknownOption() // allow jest config
.option('--config <config>', 'use custom config')
.action(async function() {
await test(getBuiltInPlugins);
});
program.parse(process.argv);
const proc = program.runningCommand;
if (proc) {
proc.on('close', process.exit.bind(process));
proc.on('error', () => {
process.exit(1);
});
}
const subCmd = program.args[0];
if (!subCmd) {
program.help();
}
};

View File

@ -0,0 +1,11 @@
const start = require('./start');
const build = require('./build');
const test = require('./test');
const childProcessStart = require('./child-process-start');
module.exports = {
start,
build,
test,
childProcessStart
};

View File

@ -8,7 +8,6 @@ const log = require('@alib/build-scripts/lib/utils/log');
let child = null;
const rawArgv = parse(process.argv.slice(2));
const scriptPath = require.resolve('./child-process-start.js');
const configPath = path.resolve(rawArgv.config || 'build.json');
const inspectRegExp = /^--(inspect(?:-brk)?)(?:=(?:([^:]+):)?(\d+))?$/;
@ -29,11 +28,11 @@ function modifyInspectArgv(argv) {
);
}
function restartProcess() {
function restartProcess(forkChildProcessPath) {
(async () => {
// remove the inspect related argv when passing to child process to avoid port-in-use error
const argv = await modifyInspectArgv(process.execArgv);
child = fork(scriptPath, process.argv.slice(2), { execArgv: argv });
child = fork(forkChildProcessPath, process.argv.slice(2), { execArgv: argv });
child.on('message', data => {
if (process.send) {
process.send(data);
@ -48,25 +47,23 @@ function restartProcess() {
})();
}
const onUserChange = () => {
module.exports = (getBuiltInPlugins, forkChildProcessPath) => {
restartProcess(forkChildProcessPath);
const watcher = chokidar.watch(configPath, {
ignoreInitial: true,
});
watcher.on('change', function() {
console.log('\n');
log.info('build.json has been changed');
log.info('restart dev server');
// add process env for mark restart dev process
process.env.RESTART_DEV = true;
child.kill();
restartProcess();
};
module.exports = () => {
restartProcess();
const watcher = chokidar.watch(configPath, {
ignoreInitial: true,
restartProcess(forkChildProcessPath);
});
watcher.on('change', onUserChange);
watcher.on('error', error => {
log.error('fail to watch file', error);
process.exit(1);

View File

@ -2,9 +2,8 @@
const parse = require('yargs-parser');
const { test } = require('@alib/build-scripts');
const log = require('@alib/build-scripts/lib/utils/log');
const getBuiltInPlugins = require('../lib/index');
module.exports = async () => {
module.exports = async (getBuiltInPlugins) => {
process.env.NODE_ENV = 'test';
const rawArgv = parse(process.argv.slice(3), {
configuration: { 'strip-dashed': true },

View File

@ -22,7 +22,7 @@
},
"dependencies": {
"@iceworks/generate-project": "^1.0.0",
"chalk": "^3.0.0",
"chalk": "^4.0.0",
"fs-extra": "^8.1.0",
"inquirer": "^7.0.4"
},

View File

@ -1,56 +1,7 @@
#!/usr/bin/env node
const detect = require('detect-port');
const inquirer = require('inquirer');
const parse = require('yargs-parser');
const { start } = require('@alib/build-scripts');
const log = require('@alib/build-scripts/lib/utils/log');
const { childProcessStart } = require('create-cli-utils');
const getBuiltInPlugins = require('../lib/index');
const rawArgv = parse(process.argv.slice(2), {
configuration: { 'strip-dashed': true },
});
const DEFAULT_PORT = rawArgv.port || process.env.PORT || 3333;
const defaultPort = parseInt(DEFAULT_PORT, 10);
(async() => {
let newPort = await detect(defaultPort);
if (newPort !== defaultPort) {
const question = {
type: 'confirm',
name: 'shouldChangePort',
message: `${defaultPort} 端口已被占用,是否使用 ${newPort} 端口启动?`,
default: true,
};
const answer = await inquirer.prompt(question);
if (!answer.shouldChangePort) {
newPort = null;
}
}
if (newPort === null) {
process.exit(1);
}
process.env.NODE_ENV = 'development';
rawArgv.port = parseInt(newPort, 10);
// ignore _ in rawArgv
delete rawArgv._;
try {
const devServer = await start({
args: { ...rawArgv },
getBuiltInPlugins,
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
devServer.close();
process.exit(0);
});
});
} catch (err) {
log.error(err.message);
console.error(err);
process.exit(1);
}
await childProcessStart(getBuiltInPlugins);
})();

View File

@ -1,65 +1,10 @@
#!/usr/bin/env node
const program = require('commander');
const checkNodeVersion = require('@alib/build-scripts/lib/utils/checkNodeVersion');
const createCli = require('create-cli-utils/lib/cli');
const packageInfo = require('../package.json');
const build = require('./build');
const start = require('./start');
const test = require('./test');
const getBuiltInPlugins = require('../lib');
const forkChildProcessPath = require.resolve('./child-process-start');
(async () => {
console.log(packageInfo.name, packageInfo.version);
// finish check before run command
checkNodeVersion(packageInfo.engines.node);
program
.version(packageInfo.version)
.usage('<command> [options]');
program
.command('build')
.description('build project')
.allowUnknownOption()
.option('--config <config>', 'use custom config')
.option('--analyzer', 'use webpack-bundle-analyzer')
.option('--analyzer-port <port>', 'webpack-bundle-analyzer port', '9000')
.action(build);
program
.command('start')
.description('start server')
.allowUnknownOption()
.option('--config <config>', 'use custom config')
.option('-h, --host <host>', 'dev server host', '0.0.0.0')
.option('-p, --port <port>', 'dev server port')
.option('--https', 'use https protocol')
.option('--analyzer', 'use webpack-bundle-analyzer')
.option('--analyzer-port <port>', 'webpack-bundle-analyzer port', '9000')
.option('--disable-reload', 'disable hot-loader module')
.option('--disable-mock', 'diable mock service')
.option('--disable-open', 'disable automatically open browser')
.option('--disable-assets', 'disable the output of webpack assets')
.action(start);
program
.command('test')
.description('run tests with jest')
.allowUnknownOption() // allow jest config
.option('--config <config>', 'use custom config')
.action(test);
program.parse(process.argv);
const proc = program.runningCommand;
if (proc) {
proc.on('close', process.exit.bind(process));
proc.on('error', () => {
process.exit(1);
});
}
const subCmd = program.args[0];
if (!subCmd) {
program.help();
}
await createCli(getBuiltInPlugins, forkChildProcessPath, packageInfo);
})();

View File

@ -1,6 +1,6 @@
{
"name": "ice.js",
"version": "1.6.4-alpha.0",
"version": "1.7.0-10",
"description": "command line interface and builtin plugin for icejs",
"author": "ice-admin@alibaba-inc.com",
"homepage": "",
@ -25,16 +25,18 @@
},
"dependencies": {
"@alib/build-scripts": "^0.1.13",
"build-plugin-ice-config": "1.6.4-alpha.0",
"build-plugin-ice-core": "1.6.4-alpha.0",
"build-plugin-ice-helpers": "1.6.4-alpha.0",
"build-plugin-ice-logger": "1.6.4-alpha.0",
"build-plugin-ice-mpa": "1.6.4-alpha.0",
"build-plugin-ice-request": "1.6.4-alpha.0",
"build-plugin-ice-router": "1.6.4-alpha.0",
"build-plugin-ice-ssr": "1.6.4-alpha.0",
"build-plugin-ice-store": "1.6.4-alpha.0",
"build-plugin-react-app": "1.6.4-alpha.0",
"build-plugin-ice-config": "1.7.0-0",
"build-plugin-app-core": "0.1.6",
"build-plugin-ice-helpers": "1.7.0-0",
"build-plugin-ice-logger": "1.7.0-0",
"build-plugin-ice-mpa": "1.7.0-0",
"build-plugin-ice-request": "1.7.0-0",
"build-plugin-ice-router": "1.7.0-0",
"build-plugin-ice-ssr": "1.7.0-1",
"build-plugin-ice-store": "1.7.0-0",
"build-plugin-react-app": "1.7.0-4",
"build-plugin-miniapp": "0.1.2",
"create-cli-utils": "^0.1.0",
"chokidar": "^3.3.1",
"commander": "^5.0.0",
"detect-port": "^1.3.0",

View File

@ -6,27 +6,53 @@ const getBuiltInPlugins = (userConfig) => {
];
}
// built-in plugins for icejs
const builtInPlugins = [
'build-plugin-ice-core',
'build-plugin-react-app',
let plugins = [];
// common plugins
const commonPlugins = [
[
'build-plugin-app-core', {
'framework': 'react'
}
],
'build-plugin-react-app'
];
// for ice/react plugins
const reactAppPlugins = [
'build-plugin-ice-router',
'build-plugin-ice-helpers',
'build-plugin-ice-logger',
'build-plugin-ice-config',
'build-plugin-ice-request',
'build-plugin-ice-mpa'
'build-plugin-ice-mpa',
'build-plugin-ice-request'
];
// for ice/miniapp plugins
const miniAppPlugins = [
'build-plugin-miniapp'
];
// for miniapp plugins
if (Array.isArray(userConfig.targets)
&& (userConfig.targets.includes('miniapp') || userConfig.targets.includes('wechat-miniprogram'))
) {
// @ts-ignore
plugins = commonPlugins.concat(miniAppPlugins);
} else {
plugins = commonPlugins.concat(reactAppPlugins);
}
if (userConfig.ssr) {
builtInPlugins.push('build-plugin-ice-ssr');
plugins.push('build-plugin-ice-ssr');
}
// add store plugin
if (!Object.prototype.hasOwnProperty.call(userConfig, 'store') || userConfig.store !== false) {
builtInPlugins.push('build-plugin-ice-store');
plugins.push('build-plugin-ice-store');
}
return builtInPlugins;
return plugins;
};
export = getBuiltInPlugins
module.exports = getBuiltInPlugins;

View File

@ -0,0 +1 @@
# `miniapp-renderer`

View File

@ -0,0 +1,32 @@
{
"name": "miniapp-renderer",
"version": "0.1.3",
"description": "",
"author": "ice-admin@alibaba-inc.com",
"homepage": "https://github.com/alibaba/ice#readme",
"license": "MIT",
"main": "lib/index.js",
"dependencies": {
"universal-env": "^3.0.0"
},
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"publishConfig": {
"registry": "http://registry.npmjs.com/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/alibaba/ice.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"bugs": {
"url": "https://github.com/alibaba/ice/issues"
}
}

View File

@ -0,0 +1,3 @@
import miniappRenderer from './miniappRenderer';
export default miniappRenderer;

View File

@ -0,0 +1,42 @@
function miniappRenderer(
{ appConfig, createBaseApp, createHistory, staticConfig, pageProps, emitLifeCycles },
{ mount, unmount, createElement, Component }
) {
const history = createHistory({ routes: staticConfig.routes });
createBaseApp(appConfig);
const rootId = appConfig.app.rootId;
emitLifeCycles();
class App extends Component {
public render() {
const { Page, ...otherProps } = this.props;
return createElement(Page, otherProps);
}
}
(window as any).__pagesRenderInfo = staticConfig.routes.map(({ source, component }: any) => {
return {
path: source,
render() {
const PageComponent = component()();
const rootEl = document.createElement('div');
rootEl.setAttribute('id', rootId);
document.body.appendChild(rootEl);
const appInstance = mount(createElement(App, {
history,
location: history.location,
...pageProps,
Page: PageComponent
}), rootEl);
(document as any).__unmount = unmount(appInstance, rootEl);
},
setDocument(value) {
// eslint-disable-next-line no-global-assign
document = value;
}
};
});
};
export default miniappRenderer;

View File

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.settings.json",
"compilerOptions": {
"baseUrl": "./",
"rootDir": "src",
"outDir": "lib"
},
}

View File

@ -0,0 +1,3 @@
# plugin-app-core
The core plugin for icejs and raxjs.

View File

@ -1,7 +1,7 @@
{
"name": "build-plugin-ice-core",
"version": "1.6.4-alpha.0",
"description": "the core plugin for icejs.",
"name": "build-plugin-app-core",
"version": "0.1.6",
"description": "the core plugin for icejs and raxjs.",
"author": "ice-admin@alibaba-inc.com",
"homepage": "",
"license": "MIT",
@ -20,12 +20,21 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"chokidar": "^3.3.1",
"deepmerge": "^4.2.2",
"chokidar": "^3.4.1",
"chalk": "^4.0.0",
"create-app-shared": "0.1.5",
"ejs": "^3.0.1",
"fs-extra": "^8.1.0",
"globby": "^11.0.0",
"prettier": "^2.0.2"
"history": "^4.9.0",
"miniapp-history": "^0.1.0",
"miniapp-renderer": "0.1.3",
"prettier": "^2.0.2",
"query-string": "^6.13.1",
"rax-use-router": "^3.0.0",
"rax-app-renderer": "^0.1.0",
"react-app-renderer": "0.1.2",
"universal-env": "^3.0.0"
},
"repository": {
"type": "git",

View File

@ -0,0 +1,15 @@
import setAlias from './setAlias';
import setProjectType from './setProjectType';
import setEntry from './setEntry';
import setTempDir from './setTempDir';
import setRegisterMethod from './setRegisterMethod';
import setRegisterUserConfig from './setRegisterUserConfig';
export {
setAlias,
setProjectType,
setEntry,
setTempDir,
setRegisterMethod,
setRegisterUserConfig
};

View File

@ -0,0 +1,19 @@
import * as path from 'path';
import { ICE_TEMP } from '../constant';
export default (api, options) => {
const { onGetWebpackConfig, context, getValue } = api;
const { rootDir } = context;
const tempPath = getValue(ICE_TEMP);
const aliasKey = options.framework === 'rax' ? 'rax-app' : 'ice';
const aliasMap = [
[`${aliasKey}$`, path.join(tempPath, 'index.ts')],
[`${aliasKey}`, path.join(tempPath, 'pages') ],
['@', path.join(rootDir, 'src')],
['aliasKey', path.join(tempPath)]
];
onGetWebpackConfig((config: any) => {
aliasMap.forEach(alias => config.resolve.alias.set(alias[0], alias[1]));
});
};

View File

@ -0,0 +1,10 @@
export default (api, options) => {
const { modifyUserConfig, context } = api;
const { userConfig } = context;
if (options.framework === 'react') {
if (!userConfig.entry) {
modifyUserConfig('entry', 'src/app');
}
}
};

View File

@ -0,0 +1,13 @@
import * as globby from 'globby';
import { PROJECT_TYPE } from '../constant';
export default (api) => {
const { context, setValue } = api;
const { rootDir } = context;
const tsEntryFiles = globby.sync(['src/app.@(ts?(x))', 'src/pages/*/app.@(ts?(x))'], {
cwd: rootDir
});
const projectType = tsEntryFiles.length ? 'ts' : 'js';
setValue(PROJECT_TYPE, projectType);
};

View File

@ -0,0 +1,45 @@
import getPages from '../utils/getPages';
import getRoutes from '../utils/getRoutes';
import formatPath from '../utils/formatPath';
import { getExportApiKeys } from '../constant';
export default (api, options) => {
const { registerMethod } = api;
const { generator } = options;
// register utils method
registerMethod('getPages', getPages);
registerMethod('formatPath', formatPath);
registerMethod('getRoutes', getRoutes);
// registerMethod for modify page
registerMethod('addPageExport', generator.addPageExport);
registerMethod('removePageExport', generator.removePageExport);
// registerMethod for add export
const apiKeys = getExportApiKeys();
apiKeys.forEach((apiKey) => {
registerMethod(apiKey, (exportData) => {
generator.addExport(apiKey, exportData);
});
registerMethod(apiKey.replace('add', 'remove'), (removeExportName) => {
generator.removeExport(apiKey, removeExportName);
});
});
const registerAPIs = {
addEntryImports: {
apiKey: 'addContent',
},
addEntryCode: {
apiKey: 'addContent',
},
};
Object.keys(registerAPIs).forEach((apiName) => {
registerMethod(apiName, (code, position = 'after') => {
const { apiKey } = registerAPIs[apiName];
generator[apiKey](apiName, code, position);
});
});
};

View File

@ -0,0 +1,6 @@
import { USER_CONFIG } from '../constant';
export default (api) => {
const { registerUserConfig } = api;
USER_CONFIG.forEach(item => registerUserConfig({ ...item }));
};

View File

@ -0,0 +1,18 @@
import * as path from 'path';
import * as fse from 'fs-extra';
import { ICE_TEMP } from '../constant';
export default (api, options) => {
const { context, setValue } = api;
const { rootDir } = context;
const { framework } = options;
const isRax = framework === 'rax';
const tempDir = isRax ? 'rax' : 'ice';
const tempPath = path.join(rootDir, `.${tempDir}`);
setValue(ICE_TEMP, tempPath);
fse.ensureDirSync(tempPath);
fse.emptyDirSync(tempPath);
};

View File

@ -0,0 +1,57 @@
export const USER_CONFIG = [
{
name: 'store',
validation: 'boolean'
},
{
name: 'ssr',
validation: 'boolean'
},
{
name: 'targets',
validation: 'array'
},
{
name: 'miniapp',
validation: 'object'
}
];
export const PROJECT_TYPE = 'PROJECT_TYPE';
export const ICE_TEMP = 'ICE_TEMP';
/**
* API_Names
*
* deprecated api
* addIceExportaddIceTypesExportaddIceAppConfigTypesaddIceAppConfigAppTypes
*
* for ice and rax
* addExportaddTypesExportaddAppConfigTypesaddAppConfigAppTypes
*/
export const EXPORT_API_MPA = [
{
name: ['addIceExport', 'addExport'],
value: ['imports', 'exports']
},
{
name: ['addIceTypesExport', 'addTypesExport'],
value: ['typesImports', 'typesExports'],
},
{
name: ['addIceAppConfigTypes', 'addAppConfigTypes'],
value: ['appConfigTypesImports', 'appConfigTypesExports']
},
{
name: ['addIceAppConfigAppTypes', 'addAppConfigAppTypes'],
value: ['appConfigAppTypesImports', 'appConfigAppTypesExports']
}
];
export const getExportApiKeys = () => {
let apiKeys = [];
EXPORT_API_MPA.forEach(item => {
apiKeys = apiKeys.concat(item.name);
});
return apiKeys;
};

View File

@ -0,0 +1,36 @@
import * as path from 'path';
import * as chokidar from 'chokidar';
export default (api, options: any = {}) => {
const { registerMethod, context } = api;
const { rootDir } = context;
const { render } = options;
const watchEvents = [];
registerMethod('watchFileChange', (pattern, action) => {
watchEvents.push([pattern, action]);
});
chokidar.watch(path.join(rootDir, 'src'), {
ignoreInitial: true,
}).on('all', (event, filePath) => {
watchEvents.forEach(([pattern, action]) => {
if (pattern instanceof RegExp && pattern.test(filePath)) {
action(event, filePath);
} else if (typeof pattern === 'string' && filePath.includes(pattern)) {
action(event, filePath);
}
});
});
// watch pages change
watchEvents.push([/src\/pages\/[A-Za-z.$]+$/, () => {
render();
}]);
// rerender when global style file added or removed
watchEvents.push([/src\/global.(scss|less|css)/, async (event: string) => {
if (event === 'unlink' || event === 'add') {
await render();
}
}]);
};

View File

@ -0,0 +1,244 @@
import * as path from 'path';
import * as fse from 'fs-extra';
import * as globby from 'globby';
import * as ejs from 'ejs';
import * as prettier from 'prettier';
import generateExports from '../utils/generateExports';
import checkExportData from '../utils/checkExportData';
import removeExportData from '../utils/removeExportData';
import getPages from '../utils/getPages';
import { IExportData } from '../types/base';
import { getExportApiKeys, EXPORT_API_MPA } from '../constant';
interface IRenderData {
[key: string]: any;
}
interface IRegistration {
[key: string]: any[];
}
interface IRenderFile {
(templatePath: string, targetDir: string, extraData?: IRenderData): void;
}
interface IPageRenderData {
pageImports: string;
pageExports: string;
}
export default class Generator {
public templatesDir: string;
public appTemplateDir: string;
public commonTemplateDir: string;
public targetDir: string;
public renderData: IRenderData;
public contentRegistration: IRegistration;
private rerender: boolean;
private rootDir: string;
private pageExports: { [key: string]: IExportData[] };
private pageRenderData: IPageRenderData;
private log: any;
private showPrettierError: boolean;
constructor({ rootDir, targetDir, templatesDir, appTemplateDir, commonTemplateDir, defaultData, log }) {
this.rootDir = rootDir;
this.templatesDir = templatesDir;
this.appTemplateDir = appTemplateDir;
this.commonTemplateDir = commonTemplateDir;
this.targetDir = targetDir;
this.renderData = defaultData;
this.contentRegistration = {};
this.rerender = false;
this.log = log;
this.showPrettierError = true;
this.pageExports = {};
this.pageRenderData = { pageImports: '', pageExports: '' };
}
public addExport = (registerKey, exportData: IExportData | IExportData[]) => {
const exportList = this.contentRegistration[registerKey] || [];
checkExportData(exportList, exportData, registerKey);
this.addContent(registerKey, exportData);
if (this.rerender) {
this.render();
}
}
public removeExport = (registerKey: string, removeExportName: string | string[]) => {
const exportList = this.contentRegistration[registerKey] || [];
this.contentRegistration[registerKey] = removeExportData(exportList, removeExportName);
if (this.rerender) {
this.render();
}
}
// addEntryImports/addEntryCode
public addContent(apiName, ...args) {
const apiKeys = getExportApiKeys();
if (!apiKeys.includes(apiName)) {
throw new Error(`invalid API ${apiName}`);
}
const [data, position] = args;
if (position && !['before', 'after'].includes(position)) {
throw new Error(`invalid position ${position}, use before|after`);
}
const registerKey = position ? `${apiName}_${position}` : apiName;
if (!this.contentRegistration[registerKey]) {
this.contentRegistration[registerKey] = [];
}
const content = Array.isArray(data) ? data : [data];
this.contentRegistration[registerKey].push(...content);
}
private getExportStr(registerKey, dataKeys) {
const exportList = this.contentRegistration[registerKey] || [];
const { importStr, exportStr } = generateExports(exportList);
const [importStrKey, exportStrKey] = dataKeys;
return {
[importStrKey]: importStr,
[exportStrKey]: exportStr
};
}
public parseRenderData() {
const staticConfig = globby.sync(['src/app.json'], { cwd: this.rootDir });
const globalStyles = globby.sync(['src/global.@(scss|less|css)'], { cwd: this.rootDir });
let exportsData = {};
EXPORT_API_MPA.forEach(item => {
item.name.forEach(key => {
const data = this.getExportStr(key, item.value);
exportsData = Object.assign({}, exportsData, data);
});
});
this.renderData = {
...this.renderData,
...this.pageRenderData,
...exportsData,
staticConfig: staticConfig.length && staticConfig[0],
globalStyle: globalStyles.length && globalStyles[0],
entryImportsBefore: this.generateImportStr('addEntryImports_before'),
entryImportsAfter: this.generateImportStr('addEntryImports_after'),
entryCodeBefore: this.contentRegistration.addEntryCode_before || '',
entryCodeAfter: this.contentRegistration.addEntryCode_after || '',
};
}
public generateImportStr(apiName) {
const imports = this.contentRegistration[apiName] || [];
return imports.map(({ source, specifier }) => {
return specifier ?
`import ${specifier} from '${source}';` : `import '${source}'`;
}).join('\n');
}
public async render() {
this.rerender = true;
const appTemplates = await globby(['**/*'], { cwd: this.appTemplateDir });
this.parseRenderData();
appTemplates.forEach((templateFile) => {
if (templateFile === 'page.ts.ejs') {
this.renderPageTemplates(templateFile);
} else {
this.renderAppTemplates(templateFile);
}
});
this.renderCommonTemplates();
}
public async renderAppTemplates(templateFile) {
this.renderFile(
path.join(this.appTemplateDir, templateFile),
path.join(this.targetDir, templateFile)
);
}
public renderPageTemplates(templateFile) {
const pages = getPages(this.rootDir);
pages.forEach((name) => {
const source = `./pages/${name}/index`;
this.pageRenderData = { ...this.getPageExport(name) };
this.renderFile(
path.join(this.appTemplateDir, templateFile),
path.join(this.targetDir, `${source}.ts`), this.pageRenderData);
});
}
public async renderCommonTemplates() {
const commonTemplates = await globby(['**/*'], { cwd: this.commonTemplateDir });
commonTemplates.forEach((templateFile) => {
this.renderFile(
path.join(this.commonTemplateDir, templateFile),
path.join(this.targetDir, templateFile)
);
});
}
public renderFile: IRenderFile = (templatePath, targetPath, extraData = {}) => {
const renderExt = '.ejs';
if (path.extname(templatePath) === '.ejs') {
const templateContent = fse.readFileSync(templatePath, 'utf-8');
let content = ejs.render(templateContent, { ...this.renderData, ...extraData });
try {
content = prettier.format(content, {
parser: 'typescript',
singleQuote: true
});
} catch (error) {
if (this.showPrettierError) {
this.log.warn(`Prettier format error: ${error.message}`);
this.showPrettierError = false;
}
}
const realTargetPath = targetPath.replace(renderExt, '');
fse.ensureDirSync(path.dirname(realTargetPath));
fse.writeFileSync(realTargetPath, content, 'utf-8');
} else {
fse.ensureDirSync(targetPath);
fse.copyFileSync(targetPath, targetPath);
}
}
private getPageExport(pageName) {
const exportList = this.pageExports[pageName] || [];
const { importStr, exportStr } = generateExports(exportList);
return {
pageImports: importStr,
pageExports: exportStr,
};
}
public addPageExport = (pageName: string, exportData: IExportData | IExportData[]) => {
if (!this.pageExports[pageName]) {
this.pageExports[pageName] = [];
}
checkExportData(this.pageExports[pageName], exportData, 'addPageExport');
this.pageExports[pageName] = [
...this.pageExports[pageName],
...(Array.isArray(exportData) ? exportData : [exportData]),
];
if (this.rerender) {
this.render();
}
}
public removePageExport = (pageName: string, removeExportName: string | string[]) => {
this.pageExports[pageName] = removeExportData(this.pageExports[pageName] || [], removeExportName);
if (this.rerender) {
this.render();
}
}
}

View File

@ -0,0 +1,9 @@
export * from './runApp';
<%- imports %>
<% if (exports) { %>
export {
<%- exports %>
}
<% } %>

View File

@ -0,0 +1,13 @@
export * from './runApp';
export { runApp as createApp } from './runApp';
export { lazy } from './lazy';
export * from './types';
export const APP_MODE = (global as any).__app_mode__ || process.env.APP_MODE;
<%- imports %>
<% if (exports) { %>
export {
<%- exports %>
}
<% } %>

View File

@ -1,6 +1,6 @@
import * as React from 'react';
function lazy(dynamicImport, isRouteComponent?: Boolean): any {
export function lazy(dynamicImport, isRouteComponent?: boolean): any {
if (isRouteComponent) {
return {
__LAZY__: true,
@ -10,6 +10,3 @@ function lazy(dynamicImport, isRouteComponent?: Boolean): any {
return React.lazy(dynamicImport);
}
}
export { lazy };

View File

@ -23,12 +23,15 @@ class ErrorBoundary extends React.Component<IProps, IState> {
Fallback: ErrorBoundaryFallback,
};
state = {
constructor(props) {
super(props);
this.state = {
error: null,
info: {
componentStack: ''
}
};
}
componentDidCatch(error: Error, info: IErrorInfo): void {
const { onError } = this.props;
@ -65,5 +68,3 @@ class ErrorBoundary extends React.Component<IProps, IState> {
}
export default ErrorBoundary;

View File

@ -0,0 +1,14 @@
let appConfig;
function setAppConfig(config) {
appConfig = config;
}
function getAppConfig() {
return appConfig;
}
export {
setAppConfig,
getAppConfig
};

View File

@ -0,0 +1,11 @@
function loadRuntimeModules(runtime) {
<% if (runtimeModules.length) {%>
<% runtimeModules.forEach((runtimeModule) => { %>
<% if(!runtimeModule.staticModule){ %>
runtime.loadModule(require('<%= runtimeModule.path %>'));
<% } %>
<% }) %>
<% } %>
}
export default loadRuntimeModules;

View File

@ -0,0 +1,11 @@
function loadStaticModules(appConfig) {
<% if (runtimeModules.length) {%>
<% runtimeModules.forEach((runtimeModule) => { %>
<% if(runtimeModule.staticModule){ %>
require('<%= runtimeModule.path %>').default({appConfig});
<% } %>
<% }) %>
<% } %>
}
export default loadStaticModules;

View File

@ -0,0 +1,24 @@
<% if(isRax){ %>
import { render } from 'rax';
import DriverUniversal from 'driver-universal';
export function mount(appInstance, rootEl) {
return render(appInstance, rootEl, {
driver: DriverUniversal
});
}
export function unmount(appInstance, rootEl) {
return appInstance._internal.unmountComponent.bind(appInstance._internal);
}
<% } else { %>
import ReactDOM from 'react-dom';
export function mount(appInstance, rootEl) {
return ReactDOM.render(appInstance, rootEl);
}
export function unmount(appInstance, rootEl) {
return () => ReactDOM.unmountComponentAtNode(rootEl);
}
<% } %>

View File

@ -0,0 +1,97 @@
import { createElement, useEffect, Component } from '<%- framework %>';
import { isMiniApp, isWeChatMiniProgram, isByteDanceMicroApp } from 'universal-env';
import miniappRenderer from 'miniapp-renderer';
import createShareAPI from 'create-app-shared';
<% if (isReact) {%>
import reactAppRenderer from 'react-app-renderer';
import { withRouter as defaultWithRouter } from 'react-router';
<% } %>
<% if (isRax) {%>
import raxAppRenderer from 'rax-app-renderer';
import { withRouter as defaultWithRouter } from 'rax-use-router';
<% } %>
import loadRuntimeModules from './loadRuntimeModules';
import loadStaticModules from './loadStaticModules';
import staticConfig from './staticConfig';
import { setAppConfig } from './appConfig';
import ErrorBoundary from './ErrorBoundary';
import { mount, unmount } from './render';
<% if (globalStyle) {%>
import '../<%= globalStyle %>'
<% } %>
const {
createBaseApp,
withRouter,
createHistory,
getHistory,
emitLifeCycles,
usePageShow,
usePageHide,
withPageLifeCycle,
pathRedirect,
registerNativeEventListeners,
addNativeEventListener,
removeNativeEventListener,
useSearchParams,
withSearchParams
} = createShareAPI({
createElement,
useEffect,
withRouter: defaultWithRouter
}, loadRuntimeModules, loadStaticModules);
export function runApp(appConfig) {
let renderer;
<% if(isRax){ %>
renderer = raxAppRenderer;
<% } %>
if (isMiniApp || isWeChatMiniProgram || isByteDanceMicroApp) {
renderer = miniappRenderer;
}<% if(isReact){ %> else {
renderer = reactAppRenderer;
}
<% } %>
renderer({
appConfig,
staticConfig,
setAppConfig,
createBaseApp,
createHistory,
getHistory,
emitLifeCycles,
pathRedirect,
ErrorBoundary
}, {
createElement,
mount,
unmount,
Component
})
};
// Public API
export {
// router api
withRouter,
getHistory,
useSearchParams,
withSearchParams,
// LifeCycles api
usePageShow,
usePageHide,
withPageLifeCycle,
// events api
registerNativeEventListeners,
addNativeEventListener,
removeNativeEventListener,
// components
ErrorBoundary,
};

View File

@ -0,0 +1,11 @@
let staticConfig = {};
try {
<% if (staticConfig) {%>
staticConfig = require('../<%= staticConfig %>');
<% } %>
} catch (error) {
// ignore error
}
export default staticConfig.__esModule ? staticConfig.default : staticConfig;

View File

@ -0,0 +1,44 @@
import React from 'react';
<%- appConfigTypesImports %>
<%- appConfigAppTypesImports %>
<%- typesImports %>
interface IOnTabItemClickParams {
from: string;
path: string;
text: string;
index: number;
}
export interface IApp {
rootId?: string;
mountNode?: HTMLElement;
addProvider?: ({ children }: { children: React.ReactNode }) => React.ReactElement;
getInitialData?: () => Promise<any>;
ErrorBoundaryFallback?: React.ComponentType,
onErrorBoundaryHander?: (error: Error, componentStack: string) => any;
onLaunch?: () => any;
onShow?: () => any;
onHide?: () => any;
onError?: (error: Error) => any;
onTabItemClick?: ({ from, path, text, index }: IOnTabItemClickParams) => any;
<% if (appConfigAppTypesImports) { %>
<%- appConfigAppTypesExports %>
<% } %>
[key: string]: any;
}
<% if (appConfigTypesImports) { %>
export interface IAppConfig {
app?: IApp
<%- appConfigTypesExports %>
}
<% } %>
declare global {
interface Window {
__ICE_SSR_ENABLED__: any;
__ICE_APP_DATA__: any;
__ICE_PAGE_PROPS__: any;
}
}

View File

@ -0,0 +1,116 @@
import * as path from 'path';
import Generator from './generator';
import getRuntimeModules from './utils/getRuntimeModules';
import { ICE_TEMP } from './constant';
import dev from './dev';
import { setAlias, setProjectType, setEntry, setTempDir, setRegisterMethod, setRegisterUserConfig } from './config';
// eslint-disable-next-line
const chalk = require('chalk');
export default (api, options) => {
const { onHook, context } = api;
const { command, userConfig } = context;
const { targets } = userConfig;
// Check target
checkTargets(targets);
// Set temporary directory
// eg: .ice or .rax
setTempDir(api, options);
// Set project type
// eg: ts | js
setProjectType(api);
// Modify default entry to src/app
// eg: src/app.(ts|js)
setEntry(api, options);
// Set alias
setAlias(api, options);
// register config in build.json
setRegisterUserConfig(api);
// register api method
const generator = initGenerator(api, options);
setRegisterMethod(api, { generator });
// watch src folder
if (command === 'start') {
dev(api, { render: generator.render });
}
onHook(`before.${command}.run`, async () => {
await generator.render();
});
};
function initGenerator(api, options) {
const { getAllPlugin, context, log, getValue } = api;
const { userConfig, rootDir } = context;
const { framework } = options;
const plugins = getAllPlugin();
const templatesDir = path.join(__dirname, './generator/templates');
const { targets = [] } = userConfig;
const isMiniapp = targets.includes('miniapp') || targets.includes('wechat-miniprogram');
return new Generator({
rootDir,
targetDir: getValue(ICE_TEMP),
templatesDir,
appTemplateDir: path.join(templatesDir, `./app/${framework}`),
commonTemplateDir: path.join(templatesDir, './common'),
defaultData: {
framework,
isReact: framework === 'react',
isRax: framework === 'rax',
isMiniapp,
runtimeModules: getRuntimeModules(plugins),
buildConfig: JSON.stringify(userConfig)
},
log
});
}
function checkTargets(targets) {
let hasError = false;
if (Object.prototype.toString.call(targets) === '[object Object]') {
hasError = true;
}
if (typeof targets === 'string') {
hasError = true;
}
if (Array.isArray(targets) && !matchTargets(targets)) {
hasError = true;
}
if (hasError) {
const msg = `
targets must be the array type in build.json.
e.g. { "targets": ["miniapp", "wechat-miniprogram"] }
if you want to describes the browserslist environments for your project.
you should set browserslist in build.json.
e.g. { "browserlist": { "chrome": "58", "ie": 11 } }
`;
console.log();
console.log(chalk.red(msg));
console.log();
process.exit(1);
}
}
function matchTargets(targets) {
return targets.every(target => {
return ['web', 'miniapp', 'wechat-miniprogram'].includes(target);
});
}

View File

@ -0,0 +1,40 @@
import * as React from 'react';
import * as queryString from 'query-string';
// @ts-ignore
import { ErrorBoundary } from 'ice';
const module = ({ addProvider, appConfig, wrapperRouteComponent }) => {
const { app = {} } = appConfig;
const { ErrorBoundaryFallback, onErrorBoundaryHander, parseSearchParams } = app;
const wrapperPageComponent = (PageComponent) => {
const { pageConfig = {} } = PageComponent;
const WrapperedPageComponent = (props) => {
const searchParams = getSearchParams(parseSearchParams, props.location.search);
if (pageConfig.errorBoundary) {
return (
<ErrorBoundary Fallback={ErrorBoundaryFallback} onError={onErrorBoundaryHander}>
<PageComponent {... Object.assign({}, props, searchParams)} />
</ErrorBoundary>
);
}
return <PageComponent {... Object.assign({}, props, searchParams)} />;
};
return WrapperedPageComponent;
};
wrapperRouteComponent(wrapperPageComponent);
if (appConfig.app && appConfig.app.addProvider) {
addProvider(appConfig.app.addProvider);
}
};
function getSearchParams(parseSearchParams, locationSearch) {
if (parseSearchParams) {
const searchParams = queryString.parse(locationSearch);
return { searchParams };
}
}
export default module;

Some files were not shown because too many files have changed in this diff Show More