From 32e6278b7436ce3b235cf6d68178f08ba80a8f44 Mon Sep 17 00:00:00 2001 From: aringlai Date: Thu, 4 Feb 2021 21:36:51 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20vuex=E6=8F=92=E4=BB=B6=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E9=87=8D=E6=9E=84=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/fes-plugin-vuex/package.json | 3 + packages/fes-plugin-vuex/src/helper.js | 160 ++++++++++++++++++ packages/fes-plugin-vuex/src/index.js | 79 ++++----- packages/fes-plugin-vuex/src/runtime/core.tpl | 26 +++ .../fes-plugin-vuex/src/runtime/runtime.js | 6 + .../fes-plugin-vuex/src/runtime/runtime.tpl | 10 -- packages/fes-template/.fes.js | 7 +- packages/fes-template/src/pages/index.vue | 10 +- packages/fes-template/src/pages/store.vue | 50 ++++++ packages/fes-template/src/stores/foo/bar.js | 23 +++ packages/fes-template/src/stores/user.js | 29 ++++ 11 files changed, 334 insertions(+), 69 deletions(-) create mode 100644 packages/fes-plugin-vuex/src/helper.js create mode 100644 packages/fes-plugin-vuex/src/runtime/core.tpl create mode 100644 packages/fes-plugin-vuex/src/runtime/runtime.js delete mode 100644 packages/fes-plugin-vuex/src/runtime/runtime.tpl create mode 100644 packages/fes-template/src/pages/store.vue create mode 100644 packages/fes-template/src/stores/foo/bar.js diff --git a/packages/fes-plugin-vuex/package.json b/packages/fes-plugin-vuex/package.json index 5074d0a7..bab1b6f0 100644 --- a/packages/fes-plugin-vuex/package.json +++ b/packages/fes-plugin-vuex/package.json @@ -26,6 +26,9 @@ "publishConfig": { "access": "public" }, + "dependencies": { + "@umijs/utils": "3.3.3" + }, "peerDependencies": { "@webank/fes": "^2.0.0-alpha.0", "vue": "3.0.5", diff --git a/packages/fes-plugin-vuex/src/helper.js b/packages/fes-plugin-vuex/src/helper.js new file mode 100644 index 00000000..9cec46dd --- /dev/null +++ b/packages/fes-plugin-vuex/src/helper.js @@ -0,0 +1,160 @@ +import { parser } from '@umijs/utils'; +import { readdirSync, readFileSync, statSync } from 'fs'; +import { join } from 'path'; + +/** + * 获取文件夹所有JS文件路径 + * @param {string} dir + */ +function getDirFilePaths(dir) { + const dirs = readdirSync(dir); + let pathList = []; + for (const name of dirs) { + const path = join(dir, name); + const info = statSync(path); + if (info.isDirectory()) { + pathList = pathList.concat(getDirFilePaths(path)); + } else if (path.endsWith('.js')) { + pathList.push(path); + } + } + return pathList; +} + +/** + * 路径转驼峰 + * @param {*} path + */ +function pathToHump(path, root) { + return path.replace(root, '') + .replace('.js', '') + .replace(/(\/|\.|-|_)\S/g, text => text[1].toUpperCase()) + .replace(/\S/, text => text.toLowerCase()); +} + +/** + * 获取vuex模块的mutations、actions、getters类型 + * @param {*} ast + * @param {*} name + */ +function getModelTypes(ast, name, namespace = '') { + const types = { + mutations: {}, + actions: {}, + getters: {} + }; + let namespaced = false; + if (ast.type !== 'ObjectExpression') return types; + ast.properties.forEach((node) => { + if (node.key.name === 'namespaced' && node.value.value) { + namespaced = true; + return; + } + if (Object.keys(types).includes(node.key.name)) { + let type = types[node.key.name]; + if (namespaced) { + type = types[node.key.name][name]; + if (!type) { + // eslint-disable-next-line no-multi-assign + type = types[node.key.name][name] = {}; + } + } + node.value.properties.forEach((prop) => { + const key = prop.key && prop.key.name; + if (key) { + type[key] = `${namespace}${namespaced ? `${name}/` : ''}${key}`; + } + }); + return; + } + if (node.key.name === 'modules') { + node.value.properties.forEach((prop) => { + const subTypes = getModelTypes(prop.value, prop.key.name, `${namespace}${namespaced ? `${name}/` : ''}`); + Object.keys(types).forEach((key) => { + if (namespaced) { + types[key][name] = { + ...subTypes[key], + ...types[key][name] + }; + } else { + types[key] = { + ...subTypes[key], + ...types[key] + }; + } + }); + }); + } + }); + return types; +} + +/** + * 解析模块 + * @param {*} paths + * @param {*} root + */ +function parseModel(paths = [], root) { + const modules = []; + const importModules = []; + let MUTATION_TYPES = {}; + let ACTION_TYPES = {}; + let GETTER_TYPES = {}; + paths.forEach((path) => { + const moduleName = pathToHump(path, root); + importModules.push(`import ${moduleName} from '${path}'`); + modules.push(moduleName); + const content = readFileSync(path).toString('utf-8'); + let ast = parser.parse(content, { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }); + ast = ast.program.body.filter(body => body.type === 'ExportDefaultDeclaration')[0]; + if (ast) { + const { mutations, actions, getters } = getModelTypes(ast.declaration, moduleName); + MUTATION_TYPES = { + ...mutations, + ...MUTATION_TYPES + }; + ACTION_TYPES = { + ...actions, + ...ACTION_TYPES + }; + GETTER_TYPES = { + ...getters, + ...GETTER_TYPES + }; + } + }); + return { + modules, importModules, MUTATION_TYPES, ACTION_TYPES, GETTER_TYPES + }; +} + +function parsePlugin(paths = [], root) { + const plugins = []; + const importPlugins = []; + paths.forEach((path) => { + const moduleName = pathToHump(path, root); + importPlugins.push(`import ${moduleName} from '${path}'`); + plugins.push(moduleName); + }); + return { plugins, importPlugins }; +} + +export function parseStore(root) { + const paths = getDirFilePaths(root); + const modelPaths = []; + const pluginPaths = []; + paths.forEach((path) => { + if (path.indexOf('plugin') > -1) { + pluginPaths.push(path); + } else { + modelPaths.push(path); + } + }); + return { + ...parsePlugin(pluginPaths, root), + ...parseModel(modelPaths, root) + }; +} diff --git a/packages/fes-plugin-vuex/src/index.js b/packages/fes-plugin-vuex/src/index.js index 22705455..7f92ffd6 100644 --- a/packages/fes-plugin-vuex/src/index.js +++ b/packages/fes-plugin-vuex/src/index.js @@ -1,5 +1,6 @@ -import { readdirSync, readFileSync, statSync } from 'fs'; +import { readFileSync } from 'fs'; import { join } from 'path'; +import { parseStore } from './helper'; const namespace = 'plugin-vuex'; @@ -9,66 +10,36 @@ export default (api) => { utils: { Mustache } } = api; - /** - * 获取文件夹所有JS文件路径 - * @param {string} dir - */ - function getDirFilePaths(dir) { - const dirs = readdirSync(dir); - let pathList = []; - for (const name of dirs) { - const path = join(dir, name); - const info = statSync(path); - if (info.isDirectory()) { - pathList = pathList.concat(getDirFilePaths(path)); - } else if (path.endsWith('.js')) { - pathList.push(path); - } + api.describe({ + key: 'vuex', + config: { + schema(joi) { + return joi.object(); + }, + onChange: api.ConfigChangeType.regenerateTmpFiles } - return pathList; - } - - /** - * 解析vuex模块及插件文件 - * @param {Array} pathList 文件路径 - * @param {string} root - */ - function parseStore(pathList, root) { - const store = { - modules: [], - plugins: [], - importModules: [], - importPlugins: [] - }; - for (const path of pathList) { - const moduleName = path.replace(root, '').replace('.js', '').replace(/(\/|\.|-|_)\S/g, text => text[1].toUpperCase()); - if (path.indexOf('plugin') > -1) { - store.importPlugins.push(`import ${moduleName} from '${path}'`); - store.plugins.push(moduleName); - } else { - store.importModules.push(`import ${moduleName} from '${path}'`); - store.modules.push(`${moduleName}`); - } - } - return store; - } + }); + const absCoreFilePath = join(namespace, 'core.js'); const absRuntimeFilePath = join(namespace, 'runtime.js'); api.onGenerateFiles(() => { - const root = join(paths.absSrcPath, 'stores'); - const storePaths = getDirFilePaths(root); - const store = parseStore(storePaths, join(root, '/')); - + const root = join(paths.absSrcPath, api.config.singular ? 'store' : 'stores'); + const store = parseStore(root); + const vuexConfig = api.config.vuex || {}; // 文件写出 api.writeTmpFile({ - path: absRuntimeFilePath, + path: absCoreFilePath, content: Mustache.render( - readFileSync(join(__dirname, 'runtime/runtime.tpl'), 'utf-8'), + readFileSync(join(__dirname, 'runtime/core.tpl'), 'utf-8'), { IMPORT_MODULES: store.importModules.join('\n'), IMPORT_PLUGINS: store.importPlugins.join('\n'), MODULES: `{ ${store.modules.join(', ')} }`, - PLUGINS: `[${store.plugins.join(', ')}]` + PLUGINS: `[${store.plugins.join(', ')}]`, + MUTATION_TYPES: JSON.stringify(store.MUTATION_TYPES), + ACTION_TYPES: JSON.stringify(store.ACTION_TYPES), + GETTER_TYPES: JSON.stringify(store.GETTER_TYPES), + VUEX_CONFIG: JSON.stringify(vuexConfig) } ) }); @@ -79,5 +50,13 @@ export default (api) => { ignore: ['.tpl'] }); }); + + api.addPluginExports(() => [ + { + specifiers: ['MUTATION_TYPES', 'ACTION_TYPES', 'GETTER_TYPES'], + source: absCoreFilePath + } + ]); + api.addRuntimePlugin(() => `@@/${absRuntimeFilePath}`); }; diff --git a/packages/fes-plugin-vuex/src/runtime/core.tpl b/packages/fes-plugin-vuex/src/runtime/core.tpl new file mode 100644 index 00000000..7366fc01 --- /dev/null +++ b/packages/fes-plugin-vuex/src/runtime/core.tpl @@ -0,0 +1,26 @@ +import { createStore } from 'vuex'; +{{{IMPORT_MODULES}}}; +{{{IMPORT_PLUGINS}}}; + +const modules = {{{MODULES}}}; +const MUTATION_TYPES = {{{MUTATION_TYPES}}}; +const ACTION_TYPES = {{{ACTION_TYPES}}}; +const GETTER_TYPES = {{{GETTER_TYPES}}}; +const conifg = {{{VUEX_CONFIG}}}; + + +const install = function (app) { + app.use(createStore({ + modules: modules, + plugins: {{{PLUGINS}}}, + strict: conifg.strict, + devtools: conifg.devtools + })); +} + +export { + install, + MUTATION_TYPES, + ACTION_TYPES, + GETTER_TYPES +}; diff --git a/packages/fes-plugin-vuex/src/runtime/runtime.js b/packages/fes-plugin-vuex/src/runtime/runtime.js new file mode 100644 index 00000000..d775ceb9 --- /dev/null +++ b/packages/fes-plugin-vuex/src/runtime/runtime.js @@ -0,0 +1,6 @@ +// eslint-disable-next-line import/extensions +import { install } from './core'; + +export function onAppCreated({ app }) { + install(app); +} diff --git a/packages/fes-plugin-vuex/src/runtime/runtime.tpl b/packages/fes-plugin-vuex/src/runtime/runtime.tpl deleted file mode 100644 index bf0aec46..00000000 --- a/packages/fes-plugin-vuex/src/runtime/runtime.tpl +++ /dev/null @@ -1,10 +0,0 @@ -import { createStore } from 'vuex' -{{{IMPORT_MODULES}}} -{{{IMPORT_PLUGINS}}} - -export function onAppCreated({ app }) { - app.use(createStore({ - modules: {{{MODULES}}}, - plugins: {{{PLUGINS}}} - })) -} \ No newline at end of file diff --git a/packages/fes-template/.fes.js b/packages/fes-template/.fes.js index db77aa6f..5c5f5dee 100644 --- a/packages/fes-template/.fes.js +++ b/packages/fes-template/.fes.js @@ -9,7 +9,7 @@ export default { publicPath: '/', access: { roles: { - admin: ["/", "/onepiece"] + admin: ["/", "/onepiece", '/store'] } }, layout: { @@ -20,6 +20,8 @@ export default { name: 'index' }, { name: 'onepiece' + }, { + name: 'store' }] }, locale: { @@ -30,5 +32,8 @@ export default { }, enums: { status: [['0', '无效的'], ['1', '有效的']] + }, + vuex: { + strict: true } }; diff --git a/packages/fes-template/src/pages/index.vue b/packages/fes-template/src/pages/index.vue index 3a304634..bf40156a 100644 --- a/packages/fes-template/src/pages/index.vue +++ b/packages/fes-template/src/pages/index.vue @@ -9,7 +9,6 @@
{{item.value}}:{{item.key}}
{{item.name}}:{{item.disabled}}
{{enumsGet('roles', '2', { dir: 'eName' })}}
-

Vuex

@@ -19,8 +18,7 @@ } + + diff --git a/packages/fes-template/src/stores/foo/bar.js b/packages/fes-template/src/stores/foo/bar.js new file mode 100644 index 00000000..78070ba3 --- /dev/null +++ b/packages/fes-template/src/stores/foo/bar.js @@ -0,0 +1,23 @@ +export default { + namespaced: true, + state: () => ({ + count: 0 + }), + mutations: { + increment(state) { + state.count++; + } + }, + getters: { + doubleCount(state) { + return state.count * 2; + } + }, + actions: { + asyncIncrement({ commit }) { + setTimeout(() => { + commit('increment'); + }, 2000); + } + } +}; diff --git a/packages/fes-template/src/stores/user.js b/packages/fes-template/src/stores/user.js index e6ffcceb..337bf8f8 100644 --- a/packages/fes-template/src/stores/user.js +++ b/packages/fes-template/src/stores/user.js @@ -20,6 +20,35 @@ export default { setTimeout(() => { commit('increment'); }, 2000); + }, + login() { + return new Promise((reslove) => { + setTimeout(() => { + console.log('login'); + reslove('OK'); + }, 1000); + }); + } + }, + modules: { + address: { + state: () => ({ + province: '广东省', + city: '深圳市', + zone: '南山区' + }), + getters: { + address(state) { + return state.province + state.city + state.zone; + } + } + }, + posts: { + namespaced: true, + state: () => ({}), + mutations: { + doSomething() {} + } } } };