diff --git a/.npmignore b/.npmignore
index 4e36cad..05225b7 100644
--- a/.npmignore
+++ b/.npmignore
@@ -10,7 +10,7 @@ docs
src
ROADMAP.MD
CONTRIBUTING.MD
-CHANGELOG.MD
+CODE_OF_CONDUCT.md
node_modules
# from docusaurus
diff --git a/CHANGELOG.MD b/CHANGELOG.MD
index 5d54e8e..eea3047 100644
--- a/CHANGELOG.MD
+++ b/CHANGELOG.MD
@@ -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
diff --git a/README.MD b/README.MD
index ec117a6..51e4f4e 100644
--- a/README.MD
+++ b/README.MD
@@ -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)
diff --git a/ROADMAP.MD b/ROADMAP.MD
index fe6baf2..1f1c280 100644
--- a/ROADMAP.MD
+++ b/ROADMAP.MD
@@ -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()` <=> ``)
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index c25842a..95049e9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "react-native-bundle-splitter",
- "version": "1.0.4",
+ "version": "2.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -20,6 +20,16 @@
"csstype": "^2.2.0"
}
},
+ "@types/react-native": {
+ "version": "0.60.0",
+ "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.60.0.tgz",
+ "integrity": "sha512-9av+Wgh3j7nQzK6MXIGMqc57M53Ilfcyhq49SRzO/Jv9e7PdQNjJrCiXoHSvtKwuQpwxMkomAfnbcxxkp0zzBw==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/react": "*"
+ }
+ },
"csstype": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz",
diff --git a/package.json b/package.json
index 9e88ce7..0326a66 100644
--- a/package.json
+++ b/package.json
@@ -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/",
@@ -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",
diff --git a/src/index.ts b/src/index.ts
index e16d921..95ee2f2 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -28,18 +28,7 @@ const register = (component: PreLoadable & Partial) => {
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);
diff --git a/src/interface.ts b/src/interface.ts
index 743d658..9ea0dde 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -1,12 +1,34 @@
import * as React from 'react';
-export type PreLoadable = {
+type ImportReturnType = {};
+
+export type RequireLoader = () => NodeRequire;
+
+export type ImportLoader = () => Promise;
+
+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 =
+ Pick>
+ & {
+ [K in Keys]-?:
+ Required>
+ & Partial, 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;
+
export type EnhancedPreLoadable = {
cached: boolean;
placeholder: React.ElementType | null,
diff --git a/src/map.ts b/src/map.ts
index a7fa852..1592b4f 100644
--- a/src/map.ts
+++ b/src/map.ts
@@ -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];
-};
\ No newline at end of file
+};
+
+export const getComponentFromCache = (name: string) => cache[name];
\ No newline at end of file
diff --git a/src/optimized.tsx b/src/optimized.tsx
index 7324fec..cf8ea28 100644
--- a/src/optimized.tsx
+++ b/src/optimized.tsx
@@ -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 => {
@@ -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 {
if (this.component === null) {
- const { component } = getComponent(screenName);
+ const { component } = await getComponent(screenName);
this.component = component;
- this.setState({ needsExpensive: true });
+ this.setState({ isComponentAvailable: true });
}
}
@@ -41,7 +41,7 @@ const optimized = (screenName: string): any => {
const Placeholder = this.placeholder;
const PlaceholderComponent = Placeholder ? : Placeholder;
- return this.state.needsExpensive && BundleComponent ?
+ return this.state.isComponentAvailable && BundleComponent ?
: PlaceholderComponent;
}
}
diff --git a/src/utils.ts b/src/utils.ts
index 643af53..7cfa247 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -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
diff --git a/tsconfig.json b/tsconfig.json
index de4e8e9..540dbb4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -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. */