Repository: prateekbh/preact-async-route Branch: master Commit: a06eb0792cb7 Files: 22 Total size: 22.2 KB Directory structure: gitextract_n6wo97o8/ ├── .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.json ├── rollup.config.js ├── src/ │ ├── index.d.ts │ └── index.js └── tests/ ├── index.js └── ts/ ├── index.tsx └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": ["es2015", "stage-0", "react"], "plugins": [ "transform-class-properties", ["transform-es2015-classes", { "loose":true }], "transform-object-assign", ["transform-react-jsx", { "pragma":"h" }] ] } ================================================ FILE: .eslintrc ================================================ { "parser": "babel-eslint", "extends": "eslint:recommended", "plugins": [ "react" ], "env": { "browser": true, "mocha": true, "es6": true, "node": true }, "parserOptions": { "ecmaFeatures": { "modules": true, "jsx": true } }, "globals": { "sinon": true, "expect": true }, "rules": { "react/jsx-uses-react": 2, "react/jsx-uses-vars": 2, "no-unused-vars": [1, { "varsIgnorePattern": "^h$" }], "no-cond-assign": 1, "no-empty": 0, "no-console": 1, "semi": 2, "camelcase": 0, "comma-style": 2, "comma-dangle": [2, "never"], "indent": [2, "tab", {"SwitchCase": 1}], "no-mixed-spaces-and-tabs": [2, "smart-tabs"], "no-trailing-spaces": [2, { "skipBlankLines": true }], "max-nested-callbacks": [2, 5], "no-eval": 2, "no-implied-eval": 2, "no-new-func": 2, "guard-for-in": 2, "eqeqeq": 0, "no-else-return": 2, "no-redeclare": 2, "no-dupe-keys": 2, "radix": 2, "strict": [2, "never"], "no-shadow": 0, "callback-return": [1, ["callback", "cb", "next", "done"]], "no-delete-var": 2, "no-undef-init": 2, "no-shadow-restricted-names": 2, "handle-callback-err": 0, "no-lonely-if": 2, "keyword-spacing": 2, "constructor-super": 2, "no-this-before-super": 2, "no-dupe-class-members": 2, "no-const-assign": 2, "prefer-spread": 2, "no-useless-concat": 2, "no-var": 2, "object-shorthand": 2, "prefer-arrow-callback": 2 } } ================================================ FILE: .gitignore ================================================ node_modules dist example/public ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - 4 ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 Prateek Bhatnagar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # preact-async-route [![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) [![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) ## Deprecation notice `preact-x` supports `Lazy` component, which can be used as shown [here](https://reactjs.org/docs/code-splitting.html#reactlazy). Prefer using the `Lazy` component along with `Suspense` instead of this package. This package is still useful for preact versions < 10 -------------------- Async route component for [preact-router](https://github.com/developit/preact-router) `npm i -D preact-async-route` preact-async-route provides ` ` tag to load your components lazily. ` ` provides similar props to return a lazily loaded component either as a Promise resolving to the component or return the component in a callback. ` ` also has a loading props, to which you can pass a component to be shown while the component is being lazily loaded. ## Version 2.0 Version 2.0 brings support for a new prop `component` in order to make usage of already imported components now preact-async-route will support 2 props 1. `component` this will just take the JSX component itself and NOT the function 2. for function calls `getComponent` is the prop check README :point_down: ## Usage Example ```js import { h, render } from 'preact'; import Router, from 'preact-router'; import AsyncRoute from 'preact-async-route'; import Home from './Components/Home/Home.jsx'; import Terms from './Components/Terms/Terms.jsx'; import Loading from './Components/Loading/Loading.jsx'; /** @jsx h */ /** arguments passed to getComponent: url -- matched url cb -- in case you are not returning a promise props -- props that component will recive upon being loaded */ function getProfile(url, cb, props){ return import('../component/Profile/Profile.jsx').then(module => module.default); } const Main = () => ( {return }} /> ); ``` ### License [MIT] [MIT]: http://choosealicense.com/licenses/mit/ ================================================ FILE: example/app.js ================================================ import { h, render } from 'preact'; import {Router, Route, route} from 'preact-router'; import AsyncRoute from '../src/'; import Home from './components/Home'; function getProfile() { return new Promise(resolve=>{ setTimeout(()=>{ System.import('./components/Profile').then(module => {resolve(module.default);}); },2000); }); } function getTerms() { return new Promise(resolve=>{ setTimeout(()=>{ System.import('./components/Terms').then(module => {resolve(module.default);}); },2000); }); } render( {return (loading2...);}}/> , document.getElementById('app') ); ================================================ FILE: example/components/Home.js ================================================ import {h, Component} from 'preact'; import {Link} from 'preact-router'; export default class Home extends Component { render() { return

