chore(): React Build Scripts (#19501)

This commit is contained in:
Ely Lucas
2019-10-07 10:37:20 -06:00
committed by GitHub
parent aed2dba5aa
commit 8706ecf9c3
26 changed files with 1629 additions and 1273 deletions

View File

@ -108,6 +108,58 @@ jobs:
paths: paths:
- "*" - "*"
build-react:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: npm install
working_directory: /tmp/workspace/packages/react
- run:
command: sudo npm link
working_directory: /tmp/workspace/core
- run:
command: sudo npm link @ionic/core
working_directory: /tmp/workspace/packages/react
- run:
command: npm run build
working_directory: /tmp/workspace/packages/react
- persist_to_workspace:
root: /tmp/workspace
paths:
- "*"
build-react-router:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: npm install
working_directory: /tmp/workspace/packages/react-router
- run:
command: sudo npm link
working_directory: /tmp/workspace/core
- run:
command: sudo npm link @ionic/core
working_directory: /tmp/workspace/packages/react-router
- run:
command: sudo npm link
working_directory: /tmp/workspace/packages/react
- run:
command: sudo npm link @ionic/react
working_directory: /tmp/workspace/packages/react-router
- run:
command: npm run build
working_directory: /tmp/workspace/packages/react-router
- persist_to_workspace:
root: /tmp/workspace
paths:
- "*"
test-core-clean-build: test-core-clean-build:
<<: *defaults <<: *defaults
steps: steps:
@ -181,6 +233,64 @@ jobs:
command: npm run lint command: npm run lint
working_directory: /tmp/workspace/angular working_directory: /tmp/workspace/angular
test-react-lint:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: npm run lint
working_directory: /tmp/workspace/packages/react
test-react-router-lint:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: npm run lint
working_directory: /tmp/workspace/packages/react-router
test-react-spec:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: sudo npm link
working_directory: /tmp/workspace/core
- run:
command: sudo npm link @ionic/core
working_directory: /tmp/workspace/packages/react
- run:
command: npm run test.spec
working_directory: /tmp/workspace/packages/react
test-react-router-spec:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: sudo npm link
working_directory: /tmp/workspace/core
- run:
command: sudo npm link @ionic/core
working_directory: /tmp/workspace/packages/react
- run:
command: sudo npm link
working_directory: /tmp/workspace/packages/react
- run:
command: sudo npm link @ionic/react
working_directory: /tmp/workspace/packages/react-router
- run:
command: npm run test.spec
working_directory: /tmp/workspace/packages/react-router
test-angular-e2e: test-angular-e2e:
<<: *defaults <<: *defaults
steps: steps:
@ -227,6 +337,18 @@ workflows:
requires: [build-core] requires: [build-core]
- build-angular-server: - build-angular-server:
requires: [build-angular] requires: [build-angular]
- build-react:
requires: [build-core]
- build-react-router:
requires: [build-core, build-react]
- test-react-lint:
requires: [build-react]
- test-react-router-lint:
requires: [build-react-router]
- test-react-spec:
requires: [build-react]
- test-react-router-spec:
requires: [build-react-router]
- test-angular-lint: - test-angular-lint:
requires: [build-angular] requires: [build-angular]
- test-angular-e2e: - test-angular-e2e:

View File

@ -11,6 +11,8 @@ const packages = [
'core', 'core',
'docs', 'docs',
'angular', 'angular',
'packages/react',
'packages/react-router'
]; ];
function readPkg(project) { function readPkg(project) {
@ -36,7 +38,8 @@ function checkGit(tasks) {
tasks.push( tasks.push(
{ {
title: 'Check current branch', title: 'Check current branch',
task: () => execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']).then(branch => { task: () =>
execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']).then(branch => {
if (branch.indexOf('release') === -1 && branch.indexOf('hotfix') === -1) { if (branch.indexOf('release') === -1 && branch.indexOf('hotfix') === -1) {
throw new Error(`Must be on a "release" or "hotfix" branch.`); throw new Error(`Must be on a "release" or "hotfix" branch.`);
} }
@ -44,7 +47,8 @@ function checkGit(tasks) {
}, },
{ {
title: 'Check local working tree', title: 'Check local working tree',
task: () => execa.stdout('git', ['status', '--porcelain']).then(status => { task: () =>
execa.stdout('git', ['status', '--porcelain']).then(status => {
if (status !== '') { if (status !== '') {
throw new Error(`Unclean working tree. Commit or stash changes first.`); throw new Error(`Unclean working tree. Commit or stash changes first.`);
} }
@ -52,7 +56,8 @@ function checkGit(tasks) {
}, },
{ {
title: 'Check remote history', title: 'Check remote history',
task: () => execa.stdout('git', ['rev-list', '--count', '--left-only', '@{u}...HEAD']).then(result => { task: () =>
execa.stdout('git', ['rev-list', '--count', '--left-only', '@{u}...HEAD']).then(result => {
if (result !== '0') { if (result !== '0') {
throw new Error(`Remote history differs. Please pull changes.`); throw new Error(`Remote history differs. Please pull changes.`);
} }
@ -61,8 +66,19 @@ function checkGit(tasks) {
); );
} }
const isValidVersion = input => Boolean(semver.valid(input)); function checkTestDist(tasks) {
tasks.push({
title: 'Check dist folders for required files',
task: () =>
execa.stdout('node', ['.scripts/test-dist.js']).then(status => {
if (status.indexOf('✅ test.dist') === -1) {
throw new Error(`Test Dist did not find some required files`);
}
})
});
}
const isValidVersion = input => Boolean(semver.valid(input));
function preparePackage(tasks, package, version, install) { function preparePackage(tasks, package, version, install) {
const projectRoot = projectPath(package); const projectRoot = projectPath(package);
@ -74,7 +90,9 @@ function preparePackage(tasks, package, version, install) {
title: `${pkg.name}: validate new version`, title: `${pkg.name}: validate new version`,
task: () => { task: () => {
if (!isVersionGreater(pkg.version, version)) { if (!isVersionGreater(pkg.version, version)) {
throw new Error(`New version \`${version}\` should be higher than current version \`${pkg.version}\``); throw new Error(
`New version \`${version}\` should be higher than current version \`${pkg.version}\``
);
} }
} }
}); });
@ -82,7 +100,7 @@ function preparePackage(tasks, package, version, install) {
projectTasks.push({ projectTasks.push({
title: `${pkg.name}: install npm dependencies`, title: `${pkg.name}: install npm dependencies`,
task: async () => { task: async () => {
await fs.remove(path.join(projectRoot, 'node_modules')) await fs.remove(path.join(projectRoot, 'node_modules'));
await execa('npm', ['i'], { cwd: projectRoot }); await execa('npm', ['i'], { cwd: projectRoot });
} }
}); });
@ -95,6 +113,13 @@ function preparePackage(tasks, package, version, install) {
title: `${pkg.name}: npm link @ionic/core`, title: `${pkg.name}: npm link @ionic/core`,
task: () => execa('npm', ['link', '@ionic/core'], { cwd: projectRoot }) task: () => execa('npm', ['link', '@ionic/core'], { cwd: projectRoot })
}); });
if (package === 'packages/react-router') {
projectTasks.push({
title: `${pkg.name}: npm link @ionic/react`,
task: () => execa('npm', ['link', '@ionic/react'], { cwd: projectRoot })
});
}
} }
if (version) { if (version) {
@ -105,7 +130,7 @@ function preparePackage(tasks, package, version, install) {
projectTasks.push({ projectTasks.push({
title: `${pkg.name}: update ionic/core dep to ${version}`, title: `${pkg.name}: update ionic/core dep to ${version}`,
task: () => { task: () => {
updateDependency(pkg, "@ionic/core", version); updateDependency(pkg, '@ionic/core', version);
writePkg(package, pkg); writePkg(package, pkg);
} }
}); });
@ -134,7 +159,6 @@ function preparePackage(tasks, package, version, install) {
}); });
} }
function prepareDevPackage(tasks, package, version) { function prepareDevPackage(tasks, package, version) {
const projectRoot = projectPath(package); const projectRoot = projectPath(package);
const pkg = readPkg(package); const pkg = readPkg(package);
@ -152,7 +176,7 @@ function prepareDevPackage(tasks, package, version) {
projectTasks.push({ projectTasks.push({
title: `${pkg.name}: update ionic/core dep to ${version}`, title: `${pkg.name}: update ionic/core dep to ${version}`,
task: () => { task: () => {
updateDependency(pkg, "@ionic/core", version); updateDependency(pkg, '@ionic/core', version);
writePkg(package, pkg); writePkg(package, pkg);
} }
}); });
@ -181,8 +205,7 @@ function updatePackageVersions(tasks, packages, version) {
packages.forEach(package => { packages.forEach(package => {
updatePackageVersion(tasks, package, version); updatePackageVersion(tasks, package, version);
tasks.push( tasks.push({
{
title: `${package} update @ionic/core dependency, if present ${tc.dim(`(${version})`)}`, title: `${package} update @ionic/core dependency, if present ${tc.dim(`(${version})`)}`,
task: async () => { task: async () => {
if (package !== 'core') { if (package !== 'core') {
@ -190,24 +213,30 @@ function updatePackageVersions(tasks, packages, version) {
updateDependency(pkg, '@ionic/core', version); updateDependency(pkg, '@ionic/core', version);
writePkg(package, pkg); writePkg(package, pkg);
} }
},
} }
) });
if (package === 'packages/react-router') {
tasks.push({
title: `${package} update @ionic/react dependency, if present ${tc.dim(`(${version})`)}`,
task: async () => {
const pkg = readPkg(package);
updateDependency(pkg, '@ionic/react', version);
writePkg(package, pkg);
}
});
}
}); });
} }
function updatePackageVersion(tasks, package, version) { function updatePackageVersion(tasks, package, version) {
const projectRoot = projectPath(package); const projectRoot = projectPath(package);
tasks.push( tasks.push({
{
title: `${package}: update package.json ${tc.dim(`(${version})`)}`, title: `${package}: update package.json ${tc.dim(`(${version})`)}`,
task: async () => { task: async () => {
await execa('npm', ['version', version], { cwd: projectRoot }); await execa('npm', ['version', version], { cwd: projectRoot });
} }
} });
);
} }
function publishPackages(tasks, packages, version, tag = 'latest') { function publishPackages(tasks, packages, version, tag = 'latest') {
@ -237,7 +266,7 @@ function publishPackages(tasks, packages, version, tag = 'latest') {
title: `${package}: publish to ${tag} tag`, title: `${package}: publish to ${tag} tag`,
task: async () => { task: async () => {
await execa('npm', ['publish', '--tag', tag], { cwd: projectRoot }); await execa('npm', ['publish', '--tag', tag], { cwd: projectRoot });
}, }
}); });
}); });
} }
@ -261,11 +290,12 @@ function isVersionGreater(oldVersion, newVersion) {
function copyCDNLoader(tasks, version) { function copyCDNLoader(tasks, version) {
tasks.push({ tasks.push({
title: `Copy CDN loader`, title: `Copy CDN loader`,
task: () => execa('node', ['copy-cdn-loader.js', version], { cwd: path.join(rootDir, 'core', 'scripts') }), task: () => execa('node', ['copy-cdn-loader.js', version], { cwd: path.join(rootDir, 'core', 'scripts') })
}); });
} }
module.exports = { module.exports = {
checkTestDist,
checkGit, checkGit,
isValidVersion, isValidVersion,
isVersionGreater, isVersionGreater,
@ -281,5 +311,5 @@ module.exports = {
updateDependency, updateDependency,
updatePackageVersion, updatePackageVersion,
updatePackageVersions, updatePackageVersions,
writePkg, writePkg
}; };

View File

@ -107,13 +107,14 @@ async function preparePackages(packages, version, install) {
}); });
// add update package.json of each project // add update package.json of each project
packages.forEach(package => { common.updatePackageVersions(tasks, packages, version);
common.updatePackageVersion(tasks, package, version);
});
// generate changelog // generate changelog
generateChangeLog(tasks); generateChangeLog(tasks);
// check dist folders
common.checkTestDist(tasks);
// update core readme with version number // update core readme with version number
updateCoreReadme(tasks, version); updateCoreReadme(tasks, version);
common.copyCDNLoader(tasks, version); common.copyCDNLoader(tasks, version);

82
.scripts/test-dist.js Normal file
View File

@ -0,0 +1,82 @@
const path = require('path');
const fs = require('fs');
// Test dist build:
// Double-triple check all the packages
// and files are good to go before publishing
[
// core
{
files: ['../core/dist/index.js', '../core/dist/ionic/index.esm.js']
},
// angular
{
files: ['../angular/dist/fesm5.cjs.js']
},
// react
{
files: ['../packages/react/dist/index.js']
},
// react-router
{
files: ['../packages/react-router/dist/index.js']
}
].forEach(testPackage);
function testPackage(testPkg) {
if (testPkg.packageJson) {
const pkgDir = path.dirname(testPkg.packageJson);
const pkgJson = require(testPkg.packageJson);
if (!pkgJson.name) {
throw new Error('missing package.json name: ' + testPkg.packageJson);
}
if (!pkgJson.main) {
throw new Error('missing package.json main: ' + testPkg.packageJson);
}
const pkgPath = path.join(pkgDir, pkgJson.main);
const pkgImport = require(pkgPath);
if (testPkg.files) {
if (!Array.isArray(pkgJson.files)) {
throw new Error(testPkg.packageJson + ' missing "files" property');
}
testPkg.files.forEach(testPkgFile => {
if (!pkgJson.files.includes(testPkgFile)) {
throw new Error(testPkg.packageJson + ' missing file ' + testPkgFile);
}
const filePath = path.join(__dirname, pkgDir, testPkgFile);
fs.accessSync(filePath);
});
}
if (pkgJson.module) {
const moduleIndex = path.join(__dirname, pkgDir, pkgJson.module);
fs.accessSync(moduleIndex);
}
if (pkgJson.types) {
const pkgTypes = path.join(__dirname, pkgDir, pkgJson.types);
fs.accessSync(pkgTypes);
}
if (testPkg.exports) {
testPkg.exports.forEach(exportName => {
const m = pkgImport[exportName];
if (!m) {
throw new Error('export "' + exportName + '" not found in: ' + testPkg.packageJson);
}
});
}
} else if (testPkg.files) {
testPkg.files.forEach(file => {
const filePath = path.join(__dirname, file);
fs.statSync(filePath);
});
}
}
console.log(`✅ test.dist`);

View File

@ -1,5 +1,3 @@
global.crypto = require('@trust/webcrypto');
window.matchMedia = window.matchMedia || function() { window.matchMedia = window.matchMedia || function() {
return { return {
matches : false, matches : false,

View File

@ -24,8 +24,10 @@
"clean": "rm -rf dist dist-transpiled", "clean": "rm -rf dist dist-transpiled",
"compile": "npm run tsc && rollup -c", "compile": "npm run tsc && rollup -c",
"release": "np --any-branch --no-cleanup", "release": "np --any-branch --no-cleanup",
"lint": "tslint --project .",
"lint.fix": "tslint --project . --fix",
"tsc": "tsc -p .", "tsc": "tsc -p .",
"test": "jest" "test.spec": "jest --ci"
}, },
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.esm.js", "module": "dist/index.esm.js",
@ -34,7 +36,10 @@
"dist/" "dist/"
], ],
"dependencies": { "dependencies": {
"tslib": "*" "tslib": "*",
"tslint": "^5.20.0",
"tslint-ionic-rules": "0.0.21",
"tslint-react": "^4.1.0"
}, },
"peerDependencies": { "peerDependencies": {
"@ionic/core": "^4.10.0", "@ionic/core": "^4.10.0",

View File

@ -1,4 +1,4 @@
import { match, RouteProps } from 'react-router-dom'; import { RouteProps, match } from 'react-router-dom';
export interface IonRouteData { export interface IonRouteData {
match: match<{ tab: string }> | null; match: match<{ tab: string }> | null;

View File

@ -3,9 +3,11 @@ import { NavContext, NavContextState } from '@ionic/react';
import { Location as HistoryLocation, UnregisterCallback } from 'history'; import { Location as HistoryLocation, UnregisterCallback } from 'history';
import React from 'react'; import React from 'react';
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom';
import { StackManager } from './StackManager';
import { generateId } from '../utils'; import { generateId } from '../utils';
import { LocationHistory } from '../utils/LocationHistory' import { LocationHistory } from '../utils/LocationHistory';
import { StackManager } from './StackManager';
import { ViewItem } from './ViewItem'; import { ViewItem } from './ViewItem';
import { ViewStack } from './ViewStacks'; import { ViewStack } from './ViewStacks';
@ -13,12 +15,11 @@ interface NavManagerProps extends RouteComponentProps {
findViewInfoByLocation: (location: HistoryLocation) => { view?: ViewItem, viewStack?: ViewStack }; findViewInfoByLocation: (location: HistoryLocation) => { view?: ViewItem, viewStack?: ViewStack };
findViewInfoById: (id: string) => { view?: ViewItem, viewStack?: ViewStack }; findViewInfoById: (id: string) => { view?: ViewItem, viewStack?: ViewStack };
getActiveIonPage: () => { view?: ViewItem, viewStack?: ViewStack }; getActiveIonPage: () => { view?: ViewItem, viewStack?: ViewStack };
}; }
interface NavManagerState extends NavContextState {};
export class NavManager extends React.Component<NavManagerProps, NavManagerState> { export class NavManager extends React.Component<NavManagerProps, NavContextState> {
listenUnregisterCallback: UnregisterCallback; listenUnregisterCallback: UnregisterCallback | undefined;
locationHistory: LocationHistory = new LocationHistory(); locationHistory: LocationHistory = new LocationHistory();
constructor(props: NavManagerProps) { constructor(props: NavManagerProps) {
@ -32,13 +33,13 @@ export class NavManager extends React.Component<NavManagerProps, NavManagerState
getStackManager: this.getStackManager.bind(this), getStackManager: this.getStackManager.bind(this),
getPageManager: this.getPageManager.bind(this), getPageManager: this.getPageManager.bind(this),
currentPath: this.props.location.pathname, currentPath: this.props.location.pathname,
registerIonPage: () => {} //overridden in View for each IonPage registerIonPage: () => { return; } // overridden in View for each IonPage
} };
this.listenUnregisterCallback = this.props.history.listen((location: HistoryLocation) => { this.listenUnregisterCallback = this.props.history.listen((location: HistoryLocation) => {
this.setState({ this.setState({
currentPath: location.pathname currentPath: location.pathname
}) });
this.locationHistory.add(location); this.locationHistory.add(location);
}); });
@ -69,10 +70,14 @@ export class NavManager extends React.Component<NavManagerProps, NavManagerState
this.props.history.replace(enteringView.routeData.match.url, { direction: 'back' }); this.props.history.replace(enteringView.routeData.match.url, { direction: 'back' });
} }
} else { } else {
defaultHref && this.props.history.replace(defaultHref, { direction: 'back' }); if (defaultHref) {
this.props.history.replace(defaultHref, { direction: 'back' });
}
} }
} else { } else {
defaultHref && this.props.history.replace(defaultHref, { direction: 'back' }); if (defaultHref) {
this.props.history.replace(defaultHref, { direction: 'back' });
}
} }
} }

