feat: support lazy store (#236)

* feat: support lazy store

* feat: support component model

* fix: typo

* feat: support store api

* chore: lazy example

* feat: add babelPluginReplacePath

* feat: replace router component path

* feat: add getRoutes util

* feat: support custom routes path

* feat: support replace custom alias

* fix: only transform Import node

* fix: alias key

* fix: only transform pages

* fix: set default alias

* fix: match pages

* fix: calculate relative path

Co-authored-by: 许文涛 <alvin.hui@qq.com>
This commit is contained in:
chenbin92 2020-04-17 12:13:37 +08:00 committed by GitHub
parent c85e2491bc
commit a787dd099b
27 changed files with 493 additions and 143 deletions

View File

@ -0,0 +1,6 @@
{
"plugins": [],
"router": {
"lazy": false
}
}

View File

@ -34,4 +34,8 @@ const About = () => {
); );
}; };
About.pageConfig = {
title: 'About'
};
export default About; export default About;

View File

@ -0,0 +1,14 @@
import React from 'react';
import model from 'ice/Home/components/List/model';
const List = () => {
console.log('List Component:', model.useValue());
return (
<>
<p>List</p>
</>
);
};
export default List;

View File

@ -0,0 +1,5 @@
export default {
state: {
title: 'List Model'
},
};

View File

@ -0,0 +1,14 @@
import React from 'react';
import model from 'ice/Home/components/Todo/model';
const Todo = () => {
console.log('Todo Component:', model.useValue());
return (
<>
<p>Todo</p>
</>
);
};
export default Todo;

View File

@ -0,0 +1,5 @@
export default {
state: {
title: 'Todo Model'
},
};

View File

