Skip to content

Commit

Permalink
Merge pull request #22 from kirillzyusko/feature/web-support
Browse files Browse the repository at this point in the history
[RNBS-021] - implemented web-support
  • Loading branch information
kirillzyusko authored Mar 20, 2021
2 parents ecbd01d + 40ed036 commit d3cfcfb
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ docs
src
ROADMAP.MD
CONTRIBUTING.MD
CHANGELOG.MD
CODE_OF_CONDUCT.md
node_modules

# from docusaurus
Expand Down
24 changes: 24 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

# 2.0.0

## Breaking changes

This release contains no breaking changes, but it does include some important changes, including:

- proposed to use `loader` instead of `require` (however you can still use `require` - this version has backward compatibility);
- `investigate` returns only initially loaded modules;
- screen gets mounted in `async` way;

## Added

- support for `web` platform (with `react-native-web` usage);
- support for `macOS` platform;
- support for `windows` platform;

## Improved

- types compatibility;
- internal naming convention;

## Fixed
- `requires a peer of react-native@^0.59.1 but none is installed. You must install peer dependencies yourself.` warning;

# 1.0.9

## Fixed
Expand Down
1 change: 1 addition & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ Decrease your start up time and RAM memory consumption by an application via spl
## Quick Links
- [Documentation](https://kirillzyusko.github.io/react-native-bundle-splitter/)
- [Example](https://github.com/kirillzyusko/react-native-bundle-splitter-example)
- [Contributing](https://github.com/kirillzyusko/react-native-bundle-splitter/blob/master/CONTRIBUTING.MD)
3 changes: 2 additions & 1 deletion ROADMAP.MD
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Features

- [x] async import instead of require
- [x] more platform support;
- [ ] timeToInteraction feature
- [ ] async import instead of require
- [ ] add `preload` on rendered component (`Loadable.preload()` <=> `<Loadable />`)
12 changes: 11 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-native-bundle-splitter",
"author": "Kiryl Ziusko",
"version": "1.0.9",
"version": "2.0.0",
"description": "Decrease your start up time and RAM memory consumption by an application via splitting JS bundle by components and navigation routes",
"repository": "https://github.com/kirillzyusko/react-native-bundle-splitter",
"homepage": "https://kirillzyusko.github.io/react-native-bundle-splitter/",
Expand All @@ -13,16 +13,18 @@
},
"devDependencies": {
"@types/react": "^16.6.1",
"@types/react-native": "^0.60.0",
"typescript": "3.6.3"
},
"peerDependencies": {
"react": "^16.6.1",
"react-native": "^0.59.1"
"react": "*",
"react-native": ">=0.59.1"
},
"keywords": [
"react",
"native",
"react-native",
"react-native-web",
"bundle",
"bundles",
"separate",
Expand Down
13 changes: 1 addition & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,7 @@ const register = (component: PreLoadable & Partial<EnhancedPreLoadable>) => {
return optimized(name);
};

const component = (name: string) => new Promise((resolve: Function, reject: Function) => {
try {
if (isCached(name)) {
resolve();
} else {
getComponent(name);
resolve();
}
} catch (e) {
reject(e);
}
});
const component = (name: string) => getComponent(name);

const group = (name: string) => {
const components = Object.keys(mapLoadable).filter((componentName) => mapLoadable[componentName].group === name);
Expand Down
26 changes: 24 additions & 2 deletions src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
import * as React from 'react';

export type PreLoadable = {
type ImportReturnType = {};

export type RequireLoader = () => NodeRequire;

export type ImportLoader = () => Promise<ImportReturnType>;

type BasePreLoadable = {
require?: RequireLoader;
loader?: ImportLoader;
name?: string;
require: () => ({});
group?: string;
static?: object;
};

// helper, which transforms optional params to mandatory
// useful for creating conditional types - see usage below
// https://stackoverflow.com/a/49725198/9272042
type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>>
& {
[K in Keys]-?:
Required<Pick<T, K>>
& Partial<Record<Exclude<Keys, K>, undefined>>
}[Keys];

// it describes the type, where `loader` or `require` should be defined, but not
// two of them at the same time
export type PreLoadable = RequireOnlyOne<BasePreLoadable, 'require' | 'loader'>;

export type EnhancedPreLoadable = {
cached: boolean;
placeholder: React.ElementType | null,
Expand Down
44 changes: 38 additions & 6 deletions src/map.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
import { mapLoadable } from './bundler';
import { RequireLoader, ImportLoader } from './interface';

const cache = {} as any;

export const isCached = (componentName: string) => !!cache[componentName];

export const getComponent = (name: string) => {
const DEPRECATED_API_MESSAGE = "You are using a deprecated API that will be removed in a future releases. Please consider using `loader` instead of `require`";
const ERROR_WHILE_LOADING = "An error occurred while lazy loading a component. Perhaps the path where you are trying to load the component does not exist? Stacktrace: ";

// In react-native world call of `require` or `loader` will block thread (since it's sync operation)
// As a result if screen is not loaded yet and you trigger a navigation - app will freeze for a time,
// until screen is not loaded. That's why `setTimeout(..., 0)` code is used here. We simply call this
// function in next event loop iteration. Such approach will not block transition animations.
const nonBlockingLoader = (loader: RequireLoader | ImportLoader) => new Promise((resolve) => {
setTimeout(async () => {
try {
const file = await loader();
resolve(file);
} catch (e) {
console.error(ERROR_WHILE_LOADING + e);
// resolve it as a `null` - another error will be thrown
// when it will evaluate `component[rest.extract]`
resolve(null);
}
}, 0);
});

export const getComponent = async (name: string) => {
if (!isCached(name)) {
const { require: load, ...rest } = mapLoadable[name];
const { require: load, loader, ...rest } = mapLoadable[name];
let component = null;

if (loader) {
component = await nonBlockingLoader(loader);
} else if (load) {
console.warn(DEPRECATED_API_MESSAGE);

component = await nonBlockingLoader(load);
}

// @ts-ignore
const component = load()[rest.extract];
cache[name] = {
...rest,
component,
// @ts-ignore
component: component[rest.extract],
};
}

return cache[name];
};
};

export const getComponentFromCache = (name: string) => cache[name];
16 changes: 8 additions & 8 deletions src/optimized.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as React from 'react';

import { getComponent, isCached } from './map';
import { getComponent, isCached, getComponentFromCache } from './map';
import { mapLoadable } from './bundler';

type Props = {};
type State = {
needsExpensive: boolean;
isComponentAvailable: boolean;
};

const optimized = (screenName: string): any => {
Expand All @@ -18,21 +18,21 @@ const optimized = (screenName: string): any => {
const cached = isCached(screenName);

if (cached) {
const { component } = getComponent(screenName);
const { component } = getComponentFromCache(screenName);
this.component = component;
}

this.state = {
needsExpensive: cached
isComponentAvailable: cached
};
}

public componentDidMount(): void {
public async componentDidMount(): Promise<void> {
if (this.component === null) {
const { component } = getComponent(screenName);
const { component } = await getComponent(screenName);
this.component = component;

this.setState({ needsExpensive: true });
this.setState({ isComponentAvailable: true });
}
}

Expand All @@ -41,7 +41,7 @@ const optimized = (screenName: string): any => {
const Placeholder = this.placeholder;
const PlaceholderComponent = Placeholder ? <Placeholder /> : Placeholder;

return this.state.needsExpensive && BundleComponent ?
return this.state.isComponentAvailable && BundleComponent ?
<BundleComponent {...this.props} /> : PlaceholderComponent;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { Platform } from 'react-native';
// quick and dirty
declare var require: any;

export const investigate = () => {
if (Platform.OS === 'web' || !__DEV__) {
// prevent crash in release and web
// this function will not work on web and in release
return { loaded: [], waiting: [] }
}

const modules = require.getModules();
const moduleIds = Object.keys(modules);
const loaded = moduleIds
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"lib": ["es2015"], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "react-native", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
Expand Down

0 comments on commit d3cfcfb

Please sign in to comment.