index.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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 lastKey: string | undefined = pathValue.pop();
  54. let deepValue: Record<any, any> = {};
  55. if (lastKey) {
  56. deepValue[lastKey.replace('_' + pathValue[0], '')] = lastKey;
  57. }
  58. // Set Deep Value
  59. deepValue = Object.assign(deepValue, dotProp.get(deepData, pathValue.join('.')));
  60. dotProp.set(deepData, pathValue.join('.'), deepValue);
  61. });
  62. if (hasDeepData) {
  63. return `const ${name} = ` + JSON.stringify(deepData).replace(/\"|\'/g, '');
  64. }
  65. return `const ${name} = { ${data.map((v) => v[0]).join(',')} }`;
  66. }
  67. const globTransform = function (config: SharedConfig): Transform {
  68. const resolver = createResolver(
  69. config.root || process.cwd(),
  70. config.resolvers || [],
  71. config.alias || {}
  72. );
  73. const { includes } = config;
  74. const cache = new Map();
  75. const urlMap = new Map();
  76. return {
  77. test({ path }) {
  78. const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
  79. try {
  80. return (
  81. !filePath.startsWith(modulesDir) &&
  82. /\.(vue|js|jsx|ts|tsx)$/.test(filePath) &&
  83. fileInclude(includes, filePath) &&
  84. lstatSync(filePath).isFile()
  85. );
  86. } catch {
  87. return false;
  88. }
  89. },
  90. transform({ code, path, isBuild }) {
  91. let result = cache.get(path);
  92. if (!result) {
  93. const reg = /import\s+([\w\s{}*]+)\s+from\s+(['"])globby(\?locale)?(\?path)?!([^'"]+)\2/g;
  94. const match = code.match(reg);
  95. if (!match) return code;
  96. const lastImport = urlMap.get(path);
  97. if (lastImport && match) {
  98. code = code.replace(lastImport, match[0]);
  99. }
  100. result = code.replace(
  101. reg,
  102. (
  103. _,
  104. // variable to export
  105. exportName,
  106. // bare export or not
  107. bareExporter,
  108. // is locale import
  109. isLocale,
  110. // inject _path attr
  111. injectPath,
  112. // path export
  113. globPath
  114. ) => {
  115. const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
  116. // resolve path
  117. const resolvedFilePath = globPath.startsWith('.')
  118. ? resolver.resolveRelativeRequest(filePath, globPath)
  119. : { pathname: resolver.requestToFile(globPath) };
  120. const files = glob.sync(resolvedFilePath.pathname, { dot: true });
  121. let templateStr = 'import #name# from #file#'; // import default
  122. let name = exportName;
  123. const m = exportName.match(/\{\s*(\w+)(\s+as\s+(\w+))?\s*\}/); // import module
  124. const m2 = exportName.match(/\*\s+as\s+(\w+)/); // import * as all module
  125. if (m) {
  126. templateStr = `import { ${m[1]} as #name# } from #file#`;
  127. name = m[3] || m[1];
  128. } else if (m2) {
  129. templateStr = 'import * as #name# from #file#';
  130. name = m2[1];
  131. }
  132. const templateRender = template(templateStr);
  133. const groups: Array<string>[] = [];
  134. const replaceFiles = files.map((f, i) => {
  135. const fileNameWithAlias = resolver.fileToRequest(f);
  136. const file = bareExporter + fileNameWithAlias + bareExporter;
  137. if (isLocale) {
  138. const globrexRes = globrex(globPath, { extended: true, globstar: true });
  139. // Get segments for files like an en/system ch/modules for:
  140. // ['en', 'system'] ['ch', 'modules']
  141. const matchedGroups = globrexRes.regex.exec(fileNameWithAlias);
  142. if (matchedGroups && matchedGroups.length) {
  143. const matchedSegments = matchedGroups[1]; //first everytime "Full Match"
  144. const name = matchedGroups[2] + '_' + matchedSegments.split('/').shift();
  145. //send deep way like an (en/modules/system/dashboard) into groups
  146. groups.push([matchedSegments + name, file]);
  147. return templateRender({
  148. name,
  149. file,
  150. });
  151. }
  152. } else {
  153. groups.push([name + i, file]);
  154. return templateRender({ name: name + i, file });
  155. }
  156. });
  157. // save in memory used result
  158. const filesJoined = replaceFiles.join('\n');
  159. urlMap.set(path, filesJoined);
  160. return [
  161. filesJoined,
  162. compareString(injectPath, groups),
  163. varTemplate(groups, name),
  164. '',
  165. ].join('\n');
  166. }
  167. );
  168. if (isBuild) cache.set(path, result);
  169. }
  170. return `${result}${hmr(isBuild)}`;
  171. },
  172. };
  173. };
  174. export default globTransform;