This is home page Prateek

; } } ================================================ FILE: example/components/Profile.js ================================================ import {h, Component} from 'preact'; import {Link, route} from 'preact-router'; export default class Home extends Component { routeToLink() { route('/terms'); } render() { return

This is Profile page of {this.props.matches.pid}
blah profile
terms
terms via route

; } } ================================================ FILE: example/components/Terms.js ================================================ import {h, Component} from 'preact'; export default class Terms extends Component { render() { return

This is terms and conditions page

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

hi

); } } class ParameterizedSampleTag extends Component { render(){ return (

hi - {this.props.matches.pid}

); } } it('should call the given function on mount', () => { let getComponent = sinon.spy(); render(, document.createElement('div')); expect(getComponent).called; }); it('should render component when returned from the callback', () => { let containerTag = document.createElement('div'); let getComponent = function(url, cb) { cb({component: SampleTag}); }; render(, containerTag); expect(containerTag.innerHTML).equal('

hi

'); }); it('should render component when resolved through a promise from a function', () => { let containerTag = document.createElement('div'); const startTime = Date.now(); const componentPromise = new Promise(resolve=>{ setTimeout(()=>{ resolve(SampleTag); },800); }); let getComponent = function() { return componentPromise; }; render(, containerTag); componentPromise.then(()=>{ const endTime = Date.now(); expect(endTime - startTime).to.be.greaterThan(800); expect(containerTag.innerHTML).equal('

hi

'); }); }); it('should render loading component while component is not resolved', () => { let containerTag = document.createElement('div'); const startTime = Date.now(); const componentPromise = new Promise(resolve=>{ setTimeout(()=>{ resolve(SampleTag); },800); }); let getComponent = function() { return componentPromise; }; render( loading...} getComponent={getComponent} />, containerTag); expect(containerTag.innerHTML).equal('loading...'); componentPromise.then(()=>{ const endTime = Date.now(); expect(endTime - startTime).to.be.greaterThan(800); expect(containerTag.innerHTML).equal('

hi

'); }); }); it('should get all props', () => { let containerTag = document.createElement('div'); class PropsTag extends Component { render(){ return (

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

); } } let getComponent = function(url, cb) { cb({component: PropsTag}); }; render(, containerTag); route('/profile/Prateek'); expect(containerTag.innerHTML).equal('

hi - Prateek - 1

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

hi - Jason - 1

'); }); it('should update on url change for same component', () => { let containerTag = document.createElement('div'); let getComponent = function(url, cb) { cb({component: ParameterizedSampleTag}); }; render(, containerTag); route('/profile/Prateek'); expect(containerTag.innerHTML).equal('

hi - Prateek

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

hi - Jason

'); }); it('should mount correct component in case of race conditions', (done) => { let containerTag = document.createElement('div'); let getParameterizedComponent = function(url, cb) { return new Promise(resolve=>{ setTimeout(()=>{ resolve(ParameterizedSampleTag); },200); }); }; let getComponent = function(url, cb) { return new Promise(resolve=>{ setTimeout(()=>{ resolve(SampleTag); },1); }); }; render(, containerTag); route('/profile/Prateek'); route('/'); setTimeout(()=>{ expect(containerTag.innerHTML).equal('

hi

'); done(); },400); }); it('should pass matches to getComponent', () => { let containerTag = document.createElement('div'); let controlMatch = Math.random().toString(); let controlProp = Math.random(); let recivedMatch; let recivedProp; let getComponent = function(url, cb, props) { recivedMatch = props.pid; recivedProp = props.sequence; cb({component: props => null}); }; render(, containerTag); route('/profile/' + controlMatch); expect(recivedMatch).equal(controlMatch); expect(recivedProp).equal(recivedProp); }) }); ================================================ FILE: tests/ts/index.tsx ================================================ import { h, render, Component } from 'preact'; import Router from 'preact-router'; import AsyncRoute from '../../'; /** * This dummy component is used to catch TypeScript * type issues via the TypeScript compiler. */ function componentFetcher(url: string, cb: (c: any) => void, props: any): Promise | void {} function loadingAnimation(): JSX.Element | any { return
; } type LabelProps = { value: string } function labelize({props}: { props: LabelProps }): JSX.Element { return } export class Index extends Component<{}, {}> { public render(): JSX.Element { return ; } } ================================================ FILE: tests/ts/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "lib": [ "es6", "dom" ], "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": false, "typeRoots": [ "../../" ], "types": [], "noEmit": true, "forceConsistentCasingInFileNames": true, "jsx": "react", "jsxFactory": "h" }, "files": [ "index.tsx", "../../src/index.d.ts" ] }