usingComponents.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.transformDynamicImports = exports.parseScriptDescriptor = exports.parseTemplateDescriptor = exports.updateMiniProgramComponentsByMainFilename = exports.updateMiniProgramGlobalComponents = exports.updateMiniProgramComponentsByTemplateFilename = exports.updateMiniProgramComponentsByScriptFilename = exports.parseMainDescriptor = void 0;
  7. const types_1 = require("@babel/types");
  8. const estree_walker_1 = require("estree-walker");
  9. const magic_string_1 = __importDefault(require("magic-string"));
  10. const shared_1 = require("@vue/shared");
  11. const uni_shared_1 = require("@dcloudio/uni-shared");
  12. const messages_1 = require("../messages");
  13. const constants_1 = require("../constants");
  14. const utils_1 = require("../utils");
  15. const utils_2 = require("../vite/utils");
  16. const jsonFile_1 = require("../json/mp/jsonFile");
  17. const mainDescriptors = new Map();
  18. const scriptDescriptors = new Map();
  19. const templateDescriptors = new Map();
  20. function findImportTemplateSource(ast) {
  21. const importDeclaration = ast.body.find((node) => (0, types_1.isImportDeclaration)(node) &&
  22. node.source.value.includes('vue&type=template'));
  23. if (importDeclaration) {
  24. return importDeclaration.source.value;
  25. }
  26. }
  27. function findImportScriptSource(ast) {
  28. const importDeclaration = ast.body.find((node) => (0, types_1.isImportDeclaration)(node) && node.source.value.includes('vue&type=script'));
  29. if (importDeclaration) {
  30. return importDeclaration.source.value;
  31. }
  32. }
  33. async function resolveSource(filename, source, resolve) {
  34. if (!source) {
  35. return;
  36. }
  37. const resolveId = await resolve(source, filename);
  38. if (resolveId) {
  39. return resolveId.id;
  40. }
  41. }
  42. async function parseMainDescriptor(filename, ast, resolve) {
  43. const script = await resolveSource(filename, findImportScriptSource(ast), resolve);
  44. const template = await resolveSource(filename, findImportTemplateSource(ast), resolve);
  45. const imports = await parseVueComponentImports(filename, ast.body.filter((node) => (0, types_1.isImportDeclaration)(node)), resolve);
  46. if (!script) {
  47. // inline script
  48. await parseScriptDescriptor(filename, ast, { resolve, isExternal: false });
  49. }
  50. if (!template) {
  51. // inline template
  52. await parseTemplateDescriptor(filename, ast, { resolve, isExternal: false });
  53. }
  54. const descriptor = {
  55. imports,
  56. script: script ? (0, utils_2.parseVueRequest)(script).filename : filename,
  57. template: template ? (0, utils_2.parseVueRequest)(template).filename : filename,
  58. };
  59. mainDescriptors.set(filename, descriptor);
  60. return descriptor;
  61. }
  62. exports.parseMainDescriptor = parseMainDescriptor;
  63. function updateMiniProgramComponentsByScriptFilename(scriptFilename, inputDir, normalizeComponentName) {
  64. const mainFilename = findMainFilenameByScriptFilename(scriptFilename);
  65. if (mainFilename) {
  66. updateMiniProgramComponentsByMainFilename(mainFilename, inputDir, normalizeComponentName);
  67. }
  68. }
  69. exports.updateMiniProgramComponentsByScriptFilename = updateMiniProgramComponentsByScriptFilename;
  70. function updateMiniProgramComponentsByTemplateFilename(templateFilename, inputDir, normalizeComponentName) {
  71. const mainFilename = findMainFilenameByTemplateFilename(templateFilename);
  72. if (mainFilename) {
  73. updateMiniProgramComponentsByMainFilename(mainFilename, inputDir, normalizeComponentName);
  74. }
  75. }
  76. exports.updateMiniProgramComponentsByTemplateFilename = updateMiniProgramComponentsByTemplateFilename;
  77. function findMainFilenameByScriptFilename(scriptFilename) {
  78. const keys = [...mainDescriptors.keys()];
  79. return keys.find((key) => mainDescriptors.get(key).script === scriptFilename);
  80. }
  81. function findMainFilenameByTemplateFilename(templateFilename) {
  82. const keys = [...mainDescriptors.keys()];
  83. return keys.find((key) => mainDescriptors.get(key).template === templateFilename);
  84. }
  85. async function updateMiniProgramGlobalComponents(filename, ast, { inputDir, resolve, normalizeComponentName, }) {
  86. const { bindingComponents, imports } = await parseGlobalDescriptor(filename, ast, resolve);
  87. (0, jsonFile_1.addMiniProgramUsingComponents)('app', createUsingComponents(bindingComponents, imports, inputDir, normalizeComponentName));
  88. return {
  89. imports,
  90. };
  91. }
  92. exports.updateMiniProgramGlobalComponents = updateMiniProgramGlobalComponents;
  93. function createUsingComponents(bindingComponents, imports, inputDir, normalizeComponentName) {
  94. const usingComponents = {};
  95. imports.forEach(({ source: { value }, specifiers: [specifier] }) => {
  96. const { name } = specifier.local;
  97. if (!bindingComponents[name]) {
  98. return;
  99. }
  100. const componentName = normalizeComponentName((0, shared_1.hyphenate)(bindingComponents[name].tag));
  101. if (!usingComponents[componentName]) {
  102. usingComponents[componentName] = (0, uni_shared_1.addLeadingSlash)((0, utils_1.removeExt)((0, utils_1.normalizeMiniProgramFilename)(value, inputDir)));
  103. }
  104. });
  105. return usingComponents;
  106. }
  107. function updateMiniProgramComponentsByMainFilename(mainFilename, inputDir, normalizeComponentName) {
  108. const mainDescriptor = mainDescriptors.get(mainFilename);
  109. if (!mainDescriptor) {
  110. return;
  111. }
  112. const templateDescriptor = templateDescriptors.get(mainDescriptor.template);
  113. if (!templateDescriptor) {
  114. return;
  115. }
  116. const scriptDescriptor = scriptDescriptors.get(mainDescriptor.script);
  117. if (!scriptDescriptor) {
  118. return;
  119. }
  120. const bindingComponents = parseBindingComponents({
  121. ...templateDescriptor.bindingComponents,
  122. ...scriptDescriptor.setupBindingComponents,
  123. }, scriptDescriptor.bindingComponents);
  124. const imports = parseImports(mainDescriptor.imports, scriptDescriptor.imports, templateDescriptor.imports);
  125. (0, jsonFile_1.addMiniProgramUsingComponents)((0, utils_1.removeExt)((0, utils_1.normalizeMiniProgramFilename)(mainFilename, inputDir)), createUsingComponents(bindingComponents, imports, inputDir, normalizeComponentName));
  126. }
  127. exports.updateMiniProgramComponentsByMainFilename = updateMiniProgramComponentsByMainFilename;
  128. function findBindingComponent(tag, bindingComponents) {
  129. return Object.keys(bindingComponents).find((name) => {
  130. const componentTag = bindingComponents[name].tag;
  131. const camelName = (0, shared_1.camelize)(componentTag);
  132. const PascalName = (0, shared_1.capitalize)(camelName);
  133. return tag === componentTag || tag === camelName || tag === PascalName;
  134. });
  135. }
  136. function normalizeComponentId(id) {
  137. // _unref(test) => test
  138. if (id.includes('_unref(')) {
  139. return id.replace('_unref(', '').replace(')', '');
  140. }
  141. // $setup["test"] => test
  142. if (id.includes('$setup[')) {
  143. return id.replace('$setup["', '').replace('"', '');
  144. }
  145. return id;
  146. }
  147. function parseBindingComponents(templateBindingComponents, scriptBindingComponents) {
  148. const bindingComponents = {};
  149. Object.keys(templateBindingComponents).forEach((id) => {
  150. bindingComponents[normalizeComponentId(id)] = templateBindingComponents[id];
  151. });
  152. Object.keys(scriptBindingComponents).forEach((id) => {
  153. const { tag } = scriptBindingComponents[id];
  154. const name = findBindingComponent(tag, templateBindingComponents);
  155. if (name) {
  156. bindingComponents[id] = bindingComponents[name];
  157. }
  158. });
  159. return bindingComponents;
  160. }
  161. function parseImports(mainImports, scriptImports, templateImports) {
  162. const imports = [...mainImports, ...templateImports, ...scriptImports];
  163. return imports;
  164. }
  165. /**
  166. * 解析 template
  167. * @param filename
  168. * @param code
  169. * @param ast
  170. * @param options
  171. * @returns
  172. */
  173. async function parseTemplateDescriptor(filename, ast, options) {
  174. // 外置时查找所有 vue component import
  175. const imports = options.isExternal
  176. ? await parseVueComponentImports(filename, ast.body.filter((node) => (0, types_1.isImportDeclaration)(node)), options.resolve)
  177. : [];
  178. const descriptor = {
  179. bindingComponents: findBindingComponents(ast.body),
  180. imports,
  181. };
  182. templateDescriptors.set(filename, descriptor);
  183. return descriptor;
  184. }
  185. exports.parseTemplateDescriptor = parseTemplateDescriptor;
  186. async function parseGlobalDescriptor(filename, ast, resolve) {
  187. // 外置时查找所有 vue component import
  188. const imports = (await parseVueComponentImports(filename, ast.body.filter((node) => (0, types_1.isImportDeclaration)(node)), resolve)).filter((item) => !(0, utils_1.isAppVue)((0, utils_2.cleanUrl)(item.source.value)));
  189. return {
  190. bindingComponents: parseGlobalComponents(ast),
  191. imports,
  192. };
  193. }
  194. /**
  195. * 解析 script
  196. * @param filename
  197. * @param code
  198. * @param ast
  199. * @param options
  200. * @returns
  201. */
  202. async function parseScriptDescriptor(filename, ast, options) {
  203. // 外置时查找所有 vue component import
  204. const imports = options.isExternal
  205. ? await parseVueComponentImports(filename, ast.body.filter((node) => (0, types_1.isImportDeclaration)(node)), options.resolve)
  206. : [];
  207. const descriptor = {
  208. bindingComponents: parseComponents(ast),
  209. setupBindingComponents: findBindingComponents(ast.body),
  210. imports,
  211. };
  212. scriptDescriptors.set(filename, descriptor);
  213. return descriptor;
  214. }
  215. exports.parseScriptDescriptor = parseScriptDescriptor;
  216. /**
  217. * 解析编译器生成的 bindingComponents
  218. * @param ast
  219. * @returns
  220. */
  221. function findBindingComponents(ast) {
  222. const mapping = findUnpluginComponents(ast);
  223. for (const node of ast) {
  224. if (!(0, types_1.isVariableDeclaration)(node)) {
  225. continue;
  226. }
  227. const declarator = node.declarations[0];
  228. if ((0, types_1.isIdentifier)(declarator.id) &&
  229. declarator.id.name === constants_1.BINDING_COMPONENTS) {
  230. const bindingComponents = JSON.parse(declarator.init.value);
  231. return Object.keys(bindingComponents).reduce((bindings, tag) => {
  232. const { name, type } = bindingComponents[tag];
  233. bindings[mapping[name] || name] = {
  234. tag,
  235. type: type,
  236. };
  237. return bindings;
  238. }, {});
  239. }
  240. }
  241. return {};
  242. }
  243. /**
  244. * 兼容:unplugin_components
  245. * https://github.com/dcloudio/uni-app/issues/3057
  246. * @param ast
  247. * @returns
  248. */
  249. function findUnpluginComponents(ast) {
  250. const res = Object.create(null);
  251. // if(!Array){}
  252. const ifStatement = ast.find((statement) => (0, types_1.isIfStatement)(statement) &&
  253. (0, types_1.isUnaryExpression)(statement.test) &&
  254. statement.test.operator === '!' &&
  255. (0, types_1.isIdentifier)(statement.test.argument) &&
  256. statement.test.argument.name === 'Array');
  257. if (!ifStatement) {
  258. return res;
  259. }
  260. if (!(0, types_1.isBlockStatement)(ifStatement.consequent)) {
  261. return res;
  262. }
  263. for (const node of ifStatement.consequent.body) {
  264. if (!(0, types_1.isVariableDeclaration)(node)) {
  265. continue;
  266. }
  267. const { id, init } = node.declarations[0];
  268. if ((0, types_1.isIdentifier)(id) &&
  269. (0, types_1.isIdentifier)(init) &&
  270. init.name.includes('unplugin_components')) {
  271. res[id.name] = init.name;
  272. }
  273. }
  274. return res;
  275. }
  276. /**
  277. * 查找全局组件定义:app.component('component-a',{})
  278. * @param ast
  279. * @returns
  280. */
  281. function parseGlobalComponents(ast) {
  282. const bindingComponents = {};
  283. estree_walker_1.walk(ast, {
  284. enter(child) {
  285. if (!(0, types_1.isCallExpression)(child)) {
  286. return;
  287. }
  288. const { callee } = child;
  289. // .component
  290. if (!(0, types_1.isMemberExpression)(callee) ||
  291. !(0, types_1.isIdentifier)(callee.property) ||
  292. callee.property.name !== 'component') {
  293. return;
  294. }
  295. // .component('component-a',{})
  296. const args = child.arguments;
  297. if (args.length !== 2) {
  298. return;
  299. }
  300. const [name, value] = args;
  301. if (!(0, types_1.isStringLiteral)(name)) {
  302. return console.warn(messages_1.M['mp.component.args[0]']);
  303. }
  304. if (!(0, types_1.isIdentifier)(value)) {
  305. return console.warn(messages_1.M['mp.component.args[1]']);
  306. }
  307. bindingComponents[value.name] = {
  308. tag: name.value,
  309. type: 'unknown',
  310. };
  311. },
  312. });
  313. return bindingComponents;
  314. }
  315. /**
  316. * 从 components 中查找定义的组件
  317. * @param ast
  318. * @param bindingComponents
  319. */
  320. function parseComponents(ast) {
  321. const bindingComponents = {};
  322. estree_walker_1.walk(ast, {
  323. enter(child) {
  324. if (!(0, types_1.isObjectExpression)(child)) {
  325. return;
  326. }
  327. const componentsProp = child.properties.find((prop) => (0, types_1.isObjectProperty)(prop) &&
  328. (0, types_1.isIdentifier)(prop.key) &&
  329. prop.key.name === 'components');
  330. if (!componentsProp) {
  331. return;
  332. }
  333. const componentsExpr = componentsProp.value;
  334. if (!(0, types_1.isObjectExpression)(componentsExpr)) {
  335. return;
  336. }
  337. componentsExpr.properties.forEach((prop) => {
  338. if (!(0, types_1.isObjectProperty)(prop)) {
  339. return;
  340. }
  341. if (!(0, types_1.isIdentifier)(prop.key) && !(0, types_1.isStringLiteral)(prop.key)) {
  342. return;
  343. }
  344. if (!(0, types_1.isIdentifier)(prop.value)) {
  345. return;
  346. }
  347. bindingComponents[prop.value.name] = {
  348. tag: (0, types_1.isIdentifier)(prop.key) ? prop.key.name : prop.key.value,
  349. type: 'unknown',
  350. };
  351. });
  352. },
  353. });
  354. return bindingComponents;
  355. }
  356. /**
  357. * vue component imports
  358. * @param filename
  359. * @param imports
  360. * @param resolve
  361. * @returns
  362. */
  363. async function parseVueComponentImports(importer, imports, resolve) {
  364. const vueComponentImports = [];
  365. for (let i = 0; i < imports.length; i++) {
  366. const { source } = imports[i];
  367. if ((0, utils_2.parseVueRequest)(source.value).query.vue) {
  368. continue;
  369. }
  370. const resolveId = await resolve(source.value, importer);
  371. if (!resolveId) {
  372. continue;
  373. }
  374. const { filename } = (0, utils_2.parseVueRequest)(resolveId.id);
  375. if (constants_1.EXTNAME_VUE_RE.test(filename)) {
  376. source.value = resolveId.id;
  377. vueComponentImports.push(imports[i]);
  378. }
  379. }
  380. return vueComponentImports;
  381. }
  382. /**
  383. * static import => dynamic import
  384. * @param code
  385. * @param imports
  386. * @param dynamicImport
  387. * @returns
  388. */
  389. async function transformDynamicImports(code, imports, { id, sourceMap, dynamicImport, }) {
  390. if (!imports.length) {
  391. return {
  392. code,
  393. map: null,
  394. };
  395. }
  396. const s = new magic_string_1.default(code);
  397. for (let i = 0; i < imports.length; i++) {
  398. const { start, end, specifiers: [specifier], source, } = imports[i];
  399. s.overwrite(start, end, dynamicImport(specifier.local.name, source.value) + ';');
  400. }
  401. return {
  402. code: s.toString(),
  403. map: null,
  404. };
  405. }
  406. exports.transformDynamicImports = transformDynamicImports;