├── .babelrc ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── app.js ├── components │ ├── Home.js │ ├── Profile.js │ └── Terms.js ├── index.html ├── npm-debug.log ├── package.json └── webpack.config.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── index.d.ts └── index.js └── tests ├── index.js └── ts ├── index.tsx └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": [ 4 | "transform-class-properties", 5 | ["transform-es2015-classes", { "loose":true }], 6 | "transform-object-assign", 7 | ["transform-react-jsx", { "pragma":"h" }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint:recommended", 4 | "plugins": [ 5 | "react" 6 | ], 7 | "env": { 8 | "browser": true, 9 | "mocha": true, 10 | "es6": true, 11 | "node": true 12 | }, 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "modules": true, 16 | "jsx": true 17 | } 18 | }, 19 | "globals": { 20 | "sinon": true, 21 | "expect": true 22 | }, 23 | "rules": { 24 | "react/jsx-uses-react": 2, 25 | "react/jsx-uses-vars": 2, 26 | "no-unused-vars": [1, { "varsIgnorePattern": "^h$" }], 27 | "no-cond-assign": 1, 28 | "no-empty": 0, 29 | "no-console": 1, 30 | "semi": 2, 31 | "camelcase": 0, 32 | "comma-style": 2, 33 | "comma-dangle": [2, "never"], 34 | "indent": [2, "tab", {"SwitchCase": 1}], 35 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"], 36 | "no-trailing-spaces": [2, { "skipBlankLines": true }], 37 | "max-nested-callbacks": [2, 5], 38 | "no-eval": 2, 39 | "no-implied-eval": 2, 40 | "no-new-func": 2, 41 | "guard-for-in": 2, 42 | "eqeqeq": 0, 43 | "no-else-return": 2, 44 | "no-redeclare": 2, 45 | "no-dupe-keys": 2, 46 | "radix": 2, 47 | "strict": [2, "never"], 48 | "no-shadow": 0, 49 | "callback-return": [1, ["callback", "cb", "next", "done"]], 50 | "no-delete-var": 2, 51 | "no-undef-init": 2, 52 | "no-shadow-restricted-names": 2, 53 | "handle-callback-err": 0, 54 | "no-lonely-if": 2, 55 | "keyword-spacing": 2, 56 | "constructor-super": 2, 57 | "no-this-before-super": 2, 58 | "no-dupe-class-members": 2, 59 | "no-const-assign": 2, 60 | "prefer-spread": 2, 61 | "no-useless-concat": 2, 62 | "no-var": 2, 63 | "object-shorthand": 2, 64 | "prefer-arrow-callback": 2 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | example/public -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Prateek Bhatnagar 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # preact-async-route 2 | [![build](https://api.travis-ci.org/prateekbh/preact-async-route.svg?branch=master)](https://api.travis-ci.org/prateekbh/preact-async-route.svg?branch=master) 3 | [![gzip size](http://img.badgesize.io/https://unpkg.com/preact-async-route/dist/index.min.js?compression=gzip)](https://unpkg.com/preact-async-route/dist/index.min.js) 4 | 5 | ## Deprecation notice 6 | `preact-x` supports `Lazy` component, which can be used as shown [here](https://reactjs.org/docs/code-splitting.html#reactlazy). 7 | Prefer using the `Lazy` component along with `Suspense` instead of this package. 8 | 9 | This package is still useful for preact versions < 10 10 | 11 | -------------------- 12 | 13 | Async route component for [preact-router](https://github.com/developit/preact-router) 14 | 15 | `npm i -D preact-async-route` 16 | 17 | preact-async-route provides ` ` tag to load your components lazily. 18 | 19 | ` ` provides similar props to return a lazily loaded component either as a Promise resolving to the component or return the component in a callback. 20 | 21 | ` ` also has a loading props, to which you can pass a component to be shown while the component is being lazily loaded. 22 | 23 | ## Version 2.0 24 | Version 2.0 brings support for a new prop `component` 25 | in order to make usage of already imported components now preact-async-route will support 2 props 26 | 27 | 1. `component` this will just take the JSX component itself and NOT the function 28 | 2. for function calls `getComponent` is the prop 29 | 30 | check README :point_down: 31 | 32 | 33 | ## Usage Example 34 | ```js 35 | import { h, render } from 'preact'; 36 | import Router, from 'preact-router'; 37 | import AsyncRoute from 'preact-async-route'; 38 | import Home from './Components/Home/Home.jsx'; 39 | import Terms from './Components/Terms/Terms.jsx'; 40 | import Loading from './Components/Loading/Loading.jsx'; 41 | /** @jsx h */ 42 | 43 | /** 44 | arguments passed to getComponent: 45 | url -- matched url 46 | cb -- in case you are not returning a promise 47 | props -- props that component will recive upon being loaded 48 | */ 49 | function getProfile(url, cb, props){ 50 | return import('../component/Profile/Profile.jsx').then(module => module.default); 51 | } 52 | 53 | const Main = () => ( 54 | 55 | 56 | 57 | 58 | {return }} /> 60 | 61 | ); 62 | ``` 63 | 64 | ### License 65 | 66 | [MIT] 67 | 68 | [MIT]: http://choosealicense.com/licenses/mit/ 69 | -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | import { h, render } from 'preact'; 2 | import {Router, Route, route} from 'preact-router'; 3 | import AsyncRoute from '../src/'; 4 | import Home from './components/Home'; 5 | 6 | function getProfile() { 7 | return new Promise(resolve=>{ 8 | setTimeout(()=>{ 9 | System.import('./components/Profile').then(module => {resolve(module.default);}); 10 | },2000); 11 | }); 12 | } 13 | 14 | function getTerms() { 15 | return new Promise(resolve=>{ 16 | setTimeout(()=>{ 17 | System.import('./components/Terms').then(module => {resolve(module.default);}); 18 | },2000); 19 | }); 20 | } 21 | 22 | render( 23 | 24 | 25 | 26 | {return (loading2...);}}/> 27 | , 28 | document.getElementById('app') 29 | ); -------------------------------------------------------------------------------- /example/components/Home.js: -------------------------------------------------------------------------------- 1 | import {h, Component} from 'preact'; 2 | import {Link} from 'preact-router'; 3 | 4 | export default class Home extends Component { 5 | render() { 6 | return

7 | This is home page 8 | Prateek 9 |

; 10 | } 11 | } -------------------------------------------------------------------------------- /example/components/Profile.js: -------------------------------------------------------------------------------- 1 | import {h, Component} from 'preact'; 2 | import {Link, route} from 'preact-router'; 3 | 4 | export default class Home extends Component { 5 | routeToLink() { 6 | route('/terms'); 7 | } 8 | render() { 9 | return

10 | This is Profile page of {this.props.matches.pid} 11 |
blah profile
12 | terms 13 |
terms via route
14 |

; 15 | } 16 | } -------------------------------------------------------------------------------- /example/components/Terms.js: -------------------------------------------------------------------------------- 1 | import {h, Component} from 'preact'; 2 | 3 | export default class Terms extends Component { 4 | render() { 5 | return

This is terms and conditions page

; 6 | } 7 | } -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /example/npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'run', 'test' ] 3 | 2 info using npm@4.0.5 4 | 3 info using node@v7.4.0 5 | 4 verbose run-script [ 'pretest', 'test', 'posttest' ] 6 | 5 info lifecycle example@1.0.0~pretest: example@1.0.0 7 | 6 silly lifecycle example@1.0.0~pretest: no script for pretest, continuing 8 | 7 info lifecycle example@1.0.0~test: example@1.0.0 9 | 8 verbose lifecycle example@1.0.0~test: unsafe-perm in lifecycle true 10 | 9 verbose lifecycle example@1.0.0~test: PATH: /usr/local/lib/node_modules/npm/bin/node-gyp-bin:/Users/prateekbh/projects/preact-async-route/example/node_modules/.bin:/usr/local/git/current/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin 11 | 10 verbose lifecycle example@1.0.0~test: CWD: /Users/prateekbh/projects/preact-async-route/example 12 | 11 silly lifecycle example@1.0.0~test: Args: [ '-c', 'echo "Error: no test specified" && exit 1' ] 13 | 12 silly lifecycle example@1.0.0~test: Returned: code: 1 signal: null 14 | 13 info lifecycle example@1.0.0~test: Failed to exec test script 15 | 14 verbose stack Error: example@1.0.0 test: `echo "Error: no test specified" && exit 1` 16 | 14 verbose stack Exit status 1 17 | 14 verbose stack at EventEmitter. (/usr/local/lib/node_modules/npm/lib/utils/lifecycle.js:279:16) 18 | 14 verbose stack at emitTwo (events.js:106:13) 19 | 14 verbose stack at EventEmitter.emit (events.js:191:7) 20 | 14 verbose stack at ChildProcess. (/usr/local/lib/node_modules/npm/lib/utils/spawn.js:40:14) 21 | 14 verbose stack at emitTwo (events.js:106:13) 22 | 14 verbose stack at ChildProcess.emit (events.js:191:7) 23 | 14 verbose stack at maybeClose (internal/child_process.js:885:16) 24 | 14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) 25 | 15 verbose pkgid example@1.0.0 26 | 16 verbose cwd /Users/prateekbh/projects/preact-async-route/example 27 | 17 error Darwin 16.4.0 28 | 18 error argv "/usr/local/bin/node" "/usr/local/bin/npm" "run" "test" 29 | 19 error node v7.4.0 30 | 20 error npm v4.0.5 31 | 21 error code ELIFECYCLE 32 | 22 error example@1.0.0 test: `echo "Error: no test specified" && exit 1` 33 | 22 error Exit status 1 34 | 23 error Failed at the example@1.0.0 test script 'echo "Error: no test specified" && exit 1'. 35 | 23 error Make sure you have the latest version of node.js and npm installed. 36 | 23 error If you do, this is most likely a problem with the example package, 37 | 23 error not with npm itself. 38 | 23 error Tell the author that this fails on your system: 39 | 23 error echo "Error: no test specified" && exit 1 40 | 23 error You can get information on how to open an issue for this project with: 41 | 23 error npm bugs example 42 | 23 error Or if that isn't available, you can get their info via: 43 | 23 error npm owner ls example 44 | 23 error There is likely additional logging output above. 45 | 24 verbose exit [ 1, true ] 46 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "babel-core": "^6.23.1", 14 | "html-webpack-plugin": "^2.28.0", 15 | "preact": "^7.2.0", 16 | "preact-router": "^2.4.1", 17 | "webpack": "^2.2.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const config = { 3 | entry: { 4 | app: './app' 5 | }, 6 | output: { 7 | path: __dirname + '/public/js', 8 | publicPath: '/public/js/', 9 | filename: '[name].js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | loader: 'babel-loader', 15 | test: /\.(js|jsx)$/, 16 | exclude: /node_modules/, 17 | options: { 18 | presets: [['es2015', {"modules": false}]], 19 | plugins:[ 20 | ["transform-react-jsx", { "pragma": "h" }] 21 | ] 22 | } 23 | } 24 | ] 25 | }, 26 | plugins: [new HtmlWebpackPlugin({ 27 | filename: '../../index.html', 28 | templateContent: '
' 29 | })] 30 | }; 31 | module.exports = config; -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | frameworks: ['mocha', 'chai-sinon'], 4 | reporters: ['mocha'], 5 | browsers: ['PhantomJS'], 6 | 7 | files: ['tests/**/*.js'], 8 | 9 | preprocessors: { 10 | '{src,tests}/**/*.js': ['webpack', 'sourcemap'] 11 | }, 12 | 13 | webpack: { 14 | module: { 15 | loaders: [{ 16 | test: /\.js?$/, 17 | exclude: /node_modules/, 18 | loader: 'babel-loader' 19 | }] 20 | }, 21 | resolve: { 22 | alias: { 23 | src: __dirname+'/src' 24 | } 25 | } 26 | }, 27 | 28 | webpackMiddleware: { 29 | noInfo: true 30 | } 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preact-async-route", 3 | "version": "2.2.1", 4 | "description": "Async route component for preact-router", 5 | "main": "dist/index.min.js", 6 | "jsnext:main": "src/index.js", 7 | "types": "src/index.d.ts", 8 | "scripts": { 9 | "clean": "rm -rf dist/*", 10 | "build": "npm-run-all clean transpile", 11 | "transpile": "rollup -c rollup.config.js", 12 | "test": "npm-run-all lint build test:karma test:types", 13 | "lint": "eslint {src,test}", 14 | "test:karma": "karma start --single-run", 15 | "test:types": "tsc --project tests/ts", 16 | "test:watch": "karma start", 17 | "prepublish": "npm-run-all build test", 18 | "release": "npm run build && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/prateekbh/preact-async-route.git" 23 | }, 24 | "keywords": [ 25 | "preact", 26 | "preact-router", 27 | "router" 28 | ], 29 | "files": [ 30 | "src", 31 | "dist" 32 | ], 33 | "author": "Prateek Bhatnagar", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/prateekbh/preact-async-route/issues" 37 | }, 38 | "homepage": "https://github.com/prateekbh/preact-async-route#readme", 39 | "devDependencies": { 40 | "Promise": "^1.0.5", 41 | "babel-cli": "^6.9.0", 42 | "babel-core": "^6.9.1", 43 | "babel-eslint": "^7.0.0", 44 | "babel-loader": "^6.2.4", 45 | "babel-plugin-transform-class-properties": "^6.9.1", 46 | "babel-plugin-transform-es2015-classes": "^6.9.0", 47 | "babel-plugin-transform-object-assign": "^6.0.0", 48 | "babel-plugin-transform-react-jsx": "^6.8.0", 49 | "babel-preset-es2015": "^6.9.0", 50 | "babel-preset-react": "^6.5.0", 51 | "babel-preset-stage-0": "^6.5.0", 52 | "chai": "^3.5.0", 53 | "eslint": "^3.0.0", 54 | "eslint-plugin-react": "^6.10.0", 55 | "karma": "^1.0.0", 56 | "karma-chai-sinon": "^0.1.5", 57 | "karma-mocha": "^1.0.1", 58 | "karma-mocha-reporter": "^2.0.3", 59 | "karma-phantomjs-launcher": "^1.0.0", 60 | "karma-sourcemap-loader": "^0.3.7", 61 | "karma-webpack": "^2.0.1", 62 | "mkdirp": "^0.5.1", 63 | "mocha": "^3.2.0", 64 | "npm-run-all": "^3.0.0", 65 | "preact": "*", 66 | "preact-router": "*", 67 | "rollup": "^0.41.4", 68 | "rollup-plugin-babel": "^2.7.1", 69 | "rollup-plugin-minify": "^1.0.3", 70 | "sinon": "^1.17.4", 71 | "sinon-chai": "^2.8.0", 72 | "typescript": "^2.7.2", 73 | "uglify-js": "^2.6.1", 74 | "webpack": "^2.2.1" 75 | }, 76 | "peerDependencies": { 77 | "preact": "*", 78 | "preact-router": "*" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import babel from 'rollup-plugin-babel'; 3 | import minify from 'rollup-plugin-minify'; 4 | const packageDetils = require(__dirname+'/package.json'); 5 | var babelRc = JSON.parse(fs.readFileSync('.babelrc','utf8')); // eslint-disable-line 6 | 7 | export default { 8 | entry: 'src/index.js', 9 | format: 'umd', 10 | sourceMap: true, 11 | moduleName: packageDetils.name, 12 | dest: 'dist/index.js', 13 | plugins: [ 14 | babel({ 15 | babelrc: false, 16 | presets: [ 17 | ['es2015', { loose:true, modules:false }] 18 | ].concat(babelRc.presets.slice(1)), 19 | plugins: babelRc.plugins, 20 | exclude: 'node_modules/**' 21 | }), 22 | minify({umd: 'dist/index.min.js'}) 23 | ] 24 | }; 25 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Component, FunctionalComponent } from 'preact'; 2 | 3 | interface IAsyncRouteProps { 4 | path: string; 5 | component?: any; 6 | getComponent?: ( 7 | this: AsyncRoute, 8 | url: string, 9 | callback: (component: any) => void, 10 | props: any 11 | ) => Promise | void; 12 | loading?: () => JSX.Element; 13 | [key:string]: any; 14 | } 15 | 16 | export default class AsyncRoute extends Component { 17 | public render(): JSX.Element | null; 18 | } 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | 3 | class AsyncRoute extends Component { 4 | constructor() { 5 | super(); 6 | this.state = { 7 | componentData: null 8 | }; 9 | } 10 | loadComponent(){ 11 | if (this.props.component) { 12 | return this.setState({ 13 | componentData: this.props.component 14 | }); 15 | } 16 | const componentData = this.props.getComponent(this.props.url, ({component}) => { 17 | // Named param for making callback future proof 18 | if (component) { 19 | this.setState({ 20 | componentData: component 21 | }); 22 | } 23 | }, Object.assign({}, this.props, this.props.matches)); 24 | 25 | // In case returned value was a promise 26 | if (componentData && componentData.then) { 27 | // IIFE to check if a later ending promise was creating a race condition 28 | // Check test case for more info 29 | ((url)=>{ 30 | componentData.then(component => { 31 | if (url !== this.props.url) { 32 | this.setState({componentData: null}, () => { 33 | this.loadComponent(); 34 | }); 35 | return; 36 | } 37 | this.setState({ 38 | componentData: component 39 | }); 40 | }); 41 | })(this.props.url); 42 | } 43 | } 44 | componentWillReceiveProps(nextProps){ 45 | if (this.props.path && this.props.path !== nextProps.path) { 46 | this.setState({ 47 | componentData: null 48 | }, ()=>{ 49 | this.loadComponent(); 50 | }); 51 | } 52 | } 53 | componentWillMount(){ 54 | this.loadComponent(); 55 | } 56 | render(){ 57 | if (this.state.componentData) { 58 | return h(this.state.componentData, this.props); 59 | } else if (this.props.loading) { 60 | const loadingComponent = this.props.loading(); 61 | return loadingComponent; 62 | } 63 | return null; 64 | } 65 | } 66 | 67 | export default AsyncRoute; 68 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | import { h, render, Component, options } from 'preact'; 2 | import Router,{route} from 'preact-router'; 3 | import AsyncRoute from 'src/index'; 4 | import Promise from 'Promise'; 5 | 6 | describe('Async Route', () => { 7 | options.syncComponentUpdates = false; 8 | options.debounceRendering = f => f(); 9 | class SampleTag extends Component { 10 | render(){ 11 | return (

hi

); 12 | } 13 | } 14 | class ParameterizedSampleTag extends Component { 15 | render(){ 16 | return (

hi - {this.props.matches.pid}

); 17 | } 18 | } 19 | 20 | it('should call the given function on mount', () => { 21 | let getComponent = sinon.spy(); 22 | render(, document.createElement('div')); 23 | expect(getComponent).called; 24 | }); 25 | 26 | it('should render component when returned from the callback', () => { 27 | let containerTag = document.createElement('div'); 28 | let getComponent = function(url, cb) { 29 | cb({component: SampleTag}); 30 | }; 31 | render(, containerTag); 32 | expect(containerTag.innerHTML).equal('

hi

'); 33 | }); 34 | 35 | it('should render component when resolved through a promise from a function', () => { 36 | let containerTag = document.createElement('div'); 37 | const startTime = Date.now(); 38 | const componentPromise = new Promise(resolve=>{ 39 | setTimeout(()=>{ 40 | resolve(SampleTag); 41 | },800); 42 | }); 43 | 44 | let getComponent = function() { 45 | return componentPromise; 46 | }; 47 | 48 | render(, containerTag); 49 | 50 | componentPromise.then(()=>{ 51 | const endTime = Date.now(); 52 | expect(endTime - startTime).to.be.greaterThan(800); 53 | expect(containerTag.innerHTML).equal('

hi

'); 54 | }); 55 | }); 56 | 57 | it('should render loading component while component is not resolved', () => { 58 | let containerTag = document.createElement('div'); 59 | const startTime = Date.now(); 60 | const componentPromise = new Promise(resolve=>{ 61 | setTimeout(()=>{ 62 | resolve(SampleTag); 63 | },800); 64 | }); 65 | 66 | let getComponent = function() { 67 | return componentPromise; 68 | }; 69 | 70 | render( loading...} getComponent={getComponent} />, containerTag); 71 | 72 | expect(containerTag.innerHTML).equal('loading...'); 73 | 74 | componentPromise.then(()=>{ 75 | const endTime = Date.now(); 76 | expect(endTime - startTime).to.be.greaterThan(800); 77 | expect(containerTag.innerHTML).equal('

hi

'); 78 | }); 79 | }); 80 | 81 | it('should get all props', () => { 82 | let containerTag = document.createElement('div'); 83 | class PropsTag extends Component { 84 | render(){ 85 | return (

hi - {this.props.matches.pid} - {this.props.sequence}

); 86 | } 87 | } 88 | let getComponent = function(url, cb) { 89 | cb({component: PropsTag}); 90 | }; 91 | render(, containerTag); 92 | route('/profile/Prateek'); 93 | expect(containerTag.innerHTML).equal('

hi - Prateek - 1

'); 94 | route('/profile/Jason'); 95 | expect(containerTag.innerHTML).equal('

hi - Jason - 1

'); 96 | }); 97 | 98 | it('should update on url change for same component', () => { 99 | let containerTag = document.createElement('div'); 100 | let getComponent = function(url, cb) { 101 | cb({component: ParameterizedSampleTag}); 102 | }; 103 | render(, containerTag); 104 | route('/profile/Prateek'); 105 | expect(containerTag.innerHTML).equal('

hi - Prateek

'); 106 | route('/profile/Jason'); 107 | expect(containerTag.innerHTML).equal('

hi - Jason

'); 108 | }); 109 | 110 | it('should mount correct component in case of race conditions', (done) => { 111 | let containerTag = document.createElement('div'); 112 | let getParameterizedComponent = function(url, cb) { 113 | return new Promise(resolve=>{ 114 | setTimeout(()=>{ 115 | resolve(ParameterizedSampleTag); 116 | },200); 117 | }); 118 | }; 119 | let getComponent = function(url, cb) { 120 | return new Promise(resolve=>{ 121 | setTimeout(()=>{ 122 | resolve(SampleTag); 123 | },1); 124 | }); 125 | }; 126 | render(, containerTag); 127 | route('/profile/Prateek'); 128 | route('/'); 129 | setTimeout(()=>{ 130 | expect(containerTag.innerHTML).equal('

hi

'); 131 | done(); 132 | },400); 133 | }); 134 | 135 | it('should pass matches to getComponent', () => { 136 | let containerTag = document.createElement('div'); 137 | let controlMatch = Math.random().toString(); 138 | let controlProp = Math.random(); 139 | let recivedMatch; 140 | let recivedProp; 141 | 142 | let getComponent = function(url, cb, props) { 143 | recivedMatch = props.pid; 144 | recivedProp = props.sequence; 145 | cb({component: props => null}); 146 | }; 147 | render(, containerTag); 148 | route('/profile/' + controlMatch); 149 | expect(recivedMatch).equal(controlMatch); 150 | expect(recivedProp).equal(recivedProp); 151 | }) 152 | }); 153 | -------------------------------------------------------------------------------- /tests/ts/index.tsx: -------------------------------------------------------------------------------- 1 | import { h, render, Component } from 'preact'; 2 | import Router from 'preact-router'; 3 | import AsyncRoute from '../../'; 4 | 5 | /** 6 | * This dummy component is used to catch TypeScript 7 | * type issues via the TypeScript compiler. 8 | */ 9 | 10 | function componentFetcher(url: string, cb: (c: any) => void, props: any): Promise | void {} 11 | function loadingAnimation(): JSX.Element | any { 12 | return
; 13 | } 14 | type LabelProps = { 15 | value: string 16 | } 17 | 18 | function labelize({props}: { props: LabelProps }): JSX.Element { 19 | return 20 | } 21 | export class Index extends Component<{}, {}> { 22 | public render(): JSX.Element { 23 | return 24 | 25 | 26 | 27 | 28 | ; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": [ 5 | "es6", 6 | "dom" 7 | ], 8 | "noImplicitAny": true, 9 | "noImplicitThis": true, 10 | "strictNullChecks": false, 11 | "typeRoots": [ 12 | "../../" 13 | ], 14 | "types": [], 15 | "noEmit": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "jsx": "react", 18 | "jsxFactory": "h" 19 | }, 20 | "files": [ 21 | "index.tsx", 22 | "../../src/index.d.ts" 23 | ] 24 | } --------------------------------------------------------------------------------