View File

@ -1,5 +1,6 @@
import React, { ReactNode } from 'react';
import { NavDirection } from '@ionic/core'; import { NavDirection } from '@ionic/core';
import React, { ReactNode } from 'react';
import { ViewStacks } from './ViewStacks'; import { ViewStacks } from './ViewStacks';
export interface RouteManagerContextState { export interface RouteManagerContextState {
@ -15,11 +16,11 @@ export const RouteManagerContext = /*@__PURE__*/React.createContext<RouteManager
viewStacks: new ViewStacks(), viewStacks: new ViewStacks(),
syncView: () => { navContextNotFoundError(); }, syncView: () => { navContextNotFoundError(); },
hideView: () => { navContextNotFoundError(); }, hideView: () => { navContextNotFoundError(); },
setupIonRouter: () => { return Promise.reject(navContextNotFoundError()) }, setupIonRouter: () => Promise.reject(navContextNotFoundError()),
removeViewStack: () => { navContextNotFoundError(); }, removeViewStack: () => { navContextNotFoundError(); },
transitionView: () => { navContextNotFoundError(); } transitionView: () => { navContextNotFoundError(); }
}); });
function navContextNotFoundError() { function navContextNotFoundError() {
console.error('IonReactRouter not found, did you add it to the app?') console.error('IonReactRouter not found, did you add it to the app?');
} }

