├── .babelrc
├── .eslintrc
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .size-limit.js
├── .size.json
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── __tests__
├── __fixtures__
│ └── babel
│ │ ├── boot
│ │ ├── actual.js
│ │ └── expected.js
│ │ ├── node
│ │ ├── actual.js
│ │ └── expected.js
│ │ └── webpack
│ │ ├── actual.js
│ │ └── expected.js
├── __snapshots__
│ └── macro.spec.ts.snap
├── babel.spec.ts
├── componentLoader.spec.tsx
├── loadable.spec.ts
├── macro.spec.ts
├── module.spec.tsx
├── rehydrate.spec.tsx
├── signatures.spec.ts
└── useImported.spec.tsx
├── assets
└── imported-logo.png
├── babel.js
├── bin
└── imported-components
├── boot
├── boot.d.ts
└── package.json
├── doczrc.js
├── example
├── app.tsx
├── assets
│ └── .gitkeep
├── index.html
└── index.tsx
├── examples
├── CRA
│ ├── .env
│ ├── .gitignore
│ ├── async-imports.js
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── async-imports.js
│ │ ├── async.js
│ │ ├── index.js
│ │ ├── load.js
│ │ └── styles.css
├── SSR
│ ├── parcel-react-ssr
│ │ ├── .babelrc
│ │ ├── .gitignore
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── app
│ │ │ ├── App.jsx
│ │ │ ├── HelloWorld.jsx
│ │ │ ├── HelloWorld.scss
│ │ │ ├── HelloWorld2.jsx
│ │ │ ├── HelloWorld3.jsx
│ │ │ ├── base.css
│ │ │ ├── client.js
│ │ │ ├── codeSplitAssets
│ │ │ │ ├── NyanCat.css
│ │ │ │ ├── NyanCat.js
│ │ │ │ └── utils.js
│ │ │ ├── imported-chunk.js
│ │ │ ├── index.html
│ │ │ └── main.js
│ │ ├── package.json
│ │ ├── server
│ │ │ ├── generateHtml.js
│ │ │ ├── index.js
│ │ │ └── middleware.js
│ │ ├── stream-server
│ │ │ ├── index.js
│ │ │ ├── interleaved-middleware.js
│ │ │ └── middleware.js
│ │ └── yarn.lock
│ ├── runtime-manifest
│ │ ├── .babelrc
│ │ ├── .circleci
│ │ │ └── config.yml
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── app
│ │ │ ├── App.css
│ │ │ ├── App.tsx
│ │ │ ├── assets
│ │ │ │ └── favicon.ico
│ │ │ ├── client.tsx
│ │ │ ├── components
│ │ │ │ ├── Another.tsx
│ │ │ │ ├── Home.tsx
│ │ │ │ ├── LazyTarget.tsx
│ │ │ │ ├── NotDefault.tsx
│ │ │ │ ├── Other.tsx
│ │ │ │ ├── OtherTween.tsx
│ │ │ │ └── library.tsx
│ │ │ ├── imported.ts
│ │ │ ├── index.html
│ │ │ └── store
│ │ │ │ ├── index.ts
│ │ │ │ └── reducers.ts
│ │ ├── package.json
│ │ ├── server
│ │ │ ├── generateHtml.ts
│ │ │ ├── index.ts
│ │ │ └── middleware.tsx
│ │ ├── tsconfig.json
│ │ ├── tslint.json
│ │ ├── types.d.ts
│ │ ├── webpack-client.config.js
│ │ ├── webpack-server.config.js
│ │ ├── webpack-shared.config.js
│ │ ├── webpack.config.js
│ │ └── yarn.lock
│ └── typescript-react
│ │ ├── .babelrc
│ │ ├── .circleci
│ │ └── config.yml
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── app
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ └── favicon.ico
│ │ ├── client.tsx
│ │ ├── components
│ │ │ ├── Another.tsx
│ │ │ ├── Home.tsx
│ │ │ ├── LazyTarget.tsx
│ │ │ ├── NotDefault.tsx
│ │ │ ├── Other.tsx
│ │ │ ├── OtherTween.tsx
│ │ │ └── library.tsx
│ │ ├── imported.ts
│ │ ├── index.html
│ │ └── store
│ │ │ ├── index.ts
│ │ │ └── reducers.ts
│ │ ├── package.json
│ │ ├── server
│ │ ├── generateHtml.ts
│ │ ├── index.ts
│ │ └── middleware.tsx
│ │ ├── tsconfig.json
│ │ ├── tslint.json
│ │ ├── types.d.ts
│ │ ├── webpack-client.config.js
│ │ ├── webpack-server.config.js
│ │ ├── webpack-shared.config.js
│ │ ├── webpack.config.js
│ │ └── yarn.lock
├── hybrid
│ └── react-snap
│ │ ├── .babelrc
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src
│ │ ├── app.js
│ │ ├── imported-chunk.js
│ │ ├── index.html
│ │ ├── main.js
│ │ ├── splitted-1.js
│ │ ├── splitted-2.js
│ │ └── splitted-3.js
│ │ └── yarn.lock
└── react-hot-loader
│ ├── .babelrc
│ ├── .flowconfig
│ ├── .gitignore
│ ├── package.json
│ ├── src
│ ├── App.js
│ ├── Counter.js
│ ├── DeferredRender.js
│ ├── HiddenComponent.js
│ ├── Lazy.js
│ ├── NotImported.js
│ ├── Portal.js
│ ├── SubAsync.js
│ ├── index.js
│ ├── indirect.js
│ ├── indirectTarget.js
│ └── indirectUsage.js
│ ├── webpack.config.babel.js
│ └── yarn.lock
├── greenkeeper.json
├── jest.config.js
├── macro
└── package.json
├── package.json
├── server
└── package.json
├── src
├── babel
│ ├── babel.ts
│ ├── magic-comments.ts
│ └── utils.ts
├── configuration
│ ├── config.ts
│ ├── configuration.ts
│ └── constants.ts
├── entrypoints
│ ├── babel.ts
│ ├── boot.ts
│ ├── index.ts
│ ├── macro.ts
│ └── server.ts
├── loadable
│ ├── assignImportedComponents.ts
│ ├── loadByChunkName.ts
│ ├── loadable.ts
│ ├── markerMapper.ts
│ ├── marks.ts
│ ├── metadata.ts
│ ├── pending.ts
│ ├── preloaders.ts
│ ├── registry.ts
│ ├── stream.ts
│ ├── toLoadable.ts
│ └── utils.ts
├── scanners
│ ├── __tests__
│ │ └── parser.spec.ts
│ ├── cli.ts
│ ├── scanForImports.ts
│ └── shared.ts
├── trackers
│ └── globalTracker.ts
├── transformers
│ └── loadableTransformer.ts
├── types.ts
├── ui
│ ├── Component.tsx
│ ├── HOC.tsx
│ ├── ImportedController.tsx
│ ├── LazyBoundary.tsx
│ ├── Module.tsx
│ ├── context.tsx
│ └── useImported.ts
└── utils
│ ├── detectBackend.ts
│ ├── helpers.ts
│ ├── signatures.ts
│ ├── useClientPhase.tsx
│ └── utils.ts
├── tsconfig.json
├── tslint.json
├── wrapper.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "cjs": {
4 | "presets": [
5 | "@babel/preset-env",
6 | "@babel/preset-react"
7 | ],
8 | "plugins": [
9 | "dynamic-import-node",
10 | "@babel/plugin-proposal-class-properties",
11 | "@babel/plugin-transform-runtime",
12 | [
13 | "transform-react-remove-prop-types",
14 | {
15 | "mode": "wrap"
16 | }
17 | ]
18 | ]
19 | },
20 | "es2015": {
21 | "presets": [
22 | [
23 | "@babel/preset-env",
24 | {
25 | "modules": false,
26 | "loose": true
27 | }
28 | ],
29 | "@babel/preset-react"
30 | ],
31 | "plugins": [
32 | "dynamic-import-node",
33 | "@babel/plugin-proposal-class-properties",
34 | "@babel/plugin-transform-runtime",
35 | [
36 | "transform-react-remove-prop-types",
37 | {
38 | "mode": "wrap"
39 | }
40 | ]
41 | ]
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "eslint:recommended",
5 | "plugin:react/recommended"
6 | ],
7 | "plugins": [
8 | "react"
9 | ],
10 | "env": {
11 | "browser": true,
12 | "node": true,
13 | "es6": true
14 | }
15 | }
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['plugin:@typescript-eslint/recommended', 'plugin:import/typescript', 'plugin:react-hooks/recommended'],
3 | parser: '@typescript-eslint/parser',
4 | plugins: ['@typescript-eslint', 'prettier', 'import'],
5 | rules: {
6 | '@typescript-eslint/ban-ts-comment': 0,
7 | '@typescript-eslint/ban-ts-ignore': 0,
8 | '@typescript-eslint/no-var-requires': 0,
9 | '@typescript-eslint/camelcase': 0,
10 | 'import/order': [
11 | 'error',
12 | {
13 | 'newlines-between': 'always-and-inside-groups',
14 | alphabetize: {
15 | order: 'asc',
16 | },
17 | groups: ['builtin', 'external', 'internal', ['parent', 'index', 'sibling']],
18 | },
19 | ],
20 | 'padding-line-between-statements': [
21 | 'error',
22 | // IMPORT
23 | {
24 | blankLine: 'always',
25 | prev: 'import',
26 | next: '*',
27 | },
28 | {
29 | blankLine: 'any',
30 | prev: 'import',
31 | next: 'import',
32 | },
33 | // EXPORT
34 | {
35 | blankLine: 'always',
36 | prev: '*',
37 | next: 'export',
38 | },
39 | {
40 | blankLine: 'any',
41 | prev: 'export',
42 | next: 'export',
43 | },
44 | {
45 | blankLine: 'always',
46 | prev: '*',
47 | next: ['const', 'let'],
48 | },
49 | {
50 | blankLine: 'any',
51 | prev: ['const', 'let'],
52 | next: ['const', 'let'],
53 | },
54 | // BLOCKS
55 | {
56 | blankLine: 'always',
57 | prev: ['block', 'block-like', 'class', 'function', 'multiline-expression'],
58 | next: '*',
59 | },
60 | {
61 | blankLine: 'always',
62 | prev: '*',
63 | next: ['block', 'block-like', 'class', 'function', 'return', 'multiline-expression'],
64 | },
65 | ],
66 | },
67 | settings: {
68 | 'import/parsers': {
69 | '@typescript-eslint/parser': ['.ts', '.tsx'],
70 | },
71 | 'import/resolver': {
72 | typescript: {
73 | alwaysTryTypes: true,
74 | },
75 | },
76 | },
77 | };
78 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | /lib/
3 | /dist/
4 | .DS_Store
5 | .nyc_output
6 | yarn-error.log
7 | *.tgz
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | examples
2 | src
3 | _tests
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | __tests__/__fixtures__
--------------------------------------------------------------------------------
/.size-limit.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | path: ['dist/es2015/entrypoints/index.js', 'dist/es2015/entrypoints/boot.js'],
4 | ignore: ['tslib'],
5 | limit: '4.3 KB',
6 | },
7 | {
8 | path: 'dist/es2015/entrypoints/index.js',
9 | ignore: ['tslib'],
10 | limit: '3.9 KB',
11 | },
12 | {
13 | path: 'dist/es2015/entrypoints/boot.js',
14 | ignore: ['tslib'],
15 | limit: '1.99 KB',
16 | },
17 | ];
18 |
--------------------------------------------------------------------------------
/.size.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "dist/es2015/entrypoints/index.js, dist/es2015/entrypoints/boot.js",
4 | "passed": true,
5 | "size": 4212
6 | },
7 | {
8 | "name": "dist/es2015/entrypoints/index.js",
9 | "passed": true,
10 | "size": 3825
11 | },
12 | {
13 | "name": "dist/es2015/entrypoints/boot.js",
14 | "passed": true,
15 | "size": 1969
16 | }
17 | ]
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '10'
4 | cache: yarn
5 | script:
6 | - yarn
7 | - yarn test:ci
8 | notifications:
9 | email: false
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Anton
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/__tests__/__fixtures__/babel/boot/actual.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* tslint:disable */
3 |
4 | // generated by react-imported-component, DO NOT EDIT
5 | import {assignImportedComponents} from 'react-imported-component/macro';
6 |
7 | const PreloadComponent = imported(() => import('./PreloadThis'));
8 | const PrefetchChunkComponent = imported(() => import('./ChunkThis'));
9 |
10 | const applicationImports = assignImportedComponents([
11 | [() => import('./should-not-be-transformed'), '', './some-file', false],
12 | ]);
13 |
14 |
--------------------------------------------------------------------------------
/__tests__/__fixtures__/babel/boot/expected.js:
--------------------------------------------------------------------------------
1 | var importedWrapper = require('react-imported-component/wrapper');
2 |
3 | import { assignImportedComponents } from "react-imported-component/boot";
4 |
5 | /* eslint-disable */
6 |
7 | /* tslint:disable */
8 | // generated by react-imported-component, DO NOT EDIT
9 | const PreloadComponent = imported(() => importedWrapper("imported_-hbct7n_component", import('./PreloadThis')));
10 | const PrefetchChunkComponent = imported(() => importedWrapper("imported_fspdct_component", import(
11 | /* webpackChunkName: "chunked-this" */
12 | './ChunkThis')));
13 | const applicationImports = assignImportedComponents([[() => importedWrapper("imported_12i8gg9_component", import('./should-not-be-transformed')), '', './some-file', false]]);
--------------------------------------------------------------------------------
/__tests__/__fixtures__/babel/node/actual.js:
--------------------------------------------------------------------------------
1 | import imported from 'react-imported-component';
2 |
3 | const AsyncComponent0 = imported(() => import(/* webpackChunkName:namedChunk */'./MyComponent'));
4 |
5 | const AsyncComponent1 = imported(() => import('./MyComponent'));
6 |
7 | const AsyncComponent2 = imported(async () => await import('./MyComponent'));
8 |
9 | const AsyncComponent3 = imported(() => Promise.all([import('./MyComponent'), import('./MyComponent')]));
10 |
11 | const AsyncComponent4 = imported(async () => (await Promise.all([import('./MyComponent1'), import('./MyComponent2')]))[0]);
12 |
13 | export default AsyncComponent1;
--------------------------------------------------------------------------------
/__tests__/__fixtures__/babel/node/expected.js:
--------------------------------------------------------------------------------
1 | var importedWrapper = require('react-imported-component/wrapper');
2 |
3 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
4 |
5 | import imported from 'react-imported-component';
6 | const AsyncComponent0 = imported(() => importedWrapper("imported_18g2v0c_component", Promise.resolve().then(() => _interopRequireWildcard(require('./MyComponent')))));
7 | const AsyncComponent1 = imported(() => importedWrapper("imported_18g2v0c_component", Promise.resolve().then(() => _interopRequireWildcard(require('./MyComponent')))));
8 | const AsyncComponent2 = imported(async () => await importedWrapper("imported_18g2v0c_component", Promise.resolve().then(() => _interopRequireWildcard(require('./MyComponent')))));
9 | const AsyncComponent3 = imported(() => Promise.all([importedWrapper("imported_18g2v0c_component", Promise.resolve().then(() => _interopRequireWildcard(require('./MyComponent')))), importedWrapper("imported_18g2v0c_component", Promise.resolve().then(() => _interopRequireWildcard(require('./MyComponent'))))]));
10 | const AsyncComponent4 = imported(async () => (await Promise.all([importedWrapper("imported_-1qs8n90_component", Promise.resolve().then(() => _interopRequireWildcard(require('./MyComponent1')))), importedWrapper("imported_9j5sqq_component", Promise.resolve().then(() => _interopRequireWildcard(require('./MyComponent2'))))]))[0]);
11 | export default AsyncComponent1;
--------------------------------------------------------------------------------
/__tests__/__fixtures__/babel/webpack/actual.js:
--------------------------------------------------------------------------------
1 | import {lazy, useImported} from 'react-imported-component/macro';
2 | import imported from 'react-imported-component';
3 |
4 | const PreloadComponent = imported(() => import('./PreloadThis'));
5 | const PrefetchChunkComponent = imported(() => import('./ChunkThis'));
6 | const AsyncComponent0 = imported(() => import(/* webpackChunkName: "namedChunk" */'./MyComponent'));
7 |
8 | const AsyncComponent1 = imported(() => import('./MyComponent'));
9 |
10 | const AsyncComponent2 = imported(async () => await import('./MyComponent'));
11 |
12 | const AsyncComponent3 = imported(() => Promise.all([import('./MyComponent'), import('./MyComponent')]));
13 |
14 | const AsyncComponent4 = imported(async () => (await Promise.all([import('./MyComponent1'), import('./MyComponent2')]))[0]);
15 |
16 | export default AsyncComponent1;
--------------------------------------------------------------------------------
/__tests__/__fixtures__/babel/webpack/expected.js:
--------------------------------------------------------------------------------
1 | var importedWrapper = require('react-imported-component/wrapper');
2 |
3 | import { lazy, useImported } from "react-imported-component";
4 | import imported from 'react-imported-component';
5 | const PreloadComponent = imported(() => importedWrapper("imported_-hbct7n_component", import(
6 | /* webpackPreload: true */
7 | './PreloadThis')));
8 | const PrefetchChunkComponent = imported(() => importedWrapper("imported_fspdct_component", import(
9 | /* webpackChunkName: "chunked-this" */
10 |
11 | /* webpackPrefetch: true */
12 | './ChunkThis')));
13 | const AsyncComponent0 = imported(() => importedWrapper("imported_18g2v0c_component", import(
14 | /* webpackChunkName: "namedChunk" */
15 | './MyComponent')));
16 | const AsyncComponent1 = imported(() => importedWrapper("imported_18g2v0c_component", import('./MyComponent')));
17 | const AsyncComponent2 = imported(async () => await importedWrapper("imported_18g2v0c_component", import('./MyComponent')));
18 | const AsyncComponent3 = imported(() => Promise.all([importedWrapper("imported_18g2v0c_component", import('./MyComponent')), importedWrapper("imported_18g2v0c_component", import('./MyComponent'))]));
19 | const AsyncComponent4 = imported(async () => (await Promise.all([importedWrapper("imported_-1qs8n90_component", import('./MyComponent1')), importedWrapper("imported_9j5sqq_component", import('./MyComponent2'))]))[0]);
20 | export default AsyncComponent1;
--------------------------------------------------------------------------------
/__tests__/__snapshots__/macro.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`babel macro macros boot: boot 1`] = `
4 | "
5 | import {assignImportedComponents, lazy} from \\"../macro\\";
6 | assignImportedComponents([() => import('./a')]);
7 | lazy(() => import('./a'));
8 |
9 | ↓ ↓ ↓ ↓ ↓ ↓
10 |
11 | function _interopRequireWildcard(obj) {
12 | if (obj && obj.__esModule) {
13 | return obj;
14 | } else {
15 | var newObj = {};
16 | if (obj != null) {
17 | for (var key in obj) {
18 | if (Object.prototype.hasOwnProperty.call(obj, key)) {
19 | var desc =
20 | Object.defineProperty && Object.getOwnPropertyDescriptor
21 | ? Object.getOwnPropertyDescriptor(obj, key)
22 | : {};
23 | if (desc.get || desc.set) {
24 | Object.defineProperty(newObj, key, desc);
25 | } else {
26 | newObj[key] = obj[key];
27 | }
28 | }
29 | }
30 | }
31 | newObj.default = obj;
32 | return newObj;
33 | }
34 | }
35 |
36 | var importedWrapper = require(\\"react-imported-component/wrapper\\");
37 |
38 | import { lazy } from \\"react-imported-component\\";
39 | import { assignImportedComponents } from \\"react-imported-component/boot\\";
40 | assignImportedComponents([
41 | () =>
42 | importedWrapper(
43 | \\"imported_-mg71kn_component\\",
44 | Promise.resolve().then(() => _interopRequireWildcard(require(\\"./a\\")))
45 | ),
46 | ]);
47 | lazy(() =>
48 | importedWrapper(
49 | \\"imported_-mg71kn_component\\",
50 | Promise.resolve().then(() => _interopRequireWildcard(require(\\"./a\\")))
51 | )
52 | );
53 |
54 | "
55 | `;
56 |
57 | exports[`babel macro macros flat import: flat import 1`] = `
58 | "
59 | import \\"../macro\\";
60 | import('./a.js')
61 |
62 | ↓ ↓ ↓ ↓ ↓ ↓
63 |
64 | function _interopRequireWildcard(obj) {
65 | if (obj && obj.__esModule) {
66 | return obj;
67 | } else {
68 | var newObj = {};
69 | if (obj != null) {
70 | for (var key in obj) {
71 | if (Object.prototype.hasOwnProperty.call(obj, key)) {
72 | var desc =
73 | Object.defineProperty && Object.getOwnPropertyDescriptor
74 | ? Object.getOwnPropertyDescriptor(obj, key)
75 | : {};
76 | if (desc.get || desc.set) {
77 | Object.defineProperty(newObj, key, desc);
78 | } else {
79 | newObj[key] = obj[key];
80 | }
81 | }
82 | }
83 | }
84 | newObj.default = obj;
85 | return newObj;
86 | }
87 | }
88 |
89 | var importedWrapper = require(\\"react-imported-component/wrapper\\");
90 |
91 | importedWrapper(
92 | \\"imported_-1ko6oiq_component\\",
93 | Promise.resolve().then(() => _interopRequireWildcard(require(\\"./a.js\\")))
94 | );
95 |
96 | "
97 | `;
98 |
99 | exports[`babel macro macros lazy: lazy 1`] = `
100 | "
101 | import {lazy} from \\"../macro\\";
102 | const v = lazy(() => import('./a'));
103 |
104 | ↓ ↓ ↓ ↓ ↓ ↓
105 |
106 | function _interopRequireWildcard(obj) {
107 | if (obj && obj.__esModule) {
108 | return obj;
109 | } else {
110 | var newObj = {};
111 | if (obj != null) {
112 | for (var key in obj) {
113 | if (Object.prototype.hasOwnProperty.call(obj, key)) {
114 | var desc =
115 | Object.defineProperty && Object.getOwnPropertyDescriptor
116 | ? Object.getOwnPropertyDescriptor(obj, key)
117 | : {};
118 | if (desc.get || desc.set) {
119 | Object.defineProperty(newObj, key, desc);
120 | } else {
121 | newObj[key] = obj[key];
122 | }
123 | }
124 | }
125 | }
126 | newObj.default = obj;
127 | return newObj;
128 | }
129 | }
130 |
131 | var importedWrapper = require(\\"react-imported-component/wrapper\\");
132 |
133 | import { lazy } from \\"react-imported-component\\";
134 | const v = lazy(() =>
135 | importedWrapper(
136 | \\"imported_-mg71kn_component\\",
137 | Promise.resolve().then(() => _interopRequireWildcard(require(\\"./a\\")))
138 | )
139 | );
140 |
141 | "
142 | `;
143 |
144 | exports[`babel macro macros many: many 1`] = `
145 | "
146 | import {imported, useImported} from \\"../macro\\";
147 | const v = imported(() => import('./a'));
148 | const x = () => useImported(() => import('./b'));
149 |
150 | ↓ ↓ ↓ ↓ ↓ ↓
151 |
152 | function _interopRequireWildcard(obj) {
153 | if (obj && obj.__esModule) {
154 | return obj;
155 | } else {
156 | var newObj = {};
157 | if (obj != null) {
158 | for (var key in obj) {
159 | if (Object.prototype.hasOwnProperty.call(obj, key)) {
160 | var desc =
161 | Object.defineProperty && Object.getOwnPropertyDescriptor
162 | ? Object.getOwnPropertyDescriptor(obj, key)
163 | : {};
164 | if (desc.get || desc.set) {
165 | Object.defineProperty(newObj, key, desc);
166 | } else {
167 | newObj[key] = obj[key];
168 | }
169 | }
170 | }
171 | }
172 | newObj.default = obj;
173 | return newObj;
174 | }
175 | }
176 |
177 | var importedWrapper = require(\\"react-imported-component/wrapper\\");
178 |
179 | import { imported, useImported } from \\"react-imported-component\\";
180 | const v = imported(() =>
181 | importedWrapper(
182 | \\"imported_-mg71kn_component\\",
183 | Promise.resolve().then(() => _interopRequireWildcard(require(\\"./a\\")))
184 | )
185 | );
186 |
187 | const x = () =>
188 | useImported(() =>
189 | importedWrapper(
190 | \\"imported_15vaa6j_component\\",
191 | Promise.resolve().then(() => _interopRequireWildcard(require(\\"./b\\")))
192 | )
193 | );
194 |
195 | "
196 | `;
197 |
198 | exports[`babel macro macros no usage: no usage 1`] = `
199 | "
200 | import {lazy} from \\"../macro\\";
201 |
202 | ↓ ↓ ↓ ↓ ↓ ↓
203 |
204 | import { lazy } from \\"react-imported-component\\";
205 |
206 | "
207 | `;
208 |
209 | exports[`babel macro macros nothing: nothing 1`] = `
210 | "
211 | const a = 42;
212 |
213 | ↓ ↓ ↓ ↓ ↓ ↓
214 |
215 | const a = 42;
216 |
217 | "
218 | `;
219 |
220 | exports[`babel macro macros plugin combination: plugin combination 1`] = `
221 | "
222 | import {imported, useImported} from \\"../macro\\";
223 | const v = imported(() => import('./a'));
224 | const x = () => useImported(() => import('./b'));
225 |
226 | ↓ ↓ ↓ ↓ ↓ ↓
227 |
228 | var importedWrapper = require(\\"react-imported-component/wrapper\\");
229 |
230 | function _interopRequireWildcard(obj) {
231 | if (obj && obj.__esModule) {
232 | return obj;
233 | } else {
234 | var newObj = {};
235 | if (obj != null) {
236 | for (var key in obj) {
237 | if (Object.prototype.hasOwnProperty.call(obj, key)) {
238 | var desc =
239 | Object.defineProperty && Object.getOwnPropertyDescriptor
240 | ? Object.getOwnPropertyDescriptor(obj, key)
241 | : {};
242 | if (desc.get || desc.set) {
243 | Object.defineProperty(newObj, key, desc);
244 | } else {
245 | newObj[key] = obj[key];
246 | }
247 | }
248 | }
249 | }
250 | newObj.default = obj;
251 | return newObj;
252 | }
253 | }
254 |
255 | var importedWrapper = require(\\"react-imported-component/wrapper\\");
256 |
257 | import { imported, useImported } from \\"react-imported-component\\";
258 | const v = imported(() =>
259 | importedWrapper(
260 | \\"imported_-mg71kn_component\\",
261 | importedWrapper(
262 | \\"imported_-mg71kn_component\\",
263 | Promise.resolve().then(() => _interopRequireWildcard(require(\\"./a\\")))
264 | )
265 | )
266 | );
267 |
268 | const x = () =>
269 | useImported(() =>
270 | importedWrapper(
271 | \\"imported_15vaa6j_component\\",
272 | importedWrapper(
273 | \\"imported_15vaa6j_component\\",
274 | Promise.resolve().then(() => _interopRequireWildcard(require(\\"./b\\")))
275 | )
276 | )
277 | );
278 |
279 | "
280 | `;
281 |
--------------------------------------------------------------------------------
/__tests__/babel.spec.ts:
--------------------------------------------------------------------------------
1 | import { transform } from '@babel/core';
2 | import { readFileSync } from 'fs';
3 | import { join } from 'path';
4 | import { ImportedConfiguration } from '../src/configuration/configuration';
5 |
6 | const FIXTURE_PATH = join(__dirname, '__fixtures__/babel');
7 |
8 | const configuration: ImportedConfiguration = {
9 | shouldPreload: filename => filename.indexOf('PreloadThis') >= 0,
10 | shouldPrefetch: filename => filename.indexOf('ChunkThis') >= 0,
11 | chunkName: filename => (filename.indexOf('ChunkThis') >= 0 ? 'chunked-this' : undefined),
12 | };
13 |
14 | const testPlugin = {
15 | node: (code: string) => {
16 | const result = transform(code, {
17 | presets: ['@babel/preset-react'],
18 | plugins: [require.resolve('../dist/es5/entrypoints/babel'), 'dynamic-import-node'],
19 | });
20 |
21 | return result!.code;
22 | },
23 | webpack: (code: string) => {
24 | const result = transform(code, {
25 | presets: ['@babel/preset-react'],
26 | plugins: [[require.resolve('../dist/es5/entrypoints/babel'), configuration]],
27 | });
28 |
29 | return result!.code;
30 | },
31 | boot: (code: string) => {
32 | const result = transform(code, {
33 | presets: ['@babel/preset-react'],
34 | plugins: [[require.resolve('../dist/es5/entrypoints/babel'), configuration]],
35 | });
36 |
37 | return result!.code;
38 | },
39 | };
40 |
41 | describe('babel', () => {
42 | Object.keys(testPlugin).forEach(folderName => {
43 | const actual = readFileSync(join(FIXTURE_PATH, folderName, 'actual.js'), 'utf8');
44 | const expected = readFileSync(join(FIXTURE_PATH, folderName, 'expected.js'), 'utf8');
45 |
46 | it(`works with ${folderName}`, () => {
47 | const result = (testPlugin as any)[folderName](actual);
48 | expect(result.trim()).toBe(expected.trim());
49 | });
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/__tests__/componentLoader.spec.tsx:
--------------------------------------------------------------------------------
1 | import '@theuiteam/lib-builder/configs/setupEnzyme';
2 | import { mount } from 'enzyme';
3 | import * as React from 'react';
4 |
5 | import { toLoadable } from '../src/loadable/toLoadable';
6 | import { ImportedComponent } from '../src/ui/Component';
7 | import loader from '../src/ui/HOC';
8 |
9 | describe('Async Component', () => {
10 | describe('loader', () => {
11 | it('should load component', done => {
12 | const TargetComponent = ({ payload }: any) =>
{payload}
;
13 | const Component = loader(() => Promise.resolve(TargetComponent));
14 |
15 | const wrapper = mount();
16 |
17 | expect(wrapper.find(TargetComponent)).toHaveLength(0);
18 | setImmediate(() => {
19 | wrapper.update();
20 | expect(wrapper.find(TargetComponent).html()).toContain('42');
21 | wrapper.unmount();
22 | done();
23 | });
24 | });
25 |
26 | it('forwardRef', done => {
27 | const TargetComponent = React.forwardRef(({ payload }, fref) => {payload}
);
28 | const Component = loader(() => Promise.resolve(TargetComponent));
29 | const ref = React.createRef();
30 | mount();
31 |
32 | setImmediate(() => {
33 | expect(ref.current).not.toBe(null);
34 | done();
35 | });
36 | });
37 | });
38 |
39 | describe('SSR', () => {
40 | it('should precache Components', async () => {
41 | const TargetComponent = ({ payload }: any) => {payload}
;
42 | const LoadingComponent = () => loading
;
43 | const loadable = toLoadable(() => Promise.resolve(TargetComponent));
44 | await loadable.load();
45 |
46 | const wrapper = mount(
47 |
48 | );
49 | expect(wrapper.find(LoadingComponent)).toHaveLength(0);
50 | expect(wrapper.find(TargetComponent).html()).toContain('42');
51 | });
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/__tests__/loadable.spec.ts:
--------------------------------------------------------------------------------
1 | import { getLoadable } from '../src/loadable/loadable';
2 |
3 | describe('getLoadable', () => {
4 | const importedWrapper = (_: any, b: any) => b;
5 |
6 | it('cache test - should use mark', () => {
7 | const l1 = getLoadable(() => importedWrapper('imported_mark1_component', Promise.resolve(42)));
8 | const l2 = getLoadable(() => importedWrapper('imported_mark1_component', Promise.resolve(42)));
9 |
10 | expect(l1).toEqual(l2);
11 | });
12 |
13 | it('cache test - no mark present', () => {
14 | const l1 = getLoadable(() => Promise.resolve(42));
15 | const l2 = getLoadable(() => Promise.resolve(42));
16 |
17 | expect(l1).not.toEqual(l2);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/__tests__/macro.spec.ts:
--------------------------------------------------------------------------------
1 | const pluginTester = require('babel-plugin-tester');
2 | const plugin = require('babel-plugin-macros');
3 | const prettier = require('prettier');
4 |
5 | describe('babel macro', () => {
6 | pluginTester({
7 | plugin,
8 | snapshot: true,
9 | babelOptions: {
10 | filename: __filename,
11 | plugins: ['dynamic-import-node'],
12 | },
13 | formatResult(result: string) {
14 | return prettier.format(result, { trailingComma: 'es5' });
15 | },
16 | tests: {
17 | nothing: 'const a = 42;',
18 | 'no usage': `import {lazy} from "../macro";`,
19 | 'flat import': `import "../macro";
20 | import('./a.js')
21 | `,
22 | boot: `
23 | import {assignImportedComponents, lazy} from "../macro";
24 | assignImportedComponents([() => import('./a')]);
25 | lazy(() => import('./a'));
26 | `,
27 | lazy: `
28 | import {lazy} from "../macro";
29 | const v = lazy(() => import('./a'));
30 | `,
31 | many: `
32 | import {imported, useImported} from "../macro";
33 | const v = imported(() => import('./a'));
34 | const x = () => useImported(() => import('./b'));
35 | `,
36 | },
37 | });
38 | });
39 |
40 | describe('babel macro', () => {
41 | pluginTester({
42 | plugin,
43 | snapshot: true,
44 | babelOptions: {
45 | filename: __filename,
46 | plugins: [require.resolve('../babel'), 'dynamic-import-node'],
47 | },
48 | formatResult(result: string) {
49 | return prettier.format(result, { trailingComma: 'es5' });
50 | },
51 | tests: {
52 | 'plugin combination': `
53 | import {imported, useImported} from "../macro";
54 | const v = imported(() => import('./a'));
55 | const x = () => useImported(() => import('./b'));
56 | `,
57 | },
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/__tests__/module.spec.tsx:
--------------------------------------------------------------------------------
1 | import '@theuiteam/lib-builder/configs/setupEnzyme';
2 | import { mount } from 'enzyme';
3 | import * as React from 'react';
4 | import { act } from 'react-dom/test-utils';
5 |
6 | import { done } from '../src/loadable/pending';
7 | import { importedModule, ImportedModule } from '../src/ui/Module';
8 |
9 | describe('Module Component', () => {
10 | describe('hoc', () => {
11 | it('should load component', async () => {
12 | const Component = importedModule(() => Promise.resolve((x: number) => x + 42));
13 |
14 | const wrapper = mount({fn => {fn(42)}});
15 |
16 | expect(wrapper.update().html()).toContain('loading');
17 |
18 | await act(async () => {
19 | await done();
20 | });
21 |
22 | expect(wrapper.update().html()).toContain('84');
23 | });
24 | });
25 |
26 | describe('component', () => {
27 | it('should load component', async () => {
28 | const importer = () => Promise.resolve((x: number) => x + 42);
29 |
30 | const wrapper = mount(
31 |
32 | {fn => {fn(42)}}
33 |
34 | );
35 |
36 | expect(wrapper.update().html()).toContain('loading');
37 |
38 | await act(async () => {
39 | await done();
40 | });
41 |
42 | expect(wrapper.update().html()).toContain('84');
43 | });
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/__tests__/signatures.spec.ts:
--------------------------------------------------------------------------------
1 | import { getFunctionSignature, importMatch } from '../src/utils/signatures';
2 |
3 | describe('signatures', () => {
4 | const a = (i: any) => i;
5 | const b = (i: any) => i;
6 |
7 | it('extract markers from function', () => {
8 | expect(importMatch(getFunctionSignature(() => a('imported_XXYY_component')))).toEqual(['XXYY']);
9 | });
10 |
11 | it('work similar for similar functions', () => {
12 | expect(getFunctionSignature(() => a('imported_XXYY_component'))).toBe(
13 | getFunctionSignature(() => b('imported_XXYY_component'))
14 | );
15 | });
16 | });
17 |
18 | describe('importMatch', () => {
19 | it('standard', () => {
20 | expect(
21 | importMatch(
22 | getFunctionSignature(
23 | `() => importedWrapper('imported_mark1_component', Promise.resolve(TargetComponent)), true)`
24 | )
25 | )
26 | ).toEqual(['mark1']);
27 | });
28 |
29 | it('webpack', () => {
30 | expect(
31 | importMatch(
32 | getFunctionSignature(
33 | `() => importedWrapper("imported_mark1_component", __webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./components/Another */ "./app/components/Another.tsx")))`
34 | )
35 | )
36 | ).toEqual(['mark1']);
37 | });
38 |
39 | it('webpack-prod', () => {
40 | expect(
41 | importMatch(
42 | getFunctionSignature(
43 | `() => importedWrapper('imported_mark1_component',__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./components/Another */ "./app/components/Another.tsx")))`
44 | )
45 | )
46 | ).toEqual(['mark1']);
47 | });
48 |
49 | it('functional', () => {
50 | expect(
51 | importMatch(
52 | getFunctionSignature(`"function loadable() {
53 | return importedWrapper('imported_1ubbetg_component', __webpack_require__.e(/*! import() | namedChunk-1 */ "namedChunk-1").then(__webpack_require__.t.bind(null, /*! ./DeferredRender */ "./src/DeferredRender.js", 7)));
54 | }"`)
55 | )
56 | ).toEqual(['1ubbetg']);
57 | });
58 |
59 | it('parcel', () => {
60 | expect(
61 | importMatch(
62 | getFunctionSignature(`function _() {
63 | return importedWrapper('imported_mark1_component', require("_bundle_loader")(require.resolve('./HelloWorld3')));
64 | }`)
65 | )
66 | ).toEqual(['mark1']);
67 | });
68 |
69 | it('ie11 uglify', () => {
70 | expect(
71 | importMatch(
72 | getFunctionSignature(`function _() {
73 | var t = 'imported_mark1_component';
74 | }`)
75 | )
76 | ).toEqual(['mark1']);
77 | });
78 |
79 | it('multiple imports in one line', () => {
80 | expect(
81 | importMatch(
82 | getFunctionSignature(`function _() {
83 | "imported_1pn9k36_component", blablabla- importedWrapper("imported_-1556gns_component")
84 | }`)
85 | )
86 | ).toEqual(['1pn9k36', '-1556gns']);
87 | });
88 |
89 | it('maps function signatures', () => {
90 | expect(getFunctionSignature(`import('file')`)).toEqual(getFunctionSignature(`import(/* */'file')`));
91 |
92 | expect(getFunctionSignature(`import('file')`)).toEqual('import(`file`)');
93 | });
94 |
95 | it('maps function signatures after terser pass', () => {
96 | expect(getFunctionSignature('()=>$(`imported_-f5674t_component`,n.e(3).then(n.bind(null,`xxx`,7)))')).toEqual(
97 | getFunctionSignature('()=>$(`imported_-f5674t_component`,x.e(3).then(x.bind(null,`xxx`,7)))')
98 | );
99 | expect(getFunctionSignature('()=>$(`imported_-f5674t_component`,n.e(3).then(n.bind(null,`xxx`,7)))')).toEqual(
100 | '()=>$(`imported_-f5674t_component`,-we().-wbind(null,`xxx`,7)))'
101 | );
102 | });
103 |
104 | it('maps internal and external signatures', () => {
105 | // internal is with Promise.resolve
106 | // extenal is with webpack_require.e
107 | expect(
108 | getFunctionSignature(
109 | '() => importedWrapper("imported_-1135avo_component", __webpack_require__.e(/*! import() */ 12).then(__webpack_require__.bind(null, /*! universal/components/SERP */ "./universal/components/SERP/index.js")))'
110 | )
111 | ).toBe(
112 | getFunctionSignature(
113 | '() => importedWrapper("imported_-1135avo_component", Promise.resolve(/*! import() */).then(__webpack_require__.bind(null, /*! universal/components/SERP */ "./universal/components/SERP/index.js")))'
114 | )
115 | );
116 | });
117 |
118 | it('maps function with different wrappers', () => {
119 | expect(getFunctionSignature('()=>P("imported_-is59m_component",t.e(41).then(t.bind(null,"./Promo.jsx")))')).toEqual(
120 | getFunctionSignature('()=>s("imported_-is59m_component",n.e(41).then(n.bind(null,"./Promo.jsx")))')
121 | );
122 | expect(getFunctionSignature('()=>P("imported_-is59m_component",t.e(41).then(t.bind(null,"./Promo.jsx")))')).toEqual(
123 | '()=>$(`imported_-is59m_component`,-we().-wbind(null,`./Promo.jsx`)))'
124 | );
125 | });
126 |
127 | it('fallback check: same signature, different function', () => {
128 | expect(
129 | getFunctionSignature('()=>P("imported_-one_component",t.e(41).then(t.bind(null,"./Promo.jsx")))')
130 | ).not.toEqual(
131 | getFunctionSignature('()=>s("imported_-another_component",n.e(41).then(n.bind(null,"./Promo.jsx")))')
132 | );
133 | });
134 | });
135 |
--------------------------------------------------------------------------------
/assets/imported-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theKashey/react-imported-component/359cb486a049a4151f64c2e5c7b34ad3851cd554/assets/imported-logo.png
--------------------------------------------------------------------------------
/babel.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./dist/es5/entrypoints/babel').default;
2 |
--------------------------------------------------------------------------------
/bin/imported-components:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require('../dist/es5/scanners/cli');
--------------------------------------------------------------------------------
/boot/boot.d.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | declare var sidecar: React.SFC;
4 |
5 | export default sidecar;
6 |
--------------------------------------------------------------------------------
/boot/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "main": "../dist/es5/entrypoints/boot.js",
4 | "jsnext:main": "../dist/es2015/entrypoints/boot.js",
5 | "module": "../dist/es2015/entrypoints/boot.js",
6 | "types": "../dist/es2015/entrypoints/boot.d.ts"
7 | }
8 |
--------------------------------------------------------------------------------
/doczrc.js:
--------------------------------------------------------------------------------
1 | export default {
2 | typescript: true,
3 | title: 'Docz',
4 | menu: ['Getting Started', 'Components'],
5 | };
6 |
--------------------------------------------------------------------------------
/example/app.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import imported, {lazy, LazyBoundary} from "../src";
4 |
5 | const deferImport = (promise: Promise) => new Promise(resolve => setTimeout(() => resolve(promise), 2000));
6 |
7 | const Lazy = () => I AM LAZY
;
8 |
9 |
10 | const ReactLazy = React.lazy(() => deferImport({default: Lazy}));
11 | const ImportedLazy = lazy(() => deferImport({default: Lazy}));
12 | const Imported = imported(() => deferImport({default: Lazy}));
13 | const ImportedLoading = imported(() => deferImport({default: Lazy}), { LoadingComponent: () => "imported is loading"});
14 |
15 | function App() {
16 | return (
17 |
18 |
Lazy Loading
19 |
20 | -
21 | Loading (React)
}>
22 |
23 |
24 |
25 |
26 | Loading (Imported)}>
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 |
41 | export default App;
--------------------------------------------------------------------------------
/example/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theKashey/react-imported-component/359cb486a049a4151f64c2e5c7b34ad3851cd554/example/assets/.gitkeep
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import App from './app';
4 |
5 | ReactDOM.render(, document.getElementById('app'));
6 |
--------------------------------------------------------------------------------
/examples/CRA/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
--------------------------------------------------------------------------------
/examples/CRA/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | .cache
4 | dist
5 | yarn.lock
--------------------------------------------------------------------------------
/examples/CRA/async-imports.js:
--------------------------------------------------------------------------------
1 |
2 | /* eslint-disable */
3 | /* tslint:disable */
4 |
5 | // generated by react-imported-component, DO NOT EDIT
6 | import {assignImportedComponents} from 'react-imported-component/macro';
7 |
8 | // all your imports are defined here
9 | // all, even the ones you tried to hide in comments (that's the cost of making a very fast parser)
10 | // to remove any import from here
11 | // 1) use magic comment `import(/* client-side */ './myFile')` - and it will disappear
12 | // 2) use file filter to ignore specific locations (refer to the README)
13 |
14 | const applicationImports = assignImportedComponents([
15 | [() => import('./src/async'), '', './src/async', false],
16 | ]);
17 |
18 | export default applicationImports;
19 |
20 | // @ts-ignore
21 | if (module.hot) {
22 | // these imports would make this module a parent for the imported modules.
23 | // but this is just a helper - so ignore(and accept!) all updates
24 |
25 | // @ts-ignore
26 | module.hot.accept(() => null);
27 | }
28 |
--------------------------------------------------------------------------------
/examples/CRA/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-react-app-example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.js",
7 | "dependencies": {
8 | "react": "16.8.6",
9 | "react-dom": "16.8.6",
10 | "react-imported-component": "6.1.1",
11 | "react-scripts": "3.0.1"
12 | },
13 | "devDependencies": {
14 | "babel-jest": "^24.8.0",
15 | "typescript": "3.3.3"
16 | },
17 | "scripts": {
18 | "start": "imported-components src async-imports.js && react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test --env=jsdom",
21 | "eject": "react-scripts eject"
22 | },
23 | "browserslist": [
24 | ">0.2%",
25 | "not dead",
26 | "not ie <= 11",
27 | "not op_mini all"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/examples/CRA/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | React App
24 |
25 |
26 |
27 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/examples/CRA/src/async-imports.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* tslint:disable */
3 |
4 | // generated by react-imported-component, DO NOT EDIT
5 | import { assignImportedComponents } from "react-imported-component/macro";
6 |
7 | // all your imports are defined here
8 | // all, even the ones you tried to hide in comments (that's the cost of making a very fast parser)
9 | // to remove any import from here
10 | // 1) use magic comment `import(/* client-side */ './myFile')` - and it will disappear
11 | // 2) use file filter to ignore specific locations (refer to the README)
12 |
13 | const applicationImports = assignImportedComponents([
14 | [() => import("./async"), "", "./async", false]
15 | ]);
16 |
17 | console.log("async imports called");
18 |
19 | export default applicationImports;
20 |
21 | // @ts-ignore
22 | if (module.hot) {
23 | // these imports would make this module a parent for the imported modules.
24 | // but this is just a helper - so ignore(and accept!) all updates
25 |
26 | // @ts-ignore
27 | module.hot.accept(() => null);
28 | }
29 |
--------------------------------------------------------------------------------
/examples/CRA/src/async.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default () => loaded async!
--------------------------------------------------------------------------------
/examples/CRA/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | // this import should enable import transpilation
5 | import "react-imported-component/macro";
6 |
7 | import './async-imports';
8 |
9 | import "./styles.css";
10 | import Load from "./load";
11 |
12 | const Async = Load(() => import("./async"));
13 |
14 | function App() {
15 | return (
16 |
17 |
Hello CodeSandbox
18 |
Start editing to see some magic happen!
19 |
20 |
21 | );
22 | }
23 |
24 | const rootElement = document.getElementById("root");
25 | ReactDOM.render(, rootElement);
26 |
--------------------------------------------------------------------------------
/examples/CRA/src/load.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { imported, lazy } from "react-imported-component/macro";
3 |
4 | const Load = component => {
5 | if (!component) return;
6 | return imported(component, {
7 | LoadingComponent: () => Loading
,
8 | ErrorComponent: () => Error
9 | });
10 | };
11 |
12 |
13 | export default Load;
14 |
--------------------------------------------------------------------------------
/examples/CRA/src/styles.css:
--------------------------------------------------------------------------------
1 | .App {
2 | font-family: sans-serif;
3 | text-align: center;
4 | }
5 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"],
3 | "plugins": [
4 | "react-imported-component/babel",
5 | "@babel/plugin-proposal-dynamic-import",
6 | "@babel/plugin-transform-modules-commonjs"
7 | ],
8 | "env": {
9 | "client": {
10 | "plugins": [
11 | "react-hot-loader/babel"
12 | ]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | .cache
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © Benoit Tremblay
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/README.md:
--------------------------------------------------------------------------------
1 | __COMPLEX__ streamed SSR example
2 |
3 | - `yarn start` - simple `renderToString` SSR + used style extraction
4 | - `yarn start:stream` - complex `renderToStream` example
5 | - if `stream-server/middleware` is used - all styles would be in HEAD
6 | - if `stream-server/interleaved-middleware` is used - all styles would be _interleaved_ in result HTML
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/App.jsx:
--------------------------------------------------------------------------------
1 | import {hot} from 'react-hot-loader';
2 | import React from 'react';
3 | import importedComponent from 'react-imported-component';
4 |
5 | import { Helmet } from 'react-helmet';
6 | import { Switch, Route, Redirect } from 'react-router-dom';
7 |
8 | import HelloWorld from './HelloWorld';
9 | import "./base.css";
10 |
11 | const HelloWorld2 = importedComponent(() => import('./HelloWorld2'));
12 |
13 | export default hot(module)(function App() {
14 | return (
15 |
16 |
17 |
18 |
19 | App
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | })
28 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/HelloWorld.jsx:
--------------------------------------------------------------------------------
1 | // Dead simple component for the hello world (hi mom!)
2 |
3 | import React from 'react';
4 | import { Link } from 'react-router-dom';
5 | import './HelloWorld.scss';
6 |
7 | export default function HelloWorld() {
8 | return
9 |
Hello world
10 |
11 | This is an ordinary react component.
12 |
13 | Click here to see a code-split component.
14 |
15 |
;
16 | }
17 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/HelloWorld.scss:
--------------------------------------------------------------------------------
1 | // With Parcel, you have nothing to setup for SCSS, you just write!
2 |
3 | $primary-color: #0098da;
4 |
5 | body {
6 | margin: 0;
7 | }
8 |
9 | .hello-world {
10 | background: $primary-color;
11 | color: white;
12 | text-align: center;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/HelloWorld2.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Link} from 'react-router-dom';
3 | import './codeSplitAssets/NyanCat.css';
4 | import Go from './codeSplitAssets/NyanCat.js';
5 | // import HelloWorld3 from './HelloWorld3';
6 |
7 | // right now Parcel does not support `import inside import` - https://github.com/parcel-bundler/parcel/issues/2620
8 |
9 | import importedComponent from "react-imported-component";
10 |
11 | const HelloWorld3 = importedComponent(() => import('./HelloWorld3'));
12 |
13 | export default class Nyan extends Component {
14 |
15 | componentDidMount() {
16 | Go();
17 | }
18 |
19 | render() {
20 | return
21 |
Code-splitted CAT!
22 |
23 | This is a code-split component.
24 |
25 | Click here to see an ordinary component.
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | and it has a nested code-splitted cat....
44 |
45 |
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/HelloWorld3.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Link} from 'react-router-dom';
3 | import './codeSplitAssets/NyanCat.css';
4 | import Go from './codeSplitAssets/NyanCat.js';
5 | import {say42} from "./codeSplitAssets/utils";
6 |
7 | export default class Nyan extends Component {
8 |
9 | componentDidMount() {
10 | Go();
11 | }
12 |
13 | render() {
14 | return
15 | I am async {say42()} cat
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/base.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | }
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/client.js:
--------------------------------------------------------------------------------
1 | // pre-import imported component
2 | import './imported-chunk';
3 |
4 | // install stream helper
5 | import {injectLoadableTracker} from 'react-imported-component/boot';
6 |
7 | injectLoadableTracker('exampleTracker');
8 |
9 | // HINT!
10 | // ------------
11 | // CASE 1 - let browser load chunks before going further
12 | // outcome - network and CPU are working together! 👍
13 | // ------------
14 | if(1) {
15 | // load the rest after letting the browser kick off chunk loading
16 | Promise.resolve().then(() =>
17 | Promise.resolve().then(() => {
18 | require('./main')
19 | })
20 | );
21 | } else {
22 |
23 | // ------------
24 | // CASE 2 - dont do that
25 | // outcome - network is idle while CPU is busy 👎
26 | // ------------
27 |
28 | // require('./main')
29 | }
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/codeSplitAssets/NyanCat.js:
--------------------------------------------------------------------------------
1 | export default () => {
2 | const stars = document.querySelectorAll('.stars')[0];
3 | for (let i = 0; i < 12; i += 1) {
4 | stars.innerHTML += '
';
5 | };
6 | }
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/codeSplitAssets/utils.js:
--------------------------------------------------------------------------------
1 | export const say42 = () => 42;
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/imported-chunk.js:
--------------------------------------------------------------------------------
1 |
2 | /* eslint-disable */
3 | /* tslint:disable */
4 |
5 | // generated by react-imported-component, DO NOT EDIT
6 | import {assignImportedComponents} from 'react-imported-component/macro';
7 |
8 | // all your imports are defined here
9 | // all, even the ones you tried to hide in comments (that's the cost of making a very fast parser)
10 | // to remove any import from here
11 | // 1) use magic comment `import(/* client-side */ './myFile')` - and it will disappear
12 | // 2) use file filter to ignore specific locations (refer to the README)
13 |
14 | const applicationImports = assignImportedComponents([
15 | [() => import('./HelloWorld2'), '', './app/HelloWorld2', false],
16 | [() => import('./HelloWorld3'), '', './app/HelloWorld3', false],
17 | ]);
18 |
19 | export default applicationImports;
20 |
21 | // @ts-ignore
22 | if (module.hot) {
23 | // these imports would make this module a parent for the imported modules.
24 | // but this is just a helper - so ignore(and accept!) all updates
25 |
26 | // @ts-ignore
27 | module.hot.accept(() => null);
28 | }
29 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/app/main.js:
--------------------------------------------------------------------------------
1 | // Entry point for the browser
2 | // Start your React application and add the required containers
3 | // Here we have for react-router
4 |
5 | import 'react-hot-loader';
6 | import {rehydrateMarks} from 'react-imported-component';
7 | import React from 'react';
8 | import ReactDOM from 'react-dom';
9 | import {BrowserRouter} from 'react-router-dom';
10 | import {moveStyles} from 'used-styles/moveStyles';
11 | // import chunk definition
12 | import './imported-chunk';
13 |
14 | import App from './App';
15 |
16 | // move SSR-ed styles to the head
17 | moveStyles();
18 |
19 | const element = document.getElementById('app');
20 | const app = (
21 |
22 |
23 |
24 | );
25 |
26 | const TM = 1000;
27 |
28 | console.log('waiting');
29 | setTimeout(function () {
30 | // rehydrate the bundle marks
31 | console.log('loading');
32 | rehydrateMarks().then(() => {
33 | console.log('loaded...');
34 | setTimeout(function () {
35 | console.log('rendering');
36 | // In production, we want to hydrate instead of render
37 | // because of the server-rendering
38 | if (1 || process.env.NODE_ENV === 'production') {
39 | ReactDOM.hydrate(app, element);
40 | } else {
41 | ReactDOM.render(app, element);
42 | }
43 | }, TM);
44 | });
45 | }, TM);
46 |
47 | // Hot reload is that easy with Parcel
48 | if (module.hot) {
49 | module.hot.accept();
50 | }
51 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parcel-react-ssr",
3 | "version": "0.4.0",
4 | "description": "Example of SSR with React and ParcelJS",
5 | "main": "index.js",
6 | "keywords": [
7 | "parcel",
8 | "react",
9 | "ssr"
10 | ],
11 | "author": "Benoit Tremblay ",
12 | "license": "MIT",
13 | "repository": "reactivestack/parcel-react-ssr",
14 | "scripts": {
15 | "dev": "parcel app/index.html",
16 | "build": "rimraf dist && npm run build-imported && npm run build-client && npm run build-server && npm run build-server-stream",
17 | "build-imported": "imported-components app app/imported-chunk.js",
18 | "build-client": "cross-env BABEL_ENV=client parcel build app/index.html -d dist/client --public-url /dist --no-minify --no-cache",
19 | "build-server": "cross-env BABEL_ENV=server parcel build server/index.js -d dist/server --public-url /dist --target=node --no-minify --no-cache",
20 | "build-server-stream": "cross-env BABEL_ENV=server parcel build stream-server/index.js -d dist/server-stream --public-url /dist --target=node --no-minify --no-cache",
21 | "start": "node dist/server",
22 | "start:stream": "node dist/server-stream"
23 | },
24 | "alias": {
25 | "react": "./node_modules/react",
26 | "react-dom": "./node_modules/react-dom"
27 | },
28 | "dependencies": {
29 | "cheerio": "^1.0.0-rc.3",
30 | "compression": "^1.7.4",
31 | "express": "^4.17.1",
32 | "multistream": "^4.0.0",
33 | "parcel-plugin-bundle-manifest": "^0.2.0",
34 | "react": "^16.12.0",
35 | "react-dom": "^16.12.0",
36 | "react-helmet": "^5.2.1",
37 | "react-imported-component": "^6.2.1",
38 | "react-router-dom": "^4.2.2",
39 | "used-styles": "^1.1.0"
40 | },
41 | "devDependencies": {
42 | "@babel/cli": "^7.8.4",
43 | "@babel/core": "^7.8.4",
44 | "@babel/plugin-proposal-dynamic-import": "^7.8.3",
45 | "@babel/plugin-transform-modules-commonjs": "^7.8.3",
46 | "@babel/preset-env": "7.8.4",
47 | "@babel/preset-react": "7.8.3",
48 | "cross-env": "^7.0.0",
49 | "node-sass": "^4.13.1",
50 | "parcel-bundler": "^1.12.4",
51 | "react-hot-loader": "^4.12.19",
52 | "rimraf": "^3.0.2"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/server/generateHtml.js:
--------------------------------------------------------------------------------
1 | // Generate the HTML using index.html as a template
2 |
3 | import fs from 'fs';
4 | import path from 'path';
5 | import cheerio from 'cheerio';
6 | import {Helmet} from 'react-helmet';
7 |
8 | // The path is relative from server bundle to client bundle, not the source
9 | const templatePath = path.join(__dirname, '..', 'client', 'index.html');
10 | const HTML_TEMPLATE = fs.readFileSync(templatePath).toString();
11 |
12 | export default function generateHtml(markup, styles) {
13 | // Get the serer-rendering values for the
14 | const helmet = Helmet.renderStatic();
15 |
16 | const $template = cheerio.load(HTML_TEMPLATE);
17 | $template('head').append(
18 | helmet.title.toString() + helmet.meta.toString() + helmet.link.toString()
19 | );
20 | styles.forEach(style => {
21 | const link = `\n`;
22 | $template('head').append(link)
23 | });
24 | $template('#app').html(markup);
25 | console.log(markup);
26 |
27 | return $template.html();
28 | }
29 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/server/index.js:
--------------------------------------------------------------------------------
1 | // Setup express to handle Web requests
2 |
3 | import compression from 'compression';
4 | import express from 'express';
5 | import middleware from './middleware';
6 |
7 | const app = express();
8 |
9 | // Add gzip compression to responses
10 | app.use(compression());
11 |
12 | // Expose the public directory as /dist and point to the browser version
13 | app.use('/dist', express.static(__dirname + '/../client'));
14 |
15 | // Anything unresolved is serving the application and let
16 | // react-router do the routing!
17 | app.get('/*', middleware);
18 |
19 | // Check for PORT environment variable, otherwise fallback on Parcel default port
20 | const port = process.env.PORT || 1234;
21 | app.listen(port, () => {
22 | console.log(`Listening on port ${port}...`);
23 | });
24 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/server/middleware.js:
--------------------------------------------------------------------------------
1 | // Middleware for the server-rendering
2 |
3 | import {printDrainHydrateMarks} from 'react-imported-component';
4 | import React from 'react';
5 | import ReactDOM from 'react-dom/server';
6 | import {StaticRouter} from 'react-router-dom';
7 |
8 | import App from '../app/App';
9 | import generateHtml from './generateHtml';
10 | import {getProjectStyles, getUsedStyles} from 'used-styles';
11 |
12 | let projectStyles;
13 | getProjectStyles(__dirname+'/../client').then(x => {
14 | projectStyles = x;
15 | console.log(x);
16 | });
17 |
18 | export default function middleware(req, res) {
19 | // Generate the server-rendered HTML using the appropriate router
20 | const context = {};
21 | const markup = ReactDOM.renderToString(
22 |
23 |
24 |
25 | ) + printDrainHydrateMarks();
26 |
27 | // If react-router is redirecting, do it on the server side
28 | if (context.url) {
29 | return res.redirect(301, context.url);
30 | }
31 |
32 | const usedStyles = getUsedStyles(markup, projectStyles);
33 | console.log('used styles', usedStyles);
34 | // Format the HTML using the template and send the result
35 | const html = generateHtml('JS will start in ~2s
' + markup, usedStyles);
36 | res.send(html);
37 | }
38 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/stream-server/index.js:
--------------------------------------------------------------------------------
1 | // Setup express to handle Web requests
2 |
3 | import compression from 'compression';
4 | import express from 'express';
5 |
6 | // Pick one of these
7 |
8 | import middleware from './middleware';
9 | // import middleware from './interleaved-middleware';
10 |
11 | const app = express();
12 |
13 | // Add gzip compression to responses
14 | app.use(compression());
15 |
16 | // Expose the public directory as /dist and point to the browser version
17 | app.use('/dist', express.static(__dirname + '/../client'));
18 |
19 | // Anything unresolved is serving the application and let
20 | // react-router do the routing!
21 | app.get('/*', middleware);
22 |
23 | // Check for PORT environment variable, otherwise fallback on Parcel default port
24 | const port = process.env.PORT || 1234;
25 | app.listen(port, () => {
26 | console.log(`Listening on port ${port}...`);
27 | });
28 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/stream-server/interleaved-middleware.js:
--------------------------------------------------------------------------------
1 | // Middleware for the server-rendering
2 | import {Readable} from 'stream';
3 | import fs from 'fs';
4 | import MultiStream from 'multistream';
5 | import {getProjectStyles, createLink} from 'used-styles';
6 | import {createStyleStream} from 'used-styles/react';
7 | import {printDrainHydrateMarks, ImportedStream} from 'react-imported-component';
8 | import {createLoadableStream} from 'react-imported-component/server';
9 | import React from 'react';
10 | import ReactDOM from 'react-dom/server';
11 | import {StaticRouter} from 'react-router-dom';
12 |
13 | import App from '../app/App';
14 |
15 | const readable = () => {
16 | const s = new Readable();
17 | s._read = () => true;
18 | return s;
19 | };
20 |
21 | const readableString = string => {
22 | const s = new Readable();
23 | s.push(string);
24 | s.push(null);
25 | s._read = () => true;
26 | return s;
27 | };
28 |
29 | let projectStyles = {};
30 | getProjectStyles(__dirname + '/../client').then(x => {
31 | projectStyles = x;
32 | console.log(x);
33 | });
34 |
35 | const manifect = JSON.parse(fs.readFileSync(__dirname + '/../client/parcel-manifest.json'));
36 |
37 | export default function middleware(req, res) {
38 | // Generate the server-rendered HTML using the appropriate router
39 | const context = {};
40 |
41 | let streamUID = createLoadableStream();
42 | const htmlStream = ReactDOM.renderToNodeStream(
43 |
44 |
45 |
46 |
47 |
48 | );
49 |
50 | // If react-router is redirecting, do it on the server side
51 | if (context.url) {
52 | return res.redirect(301, context.url);
53 | }
54 |
55 | // create a style steam
56 | const styledStream = createStyleStream(projectStyles, (style) => (
57 | // just return it
58 | createLink(`dist/${style}`)
59 | ));
60 |
61 | // allow client to start loading js bundle
62 | res.write(``);
63 |
64 | const endStream = readableString('');
65 |
66 | const streams = [
67 | styledStream,
68 | endStream,
69 | ];
70 |
71 | MultiStream(streams).pipe(res);
72 |
73 | // start by piping react and styled transform stream
74 | htmlStream.pipe(styledStream);
75 | styledStream.on('end', () => {
76 | res.write('
');
77 | // push loaded chunks information
78 | res.write(printDrainHydrateMarks(streamUID));
79 | res.write('');
80 | res.end();
81 | });
82 | }
83 |
--------------------------------------------------------------------------------
/examples/SSR/parcel-react-ssr/stream-server/middleware.js:
--------------------------------------------------------------------------------
1 | // Middleware for the server-rendering
2 | import {Readable} from 'stream';
3 | import fs from 'fs';
4 | import MultiStream from 'multistream';
5 | import {getProjectStyles, createStyleStream} from 'used-styles';
6 | import {printDrainHydrateMarks, ImportedStream} from 'react-imported-component';
7 | import {createLoadableStream, createLoadableTransformer, getLoadableTrackerCallback} from 'react-imported-component/server';
8 | import React from 'react';
9 | import ReactDOM from 'react-dom/server';
10 | import {StaticRouter} from 'react-router-dom';
11 |
12 | import App from '../app/App';
13 |
14 | const readable = () => {
15 | const s = new Readable();
16 | s._read = () => true;
17 | return s;
18 | };
19 |
20 | const readableString = string => {
21 | const s = new Readable();
22 | s.push(string);
23 | s.push(null);
24 | s._read = () => true;
25 | return s;
26 | };
27 |
28 | let projectStyles = {};
29 | getProjectStyles(__dirname + '/../client').then(x => {
30 | projectStyles = x;
31 | console.log(x);
32 | });
33 |
34 | const manifect = JSON.parse(fs.readFileSync(__dirname + '/../client/parcel-manifest.json'));
35 |
36 | export default function middleware(req, res) {
37 | // Generate the server-rendered HTML using the appropriate router
38 | const context = {};
39 |
40 | let streamUID = createLoadableStream();
41 | const htmlStream = ReactDOM.renderToNodeStream(
42 |
43 |
44 |
45 |
46 |
47 | );
48 |
49 | // If react-router is redirecting, do it on the server side
50 | if (context.url) {
51 | return res.redirect(301, context.url);
52 | }
53 |
54 | // create a "header" stream
55 | const headerStream = readable();
56 |
57 | // create a style steam
58 | const styledStream = createStyleStream(projectStyles, (style) => {
59 | // emit a line to header Stream
60 | headerStream.push(`\n/*used style*/\n`);
61 | });
62 |
63 | // allow client to start loading js bundle
64 | res.write([
65 | ''
66 | ``,
67 | // HINT!
68 | // uncomment this line to convert this example in a "classical one", when all scripts are present in the HTML code
69 | // ``,
70 | ].join('\n'));
71 |
72 | /*
73 |
74 | res
75 | <- headerStream
76 | <- middleStream
77 | <- styledStream
78 | <- importedHelper
79 | <- htmlStream (React)
80 | <- htmlStream "end"
81 | */
82 |
83 |
84 | const middleStream = readableString('');
85 | const endStream = readableString('
');
86 |
87 | const streams = [
88 | headerStream,
89 | middleStream,
90 | styledStream,
91 | endStream,
92 | ];
93 |
94 | const importedHelper = createLoadableTransformer(streamUID, getLoadableTrackerCallback("exampleTracker"));
95 |
96 | MultiStream(streams).pipe(res);
97 |
98 | // start by piping react and styled transform stream
99 | htmlStream
100 | .pipe(importedHelper)
101 | .pipe(styledStream, {end: false});
102 |
103 | htmlStream.on('end', () => {
104 | // push loaded chunks information
105 | headerStream.push(printDrainHydrateMarks(streamUID));
106 | // kill header stream on the main stream end
107 | headerStream.push(null);
108 | styledStream.end();
109 | });
110 | }
111 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["react-imported-component/babel"]
3 | }
4 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: debian:stretch
6 | steps:
7 | - checkout
8 | - run:
9 | name: Greeting
10 | command: echo Hello, world.
11 | - run:
12 | name: Print the Current Time
13 | command: date
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | .cache
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/README.md:
--------------------------------------------------------------------------------
1 | Example using `runtime chunk` or `manifest` to speed up chunk loading
2 |
3 |
4 | -- not complete!
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #EEE;
3 | }
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Home from "./components/Home";
3 | import importedComponent, {ComponentLoader, loadableResource} from "react-imported-component";
4 |
5 | const Another = importedComponent(() => import(/* webpackChunkName: namedChunk-0 */"./components/Another"), {
6 | LoadingComponent: () => loading
7 | });
8 | const Other1 = importedComponent(() => import(/* webpackChunkName: "namedChunk-1" */"./components/Other"));
9 | const Other2 = importedComponent(() => import(/* webpackChunkName: "namedChunk-1" */"./components/OtherTween"));
10 |
11 | const AnotherWrapped = importedComponent(
12 | () => import(/* webpackChunkName: namedChunk-0 */"./components/Another"), {
13 | render(Component, state, props: { prop: number }) {
14 | if (state.loading) {
15 | return
16 | }
17 | return
18 | }
19 | });
20 | //import Another from "./components/Another";
21 |
22 | // @ts-ignore
23 | // const importCss = () => im port("./App.css");
24 |
25 | export default function App() {
26 | return (
27 |
28 | [not-trackable]
29 |
import("./components/Another")}
31 | />
32 | import("./components/Another"))}
34 | />
35 | [/not-trackable]
36 | [home][/home]
37 |
38 |
39 |
40 |
41 |
42 | {0 && }
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theKashey/react-imported-component/359cb486a049a4151f64c2e5c7b34ad3851cd554/examples/SSR/runtime-manifest/app/assets/favicon.ico
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/client.tsx:
--------------------------------------------------------------------------------
1 | import { rehydrateMarks } from "react-imported-component";
2 | import * as React from "react";
3 | import ReactDOM from "react-dom";
4 | import { BrowserRouter } from "react-router-dom";
5 | import { Provider } from "react-redux";
6 | import { configureStore } from "./store";
7 | import "./imported"; // eslint-disable-line
8 |
9 | import App from "./App";
10 |
11 | const preloadedState = window.__PRELOADED_STATE__;
12 | delete window.__PRELOADED_STATE__;
13 |
14 | const store = configureStore(preloadedState);
15 |
16 | const element = document.getElementById("app");
17 |
18 | const app = (
19 |
20 |
21 |
22 |
23 |
24 | );
25 |
26 | rehydrateMarks().then(() => {
27 | console.log(element.innerHTML);
28 | ReactDOM.hydrate(app, element);
29 | console.log(element.innerHTML);
30 | });
31 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/components/Another.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Another:React.SFC<{test: number, p2: number}> = () => Another!;
4 | export default Another;
5 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/components/Home.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {Helmet} from "react-helmet";
3 | import {Link} from "react-router-dom";
4 | import imported, {lazy, LazyBoundary, remapImports} from "react-imported-component";
5 |
6 | const LZ = lazy(() => import("./LazyTarget"));
7 |
8 | const ND = imported(
9 | () => remapImports(
10 | import('./NotDefault'),
11 | ({NotDefault}) => NotDefault
12 | )
13 | );
14 |
15 | const Home = () => (
16 |
17 |
18 | Homepage!
19 |
20 |
Link to Another
21 |
Hello!
22 |
23 |
24 |
25 |
26 |
27 | );
28 | export default Home;
29 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/components/LazyTarget.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const LazyTarget: React.SFC = () => I am not lazy Lazy;
4 | export default LazyTarget;
5 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/components/NotDefault.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {useImported} from "react-imported-component"
3 |
4 | export const NotDefault: React.FC<{ p1: number }> = ({p1}) => {
5 | const {
6 | imported = x => `!${x}!`,
7 | } = useImported(() => import("./library"), (lib) => lib.magicFunction);
8 |
9 | return (
10 |
11 | I am not not Default Export({p1} prop == {imported(p1)})
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/components/Other.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const Another:React.SFC<{test: number}> = () => Other!;
4 | export default Another;
5 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/components/OtherTween.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const Another: React.SFC = () => Other Twin!;
4 | export default Another;
5 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/components/library.tsx:
--------------------------------------------------------------------------------
1 | console.log('loading library function');
2 |
3 | export const magicFunction = (x: number) => `** ${x} **`;
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/imported.ts:
--------------------------------------------------------------------------------
1 |
2 | /* eslint-disable */
3 | /* tslint:disable */
4 |
5 | import {assignImportedComponents} from 'react-imported-component/boot';
6 |
7 | const applicationImports = [
8 | () => import('./components/Another'),
9 | () => import('./components/LazyTarget'),
10 | () => import('./components/NotDefault'),
11 | () => import('./components/library'),
12 | () => import(/* webpackChunkName: "namedChunk-1" */'./components/Other'),
13 | () => import(/* webpackChunkName: "namedChunk-1" */'./components/OtherTween'),
14 | ];
15 |
16 | assignImportedComponents(applicationImports);
17 | export default applicationImports;
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from "redux";
2 | import { composeWithDevTools } from "redux-devtools-extension";
3 | import rootReducer from "./reducers";
4 |
5 | export function configureStore(initialState = {}) {
6 | const store = createStore(rootReducer, initialState, composeWithDevTools());
7 |
8 | if (module.hot) {
9 | module.hot.accept("./reducers", () => {
10 | console.log("Replacing reducers...");
11 | store.replaceReducer(require("./reducers").default);
12 | });
13 | }
14 |
15 | return store;
16 | }
17 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/app/store/reducers.ts:
--------------------------------------------------------------------------------
1 | export default function rootReducer(state) {
2 | return state;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "generate-imported-component": "imported-components app app/imported.ts",
4 | "build": "yarn webpack",
5 | "start": "node dist/server/index.js",
6 | "cp:1": "cp -R ../../../dist ./node_modules/react-imported-component/",
7 | "cp:2": "cp ../../../package.json ./node_modules/react-imported-component",
8 | "cp:3": "cp ../../../boot ./node_modules/react-imported-component",
9 | "cp": "yarn cp:1 && yarn cp:2 && yarn cp:3"
10 | },
11 | "dependencies": {
12 | "@types/cheerio": "^0.22.7",
13 | "@types/react": "^16.9.2",
14 | "@types/redux-devtools-extension": "^2.13.2",
15 | "@types/webpack-env": "^1.13.6",
16 | "awesome-typescript-loader": "^5.0.0",
17 | "babel-plugin-dynamic-import-node": "^1.2.0",
18 | "cheerio": "^1.0.0-rc.2",
19 | "compression": "^1.7.2",
20 | "express": "^4.16.3",
21 | "file-loader": "^1.1.11",
22 | "html-webpack-plugin": "^3.2.0",
23 | "react": "^16.9.0",
24 | "react-dom": "^16.9.0",
25 | "react-helmet": "^5.2.0",
26 | "react-imported-component": "^6.0.0-beta.3",
27 | "react-redux": "^5.0.7",
28 | "react-router-dom": "^4.2.2",
29 | "redux": "^4.0.0",
30 | "redux-devtools-extension": "^2.13.2",
31 | "ts-loader": "^4.2.0",
32 | "typescript": "^2.8.3",
33 | "webpack-serve": "^1.0.2"
34 | },
35 | "devDependencies": {
36 | "babel-core": "^6.21.0",
37 | "babel-loader": "^7.1.4",
38 | "babel-preset-es2015": "^6.18.0",
39 | "css-loader": "^2.1.0",
40 | "style-loader": "^0.23.1",
41 | "webpack": "^4.29.0",
42 | "webpack-cli": "^3.2.1"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/server/generateHtml.ts:
--------------------------------------------------------------------------------
1 | // Generate the HTML using index.html as a template
2 |
3 | import fs from "fs";
4 | import path from "path";
5 | import cheerio from "cheerio";
6 | import { Helmet } from "react-helmet";
7 | import { printDrainHydrateMarks } from "react-imported-component";
8 |
9 | // The path is relative from server bundle to client bundle, not the source
10 | const templatePath = path.join(__dirname, "..", "client", "index.html");
11 | const HTML_TEMPLATE = fs.readFileSync(templatePath).toString();
12 |
13 | export default function generateHtml(markup, state, getStream) {
14 | // Get the serer-rendering values for the
15 | const helmet = Helmet.renderStatic();
16 |
17 | const $template = cheerio.load(HTML_TEMPLATE);
18 | $template("head").append(
19 | helmet.title.toString() + helmet.meta.toString() + helmet.link.toString()
20 | );
21 | $template("head").append(
22 | ``
25 | );
26 | $template("head").append(printDrainHydrateMarks(getStream()));
27 | $template("#app").html(markup);
28 |
29 | return $template.html();
30 | }
31 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/server/index.ts:
--------------------------------------------------------------------------------
1 | import compression from "compression";
2 | import express from "express";
3 | import middleware from "./middleware";
4 |
5 | const app = express();
6 |
7 | // Add gzip compression to responses
8 | app.use(compression());
9 |
10 | // Expose the public directory as /dist and point to the browser version
11 | app.use(express.static(`${__dirname}/../client`, { index: false }));
12 |
13 | // Anything unresolved is serving the application and let
14 | // react-router do the routing!
15 | app.get("/*", middleware);
16 |
17 | // Check for PORT environment variable, otherwise fallback on Parcel default port
18 | const port = process.env.PORT || 1234;
19 | app.listen(port, () => {
20 | console.log(`Listening on port ${port}...`);
21 | });
22 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/server/middleware.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/server";
3 | import {StaticRouter} from "react-router-dom";
4 | import {Provider} from "react-redux";
5 |
6 | import "../app/imported";
7 | import App from "../app/App";
8 | import {configureStore} from "../app/store";
9 |
10 | import generateHtml from "./generateHtml";
11 | import {ImportedStream} from "react-imported-component";
12 | import {createLoadableStream} from "react-imported-component/server";
13 |
14 | const store = configureStore();
15 |
16 | export default (req, res) => {
17 | // Generate the server-rendered HTML using the appropriate router
18 | const context: { url?: string } = {};
19 | let streamId = createLoadableStream();
20 | const router = (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | const markup = ReactDOM.renderToString(router);
30 |
31 | // If react-router is redirecting, do it on the server side
32 | if (context.url) {
33 | res.redirect(301, context.url);
34 | } else {
35 | // Format the HTML using the template and send the result
36 |
37 | // stream is not defined unless we call the render
38 | const html = generateHtml(markup, store.getState(), () => streamId);
39 | res.send(html);
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "lib": ["dom", "es2015", "es2015.promise", "es2016"],
7 | "jsx": "react",
8 | "allowJs": true,
9 | "moduleResolution": "node",
10 | "allowSyntheticDefaultImports": true,
11 | "noUnusedLocals": true,
12 | "noUnusedParameters": true,
13 | "removeComments": false,
14 | "preserveConstEnums": true,
15 | "sourceMap": true,
16 | "skipLibCheck": true,
17 | "baseUrl": "."
18 | },
19 | "files": ["types.d.ts"]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": ["tslint-config-airbnb", "tslint-react", "tslint-config-prettier"],
4 | "jsRules": {},
5 | "rules": {
6 | "variable-name": false,
7 | "function-name": false,
8 | "jsx-no-lambda": false,
9 | "import-name": false
10 | },
11 | "rulesDirectory": []
12 | }
13 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/types.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.ico";
2 |
3 | interface Window {
4 | ___REACT_DEFERRED_COMPONENT_MARKS: any;
5 | __PRELOADED_STATE__: any;
6 | }
7 |
8 | declare var window: Window;
9 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/webpack-client.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 | const {
4 | CheckerPlugin
5 | } = require("awesome-typescript-loader");
6 | const sharedConfig = require("./webpack-shared.config.js");
7 |
8 | module.exports = {
9 | ...sharedConfig(false),
10 | entry: path.join(__dirname, "app", "client.tsx"),
11 | output: {
12 | // publicPath: "/dist",
13 | filename: "client.js",
14 | path: path.resolve(__dirname, "dist", "client"),
15 | chunkFilename: '[name].client.js',
16 | },
17 | optimization: {
18 | runtimeChunk: {
19 | name: 'manifest',
20 | },
21 | },
22 | plugins: [
23 | new HtmlWebpackPlugin({
24 | hash: true,
25 | filename: path.resolve(__dirname, "dist", "client", "index.html"),
26 | template: path.resolve(__dirname, "app", "index.html")
27 | }),
28 | new CheckerPlugin()
29 | ],
30 | devtool: "source-map",
31 | devServer: {
32 | // open: true,
33 | content: path.join("/dist/client/")
34 | // dev: { publicPath: "/dist" }
35 | }
36 | };
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/webpack-server.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const webpack = require("webpack");
3 | const HtmlWebpackPlugin = require("html-webpack-plugin");
4 | const {
5 | CheckerPlugin
6 | } = require("awesome-typescript-loader");
7 | const sharedConfig = require("./webpack-shared.config.js");
8 |
9 | module.exports = {
10 | ...sharedConfig(true),
11 | target: "node",
12 | node: {
13 | __dirname: false,
14 | __filename: false,
15 | fs: "empty",
16 | net: "empty"
17 | },
18 | entry: path.join(__dirname, "server", "index.ts"),
19 | output: {
20 | filename: "index.js",
21 | path: path.resolve(__dirname, "dist", "server")
22 | },
23 | plugins: [new CheckerPlugin()],
24 | devtool: "source-map"
25 | };
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/webpack-shared.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = function (node) {
4 | return {
5 | module: {
6 | rules: [
7 | {
8 | test: /\.js?$/,
9 | use: {
10 | loader: "babel-loader",
11 | },
12 | exclude: /node_modules/
13 | },
14 | {
15 | test: /\.css$/,
16 | use: ['css-loader'],
17 | exclude: /node_modules/
18 | },
19 | {
20 | test: /\.tsx?$/,
21 | use: {
22 | loader: "awesome-typescript-loader",
23 | options: {
24 | silent: process.argv.indexOf("--json") !== -1,
25 | useBabel: true,
26 | babelOptions: {
27 | plugins: node
28 | ? [
29 | "react-imported-component/babel",
30 | "babel-plugin-dynamic-import-node"
31 | ]
32 | : ["react-imported-component/babel"]
33 | }
34 | }
35 | },
36 | exclude: /node_modules/
37 | },
38 | {
39 | test: /\.(jpg|jpeg|gif|png|ico)$/,
40 | exclude: /node_modules/,
41 | loader: "file-loader?name=img/[path][name].[ext]&context=./app/images"
42 | }
43 | ]
44 | },
45 | resolve: {
46 | extensions: [".ts", ".tsx", ".js", ".css"],
47 | alias: {
48 | react: path.resolve(path.join(__dirname, './node_modules/react')),
49 | 'babel-core': path.resolve(
50 | path.join(__dirname, './node_modules/@babel/core'),
51 | ),
52 | },
53 | },
54 | mode: "development"
55 | };
56 | };
57 |
--------------------------------------------------------------------------------
/examples/SSR/runtime-manifest/webpack.config.js:
--------------------------------------------------------------------------------
1 | const clientConfig = require("./webpack-client.config.js");
2 | const serverConfig = require("./webpack-server.config.js");
3 |
4 | module.exports = [clientConfig, serverConfig];
5 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["react-imported-component/babel"]
3 | }
4 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: debian:stretch
6 | steps:
7 | - checkout
8 | - run:
9 | name: Greeting
10 | command: echo Hello, world.
11 | - run:
12 | name: Print the Current Time
13 | command: date
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | .cache
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/README.md:
--------------------------------------------------------------------------------
1 | Simple example with TS, React + imported-components using `renderToString` and `printDrainHydrateMarks`
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #EEE;
3 | }
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Home from "./components/Home";
3 | import importedComponent, {ComponentLoader, loadableResource} from "react-imported-component";
4 |
5 | const Another = importedComponent(() => import(/* webpackChunkName: namedChunk-0 */"./components/Another"), {
6 | LoadingComponent: () => loading
7 | });
8 | const Other1 = importedComponent(() => import(/* webpackChunkName: "namedChunk-1" */"./components/Other"));
9 | const Other2 = importedComponent(() => import(/* webpackChunkName: "namedChunk-1" */"./components/OtherTween"));
10 |
11 | const AnotherWrapped = importedComponent(
12 | () => import(/* webpackChunkName: namedChunk-0 */"./components/Another"), {
13 | render(Component, state, props: { prop: number }) {
14 | if (state.loading) {
15 | return
16 | }
17 | return
18 | }
19 | });
20 | //import Another from "./components/Another";
21 |
22 | // @ts-ignore
23 | // const importCss = () => im port("./App.css");
24 |
25 | export default function App() {
26 | return (
27 |
28 | [not-trackable]
29 |
import("./components/Another")}
31 | />
32 | import("./components/Another"))}
34 | />
35 | [/not-trackable]
36 | [home][/home]
37 |
38 |
39 |
40 |
41 |
42 | {0 && }
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theKashey/react-imported-component/359cb486a049a4151f64c2e5c7b34ad3851cd554/examples/SSR/typescript-react/app/assets/favicon.ico
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/client.tsx:
--------------------------------------------------------------------------------
1 | import { rehydrateMarks } from "react-imported-component";
2 | import * as React from "react";
3 | import ReactDOM from "react-dom";
4 | import { BrowserRouter } from "react-router-dom";
5 | import { Provider } from "react-redux";
6 | import { configureStore } from "./store";
7 | import "./imported"; // eslint-disable-line
8 |
9 | import App from "./App";
10 |
11 | const preloadedState = window.__PRELOADED_STATE__;
12 | delete window.__PRELOADED_STATE__;
13 |
14 | const store = configureStore(preloadedState);
15 |
16 | const element = document.getElementById("app");
17 |
18 | const app = (
19 |
20 |
21 |
22 |
23 |
24 | );
25 |
26 | rehydrateMarks().then(() => {
27 | console.log(element.innerHTML);
28 | ReactDOM.hydrate(app, element);
29 | console.log(element.innerHTML);
30 | });
31 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/components/Another.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Another:React.SFC<{test: number, p2: number}> = () => Another!;
4 | export default Another;
5 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/components/Home.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {Helmet} from "react-helmet";
3 | import {Link} from "react-router-dom";
4 | import imported, {lazy, LazyBoundary, remapImports} from "react-imported-component";
5 |
6 | const LZ = lazy(() => import("./LazyTarget"));
7 |
8 | const ND = imported(
9 | () => remapImports(
10 | import('./NotDefault'),
11 | ({NotDefault}) => NotDefault
12 | )
13 | );
14 |
15 | const Home = () => (
16 |
17 |
18 | Homepage!
19 |
20 |
Link to Another
21 |
Hello!
22 |
23 |
24 |
25 |
26 |
27 | );
28 | export default Home;
29 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/components/LazyTarget.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const LazyTarget: React.SFC = () => I am not lazy Lazy;
4 | export default LazyTarget;
5 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/components/NotDefault.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {useImported} from "react-imported-component"
3 |
4 | export const NotDefault: React.FC<{ p1: number }> = ({p1}) => {
5 | const {
6 | imported = x => `!${x}!`,
7 | } = useImported(() => import("./library"), (lib) => lib.magicFunction);
8 |
9 | return (
10 |
11 | I am not not Default Export({p1} prop == {imported(p1)})
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/components/Other.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const Another:React.SFC<{test: number}> = () => Other!;
4 | export default Another;
5 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/components/OtherTween.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const Another: React.SFC = () => Other Twin!;
4 | export default Another;
5 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/components/library.tsx:
--------------------------------------------------------------------------------
1 | console.log('loading library function');
2 |
3 | export const magicFunction = (x: number) => `** ${x} **`;
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/imported.ts:
--------------------------------------------------------------------------------
1 |
2 | /* eslint-disable */
3 | /* tslint:disable */
4 |
5 | import {assignImportedComponents} from 'react-imported-component/boot';
6 |
7 | const applicationImports = [
8 | () => import('./components/Another'),
9 | () => import('./components/LazyTarget'),
10 | () => import('./components/NotDefault'),
11 | () => import('./components/library'),
12 | () => import(/* webpackChunkName: "namedChunk-1" */'./components/Other'),
13 | () => import(/* webpackChunkName: "namedChunk-1" */'./components/OtherTween'),
14 | ];
15 |
16 | assignImportedComponents(applicationImports);
17 | export default applicationImports;
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from "redux";
2 | import { composeWithDevTools } from "redux-devtools-extension";
3 | import rootReducer from "./reducers";
4 |
5 | export function configureStore(initialState = {}) {
6 | const store = createStore(rootReducer, initialState, composeWithDevTools());
7 |
8 | if (module.hot) {
9 | module.hot.accept("./reducers", () => {
10 | console.log("Replacing reducers...");
11 | store.replaceReducer(require("./reducers").default);
12 | });
13 | }
14 |
15 | return store;
16 | }
17 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/app/store/reducers.ts:
--------------------------------------------------------------------------------
1 | export default function rootReducer(state) {
2 | return state;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "generate-imported-component": "imported-components app app/imported.ts",
4 | "build": "yarn webpack",
5 | "start": "node dist/server/index.js",
6 | "cp:1": "cp -R ../../../dist ./node_modules/react-imported-component/",
7 | "cp:2": "cp ../../../package.json ./node_modules/react-imported-component",
8 | "cp:3": "cp ../../../boot ./node_modules/react-imported-component",
9 | "cp": "yarn cp:1 && yarn cp:2 && yarn cp:3"
10 | },
11 | "dependencies": {
12 | "@types/cheerio": "^0.22.7",
13 | "@types/react": "^16.9.2",
14 | "@types/redux-devtools-extension": "^2.13.2",
15 | "@types/webpack-env": "^1.13.6",
16 | "awesome-typescript-loader": "^5.0.0",
17 | "babel-plugin-dynamic-import-node": "^1.2.0",
18 | "cheerio": "^1.0.0-rc.2",
19 | "compression": "^1.7.2",
20 | "express": "^4.16.3",
21 | "file-loader": "^1.1.11",
22 | "html-webpack-plugin": "^3.2.0",
23 | "react": "^16.9.0",
24 | "react-dom": "^16.9.0",
25 | "react-helmet": "^5.2.0",
26 | "react-imported-component": "^6.1.1",
27 | "react-redux": "^5.0.7",
28 | "react-router-dom": "^4.2.2",
29 | "redux": "^4.0.0",
30 | "redux-devtools-extension": "^2.13.2",
31 | "ts-loader": "^4.2.0",
32 | "typescript": "^2.8.3",
33 | "webpack-serve": "^1.0.2"
34 | },
35 | "devDependencies": {
36 | "babel-core": "^6.21.0",
37 | "babel-loader": "^8.0.5",
38 | "babel-preset-es2015": "^6.18.0",
39 | "css-loader": "^2.1.0",
40 | "style-loader": "^1.0.0",
41 | "webpack": "^4.29.0",
42 | "webpack-cli": "^3.2.1"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/server/generateHtml.ts:
--------------------------------------------------------------------------------
1 | // Generate the HTML using index.html as a template
2 |
3 | import fs from "fs";
4 | import path from "path";
5 | import cheerio from "cheerio";
6 | import { Helmet } from "react-helmet";
7 | import { printDrainHydrateMarks } from "react-imported-component";
8 |
9 | // The path is relative from server bundle to client bundle, not the source
10 | const templatePath = path.join(__dirname, "..", "client", "index.html");
11 | const HTML_TEMPLATE = fs.readFileSync(templatePath).toString();
12 |
13 | export default function generateHtml(markup, state, getStream) {
14 | // Get the serer-rendering values for the
15 | const helmet = Helmet.renderStatic();
16 |
17 | const $template = cheerio.load(HTML_TEMPLATE);
18 | $template("head").append(
19 | helmet.title.toString() + helmet.meta.toString() + helmet.link.toString()
20 | );
21 | $template("head").append(
22 | ``
25 | );
26 | $template("head").append(printDrainHydrateMarks(getStream()));
27 | $template("#app").html(markup);
28 |
29 | return $template.html();
30 | }
31 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/server/index.ts:
--------------------------------------------------------------------------------
1 | import compression from "compression";
2 | import express from "express";
3 | import middleware from "./middleware";
4 |
5 | const app = express();
6 |
7 | // Add gzip compression to responses
8 | app.use(compression());
9 |
10 | // Expose the public directory as /dist and point to the browser version
11 | app.use(express.static(`${__dirname}/../client`, { index: false }));
12 |
13 | // Anything unresolved is serving the application and let
14 | // react-router do the routing!
15 | app.get("/*", middleware);
16 |
17 | // Check for PORT environment variable, otherwise fallback on Parcel default port
18 | const port = process.env.PORT || 1234;
19 | app.listen(port, () => {
20 | console.log(`Listening on port ${port}...`);
21 | });
22 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/server/middleware.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/server";
3 | import {StaticRouter} from "react-router-dom";
4 | import {Provider} from "react-redux";
5 |
6 | import "../app/imported";
7 | import App from "../app/App";
8 | import {configureStore} from "../app/store";
9 |
10 | import generateHtml from "./generateHtml";
11 | import {ImportedStream} from "react-imported-component";
12 | import {createLoadableStream} from "react-imported-component/server";
13 |
14 | const store = configureStore();
15 |
16 | export default (req, res) => {
17 | // Generate the server-rendered HTML using the appropriate router
18 | const context: { url?: string } = {};
19 | let streamId = createLoadableStream();
20 | const router = (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | const markup = ReactDOM.renderToString(router);
30 |
31 | // If react-router is redirecting, do it on the server side
32 | if (context.url) {
33 | res.redirect(301, context.url);
34 | } else {
35 | // Format the HTML using the template and send the result
36 |
37 | // stream is not defined unless we call the render
38 | const html = generateHtml(markup, store.getState(), () => streamId);
39 | res.send(html);
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "lib": ["dom", "es2015", "es2015.promise", "es2016"],
7 | "jsx": "react",
8 | "allowJs": true,
9 | "moduleResolution": "node",
10 | "allowSyntheticDefaultImports": true,
11 | "noUnusedLocals": true,
12 | "noUnusedParameters": true,
13 | "removeComments": false,
14 | "preserveConstEnums": true,
15 | "sourceMap": true,
16 | "skipLibCheck": true,
17 | "baseUrl": "."
18 | },
19 | "files": ["types.d.ts"]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": ["tslint-config-airbnb", "tslint-react", "tslint-config-prettier"],
4 | "jsRules": {},
5 | "rules": {
6 | "variable-name": false,
7 | "function-name": false,
8 | "jsx-no-lambda": false,
9 | "import-name": false
10 | },
11 | "rulesDirectory": []
12 | }
13 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/types.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.ico";
2 |
3 | interface Window {
4 | ___REACT_DEFERRED_COMPONENT_MARKS: any;
5 | __PRELOADED_STATE__: any;
6 | }
7 |
8 | declare var window: Window;
9 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/webpack-client.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 | const {
4 | CheckerPlugin
5 | } = require("awesome-typescript-loader");
6 | const sharedConfig = require("./webpack-shared.config.js");
7 |
8 | module.exports = {
9 | ...sharedConfig(false),
10 | entry: path.join(__dirname, "app", "client.tsx"),
11 | output: {
12 | // publicPath: "/dist",
13 | filename: "client.js",
14 | path: path.resolve(__dirname, "dist", "client"),
15 | chunkFilename: '[name].client.js',
16 | },
17 | plugins: [
18 | new HtmlWebpackPlugin({
19 | hash: true,
20 | filename: path.resolve(__dirname, "dist", "client", "index.html"),
21 | template: path.resolve(__dirname, "app", "index.html")
22 | }),
23 | new CheckerPlugin()
24 | ],
25 | devtool: "source-map",
26 | devServer: {
27 | // open: true,
28 | content: path.join("/dist/client/")
29 | // dev: { publicPath: "/dist" }
30 | }
31 | };
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/webpack-server.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const webpack = require("webpack");
3 | const HtmlWebpackPlugin = require("html-webpack-plugin");
4 | const {
5 | CheckerPlugin
6 | } = require("awesome-typescript-loader");
7 | const sharedConfig = require("./webpack-shared.config.js");
8 |
9 | module.exports = {
10 | ...sharedConfig(true),
11 | target: "node",
12 | node: {
13 | __dirname: false,
14 | __filename: false,
15 | fs: "empty",
16 | net: "empty"
17 | },
18 | entry: path.join(__dirname, "server", "index.ts"),
19 | output: {
20 | filename: "index.js",
21 | path: path.resolve(__dirname, "dist", "server")
22 | },
23 | plugins: [new CheckerPlugin()],
24 | devtool: "source-map"
25 | };
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/webpack-shared.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = function (node) {
4 | return {
5 | module: {
6 | rules: [
7 | {
8 | test: /\.js?$/,
9 | use: {
10 | loader: "babel-loader",
11 | },
12 | exclude: /node_modules/
13 | },
14 | {
15 | test: /\.css$/,
16 | use: ['css-loader'],
17 | exclude: /node_modules/
18 | },
19 | {
20 | test: /\.tsx?$/,
21 | use: {
22 | loader: "awesome-typescript-loader",
23 | options: {
24 | silent: process.argv.indexOf("--json") !== -1,
25 | useBabel: true,
26 | babelOptions: {
27 | plugins: node
28 | ? [
29 | "react-imported-component/babel",
30 | "babel-plugin-dynamic-import-node"
31 | ]
32 | : ["react-imported-component/babel"]
33 | }
34 | }
35 | },
36 | exclude: /node_modules/
37 | },
38 | {
39 | test: /\.(jpg|jpeg|gif|png|ico)$/,
40 | exclude: /node_modules/,
41 | loader: "file-loader?name=img/[path][name].[ext]&context=./app/images"
42 | }
43 | ]
44 | },
45 | resolve: {
46 | extensions: [".ts", ".tsx", ".js", ".css"],
47 | alias: {
48 | react: path.resolve(path.join(__dirname, './node_modules/react')),
49 | 'babel-core': path.resolve(
50 | path.join(__dirname, './node_modules/@babel/core'),
51 | ),
52 | },
53 | },
54 | mode: "development"
55 | };
56 | };
57 |
--------------------------------------------------------------------------------
/examples/SSR/typescript-react/webpack.config.js:
--------------------------------------------------------------------------------
1 | const clientConfig = require("./webpack-client.config.js");
2 | const serverConfig = require("./webpack-server.config.js");
3 |
4 | module.exports = [clientConfig, serverConfig];
5 |
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/preset-react"
5 | ],
6 | "plugins": ["react-imported-component/babel"]
7 | }
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | .cache
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/README.md:
--------------------------------------------------------------------------------
1 | react-snap-example
2 | ====
3 |
4 | Using `react-imported-component` with `react-snap` without a flash of unloaded content.
5 |
6 | working out of the box
7 | ```js
8 | import imported, {rehydrateMarks, drainHydrateMarks} from 'react-imported-component';
9 | // import chunk definition
10 | import './imported-chunk';
11 |
12 | const Component1 = imported(() => import('./splitted-1'));
13 |
14 | // wait for all the used marks to load
15 | rehydrateMarks(window.__IMPORTED_COMPONENTS__).then(() => {
16 | ReactDOM.render(, document.getElementById('app'));
17 | });
18 |
19 | // save used marks
20 | window.snapSaveState = () => ({
21 | "__IMPORTED_COMPONENTS__": drainHydrateMarks(),
22 | });
23 | ```
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-snap",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "build-imported": "imported-components src src/imported-chunk.js",
9 | "build": "rm -rf dist && npm run build-imported && npm run build-client",
10 | "build-client": "parcel build src/index.html -d dist/ --public-url ./ --no-minify --no-cache",
11 | "postbuild": "react-snap"
12 | },
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "react": "^16.8.4",
17 | "react-dom": "^16.8.4",
18 | "react-imported-component": "^5.5.3",
19 | "react-snap": "^1.23.0"
20 | },
21 | "devDependencies": {
22 | "@babel/core": "^7.3.4",
23 | "parcel-bundler": "^1.12.2"
24 | },
25 | "reactSnap": {
26 | "source": "dist"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/src/app.js:
--------------------------------------------------------------------------------
1 | import imported, {rehydrateMarks, drainHydrateMarks} from 'react-imported-component';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | // import chunk definition
5 | import './imported-chunk';
6 |
7 | const Component1 = imported(() => import('./splitted-1'));
8 | const Component2 = imported(() => import('./splitted-2'));
9 |
10 | function hereTime(prefix) {
11 | var perfData = window.performance.timing;
12 | var pageLoadTime = Date.now() - perfData.navigationStart;
13 | console.log(prefix, pageLoadTime);
14 | }
15 |
16 | hereTime('enter');
17 | // wait for all the used marks to load
18 | rehydrateMarks(window.__IMPORTED_COMPONENTS__).then(() => {
19 | hereTime('start');
20 | ReactDOM.render(
21 |
22 | APP
23 |
24 | first component ->
25 |
26 |
27 | second component ->
28 |
29 |
30 | , document.getElementById('app'));
31 | });
32 |
33 | // save used marks
34 | window.snapSaveState = () => ({
35 | "__IMPORTED_COMPONENTS__": drainHydrateMarks(),
36 | });
37 |
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/src/imported-chunk.js:
--------------------------------------------------------------------------------
1 |
2 | /* eslint-disable */
3 | /* tslint:disable */
4 |
5 | import {assignImportedComponents} from 'react-imported-component';
6 |
7 | const applicationImports = [
8 | () => import('./splitted-1'),
9 | () => import('./splitted-2'),
10 | () => import('./splitted-3'),
11 | ];
12 |
13 | assignImportedComponents(applicationImports);
14 | export default applicationImports;
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/src/main.js:
--------------------------------------------------------------------------------
1 | import {rehydrateMarks} from 'react-imported-component/dist/es2015/marks';
2 | import './imported-chunk';
3 | import './app';
4 |
5 | function hereTime(prefix) {
6 | var perfData = window.performance.timing;
7 | var pageLoadTime = Date.now() - perfData.navigationStart;
8 | console.log(prefix, pageLoadTime);
9 | }
10 |
11 | hereTime('root');
12 |
13 | rehydrateMarks(window.__IMPORTED_COMPONENTS__).then(() => hereTime('chunks loaded'));
14 |
15 |
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/src/splitted-1.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default () => This is code - splitted component1
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/src/splitted-2.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {lazy} from "react-imported-component";
3 |
4 | const Component3 = lazy(() => import('./splitted-3'));
5 |
6 | export default () => (
7 |
8 | This is code - splitted component 2, with
9 | -
10 |
11 | -
12 | inside Suspense
13 |
14 | )
--------------------------------------------------------------------------------
/examples/hybrid/react-snap/src/splitted-3.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default () => Component3
--------------------------------------------------------------------------------
/examples/react-hot-loader/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env",
4 | "react"
5 | ],
6 | "plugins": [
7 | "react-hot-loader/babel",
8 | "transform-class-properties",
9 | "syntax-dynamic-import",
10 | "react-imported-component/babel"
11 | // "dynamic-import-node"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 |
5 | [libs]
6 |
7 | [lints]
8 |
9 | [options]
10 | include_warnings=true
11 |
12 | [strict]
13 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hot-async-portals",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "start": "NODE_ENV=development webpack-dev-server --hot"
7 | },
8 | "devDependencies": {
9 | "babel-core": "^6.26.0",
10 | "babel-loader": "^8.0.5",
11 | "babel-plugin-transform-class-properties": "^6.24.1",
12 | "babel-preset-env": "^1.6.1",
13 | "babel-preset-react": "^6.24.1",
14 | "flow-bin": "^0.110.0",
15 | "html-webpack-plugin": "^3.2.0",
16 | "webpack": "^4.21.0",
17 | "webpack-cli": "^3.1.2",
18 | "webpack-dev-server": "^3.1.9"
19 | },
20 | "dependencies": {
21 | "react": "^16.9.0",
22 | "react-dom": "^16.9.0",
23 | "react-hot-loader": "^4.12.12",
24 | "react-imported-component": "^6.1.1",
25 | "react-portal": "^4.1.2"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/App.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import {hot, setConfig} from 'react-hot-loader'
3 | import * as React from 'react'
4 | import Counter from './Counter'
5 | import imported, {lazy, ComponentLoader, printDrainHydrateMarks} from 'react-imported-component'
6 | import Portal from './Portal'
7 | import Indirect from './indirectUsage';
8 |
9 | imported(() => import(/* webpackChunkName: "namedChunk-1" */'./DeferredRender'), {
10 | async: true
11 | });
12 |
13 | const Async = imported(() => import(/* webpackChunkName: "namedChunk-1" */'./DeferredRender'));
14 | const Async2 = lazy(() => {
15 | console.log('loading lazy');
16 | return import(/* webpackChunkName: "namedChunk-2" */'./Lazy')
17 | });
18 | const ShouldNotBeImported = imported(() => import(/* webpackChunkName: "namedChunk-2" */'./NotImported'));
19 |
20 | const App = () => (
21 |
22 |
Component loaded
23 | import(/* webpackChunkName: "namedChunk-1" */'./DeferredRender')}
25 | />
26 | test!
27 |
28 | C:
29 |
30 | Imported:
31 |
32 |
33 | Lazy:
34 |
35 |
36 | Portal:
37 |
38 |
39 | {Date.now() < 0 && }
40 |
41 | )
42 |
43 | setConfig({logLevel: 'debug'})
44 |
45 | setTimeout(() => console.log('marks', printDrainHydrateMarks()), 1000);
46 |
47 | export default hot(module)(App)
48 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/Counter.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // const RAND = Math.round(Math.random() * 1000)
4 |
5 | class Counter extends React.Component {
6 | state = { count: Math.round(Math.random() * 1000) };
7 | gen = 0;
8 |
9 | render() {
10 | // gen should change. count - no.
11 | return (
12 |
13 | state({this.state.count}):rerender({this.gen++})
14 |
15 | )
16 | }
17 | }
18 |
19 | export default Counter
20 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/DeferredRender.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import imported, {remapImports} from 'react-imported-component';
3 | import {Portal} from 'react-portal'
4 | import hidden from './HiddenComponent'
5 |
6 | const V = 2;
7 | console.log('run ', V);
8 | const SubAsync = imported(() => {
9 | console.log('import ', V);
10 | return remapImports(import('./SubAsync'), x => x.SubAsync)
11 | });
12 |
13 | const Hidden = hidden();
14 |
15 | const APortal = () => (
16 |
17 | This is a async portal
18 |
19 |
20 | );
21 |
22 | export default () => (
23 |
24 | ASYNC 77
25 |
26 | and
27 | +
28 |
29 | );
30 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/HiddenComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Counter from './Counter'
3 |
4 | const hidden = function() {
5 | return {
6 | counter: () => (
7 |
8 | this is hidden counter()
9 |
10 | ),
11 | }
12 | }
13 |
14 | export default hidden
15 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/Lazy.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import hidden from './HiddenComponent'
3 |
4 | const Hidden = hidden();
5 |
6 | const N = 240;
7 | console.log('execute lazy', N);
8 |
9 | export default () => (
10 |
11 | Lazy {N}
12 |
13 |
14 | )
15 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/NotImported.js:
--------------------------------------------------------------------------------
1 | alert('this file should not be imported!');
2 | debugger;
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/Portal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Portal } from 'react-portal'
3 | import hidden from './HiddenComponent'
4 |
5 | const Hidden = hidden()
6 |
7 | const InPortal = ({ children }) => + {children}
8 |
9 | export default () => (
10 |
11 |
12 | This is a first portal
13 |
14 |
15 |
16 | )
17 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/SubAsync.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | console.log('sub async 4');
4 |
5 | export const SubAsync = () => (
6 |
7 | Async In Async!
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import App from './App'
4 |
5 | const root = document.createElement('div')
6 | document.body.appendChild(root)
7 |
8 | render(, root)
9 |
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/indirect.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import importedComponent from 'react-imported-component'
3 | import Counter from './Counter'
4 |
5 | const DefaultLoading = () => ;
6 |
7 | export const makeSplitPoint = fn => importedComponent(fn, {LoadingComponent: DefaultLoading})
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/indirectTarget.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default () => Indirect Target
--------------------------------------------------------------------------------
/examples/react-hot-loader/src/indirectUsage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {makeSplitPoint} from './indirect'
3 |
4 | const AsyncComponent = makeSplitPoint(() => import('./indirectTarget'))
5 |
6 | export default () => ;
--------------------------------------------------------------------------------
/examples/react-hot-loader/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | const HtmlWebpackPlugin = require('html-webpack-plugin')
5 |
6 | module.exports = {
7 | entry: ['./src/index'],
8 | mode: "development",
9 | output: {
10 | path: path.join(__dirname, 'dist'),
11 | filename: 'bundle.js',
12 | chunkFilename: '[name].client.js',
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.js$/,
18 | include: /node_modules\/react-dom/,
19 | use: ['react-hot-loader/webpack'],
20 | },
21 | {
22 | exclude: /node_modules|packages/, // should work without exclude
23 | test: /\.js$/,
24 | use: 'babel-loader',
25 | },
26 | ],
27 | },
28 | resolve: {
29 | extensions: ['.ts', '.tsx', '.js', '.jsx'],
30 | alias: {
31 | react: path.resolve(path.join(__dirname, './node_modules/react')),
32 | 'babel-core': path.resolve(
33 | path.join(__dirname, './node_modules/@babel/core'),
34 | ),
35 | },
36 | },
37 | plugins: [new HtmlWebpackPlugin(), new webpack.NamedModulesPlugin()],
38 | }
39 |
--------------------------------------------------------------------------------
/greenkeeper.json:
--------------------------------------------------------------------------------
1 | {
2 | "groups": {
3 | "default": {
4 | "packages": [
5 | "examples/SSR/parcel-react-ssr/package.json",
6 | "examples/SSR/typescript-react/package.json",
7 | "examples/react-hot-loader/package.json",
8 | "package.json"
9 | ]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testMatch: ['**/__tests__/*.spec.(ts|tsx)'],
4 | };
5 |
--------------------------------------------------------------------------------
/macro/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "main": "../dist/es5/entrypoints/macro.js",
4 | "jsnext:main": "../dist/es2015/entrypoints/macro.js",
5 | "module": "../dist/es2015/entrypoints/macro.js",
6 | "types": "../dist/es2015/entrypoints/macro.d.ts"
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-imported-component",
3 | "version": "6.5.4",
4 | "description": "I will import your component, and help to handle it",
5 | "main": "dist/es5/entrypoints/index.js",
6 | "jsnext:main": "dist/es2015/entrypoints/index.js",
7 | "module": "dist/es2015/entrypoints/index.js",
8 | "module:es2019": "dist/es2019/entrypoints/index.js",
9 | "types": "dist/es5/entrypoints/index.d.ts",
10 | "sideEffects": false,
11 | "scripts": {
12 | "build:ci": "lib-builder build && yarn size",
13 | "build": "lib-builder build && yarn size && yarn size:report",
14 | "test": "jest",
15 | "prepublish": "yarn build && yarn changelog",
16 | "lint": "lib-builder lint",
17 | "dev": "lib-builder dev",
18 | "test:ci": "jest --runInBand --coverage",
19 | "release": "yarn build && yarn test",
20 | "format": "lib-builder format",
21 | "size": "npx size-limit",
22 | "size:report": "npx size-limit --json > .size.json",
23 | "update": "lib-builder update",
24 | "docz:dev": "docz dev",
25 | "docz:build": "docz build",
26 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
27 | "changelog:rewrite": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
28 | },
29 | "repository": {
30 | "type": "git",
31 | "url": "git+https://github.com/theKashey/react-hot-component-loader.git"
32 | },
33 | "bin": {
34 | "imported-components": "./bin/imported-components"
35 | },
36 | "keywords": [
37 | "react-hot-loader",
38 | "loader",
39 | "import",
40 | "async",
41 | "ssr",
42 | "code splitting",
43 | "hmr"
44 | ],
45 | "peerDependencies": {
46 | "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
47 | "react": "^16.9.0 || ^17.0.0 || ^18.0.0"
48 | },
49 | "peerDependenciesMeta": {
50 | "@types/react": {
51 | "optional": true
52 | }
53 | },
54 | "author": "theKashey ",
55 | "license": "MIT",
56 | "bugs": {
57 | "url": "https://github.com/theKashey/react-hot-component-loader/issues"
58 | },
59 | "homepage": "https://github.com/theKashey/react-hot-component-loader#readme",
60 | "devDependencies": {
61 | "@theuiteam/lib-builder": "^0.1.4",
62 | "@size-limit/preset-small-lib": "^4.5.1",
63 | "@babel/core": "^7.5.5",
64 | "@babel/preset-react": "^7.0.0",
65 | "@babel/runtime": "^7.3.1",
66 | "@babel/plugin-syntax-dynamic-import": "^7.5.5",
67 | "babel-plugin-dynamic-import-node": "^2.3.0",
68 | "babel-plugin-tester": "^7.0.1"
69 | },
70 | "dependencies": {
71 | "babel-plugin-macros": "^2.6.1",
72 | "crc-32": "^1.2.0",
73 | "detect-node-es": "^1.0.0",
74 | "scan-directory": "^2.0.0",
75 | "tslib": "^2.0.0"
76 | },
77 | "engines": {
78 | "node": ">=10"
79 | },
80 | "files": [
81 | "bin",
82 | "dist",
83 | "boot",
84 | "server",
85 | "macro",
86 | "babel.js",
87 | "wrapper.js"
88 | ],
89 | "husky": {
90 | "hooks": {
91 | "pre-commit": "lint-staged"
92 | }
93 | },
94 | "lint-staged": {
95 | "*.{ts,tsx}": [
96 | "prettier --write",
97 | "eslint --fix",
98 | "git add"
99 | ],
100 | "*.{js,css,json,md}": [
101 | "prettier --write",
102 | "git add"
103 | ]
104 | },
105 | "prettier": {
106 | "printWidth": 120,
107 | "trailingComma": "es5",
108 | "tabWidth": 2,
109 | "semi": true,
110 | "singleQuote": true
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "main": "../dist/es5/entrypoints/server.js",
4 | "jsnext:main": "../dist/es2015/entrypoints/server.js",
5 | "module": "../dist/es2015/entrypoints/server.js",
6 | "types": "../dist/es2015/entrypoints/server.d.ts"
7 | }
8 |
--------------------------------------------------------------------------------
/src/babel/babel.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import { existsSync } from 'fs';
3 | import { dirname, join, relative, resolve } from 'path';
4 |
5 | import * as crc32 from 'crc-32';
6 |
7 | import { ImportedConfiguration } from '../configuration/configuration';
8 | import { processComment } from './magic-comments';
9 |
10 | export const encipherImport = (str: string) => {
11 | return crc32.str(str).toString(32);
12 | };
13 |
14 | // Babel v7 compat
15 | let syntax: any;
16 |
17 | try {
18 | syntax = require('babel-plugin-syntax-dynamic-import');
19 | } catch (err) {
20 | try {
21 | syntax = require('@babel/plugin-syntax-dynamic-import');
22 | } catch (e) {
23 | throw new Error(
24 | 'react-imported-component babel plugin is requiring `babel-plugin-syntax-dynamic-import` or `@babel/plugin-syntax-dynamic-import` to work. Please add this dependency.'
25 | );
26 | }
27 | }
28 |
29 | syntax = syntax.default || syntax;
30 |
31 | const resolveImport = (importName: string, file = '') => {
32 | if (importName.charAt(0) === '.') {
33 | return relative(process.cwd(), resolve(dirname(file), importName));
34 | }
35 |
36 | return importName;
37 | };
38 |
39 | const templateOptions = {
40 | placeholderPattern: /^([A-Z0-9]+)([A-Z0-9_]+)$/,
41 | };
42 |
43 | function getImportArg(callPath: any) {
44 | return callPath.get('arguments.0');
45 | }
46 |
47 | function getComments(callPath: any) {
48 | return callPath.has('leadingComments') ? callPath.get('leadingComments') : [];
49 | }
50 |
51 | // load configuration
52 | const configurationFile = join(process.cwd(), '.imported.js');
53 | const defaultConfiguration: ImportedConfiguration = (
54 | existsSync(configurationFile) ? require(configurationFile) : {}
55 | ) as ImportedConfiguration;
56 |
57 | export const createTransformer = (
58 | { types: t, template }: any,
59 | excludeMacro = false,
60 | configuration = defaultConfiguration
61 | ) => {
62 | const headerTemplate = template(
63 | `var importedWrapper = require('react-imported-component/wrapper');`,
64 | templateOptions
65 | );
66 |
67 | const importRegistration = template('importedWrapper(MARK, IMPORT)', templateOptions);
68 |
69 | const hasImports = new Set();
70 | const visitedNodes = new Map();
71 |
72 | return {
73 | traverse(programPath: any, fileName: string) {
74 | let isBootstrapFile = false;
75 |
76 | programPath.traverse({
77 | ImportDeclaration(path: any) {
78 | if (excludeMacro) {
79 | return;
80 | }
81 |
82 | const source = path.node.source.value;
83 |
84 | if (source === 'react-imported-component/macro') {
85 | const { specifiers } = path.node;
86 | path.remove();
87 |
88 | const assignName = 'assignImportedComponents';
89 |
90 | if (specifiers.length === 1 && specifiers[0].imported.name === assignName) {
91 | isBootstrapFile = true;
92 |
93 | programPath.node.body.unshift(
94 | t.importDeclaration(
95 | [t.importSpecifier(t.identifier(assignName), t.identifier(assignName))],
96 | t.stringLiteral('react-imported-component/boot')
97 | )
98 | );
99 | } else {
100 | programPath.node.body.unshift(
101 | t.importDeclaration(
102 | specifiers.map((spec: any) =>
103 | t.importSpecifier(t.identifier(spec.imported.name), t.identifier(spec.imported.name))
104 | ),
105 | t.stringLiteral('react-imported-component')
106 | )
107 | );
108 | }
109 | }
110 | },
111 | Import({ parentPath }: any) {
112 | if (visitedNodes.has(parentPath.node)) {
113 | return;
114 | }
115 |
116 | const newImport = parentPath.node;
117 | const rawImport = getImportArg(parentPath);
118 | const importName = rawImport.node.value;
119 | const rawComments = getComments(rawImport);
120 | const comments = rawComments.map((parent: any) => parent.node.value);
121 |
122 | const newComments = processComment(configuration, comments, importName, fileName, {
123 | isBootstrapFile,
124 | });
125 |
126 | if (newComments !== comments) {
127 | rawComments.forEach((comment: any) => comment.remove());
128 |
129 | newComments.forEach((comment: string) => {
130 | rawImport.addComment('leading', ` ${comment} `);
131 | });
132 | }
133 |
134 | if (!importName) {
135 | return;
136 | }
137 |
138 | const requiredFileHash = encipherImport(resolveImport(importName, fileName));
139 |
140 | let replace = null;
141 |
142 | replace = importRegistration({
143 | MARK: t.stringLiteral(`imported_${requiredFileHash}_component`),
144 | IMPORT: newImport,
145 | });
146 |
147 | hasImports.add(fileName);
148 | visitedNodes.set(newImport, true);
149 |
150 | parentPath.replaceWith(replace);
151 | },
152 | });
153 | },
154 |
155 | finish(node: any, filename: string) {
156 | if (!hasImports.has(filename)) {
157 | return;
158 | }
159 |
160 | node.body.unshift(headerTemplate());
161 | },
162 |
163 | hasImports,
164 | };
165 | };
166 |
167 | export const babelPlugin = (babel: any, options: ImportedConfiguration = {}) => {
168 | const transformer = createTransformer(babel, false, {
169 | ...defaultConfiguration,
170 | ...options,
171 | });
172 |
173 | return {
174 | inherits: syntax,
175 |
176 | visitor: {
177 | Program: {
178 | enter(programPath: any, { file }: any) {
179 | transformer.traverse(programPath, file.opts.filename);
180 | },
181 |
182 | exit({ node }: any, { file }: any) {
183 | transformer.finish(node, file.opts.filename);
184 | },
185 | },
186 | },
187 | };
188 | };
189 |
--------------------------------------------------------------------------------
/src/babel/magic-comments.ts:
--------------------------------------------------------------------------------
1 | import { ImportedConfiguration, KnownImportOptions } from '../configuration/configuration';
2 | import { commentsToConfiguration } from './utils';
3 |
4 | const preservePrefetch = (_: any, __: any, options: KnownImportOptions) => !!options.webpackPrefetch;
5 | const preservePreload = (_: any, __: any, options: KnownImportOptions) => !!options.webpackPreload;
6 | const preserveChunkName = (_: any, __: any, options: KnownImportOptions) =>
7 | options.webpackChunkName || options.chunkName;
8 |
9 | const chunkComment = (chunk: string) => `webpackChunkName: "${chunk}"`;
10 | const preloadComment = () => `webpackPreload: true`;
11 | const prefetchComment = () => `webpackPrefetch: true`;
12 |
13 | const knownMagics = ['webpackChunkName', 'webpackPrefetch', 'webpackPreload'];
14 |
15 | const toComments = >(conf: T): string[] =>
16 | (Object.keys(conf) as Array)
17 | .filter((key) => !knownMagics.includes(key as any))
18 | .reduce((acc, key) => {
19 | acc.concat(`${key}:${JSON.stringify(conf[key])}`);
20 |
21 | return acc;
22 | }, [] as string[]);
23 |
24 | const nullish = (a: T, b: T): T => {
25 | if (a === undefined) {
26 | return b;
27 | }
28 |
29 | return a;
30 | };
31 |
32 | export const processComment = (
33 | configuration: ImportedConfiguration,
34 | comments: string[],
35 | importName: string,
36 | fileName: string,
37 | options: {
38 | isBootstrapFile: boolean;
39 | }
40 | ): string[] => {
41 | const {
42 | shouldPrefetch = preservePrefetch,
43 | shouldPreload = preservePreload,
44 | chunkName = preserveChunkName,
45 | } = configuration;
46 |
47 | const importConfiguration = commentsToConfiguration(comments);
48 |
49 | const newChunkName = nullish(
50 | chunkName(importName, fileName, importConfiguration),
51 | preserveChunkName(importName, fileName, importConfiguration)
52 | );
53 | const { isBootstrapFile } = options;
54 |
55 | return [
56 | ...toComments(importConfiguration),
57 | !isBootstrapFile && shouldPrefetch(importName, fileName, importConfiguration) ? prefetchComment() : '',
58 | !isBootstrapFile && shouldPreload(importName, fileName, importConfiguration) ? preloadComment() : '',
59 | newChunkName ? chunkComment(newChunkName) : '',
60 | ].filter((x) => !!x);
61 | };
62 |
--------------------------------------------------------------------------------
/src/babel/utils.ts:
--------------------------------------------------------------------------------
1 | import vm from 'vm';
2 |
3 | import { CLIENT_SIDE_ONLY } from '../configuration/constants';
4 |
5 | const parseMagicComments = (str: string): Record => {
6 | if (str.trim() === CLIENT_SIDE_ONLY) {
7 | return {};
8 | }
9 |
10 | try {
11 | const values = vm.runInNewContext(`(function(){return {${str}};})()`);
12 |
13 | return values;
14 | } catch (e) {
15 | return {};
16 | }
17 | };
18 |
19 | export const commentsToConfiguration = (comments: any[]): Record =>
20 | comments.reduce(
21 | (acc, comment) => ({
22 | ...acc,
23 | ...parseMagicComments(comment),
24 | }),
25 | {} as any
26 | );
27 |
--------------------------------------------------------------------------------
/src/configuration/config.ts:
--------------------------------------------------------------------------------
1 | import { isBackend } from '../utils/detectBackend';
2 |
3 | const rejectNetwork = (url: string): boolean => url.indexOf('http') !== 0;
4 |
5 | /**
6 | * client-side only imported settings
7 | */
8 | export interface ImportedClientSettings {
9 | /**
10 | * enabled hot module replacement
11 | * @autoconfig enabled if HMR is detected
12 | */
13 | hot: boolean;
14 | /**
15 | * Sets SSR mode
16 | * @autoconfig autodetects environment
17 | */
18 | SSR: boolean;
19 | /**
20 | * rethrows errors from loading
21 | * @autoconfig enabled in development
22 | */
23 | rethrowErrors: boolean;
24 | /**
25 | * Controls which imports should be controlled via imported
26 | * @default - everything non http
27 | */
28 | fileFilter: (url: string) => boolean;
29 | /**
30 | * Controls import signature matching
31 | * - true(default): checks signatures
32 | * - false: uses "marks"(file names) only
33 | */
34 | checkSignatures: boolean;
35 | }
36 |
37 | const localSettings: ImportedClientSettings = {
38 | hot: typeof module !== 'undefined' && (!!module as any).hot,
39 | SSR: isBackend,
40 | rethrowErrors: process.env.NODE_ENV !== 'production',
41 | fileFilter: rejectNetwork,
42 | checkSignatures: true,
43 | };
44 |
45 | export const settings: ImportedClientSettings = {
46 | get hot() {
47 | return localSettings.hot;
48 | },
49 | get SSR() {
50 | return localSettings.SSR;
51 | },
52 | get rethrowErrors() {
53 | return localSettings.rethrowErrors;
54 | },
55 | get fileFilter() {
56 | return localSettings.fileFilter;
57 | },
58 | get checkSignatures() {
59 | return localSettings.checkSignatures;
60 | },
61 | };
62 |
63 | /**
64 | * allows fine tune imported logic
65 | * client side only!
66 | * @internal
67 | * @see configuration via imported.json {@link https://github.com/theKashey/react-imported-component#importedjs}
68 | */
69 | export const setConfiguration = (config: Partial): void => {
70 | Object.assign(localSettings, config);
71 | };
72 |
--------------------------------------------------------------------------------
/src/configuration/configuration.ts:
--------------------------------------------------------------------------------
1 | import { ImportedClientSettings } from './config';
2 |
3 | export interface KnownImportOptions {
4 | chunkName?: string;
5 | webpackChunkName?: string;
6 |
7 | webpackPreload?: boolean;
8 | webpackPrefetch?: boolean;
9 | }
10 |
11 | type ImportOptions = KnownImportOptions | Record;
12 |
13 | /**
14 | * @name ImportedConfiguration
15 | * react-imported-component configuration
16 | * __TO BE USED AT `imported.js`__
17 | * @see {@link https://github.com/theKashey/react-imported-component#-imported-js}
18 | */
19 | export interface ImportedConfiguration {
20 | /**
21 | * tests folder during scanning process. Can be used to optimize scanning process.
22 | * @default ignores `node_modules` and `.*` directories
23 | * @returns boolean flag
24 | * - true, dive in
25 | * - false, stop here
26 | */
27 | testFolder?: (targetName: string) => boolean;
28 | /**
29 | * tests if this file should scanned by `imported-component`.
30 | * Keep in mind that you might consider removing (unit)test files from the scan
31 | * @param fileName - source file name
32 | * @returns {Boolean} true - if should, false - is should not
33 | * @example
34 | * // hides node modules
35 | * testFile(filename) { return !filename.test(/node_modules/); }
36 | */
37 | testFile?: (fileName: string) => boolean;
38 | /**
39 | * tests if a given import should be visible to a `imported-component`
40 | * This method is equivalent to `client-side` magic comment
41 | * @param {String} targetFileName - import target
42 | * @param {String} sourceFileName - source filename
43 | * @returns {Boolean} false if import should be ignored by the `imported-components`
44 | * @see {@link https://github.com/theKashey/react-imported-component/#server-side-auto-import}
45 | * @example
46 | * //a.js -> source
47 | * import('./b.js) -> target
48 | * @example
49 | * testImport(filename, source, config) {
50 | * return !(
51 | * // no mjs please
52 | * filename.indexOf('.mjs')===-1
53 | * // no webpack-ignore please (don't do it)
54 | * config.webpackIgnore
55 | * )
56 | * }
57 | */
58 | testImport?: (targetFileName: string, sourceFileName: string) => boolean;
59 | /**
60 | * marks import with prefetch comment (if possible)
61 | * @param {String} targetFile
62 | * @param {String} sourceFile
63 | * @param sourceConfiguration
64 | */
65 | shouldPrefetch?: (targetFile: string, sourceFile: string, sourceConfiguration: ImportOptions) => boolean;
66 | /**
67 | * marks import with preload comment (if possible)
68 | * @param {String} targetFile
69 | * @param {String} sourceFile
70 | * @param sourceConfiguration
71 | */
72 | shouldPreload?: (targetFile: string, sourceFile: string, sourceConfiguration: ImportOptions) => boolean;
73 | /**
74 | * adds custom chunkname to a import (if possible)
75 | * @param {String} targetFile
76 | * @param {String} sourceFile
77 | * @param {String|undefined} givenChunkName
78 | * @returns
79 | * {string} - a new chunk name
80 | * {undefined} - keep as is
81 | * {null} - keep as is (will remove in the future)
82 | */
83 | chunkName?: (targetFile: string, sourceFile: string, importOptions: ImportOptions) => string | null | undefined;
84 |
85 | /**
86 | * clientside configuration properties to be passed into `setConfiguration`
87 | */
88 | configuration?: Partial;
89 | }
90 |
91 | /**
92 | * provides react-imported-component configuration
93 | * @param {ImportedConfiguration} config
94 | */
95 | export const configure = (config: ImportedConfiguration): ImportedConfiguration => config;
96 |
--------------------------------------------------------------------------------
/src/configuration/constants.ts:
--------------------------------------------------------------------------------
1 | export const CLIENT_SIDE_ONLY = 'client-side';
2 |
--------------------------------------------------------------------------------
/src/entrypoints/babel.ts:
--------------------------------------------------------------------------------
1 | import { babelPlugin } from '../babel/babel';
2 |
3 | export default babelPlugin;
4 |
--------------------------------------------------------------------------------
/src/entrypoints/boot.ts:
--------------------------------------------------------------------------------
1 | import { setConfiguration } from '../configuration/config';
2 | import { assignImportedComponents } from '../loadable/assignImportedComponents';
3 | import { loadByChunkname } from '../loadable/loadByChunkName';
4 | import { rehydrateMarks } from '../loadable/marks';
5 | import { done as whenComponentsReady } from '../loadable/pending';
6 | import { addPreloader } from '../loadable/preloaders';
7 | import { injectLoadableTracker } from '../trackers/globalTracker';
8 |
9 | export {
10 | rehydrateMarks,
11 | whenComponentsReady,
12 | assignImportedComponents,
13 | loadByChunkname,
14 | setConfiguration,
15 | injectLoadableTracker,
16 | addPreloader,
17 | };
18 |
--------------------------------------------------------------------------------
/src/entrypoints/index.ts:
--------------------------------------------------------------------------------
1 | import { setConfiguration } from '../configuration/config';
2 | import { configure, ImportedConfiguration } from '../configuration/configuration';
3 | import { assignImportedComponents } from '../loadable/assignImportedComponents';
4 | import { loadByChunkname } from '../loadable/loadByChunkName';
5 | import { clearImportedCache, dryRender, getLoadable as loadableResource } from '../loadable/loadable';
6 | import { getMarkedChunks, getMarkedFileNames } from '../loadable/markerMapper';
7 | import { drainHydrateMarks, printDrainHydrateMarks, rehydrateMarks, waitForMarks } from '../loadable/marks';
8 | import { done as whenComponentsReady } from '../loadable/pending';
9 | import { addPreloader } from '../loadable/preloaders';
10 | import { DefaultImport } from '../types';
11 | import { ImportedComponent } from '../ui/Component';
12 | import { ImportedComponent as ComponentLoader } from '../ui/Component';
13 | import imported, { lazy } from '../ui/HOC';
14 | import { ImportedController } from '../ui/ImportedController';
15 | import { LazyBoundary } from '../ui/LazyBoundary';
16 | import { ImportedModule, importedModule } from '../ui/Module';
17 | import { ImportedStream } from '../ui/context';
18 | import { useImported, useLazy, useLoadable } from '../ui/useImported';
19 | import { remapImports } from '../utils/helpers';
20 | import { useIsClientPhase } from '../utils/useClientPhase';
21 |
22 | export {
23 | printDrainHydrateMarks,
24 | drainHydrateMarks,
25 | rehydrateMarks,
26 | waitForMarks,
27 | whenComponentsReady,
28 | dryRender,
29 | assignImportedComponents,
30 | loadByChunkname,
31 | ComponentLoader,
32 | ImportedComponent,
33 | ImportedModule,
34 | loadableResource,
35 | ImportedStream,
36 | setConfiguration,
37 | imported,
38 | importedModule,
39 | lazy,
40 | LazyBoundary,
41 | ImportedController,
42 | useIsClientPhase,
43 | remapImports,
44 | useLoadable,
45 | useImported,
46 | useLazy,
47 | addPreloader,
48 | getMarkedChunks,
49 | getMarkedFileNames,
50 | clearImportedCache,
51 | ImportedConfiguration,
52 | configure,
53 | DefaultImport,
54 | };
55 | export default imported;
56 |
--------------------------------------------------------------------------------
/src/entrypoints/macro.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import { createMacro } from 'babel-plugin-macros';
3 |
4 | import { createTransformer } from '../babel/babel';
5 | import { assignImportedComponents } from '../loadable/assignImportedComponents';
6 |
7 | function getMacroType(tagName: string) {
8 | switch (tagName) {
9 | case 'importedModule':
10 | case 'imported':
11 | case 'lazy':
12 | case 'useImported':
13 |
14 | case 'ImportedModule':
15 | case 'ImportedComponent':
16 | return 'react-imported-component';
17 |
18 | case 'assignImportedComponents':
19 | return 'react-imported-component/boot';
20 |
21 | default:
22 | return false;
23 | }
24 | }
25 |
26 | function macro({ references, state, babel }: any) {
27 | const { types: t } = babel;
28 | const fileName = state.file.opts.filename;
29 |
30 | const imports: Record = {};
31 | const transformer = createTransformer(babel, true);
32 |
33 | Object.keys(references).forEach((tagName: string) => {
34 | const origin = getMacroType(tagName);
35 |
36 | if (origin) {
37 | imports[origin] = imports[origin] || [];
38 | imports[origin].push(tagName);
39 |
40 | const tags = references[tagName];
41 |
42 | tags.forEach((tag: any) => {
43 | const expression = tag.parentPath;
44 |
45 | if (t.isCallExpression(expression)) {
46 | transformer.traverse(expression, fileName);
47 | }
48 | });
49 | }
50 | });
51 |
52 | addReactImports(babel, state, imports);
53 | transformer.traverse(state.file.path, fileName);
54 | transformer.finish(state.file.path.node, fileName);
55 | }
56 |
57 | function addReactImports(babel: any, state: any, importsGroups: Record) {
58 | const { types: t } = babel;
59 |
60 | return Object.keys(importsGroups)
61 | .map((origin) => {
62 | const imports = importsGroups[origin];
63 |
64 | if (!imports.length) {
65 | return false;
66 | }
67 |
68 | const importedImport = state.file.path.node.body.find(
69 | (importNode: any) => t.isImportDeclaration(importNode) && importNode.source.value === origin
70 | );
71 |
72 | // Handle adding the import or altering the existing import
73 | if (importedImport) {
74 | imports.forEach((name) => {
75 | if (
76 | !importedImport.specifiers.find((specifier: any) => specifier.imported && specifier.imported.name === name)
77 | ) {
78 | importedImport.specifiers.push(t.importSpecifier(t.identifier(name), t.identifier(name)));
79 | }
80 | });
81 | } else {
82 | state.file.path.node.body.unshift(
83 | t.importDeclaration(
84 | imports.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name))),
85 | t.stringLiteral(origin)
86 | )
87 | );
88 | }
89 |
90 | return true;
91 | })
92 | .some(Boolean);
93 | }
94 |
95 | const neverCallMe: any = () => {
96 | throw new Error(
97 | 'you have used `react-imported-component/macro` without `babel-plugin-macro` or `react-hot-loader/babel` installed'
98 | );
99 | };
100 |
101 | const lazy: typeof import('../ui/HOC').lazy = neverCallMe;
102 | const imported: typeof import('../ui/HOC').default = neverCallMe;
103 | const importedModule: typeof import('../ui/Module').importedModule = neverCallMe;
104 | const useImported: typeof import('../ui/useImported').useImported = neverCallMe;
105 |
106 | const ImportedModule: typeof import('../ui/Module').ImportedModule = neverCallMe;
107 | const ImportedComponent: typeof import('../ui/Component').ImportedComponent = neverCallMe;
108 |
109 | export { lazy, imported, importedModule, ImportedModule, ImportedComponent, useImported, assignImportedComponents };
110 |
111 | export default createMacro(macro);
112 |
--------------------------------------------------------------------------------
/src/entrypoints/server.ts:
--------------------------------------------------------------------------------
1 | import { setConfiguration } from '../configuration/config';
2 | import { getMarkedChunks, getMarkedFileNames } from '../loadable/markerMapper';
3 | import { drainHydrateMarks, printDrainHydrateMarks } from '../loadable/marks';
4 | import { createLoadableStream } from '../loadable/stream';
5 | import { getLoadableTrackerCallback } from '../trackers/globalTracker';
6 | import { createLoadableTransformer } from '../transformers/loadableTransformer';
7 | import { Stream as ImportedStreamTracker } from '../types';
8 | import { ImportedStream } from '../ui/context';
9 |
10 | export {
11 | printDrainHydrateMarks,
12 | drainHydrateMarks,
13 | ImportedStream,
14 | setConfiguration,
15 | createLoadableStream,
16 | createLoadableTransformer,
17 | getLoadableTrackerCallback,
18 | getMarkedChunks,
19 | getMarkedFileNames,
20 | ImportedStreamTracker,
21 | };
22 |
--------------------------------------------------------------------------------
/src/loadable/assignImportedComponents.ts:
--------------------------------------------------------------------------------
1 | import { settings } from '../configuration/config';
2 | import { Promised } from '../types';
3 | import { assignMetaData } from './metadata';
4 | import { done } from './pending';
5 | import { LOADABLE_SIGNATURE } from './registry';
6 | import { toLoadable } from './toLoadable';
7 |
8 | type ImportedDefinition = [Promised, string, string, boolean];
9 |
10 | /**
11 | * to be used __only via CLI tools__
12 | */
13 | export const assignImportedComponents = (set: ImportedDefinition[]) => {
14 | const countBefore = LOADABLE_SIGNATURE.size;
15 |
16 | set.forEach((imported) => {
17 | const allowAutoLoad = !(imported[3] || !settings.fileFilter(imported[2]));
18 | const loadable = toLoadable(imported[0], allowAutoLoad);
19 | assignMetaData(loadable.mark, loadable, imported[1], imported[2]);
20 | });
21 |
22 | if (set.length === 0) {
23 | // tslint:disable-next-line:no-console
24 | console.error('react-imported-component: no import-marks found, please check babel plugin');
25 | }
26 |
27 | if (countBefore === LOADABLE_SIGNATURE.size) {
28 | // tslint:disable-next-line:no-console
29 | console.error('react-imported-component: no new imports found');
30 | }
31 |
32 | done();
33 |
34 | return set;
35 | };
36 |
--------------------------------------------------------------------------------
/src/loadable/loadByChunkName.ts:
--------------------------------------------------------------------------------
1 | import { markMeta } from './metadata';
2 |
3 | /**
4 | * loads chunk by a known chunkname
5 | * @param {String} chunkName
6 | */
7 | export const loadByChunkname = (chunkName: string): Promise =>
8 | Promise.all(markMeta.filter((meta) => meta.chunkName === chunkName).map((meta) => meta.loadable.load()));
9 |
--------------------------------------------------------------------------------
/src/loadable/loadable.ts:
--------------------------------------------------------------------------------
1 | import { DefaultImport, Loadable } from '../types';
2 | import { getFunctionSignature, importMatch } from '../utils/signatures';
3 | import { done } from './pending';
4 | import { LOADABLE_SIGNATURE, LOADABLE_WEAK_SIGNATURE } from './registry';
5 | import { toLoadable } from './toLoadable';
6 | import { toKnownSignature } from './utils';
7 |
8 | /**
9 | * try to perform a render and loads all chunks required for it
10 | * @deprecated
11 | */
12 | export const dryRender = (renderFunction: () => void): Promise => {
13 | renderFunction();
14 |
15 | return Promise.resolve().then(done);
16 | };
17 |
18 | export function executeLoadable(importFunction: DefaultImport | Loadable) {
19 | if ('resolution' in importFunction) {
20 | return importFunction.reload();
21 | } else {
22 | return importFunction();
23 | }
24 | }
25 |
26 | /**
27 | * wraps an `import` function with a tracker
28 | * @internal
29 | * @param importFunction
30 | */
31 | export function getLoadable(importFunction: DefaultImport | Loadable): Loadable {
32 | if ('resolution' in importFunction) {
33 | return importFunction;
34 | }
35 |
36 | if (LOADABLE_WEAK_SIGNATURE.has(importFunction)) {
37 | return LOADABLE_WEAK_SIGNATURE.get(importFunction) as any;
38 | }
39 |
40 | const rawSignature = getFunctionSignature(importFunction);
41 | const ownMark = importMatch(String(rawSignature));
42 | // read cache signature
43 | const functionSignature = toKnownSignature(rawSignature, ownMark);
44 |
45 | if (LOADABLE_SIGNATURE.has(functionSignature)) {
46 | // tslint:disable-next-line:no-shadowed-variable
47 | const loadable = LOADABLE_SIGNATURE.get(functionSignature)!;
48 | loadable.replaceImportFunction(importFunction);
49 |
50 | return loadable as any;
51 | }
52 |
53 | if (ownMark) {
54 | if (process.env.NODE_ENV !== 'production') {
55 | if (ownMark.length > 1) {
56 | // tslint:disable-next-line:no-console
57 | console.warn('A function exposes multiple imports. Imported-Components cannot know which one will be used.', {
58 | importFunction,
59 | });
60 | }
61 | }
62 |
63 | LOADABLE_SIGNATURE.forEach(({ mark, importer }) => {
64 | if (mark[0] === ownMark[1] && mark.join('|') === ownMark.join('|')) {
65 | // tslint:disable-next-line:no-console
66 | console.warn(
67 | "Another loadable found for an existing mark. That's possible, but signatures must match (https://github.com/theKashey/react-imported-component/issues/192):",
68 | {
69 | mark,
70 | knownImporter: importer,
71 | currentImported: importFunction,
72 | currentSignature: String(importFunction),
73 | knownSignature: String(importer),
74 | }
75 | );
76 | }
77 | });
78 | }
79 |
80 | const loadable = toLoadable(importFunction as any);
81 | LOADABLE_WEAK_SIGNATURE.set(importFunction, loadable);
82 |
83 | return loadable as any;
84 | }
85 |
86 | /**
87 | * Reset `importers` weak cache
88 | * @internal
89 | */
90 | export const clearImportedCache = (): void => LOADABLE_SIGNATURE.clear();
91 |
--------------------------------------------------------------------------------
/src/loadable/markerMapper.ts:
--------------------------------------------------------------------------------
1 | import { Mark, MarkMeta } from '../types';
2 | import { markMeta } from './metadata';
3 | import { markerOverlap } from './utils';
4 |
5 | const getMarkedMeta = (marks: Mark, mapping: (meta: MarkMeta) => string) => {
6 | if (markMeta.length === 0) {
7 | throw new Error('react-imported-component: no import meta-information found. Have you imported async-requires?');
8 | }
9 |
10 | return Array.from(
11 | new Set(
12 | markMeta
13 | .filter(meta => markerOverlap(meta.mark, marks))
14 | .map(mapping)
15 | .filter(Boolean)
16 | ).values()
17 | );
18 | };
19 |
20 | export const getMarkedChunks = (marks: Mark) => getMarkedMeta(marks, meta => meta.chunkName);
21 | export const getMarkedFileNames = (marks: Mark) => getMarkedMeta(marks, meta => meta.fileName);
22 |
--------------------------------------------------------------------------------
/src/loadable/marks.ts:
--------------------------------------------------------------------------------
1 | import { Loadable, Mark, Stream } from '../types';
2 | import { checkStream, clearStream, defaultStream } from './stream';
3 | import { markerOverlap } from './utils';
4 |
5 | interface MarkPair {
6 | mark: Mark;
7 | loadable: Loadable;
8 | }
9 |
10 | const LOADABLE_MARKS = new Map();
11 |
12 | export const consumeMark = (stream: Stream = defaultStream, marks: string[]): void => {
13 | checkStream(stream);
14 |
15 | if (marks && marks.length) {
16 | marks.forEach((a) => (stream.marks[a] = true));
17 | }
18 | };
19 |
20 | export const assignLoadableMark = (mark: Mark, loadable: Loadable): void => {
21 | LOADABLE_MARKS.set(JSON.stringify(mark), { mark, loadable });
22 | };
23 |
24 | /**
25 | * returns marks used in the stream
26 | * @param stream
27 | */
28 | export const getUsedMarks = (stream: Stream = defaultStream): string[] => (stream ? Object.keys(stream.marks) : []);
29 |
30 | /**
31 | * SSR
32 | * @returns list or marks used
33 | */
34 | export const drainHydrateMarks = (stream: Stream = defaultStream): string[] => {
35 | checkStream(stream);
36 |
37 | const marks = getUsedMarks(stream);
38 | clearStream(stream);
39 |
40 | return marks;
41 | };
42 |
43 | /**
44 | * Loads a given marks/chunks
45 | * @see returns a promise for a given marks only. In order to await all requests currently in flight use {@link waitForMarks}
46 | * @param marks
47 | * @returns a resolution promise
48 | */
49 | export const rehydrateMarks = (marks?: string[]): Promise => {
50 | const rehydratedMarks: string[] = marks || (global as any).___REACT_DEFERRED_COMPONENT_MARKS || [];
51 | const tasks: Array> = [];
52 |
53 | const usedMarks = new Set();
54 |
55 | LOADABLE_MARKS.forEach(({ mark, loadable }) => {
56 | if (markerOverlap(mark, rehydratedMarks)) {
57 | mark.forEach((m) => usedMarks.add(m));
58 | tasks.push(loadable.load());
59 | }
60 | });
61 |
62 | rehydratedMarks.forEach((m) => {
63 | if (!usedMarks.has(m)) {
64 | throw new Error(
65 | `react-imported-component: unknown mark(${m}) has been used. Client and Server should have the same babel configuration.`
66 | );
67 | }
68 | });
69 |
70 | return Promise.all(tasks);
71 | };
72 |
73 | /**
74 | * waits for the given marks to load
75 | * @param marks
76 | */
77 | export const waitForMarks = (marks: string[]): Promise => {
78 | const tasks: Array> = [];
79 |
80 | LOADABLE_MARKS.forEach(({ mark, loadable }) => {
81 | if (markerOverlap(mark, marks)) {
82 | tasks.push(loadable.resolution);
83 | }
84 | });
85 |
86 | return Promise.all(tasks);
87 | };
88 |
89 | /**
90 | * @returns a `;
96 | };
97 |
--------------------------------------------------------------------------------
/src/loadable/metadata.ts:
--------------------------------------------------------------------------------
1 | import { Loadable, Mark, MarkMeta } from '../types';
2 |
3 | export const markMeta: MarkMeta[] = [];
4 |
5 | export const assignMetaData = (mark: Mark, loadable: Loadable, chunkName: string, fileName: string): void => {
6 | markMeta.push({ mark, loadable, chunkName, fileName });
7 | };
8 |
--------------------------------------------------------------------------------
/src/loadable/pending.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * pending indicates any ongoing procceses
3 | */
4 | let pending: Array> = [];
5 |
6 | export const addPending = (promise: Promise): void => {
7 | pending.push(promise);
8 | };
9 |
10 | export const removeFromPending = (promise: Promise): void => {
11 | pending = pending.filter((a) => a !== promise);
12 | };
13 |
14 | /**
15 | * is it really ready?
16 | */
17 | let readyFlag = false;
18 |
19 | export const isItReady = (): boolean => readyFlag;
20 |
21 | /**
22 | * waits for all necessary imports to be fulfilled
23 | */
24 | export const done = (): Promise => {
25 | if (pending.length) {
26 | readyFlag = false;
27 |
28 | return Promise.all(pending)
29 | .then((a) => a[1])
30 | .then(done);
31 | }
32 |
33 | readyFlag = true;
34 |
35 | return Promise.resolve();
36 | };
37 |
--------------------------------------------------------------------------------
/src/loadable/preloaders.ts:
--------------------------------------------------------------------------------
1 | export type Preloader = () => any;
2 |
3 | let preloaders: Preloader[] = [];
4 | type Callback = () => void;
5 |
6 | /**
7 | * adds a precondition before resolving any imported object
8 | */
9 | export const addPreloader = (preloader: Preloader): Callback => {
10 | preloaders.push(preloader);
11 |
12 | return () => {
13 | preloaders = preloaders.filter((p) => p !== preloader);
14 | };
15 | };
16 |
17 | export const getPreloaders = (): any[] => preloaders.map((preloader) => preloader());
18 |
--------------------------------------------------------------------------------
/src/loadable/registry.ts:
--------------------------------------------------------------------------------
1 | import { Loadable } from '../types';
2 |
3 | export const LOADABLE_WEAK_SIGNATURE = new WeakMap>();
4 | export const LOADABLE_SIGNATURE = new Map>();
5 |
--------------------------------------------------------------------------------
/src/loadable/stream.ts:
--------------------------------------------------------------------------------
1 | import { Stream } from '../types';
2 |
3 | export const createLoadableStream = (): Stream => ({ marks: {} });
4 |
5 | export const clearStream = (stream?: Stream): void => {
6 | if (stream) {
7 | stream.marks = {};
8 | }
9 | };
10 |
11 | export const checkStream = (stream: Stream | number | string | undefined): void => {
12 | if (process.env.NODE_ENV !== 'production') {
13 | if (!stream) {
14 | return;
15 | }
16 |
17 | if (typeof stream !== 'object' || !stream.marks) {
18 | throw new Error(
19 | 'react-imported-component: version 6 requires `stream` to be an object. Refer to the migration guide'
20 | );
21 | }
22 | }
23 | };
24 |
25 | export const defaultStream = createLoadableStream();
26 |
--------------------------------------------------------------------------------
/src/loadable/toLoadable.ts:
--------------------------------------------------------------------------------
1 | import { AnyFunction, Loadable, Promised } from '../types';
2 | import { isBackend } from '../utils/detectBackend';
3 | import { getFunctionSignature, importMatch } from '../utils/signatures';
4 | import { assignLoadableMark } from './marks';
5 | import { addPending, removeFromPending } from './pending';
6 | import { getPreloaders } from './preloaders';
7 | import { LOADABLE_SIGNATURE } from './registry';
8 | import { toKnownSignature } from './utils';
9 |
10 | export interface InnerLoadable extends Loadable {
11 | ok: boolean;
12 | promise: Promise | undefined;
13 |
14 | _probeChanges(): Promise;
15 | }
16 |
17 | export function toLoadable(firstImportFunction: Promised, autoImport = true): Loadable {
18 | let importFunction = firstImportFunction;
19 | const loadImportedComponent = (): Promise =>
20 | Promise.all([importFunction(), ...getPreloaders()]).then(([result]) => result);
21 | const functionSignature = getFunctionSignature(importFunction);
22 | const mark = importMatch(functionSignature);
23 |
24 | let resolveResolution: AnyFunction;
25 | const resolution = new Promise((r) => {
26 | resolveResolution = r;
27 | });
28 |
29 | const loadable: InnerLoadable = {
30 | // importFunction,
31 | mark,
32 | resolution,
33 | done: false,
34 | ok: false,
35 | error: null,
36 | payload: undefined,
37 | promise: undefined,
38 |
39 | isLoading() {
40 | return !!this.promise && !this.done;
41 | },
42 |
43 | reset() {
44 | this.done = false;
45 | this.ok = true;
46 | this.payload = undefined;
47 | this.promise = undefined;
48 | },
49 |
50 | replaceImportFunction(newImportFunction) {
51 | importFunction = newImportFunction;
52 | },
53 |
54 | get importer() {
55 | return importFunction;
56 | },
57 |
58 | then(cb, err) {
59 | if (this.promise) {
60 | return this.promise.then(cb, err);
61 | }
62 |
63 | if (err) {
64 | err();
65 | }
66 |
67 | return Promise.reject();
68 | },
69 |
70 | loadIfNeeded() {
71 | if (this.error) {
72 | this.reset();
73 | }
74 |
75 | if (!this.promise) {
76 | this.load();
77 | }
78 |
79 | return this.promise!;
80 | },
81 |
82 | tryResolveSync(then) {
83 | if (this.done) {
84 | const result = then(this.payload as any);
85 |
86 | return {
87 | then(cb: any) {
88 | // synchronous thenable - https://github.com/facebook/react/pull/14626
89 | cb(result);
90 |
91 | return Promise.resolve(result);
92 | },
93 | } as any;
94 | }
95 |
96 | return this.loadIfNeeded().then(then);
97 | },
98 |
99 | reload() {
100 | if (this.promise) {
101 | this.promise = undefined;
102 |
103 | return this.load() as any;
104 | }
105 |
106 | return Promise.resolve();
107 | },
108 |
109 | _probeChanges() {
110 | return Promise.resolve(importFunction())
111 | .then((payload) => payload !== this.payload)
112 | .catch((err) => {
113 | throw err;
114 | });
115 | },
116 |
117 | load() {
118 | if (!this.promise) {
119 | const promise = (this.promise = loadImportedComponent().then(
120 | (payload) => {
121 | this.done = true;
122 | this.ok = true;
123 | this.payload = payload;
124 | this.error = null;
125 | removeFromPending(promise);
126 | resolveResolution(payload);
127 |
128 | return payload;
129 | },
130 | (err) => {
131 | this.done = true;
132 | this.ok = false;
133 | this.error = err;
134 | removeFromPending(promise);
135 | throw err;
136 | }
137 | ));
138 | addPending(promise);
139 | }
140 |
141 | return this.promise;
142 | },
143 | };
144 |
145 | if (mark && mark.length) {
146 | LOADABLE_SIGNATURE.set(toKnownSignature(functionSignature, mark), loadable);
147 | assignLoadableMark(mark, loadable);
148 | } else {
149 | if (process.env.NODE_ENV !== 'development') {
150 | // tslint:disable-next-line:no-console
151 | console.warn(
152 | 'react-imported-component: no mark found at',
153 | importFunction,
154 | "Please check babel plugin or macro setup, as well as imported-component's limitations. See https://github.com/theKashey/react-imported-component/issues/147"
155 | );
156 | }
157 | }
158 |
159 | // trigger preload on the server side
160 | if (isBackend && autoImport) {
161 | loadable.load();
162 | }
163 |
164 | return loadable;
165 | }
166 |
--------------------------------------------------------------------------------
/src/loadable/utils.ts:
--------------------------------------------------------------------------------
1 | import { settings } from '../configuration/config';
2 |
3 | export const toKnownSignature = (signature: string, marks: string[]): string =>
4 | (!settings.checkSignatures && marks.join('|')) || signature;
5 |
6 | export const markerOverlap = (a1: string[], a2: string[]): boolean =>
7 | a1.filter((mark) => a2.indexOf(mark) >= 0).length === a1.length;
8 |
--------------------------------------------------------------------------------
/src/scanners/__tests__/parser.spec.ts:
--------------------------------------------------------------------------------
1 | import { dirname } from 'path';
2 |
3 | import { remapImports } from '../scanForImports';
4 | import { getRelative } from '../shared';
5 |
6 | describe('scanForImports', () => {
7 | const rel = dirname(__dirname);
8 | const root = '.';
9 | const rootRel = '.' + process.cwd();
10 | const sourceFile = `${rel}/a`;
11 |
12 | it('should map simple import', () => {
13 | const imports = {};
14 |
15 | remapImports(
16 | [{ file: `${rel}/a`, content: 'blabla;import("./b.js"); blabla;' }],
17 | rel,
18 | rel,
19 | getRelative,
20 | imports,
21 | () => true
22 | );
23 |
24 | expect(Object.values(imports)).toEqual([`[() => import('./b.js'), '', './b.js', false] /* from ./a */`]);
25 | });
26 |
27 | it('handles imports in jsdoc', () => {
28 | const imports = {};
29 |
30 | remapImports(
31 | [
32 | {
33 | file: sourceFile,
34 | content: `
35 | /**
36 | * @type {import('wrong-import')}
37 | */
38 | import(/* comment:valuable */ "./a.js");
39 | import("./b.js");
40 | // import('another-wrong-import');// FIXME: temporary removed
41 | `,
42 | },
43 | ],
44 | rel,
45 | rel,
46 | getRelative,
47 | imports,
48 | () => true
49 | );
50 |
51 | expect(Object.values(imports)).toEqual([
52 | `[() => import(/* comment:valuable */'./a.js'), '', './a.js', false] /* from ./a */`,
53 | `[() => import('./b.js'), '', './b.js', false] /* from ./a */`,
54 | ]);
55 | });
56 |
57 | it('should map client-side import', () => {
58 | const imports = {};
59 |
60 | remapImports(
61 | [{ file: sourceFile, content: 'blabla;import(/* client-side */"./a.js"); blabla;' }],
62 | rel,
63 | rel,
64 | getRelative,
65 | imports,
66 | () => true
67 | );
68 |
69 | expect(Object.values(imports)).toEqual([
70 | `[() => import(/* client-side */'./a.js'), '', './a.js', true] /* from ./a */`,
71 | ]);
72 | });
73 |
74 | it('should map simple import with a comment', () => {
75 | const imports = {};
76 |
77 | remapImports(
78 | [{ file: sourceFile, content: 'blabla;import(/* comment:42 */"./a.js"); blabla;' }],
79 | rel,
80 | rel,
81 | getRelative,
82 | imports,
83 | () => true
84 | );
85 |
86 | expect(Object.values(imports)).toEqual([
87 | `[() => import(/* comment:42 */'./a.js'), '', './a.js', false] /* from ./a */`,
88 | ]);
89 | });
90 |
91 | it('should map complex import', () => {
92 | const imports = {};
93 |
94 | remapImports(
95 | [
96 | {
97 | file: sourceFile,
98 | content: 'blabla;import(/* webpack: "123" */"./a.js"); blabla; import(/* webpack: 123 */ \'./b.js\');',
99 | },
100 | ],
101 | rel,
102 | rel,
103 | getRelative,
104 | imports,
105 | () => true
106 | );
107 |
108 | expect(Object.values(imports)).toEqual([
109 | `[() => import(/* webpack: \"123\" */'./a.js'), '', './a.js', false] /* from ./a */`,
110 | `[() => import(/* webpack: 123 */'./b.js'), '', './b.js', false] /* from ./a */`,
111 | ]);
112 | });
113 |
114 | it('should match chunk name', () => {
115 | const imports = {};
116 |
117 | remapImports(
118 | [
119 | {
120 | file: 'a',
121 | content:
122 | 'blabla;import(/* webpackChunkName: "chunk-a" */"./a.js"); blabla; import(/* webpack: 123 */ \'./b.js\');',
123 | },
124 | ],
125 | root,
126 | root,
127 | (a, b) => a + b,
128 | imports,
129 | () => true
130 | );
131 |
132 | expect(Object.values(imports)).toEqual([
133 | `[() => import(/* webpackChunkName: "chunk-a" */'${rootRel}/a.js'), 'chunk-a', '${rootRel}/a.js', false] /* from .a */`,
134 | `[() => import(/* webpack: 123 */'${rootRel}/b.js'), '', '${rootRel}/b.js', false] /* from .a */`,
135 | ]);
136 | });
137 |
138 | it('should override chunk name', () => {
139 | const imports = {};
140 |
141 | remapImports(
142 | [
143 | {
144 | file: 'a',
145 | content:
146 | 'blabla;import(/* webpackChunkName: "chunk-a" */"./a.js"); blabla; import(/* webpackChunkName: "chunk-b" */"./b.js"); import(/* webpackChunkName: "chunk-c" */"./c.js");',
147 | },
148 | ],
149 | root,
150 | root,
151 | (a, b) => a + b,
152 | imports,
153 | (imported) => imported.indexOf('c.js') < 0,
154 | (imported, _, options) => (imported.indexOf('a.js') > 0 ? `test-${options.chunkName}-test` : 'bundle-b')
155 | );
156 |
157 | expect(Object.values(imports)).toEqual([
158 | `[() => import(/* webpackChunkName: \"chunk-a\" */'${rootRel}/a.js'), 'test-chunk-a-test', '${rootRel}/a.js', false] /* from .a */`,
159 | `[() => import(/* webpackChunkName: \"chunk-b\" */'${rootRel}/b.js'), 'bundle-b', '${rootRel}/b.js', false] /* from .a */`,
160 | ]);
161 | });
162 |
163 | it('should match support multiline imports', () => {
164 | const imports = {};
165 |
166 | remapImports(
167 | [
168 | {
169 | file: 'a',
170 | content: `
171 | blabla;import(
172 | /* webpackChunkName: "chunk-a" */
173 | "./a.js"
174 | );
175 | something else
176 | import(
177 | // ts-ignore
178 | "./b.js"
179 | );
180 | `,
181 | },
182 | ],
183 | root,
184 | root,
185 | (a, b) => a + b,
186 | imports,
187 | () => true
188 | );
189 |
190 | expect(Object.values(imports)).toEqual([
191 | `[() => import(/* webpackChunkName: \"chunk-a\" */'${rootRel}/a.js'), 'chunk-a', '${rootRel}/a.js', false] /* from .a */`,
192 | `[() => import('${rootRel}/b.js'), '', '${rootRel}/b.js', false] /* from .a */`,
193 | ]);
194 | });
195 |
196 | it('should remove webpackPrefetch and webpackPreload', () => {
197 | const imports = {};
198 |
199 | remapImports(
200 | [
201 | {
202 | file: 'a',
203 | content:
204 | 'blabla;import(/* webpackPrefetch: true *//* webpack: "123" */"./a.js"); blabla; import(/* webpackPreload: true */ \'./b.js\');',
205 | },
206 | ],
207 | root,
208 | root,
209 | (a, b) => a + b,
210 | imports,
211 | () => true
212 | );
213 |
214 | expect(Object.values(imports)).toEqual([
215 | `[() => import(/* *//* webpack: \"123\" */'${rootRel}/a.js'), '', '${rootRel}/a.js', false] /* from .a */`,
216 | `[() => import(/* */'${rootRel}/b.js'), '', '${rootRel}/b.js', false] /* from .a */`,
217 | ]);
218 | });
219 | });
220 |
--------------------------------------------------------------------------------
/src/scanners/cli.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable no-console */
2 |
3 | import { scanTop } from './scanForImports';
4 |
5 | if (!process.argv[3]) {
6 | console.log('usage: imported-components sourceRoot targetFile');
7 | console.log('example: imported-components src src/importedComponents.js');
8 | } else {
9 | scanTop(process.cwd(), process.argv[2], process.argv[3]);
10 | }
11 |
--------------------------------------------------------------------------------
/src/scanners/scanForImports.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable no-console */
2 |
3 | import { existsSync, Stats } from 'fs';
4 | import { dirname, extname, resolve } from 'path';
5 |
6 | // @ts-ignore
7 | import scanDirectory from 'scan-directory';
8 |
9 | import { ImportedConfiguration } from '../configuration/configuration';
10 | import { CLIENT_SIDE_ONLY } from '../configuration/constants';
11 | import { getFileContent, getMatchString, getRelative, normalizePath, pWriteFile } from './shared';
12 |
13 | const RESOLVE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.mjs'];
14 |
15 | const trimImport = (str: string) => str.replace(/['"]/g, '');
16 |
17 | const getImportMatch = getMatchString(`(['"]?[\\w-/.@]+['"]?)\\)`, 1);
18 | const getImports = (str: string) =>
19 | getImportMatch(
20 | str
21 | // remove comments
22 | .replace(/\/\*([^\*]*)\*\//gi, '')
23 | .replace(/\/\/(.*)/gi, '')
24 | // remove new lines
25 | .replace(/\n/gi, '')
26 | // remove spaces?
27 | .replace(/[\s]+\)/i, ')')
28 | );
29 |
30 | const getComment = getMatchString(/\/\*.*\*\// as any, 0);
31 |
32 | const getChunkName = getMatchString('webpackChunkName: "([^"]*)"', 1);
33 |
34 | const clientSideOnly = (comment: string) => comment.indexOf(CLIENT_SIDE_ONLY) >= 0;
35 |
36 | const clearComment = (str: string) => str.replace('webpackPrefetch: true', '').replace('webpackPreload: true', '');
37 |
38 | interface MappedImport {
39 | name: string;
40 | comment: string;
41 | }
42 |
43 | interface FileContent {
44 | file: string;
45 | content: string;
46 | }
47 |
48 | const getImportString =
49 | (pattern: string, selected: number) =>
50 | (str: string): MappedImport[] =>
51 | getMatchString(
52 | pattern,
53 | selected
54 | )(str).map((statement) => {
55 | return {
56 | name: trimImport(getImports(statement + ')')[0] || ''),
57 | comment: clearComment(getComment(statement)[0] || ''),
58 | };
59 | });
60 |
61 | export const getDynamicImports = getImportString(`import[\\s]?\\((([^)])+['"]?)\\)`, 1);
62 |
63 | export const cleanFileContent = (content: string): string => {
64 | const mapping: string[] = [];
65 | // wrap
66 | const wrapped = content.replace(new RegExp(`import[\\s]?\\((([^)])+['"]?)\\)`, 'g'), (match) => {
67 | const placement = mapping.push(match) - 1;
68 |
69 | return `imported_${placement}_replacement`;
70 | });
71 |
72 | const cleaned = wrapped.replace(new RegExp('//.*', 'g'), '').replace(new RegExp('\\/\\*[\\s\\S]*?\\*\\/', 'gm'), '');
73 |
74 | const unwrapped = cleaned.replace(new RegExp('imported_([\\d]*)_replacement', 'g'), (_, b: string) => {
75 | return mapping[+b];
76 | });
77 |
78 | return unwrapped;
79 | };
80 |
81 | const mapImports = (file: string, imports: MappedImport[]) =>
82 | imports.map((dep) => {
83 | const { name } = dep;
84 |
85 | if (name && name.charAt(0) === '.') {
86 | return {
87 | ...dep,
88 | file,
89 | name: resolve(dirname(file), name),
90 | doNotTransform: false,
91 | };
92 | }
93 |
94 | return {
95 | ...dep,
96 | file,
97 | doNotTransform: true,
98 | };
99 | });
100 |
101 | const rejectSystemFiles = (test: Required['testFolder']) => (file: string, stats: Stats) => {
102 | if (stats.isDirectory()) {
103 | return !test(file);
104 | }
105 |
106 | return false;
107 | };
108 |
109 | const rejectNodeModulesAndDotFolders = (file: string) => !(file.match(/node_modules/) || file.match(/(\/\.\w+)/));
110 |
111 | export const remapImports = (
112 | data: FileContent[],
113 | root: string,
114 | targetDir: string,
115 | getRelativeName: (a: string, b: string) => string,
116 | imports: Record,
117 | testImport: NonNullable,
118 | chunkName?: ImportedConfiguration['chunkName']
119 | ): void =>
120 | data
121 | .map(({ file, content }) => mapImports(file, getDynamicImports(cleanFileContent(content))))
122 | .forEach((importBlock) =>
123 | importBlock.forEach(({ name, comment, doNotTransform, file }) => {
124 | const rootName = doNotTransform ? name : getRelativeName(root, name);
125 | const fileName = doNotTransform ? name : getRelativeName(targetDir, name);
126 | const sourceName = getRelativeName(root, file);
127 |
128 | if (testImport(rootName, sourceName)) {
129 | const isClientSideOnly = clientSideOnly(comment);
130 | const givenChunkName = getChunkName(comment)[0] || '';
131 | const def = `[() => import(${comment}'${fileName}'), '${
132 | (chunkName && chunkName(rootName, sourceName, { chunkName: givenChunkName })) || givenChunkName
133 | }', '${rootName}', ${isClientSideOnly}] /* from ${sourceName} */`;
134 | const slot = getRelativeName(root, name);
135 |
136 | // keep the maximal definition
137 | imports[slot] = !imports[slot] ? def : imports[slot].length > def.length ? imports[slot] : def;
138 | }
139 | })
140 | );
141 |
142 | export function scanTop(root: string, start: string, target: string): Promise {
143 | async function scan() {
144 | const sourceDir = resolve(root, start);
145 | console.log('scanning', sourceDir, 'for imports...');
146 |
147 | // try load configuration
148 | const configurationFile = resolve(root, '.imported.js');
149 | const {
150 | testFolder = rejectNodeModulesAndDotFolders,
151 | testFile = () => true,
152 | testImport = () => true,
153 | chunkName,
154 | configuration,
155 | }: ImportedConfiguration = existsSync(configurationFile) ? require(configurationFile) : {};
156 |
157 | const files = ((await scanDirectory(sourceDir, undefined, rejectSystemFiles(testFolder))) as string[])
158 | .filter((name) => normalizePath(name).indexOf(target) === -1)
159 | .filter((name) => RESOLVE_EXTENSIONS.indexOf(extname(name)) >= 0)
160 | .filter((name) => testFile(name))
161 | .sort();
162 |
163 | const data: FileContent[] = await Promise.all(
164 | files.map(async (file) => {
165 | const content = await getFileContent(file);
166 |
167 | return {
168 | file,
169 | content,
170 | };
171 | })
172 | );
173 |
174 | const imports: Record = {};
175 | const targetDir = resolve(root, dirname(target));
176 | remapImports(data, root, targetDir, getRelative, imports, testImport, chunkName);
177 |
178 | console.log(`${Object.keys(imports).length} imports found, saving to ${target}`);
179 |
180 | pWriteFile(
181 | target,
182 | `
183 | /* eslint-disable */
184 | /* tslint:disable */
185 |
186 | // generated by react-imported-component, DO NOT EDIT
187 | import {assignImportedComponents} from 'react-imported-component/macro';
188 | ${
189 | configuration &&
190 | `import {setConfiguration} from 'react-imported-component/boot';
191 | // as configured in .imported.js
192 | setConfiguration(${JSON.stringify(configuration, null, 2)});
193 | `
194 | }
195 |
196 | // all your imports are defined here
197 | // all, even the ones you tried to hide in comments (that's the cost of making a very fast parser)
198 | // to remove any import from here
199 | // 1) use magic comment \`import(/* client-side */ './myFile')\` - and it will disappear
200 | // 2) use file filter to ignore specific locations (refer to the README - https://github.com/theKashey/react-imported-component/#server-side-auto-import)
201 | // 3) use .imported.js to control this table generation (refer to the README - https://github.com/theKashey/react-imported-component/#-imported-js)
202 |
203 | const applicationImports = assignImportedComponents([
204 | ${Object.keys(imports)
205 | .map((key) => ` ${imports[key]},`)
206 | .sort()
207 | .join('\n')}
208 | ]);
209 |
210 | export default applicationImports;
211 |
212 | // @ts-ignore
213 | if (module.hot) {
214 | // these imports would make this module a parent for the imported modules.
215 | // but this is just a helper - so ignore(and accept!) all updates
216 |
217 | // @ts-ignore
218 | module.hot.accept(() => null);
219 | }
220 | `
221 | );
222 | }
223 |
224 | return scan();
225 | }
226 |
--------------------------------------------------------------------------------
/src/scanners/shared.ts:
--------------------------------------------------------------------------------
1 | import { readFile, writeFile } from 'fs';
2 | import { relative, sep } from 'path';
3 | import { promisify } from 'util';
4 |
5 | export const normalizePath = (path: string): string => path.split(sep).join('/');
6 |
7 | export const getRelative = (from: string, to: string): string => {
8 | // force one unit paths
9 | const rel = normalizePath(relative(from, to));
10 |
11 | return rel[0] !== '.' ? './' + rel : rel;
12 | };
13 |
14 | export const getMatchString =
15 | (pattern: string, selected: number) =>
16 | (str: string): string[] =>
17 | (str.match(new RegExp(pattern, 'g')) || []).map(
18 | (statement) => (statement.match(new RegExp(pattern, 'i')) || [])[selected]
19 | );
20 |
21 | export const pReadFile = promisify(readFile);
22 | export const pWriteFile = promisify(writeFile);
23 | export const getFileContent = (file: string): Promise => pReadFile(file, 'utf8');
24 |
--------------------------------------------------------------------------------
/src/trackers/globalTracker.ts:
--------------------------------------------------------------------------------
1 | import { rehydrateMarks } from '../loadable/marks';
2 |
3 | /**
4 | * injects a loadable tracker on a given global variable name.
5 | *
6 | * WARNING: mutates the origin variable!
7 | *
8 | * @param name = 'importedComponents`
9 | * @example
10 | * ```js
11 | * window.importedMarks = window.importedMarks || [];
12 | * importedMarks.push(hydratedMarks[0]);
13 | * ///
14 | * injectLoadableTracker();
15 | */
16 | export const injectLoadableTracker = (name = 'importedComponents'): void => {
17 | const value = (global as any)[name] as string[][];
18 |
19 | if (value) {
20 | if (!value.push || (Boolean(value.push) && !Boolean(value.forEach))) {
21 | // tslint:disable-next-line:no-console
22 | console.error('given: ', value);
23 | throw new Error(`injectLoadableTracker(${name}) expected to be expected on Array-like variable, and only once.`);
24 | }
25 |
26 | value.forEach((mark) => rehydrateMarks(mark));
27 | }
28 |
29 | (global as any)[name] = {
30 | push: rehydrateMarks,
31 | };
32 | };
33 |
34 | export const getLoadableTrackerCallback =
35 | (name = 'importedComponents') =>
36 | (marks: string[]) =>
37 | ``;
38 |
--------------------------------------------------------------------------------
/src/transformers/loadableTransformer.ts:
--------------------------------------------------------------------------------
1 | import { Transform } from 'stream';
2 |
3 | import { getUsedMarks } from '../loadable/marks';
4 | import { Stream } from '../types';
5 |
6 | type LoadableStreamCallback = (marks: string[]) => string;
7 |
8 | export const createLoadableTransformer = (stream: Stream, callback: LoadableStreamCallback): Transform => {
9 | const usedMarks = new Set();
10 |
11 | return new Transform({
12 | // transform() is called with each chunk of data
13 | // tslint:disable-next-line:variable-name
14 | transform(chunk, _, _callback) {
15 | const marks = getUsedMarks(stream);
16 | const newMarks: string[] = [];
17 |
18 | marks.forEach((mark) => {
19 | if (!usedMarks.has(mark)) {
20 | newMarks.push(mark);
21 | usedMarks.add(mark);
22 | }
23 | });
24 |
25 | const chunkData = Buffer.from(chunk, 'utf-8');
26 |
27 | _callback(undefined, callback(newMarks) + chunkData);
28 | },
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import { ComponentType, ForwardRefExoticComponent, ReactElement, ReactNode, Ref } from 'react';
2 |
3 | export interface DefaultImportedComponent {
4 | default: ComponentType
;
5 | }
6 |
7 | export type AnyFunction = (x: any) => any;
8 |
9 | export interface Default {
10 | default: T;
11 | }
12 |
13 | export interface Stream {
14 | marks: Record;
15 | }
16 |
17 | export type Mark = string[];
18 |
19 | export type Promised = () => Promise;
20 |
21 | export type DefaultComponent = ComponentType
| DefaultImportedComponent
;
22 | export type DefaultComponentImport = () => Promise>;
23 |
24 | export type Defaultable = P | Default
;
25 | /**
26 | * standard "importer" accepted by the package.
27 | * Could be {default:T} or T
28 | */
29 | export type DefaultImport = () => Promise>;
30 |
31 | export interface MarkMeta {
32 | loadable: Loadable;
33 | mark: Mark;
34 | chunkName: string;
35 | fileName: string;
36 | }
37 |
38 | export type LazyImport = () => Promise>;
39 |
40 | export interface LoadableComponentState {
41 | loading?: boolean;
42 | error?: any;
43 | }
44 |
45 | export interface Loadable {
46 | done: boolean;
47 | error: any;
48 | payload: T | undefined;
49 |
50 | mark: Mark;
51 |
52 | resolution: Promise;
53 | importer: any;
54 |
55 | isLoading(): boolean;
56 |
57 | reset(): void;
58 | replaceImportFunction(newImportFunction: Promised): void;
59 |
60 | loadIfNeeded(): Promise;
61 |
62 | tryResolveSync(then: (x: T) => Y): Promise;
63 |
64 | load(): Promise;
65 | reload(): Promise;
66 |
67 | then(callback: (x: T) => void, err: () => void): Promise;
68 | }
69 |
70 | export interface ComponentOptions {
71 | loadable: DefaultComponentImport
| Loadable>;
72 |
73 | LoadingComponent?: ComponentType;
74 | ErrorComponent?: ComponentType;
75 |
76 | onError?: (a: any) => void;
77 |
78 | async?: boolean;
79 |
80 | render?: (Component: ComponentType | undefined, State: LoadableComponentState, props?: K) => ReactElement | null;
81 |
82 | forwardRef?: Ref;
83 | forwardProps?: K;
84 | }
85 |
86 | export interface HOCOptions {
87 | noAutoImport?: boolean;
88 | }
89 |
90 | export interface AdditionalHOC {
91 | done: Promise;
92 | preload(): Promise;
93 | }
94 |
95 | export type HOCType = ForwardRefExoticComponent> }> &
96 | AdditionalHOC;
97 |
98 | export interface ImportModuleHOCProps {
99 | fallback: NonNullable | null;
100 | }
101 |
102 | export interface ImportModuleProps extends ImportModuleHOCProps {
103 | children: (arg: T) => ReactElement | null;
104 | }
105 |
106 | export interface FullImportModuleProps extends ImportModuleProps {
107 | import: DefaultImport | Loadable;
108 | }
109 |
110 | export type ModuleFC = (props: ImportModuleProps) => ReactElement | null;
111 |
112 | export type HOCModuleType = ModuleFC & AdditionalHOC;
113 |
114 | export type HOC = (
115 | loader: DefaultComponentImport
,
116 | options?: Partial> & HOCOptions
117 | ) => HOCType;
118 |
119 | export interface ImportedComponents {
120 | [index: number]: () => Promise>;
121 | }
122 |
--------------------------------------------------------------------------------
/src/ui/Component.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { ReactElement } from 'react';
3 |
4 | import { settings } from '../configuration/config';
5 | import { ComponentOptions } from '../types';
6 | import { useImported } from './useImported';
7 |
8 | /**
9 | * @deprecated use {@link useImported} instead
10 | */
11 | function ImportedComponent(props: ComponentOptions
): ReactElement | null {
12 | const { loading, error, loadable, imported: Component, retry } = useImported(props.loadable);
13 |
14 | if (loading && props.async) {
15 | throw loadable.resolution;
16 | }
17 |
18 | if ('render' in props && props.render) {
19 | return props.render(Component, { loading, error }, props.forwardProps);
20 | }
21 |
22 | if (Component) {
23 | // importedUUID for cache busting
24 | // @ts-ignore
25 | return ;
26 | }
27 |
28 | if (loading) {
29 | if (props.async) {
30 | throw loadable.resolution;
31 | }
32 |
33 | return props.LoadingComponent ? : null;
34 | }
35 |
36 | if (error) {
37 | // always report errors
38 | // tslint:disable-next-line:no-console
39 | console.error('react-imported-component', error);
40 |
41 | // let's rethrow the error after react leaves this function
42 | // this might be very crucial for the "safe" dev mode
43 | if (settings.rethrowErrors) {
44 | setTimeout(() => {
45 | throw error;
46 | });
47 | }
48 |
49 | if (props.ErrorComponent) {
50 | return ;
51 | }
52 |
53 | throw error;
54 | }
55 |
56 | return null;
57 | }
58 |
59 | export { ImportedComponent };
60 |
--------------------------------------------------------------------------------
/src/ui/HOC.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { useMemo } from 'react';
3 |
4 | import { getLoadable } from '../loadable/loadable';
5 | import { ComponentOptions, DefaultComponentImport, HOCOptions, HOCType, LazyImport } from '../types';
6 | import { isBackend } from '../utils/detectBackend';
7 | import { asDefault } from '../utils/utils';
8 | import { ImportedComponent } from './Component';
9 | import { useLoadable } from './useImported';
10 |
11 | /**
12 | * creates a "lazy" component, like `React.lazy`
13 | * @see {@link useImported} or {@link useLazy}
14 | * @param {Function} loaderFunction - () => import('a'), or () => require('b')
15 | * @param {Object} [options]
16 | * @param {React.Component} [options.LoadingComponent]
17 | * @param {React.Component} [options.ErrorComponent]
18 | * @param {Function} [options.onError] - error handler. Will consume the real error.
19 | * @param {Function} [options.async = false] - enable React 16+ suspense.
20 | *
21 | * @example
22 | * const PageA = imported('./pageA', { async: true });
23 | */
24 | function loader
(
25 | loaderFunction: DefaultComponentImport
,
26 | baseOptions: Partial> & HOCOptions = {}
27 | ): HOCType {
28 | let loadable = getLoadable(loaderFunction);
29 |
30 | const Imported = React.forwardRef(function ImportedComponentHOC({ importedProps = {}, ...props }, ref) {
31 | const options = { ...baseOptions, ...importedProps };
32 | // re-get loadable in order to have fresh reference
33 | loadable = getLoadable(loaderFunction);
34 |
35 | return (
36 |
46 | );
47 | }) as HOCType;
48 |
49 | Imported.preload = () => {
50 | loadable.load().catch(() => ({}));
51 |
52 | return loadable.resolution;
53 | };
54 |
55 | Object.defineProperty(Imported, 'done', {
56 | get() {
57 | return loadable.resolution;
58 | },
59 | });
60 |
61 | return Imported;
62 | }
63 |
64 | const ReactLazy = React.lazy;
65 |
66 | /**
67 | * React.lazy "as-is" replacement
68 | */
69 | export function lazy(importer: LazyImport): React.FC {
70 | if (isBackend) {
71 | return loader(importer);
72 | }
73 |
74 | if (process.env.NODE_ENV !== 'production') {
75 | // lazy is not hot-reloadable
76 | if ((module as any).hot) {
77 | return loader(importer, { async: true });
78 | }
79 | }
80 |
81 | const topLoadable = getLoadable(importer);
82 |
83 | return function ImportedLazy(props: T) {
84 | const { loadable } = useLoadable(topLoadable);
85 |
86 | const Lazy = useMemo(() => ReactLazy(() => loadable.tryResolveSync(asDefault as any) as any), [loadable]);
87 |
88 | return ;
89 | };
90 | }
91 |
92 | export default loader;
93 |
--------------------------------------------------------------------------------
/src/ui/ImportedController.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useEffect, useState } from 'react';
2 |
3 | interface ImportedState {
4 | usesHydration: boolean;
5 | pastHydration: boolean;
6 | }
7 |
8 | export const importedState = createContext(undefined);
9 |
10 | export const HydrationState: React.FC<{ state: ImportedState }> = ({ state, children }) => (
11 | {children}
12 | );
13 |
14 | /**
15 | * @see [LazyBoundary]{@link LazyBoundary} - HydrationController is required for LazyBoundary to properly work with React>16.10
16 | * Established a control over LazyBoundary suppressing fallback during the initial hydration
17 | * @param props
18 | * @param [props.usesHydration=true] determines of Application is rendered using hydrate
19 | */
20 | export const ImportedController: React.FC<{
21 | /**
22 | * determines of Application is rendered using hydrate
23 | */
24 | usesHydration?: boolean;
25 | }> = ({ children, usesHydration = true }) => {
26 | const [state, setState] = useState({
27 | usesHydration,
28 | pastHydration: false,
29 | });
30 |
31 | useEffect(() => {
32 | setState((oldState) => ({ ...oldState, pastHydration: true }));
33 | }, []);
34 |
35 | return {children};
36 | };
37 |
--------------------------------------------------------------------------------
/src/ui/LazyBoundary.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { isBackend } from '../utils/detectBackend';
4 | import { useIsClientPhase } from '../utils/useClientPhase';
5 |
6 | const LazyServerBoundary: React.FC<{
7 | fallback: NonNullable | null;
8 | }> = ({ children }) => {children};
9 |
10 | const LazyClientBoundary: React.FC<{
11 | fallback: NonNullable | null;
12 | }> = ({ children, fallback }) => (
13 |
18 | {children}
19 |
20 | );
21 |
22 | /**
23 | * React.Suspense "as-is" replacement. Automatically "removed" during SSR and "patched" to work accordingly on the clientside
24 | *
25 | * @see {@link HydrationController} has to wrap entire application in order to provide required information
26 | */
27 | export const LazyBoundary = isBackend ? LazyServerBoundary : LazyClientBoundary;
28 |
--------------------------------------------------------------------------------
/src/ui/Module.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { getLoadable } from '../loadable/loadable';
4 | import { DefaultImport, FullImportModuleProps, HOCModuleType, ImportModuleProps } from '../types';
5 | import { useImported } from './useImported';
6 |
7 | /**
8 | * @deprecated use {@link useImported} instead
9 | */
10 | export function ImportedModule(props: FullImportModuleProps): React.ReactElement | null {
11 | const { error, loadable, imported: module } = useImported(props.import);
12 |
13 | if (error) {
14 | throw error;
15 | }
16 |
17 | if (module) {
18 | return props.children(module);
19 | }
20 |
21 | if (!props.fallback) {
22 | throw loadable.resolution;
23 | }
24 |
25 | return props.fallback as any;
26 | }
27 |
28 | /**
29 | * @deprecated use {@link useImported} instead
30 | */
31 | export function importedModule(loaderFunction: DefaultImport): HOCModuleType {
32 | const loadable = getLoadable(loaderFunction);
33 |
34 | const Module = ((props: ImportModuleProps) => (
35 |
36 | )) as HOCModuleType;
37 |
38 | Module.preload = () => {
39 | loadable.load().catch(() => ({}));
40 |
41 | return loadable.resolution;
42 | };
43 |
44 | Module.done = loadable.resolution;
45 |
46 | return Module;
47 | }
48 |
--------------------------------------------------------------------------------
/src/ui/context.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { defaultStream } from '../loadable/stream';
4 | import { Stream } from '../types';
5 |
6 | export const streamContext = React.createContext(defaultStream);
7 |
8 | /**
9 | * SSR. Tracker for used marks
10 | */
11 | export const ImportedStream: React.FC<{
12 | stream: Stream;
13 | }> = ({ stream, children, ...props }) => {
14 | if (process.env.NODE_ENV !== 'development') {
15 | if ('takeUID' in props) {
16 | throw new Error('react-imported-component: `takeUID` was replaced by `stream`.');
17 | }
18 | }
19 |
20 | return {children};
21 | };
22 |
23 | export const UIDConsumer = streamContext.Consumer;
24 |
--------------------------------------------------------------------------------
/src/ui/useImported.ts:
--------------------------------------------------------------------------------
1 | import { ComponentType, lazy, LazyExoticComponent, useCallback, useContext, useEffect, useMemo, useState } from 'react';
2 |
3 | import { getLoadable } from '../loadable/loadable';
4 | import { consumeMark } from '../loadable/marks';
5 | import { isItReady } from '../loadable/pending';
6 | import { InnerLoadable } from '../loadable/toLoadable';
7 | import { DefaultComponentImport, DefaultImport, DefaultImportedComponent, Loadable } from '../types';
8 | import { isBackend } from '../utils/detectBackend';
9 | import { es6import } from '../utils/utils';
10 | import { streamContext } from './context';
11 |
12 | function loadLoadable(loadable: Loadable, callback: (l: any) => void) {
13 | const upd = () => callback({});
14 |
15 | loadable.loadIfNeeded().then(upd, upd);
16 | }
17 |
18 | function updateLoadable(loadable: Loadable, callback: (l: any) => void) {
19 | // support HMR
20 | if (process.env.NODE_ENV === 'development') {
21 | const upd = () => callback({});
22 |
23 | (loadable as InnerLoadable)._probeChanges().then((changed) => changed && upd(), upd);
24 | }
25 | }
26 |
27 | interface ImportedShape {
28 | imported?: T;
29 | error?: any;
30 | loading?: boolean;
31 |
32 | loadable: Loadable;
33 |
34 | retry(): void;
35 | }
36 |
37 | interface HookOptions {
38 | import?: boolean;
39 | track?: boolean;
40 | }
41 |
42 | /**
43 | * react hook to wrap `import` with a tracker
44 | * used by {@link useImported}
45 | * @internal
46 | */
47 | export function useLoadable(loadable: Loadable, options: HookOptions = {}) {
48 | const UID = useContext(streamContext);
49 |
50 | const wasDone = loadable.done;
51 |
52 | const [, forceUpdate] = useState({});
53 |
54 | useMemo(() => {
55 | if (options.import !== false) {
56 | if (options.track !== false) {
57 | consumeMark(UID, loadable.mark);
58 | }
59 |
60 | if (!wasDone) {
61 | loadLoadable(loadable, forceUpdate);
62 | } else {
63 | updateLoadable(loadable, forceUpdate);
64 | }
65 | }
66 |
67 | return true;
68 | }, [loadable, options.import, options.track]);
69 |
70 | if (isBackend && !isItReady() && loadable.isLoading()) {
71 | /* tslint:disable:next-line no-console */
72 | console.error(
73 | 'react-imported-component: trying to render a component which is not ready. You should `await whenComponentsReady()`?'
74 | );
75 | }
76 |
77 | // use mark
78 | // retry
79 | const retry = useCallback(() => {
80 | if (!loadable) {
81 | return;
82 | }
83 |
84 | loadable.reset();
85 | forceUpdate({});
86 | updateLoadable(loadable, forceUpdate);
87 | }, [loadable]);
88 |
89 | if (process.env.NODE_ENV !== 'production') {
90 | if (isBackend) {
91 | if (!loadable.done) {
92 | /* tslint:disable:next-line no-console */
93 | console.error(
94 | 'react-imported-component: using not resolved loadable. You should `await whenComponentsReady()`.'
95 | );
96 | }
97 | }
98 | }
99 |
100 | return useMemo(
101 | () => ({
102 | loadable,
103 | retry,
104 | update: forceUpdate,
105 | }),
106 | [loadable, retry, forceUpdate]
107 | );
108 | }
109 |
110 | /**
111 | * short version of {@link useImported}
112 | * @param {Function} importer - an function with `import` inside it
113 | *
114 | * @return {Object}
115 | * - imported: if non empty - the data is loaded
116 | * - error: if non empty - there is an error
117 | * - loading: if true - then it's still loading
118 | * - loadable: the under laying reference
119 | * - retry: retry if case of failure
120 | *
121 | * @example
122 | * const { imported: Imported, loadable } = useImported(importer);
123 | * if (Imported) {
124 | * // yep, it's imported
125 | * return ;
126 | * }
127 | * // else throw resolution
128 | * throw loadable.resolution;
129 | */
130 | export function useImported(importer: DefaultImport | Loadable): ImportedShape;
131 | /**
132 | * The code splitting hook, the full version
133 | * @param {Function} importer - an function with `import` inside it
134 | * @param {Function} [exportPicker] - a "picker" of the export inside
135 | * @param {HookOptions} options
136 | * @param {Boolean} [options.import=true] - should the component be imported. Allow to defer execution.
137 | * @param {Boolean} [options.track=true] - allows disabling tracking of components, isolating them to SSR
138 | *
139 | * @return {Object}
140 | * - imported: if non empty - the data is loaded
141 | * - error: if non empty - there is an error
142 | * - loading: if true - then it's still loading
143 | * - loadable: the under laying reference
144 | * - retry: retry if case of failure
145 | *
146 | * @see if you dont need precise control consider(and loading Components) {@link useLazy}
147 | *
148 | * @example
149 | * const { imported: Imported, loadable } = useImported(importer, ({namedExport} => namedExport);
150 | * @example
151 | * const { imported: Imported, loadable } = useImported(importer);
152 | * if (Imported) {
153 | * // yep, it's imported
154 | * return ;
155 | * }
156 | * // else throw resolution
157 | * throw loadable.resolution;
158 | */
159 | export function useImported(
160 | importer: DefaultImport | Loadable,
161 | exportPicker: undefined | ((x: T) => K),
162 | options?: HookOptions
163 | ): ImportedShape;
164 |
165 | export function useImported(
166 | importer: DefaultImport | Loadable,
167 | exportPicker: undefined | ((x: T) => K) = es6import,
168 | options: HookOptions = {}
169 | ): ImportedShape {
170 | const topLoadable = getLoadable(importer);
171 | const { loadable, retry } = useLoadable(topLoadable, options);
172 |
173 | const { error, done, payload } = loadable;
174 | const loading = loadable.isLoading();
175 |
176 | return useMemo(() => {
177 | if (error) {
178 | return {
179 | error,
180 | loadable,
181 | retry,
182 | };
183 | }
184 |
185 | if (done) {
186 | return {
187 | imported: exportPicker(payload as T),
188 | loadable,
189 | retry,
190 | };
191 | }
192 |
193 | return {
194 | loading,
195 | loadable,
196 | retry,
197 | };
198 | }, [error, loading, payload, loadable]);
199 | }
200 |
201 | /**
202 | * A mix of React.lazy and useImported - uses React.lazy for Component and `useImported` to track the promise
203 | * not "retry"-able
204 | * @see if you need precise control consider {@link useImported}
205 | * @example
206 | * const Component = useLazy(() => import('./MyComponent');
207 | * return // throws to SuspenseBoundary if not ready
208 | */
209 | export function useLazy(importer: DefaultComponentImport): LazyExoticComponent> {
210 | const [{ resolve, reject, lazyComponent }] = useState(() => {
211 | /* tslint:disable no-shadowed-variable */
212 | let resolve: any;
213 | let reject: any;
214 | const promise = new Promise>((rs, rej) => {
215 | resolve = rs;
216 | reject = rej;
217 | });
218 |
219 | return {
220 | resolve,
221 | reject,
222 | lazyComponent: lazy(() => promise),
223 | };
224 | /* tslint:enable */
225 | });
226 |
227 | const { error, imported } = useImported(importer);
228 |
229 | useEffect(() => {
230 | if (error) {
231 | reject!(error);
232 | }
233 |
234 | if (imported) {
235 | resolve!(imported);
236 | }
237 | }, [error, imported]);
238 |
239 | return lazyComponent;
240 | }
241 |
--------------------------------------------------------------------------------
/src/utils/detectBackend.ts:
--------------------------------------------------------------------------------
1 | import { isNode } from 'detect-node-es';
2 |
3 | export const isBackend = isNode || typeof window === 'undefined';
4 |
--------------------------------------------------------------------------------
/src/utils/helpers.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * helper function to remap imports
3 | * @param x
4 | * @param map
5 | */
6 | export function remapImports(x: Promise, map: (a: T) => Y): Promise {
7 | return x.then(map);
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/signatures.ts:
--------------------------------------------------------------------------------
1 | import { AnyFunction, Mark } from '../types';
2 |
3 | const trimImport = (str: string) => str.replace(/['"]/g, '');
4 |
5 | export const importMatch = (functionString: string): Mark => {
6 | const markMatches = functionString.match(/`imported_(.*?)_component`/g) || [];
7 |
8 | return markMatches.map((match) => match && trimImport((match.match(/`imported_(.*?)_component`/i) || [])[1]));
9 | };
10 |
11 | /**
12 | * the intention of this function is to "clear some (minification) noise from the function
13 | * basically from file to file different "short" names could be used
14 | * @param fn
15 | */
16 | export const getFunctionSignature = (fn: AnyFunction | string): string =>
17 | String(fn)
18 | // quotes
19 | .replace(/(["'])/g, '`')
20 |
21 | // comments
22 | .replace(/\/\*([^\*]*)\*\//gi, '')
23 |
24 | // webpack specific
25 | .replace(/Promise.resolve\([^)]*\)/, '-we()')
26 | .replace(/\w+.e\([^)]*\)/, '-we()')
27 | .replace(/\w+.\w.bind\(/, '-wbind(')
28 | .replace(/\w+.bind\(/, '-wbind(')
29 |
30 | // prefix imported
31 | .replace(/([A-z0-9_]+)\(`imported_/g, '$(`imported_');
32 |
--------------------------------------------------------------------------------
/src/utils/useClientPhase.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 |
3 | import { importedState } from '../ui/ImportedController';
4 |
5 | /**
6 | * returns "true" if currently is a "client" phase and all features should be active
7 | * @see {@link HydrationController}
8 | */
9 | export const useIsClientPhase = (): boolean => {
10 | const value = useContext(importedState);
11 |
12 | if (!value) {
13 | if (process.env.NODE_ENV !== 'production') {
14 | // tslint:disable-next-line:no-console
15 | console.warn('react-imported-component: please wrap your entire application with ImportedController');
16 | }
17 |
18 | return true;
19 | }
20 |
21 | return value.pastHydration;
22 | };
23 |
--------------------------------------------------------------------------------
/src/utils/utils.ts:
--------------------------------------------------------------------------------
1 | import { Default } from '../types';
2 |
3 | // eslint-disable-next-line @typescript-eslint/ban-types
4 | type ObjectOrFunction = object | (() => any);
5 |
6 | export function asDefault(mayBeNotDefault: T | Default): Default {
7 | if ('default' in mayBeNotDefault) {
8 | return mayBeNotDefault;
9 | }
10 |
11 | return {
12 | default: mayBeNotDefault,
13 | };
14 | }
15 |
16 | export const es6import = (module: any) => (module.default ? module.default : module);
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "allowSyntheticDefaultImports": true,
5 | "strict": true,
6 | "strictNullChecks": true,
7 | "strictFunctionTypes": true,
8 | "noImplicitThis": true,
9 | "alwaysStrict": true,
10 | "noUnusedLocals": true,
11 | "noUnusedParameters": true,
12 | "noImplicitReturns": true,
13 | "noFallthroughCasesInSwitch": true,
14 | "noImplicitAny": true,
15 | "removeComments": false,
16 | "importHelpers": true,
17 | "target": "es6",
18 | "moduleResolution": "node",
19 | "lib": ["dom", "es5", "scripthost", "es2015.collection", "es2015.symbol", "es2015.iterable", "es2015.promise"],
20 | "types": ["node", "jest"],
21 | "typeRoots": ["./node_modules/@types"],
22 | "jsx": "react"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-react", "tslint-react-hooks", "tslint-config-prettier"],
3 | "linterOptions": {
4 | "exclude": ["node_modules/**/*.ts"]
5 | },
6 | "rules": {
7 | "no-bitwise": false,
8 | "quotemark": [true, "single", "jsx-double"],
9 | "no-unused-expression": [true, "allow-fast-null-checks"],
10 | "object-literal-sort-keys": false,
11 | "interface-name": false,
12 | "no-var-requires": false,
13 | "jsx-no-lambda": false,
14 | "max-classes-per-file": false,
15 | "jsx-self-close": false
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/wrapper.js:
--------------------------------------------------------------------------------
1 | var importedWrapper = function(marker, realImport) {
2 | if (typeof __deoptimization_sideEffect__ !== 'undefined') {
3 | __deoptimization_sideEffect__(marker, realImport);
4 | }
5 | return realImport;
6 | };
7 |
8 | module.exports = importedWrapper;
9 |
--------------------------------------------------------------------------------