index.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // Modified from
  2. // https://github.com/luxueyan/vite-transform-globby-import/blob/master/src/index.ts
  3. // TODO Deleting files requires re-running the project
  4. import { join } from 'path';
  5. import { lstatSync } from 'fs';
  6. import glob from 'glob';
  7. import globrex from 'globrex';
  8. import dotProp from 'dot-prop';
  9. import { createResolver, Resolver } from 'vite/dist/node/resolver.js';
  10. import { Transform } from 'vite/dist/node/transform.js';
  11. const modulesDir: string = join(process.cwd(), '/node_modules/');
  12. interface SharedConfig {
  13. root?: string;
  14. alias?: Record<string, string>;
  15. resolvers?: Resolver[];
  16. includes?: string[];
  17. }
  18. function template(template: string) {
  19. return (data: { [x: string]: any }) => {
  20. return template.replace(/#([^#]+)#/g, (_, g1) => data[g1] || g1);
  21. };
  22. }
  23. // TODO support hmr
  24. function hmr(isBuild = false) {
  25. if (isBuild) return '';
  26. return `
  27. if (import.meta.hot) {
  28. import.meta.hot.accept();
  29. }`;
  30. }
  31. // handle includes
  32. function fileInclude(includes: string | string[] | undefined, filePath: string) {
  33. return !includes || !Array.isArray(includes)
  34. ? true
  35. : includes.some((item) => filePath.startsWith(item));
  36. }
  37. // Bare exporter
  38. function compareString(modify: any, data: string[][]) {
  39. return modify ? '\n' + data.map((v) => `${v[0]}._path = ${v[1]}`).join('\n') : '';
  40. }
  41. function varTemplate(data: string[][], name: string) {
  42. //prepare deep data (for locales)
  43. let deepData: Record<string, object | string> = {};
  44. let hasDeepData = false;
  45. //data modify
  46. data.map((v) => {
  47. //check for has deep data
  48. if (v[0].includes('/')) {
  49. hasDeepData = true;
  50. }
  51. // lastKey is a data
  52. let pathValue = v[0].replace(/\//g, '.').split('.');
  53. // let scopeKey = '';
  54. // const len=pathValue.length
  55. // const scope=pathValue[len-2]
  56. let lastKey: string | undefined = pathValue.pop();
  57. let deepValue: Record<any, any> = {};
  58. if (lastKey) {
  59. // Solve the problem of files with the same name in different folders
  60. const lastKeyList = lastKey.replace('_' + pathValue[0], '').split('_');
  61. const key = lastKeyList.pop();
  62. if (key) {
  63. deepValue[key] = lastKey;
  64. }
  65. }
  66. // Set Deep Value
  67. deepValue = Object.assign(deepValue, dotProp.get(deepData, pathValue.join('.')));
  68. dotProp.set(deepData, pathValue.join('.'), deepValue);
  69. });
  70. if (hasDeepData) {
  71. return `const ${name} = ` + JSON.stringify(deepData).replace(/\"|\'/g, '');
  72. }
  73. return `const ${name} = { ${data.map((v) => v[0]).join(',')} }`;
  74. }
  75. const globTransform = function (config: SharedConfig): Transform {
  76. const resolver = createResolver(
  77. config.root || process.cwd(),
  78. config.resolvers || [],
  79. config.alias || {}
  80. );
  81. const { includes } = config;
  82. const cache = new Map();
  83. const urlMap = new Map();
  84. return {
  85. test({ path }) {
  86. const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
  87. try {
  88. return (
  89. !filePath.startsWith(modulesDir) &&
  90. /\.(vue|js|jsx|ts|tsx)$/.test(filePath) &&
  91. fileInclude(includes, filePath) &&
  92. lstatSync(filePath).isFile()
  93. );
  94. } catch {
  95. return false;
  96. }
  97. },
  98. transform({ code, path, isBuild }) {
  99. let result = cache.get(path);
  100. if (!result) {
  101. const reg = /import\s+([\w\s{}*]+)\s+from\s+(['"])globby(\?locale)?(\?path)?!([^'"]+)\2/g;
  102. const match = code.match(reg);
  103. if (!match) return code;
  104. const lastImport = urlMap.get(path);
  105. if (lastImport && match) {
  106. code = code.replace(lastImport, match[0]);
  107. }
  108. result = code.replace(
  109. reg,
  110. (
  111. _,
  112. // variable to export
  113. exportName,
  114. // bare export or not
  115. bareExporter,
  116. // is locale import
  117. isLocale,
  118. // inject _path attr
  119. injectPath,
  120. // path export
  121. globPath
  122. ) => {
  123. const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
  124. // resolve path
  125. const resolvedFilePath = globPath.startsWith('.')
  126. ? resolver.resolveRelativeRequest(filePath, globPath)
  127. : { pathname: resolver.requestToFile(globPath) };
  128. const files = glob.sync(resolvedFilePath.pathname, { dot: true });
  129. let templateStr = 'import #name# from #file#'; // import default
  130. let name = exportName;
  131. const m = exportName.match(/\{\s*(\w+)(\s+as\s+(\w+))?\s*\}/); // import module
  132. const m2 = exportName.match(/\*\s+as\s+(\w+)/); // import * as all module
  133. if (m) {
  134. templateStr = `import { ${m[1]} as #name# } from #file#`;
  135. name = m[3] || m[1];
  136. } else if (m2) {
  137. templateStr = 'import * as #name# from #file#';
  138. name = m2[1];
  139. }
  140. const templateRender = template(templateStr);
  141. const groups: Array<string>[] = [];
  142. const replaceFiles = files.map((f, i) => {
  143. const filePath = resolver.fileToRequest(f);
  144. const file = bareExporter + filePath + bareExporter;
  145. if (isLocale) {
  146. const globrexRes = globrex(globPath, { extended: true, globstar: true });
  147. // Get segments for files like an en/system ch/modules for:
  148. // ['en', 'system'] ['ch', 'modules']
  149. // TODO The window system and mac system path are inconsistent?
  150. const fileNameWithAlias = filePath.replace(/^(\/src\/)/, '/@/');
  151. const matchedGroups = globrexRes.regex.exec(fileNameWithAlias);
  152. if (matchedGroups && matchedGroups.length) {
  153. const matchedSegments = matchedGroups[1]; //first everytime "Full Match"
  154. const matchList = matchedSegments.split('/').filter(Boolean);
  155. const lang = matchList.shift();
  156. const scope = matchList.pop();
  157. // Solve the problem of files with the same name in different folders
  158. const scopeKey = scope ? `${scope}_` : '';
  159. const fileName = matchedGroups[2];
  160. const name = scopeKey + fileName + '_' + lang;
  161. //send deep way like an (en/modules/system/dashboard) into groups
  162. groups.push([matchedSegments + name, file]);
  163. return templateRender({
  164. name,
  165. file,
  166. });
  167. }
  168. } else {
  169. groups.push([name + i, file]);
  170. return templateRender({ name: name + i, file });
  171. }
  172. });
  173. // save in memory used result
  174. const filesJoined = replaceFiles.join('\n');
  175. urlMap.set(path, filesJoined);
  176. // console.log('======================');
  177. // console.log(filesJoined, varTemplate(groups, name));
  178. // console.log('======================');
  179. return [
  180. filesJoined,
  181. compareString(injectPath, groups),
  182. varTemplate(groups, name),
  183. '',
  184. ].join('\n');
  185. }
  186. );
  187. if (isBuild) cache.set(path, result);
  188. }
  189. return `${result}${hmr(isBuild)}`;
  190. },
  191. };
  192. };
  193. export default globTransform;