View File

@ -2,27 +2,26 @@ import { NavDirection } from '@ionic/core';
import { RouterDirection } from '@ionic/react'; import { RouterDirection } from '@ionic/react';
import { Action as HistoryAction, Location as HistoryLocation, UnregisterCallback } from 'history'; import { Action as HistoryAction, Location as HistoryLocation, UnregisterCallback } from 'history';
import React from 'react'; import React from 'react';
import { BrowserRouter, BrowserRouterProps, matchPath, RouteComponentProps, withRouter } from 'react-router-dom'; import { BrowserRouter, BrowserRouterProps, RouteComponentProps, matchPath, withRouter } from 'react-router-dom';
import { generateId } from '../utils'; import { generateId } from '../utils';
import { IonRouteData } from './IonRouteData'; import { IonRouteData } from './IonRouteData';
import { NavManager } from './NavManager'; import { NavManager } from './NavManager';
import { RouteManagerContext, RouteManagerContextState } from './RouteManagerContext'; import { RouteManagerContext, RouteManagerContextState } from './RouteManagerContext';
import { ViewItem } from './ViewItem'; import { ViewItem } from './ViewItem';
import { ViewStacks, ViewStack } from './ViewStacks'; import { ViewStack, ViewStacks } from './ViewStacks';
interface RouteManagerProps extends RouteComponentProps { }
interface RouteManagerState extends RouteManagerContextState { interface RouteManagerState extends RouteManagerContextState {
location?: HistoryLocation, location?: HistoryLocation;
action?: HistoryAction action?: HistoryAction;
} }
class RouteManager extends React.Component<RouteManagerProps, RouteManagerState> { class RouteManager extends React.Component<RouteComponentProps, RouteManagerState> {
listenUnregisterCallback: UnregisterCallback | undefined; listenUnregisterCallback: UnregisterCallback | undefined;
activeIonPageId?: string; activeIonPageId?: string;
constructor(props: RouteManagerProps) { constructor(props: RouteComponentProps) {
super(props); super(props);
this.listenUnregisterCallback = this.props.history.listen(this.historyChange.bind(this)); this.listenUnregisterCallback = this.props.history.listen(this.historyChange.bind(this));
this.state = { this.state = {
@ -35,7 +34,7 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
}; };
} }
componentDidUpdate(_prevProps: RouteManagerProps, prevState: RouteManagerState) { componentDidUpdate(_prevProps: RouteComponentProps, prevState: RouteManagerState) {
// Trigger a page change if the location or action is different // Trigger a page change if the location or action is different
if (this.state.location && prevState.location !== this.state.location || prevState.action !== this.state.action) { if (this.state.location && prevState.location !== this.state.location || prevState.action !== this.state.action) {
this.setActiveView(this.state.location!, this.state.action!); this.setActiveView(this.state.location!, this.state.action!);
@ -61,7 +60,7 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
this.setState({ this.setState({
location, location,
action action
}) });
} }
setActiveView(location: HistoryLocation, action: HistoryAction) { setActiveView(location: HistoryLocation, action: HistoryAction) {
@ -77,8 +76,6 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
} }
leavingView = viewStacks.findViewInfoById(this.activeIonPageId).view; leavingView = viewStacks.findViewInfoById(this.activeIonPageId).view;
if (enteringView) {
if (enteringView.isIonRoute) { if (enteringView.isIonRoute) {
enteringView.show = true; enteringView.show = true;
enteringView.mount = true; enteringView.mount = true;
@ -107,7 +104,6 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
enteringView.mount = true; enteringView.mount = true;
enteringView.routeData.match = match!; enteringView.routeData.match = match!;
} }
}
}); });
if (leavingView) { if (leavingView) {
@ -120,7 +116,7 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
this.setState({ this.setState({
viewStacks viewStacks
}, () => { }, () => {
const { view: enteringView, viewStack } = this.state.viewStacks.findViewInfoById(this.activeIonPageId) const { view: enteringView, viewStack } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
if (enteringView && viewStack) { if (enteringView && viewStack) {
const enteringEl = enteringView.ionPageElement ? enteringView.ionPageElement : undefined; const enteringEl = enteringView.ionPageElement ? enteringView.ionPageElement : undefined;
const leavingEl = leavingView && leavingView.ionPageElement ? leavingView.ionPageElement : undefined; const leavingEl = leavingView && leavingView.ionPageElement ? leavingView.ionPageElement : undefined;
@ -132,7 +128,7 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
enteringEl!, enteringEl!,
leavingEl!, leavingEl!,
viewStack.routerOutlet, viewStack.routerOutlet,
navDirection) navDirection);
} else if (leavingEl) { } else if (leavingEl) {
leavingEl.classList.add('ion-page-hidden'); leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true'); leavingEl.setAttribute('aria-hidden', 'true');
@ -142,7 +138,9 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
} }
componentWillUnmount() { componentWillUnmount() {
this.listenUnregisterCallback && this.listenUnregisterCallback(); if (this.listenUnregisterCallback) {
this.listenUnregisterCallback();
}
} }
async setupIonRouter(id: string, children: any, routerOutlet: HTMLIonRouterOutletElement) { async setupIonRouter(id: string, children: any, routerOutlet: HTMLIonRouterOutletElement) {
@ -172,12 +170,12 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
match, match,
childProps: child.props childProps: child.props
}, },
route: route, route,
mount: true, mount: true,
show: !!match, show: !!match,
isIonRoute: false isIonRoute: false
}; };
if (!!match && view.isIonRoute) { if (match && view.isIonRoute) {
activeId = viewId; activeId = viewId;
} }
return view; return view;
@ -186,9 +184,9 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
async registerViewStack(stack: string, activeId: string | undefined, stackItems: ViewItem[], routerOutlet: HTMLIonRouterOutletElement, _location: HistoryLocation) { async registerViewStack(stack: string, activeId: string | undefined, stackItems: ViewItem[], routerOutlet: HTMLIonRouterOutletElement, _location: HistoryLocation) {
return new Promise((resolve) => { return new Promise(resolve => {
this.setState((prevState) => { this.setState(prevState => {
const prevViewStacks = Object.assign(new ViewStacks, prevState.viewStacks); const prevViewStacks = Object.assign(new ViewStacks(), prevState.viewStacks);
const newStack: ViewStack = { const newStack: ViewStack = {
id: stack, id: stack,
views: stackItems, views: stackItems,
@ -205,7 +203,7 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
resolve(); resolve();
}); });
}); });
}; }
removeViewStack(stack: string) { removeViewStack(stack: string) {
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks); const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
@ -216,7 +214,7 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
} }
syncView(page: HTMLElement, viewId: string) { syncView(page: HTMLElement, viewId: string) {
this.setState((state) => { this.setState(state => {
const viewStacks = Object.assign(new ViewStacks(), state.viewStacks); const viewStacks = Object.assign(new ViewStacks(), state.viewStacks);
const { view } = viewStacks.findViewInfoById(viewId); const { view } = viewStacks.findViewInfoById(viewId);
@ -226,14 +224,14 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
return { return {
viewStacks viewStacks
} };
}, () => { }, () => {
this.setActiveView(this.state.location || this.props.location, this.state.action!); this.setActiveView(this.state.location || this.props.location, this.state.action!);
}) });
} }
transitionView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet: HTMLIonRouterOutletElement, direction?: NavDirection) { transitionView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet?: HTMLIonRouterOutletElement, direction?: NavDirection) {
/** /**
* Super hacky workaround to make sure ionRouterOutlet is available * Super hacky workaround to make sure ionRouterOutlet is available
* since transitionView might be called before IonRouterOutlet is fully mounted * since transitionView might be called before IonRouterOutlet is fully mounted
@ -256,7 +254,7 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
await ionRouterOuter.commit(enteringEl, leavingEl, { await ionRouterOuter.commit(enteringEl, leavingEl, {
deepWait: true, deepWait: true,
duration: direction === undefined ? 0 : undefined, duration: direction === undefined ? 0 : undefined,
direction: direction, direction,
showGoBack: direction === 'forward', showGoBack: direction === 'forward',
progressAnimation: false progressAnimation: false
}); });
@ -271,7 +269,8 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
render() { render() {
return ( return (
<RouteManagerContext.Provider value={this.state}> <RouteManagerContext.Provider value={this.state}>
<NavManager {...this.props} <NavManager
{...this.props}
findViewInfoById={(id: string) => this.state.viewStacks.findViewInfoById(id)} findViewInfoById={(id: string) => this.state.viewStacks.findViewInfoById(id)}
findViewInfoByLocation={(location: HistoryLocation) => this.state.viewStacks.findViewInfoByLocation(location)} findViewInfoByLocation={(location: HistoryLocation) => this.state.viewStacks.findViewInfoByLocation(location)}
getActiveIonPage={() => this.state.viewStacks.findViewInfoById(this.activeIonPageId)} getActiveIonPage={() => this.state.viewStacks.findViewInfoById(this.activeIonPageId)}
@ -281,7 +280,7 @@ class RouteManager extends React.Component<RouteManagerProps, RouteManagerState>
</RouteManagerContext.Provider> </RouteManagerContext.Provider>
); );
} }
}; }
const RouteManagerWithRouter = withRouter(RouteManager); const RouteManagerWithRouter = withRouter(RouteManager);
RouteManagerWithRouter.displayName = 'RouteManager'; RouteManagerWithRouter.displayName = 'RouteManager';

View File

@ -1,17 +1,17 @@
import React from 'react'; import React from 'react';
import { generateId, isDevMode } from '../utils'; import { generateId, isDevMode } from '../utils';
import { View } from './View';
import { ViewTransitionManager } from './ViewTransitionManager';
import { RouteManagerContext } from './RouteManagerContext'; import { RouteManagerContext } from './RouteManagerContext';
import { View } from './View';
import { ViewItem } from './ViewItem'; import { ViewItem } from './ViewItem';
import { ViewTransitionManager } from './ViewTransitionManager';
type StackManagerProps = { interface StackManagerProps {
id?: string; id?: string;
}; }
type StackManagerState = {} export class StackManager extends React.Component<StackManagerProps, {}> {
export class StackManager extends React.Component<StackManagerProps, StackManagerState> {
routerOutletEl: React.RefObject<HTMLIonRouterOutletElement> = React.createRef(); routerOutletEl: React.RefObject<HTMLIonRouterOutletElement> = React.createRef();
context!: React.ContextType<typeof RouteManagerContext>; context!: React.ContextType<typeof RouteManagerContext>;
id: string; id: string;
@ -52,7 +52,7 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
const views = (viewStack || { views: [] }).views.filter(x => x.show); const views = (viewStack || { views: [] }).views.filter(x => x.show);
const ionRouterOutlet = React.Children.only(this.props.children) as React.ReactElement; const ionRouterOutlet = React.Children.only(this.props.children) as React.ReactElement;
const childElements = views.map((view) => { const childElements = views.map(view => {
return ( return (
<ViewTransitionManager <ViewTransitionManager
id={view.id} id={view.id}
@ -72,15 +72,14 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
const elementProps: any = { const elementProps: any = {
ref: this.routerOutletEl ref: this.routerOutletEl
} };
if (isDevMode()) { if (isDevMode()) {
elementProps['data-stack-id'] = this.id elementProps['data-stack-id'] = this.id;
} }
const routerOutletChild = React.cloneElement(ionRouterOutlet, elementProps, childElements); const routerOutletChild = React.cloneElement(ionRouterOutlet, elementProps, childElements);
return routerOutletChild; return routerOutletChild;
} }

View File

@ -1,21 +1,21 @@
import React from 'react';
import { IonLifeCycleContext, NavContext } from '@ionic/react'; import { IonLifeCycleContext, NavContext } from '@ionic/react';
import { ViewItem } from './ViewItem'; import React from 'react';
import { Route, Redirect } from 'react-router-dom'; import { Redirect, Route } from 'react-router-dom';
import { isDevMode } from '../utils'; import { isDevMode } from '../utils';
import { ViewItem } from './ViewItem';
interface ViewProps extends React.HTMLAttributes<HTMLElement> { interface ViewProps extends React.HTMLAttributes<HTMLElement> {
onViewSync: (page: HTMLElement, viewId: string) => void; onViewSync: (page: HTMLElement, viewId: string) => void;
onHideView: (viewId: string) => void; onHideView: (viewId: string) => void;
view: ViewItem; view: ViewItem;
}; }
interface StackViewState { }
/** /**
* The View component helps manage the IonPage's lifecycle and registration * The View component helps manage the IonPage's lifecycle and registration
*/ */
export class View extends React.Component<ViewProps, StackViewState> { export class View extends React.Component<ViewProps, {}> {
context!: React.ContextType<typeof IonLifeCycleContext>; context!: React.ContextType<typeof IonLifeCycleContext>;
ionPage?: HTMLElement; ionPage?: HTMLElement;
@ -78,7 +78,7 @@ export class View extends React.Component<ViewProps, StackViewState> {
const newProvider = { const newProvider = {
...value, ...value,
registerIonPage: this.registerIonPage.bind(this) registerIonPage: this.registerIonPage.bind(this)
} };
return ( return (
<NavContext.Provider value={newProvider}> <NavContext.Provider value={newProvider}>
{this.props.children} {this.props.children}

View File

@ -1,14 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import { deprecationWarning } from '../utils'; import { deprecationWarning } from '../utils';
interface ViewManagerProps { } export class ViewManager extends React.Component<{}, {}> {
interface ViewManagerState { }
export class ViewManager extends React.Component<ViewManagerProps, ViewManagerState> {
componentDidMount() { componentDidMount() {
deprecationWarning('As of @ionic/react RC2, ViewManager is no longer needed and can be removed. This component is now deprecated will be removed from @ionic/react final.') deprecationWarning('As of @ionic/react RC2, ViewManager is no longer needed and can be removed. This component is now deprecated will be removed from @ionic/react final.');
} }
render() { render() {

View File

@ -1,19 +1,20 @@
import { Location as HistoryLocation } from 'history'; import { Location as HistoryLocation } from 'history';
import { ViewItem } from './ViewItem';
import { IonRouteData } from './IonRouteData';
import { matchPath } from 'react-router-dom'; import { matchPath } from 'react-router-dom';
import { IonRouteData } from './IonRouteData';
import { ViewItem } from './ViewItem';
export interface ViewStack { export interface ViewStack {
id: string; id: string;
routerOutlet: HTMLIonRouterOutletElement; routerOutlet: HTMLIonRouterOutletElement;
views: ViewItem[] views: ViewItem[];
} }
/** /**
* The holistic view of all the Routes configured for an application inside of an IonRouterOutlet. * The holistic view of all the Routes configured for an application inside of an IonRouterOutlet.
*/ */
export class ViewStacks { export class ViewStacks {
private viewStacks: { [key: string]: ViewStack } = {}; private viewStacks: { [key: string]: ViewStack | undefined } = {};
get(key: string) { get(key: string) {
return this.viewStacks[key]; return this.viewStacks[key];
@ -31,12 +32,12 @@ export class ViewStacks {
delete this.viewStacks[key]; delete this.viewStacks[key];
} }
findViewInfoByLocation(location: HistoryLocation, key?: string) { findViewInfoByLocation(location: HistoryLocation, viewKey?: string) {
let view: ViewItem<IonRouteData> | undefined; let view: ViewItem<IonRouteData> | undefined;
let match: IonRouteData["match"] | null | undefined; let match: IonRouteData['match'] | null | undefined;
let viewStack: ViewStack | undefined; let viewStack: ViewStack | undefined;
if (key) { if (viewKey) {
viewStack = this.viewStacks[key]; viewStack = this.viewStacks[viewKey];
if (viewStack) { if (viewStack) {
viewStack.views.some(matchView); viewStack.views.some(matchView);
} }
@ -44,7 +45,7 @@ export class ViewStacks {
const keys = this.getKeys(); const keys = this.getKeys();
keys.some(key => { keys.some(key => {
viewStack = this.viewStacks[key]; viewStack = this.viewStacks[key];
return viewStack.views.some(matchView); return viewStack!.views.some(matchView);
}); });
} }
@ -57,7 +58,7 @@ export class ViewStacks {
path: v.routeData.childProps.path || v.routeData.childProps.from, path: v.routeData.childProps.path || v.routeData.childProps.from,
component: v.routeData.childProps.component component: v.routeData.childProps.component
}; };
match = matchPath(location.pathname, matchProps) match = matchPath(location.pathname, matchProps);
if (match) { if (match) {
view = v; view = v;
return true; return true;
@ -67,13 +68,13 @@ export class ViewStacks {
} }
findViewInfoById(id: string = '') { findViewInfoById(id = '') {
let view: ViewItem<IonRouteData> | undefined; let view: ViewItem<IonRouteData> | undefined;
let viewStack: ViewStack | undefined; let viewStack: ViewStack | undefined;
const keys = this.getKeys(); const keys = this.getKeys();
keys.some(key => { keys.some(key => {
const vs = this.viewStacks[key]; const vs = this.viewStacks[key];
view = vs.views.find(x => x.id === id); view = vs!.views.find(x => x.id === id);
if (view) { if (view) {
viewStack = vs; viewStack = vs;
return true; return true;
@ -88,13 +89,12 @@ export class ViewStacks {
const keys = this.getKeys(); const keys = this.getKeys();
keys.forEach(key => { keys.forEach(key => {
const viewStack = this.viewStacks[key]; const viewStack = this.viewStacks[key];
viewStack.views.forEach(view => { viewStack!.views.forEach(view => {
if (!view.routeData.match && !view.isIonRoute) { if (!view.routeData.match && !view.isIonRoute) {
view.show = false; view.show = false;
view.mount = false; view.mount = false;
} }
}) });
}) });
} }
} }

View File

@ -1,5 +1,6 @@
import { DefaultIonLifeCycleContext, IonLifeCycleContext } from '@ionic/react';
import React from 'react'; import React from 'react';
import { IonLifeCycleContext, DefaultIonLifeCycleContext } from '@ionic/react';
import { RouteManagerContext } from './RouteManagerContext'; import { RouteManagerContext } from './RouteManagerContext';
interface ViewTransitionManagerProps { interface ViewTransitionManagerProps {
@ -20,7 +21,7 @@ export class ViewTransitionManager extends React.Component<ViewTransitionManager
context!: React.ContextType<typeof RouteManagerContext>; context!: React.ContextType<typeof RouteManagerContext>;
constructor(props: ViewTransitionManagerProps) { constructor(props: ViewTransitionManagerProps) {
super(props) super(props);
this.state = { this.state = {
show: true show: true
}; };
@ -52,7 +53,7 @@ export class ViewTransitionManager extends React.Component<ViewTransitionManager
<IonLifeCycleContext.Provider value={this.ionLifeCycleContext}> <IonLifeCycleContext.Provider value={this.ionLifeCycleContext}>
{show && this.props.children} {show && this.props.children}
</IonLifeCycleContext.Provider> </IonLifeCycleContext.Provider>
) );
} }
static get contextType() { static get contextType() {

View File

@ -1 +1 @@
export * from './ReactRouter' export * from './ReactRouter';

View File

@ -0,0 +1,14 @@
import { isDevMode } from '../dev';
describe('isDevMode', () => {
it('by default, should return false since we are in test', () => {
const isDev = isDevMode();
expect(isDev).toBeFalsy();
});
it('when in dev mode, should return true', () => {
process.env.NODE_ENV = 'development';
const isDev = isDevMode();
expect(isDev).toBeTruthy()
});
});

View File

@ -1,3 +1,32 @@
{ {
"extends": "tslint-ionic-rules" "extends": ["tslint-ionic-rules/strict", "tslint-react"],
"linterOptions": {
"exclude": [
"**/*.spec.ts",
"**/*.spec.tsx"
]
},
"rules": {
"no-conditional-assignment": false,
"no-non-null-assertion": false,
"no-unnecessary-type-assertion": false,
"no-import-side-effect": false,
"trailing-comma": false,
"no-null-keyword": false,
"no-console": false,
"no-unbound-method": false,
"no-floating-promises": false,
"no-invalid-template-strings": true,
"ban-export-const-enum": true,
"only-arrow-functions": false,
"strict-boolean-conditions": [true, "allow-null-union", "allow-undefined-union", "allow-boolean-or-undefined", "allow-string"],
"jsx-key": false,
"jsx-self-close": false,
"jsx-curly-spacing": [true, "never"],
"jsx-boolean-value": [true, "never"],
"jsx-no-bind": false,
"jsx-no-lambda": false,
"jsx-no-multiline-js": false,
"jsx-wrap-multiline": false
}
} }

View File

@ -28,6 +28,7 @@
"lint.fix": "tslint --project . --fix", "lint.fix": "tslint --project . --fix",
"tsc": "tsc -p .", "tsc": "tsc -p .",
"copy": "node scripts/copy.js", "copy": "node scripts/copy.js",
"test.spec": "jest --ci",
"test.treeshake": "node scripts/treeshaking.js dist/index.esm.js" "test.treeshake": "node scripts/treeshaking.js dist/index.esm.js"
}, },
"main": "dist/index.js", "main": "dist/index.js",

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { IonTabs, IonTabButton, IonLabel, IonIcon, IonTabBar} from '../index'; import { IonTabs, IonTabButton, IonLabel, IonIcon, IonTabBar} from '../index';
import { render, cleanup } from 'react-testing-library'; import { render, cleanup } from 'react-testing-library';
import { IonRouterOutlet } from '../proxies'; import { IonRouterOutlet } from '../IonRouterOutlet';
afterEach(cleanup) afterEach(cleanup)

View File

@ -22,7 +22,7 @@ describe('createComponent - events', () => {
test('should add custom events', () => { test('should add custom events', () => {
const FakeIonFocus = jest.fn((e) => e); const FakeIonFocus = jest.fn((e) => e);
const IonInput = createReactComponent<JSX.IonInput>('ion-input'); const IonInput = createReactComponent<JSX.IonInput, HTMLIonInputElement>('ion-input');
const { getByText } = render( const { getByText } = render(
<IonInput onIonFocus={FakeIonFocus}> <IonInput onIonFocus={FakeIonFocus}>

View File

@ -69,8 +69,8 @@ export const isCoveredByReact = (eventNameSuffix: string, doc: Document = docume
return isSupported; return isSupported;
}; };
export const syncEvent = (node: Element, eventName: string, newEventHandler: (e: Event) => any) => { export const syncEvent = (node: Element & {__events?: {[key: string]: ((e: Event) => any) | undefined}}, eventName: string, newEventHandler?: (e: Event) => any) => {
const eventStore = (node as any).__events || ((node as any).__events = {}); const eventStore = node.__events || (node.__events = {});
const oldEventHandler = eventStore[eventName]; const oldEventHandler = eventStore[eventName];
// Remove old listener so they don't double up. // Remove old listener so they don't double up.

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
"@types/react-dom": "16.8.5", "@types/react-dom": "16.8.5",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
"react-scripts": "^3.1.0", "react-scripts": "^3.1.2",
"serve": "^11.1.0", "serve": "^11.1.0",
"typescript": "3.5.3" "typescript": "3.5.3"
}, },

View File

@ -19,7 +19,7 @@
"no-invalid-template-strings": true, "no-invalid-template-strings": true,
"ban-export-const-enum": true, "ban-export-const-enum": true,
"only-arrow-functions": true, "only-arrow-functions": true,
"strict-boolean-conditions": [true, "allow-null-union", "allow-undefined-union", "allow-boolean-or-undefined", "allow-string"],
"jsx-key": false, "jsx-key": false,
"jsx-self-close": false, "jsx-self-close": false,
"jsx-curly-spacing": [true, "never"], "jsx-curly-spacing": [true, "never"],