ResolverFactory.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const versions = require("process").versions;
  7. const Resolver = require("./Resolver");
  8. const { getType, PathType } = require("./util/path");
  9. const SyncAsyncFileSystemDecorator = require("./SyncAsyncFileSystemDecorator");
  10. const AliasFieldPlugin = require("./AliasFieldPlugin");
  11. const AliasPlugin = require("./AliasPlugin");
  12. const AppendPlugin = require("./AppendPlugin");
  13. const ConditionalPlugin = require("./ConditionalPlugin");
  14. const DescriptionFilePlugin = require("./DescriptionFilePlugin");
  15. const DirectoryExistsPlugin = require("./DirectoryExistsPlugin");
  16. const ExportsFieldPlugin = require("./ExportsFieldPlugin");
  17. const FileExistsPlugin = require("./FileExistsPlugin");
  18. const ImportsFieldPlugin = require("./ImportsFieldPlugin");
  19. const JoinRequestPartPlugin = require("./JoinRequestPartPlugin");
  20. const JoinRequestPlugin = require("./JoinRequestPlugin");
  21. const MainFieldPlugin = require("./MainFieldPlugin");
  22. const ModulesInHierarchicalDirectoriesPlugin = require("./ModulesInHierarchicalDirectoriesPlugin");
  23. const ModulesInRootPlugin = require("./ModulesInRootPlugin");
  24. const NextPlugin = require("./NextPlugin");
  25. const ParsePlugin = require("./ParsePlugin");
  26. const PnpPlugin = require("./PnpPlugin");
  27. const RestrictionsPlugin = require("./RestrictionsPlugin");
  28. const ResultPlugin = require("./ResultPlugin");
  29. const RootsPlugin = require("./RootsPlugin");
  30. const SelfReferencePlugin = require("./SelfReferencePlugin");
  31. const SymlinkPlugin = require("./SymlinkPlugin");
  32. const TryNextPlugin = require("./TryNextPlugin");
  33. const UnsafeCachePlugin = require("./UnsafeCachePlugin");
  34. const UseFilePlugin = require("./UseFilePlugin");
  35. /** @typedef {import("./AliasPlugin").AliasOption} AliasOptionEntry */
  36. /** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
  37. /** @typedef {import("./Resolver").FileSystem} FileSystem */
  38. /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
  39. /** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
  40. /** @typedef {string|string[]|false} AliasOptionNewRequest */
  41. /** @typedef {{[k: string]: AliasOptionNewRequest}} AliasOptions */
  42. /** @typedef {{apply: function(Resolver): void} | function(this: Resolver, Resolver): void} Plugin */
  43. /**
  44. * @typedef {Object} UserResolveOptions
  45. * @property {(AliasOptions | AliasOptionEntry[])=} alias A list of module alias configurations or an object which maps key to value
  46. * @property {(AliasOptions | AliasOptionEntry[])=} fallback A list of module alias configurations or an object which maps key to value, applied only after modules option
  47. * @property {(string | string[])[]=} aliasFields A list of alias fields in description files
  48. * @property {(function(ResolveRequest): boolean)=} cachePredicate A function which decides whether a request should be cached or not. An object is passed with at least `path` and `request` properties.
  49. * @property {boolean=} cacheWithContext Whether or not the unsafeCache should include request context as part of the cache key.
  50. * @property {string[]=} descriptionFiles A list of description files to read from
  51. * @property {string[]=} conditionNames A list of exports field condition names.
  52. * @property {boolean=} enforceExtension Enforce that a extension from extensions must be used
  53. * @property {(string | string[])[]=} exportsFields A list of exports fields in description files
  54. * @property {(string | string[])[]=} importsFields A list of imports fields in description files
  55. * @property {string[]=} extensions A list of extensions which should be tried for files
  56. * @property {FileSystem} fileSystem The file system which should be used
  57. * @property {(object | boolean)=} unsafeCache Use this cache object to unsafely cache the successful requests
  58. * @property {boolean=} symlinks Resolve symlinks to their symlinked location
  59. * @property {Resolver=} resolver A prepared Resolver to which the plugins are attached
  60. * @property {string[] | string=} modules A list of directories to resolve modules from, can be absolute path or folder name
  61. * @property {(string | string[] | {name: string | string[], forceRelative: boolean})[]=} mainFields A list of main fields in description files
  62. * @property {string[]=} mainFiles A list of main files in directories
  63. * @property {Plugin[]=} plugins A list of additional resolve plugins which should be applied
  64. * @property {PnpApi | null=} pnpApi A PnP API that should be used - null is "never", undefined is "auto"
  65. * @property {string[]=} roots A list of root paths
  66. * @property {boolean=} fullySpecified The request is already fully specified and no extensions or directories are resolved for it
  67. * @property {boolean=} resolveToContext Resolve to a context instead of a file
  68. * @property {(string|RegExp)[]=} restrictions A list of resolve restrictions
  69. * @property {boolean=} useSyncFileSystemCalls Use only the sync constraints of the file system calls
  70. * @property {boolean=} preferRelative Prefer to resolve module requests as relative requests before falling back to modules
  71. * @property {boolean=} preferAbsolute Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots
  72. */
  73. /**
  74. * @typedef {Object} ResolveOptions
  75. * @property {AliasOptionEntry[]} alias
  76. * @property {AliasOptionEntry[]} fallback
  77. * @property {Set<string | string[]>} aliasFields
  78. * @property {(function(ResolveRequest): boolean)} cachePredicate
  79. * @property {boolean} cacheWithContext
  80. * @property {Set<string>} conditionNames A list of exports field condition names.
  81. * @property {string[]} descriptionFiles
  82. * @property {boolean} enforceExtension
  83. * @property {Set<string | string[]>} exportsFields
  84. * @property {Set<string | string[]>} importsFields
  85. * @property {Set<string>} extensions
  86. * @property {FileSystem} fileSystem
  87. * @property {object | false} unsafeCache
  88. * @property {boolean} symlinks
  89. * @property {Resolver=} resolver
  90. * @property {Array<string | string[]>} modules
  91. * @property {{name: string[], forceRelative: boolean}[]} mainFields
  92. * @property {Set<string>} mainFiles
  93. * @property {Plugin[]} plugins
  94. * @property {PnpApi | null} pnpApi
  95. * @property {Set<string>} roots
  96. * @property {boolean} fullySpecified
  97. * @property {boolean} resolveToContext
  98. * @property {Set<string|RegExp>} restrictions
  99. * @property {boolean} preferRelative
  100. * @property {boolean} preferAbsolute
  101. */
  102. /**
  103. * @param {PnpApi | null=} option option
  104. * @returns {PnpApi | null} processed option
  105. */
  106. function processPnpApiOption(option) {
  107. if (
  108. option === undefined &&
  109. /** @type {NodeJS.ProcessVersions & {pnp: string}} */ versions.pnp
  110. ) {
  111. // @ts-ignore
  112. return require("pnpapi"); // eslint-disable-line node/no-missing-require
  113. }
  114. return option || null;
  115. }
  116. /**
  117. * @param {AliasOptions | AliasOptionEntry[] | undefined} alias alias
  118. * @returns {AliasOptionEntry[]} normalized aliases
  119. */
  120. function normalizeAlias(alias) {
  121. return typeof alias === "object" && !Array.isArray(alias) && alias !== null
  122. ? Object.keys(alias).map(key => {
  123. /** @type {AliasOptionEntry} */
  124. const obj = { name: key, onlyModule: false, alias: alias[key] };
  125. if (/\$$/.test(key)) {
  126. obj.onlyModule = true;
  127. obj.name = key.substr(0, key.length - 1);
  128. }
  129. return obj;
  130. })
  131. : /** @type {Array<AliasOptionEntry>} */ (alias) || [];
  132. }
  133. /**
  134. * @param {UserResolveOptions} options input options
  135. * @returns {ResolveOptions} output options
  136. */
  137. function createOptions(options) {
  138. const mainFieldsSet = new Set(options.mainFields || ["main"]);
  139. const mainFields = [];
  140. for (const item of mainFieldsSet) {
  141. if (typeof item === "string") {
  142. mainFields.push({
  143. name: [item],
  144. forceRelative: true
  145. });
  146. } else if (Array.isArray(item)) {
  147. mainFields.push({
  148. name: item,
  149. forceRelative: true
  150. });
  151. } else {
  152. mainFields.push({
  153. name: Array.isArray(item.name) ? item.name : [item.name],
  154. forceRelative: item.forceRelative
  155. });
  156. }
  157. }
  158. return {
  159. alias: normalizeAlias(options.alias),
  160. fallback: normalizeAlias(options.fallback),
  161. aliasFields: new Set(options.aliasFields),
  162. cachePredicate:
  163. options.cachePredicate ||
  164. function () {
  165. return true;
  166. },
  167. cacheWithContext:
  168. typeof options.cacheWithContext !== "undefined"
  169. ? options.cacheWithContext
  170. : true,
  171. exportsFields: new Set(options.exportsFields || ["exports"]),
  172. importsFields: new Set(options.importsFields || ["imports"]),
  173. conditionNames: new Set(options.conditionNames),
  174. descriptionFiles: Array.from(
  175. new Set(options.descriptionFiles || ["package.json"])
  176. ),
  177. enforceExtension:
  178. options.enforceExtension === undefined
  179. ? options.extensions && options.extensions.includes("")
  180. ? true
  181. : false
  182. : options.enforceExtension,
  183. extensions: new Set(options.extensions || [".js", ".json", ".node"]),
  184. fileSystem: options.useSyncFileSystemCalls
  185. ? new SyncAsyncFileSystemDecorator(
  186. /** @type {SyncFileSystem} */ (
  187. /** @type {unknown} */ (options.fileSystem)
  188. )
  189. )
  190. : options.fileSystem,
  191. unsafeCache:
  192. options.unsafeCache && typeof options.unsafeCache !== "object"
  193. ? {}
  194. : options.unsafeCache || false,
  195. symlinks: typeof options.symlinks !== "undefined" ? options.symlinks : true,
  196. resolver: options.resolver,
  197. modules: mergeFilteredToArray(
  198. Array.isArray(options.modules)
  199. ? options.modules
  200. : options.modules
  201. ? [options.modules]
  202. : ["node_modules"],
  203. item => {
  204. const type = getType(item);
  205. return type === PathType.Normal || type === PathType.Relative;
  206. }
  207. ),
  208. mainFields,
  209. mainFiles: new Set(options.mainFiles || ["index"]),
  210. plugins: options.plugins || [],
  211. pnpApi: processPnpApiOption(options.pnpApi),
  212. roots: new Set(options.roots || undefined),
  213. fullySpecified: options.fullySpecified || false,
  214. resolveToContext: options.resolveToContext || false,
  215. preferRelative: options.preferRelative || false,
  216. preferAbsolute: options.preferAbsolute || false,
  217. restrictions: new Set(options.restrictions)
  218. };
  219. }
  220. /**
  221. * @param {UserResolveOptions} options resolve options
  222. * @returns {Resolver} created resolver
  223. */
  224. exports.createResolver = function (options) {
  225. const normalizedOptions = createOptions(options);
  226. const {
  227. alias,
  228. fallback,
  229. aliasFields,
  230. cachePredicate,
  231. cacheWithContext,
  232. conditionNames,
  233. descriptionFiles,
  234. enforceExtension,
  235. exportsFields,
  236. importsFields,
  237. extensions,
  238. fileSystem,
  239. fullySpecified,
  240. mainFields,
  241. mainFiles,
  242. modules,
  243. plugins: userPlugins,
  244. pnpApi,
  245. resolveToContext,
  246. preferRelative,
  247. preferAbsolute,
  248. symlinks,
  249. unsafeCache,
  250. resolver: customResolver,
  251. restrictions,
  252. roots
  253. } = normalizedOptions;
  254. const plugins = userPlugins.slice();
  255. const resolver = customResolver
  256. ? customResolver
  257. : new Resolver(fileSystem, normalizedOptions);
  258. //// pipeline ////
  259. resolver.ensureHook("resolve");
  260. resolver.ensureHook("internalResolve");
  261. resolver.ensureHook("newInternalResolve");
  262. resolver.ensureHook("parsedResolve");
  263. resolver.ensureHook("describedResolve");
  264. resolver.ensureHook("internal");
  265. resolver.ensureHook("rawModule");
  266. resolver.ensureHook("module");
  267. resolver.ensureHook("resolveAsModule");
  268. resolver.ensureHook("undescribedResolveInPackage");
  269. resolver.ensureHook("resolveInPackage");
  270. resolver.ensureHook("resolveInExistingDirectory");
  271. resolver.ensureHook("relative");
  272. resolver.ensureHook("describedRelative");
  273. resolver.ensureHook("directory");
  274. resolver.ensureHook("undescribedExistingDirectory");
  275. resolver.ensureHook("existingDirectory");
  276. resolver.ensureHook("undescribedRawFile");
  277. resolver.ensureHook("rawFile");
  278. resolver.ensureHook("file");
  279. resolver.ensureHook("finalFile");
  280. resolver.ensureHook("existingFile");
  281. resolver.ensureHook("resolved");
  282. // TODO remove in next major
  283. // cspell:word Interal
  284. // Backward-compat
  285. resolver.hooks.newInteralResolve = resolver.hooks.newInternalResolve;
  286. // resolve
  287. for (const { source, resolveOptions } of [
  288. { source: "resolve", resolveOptions: { fullySpecified } },
  289. { source: "internal-resolve", resolveOptions: { fullySpecified: false } }
  290. ]) {
  291. if (unsafeCache) {
  292. plugins.push(
  293. new UnsafeCachePlugin(
  294. source,
  295. cachePredicate,
  296. unsafeCache,
  297. cacheWithContext,
  298. `new-${source}`
  299. )
  300. );
  301. plugins.push(
  302. new ParsePlugin(`new-${source}`, resolveOptions, "parsed-resolve")
  303. );
  304. } else {
  305. plugins.push(new ParsePlugin(source, resolveOptions, "parsed-resolve"));
  306. }
  307. }
  308. // parsed-resolve
  309. plugins.push(
  310. new DescriptionFilePlugin(
  311. "parsed-resolve",
  312. descriptionFiles,
  313. false,
  314. "described-resolve"
  315. )
  316. );
  317. plugins.push(new NextPlugin("after-parsed-resolve", "described-resolve"));
  318. // described-resolve
  319. plugins.push(new NextPlugin("described-resolve", "normal-resolve"));
  320. if (fallback.length > 0) {
  321. plugins.push(
  322. new AliasPlugin("described-resolve", fallback, "internal-resolve")
  323. );
  324. }
  325. // normal-resolve
  326. if (alias.length > 0)
  327. plugins.push(new AliasPlugin("normal-resolve", alias, "internal-resolve"));
  328. aliasFields.forEach(item => {
  329. plugins.push(
  330. new AliasFieldPlugin("normal-resolve", item, "internal-resolve")
  331. );
  332. });
  333. if (preferRelative) {
  334. plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
  335. }
  336. plugins.push(
  337. new ConditionalPlugin(
  338. "after-normal-resolve",
  339. { module: true },
  340. "resolve as module",
  341. false,
  342. "raw-module"
  343. )
  344. );
  345. plugins.push(
  346. new ConditionalPlugin(
  347. "after-normal-resolve",
  348. { internal: true },
  349. "resolve as internal import",
  350. false,
  351. "internal"
  352. )
  353. );
  354. if (preferAbsolute) {
  355. plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
  356. }
  357. if (roots.size > 0) {
  358. plugins.push(new RootsPlugin("after-normal-resolve", roots, "relative"));
  359. }
  360. if (!preferRelative && !preferAbsolute) {
  361. plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
  362. }
  363. // internal
  364. importsFields.forEach(importsField => {
  365. plugins.push(
  366. new ImportsFieldPlugin(
  367. "internal",
  368. conditionNames,
  369. importsField,
  370. "relative",
  371. "internal-resolve"
  372. )
  373. );
  374. });
  375. // raw-module
  376. exportsFields.forEach(exportsField => {
  377. plugins.push(
  378. new SelfReferencePlugin("raw-module", exportsField, "resolve-as-module")
  379. );
  380. });
  381. modules.forEach(item => {
  382. if (Array.isArray(item)) {
  383. if (item.includes("node_modules") && pnpApi) {
  384. plugins.push(
  385. new ModulesInHierarchicalDirectoriesPlugin(
  386. "raw-module",
  387. item.filter(i => i !== "node_modules"),
  388. "module"
  389. )
  390. );
  391. plugins.push(
  392. new PnpPlugin("raw-module", pnpApi, "undescribed-resolve-in-package")
  393. );
  394. } else {
  395. plugins.push(
  396. new ModulesInHierarchicalDirectoriesPlugin(
  397. "raw-module",
  398. item,
  399. "module"
  400. )
  401. );
  402. }
  403. } else {
  404. plugins.push(new ModulesInRootPlugin("raw-module", item, "module"));
  405. }
  406. });
  407. // module
  408. plugins.push(new JoinRequestPartPlugin("module", "resolve-as-module"));
  409. // resolve-as-module
  410. if (!resolveToContext) {
  411. plugins.push(
  412. new ConditionalPlugin(
  413. "resolve-as-module",
  414. { directory: false, request: "." },
  415. "single file module",
  416. true,
  417. "undescribed-raw-file"
  418. )
  419. );
  420. }
  421. plugins.push(
  422. new DirectoryExistsPlugin(
  423. "resolve-as-module",
  424. "undescribed-resolve-in-package"
  425. )
  426. );
  427. // undescribed-resolve-in-package
  428. plugins.push(
  429. new DescriptionFilePlugin(
  430. "undescribed-resolve-in-package",
  431. descriptionFiles,
  432. false,
  433. "resolve-in-package"
  434. )
  435. );
  436. plugins.push(
  437. new NextPlugin("after-undescribed-resolve-in-package", "resolve-in-package")
  438. );
  439. // resolve-in-package
  440. exportsFields.forEach(exportsField => {
  441. plugins.push(
  442. new ExportsFieldPlugin(
  443. "resolve-in-package",
  444. conditionNames,
  445. exportsField,
  446. "relative"
  447. )
  448. );
  449. });
  450. plugins.push(
  451. new NextPlugin("resolve-in-package", "resolve-in-existing-directory")
  452. );
  453. // resolve-in-existing-directory
  454. plugins.push(
  455. new JoinRequestPlugin("resolve-in-existing-directory", "relative")
  456. );
  457. // relative
  458. plugins.push(
  459. new DescriptionFilePlugin(
  460. "relative",
  461. descriptionFiles,
  462. true,
  463. "described-relative"
  464. )
  465. );
  466. plugins.push(new NextPlugin("after-relative", "described-relative"));
  467. // described-relative
  468. if (resolveToContext) {
  469. plugins.push(new NextPlugin("described-relative", "directory"));
  470. } else {
  471. plugins.push(
  472. new ConditionalPlugin(
  473. "described-relative",
  474. { directory: false },
  475. null,
  476. true,
  477. "raw-file"
  478. )
  479. );
  480. plugins.push(
  481. new ConditionalPlugin(
  482. "described-relative",
  483. { fullySpecified: false },
  484. "as directory",
  485. true,
  486. "directory"
  487. )
  488. );
  489. }
  490. // directory
  491. plugins.push(
  492. new DirectoryExistsPlugin("directory", "undescribed-existing-directory")
  493. );
  494. if (resolveToContext) {
  495. // undescribed-existing-directory
  496. plugins.push(new NextPlugin("undescribed-existing-directory", "resolved"));
  497. } else {
  498. // undescribed-existing-directory
  499. plugins.push(
  500. new DescriptionFilePlugin(
  501. "undescribed-existing-directory",
  502. descriptionFiles,
  503. false,
  504. "existing-directory"
  505. )
  506. );
  507. mainFiles.forEach(item => {
  508. plugins.push(
  509. new UseFilePlugin(
  510. "undescribed-existing-directory",
  511. item,
  512. "undescribed-raw-file"
  513. )
  514. );
  515. });
  516. // described-existing-directory
  517. mainFields.forEach(item => {
  518. plugins.push(
  519. new MainFieldPlugin(
  520. "existing-directory",
  521. item,
  522. "resolve-in-existing-directory"
  523. )
  524. );
  525. });
  526. mainFiles.forEach(item => {
  527. plugins.push(
  528. new UseFilePlugin("existing-directory", item, "undescribed-raw-file")
  529. );
  530. });
  531. // undescribed-raw-file
  532. plugins.push(
  533. new DescriptionFilePlugin(
  534. "undescribed-raw-file",
  535. descriptionFiles,
  536. true,
  537. "raw-file"
  538. )
  539. );
  540. plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file"));
  541. // raw-file
  542. plugins.push(
  543. new ConditionalPlugin(
  544. "raw-file",
  545. { fullySpecified: true },
  546. null,
  547. false,
  548. "file"
  549. )
  550. );
  551. if (!enforceExtension) {
  552. plugins.push(new TryNextPlugin("raw-file", "no extension", "file"));
  553. }
  554. extensions.forEach(item => {
  555. plugins.push(new AppendPlugin("raw-file", item, "file"));
  556. });
  557. // file
  558. if (alias.length > 0)
  559. plugins.push(new AliasPlugin("file", alias, "internal-resolve"));
  560. aliasFields.forEach(item => {
  561. plugins.push(new AliasFieldPlugin("file", item, "internal-resolve"));
  562. });
  563. plugins.push(new NextPlugin("file", "final-file"));
  564. // final-file
  565. plugins.push(new FileExistsPlugin("final-file", "existing-file"));
  566. // existing-file
  567. if (symlinks)
  568. plugins.push(new SymlinkPlugin("existing-file", "existing-file"));
  569. plugins.push(new NextPlugin("existing-file", "resolved"));
  570. }
  571. // resolved
  572. if (restrictions.size > 0) {
  573. plugins.push(new RestrictionsPlugin(resolver.hooks.resolved, restrictions));
  574. }
  575. plugins.push(new ResultPlugin(resolver.hooks.resolved));
  576. //// RESOLVER ////
  577. for (const plugin of plugins) {
  578. if (typeof plugin === "function") {
  579. plugin.call(resolver, resolver);
  580. } else {
  581. plugin.apply(resolver);
  582. }
  583. }
  584. return resolver;
  585. };
  586. /**
  587. * Merging filtered elements
  588. * @param {string[]} array source array
  589. * @param {function(string): boolean} filter predicate
  590. * @returns {Array<string | string[]>} merge result
  591. */
  592. function mergeFilteredToArray(array, filter) {
  593. /** @type {Array<string | string[]>} */
  594. const result = [];
  595. const set = new Set(array);
  596. for (const item of set) {
  597. if (filter(item)) {
  598. const lastElement =
  599. result.length > 0 ? result[result.length - 1] : undefined;
  600. if (Array.isArray(lastElement)) {
  601. lastElement.push(item);
  602. } else {
  603. result.push([item]);
  604. }
  605. } else {
  606. result.push(item);
  607. }
  608. }
  609. return result;
  610. }