mirror of https://github.com/alibaba/ice.git
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:
parent
c85e2491bc
commit
a787dd099b
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"plugins": [],
|
||||
"router": {
|
||||
"lazy": false
|
||||
}
|
||||
}
|
||||
|
|
@ -34,4 +34,8 @@ const About = () => {
|
|||
);
|
||||
};
|
||||
|
||||
About.pageConfig = {
|
||||
title: 'About'
|
||||
};
|
||||
|
||||
export default About;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
state: {
|
||||
title: 'List Model'
|
||||
},
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
state: {
|
||||
title: 'Todo Model'
|
||||
},
|
||||
};
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import { Link, store as appStore } from 'ice';
|
||||
import { store as pageStore } from 'ice/Home';
|
||||
import Todo from './components/Todo';
|
||||
import List from './components/List';
|
||||
|
||||
const Home = () => {
|
||||
const [counterState, counterActions] = appStore.useModel('counter');
|
||||
|
|
@ -16,6 +18,9 @@ const Home = () => {
|
|||
<button type="button" onClick={counterActions.decrementAsync}>-</button>
|
||||
</div>
|
||||
|
||||
<Todo />
|
||||
<List />
|
||||
|
||||
<Link to="/about">about</Link>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import Home from '@/pages/Home';
|
||||
import About from '@/pages/About';
|
||||
import { lazy } from 'ice';
|
||||
|
||||
// import Home from '@/pages/Home';
|
||||
// import About from '@/pages/About';
|
||||
|
||||
const Home = lazy(() => import('@/pages/Home'));
|
||||
const About =lazy (() => import('@/pages/About'));
|
||||
|
||||
export default [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import checkExportData from '../utils/checkExportData';
|
|||
import removeExportData from '../utils/removeExportData';
|
||||
import { IExportData } from '../types';
|
||||
|
||||
export default class UsePageGenerator {
|
||||
export default class PageGenerator {
|
||||
private generator: Generator;
|
||||
|
||||
private templatePath: string;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import * as globby from 'globby';
|
|||
import Generator from './generator';
|
||||
import PageGenerator from './generator/pageGenerator';
|
||||
import getPages from './utils/getPages';
|
||||
import getRoutes from './utils/getRoutes';
|
||||
import formatPath from './utils/formatPath';
|
||||
|
||||
export default (api) => {
|
||||
|
|
@ -143,6 +144,7 @@ export default (api) => {
|
|||
// register utils method
|
||||
registerMethod('getPages', getPages);
|
||||
registerMethod('formatPath', formatPath);
|
||||
registerMethod('getRoutes', getRoutes);
|
||||
|
||||
// registerMethod for modify page
|
||||
registerMethod('addPageExport', pageGenerator.addPageExport);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = ({ types }, { routeFile }) => {
|
||||
module.exports = ({ types }, { routesPath }) => {
|
||||
let hasLazyImport = false;
|
||||
let importName = '';
|
||||
return {
|
||||
visitor: {
|
||||
ImportDeclaration(nodePath, state) {
|
||||
// only transform route files
|
||||
const isRouteConfig = routeFile === state.filename;
|
||||
if (isRouteConfig) {
|
||||
const isRoutesFile = routesPath === state.filename;
|
||||
if (isRoutesFile) {
|
||||
const { node } = nodePath;
|
||||
if (types.isStringLiteral(node.source, { value: 'ice'})) {
|
||||
node.specifiers.forEach((importSpecifier) => {
|
||||
|
|
@ -24,8 +23,8 @@ module.exports = ({ types }, { routeFile }) => {
|
|||
},
|
||||
CallExpression(nodePath, state) {
|
||||
// only transform route files
|
||||
const isRouteConfig = routeFile === state.filename;
|
||||
if (isRouteConfig) {
|
||||
const isRoutesFile = routesPath === state.filename;
|
||||
if (isRoutesFile) {
|
||||
const { node } = nodePath;
|
||||
if (
|
||||
// case import * as xxx from 'ice'; xxx.lazy
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ function ignorePath(checkPath: string, ignoreOptions: IgnoreOptions) {
|
|||
export default function walker({
|
||||
rootDir,
|
||||
routerOptions,
|
||||
routersTempPath,
|
||||
routesTempPath,
|
||||
pagesDir,
|
||||
}) {
|
||||
const { ignoreRoutes, ignorePaths = ['components'], caseSensitive } = routerOptions;
|
||||
|
|
@ -84,7 +84,7 @@ export default function walker({
|
|||
const layoutName = `Layout${dirArrUpper}`;
|
||||
const filePath = formatPathForWin(
|
||||
path.relative(
|
||||
path.dirname(routersTempPath),
|
||||
path.dirname(routesTempPath),
|
||||
path.join(pagesDir, pageFilePath)
|
||||
)
|
||||
);
|
||||
|
|
@ -117,10 +117,10 @@ export default function walker({
|
|||
});
|
||||
|
||||
// amend collects
|
||||
amender(rootDir, routersTempPath, routesCollect);
|
||||
amender(rootDir, routesTempPath, routesCollect);
|
||||
// generate splices
|
||||
let routesSplices = splicer(routesCollect, routerOptions);
|
||||
// output into .tmp
|
||||
fse.outputFileSync(routersTempPath, routesSplices);
|
||||
fse.outputFileSync(routesTempPath, routesSplices);
|
||||
routesSplices = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,26 +11,41 @@ const TEM_ROUTER_SETS = [TEM_ROUTER_COMPATIBLE];
|
|||
const plugin: IPlugin = ({ context, onGetWebpackConfig, modifyUserConfig, getValue, applyMethod, registerUserConfig }) => {
|
||||
const { rootDir, userConfig, command } = context;
|
||||
// [enum] js or ts
|
||||
const isMpa = userConfig.mpa;
|
||||
const projectType = getValue('PROJECT_TYPE');
|
||||
|
||||
// .tmp path
|
||||
const iceTempPath = getValue('ICE_TEMP');
|
||||
const routersTempPath = path.join(iceTempPath, `routes.${projectType}`);
|
||||
const routerOptions = (userConfig.router || {}) as IRouterOptions;
|
||||
const { configPath } = routerOptions;
|
||||
let routeConfigPath = configPath
|
||||
? path.join(rootDir, configPath)
|
||||
: path.join(rootDir, `src/routes.${projectType}`);
|
||||
let { configPath } = routerOptions;
|
||||
|
||||
const isMpa = userConfig.mpa;
|
||||
const routesTempPath = path.join(iceTempPath, `routes.${projectType}`);
|
||||
// if is mpa use empty router file
|
||||
if (isMpa) {
|
||||
// if is mpa use empty router file
|
||||
fse.writeFileSync(routersTempPath, 'export default [];', 'utf-8');
|
||||
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
|
||||
const routerTemplatesPath = path.join(__dirname, '../templates');
|
||||
const routerTargetPath = path.join(iceTempPath, 'router');
|
||||
const routerTargetPath = path.join(iceTempPath, 'router');
|
||||
fse.ensureDirSync(routerTargetPath);
|
||||
fse.copySync(routerTemplatesPath, routerTargetPath);
|
||||
applyMethod('addIceExport', { source: './router' });
|
||||
|
|
@ -38,14 +53,12 @@ const plugin: IPlugin = ({ context, onGetWebpackConfig, modifyUserConfig, getVal
|
|||
// copy types
|
||||
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' });
|
||||
const routeFile = hasRouteFile ? routeConfigPath : routersTempPath;
|
||||
// add babel plugins for ice lazy
|
||||
modifyUserConfig('babelPlugins', [...(userConfig.babelPlugins as [] || []), [require.resolve('./babelPluginLazy'), { routeFile }]]);
|
||||
|
||||
// modify webpack config
|
||||
onGetWebpackConfig((config) => {
|
||||
// add alias
|
||||
TEM_ROUTER_SETS.forEach(i => {
|
||||
config.resolve.alias.set(i, routeFile);
|
||||
config.resolve.alias.set(i, routesPath);
|
||||
});
|
||||
// alias for 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
|
||||
if (!hasRouteFile) {
|
||||
if (!isConfigRoutes) {
|
||||
const routerMatch = 'src/pages';
|
||||
const pagesDir = path.join(rootDir, routerMatch);
|
||||
const walkerOptions = { rootDir, routerOptions, routersTempPath, pagesDir };
|
||||
const walkerOptions = { rootDir, routerOptions, routesTempPath, pagesDir };
|
||||
walker(walkerOptions);
|
||||
if (command === 'start') {
|
||||
// watch folder change when dev
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
import { createStore } from '@ice/store';
|
||||
|
||||
const Dashboard = createStore({});
|
||||
|
||||
const About = createStore({});
|
||||
|
||||
const PageStores = { Dashboard, About };
|
||||
|
||||
export default PageStores;
|
||||
|
|
@ -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)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -4,10 +4,12 @@ import * as ejs from 'ejs';
|
|||
import * as recursiveReaddir from 'fs-readdir-recursive';
|
||||
import * as prettier from 'prettier';
|
||||
|
||||
export interface IExportData {
|
||||
specifier?: string;
|
||||
source: string;
|
||||
exportName: string;
|
||||
export interface IRenderPageParams {
|
||||
pageName: string;
|
||||
pageNameDir: string;
|
||||
pageModelsDir: string;
|
||||
pageModelFile: string;
|
||||
pageComponentFiles: [];
|
||||
}
|
||||
|
||||
export default class Generator {
|
||||
|
|
@ -17,8 +19,6 @@ export default class Generator {
|
|||
|
||||
private pageStoreTemplatePath: string
|
||||
|
||||
private pageStoresTemplatePath: string
|
||||
|
||||
private targetPath: string
|
||||
|
||||
private projectType: string
|
||||
|
|
@ -29,7 +29,6 @@ export default class Generator {
|
|||
rootDir,
|
||||
appStoreTemplatePath,
|
||||
pageStoreTemplatePath,
|
||||
pageStoresTemplatePath,
|
||||
targetPath,
|
||||
applyMethod,
|
||||
projectType
|
||||
|
|
@ -45,13 +44,12 @@ export default class Generator {
|
|||
this.rootDir = rootDir;
|
||||
this.appStoreTemplatePath = appStoreTemplatePath;
|
||||
this.pageStoreTemplatePath = pageStoreTemplatePath;
|
||||
this.pageStoresTemplatePath = pageStoresTemplatePath;
|
||||
this.targetPath = targetPath;
|
||||
this.applyMethod = applyMethod;
|
||||
this.projectType = projectType;
|
||||
}
|
||||
|
||||
private getPageModels (pageName: string, pageModelsDir: string, pageModelFile: string) {
|
||||
private getPageModels(pageName: string, pageModelsDir: string, pageModelFile: string) {
|
||||
if (fse.pathExistsSync(pageModelsDir)) {
|
||||
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() {
|
||||
const sourceFilename = 'appStore';
|
||||
const sourceFilename = 'store/index';
|
||||
const exportName = 'store';
|
||||
const targetPath = path.join(this.targetPath, `${sourceFilename}.ts`);
|
||||
|
||||
|
|
@ -106,50 +115,72 @@ export default class Generator {
|
|||
this.applyMethod('addIceExport', { source: `./${sourceFilename}`, exportName });
|
||||
}
|
||||
|
||||
private renderPageStores() {
|
||||
const pages = this.applyMethod('getPages', this.rootDir);
|
||||
const pageStores = [];
|
||||
|
||||
// generate .ice/pages/*/store.ts
|
||||
pages.forEach(pageName => {
|
||||
private renderPageStore({ pageName, pageNameDir, pageModelsDir, pageModelFile }: IRenderPageParams) {
|
||||
if (fse.pathExistsSync(pageModelsDir) || fse.pathExistsSync(pageModelFile)) {
|
||||
const sourceFilename = 'store';
|
||||
const exportName = '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');
|
||||
const pageModelFilePath = path.join(pageNameDir, 'model');
|
||||
const renderData = this.getPageModels(pageName, pageModelsDir, pageModelFilePath);
|
||||
this.renderFile(this.pageStoreTemplatePath, targetPath, renderData);
|
||||
|
||||
// example: src/pages/*/model.ts
|
||||
const pageModelFile = path.join(pageNameDir, `model.${this.projectType}`);
|
||||
|
||||
if (fse.pathExistsSync(pageModelsDir) || fse.pathExistsSync(pageModelFile)) {
|
||||
pageStores.push(pageName);
|
||||
|
||||
const pageModelFilePath = path.join(pageNameDir, 'model');
|
||||
const renderData = this.getPageModels(pageName, pageModelsDir, pageModelFilePath);
|
||||
this.renderFile(this.pageStoreTemplatePath, targetPath, renderData);
|
||||
|
||||
const exportName = 'store';
|
||||
this.applyMethod('removePageExport', pageName, exportName);
|
||||
this.applyMethod('addPageExport', pageName, { source: `./${sourceFilename}`, exportName });
|
||||
}
|
||||
});
|
||||
|
||||
// generate .ice/pageStores.ts
|
||||
this.generatePageStores(pageStores);
|
||||
this.applyMethod('removePageExport', pageName, exportName);
|
||||
this.applyMethod('addPageExport', pageName, { source: `./${sourceFilename}`, exportName });
|
||||
}
|
||||
}
|
||||
|
||||
private generatePageStores(pageStores: string[]) {
|
||||
const targetPath = path.join(this.targetPath, 'pageStores.ts');
|
||||
private renderPageComponent({ pageName, pageNameDir, pageModelsDir, pageModelFile, pageComponentFiles }: IRenderPageParams) {
|
||||
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 = '';
|
||||
let pageStoreStr = '';
|
||||
pageStores.forEach(name => {
|
||||
importPageStoreStr += `\nimport ${name} from './pages/${name}/store';`;
|
||||
pageStoreStr += `${name},`;
|
||||
const pageComponentRenderData = {
|
||||
pageComponentImport: `import ${pageName} from '${pageComponentSourcePath}'` ,
|
||||
pageComponentExport: pageName,
|
||||
hasPageStore: false,
|
||||
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 = {}) {
|
||||
|
|
@ -168,7 +199,34 @@ export default class Generator {
|
|||
}
|
||||
|
||||
public render() {
|
||||
// generate .ice/store/index.ts
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import * as ejs from 'ejs';
|
|||
import Generator from './generator';
|
||||
|
||||
export default async (api) => {
|
||||
const { context, getValue, onHook, applyMethod, onGetWebpackConfig } = api;
|
||||
const { rootDir, command } = context;
|
||||
const { context, getValue, onHook, applyMethod, onGetWebpackConfig, modifyUserConfig } = api;
|
||||
const { rootDir, command, userConfig } = context;
|
||||
|
||||
const targetPath = getValue('ICE_TEMP');
|
||||
const templatePath = path.join(__dirname, 'template');
|
||||
|
|
@ -15,8 +15,7 @@ export default async (api) => {
|
|||
const typesTemplatePath = path.join(templatePath, 'types.ts.ejs');
|
||||
const projectType = getValue('PROJECT_TYPE');
|
||||
|
||||
// copy types/index.ts to .ice/store/index.ts
|
||||
await fse.copy(path.join(__dirname, '..', 'src/types'), path.join(targetPath, './store'));
|
||||
// set IStore to IAppConfig
|
||||
applyMethod('addIceTypesExport', { source: './store', specifier: '{ IStore }', exportName: 'store?: IStore' });
|
||||
|
||||
// render template/types.ts.ejs to .ice/store/types.ts
|
||||
|
|
@ -27,6 +26,24 @@ export default async (api) => {
|
|||
fse.writeFileSync(typesTargetPath, content, 'utf-8');
|
||||
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 => {
|
||||
if (command === 'build') {
|
||||
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/pageStores', path.join(targetPath, 'pageStores.ts'));
|
||||
config.resolve.alias.set('$ice/store', path.join(targetPath, 'store', 'index.ts'));
|
||||
});
|
||||
|
||||
const gen = new Generator({
|
||||
|
|
|
|||
|
|
@ -1,26 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import AppStore from '$ice/appStore';
|
||||
import PageStores from '$ice/pageStores';
|
||||
import AppStore from '$ice/store';
|
||||
|
||||
const wrapperComponent = (PageComponent) => {
|
||||
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);
|
||||
export default ({ addProvider, appConfig, context }) => {
|
||||
|
||||
const StoreProvider = ({children}) => {
|
||||
const storeConfig = appConfig.store || {};
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
<% if (importStr) { %>
|
||||
import { createStore, IcestoreRootState, IcestoreDispatch, Models } from '@ice/store';
|
||||
<%- importStr %>
|
||||
import { createStore, IcestoreRootState, IcestoreDispatch, Models } from '@ice/store';
|
||||
<%- importStr %>
|
||||
|
||||
interface AppModel extends Models {
|
||||
<% modelsStr.split(',').forEach(function(model){ %>
|
||||
<% if (model) { %>
|
||||
<%- model %>: typeof <%- model %>;
|
||||
<% } %>
|
||||
<% }); %>
|
||||
interface AppModel extends Models {
|
||||
<% modelsStr.split(',').forEach(function(model){ %>
|
||||
<% if (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 %> };
|
||||
|
||||
const store = createStore(appModel);
|
||||
export default store;
|
||||
|
||||
export type IRootDispatch = IcestoreDispatch<typeof appModel>;
|
||||
export type IRootState = IcestoreRootState<typeof appModel>;
|
||||
<% } %>
|
||||
export interface IStore {
|
||||
initialStates?: IInitialStates;
|
||||
getInitialStates?: (initialData) => IInitialStates;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<% if (importStr) { %>
|
||||
import { createStore } from '@ice/store';
|
||||
<%- importStr %>
|
||||
|
||||
const models = { <%- modelsStr %> }
|
||||
const store = createStore(models);
|
||||
export default store;
|
||||
<% } %>
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<%- importPageStoreStr %>
|
||||
|
||||
const pageStores = { <%- pageStoreStr %> };
|
||||
|
||||
export default pageStores;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
export interface IInitialStates {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface IStore {
|
||||
initialStates?: IInitialStates;
|
||||
getInitialStates?: (initialData) => IInitialStates;
|
||||
}
|
||||
|
|
@ -5,8 +5,7 @@
|
|||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"paths": {
|
||||
"$ice/appStore": ["./src/_appStore"],
|
||||
"$ice/pageStores": ["./src/_pageStores"]
|
||||
"$ice/store": ["./src/_store"]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue