packages/plugin-store/src/index.ts (115 lines of code) (raw):
import * as path from 'path';
import type { Config, Plugin } from '@ice/app/types';
import micromatch from 'micromatch';
import fg from 'fast-glob';
import { PAGE_STORE_MODULE, PAGE_STORE_PROVIDER, PAGE_STORE_INITIAL_STATES } from './constants.js';
interface Options {
resetPageState?: boolean;
}
const PLUGIN_NAME = '@ice/plugin-store';
const storeFilePattern = '**/store.{js,ts}';
const ignoreStoreFilePatterns = ['**/models/**', storeFilePattern];
const plugin: Plugin<Options> = (options) => ({
name: PLUGIN_NAME,
setup: ({ onGetConfig, modifyUserConfig, generator, context: { rootDir, userConfig }, watch }) => {
const { resetPageState = false } = options || {};
const srcDir = path.join(rootDir, 'src');
const pageDir = path.join(srcDir, 'pages');
modifyUserConfig('routes', {
...(userConfig.routes || {}),
ignoreFiles: [...(userConfig?.routes?.ignoreFiles || []), ...ignoreStoreFilePatterns],
});
if (getAppStorePath(srcDir)) {
generator.addRuntimeOptions({
source: '@/store',
specifier: 'appStore',
});
}
watch.addEvent([
/src\/store.(js|ts)$/,
(event) => {
if (event === 'unlink') {
generator.removeRuntimeOptions('@/store');
}
if (event === 'add') {
generator.addRuntimeOptions({
source: '@/store',
specifier: 'appStore',
});
}
if (['add', 'unlink'].includes(event)) {
generator.render();
}
},
]);
onGetConfig(config => {
config.transformPlugins = [
...(config.transformPlugins || []),
exportStoreProviderPlugin({ pageDir, resetPageState }),
];
return config;
});
// Export store api: createStore, createModel from `.ice/index.ts`.
generator.addExport({
specifier: ['createStore', 'createModel'],
source: '@ice/plugin-store/runtime',
type: false,
});
},
runtime: `${PLUGIN_NAME}/runtime`,
});
const formatId = (id: string) => id.split(path.sep).join('/');
function exportStoreProviderPlugin({ pageDir, resetPageState }: { pageDir: string; resetPageState: boolean }): Config['transformPlugins'][0] {
return {
name: 'export-store-provider',
enforce: 'pre',
transformInclude: (id) => {
return (
/\.[jt]sx?$/i.test(id) &&
formatId(id).startsWith(formatId(pageDir)) &&
!micromatch.isMatch(id, ignoreStoreFilePatterns)
);
},
transform: async (source, id) => {
const pageStorePath = getPageStorePath(id);
if (pageStorePath) {
if (
isLayout(id) || // Current id is layout.
!isLayoutExisted(id) // If current id is route and there is no layout in the current dir.
) {
return exportPageStore(source, resetPageState);
}
}
return source;
},
};
}
function exportPageStore(source: string, resetPageState: boolean) {
const importStoreStatement = `import ${PAGE_STORE_MODULE} from './store';\n`;
const exportStoreProviderStatement = resetPageState ? `
const { Provider: ${PAGE_STORE_PROVIDER}, getState } = ${PAGE_STORE_MODULE};
const ${PAGE_STORE_INITIAL_STATES} = getState();
export { ${PAGE_STORE_PROVIDER}, ${PAGE_STORE_INITIAL_STATES} };` : `
const { Provider: ${PAGE_STORE_PROVIDER} } = ${PAGE_STORE_MODULE};
export { ${PAGE_STORE_PROVIDER} };`;
return importStoreStatement + source + exportStoreProviderStatement;
}
/**
* Get the page store path which is at the same directory level.
* @param {string} id Route absolute path.
* @returns {string|undefined}
*/
function getPageStorePath(id: string): string | undefined {
const dir = path.dirname(id);
const result = fg.sync(storeFilePattern, { cwd: dir, deep: 1 });
return result.length ? path.join(dir, result[0]) : undefined;
}
function isLayout(id: string): boolean {
const extname = path.extname(id);
const idWithoutExtname = id.substring(0, id.length - extname.length);
return idWithoutExtname.endsWith('layout');
}
/**
* Check the current route component if there is layout.tsx at the same directory level.
* @param {string} id Route absolute path.
* @returns {boolean}
*/
function isLayoutExisted(id: string): boolean {
const dir = path.dirname(id);
const result = fg.sync('layout.{js,jsx,tsx}', { cwd: dir, deep: 1 });
return !!result.length;
}
function getAppStorePath(srcPath: string) {
const result = fg.sync(storeFilePattern, { cwd: srcPath, deep: 1 });
return result.length ? path.join(srcPath, result[0]) : undefined;
}
export default plugin;