@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import { Link, store as appStore } from 'ice'; import { Link, store as appStore } from 'ice';
import { store as pageStore } from 'ice/Home'; import { store as pageStore } from 'ice/Home';
import Todo from './components/Todo';
import List from './components/List';
const Home = () => { const Home = () => {
const [counterState, counterActions] = appStore.useModel('counter'); const [counterState, counterActions] = appStore.useModel('counter');
@ -16,6 +18,9 @@ const Home = () => {
<button type="button" onClick={counterActions.decrementAsync}>-</button> <button type="button" onClick={counterActions.decrementAsync}>-</button>
</div> </div>
<Todo />
<List />
<Link to="/about">about</Link> <Link to="/about">about</Link>
</> </>
); );

View File

@ -1,5 +1,10 @@
import Home from '@/pages/Home'; import { lazy } from 'ice';
import About from '@/pages/About';
// import Home from '@/pages/Home';
// import About from '@/pages/About';
const Home = lazy(() => import('@/pages/Home'));
const About =lazy (() => import('@/pages/About'));
export default [ export default [
{ {

View File

@ -6,7 +6,7 @@ import checkExportData from '../utils/checkExportData';
import removeExportData from '../utils/removeExportData'; import removeExportData from '../utils/removeExportData';
import { IExportData } from '../types'; import { IExportData } from '../types';
export default class UsePageGenerator { export default class PageGenerator {
private generator: Generator; private generator: Generator;
private templatePath: string; private templatePath: string;

View File

@ -5,6 +5,7 @@ import * as globby from 'globby';
import Generator from './generator'; import Generator from './generator';
import PageGenerator from './generator/pageGenerator'; import PageGenerator from './generator/pageGenerator';
import getPages from './utils/getPages'; import getPages from './utils/getPages';
import getRoutes from './utils/getRoutes';
import formatPath from './utils/formatPath'; import formatPath from './utils/formatPath';
export default (api) => { export default (api) => {
@ -143,6 +144,7 @@ export default (api) => {
// register utils method // register utils method
registerMethod('getPages', getPages); registerMethod('getPages', getPages);
registerMethod('formatPath', formatPath); registerMethod('formatPath', formatPath);
registerMethod('getRoutes', getRoutes);
// registerMethod for modify page // registerMethod for modify page
registerMethod('addPageExport', pageGenerator.addPageExport); registerMethod('addPageExport', pageGenerator.addPageExport);

View File

@ -0,0 +1,37 @@
import * as path from 'path';
import * as fse from 'fs-extra';
interface IParams {
rootDir: string;
tempDir: string;
configPath: string;
projectType: string;
}
interface IResult {
routesPath: string;
isConfigRoutes: boolean;
}
function getRoutes({ rootDir, tempDir, configPath, projectType }: IParams): IResult {
const routesPath = configPath
? path.join(rootDir, configPath)
: path.join(rootDir, `src/routes.${projectType}`);
// 配置式路由
const configPathExists = fse.existsSync(routesPath);
if (configPathExists) {
return {
routesPath,
isConfigRoutes: true
};
}
// 约定式路由
return {
routesPath: path.join(tempDir, `routes.${projectType}`),
isConfigRoutes: false
};
}
export default getRoutes;

View File

@ -1,13 +1,12 @@
// eslint-disable-next-line no-unused-vars module.exports = ({ types }, { routesPath }) => {
module.exports = ({ types }, { routeFile }) => {
let hasLazyImport = false; let hasLazyImport = false;
let importName = ''; let importName = '';
return { return {
visitor: { visitor: {
ImportDeclaration(nodePath, state) { ImportDeclaration(nodePath, state) {
// only transform route files // only transform route files
const isRouteConfig = routeFile === state.filename; const isRoutesFile = routesPath === state.filename;
if (isRouteConfig) { if (isRoutesFile) {
const { node } = nodePath; const { node } = nodePath;
if (types.isStringLiteral(node.source, { value: 'ice'})) { if (types.isStringLiteral(node.source, { value: 'ice'})) {
node.specifiers.forEach((importSpecifier) => { node.specifiers.forEach((importSpecifier) => {
@ -24,8 +23,8 @@ module.exports = ({ types }, { routeFile }) => {
}, },
CallExpression(nodePath, state) { CallExpression(nodePath, state) {
// only transform route files // only transform route files
const isRouteConfig = routeFile === state.filename; const isRoutesFile = routesPath === state.filename;
if (isRouteConfig) { if (isRoutesFile) {
const { node } = nodePath; const { node } = nodePath;
if ( if (
// case import * as xxx from 'ice'; xxx.lazy // case import * as xxx from 'ice'; xxx.lazy

View File

@ -39,7 +39,7 @@ function ignorePath(checkPath: string, ignoreOptions: IgnoreOptions) {
export default function walker({ export default function walker({
rootDir, rootDir,
routerOptions, routerOptions,
routersTempPath, routesTempPath,
pagesDir, pagesDir,
}) { }) {
const { ignoreRoutes, ignorePaths = ['components'], caseSensitive } = routerOptions; const { ignoreRoutes, ignorePaths = ['components'], caseSensitive } = routerOptions;
@ -84,7 +84,7 @@ export default function walker({
const layoutName = `Layout${dirArrUpper}`; const layoutName = `Layout${dirArrUpper}`;
const filePath = formatPathForWin( const filePath = formatPathForWin(
path.relative( path.relative(
path.dirname(routersTempPath), path.dirname(routesTempPath),
path.join(pagesDir, pageFilePath) path.join(pagesDir, pageFilePath)
) )
); );
@ -117,10 +117,10 @@ export default function walker({
}); });
// amend collects // amend collects
amender(rootDir, routersTempPath, routesCollect); amender(rootDir, routesTempPath, routesCollect);
// generate splices // generate splices
let routesSplices = splicer(routesCollect, routerOptions); let routesSplices = splicer(routesCollect, routerOptions);
// output into .tmp // output into .tmp
fse.outputFileSync(routersTempPath, routesSplices); fse.outputFileSync(routesTempPath, routesSplices);
routesSplices = null; routesSplices = null;
} }

View File

@ -11,22 +11,37 @@ const TEM_ROUTER_SETS = [TEM_ROUTER_COMPATIBLE];
const plugin: IPlugin = ({ context, onGetWebpackConfig, modifyUserConfig, getValue, applyMethod, registerUserConfig }) => { const plugin: IPlugin = ({ context, onGetWebpackConfig, modifyUserConfig, getValue, applyMethod, registerUserConfig }) => {
const { rootDir, userConfig, command } = context; const { rootDir, userConfig, command } = context;
// [enum] js or ts // [enum] js or ts
const isMpa = userConfig.mpa;
const projectType = getValue('PROJECT_TYPE'); const projectType = getValue('PROJECT_TYPE');
// .tmp path // .tmp path
const iceTempPath = getValue('ICE_TEMP'); const iceTempPath = getValue('ICE_TEMP');
const routersTempPath = path.join(iceTempPath, `routes.${projectType}`);
const routerOptions = (userConfig.router || {}) as IRouterOptions; const routerOptions = (userConfig.router || {}) as IRouterOptions;
const { configPath } = routerOptions; let { configPath } = routerOptions;
let routeConfigPath = configPath
? path.join(rootDir, configPath) const isMpa = userConfig.mpa;
: path.join(rootDir, `src/routes.${projectType}`); const routesTempPath = path.join(iceTempPath, `routes.${projectType}`);
if (isMpa) {
// if is mpa use empty router file // if is mpa use empty router file
fse.writeFileSync(routersTempPath, 'export default [];', 'utf-8'); if (isMpa) {
routeConfigPath = routersTempPath; fse.writeFileSync(routesTempPath, 'export default [];', 'utf-8');
configPath = routesTempPath;
} }
const hasRouteFile = fse.existsSync(routeConfigPath);
const { routesPath, isConfigRoutes } = applyMethod('getRoutes', {
rootDir,
tempDir: iceTempPath,
configPath,
projectType
});
// add babel plugins for ice lazy
modifyUserConfig('babelPlugins',
[
...(userConfig.babelPlugins as [] || []),
[
require.resolve('./babelPluginLazy'),
{ routesPath }
]
]);
// copy templates and export react-router-dom/history apis to ice // copy templates and export react-router-dom/history apis to ice
const routerTemplatesPath = path.join(__dirname, '../templates'); const routerTemplatesPath = path.join(__dirname, '../templates');
@ -38,14 +53,12 @@ const plugin: IPlugin = ({ context, onGetWebpackConfig, modifyUserConfig, getVal
// copy types // copy types
fse.copySync(path.join(__dirname, '../src/types/index.ts'), path.join(iceTempPath, 'router/types.ts')); fse.copySync(path.join(__dirname, '../src/types/index.ts'), path.join(iceTempPath, 'router/types.ts'));
applyMethod('addIceTypesExport', { source: './router/types', specifier: '{ IAppRouterProps }', exportName: 'router?: IAppRouterProps' }); applyMethod('addIceTypesExport', { source: './router/types', specifier: '{ IAppRouterProps }', exportName: 'router?: IAppRouterProps' });
const routeFile = hasRouteFile ? routeConfigPath : routersTempPath;
// add babel plugins for ice lazy
modifyUserConfig('babelPlugins', [...(userConfig.babelPlugins as [] || []), [require.resolve('./babelPluginLazy'), { routeFile }]]);
// modify webpack config // modify webpack config
onGetWebpackConfig((config) => { onGetWebpackConfig((config) => {
// add alias // add alias
TEM_ROUTER_SETS.forEach(i => { TEM_ROUTER_SETS.forEach(i => {
config.resolve.alias.set(i, routeFile); config.resolve.alias.set(i, routesPath);
}); });
// alias for runtime/Router // alias for runtime/Router
config.resolve.alias.set('$ice/Router', path.join(__dirname, 'runtime/Router')); config.resolve.alias.set('$ice/Router', path.join(__dirname, 'runtime/Router'));
@ -68,10 +81,10 @@ const plugin: IPlugin = ({ context, onGetWebpackConfig, modifyUserConfig, getVal
}); });
// do not watch folder pages when route config is exsits // do not watch folder pages when route config is exsits
if (!hasRouteFile) { if (!isConfigRoutes) {
const routerMatch = 'src/pages'; const routerMatch = 'src/pages';
const pagesDir = path.join(rootDir, routerMatch); const pagesDir = path.join(rootDir, routerMatch);
const walkerOptions = { rootDir, routerOptions, routersTempPath, pagesDir }; const walkerOptions = { rootDir, routerOptions, routesTempPath, pagesDir };
walker(walkerOptions); walker(walkerOptions);
if (command === 'start') { if (command === 'start') {
// watch folder change when dev // watch folder change when dev

View File

@ -1,9 +0,0 @@
import { createStore } from '@ice/store';
const Dashboard = createStore({});
const About = createStore({});
const PageStores = { Dashboard, About };
export default PageStores;

View File

@ -0,0 +1,115 @@
import * as path from 'path';
module.exports = ({ types: t }, { routesPath, alias }) => {
const regex = /src\/pages\/\w+(.tsx|.jsx)?(\/index(.tsx|.jsx)?)?/;
return {
visitor: {
ImportDeclaration(nodePath, state) {
const isRoutesFile = (routesPath === state.filename);
if (isRoutesFile) {
let value = nodePath.node.source.value;
if (typeof value === 'string') {
// 配置式路由
// default alias: import Home from '@/pages/Home';
// custom alias: import Home from '$pages/Home';
// relative path: import Home from '../pages/Home'
const matchedPagePath = matchRelativePath(routesPath, value) || matchAliasPath(alias, value);
if (matchedPagePath && regex.test(matchedPagePath)) {
const [, , pageName] = matchedPagePath.split('/');
// replace to: import Home from 'ice/Home/Home'
value = `ice/${pageName}/${pageName}`;
replaceWith(t, nodePath, value);
}
// 约定式路由
// e.g: import Home from '../src/pages/Home/index.tsx';
if (value.startsWith('../src/pages')) {
const [, , pageName] = value.split('/');
// replace to: import Home from './pages/Home/Home'
value = `./pages/${pageName}/${pageName}`;
replaceWith(t, nodePath, value);
}
}
}
},
CallExpression(nodePath, state) {
const isRoutesFile = (routesPath === state.filename);
if (isRoutesFile) {
if (t.isImport(nodePath.node.callee)) {
const args = nodePath.node.arguments;
for (let i = 0; i < args.length; i++) {
let value = args[i].value;
if (typeof value === 'string') {
// 配置式路由
// default alias: const Home = lazy(() => import('@/pages/Home'));
// custom alias: const Home = lazy(() => import('$pages/home));
// relative path: const Home = lazy(() => import('../pages/Home'));
const matchedPagePath = matchRelativePath(routesPath, value) || matchAliasPath(alias, value);
if (matchedPagePath && regex.test(matchedPagePath)) {
const [, , pageName] = matchedPagePath.split('/');
// replace to: const Home =lazy (() => import('ice/Home/Home'));
value = `ice/${pageName}/${pageName}`;
args[i].value = value;
}
// 约定式路由
// e.g: const Home = lazy(() => import(/* webpackChunkName: 'Home' */ '../src/pages/Home/index.tsx'));
if (value.startsWith('../src/pages')) {
const [, , pageName] = value.split('/');
// replace to: import Home from './pages/Home/Home'
value = `./pages/${pageName}/${pageName}`;
args[i].value = value;
}
}
}
}
}
},
},
};
};
interface IAlias {
[key: string]: string;
}
// enum alias:
// case1: { "@": "./src", "@pages": "./src/pages" }
// case2: { "@src": "./src", "@pages": "./src/pages" }
// case3: { "@": "./src", "@/pages": "./src/pages" }
function matchAliasPath(alias: IAlias, value: string): string {
let aliasPath = '';
// use default alias
if (!Object.keys(alias).length) {
alias['@'] = 'src';
}
// use custom alias
Object.keys(alias).forEach(currKey => {
if (value.startsWith(currKey)) {
const [, ...args] = value.split(currKey);
const currAliasPath = path.join(alias[currKey], ...args);
if (currAliasPath.includes('src/pages')) {
aliasPath = currAliasPath;
}
}
});
return aliasPath;
}
function matchRelativePath(routesPath: string, value: string) {
let relativePath = '';
if (/^(\.\/|\.{2}\/)/.test(value)) {
relativePath = path.relative(process.cwd(), path.join(routesPath, '..', value));
}
return relativePath;
}
function replaceWith(t, nodePath, value) {
nodePath.replaceWith(
t.ImportDeclaration(
nodePath.node.specifiers,
t.stringLiteral(value)
)
);
}

View File

@ -4,10 +4,12 @@ import * as ejs from 'ejs';
import * as recursiveReaddir from 'fs-readdir-recursive'; import * as recursiveReaddir from 'fs-readdir-recursive';
import * as prettier from 'prettier'; import * as prettier from 'prettier';
export interface IExportData { export interface IRenderPageParams {
specifier?: string; pageName: string;
source: string; pageNameDir: string;
exportName: string; pageModelsDir: string;
pageModelFile: string;
pageComponentFiles: [];
} }
export default class Generator { export default class Generator {
@ -17,8 +19,6 @@ export default class Generator {
private pageStoreTemplatePath: string private pageStoreTemplatePath: string
private pageStoresTemplatePath: string
private targetPath: string private targetPath: string
private projectType: string private projectType: string
@ -29,7 +29,6 @@ export default class Generator {
rootDir, rootDir,
appStoreTemplatePath, appStoreTemplatePath,
pageStoreTemplatePath, pageStoreTemplatePath,
pageStoresTemplatePath,
targetPath, targetPath,
applyMethod, applyMethod,
projectType projectType
@ -45,13 +44,12 @@ export default class Generator {
this.rootDir = rootDir; this.rootDir = rootDir;
this.appStoreTemplatePath = appStoreTemplatePath; this.appStoreTemplatePath = appStoreTemplatePath;
this.pageStoreTemplatePath = pageStoreTemplatePath; this.pageStoreTemplatePath = pageStoreTemplatePath;
this.pageStoresTemplatePath = pageStoresTemplatePath;
this.targetPath = targetPath; this.targetPath = targetPath;
this.applyMethod = applyMethod; this.applyMethod = applyMethod;
this.projectType = projectType; this.projectType = projectType;
} }
private getPageModels (pageName: string, pageModelsDir: string, pageModelFile: string) { private getPageModels(pageName: string, pageModelsDir: string, pageModelFile: string) {
if (fse.pathExistsSync(pageModelsDir)) { if (fse.pathExistsSync(pageModelsDir)) {
const pageModels = recursiveReaddir(pageModelsDir).map(item => path.parse(item)); const pageModels = recursiveReaddir(pageModelsDir).map(item => path.parse(item));
@ -82,8 +80,19 @@ export default class Generator {
}; };
} }
private getComponentModels(componentFiles: []) {
const componentModels = [];
componentFiles.forEach(componentFile => {
const parsedPath = path.parse(componentFile);
if (parsedPath.name === 'model') {
componentModels.push(parsedPath);
}
});
return componentModels;
}
private renderAppStore() { private renderAppStore() {
const sourceFilename = 'appStore'; const sourceFilename = 'store/index';
const exportName = 'store'; const exportName = 'store';
const targetPath = path.join(this.targetPath, `${sourceFilename}.ts`); const targetPath = path.join(this.targetPath, `${sourceFilename}.ts`);
@ -106,50 +115,72 @@ export default class Generator {
this.applyMethod('addIceExport', { source: `./${sourceFilename}`, exportName }); this.applyMethod('addIceExport', { source: `./${sourceFilename}`, exportName });
} }
private renderPageStores() { private renderPageStore({ pageName, pageNameDir, pageModelsDir, pageModelFile }: IRenderPageParams) {
const pages = this.applyMethod('getPages', this.rootDir);
const pageStores = [];
// generate .ice/pages/*/store.ts
pages.forEach(pageName => {
const sourceFilename = 'store';
const targetPath = path.join(this.targetPath, 'pages', pageName, `${sourceFilename}.ts`);
const pageNameDir = path.join(this.rootDir, 'src', 'pages', pageName);
// example: src/pages/*/models/*
const pageModelsDir = path.join(pageNameDir, 'models');
// example: src/pages/*/model.ts
const pageModelFile = path.join(pageNameDir, `model.${this.projectType}`);
if (fse.pathExistsSync(pageModelsDir) || fse.pathExistsSync(pageModelFile)) { if (fse.pathExistsSync(pageModelsDir) || fse.pathExistsSync(pageModelFile)) {
pageStores.push(pageName); const sourceFilename = 'store';
const exportName = 'store';
const targetPath = path.join(this.targetPath, 'pages', pageName, `${sourceFilename}.ts`);
const pageModelFilePath = path.join(pageNameDir, 'model'); const pageModelFilePath = path.join(pageNameDir, 'model');
const renderData = this.getPageModels(pageName, pageModelsDir, pageModelFilePath); const renderData = this.getPageModels(pageName, pageModelsDir, pageModelFilePath);
this.renderFile(this.pageStoreTemplatePath, targetPath, renderData); this.renderFile(this.pageStoreTemplatePath, targetPath, renderData);
const exportName = 'store';
this.applyMethod('removePageExport', pageName, exportName); this.applyMethod('removePageExport', pageName, exportName);
this.applyMethod('addPageExport', pageName, { source: `./${sourceFilename}`, exportName }); this.applyMethod('addPageExport', pageName, { source: `./${sourceFilename}`, exportName });
} }
});
// generate .ice/pageStores.ts
this.generatePageStores(pageStores);
} }
private generatePageStores(pageStores: string[]) { private renderPageComponent({ pageName, pageNameDir, pageModelsDir, pageModelFile, pageComponentFiles }: IRenderPageParams) {
const targetPath = path.join(this.targetPath, 'pageStores.ts'); const pageComponentTemplatePath = path.join(__dirname, './template/pageComponent.tsx.ejs');
const pageComponentTargetPath = path.join(this.targetPath, 'pages', pageName, `${pageName}.tsx`);
const pageComponentSourcePath = this.applyMethod('formatPath', pageNameDir);
let importPageStoreStr = ''; const pageComponentRenderData = {
let pageStoreStr = ''; pageComponentImport: `import ${pageName} from '${pageComponentSourcePath}'` ,
pageStores.forEach(name => { pageComponentExport: pageName,
importPageStoreStr += `\nimport ${name} from './pages/${name}/store';`; hasPageStore: false,
pageStoreStr += `${name},`; hasComponentStore: false
};
if (fse.pathExistsSync(pageModelsDir) || fse.pathExistsSync(pageModelFile)) {
pageComponentRenderData.hasPageStore = true;
}
if (this.getComponentModels(pageComponentFiles).length) {
pageComponentRenderData.hasComponentStore = true;
}
this.renderFile(pageComponentTemplatePath, pageComponentTargetPath , pageComponentRenderData);
}
private renderComponentStore(pageName: string, componentDir: string, componentFiles: []) {
const componentModels = this.getComponentModels(componentFiles);
let importStr = '';
let modelsStr = '';
componentModels.forEach(componentModel => {
importStr += `\nimport ${componentModel.dir} from '${componentDir}/${componentModel.dir}/${componentModel.name}'`;
modelsStr += `${componentModel.dir},`;
}); });
this.renderFile(this.pageStoresTemplatePath, targetPath, { importPageStoreStr, pageStoreStr }); const templatePath = path.join(__dirname, './template/componentStore.ts.ejs');
const targetPath = path.join(this.targetPath, `pages/${pageName}/componentStore.ts`);
componentFiles.forEach(componentFile => {
const parsedPath = path.parse(componentFile);
if (parsedPath.name === 'model') {
this.renderFile(templatePath, targetPath, { importStr, modelsStr });
}
});
}
private renderComponentModel(pageName: string, componentDir: string, componentFiles: []) {
const componentModels = this.getComponentModels(componentFiles);
const templatePath = path.join(__dirname, './template/componentModel.ts.ejs');
componentModels.forEach(componentModel => {
const targetPath = path.join(this.targetPath, `pages/${pageName}/components/${componentModel.dir}/${componentModel.base}`);
this.renderFile(templatePath, targetPath, { modelName: componentModel.dir });
});
} }
private renderFile(templatePath: string, targetPath: string, extraData = {}) { private renderFile(templatePath: string, targetPath: string, extraData = {}) {
@ -168,7 +199,34 @@ export default class Generator {
} }
public render() { public render() {
// generate .ice/store/index.ts
this.renderAppStore(); this.renderAppStore();
this.renderPageStores();
const pages = this.applyMethod('getPages', this.rootDir);
pages.forEach(pageName => {
const pageNameDir = path.join(this.rootDir, 'src', 'pages', pageName);
const pageComponentDir = path.join(pageNameDir, 'components');
const pageComponentFiles = recursiveReaddir(pageComponentDir);
// e.g: src/pages/${pageName}/models/*
const pageModelsDir = path.join(pageNameDir, 'models');
// e.g: src/pages/${pageName}/model.ts
const pageModelFile = path.join(pageNameDir, `model.${this.projectType}`);
const params = { pageName, pageNameDir, pageModelsDir, pageModelFile, pageComponentFiles };
// generate .ice/pages/${pageName}/store.ts
this.renderPageStore(params);
// generate .ice/pages/${pageName}/${pageName}.tsx
this.renderPageComponent(params);
// generate .ice/pages/${pageName}/componentStore.ts
this.renderComponentStore(pageName, pageComponentDir, pageComponentFiles);
// generate .ice/pages/${pageName}/components/${componentName}/model.ts
this.renderComponentModel(pageName, pageComponentDir, pageComponentFiles);
});
} }
} }

View File

@ -4,8 +4,8 @@ import * as ejs from 'ejs';
import Generator from './generator'; import Generator from './generator';
export default async (api) => { export default async (api) => {
const { context, getValue, onHook, applyMethod, onGetWebpackConfig } = api; const { context, getValue, onHook, applyMethod, onGetWebpackConfig, modifyUserConfig } = api;
const { rootDir, command } = context; const { rootDir, command, userConfig } = context;
const targetPath = getValue('ICE_TEMP'); const targetPath = getValue('ICE_TEMP');
const templatePath = path.join(__dirname, 'template'); const templatePath = path.join(__dirname, 'template');
@ -15,8 +15,7 @@ export default async (api) => {
const typesTemplatePath = path.join(templatePath, 'types.ts.ejs'); const typesTemplatePath = path.join(templatePath, 'types.ts.ejs');
const projectType = getValue('PROJECT_TYPE'); const projectType = getValue('PROJECT_TYPE');
// copy types/index.ts to .ice/store/index.ts // set IStore to IAppConfig
await fse.copy(path.join(__dirname, '..', 'src/types'), path.join(targetPath, './store'));
applyMethod('addIceTypesExport', { source: './store', specifier: '{ IStore }', exportName: 'store?: IStore' }); applyMethod('addIceTypesExport', { source: './store', specifier: '{ IStore }', exportName: 'store?: IStore' });
// render template/types.ts.ejs to .ice/store/types.ts // render template/types.ts.ejs to .ice/store/types.ts
@ -27,6 +26,24 @@ export default async (api) => {
fse.writeFileSync(typesTargetPath, content, 'utf-8'); fse.writeFileSync(typesTargetPath, content, 'utf-8');
applyMethod('addIceTypesExport', { source: './store/types' }); applyMethod('addIceTypesExport', { source: './store/types' });
// add babel plugins for ice lazy
const { configPath } = userConfig.router || {};
const { routesPath } = applyMethod('getRoutes', {
rootDir,
tempDir: targetPath,
configPath,
projectType
});
modifyUserConfig('babelPlugins',
[
...(userConfig.babelPlugins as [] || []),
[
require.resolve('./babelPluginReplacePath'),
{ routesPath, alias: userConfig.alias }
]
]
);
onGetWebpackConfig(config => { onGetWebpackConfig(config => {
if (command === 'build') { if (command === 'build') {
config.optimization.minimizer('TerserPlugin').tap(([args]) => [ config.optimization.minimizer('TerserPlugin').tap(([args]) => [
@ -38,8 +55,7 @@ export default async (api) => {
]); ]);
} }
config.resolve.alias.set('$ice/appStore', path.join(targetPath, 'appStore.ts')); config.resolve.alias.set('$ice/store', path.join(targetPath, 'store', 'index.ts'));
config.resolve.alias.set('$ice/pageStores', path.join(targetPath, 'pageStores.ts'));
}); });
const gen = new Generator({ const gen = new Generator({

View File

@ -1,26 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import AppStore from '$ice/appStore'; import AppStore from '$ice/store';
import PageStores from '$ice/pageStores';
const wrapperComponent = (PageComponent) => { export default ({ addProvider, appConfig, context }) => {
const { pageConfig = {} } = PageComponent;
const StoreWrapperedComponent = (props) => {
const pageComponentName = pageConfig.componentName;
const PageStore = PageStores[pageComponentName];
if (PageStore) {
return (
<PageStore.Provider initialStates={pageConfig.initialStates}>
<PageComponent {...props}/>
</PageStore.Provider>
);
}
return <PageComponent {...props} />;
};
return StoreWrapperedComponent;
};
export default ({ addProvider, wrapperRouteComponent, appConfig, context }) => {
wrapperRouteComponent(wrapperComponent);
const StoreProvider = ({children}) => { const StoreProvider = ({children}) => {
const storeConfig = appConfig.store || {}; const storeConfig = appConfig.store || {};

View File

@ -1,20 +1,28 @@
<% if (importStr) { %> <% if (importStr) { %>
import { createStore, IcestoreRootState, IcestoreDispatch, Models } from '@ice/store'; import { createStore, IcestoreRootState, IcestoreDispatch, Models } from '@ice/store';
<%- importStr %> <%- importStr %>
interface AppModel extends Models { interface AppModel extends Models {
<% modelsStr.split(',').forEach(function(model){ %> <% modelsStr.split(',').forEach(function(model){ %>
<% if (model) { %> <% if (model) { %>
<%- model %>: typeof <%- model %>; <%- model %>: typeof <%- model %>;
<% } %> <% } %>
<% }); %> <% }); %>
}
const appModel: AppModel = { <%- modelsStr %> };
const store = createStore(appModel);
export default store;
export type IRootDispatch = IcestoreDispatch<typeof appModel>;
export type IRootState = IcestoreRootState<typeof appModel>;
<% } %>
interface IInitialStates {
[key: string]: any;
} }
const appModel: AppModel = { <%- modelsStr %> }; export interface IStore {
initialStates?: IInitialStates;
const store = createStore(appModel); getInitialStates?: (initialData) => IInitialStates;
export default store; }
export type IRootDispatch = IcestoreDispatch<typeof appModel>;
export type IRootState = IcestoreRootState<typeof appModel>;
<% } %>

View File

@ -0,0 +1,25 @@
// .ice/pages/components/Todo/store.ts
import store from '../../componentStore'
const modelName = '<%= modelName %>';
export default {
useValue() {
return store.useModel(modelName)
},
useState() {
return store.useModelState(modelName)
},
useDispatchers() {
return store.useModelDispatchers(modelName)
},
getValue() {
return store.getModel(modelName)
},
getState() {
return store.getModelState(modelName)
},
getDispatchers() {
return store.getModelDispatchers(modelName)
}
}

View File

@ -0,0 +1,8 @@
<% if (importStr) { %>
import { createStore } from '@ice/store';
<%- importStr %>
const models = { <%- modelsStr %> }
const store = createStore(models);
export default store;
<% } %>

View File

@ -0,0 +1,53 @@
import * as React from 'react';
<%- pageComponentImport %>
<% if(hasPageStore) { %>
import store from './store';
<% } %>
<% if(hasComponentStore) { %>
import componentStore from './componentStore';
<% } %>
const PageComponentName = <%= pageComponentExport %>;
<% if(hasPageStore || hasComponentStore) { %>
let PageProvider = null;
let ComponentProvider = null;
<% if(hasPageStore) { %>
PageProvider = store.Provider;
<% } %>
<% if(hasComponentStore) { %>
ComponentProvider = componentStore.Provider;
<% } %>
const StoreWrapperedPage = () => getComponent(PageProvider, ComponentProvider);
StoreWrapperedPage.pageConfig = PageComponentName.pageConfig || {};
export default StoreWrapperedPage;
<% } else { %>
export default PageComponentName;
<% } %>
function getComponent(PageProvider, ComponentProvider) {
if (PageProvider && ComponentProvider) {
return (
<PageProvider>
<ComponentProvider>
<PageComponentName />
</ComponentProvider>
</PageProvider>
)
} else if (PageProvider) {
return (
<PageProvider>
<PageComponentName />
</PageProvider>
)
} else if (ComponentProvider) (
<ComponentProvider>
<PageComponentName />
</ComponentProvider>
)
}

View File

@ -1,5 +0,0 @@
<%- importPageStoreStr %>
const pageStores = { <%- pageStoreStr %> };
export default pageStores;

View File

@ -1,8 +0,0 @@
export interface IInitialStates {
[key: string]: any;
}
export interface IStore {
initialStates?: IInitialStates;
getInitialStates?: (initialData) => IInitialStates;
}

View File

@ -5,8 +5,7 @@
"rootDir": "src", "rootDir": "src",
"outDir": "lib", "outDir": "lib",
"paths": { "paths": {
"$ice/appStore": ["./src/_appStore"], "$ice/store": ["./src/_store"]
"$ice/pageStores": ["./src/_pageStores"]
} }
}, },
} }