1.11.2 • Published 12 months ago

@pickjunk/min v1.11.2

Weekly downloads
131
License
MIT
Repository
github
Last release
12 months ago

min

NPM Version Build Status NPM Downloads

A Tiny React Framework. Routing, SSR, out-of-box webpack & babel configuration, make it work, but not annoying.

Quick Start

A mobile example with zarm:

$ npm init @pickjunk/min create mobile min-mobile

An admin example with antd:

$ npm init @pickjunk/min create mobile min-admin

Routes.js

Actually, the type definition of MIN's route is:

type Route {
  path?: string;
  name?: string;
  component: string;
  children?: Route[];
}

It's simple. With some features you might care about:

  • path will be concated to a complete route path without any implicit '/'. In other words, any '/' should not be ignored. You should write it explicitly in any time.

  • name is a useful but not required property. In other react routers, it's usually considered as a feature called Named Route for convenient imperative navigation. More detail is below in Imperative Router.

  • component path is always relative to routes.js.

  • Depth-First is the router matching strategy.

  • Default Route (the route without path) supported. If the matching complete in the parent of the default route, router will try to search the children of the parent. Once it check a route without path property, the default route is found.

Dynamic Routes

There are two ways to define a dynamic route:

  • Short Variable. It looks like :var. Convient, but not flexible. Noticed that it must end with a '/' or be the end of the route path. Otherwise, unexpected behavior will occur.

  • Regexp Variable. The format is (var:regexp). It's powerful but escaping consideration may be annoying. In fact, :var is equal to (var:[^/]+).

Router Arguments

Once you define a dynamic route, it's easy to get its arguments by useRouter hook.

import React from 'react';
import { useRouter } from '@pickjunk/min';

export default function World() {
  const {
    args: { who },
  } = useRouter();

  return <span>{who}</span>;
}

Imperative Router

push, replace, forward, back, go, link, six useful methods exported for imperative router actions.

import { router } from 'react';

// navigate with history.push
router.push('name or path', { args: 'args for named routes' });

// navigate with history.replace
router.replace('name or path', { args: 'args for named routes' });

// equal to history.forward
router.forward();

// equal to history.back
router.back();

// equal to history.go
router.go(2);

// generate url to render, very useful for named routes
router.link('name or path', { args: 'args for named routes' });

Production

It's really simple to release a MIN app.

// package.json
// Add build & start script.
"scripts": {
  "build": "min build -p",
  "start": "min start"
},
$ npm run build
$ npm start

That's all. A production server with SSR enabled is ready on port 8000.

Because of the demand of SSR and dynamic routes, the production server is necessary. In other words, MIN app can not be released as pure static resources without a matching server. It might be heavier than many traditional frontend frameworks. But I think it worth.

Additionally, you might need to optimize the bundles in production. Here is the analyze command:

// package.json
// Add analyze script.
"scripts": {
  "analyze": "min analyze"
},
$ npm run analyze

Command Line Tools

More defails for min can be found in min's help manual.

// package.json
// Add min script.
"scripts": {
  "help": "min"
},
$ npm run min

cli

Reversed Log

You might have noticed that when you run min dev, a logger is enabled by default:

cli

It's MIN's "Reversed Logger" developed based on a thought that simple browser logger (console.log) is not good enough in a web app, especially in production. In most of cases, we need to collect browser logs and report them to the server finally persisting the logs.

By default, MIN enable the reversed logger and provide the basically navigating log.

To use reversed logger in your code:

import { log } from '@pickjunk/min';

log.trace('Object or String');
log.debug('Object or String');
log.info('Object or String');
log.warn('Object or String');
log.error('Object or String');
log.fatal('Object or String');

To config your logger:

// create min.config.js in your project root directory

let config = {
  log: {
    // Optional. Null by default, means console. String, means file path.
    file: process.env.NODE_ENV === 'production' ? 'log/app.log' : null,
    // Optional. '/__log__' by default.
    endpoint: '/__log__',
  },

  // disable the logger
  log: false,
};

module.exports = config;

Advanced (Under the Hood)

