npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aktiv/launcher

v2.0.0-next.4

Published

一个轻量级的,配置化的启动器

Readme

Launcher · NPM Build Status codecov

一个轻量级的,配置化的启动器

Install

npm install @aktiv/launcher

Introduction

在此之前,你的react项目可能需要安装 react-router react-router-dom redux react-redux redux-thunk...,现在,这些都不需要了,你只需要Launcher, 它内置了这些东西,并提供了一套简单的可配置化的开发方案, 查看内置库的版本

此外,它还提供了一套插件化的流程,使应用的变得更加可复用,可拓展

Get Started

import Launcher from '@aktiv/launcher';

const Home = () => {
    return <div>home</div>;
};
const About = () => {
    return <div>home</div>;
};

const RouterConfig = [
    {
        path: '/',
        component: Home,
    },
    {
        path: '/about',
        component: About,
    },
];

const app = new Launcher({
    routes: RouterConfig,
});

app.start();

它仅有一个必传的参数,就是你的router配置

整体而言,参数分为两类:routerstore

interface ConstructorOptionsType {
    hash?: boolean;
    routes: Array<RouteItem>;
    reducerConfig?: ReducerConfig;
    reducers?: ReducersMapObject;
    immerEnableES5?: boolean;
    routerBasePath?: string;
    rootNode?: string;
    reduxMiddleware?: Middleware[];
}

rootNode

ReactDOM.render 挂载的节点

router

hash

默认为browserRouter, 通过hash参数开启hashRouter

routerBasePath

公有的路由前缀,详情查看react-router: https://github.com/ReactTraining/react-router/blob/dev/docs/advanced-guides/migrating-5-to-6.md#move-basename-from-router-to-routes

routes

静态路由配置

interface RouteItemBase {
    path?: string;
    casessensitive?: boolean;
    children?: Array<RouteItem>;
    lazy?: boolean;
    title?: string;
}
export interface RouteItem extends RouteItemBase {
    redirect?: string;
    component?: ComponentType | (() => DynamicImportType);
    // plugin route options
    [x: string]: unknown;
}

在react-router的基础上我们扩展了 lazy、title和redirect选项

lazy

开启懒加载模式

title

在加载到当前路由时,launcher会自动帮你赋值 document.title

redirect

由于react-router v6 不再支持 redirect 组件,所以这里我们内置了redirect功能, 分为三种情况:

interface RouteItemBase {
    path?: string;
    casessensitive?: boolean;
    children?: Array<RouteItem>;
    lazy?: boolean;
    title?: string;
}

export interface RedirectRouteItem extends RouteItemBase {
    redirect: string;
}

export interface NormalRouteItem extends RouteItemBase {
    component: ComponentType | (() => DynamicImportType);
}

export interface ParentRedirectRoteItem extends RouteItemBase {
    redirect: string;
    component: ComponentType | (() => DynamicImportType);
}

需要注意的是,如果你的项目传入了,routerBasePath,正常情况下当你使用redirect,并传入绝对路径时应该加上前缀名,但是在这里我们已经帮您内置了,所以你可以忽略它

store

store的配置可能稍显复杂,首先这是我们的scr目录

actions
    |-list
    |-index.js
index.tsx

actions/list

import { createActions } from '@aktiv/launcher';
const state = {  // state的默认值
    a:"1" 
} 
const config  = {
    editorA:{
        key:'a',  // 发起dispatch的时候修改的对象state key
        payload:(data) => data
    }
}
export { 
    state,
    config,
    actions: createActions(config)
}

actions/index

import list from './list'
export { list }

index.tsx

import Launcher from '@aktiv/launcher';
import reducerConfig from './actions'

const app = new Launcher({
    routes: RouterConfig,
    reducerConfig
});

app.start();

这样的配置会产生如下的state

{
    list: {
        a: "1"
    }
}

使用dispatch:

import { actions as listActions } from './actions/list'

listActions.editorA('hello')

此时的state为

{
    list: {
        a: "hello"
    }
}

当然你也可以在payload里边做一些其它事情,比如发起请求

import { createActions } from '../src';
const state = {  // state的默认值
    a:"1" 
} 
const config  = {
    editorA:{
        key:'a',  // 发起dispatch的时候修改的对象state key

        payload:(data) => {
            return api(data)
        }

    }
}
export { 
    state,
    config,
    actions: createActions(config)
}

此时调用dispatch你的state如下:

{
    list: {
        a: "apiResponse"
    }
}

默认情况下,payload的返回值只会替换对应的state key,当然你也可以自定义处理

import { createActions } from '../src';
const state = {  // state的默认值
    a:"1" 
} 
const config  = {
    editorA:{
        key:'a',  // 发起dispatch的时候修改的对象state key

        payload:(data) => {
            return api(data)
        },
        // 自定义处理state只需要加上handle, 它接收当前的state 和你的payload
        handle:(state, payload) => {
            // 例如根据api的返回,改变state.a
            if(payload.data === true) {
                // 当然,你也可以改变 state.b  state.c 或者其它你想处理的state
                state.a="hello"
            } else {
                state.a='hi'
            }
        }
    }
}
export { 
    state,
    config,
    actions: createActions(config)
}

需要注意的是,我们内置了immer,并使用produce包裹了handle,所以你只需要对state直接赋值可以,并且不需要返回,由于state这时为immer的draft,所以如果你想debug,需要使用immer的功能,例如:

import { current } from '@aktiv/launcher';
// 我们提供了内置库的所有导出,你只需从launcher导出即可

const config  = {
    editorA:{
        key:'a',  // 发起dispatch的时候修改的对象state key

        payload:(data) => {
            return api(data)
        },
        // 自定义处理state只需要加上handle, 它接收当前的state 和你的payload
        handle:(state, payload) => {
            // debug state
            console.log(current(state))
            // 例如根据api的返回,改变state
            if(payload.data === true) {
                // 当然,你也可以改变 state.b  state.c 或者其它你想处理的state
                state.a="hello"
            } else {
                state.a='hi'
            }
        }
    }
}

详细使用可以查看immer官方文档:https://immerjs.github.io/immer/current

好了,这就是store的大致用法,除此之外,还有一些参数:immerEnableES5、reducers, reduxMiddleware

reduxMiddleware: 扩展的redux 中间件数组

immerEnableES5: 开启immer兼容es5,详情可查看immer官方文档:https://immerjs.github.io/immer/installation

reducers: 自定义的reducers,需要注意的是,这里你要像正常情况下一样使用不可变数据的写法,并返回它,因为这里并没有使用immer对其处理

plugin

plugin功能是launcher的最强大的功能,它基于router开发,一个plugin看起来是这样的

export interface Plugin {
    name: string;
    outer?: PluginOuterRenderType;
    inner?: PluginInnerRenderType;
    reducerConfig?: ReducerConfigItem;
}

最简单的场景是登陆接口请求成功之后,你才想展示你的应用, 并且把用户信息传递下去

import React, { useEffect, useState } from 'react';
import Launcher from '@aktiv/launcher';
const LoginProviderContext = React.createContext(null);

const LoginProvider = ({ children }) => {
    const [isLogin, setLogin] = useState(false);
    const [userInfo, setUserInfo] = useState({});

    useEffect(() => {
        loginApi()
            .then(res => {
                setLogin(true);
                setUserInfo(res.data);
            })
            .catch(error => {
                redirect('/login');
            });
    }, []);

    return isLogin ? (
        <LoginProviderContext.Provider value={userInfo}>{children}</LoginProviderContext.Provider>
    ) : null;
};

const loginPlugin = {
    name: 'login',
    // 第一个参数为内层的组件,第二个参数为 use 时传入的参数
    outer: (children, opt) => {
        <LoginProvider opt={opt}>{children}</LoginProvider>;
    },
};
const app = new Launcher({...});
// 通过第二个参数给插件传参
app.use(loginPlugin, opt)
app.start()

当然你可以拥有多个插件,只需注意插件的调用层级,在后边调用的会包裹在外层

outer

outer的例子如上所示,这里要说明的是outer是包裹在router外层的,所以你可以理解为全局唯一的

inner

inner常用的例子是对每个路由进行控制,因为它是包裹在每个route外层的,例如需要对每个路由进行鉴权

import React, { useEffect, useState, useContext } from 'react';
import Launcher, { useLocation } from '@aktiv/launcher';

const AuthContext = React.createContext()

const AuthProvider = ({children}) => {
    const [ authData, setAuthData ] = useState()
    useEffect(()=>{
        authApi()
            .then(res => {
                setAuthData(res.data)
            })
    },[])
    return authData ? (
        <AuthContext.Provider value={authData}>{children}</AuthContext.Provider>
    ) : null;
}

const AuthRouteComponent = ({ children, route }) => {
    const { hasAuth } = route
    const { pathname } = useLocation()
    const authInfo = useContext(AuthContext)

    if(hasAuth && !authInfo.has(pathname)){
        return '没有权限'
    }
    return children
}

const plugin = {
    name:'auth',
    outer:(children, opt) => {
        return <AuthProvider opt={opt}>{children}</AuthProvider>
    },
    inner:(children, route, opt) => {
        return <AuthRouteComponent route={route} opt={opt}>{children}</AuthRouteComponent>
    }
}

const Home = () => <div>home</div>
const About = () => <div>about</div>

const app = new Launcher({
    routes:[
        {
            path:'/',
            component:Home
        },
        {
            path:'/about',
            component:Home,
            // /about需要鉴权
            hasAuth:true
        }
    ]
})
app.use(plugin, opt)

reducerConfig

每个插件也具有参与store的能力

const authReducerConfig = {
    state:{
        a:1
    },
    config:{
        editorA:{
            key:'a',
            payload: data => data
        }
    }
}
const plugin = {
    name:"auth",
    inner:...,
    out:....,
    reducerConfig: authReducerConfig
}
app.use(plugin)

state如下

{
    auth:{
        a:1
    }
}

hooks

内置了一些便捷的hooks

useRouter

type UseRouterReturns = {
    redirect(path: string, state?: UseRouterState): void;
    replace(path: string, state?: UseRouterState): void;
};

redirect 仅在navigate包了一层

navigate(resultPath, {
    state,
});

replace 同样如此

navigate(resultPath, {
    replace: true,
    state,
});

此外,它们会当你传入绝对路径时,拼接你的 routerBasePath,但是由此带来的影响是,你传入的path只能为一个string

useQuery

获取当前路由的query参数

example current location: /home?a=123

const query = useQuery()
// query: { a:123 }

内置库

    "history": "^5.0.0",
    "immer": "^9.0.5",
    "react-hot-loader": "^4.13.0",
    "react-loadable": "^5.5.0",
    "react-redux": "^7.2.4",
    "react-router": "^6.0.0-beta.0",
    "react-router-dom": "^6.0.0-beta.0",
    "redux": "^4.1.0",
    "redux-thunk": "^2.3.0",

此外内部实现了一个基于 redux-promise 改造而来的 处理promise的 redux 中间件