feat: rework entry

This commit is contained in:
liuxiong.lx 2022-03-07 18:29:39 +08:00 committed by ClarkXia
parent 6bbc638e45
commit 72a6f3bfb7
33 changed files with 379 additions and 102 deletions

View File

@ -1,4 +1,20 @@
import * as React from 'react'; import * as React from 'react';
import { runApp } from 'ice'; // import { AppConfig } from 'ice';
runApp({}); // runApp({
// app: {
// getInitialData: async (ctx) => {
// console.log(ctx);
// return {
// auth: {
// admin: true,
// },
// };
// },
// },
// });
const appConfig = {
};
export default appConfig;

View File

@ -30,7 +30,7 @@ const getWebpackConfig: GetWebpackConfig = ({ rootDir, config }) => {
const webpackPlugins = getTransformPlugins(rootDir, config).map((plugin) => createUnplugin(() => plugin).webpack()); const webpackPlugins = getTransformPlugins(rootDir, config).map((plugin) => createUnplugin(() => plugin).webpack());
return { return {
mode, mode,
entry: path.join(rootDir, 'src/app'), entry: path.join(rootDir, '.ice/entry.client'),
externals, externals,
output: { output: {
publicPath, publicPath,
@ -44,6 +44,7 @@ const getWebpackConfig: GetWebpackConfig = ({ rootDir, config }) => {
resolve: { resolve: {
alias: { alias: {
ice: path.join(rootDir, '.ice', 'index.ts'), ice: path.join(rootDir, '.ice', 'index.ts'),
'@': path.join(rootDir, 'src'),
...alias, ...alias,
}, },
extensions: ['.ts', '.tsx', '.jsx', '...'], extensions: ['.ts', '.tsx', '.jsx', '...'],

View File

@ -22,6 +22,7 @@
"@builder/pack": "^0.6.0", "@builder/pack": "^0.6.0",
"@builder/webpack-config": "^3.0.0", "@builder/webpack-config": "^3.0.0",
"@ice/plugin-app": "^1.0.0", "@ice/plugin-app": "^1.0.0",
"@ice/plugin-auth": "^1.0.0",
"@ice/plugin-router": "^1.0.0", "@ice/plugin-router": "^1.0.0",
"@ice/runtime": "^1.0.0", "@ice/runtime": "^1.0.0",
"address": "^1.1.2", "address": "^1.1.2",

View File

@ -1,3 +1,4 @@
import * as path from 'path';
import consola from 'consola'; import consola from 'consola';
import type { Context } from 'build-scripts'; import type { Context } from 'build-scripts';
import type { StatsError } from 'webpack'; import type { StatsError } from 'webpack';
@ -5,6 +6,7 @@ import webpackCompiler from '../service/webpackCompiler.js';
import formatWebpackMessages from '../utils/formatWebpackMessages.js'; import formatWebpackMessages from '../utils/formatWebpackMessages.js';
import { getWebpackConfig, getTransformPlugins } from '@builder/webpack-config'; import { getWebpackConfig, getTransformPlugins } from '@builder/webpack-config';
import type { Config } from '@ice/types'; import type { Config } from '@ice/types';
import { config } from 'process';
const build = async (context: Context<any>) => { const build = async (context: Context<any>) => {
const { getConfig, applyHook, commandArgs, command } = context; const { getConfig, applyHook, commandArgs, command } = context;
@ -14,11 +16,17 @@ const build = async (context: Context<any>) => {
await applyHook('error', { err: new Error(errMsg) }); await applyHook('error', { err: new Error(errMsg) });
return; return;
} }
// transform config to webpack config // transform config to webpack config
const webpackConfig = configs.map((task) => { const webpackConfig = configs.map((task) => {
const { config } = task;
config.alias = {
...config.alias,
'@ice/plugin-auth/runtime': path.join(require.resolve('@ice/plugin-auth'), '../../runtime'),
};
return getWebpackConfig({ return getWebpackConfig({
rootDir, rootDir,
config: task.config, config,
}); });
}); });
const transformPlugins = getTransformPlugins(rootDir, configs.find(({ name }) => name === 'web').config); const transformPlugins = getTransformPlugins(rootDir, configs.find(({ name }) => name === 'web').config);

View File

@ -1,3 +1,5 @@
import { createRequire } from 'module';
import * as path from 'path';
import WebpackDevServer from 'webpack-dev-server'; import WebpackDevServer from 'webpack-dev-server';
import type { Context } from 'build-scripts'; import type { Context } from 'build-scripts';
import { getWebpackConfig, getTransformPlugins } from '@builder/webpack-config'; import { getWebpackConfig, getTransformPlugins } from '@builder/webpack-config';
@ -6,6 +8,7 @@ import webpackCompiler from '../service/webpackCompiler.js';
import prepareURLs from '../utils/prepareURLs.js'; import prepareURLs from '../utils/prepareURLs.js';
import type { Config } from '@ice/types'; import type { Config } from '@ice/types';
const require = createRequire(import.meta.url);
const { defaultsDeep } = lodash; const { defaultsDeep } = lodash;
interface IWebTaskConfig { interface IWebTaskConfig {
@ -32,6 +35,13 @@ const start = async (context: Context<any>) => {
return; return;
} }
const { config } = webConfig as IWebTaskConfig; const { config } = webConfig as IWebTaskConfig;
config.alias = {
...config.alias,
// TODO: 放在各自插件里还是放在 ice 里?
// TODO: make pkg.json exports works; build 复用逻辑
// '@ice/plugin-auth/runtime': require.resolve('@ice/plugin-auth/runtime'),
'@ice/plugin-auth/runtime': path.join(require.resolve('@ice/plugin-auth'), '../../runtime'),
};
// transform config to webpack config // transform config to webpack config
const webpackConfig = getWebpackConfig({ const webpackConfig = getWebpackConfig({

View File

@ -64,6 +64,7 @@ const getBuiltInPlugins: IGetBuiltInPlugins = (userConfig) => {
// react base plugin // react base plugin
require.resolve('@ice/plugin-app'), require.resolve('@ice/plugin-app'),
require.resolve('@ice/plugin-router'), require.resolve('@ice/plugin-router'),
require.resolve('@ice/plugin-auth'),
// for ice/react plugins // for ice/react plugins
/* 'build-plugin-ice-config', /* 'build-plugin-ice-config',
'build-plugin-ice-mpa', 'build-plugin-ice-mpa',

View File

@ -0,0 +1,6 @@
import { runApp } from '@ice/runtime';
import appConfig from '@/app';
import runtimeModules from './runtimeModules';
import routes from './routes';
runApp(appConfig, runtimeModules, routes);

View File

@ -0,0 +1,8 @@
import { runServerApp } from '@ice/runtime';
import appConfig from '@/app';
import runtimeModules from './runtimeModules';
import routes from './routes';
export async function render() {
return await runServerApp(appConfig, runtimeModules, routes);
}

View File

@ -4,5 +4,4 @@
export { export {
<%- framework.exports %> <%- framework.exports %>
} }
<% } %> <% } %>
export * from './runApp';

View File

@ -1,13 +0,0 @@
<% const moduleNames = []; %>
<% if (runtimeModules.length) {%>
<% runtimeModules.filter((moduleInfo) => !moduleInfo.staticModule).forEach((runtimeModule, index) => { %>
<% moduleNames.push('module' + index) %>import module<%= index %> from '<%= runtimeModule.path %>';<% }) %><% } %>
import type { Runtime } from '@ice/runtime';
function loadRuntimeModules(runtime: Runtime) {
<% if (moduleNames.length) {%>
<% moduleNames.forEach((name) => { %>runtime.loadModule(<%= name%>);<% }) %>
<% } %>
}
export default loadRuntimeModules;

View File

@ -1,14 +0,0 @@
<% const moduleNames = []; %>
<% if (runtimeModules.length) {%>
<% runtimeModules.filter((moduleInfo) => moduleInfo.staticModule).forEach((runtimeModule, index) => { %>
<% moduleNames.push('module' + index) %>import module<%= index %> from '<%= runtimeModule.path %>';<% }) %>
<% } %>
import type { AppConfig } from './types';
function loadStaticModules(appConfig: AppConfig) {
<% if (moduleNames.length) {%>
<% moduleNames.forEach((name) => { %><%= name%>({appConfig});<% }) %>
<% } %>
}
export default loadStaticModules;

View File

@ -1,40 +0,0 @@
import * as React from 'react';
import { Runtime, App, render } from '@ice/runtime';
import type { BuildConfig } from '@ice/runtime';
import loadStaticModules from './loadStaticModules';
import loadRuntimeModules from './loadRuntimeModules';
import type { AppConfig } from './types';
import routes from './routes';
export function runApp(config: AppConfig = {}) {
const appConfig = {
...config,
app: {
rootId: 'root',
strict: true,
...(config?.app || {}),
},
router: {
type: 'hash',
...(config?.router || {}),
}
};
loadStaticModules(appConfig);
// TODO generate buildConfig
const buildConfig: BuildConfig = {
ssr: false,
};
// TODO create context
const context: Context = {
routes,
};
const runtime = new Runtime(appConfig, buildConfig, context);
runtime.setRenderApp((args) => {
return <App {...args} />;
});
loadRuntimeModules(runtime);
render(runtime);
}

View File

@ -0,0 +1,14 @@
<% const moduleNames = []; %>
<% if (runtimeModules.length) {%>
<% runtimeModules.filter((moduleInfo) => !moduleInfo.staticModule).forEach((runtimeModule, index) => { %>
<% moduleNames.push('module' + index) %>import module<%= index %> from '<%= runtimeModule.path %>';
<% }) %>
<% } %>
const modules = [
<% moduleNames.forEach((moduleName, index) => { %>
<%= moduleName %>,
<% }) %>
];
export default modules;

View File

@ -1,21 +1,15 @@
import type { AppConfig as DefaultAppConfig } from '@ice/runtime';
<%- configTypes.imports %> <%- configTypes.imports %>
export interface App {
rootId?: string;
mountNode?: HTMLElement;
renderComponent?: ComponentType;
}
<% if (configTypes.imports) {%> <% if (configTypes.imports) {%>
interface ExtendsAppConfig { interface ExtendsAppConfig extends DefaultAppConfig {
<% if (configTypes.imports) { %> <% if (configTypes.imports) { %>
<%- configTypes.exports %> <%- configTypes.exports %>
<% } %> <% } %>
}; };
export type AppConfig = Omit<ExtendsAppConfig, 'app'> & {
app?: App & ('app' extends keyof ExtendsAppConfig ? ExtendsAppConfig['app'] : {}); export type AppConfig = ExtendsAppConfig;
}
<% } else { %> <% } else { %>
export interface AppConfig { export type AppConfig = DefaultAppConfig;
app?: App;
}
<% } %> <% } %>

View File

@ -0,0 +1,3 @@
# Changelog
## 1.0.0

View File

@ -0,0 +1 @@
# `plugin-auth`

View File

@ -0,0 +1,28 @@
{
"name": "@ice/plugin-auth",
"version": "1.0.0",
"description": "",
"license": "MIT",
"type": "module",
"main": "./esm/index.js",
"exports": {
".": "./esm/index.js",
"./runtime": "./runtime"
},
"files": [
"esm",
"runtime",
"!esm/**/*.map"
],
"dependencies": {
"@ice/types": "^1.0.0"
},
"peerDependencies": {
"react": ">16.0.0",
"react-dom": ">16.0.0"
},
"repository": {
"type": "http",
"url": "https://github.com/ice-lab/ice-next/tree/master/packages/plugin-auth"
}
}

View File

@ -0,0 +1,44 @@
import * as React from 'react';
import { createContext, useState, useContext } from 'react';
import type { FC } from 'react';
import type { ContextType, AuthType } from './types';
const Context = createContext<any>(null);
interface ProviderProps {
value: AuthType;
}
interface InjectProps {
auth: ContextType[0];
useAuth: ContextType[1];
}
const AuthProvider: FC<ProviderProps> = ({ value = {}, children }) => {
const [state, setState] = useState<AuthType>(value);
const updateState: InjectProps['useAuth'] = (newState = {}) => {
setState({
...state,
...newState,
});
};
return <Context.Provider value={[state, updateState]}>{children}</Context.Provider>;
};
const useAuth = (): ContextType => {
const value = useContext(Context);
return value;
};
// class 组件支持 Hoc 用法
function withAuth<Props extends InjectProps>(Component: React.ComponentType<Props>) {
type OriginalProps = Omit<Props, keyof InjectProps>;
const AuthWrapped = (props: OriginalProps) => {
const [auth, setAuth] = useAuth();
const WrappedComponent = Component as React.ComponentType<OriginalProps>;
return <WrappedComponent {...props} auth={auth} setAuth={setAuth} />;
};
return AuthWrapped;
}
export { useAuth, withAuth, AuthProvider };

View File

@ -0,0 +1,52 @@
import * as React from 'react';
import type { RuntimePlugin } from '@ice/types';
import { AuthProvider, useAuth } from './Auth';
import type { IAuth } from './types';
const wrapperComponentFn = (authConfig: IAuth) => (PageComponent) => {
const { pageConfig = {} } = PageComponent;
const AuthWrappedComponent = (props) => {
const [auth] = useAuth();
const pageConfigAuth = pageConfig.auth;
if (pageConfigAuth && !Array.isArray(pageConfigAuth)) {
throw new Error('pageConfig.auth must be an array');
}
const hasAuth =
Array.isArray(pageConfigAuth) && pageConfigAuth.length
? Object.keys(auth).filter((item) =>
(pageConfigAuth.includes(item) ? auth[item] : false),
).length
: true;
if (!hasAuth) {
if (authConfig.NoAuthFallback) {
if (typeof authConfig.NoAuthFallback === 'function') {
return <authConfig.NoAuthFallback {...Object.assign({}, props, { pageConfig })} />;
}
return authConfig.NoAuthFallback;
}
return <>No Auth</>;
}
return <PageComponent {...props} />;
};
return AuthWrappedComponent;
};
const runtime: RuntimePlugin = ({ context, appConfig, addProvider, wrapperPageComponent }) => {
const initialData = context && context.initialData ? context.initialData : {};
const initialAuth = initialData.auth || {};
const authConfig = appConfig.auth || {};
// TODO: React Devtools 里多一层
const AuthStoreProvider = ({ children }) => {
return <AuthProvider value={initialAuth}>{children}</AuthProvider>;
};
addProvider(AuthStoreProvider);
wrapperPageComponent(wrapperComponentFn(authConfig));
};
export default runtime;

View File

@ -0,0 +1,6 @@
export interface IAuth {
NoAuthFallback?: React.ReactNode;
}
export type AuthType = Record<string, boolean>;
export type ContextType = [AuthType, React.Dispatch<React.SetStateAction<AuthType>>];

View File

@ -0,0 +1,24 @@
import type { Plugin } from '@ice/types';
const plugin: Plugin = ({ generator }) => {
// 注册 APIimport { useAuth, withAuth } from 'ice';
generator.addExport({
specifier: ['withAuth', 'useAuth'],
source: '@ice/plugin-auth/runtime/Auth',
});
// 注册类型appConfig.auth
// export interface IAppConfig {
// auth?: IAuth;
// }
generator.addConfigTypes({
specifier: ['IAuth'],
source: '@ice/plugin-auth/runtime/types',
type: true,
exportAlias: {
IAuth: 'auth?',
},
});
};
export default plugin;

View File

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "./",
"rootDir": "src",
"outDir": "esm",
"jsx": "react"
},
"include": ["src"]
}

View File

@ -5,6 +5,7 @@ const runtime: RuntimePlugin = ({ setRenderApp }) => {
// setRenderApp(({ renderComponent }) => { // setRenderApp(({ renderComponent }) => {
// return renderComponent || (() => <>empty content</>); // return renderComponent || (() => <>empty content</>);
// }); // });
}; };
export default runtime; export default runtime;

View File

@ -21,14 +21,14 @@ export default function AppRouter(props: RenderOptions) {
<Routes> <Routes>
{ {
routes.map((route, index) => { routes.map((route, index) => {
const RouteComponent = route.component; const PageComponent = route.component;
return ( return (
<Route <Route
key={index} key={index}
path={route.path} path={route.path}
element={ element={
<React.Suspense fallback={<>loading chunk....</>}> <React.Suspense fallback={<>loading chunk....</>}>
<RouteComponent /> <PageComponent />
</React.Suspense> </React.Suspense>
} }
/> />

View File

@ -0,0 +1,33 @@
import * as React from 'react';
import type { Context } from './types';
interface Props {
context: Context;
// pageWrappers: React.ComponentType<any>[];
PageComponent: React.ComponentType<any>;
}
export default function RouteItem(props: Props) {
const { context, PageComponent } = props;
// const Wrapper = pageWrappers[0];
return (
<React.Suspense fallback={<>loading chunk....</>}>
{/* {
(pageWrappers || []).reduce((Prev, Curr) => {
// const compose = curr(acc);
// if (acc.pageConfig) {
// compose.pageConfig = acc.pageConfig;
// }
// if (acc.getInitialProps) {
// compose.getInitialProps = acc.getInitialProps;
// }
return <Prev><Curr /></Prev>;
}, PageComponent)
} */}
{/* <Wrapper> */}
<PageComponent />
{/* </Wrapper> */}
</React.Suspense>
);
}

View File

@ -1,19 +1,24 @@
import Runtime from './runtime.js'; import Runtime from './runtime.js';
import App from './App.js'; import App from './App.js';
import render from './render.js'; import render from './render.js';
import runApp from './runApp.js';
import runServerApp from './runServerApp.js';
import { import {
RuntimePlugin, RuntimePlugin,
Context, Context,
BuildConfig, BuildConfig,
AppConfig,
} from './types.js'; } from './types.js';
export { export {
Runtime, Runtime,
App, App,
render, render,
runApp,
runServerApp,
// types // types
RuntimePlugin, RuntimePlugin,
BuildConfig, BuildConfig,
Context, Context,
AppConfig,
}; };

View File

@ -0,0 +1,60 @@
import * as React from 'react';
import Runtime from './runtime.js';
import App from './App.js';
import render from './render.js';
import type { BuildConfig, Context, InitialContext, AppConfig } from './types';
export default async function runApp(config: AppConfig, runtimeModules, routes) {
const appConfig: AppConfig = {
...config,
app: {
rootId: 'root',
strict: true,
...(config?.app || {}),
},
router: {
type: 'hash',
...(config?.router || {}),
},
};
// loadStaticModules(appConfig);
// TODO generate buildConfig
const buildConfig: BuildConfig = {};
const context: Context = {
routes,
};
// ssr enabled and the server has returned data
if ((window as any).__ICE_APP_DATA__) {
context.initialData = (window as any).__ICE_APP_DATA__;
// context.pageInitialProps = (window as any).__ICE_PAGE_PROPS__;
} else if (appConfig?.app?.getInitialData) {
const { href, origin, pathname, search } = window.location;
const path = href.replace(origin, '');
// const query = queryString.parse(search);
const query = {};
const ssrError = (window as any).__ICE_SSR_ERROR__;
const initialContext: InitialContext = {
pathname,
path,
query,
ssrError,
};
context.initialData = await appConfig.app.getInitialData(initialContext);
}
const runtime = new Runtime(appConfig, buildConfig, context);
runtime.setRenderApp((args) => {
return <App {...args} />;
});
runtimeModules.forEach(m => {
runtime.loadModule(m);
});
render(runtime);
}

View File

@ -0,0 +1,4 @@
export default async function runServerApp(appConfig, runtimeModules, routes) {
}

View File

@ -1,20 +1,19 @@
import type { ComponentType } from 'react'; import type { ComponentType, ReactNode } from 'react';
import { RouterProps } from 'react-router-dom';
type VoidFunction = () => void; type VoidFunction = () => void;
type AppLifecycle = 'onShow' | 'onHide' | 'onPageNotFound' | 'onShareAppMessage' | 'onUnhandledRejection' | 'onLaunch' | 'onError' | 'onTabItemClick'; type AppLifecycle = 'onShow' | 'onHide' | 'onPageNotFound' | 'onShareAppMessage' | 'onUnhandledRejection' | 'onLaunch' | 'onError' | 'onTabItemClick';
type App = Partial<{ type App = Partial<{
rootId: string; rootId?: string;
strict: boolean; strict?: boolean;
addProvider?: ({ children }: { children: ReactNode }) => ReactNode;
getInitialData?: (ctx?: any) => Promise<any>;
} & Record<AppLifecycle, VoidFunction>>; } & Record<AppLifecycle, VoidFunction>>;
interface Router {
type: 'hash' | 'browser';
basename: string;
}
export interface AppConfig extends Record<string, any> { export interface AppConfig extends Record<string, any> {
app: App; app?: App;
router: Router; router?: {
type: 'hash' | 'browser';
basename?: string;
};
} }
// simplify page item type while it has been defined in plugin-router // simplify page item type while it has been defined in plugin-router
export interface DOMRender { export interface DOMRender {
@ -47,10 +46,17 @@ export interface BuildConfig extends Record<string, any> {
target?: string[]; target?: string[];
} }
// getInitialData: (ctx: InitialContext) => {}
export interface InitialContext {
pathname: string;
path: string;
query: Record<string, any>;
ssrError?: any;
}
export interface Context { export interface Context {
appManifest?: Record<string, any>; appManifest?: Record<string, any>;
routes?: Routes; routes?: Routes;
initialContext?: Record<string, any>;
initialData?: any; initialData?: any;
} }
export interface RuntimeAPI { export interface RuntimeAPI {

View File

@ -29,7 +29,7 @@ export type ParseRenderData = () => Record<string, unknown>;
export type GenerateImportStr = (apiName: string) => string; export type GenerateImportStr = (apiName: string) => string;
export type Render = () => void; export type Render = () => void;
export type ModifyRenderData = (registration: RenderDataRegistration) => void; export type ModifyRenderData = (registration: RenderDataRegistration) => void;
export type AddRenderFile = (templatePath: string, targetPath: string, extraData: ExtraData) => void; export type AddRenderFile = (templatePath: string, targetPath: string, extraData?: ExtraData) => void;
export type AddTemplateFiles = (templateOptions: string | TemplateOptions, extraData?: ExtraData) => void; export type AddTemplateFiles = (templateOptions: string | TemplateOptions, extraData?: ExtraData) => void;
export type RenderFile = (templatePath: string, targetDir: string, extraData?: ExtraData) => void; export type RenderFile = (templatePath: string, targetDir: string, extraData?: ExtraData) => void;
export type AddDisableRuntimePlugin = (pluginName: string) => void; export type AddDisableRuntimePlugin = (pluginName: string) => void;

View File

@ -109,6 +109,7 @@ importers:
'@builder/pack': ^0.6.0 '@builder/pack': ^0.6.0
'@builder/webpack-config': ^3.0.0 '@builder/webpack-config': ^3.0.0
'@ice/plugin-app': ^1.0.0 '@ice/plugin-app': ^1.0.0
'@ice/plugin-auth': workspace:^1.0.0
'@ice/plugin-router': ^1.0.0 '@ice/plugin-router': ^1.0.0
'@ice/runtime': ^1.0.0 '@ice/runtime': ^1.0.0
'@ice/types': ^1.0.0 '@ice/types': ^1.0.0
@ -134,6 +135,7 @@ importers:
'@builder/pack': 0.6.0 '@builder/pack': 0.6.0
'@builder/webpack-config': link:../build-webpack-config '@builder/webpack-config': link:../build-webpack-config
'@ice/plugin-app': link:../plugin-app '@ice/plugin-app': link:../plugin-app
'@ice/plugin-auth': link:../plugin-auth
'@ice/plugin-router': link:../plugin-router '@ice/plugin-router': link:../plugin-router
'@ice/runtime': link:../runtime '@ice/runtime': link:../runtime
address: 1.1.2 address: 1.1.2
@ -176,6 +178,12 @@ importers:
'@types/react': 17.0.39 '@types/react': 17.0.39
'@types/react-dom': 17.0.11 '@types/react-dom': 17.0.11
packages/plugin-auth:
specifiers:
'@ice/types': workspace:^1.0.0
dependencies:
'@ice/types': link:../types
packages/plugin-router: packages/plugin-router:
specifiers: specifiers:
'@ice/types': ^1.0.0 '@ice/types': ^1.0.0

View File

@ -6,6 +6,7 @@
{ "path": "packages/build-webpack-config" }, { "path": "packages/build-webpack-config" },
{ "path": "packages/plugin-app"}, { "path": "packages/plugin-app"},
{ "path": "packages/ice" }, { "path": "packages/ice" },
{ "path": "packages/plugin-router" } { "path": "packages/plugin-router" },
{ "path": "packages/plugin-auth" }
] ]
} }