The content from here on is not necessary to read. But if you are curious about how MIN works under the hood, topics below may be a good guide for you.

Code Splitting

MIN provide a simple and intuitive approach to concrete code splitting. How it works can be explained in one sentence - the component property of every Route node in routes.js will be transformed to a require.ensure loading function.

Yes, transform, a special webpack loader do this work to routes.js. No need to concern about code splitting too much because this behavior is good enough in most cases.

The only thing you should notice is always naming routes.js as 'routes.js'. Because the test rule of the webpack loader depend on it.

Better Router

There are two problems with that many other react router does not do well:

  • Unintuitive reconciliation for dynamic route components. The most popular case is defining a route like { path: '/post/:id', component: './Post' }. In many other react router, the Post Component will not be unmounted and then mounted in a navigation from /post/1 to post/2. It's really annoying because most people will write the data loading logic in component mounted hook and do not expect a extra prop listener for refreshing data. React Hooks (useEffect) may help you out nowadays. But honestly, it's still not intuitive.

  • Race condition in async route loading. Once you trigger two different navigations almost in the same time, this problem can't be ignored. If the former one completed after the latter one, "rollback" shows.

To be a better router, MIN has fixed these two problems:

  • All Route Components take their path as their key prop. For example above, <Post key={'/post/1'} /> and <Post key={'/post/2'} /> will be rendered. Tell react to reconcile intuitively.

  • Add switchMap operation to routing pipeline. Thanks to rxjs, race condition is fixed so elegantly. If you isn't familar with rxjs, a document and a marble diagram may help you.

SSR (Server Side Render)

Complicated topic. It contains many details about engineering but not theory.

Without doubt, MIN use ReactDOMServer.renderToString and ReactDOM.hydrate. But when facing to the problem about how to make a pretty app entry, which only export necessary interfaces but not export any annoying tech details about SSR, work become hard. You can't see ReactDOMServer.renderToString and ReactDOM.hydrate but a simple app() entry nowadays. MIN take all its effort to make it be.

The main code about SSR is in bin/ssr.js, src/index.ts and src/Router.ts. In development environment, situations in bin/dev.js is more complicated. Thanks to the serverSideRender option of webpack-dev-middleware, work should have been harder without it.

Besides, like other SSR supported frameworks, MIN also support the initialProps feature:

import React from 'react';
import { initialProps } from '@pickjunk/min/Router';

export default initialProps(function({ path, args, name }) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve({ path, args, name });
    }, 2000);
  });
})(function One({ path, args, name }) {
  return (
    <>
      <h4>This is one</h4>
      <h4>
        path:{path} args:{JSON.stringify(args)} name:{name}
      </h4>
    </>
  );
});

initialProps not only run in browser but also in SSR server. Make sure the code in it is isomorphic. Fetching data in initialProps and return a Promise result, a "pure" SSR app is born.

initialProps is a powerful feature. But it require isomorphic code which usually makes things complicated. "Pure" SSR app is not necessary in most cases. So it's not recommanded.

1.11.2

12 months ago

1.11.1

12 months ago

1.11.0

2 years ago

1.10.0

3 years ago

1.9.0

3 years ago

1.8.2

3 years ago

1.8.1

3 years ago

1.8.0

3 years ago

1.7.1

3 years ago

1.7.0

3 years ago

1.5.5

3 years ago

1.5.4

3 years ago

1.5.3

3 years ago

1.5.2

3 years ago

1.5.1

3 years ago

1.5.0

4 years ago

1.4.5

4 years ago

1.4.4

5 years ago

1.4.3

5 years ago

1.4.2

5 years ago

1.4.1

5 years ago

1.4.0

5 years ago

1.3.0

5 years ago

1.2.0-alpha.8

5 years ago

1.2.0-alpha.7

5 years ago

1.2.0-alpha.6

5 years ago

1.2.0-alpha.5

5 years ago

1.2.0-alpha.4

5 years ago

1.2.0-alpha.3

5 years ago

1.2.0-alpha.2

5 years ago

1.2.0-alpha.1

5 years ago

1.1.0

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago