index.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. 'use strict';
  2. const fs = require('fs');
  3. const path = require('upath');
  4. const chokidar = require('chokidar');
  5. const consola = require('consola');
  6. const chalk = require('chalk');
  7. const semver = require('semver');
  8. const globby = require('globby');
  9. const scule = require('scule');
  10. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  11. const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
  12. const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
  13. const chokidar__default = /*#__PURE__*/_interopDefaultLegacy(chokidar);
  14. const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
  15. const chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
  16. const semver__default = /*#__PURE__*/_interopDefaultLegacy(semver);
  17. const globby__default = /*#__PURE__*/_interopDefaultLegacy(globby);
  18. function requireNuxtVersion(currentVersion, requiredVersion) {
  19. const pkgName = require("../package.json").name;
  20. if (!currentVersion || !requireNuxtVersion) {
  21. return;
  22. }
  23. const _currentVersion = semver__default['default'].coerce(currentVersion);
  24. const _requiredVersion = semver__default['default'].coerce(requiredVersion);
  25. if (semver__default['default'].lt(_currentVersion, _requiredVersion)) {
  26. throw new Error(`
  27. ${chalk__default['default'].cyan(pkgName)} is not compatible with your current Nuxt version : ${chalk__default['default'].yellow("v" + currentVersion)}
  28. Required: ${chalk__default['default'].green("v" + requiredVersion)} or ${chalk__default['default'].cyan("higher")}
  29. `);
  30. }
  31. }
  32. function sortDirsByPathLength({ path: pathA }, { path: pathB }) {
  33. return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length;
  34. }
  35. function hyphenate(str) {
  36. return str.replace(/\B([A-Z])/g, "-$1").toLowerCase();
  37. }
  38. async function scanComponents(dirs, srcDir) {
  39. const components = [];
  40. const filePaths = new Set();
  41. const scannedPaths = [];
  42. for (const { path: path$1, pattern, ignore = [], prefix, extendComponent, pathPrefix, level, prefetch = false, preload = false, isAsync: dirIsAsync } of dirs.sort(sortDirsByPathLength)) {
  43. const resolvedNames = new Map();
  44. for (const _file of await globby__default['default'](pattern, { cwd: path$1, ignore })) {
  45. const filePath = path.join(path$1, _file);
  46. if (scannedPaths.find((d) => filePath.startsWith(d))) {
  47. continue;
  48. }
  49. if (filePaths.has(filePath)) {
  50. continue;
  51. }
  52. filePaths.add(filePath);
  53. const prefixParts = [].concat(prefix ? scule.splitByCase(prefix) : [], pathPrefix !== false ? scule.splitByCase(path.relative(path$1, path.dirname(filePath))) : []);
  54. let fileName = path.basename(filePath, path.extname(filePath));
  55. if (fileName.toLowerCase() === "index") {
  56. fileName = pathPrefix === false ? path.basename(path.dirname(filePath)) : "";
  57. }
  58. const isAsync = (fileName.endsWith(".async") ? true : dirIsAsync) || null;
  59. fileName = fileName.replace(/\.async$/, "");
  60. const fileNameParts = scule.splitByCase(fileName);
  61. const componentNameParts = [];
  62. while (prefixParts.length && (prefixParts[0] || "").toLowerCase() !== (fileNameParts[0] || "").toLowerCase()) {
  63. componentNameParts.push(prefixParts.shift());
  64. }
  65. const componentName = scule.pascalCase(componentNameParts).replace(/^\d+/, "") + scule.pascalCase(fileNameParts).replace(/^\d+/, "");
  66. if (resolvedNames.has(componentName)) {
  67. console.warn(`Two component files resolving to the same name \`${componentName}\`:
  68. - ${filePath}
  69. - ${resolvedNames.get(componentName)}`);
  70. continue;
  71. }
  72. resolvedNames.set(componentName, filePath);
  73. const pascalName = scule.pascalCase(componentName);
  74. const kebabName = hyphenate(componentName);
  75. const shortPath = path.relative(srcDir, filePath);
  76. const chunkName = "components/" + kebabName;
  77. let component = {
  78. filePath,
  79. pascalName,
  80. kebabName,
  81. chunkName,
  82. shortPath,
  83. isAsync,
  84. import: "",
  85. asyncImport: "",
  86. export: "default",
  87. global: Boolean(global),
  88. level: Number(level),
  89. prefetch: Boolean(prefetch),
  90. preload: Boolean(preload)
  91. };
  92. if (typeof extendComponent === "function") {
  93. component = await extendComponent(component) || component;
  94. }
  95. component.import = component.import || `require('${component.filePath}').${component.export}`;
  96. component.asyncImport = component.asyncImport || `function () { return import('${component.filePath}' /* webpackChunkName: "${component.chunkName}" */).then(function(m) { return m['${component.export}'] || m }) }`;
  97. const definedComponent = components.find((c) => c.pascalName === component.pascalName);
  98. if (definedComponent && component.level < definedComponent.level) {
  99. Object.assign(definedComponent, component);
  100. } else if (!definedComponent) {
  101. components.push(component);
  102. }
  103. }
  104. scannedPaths.push(path$1);
  105. }
  106. return components;
  107. }
  108. const isPureObjectOrString = (val) => !Array.isArray(val) && typeof val === "object" || typeof val === "string";
  109. const getDir = (p) => fs__default['default'].statSync(p).isDirectory() ? p : path__default['default'].dirname(p);
  110. const componentsModule = function() {
  111. var _a;
  112. const { nuxt } = this;
  113. const { components } = nuxt.options;
  114. if (!components) {
  115. return;
  116. }
  117. requireNuxtVersion((_a = nuxt == null ? void 0 : nuxt.constructor) == null ? void 0 : _a.version, "2.10");
  118. const options = {
  119. dirs: ["~/components"],
  120. loader: !nuxt.options.dev,
  121. ...Array.isArray(components) ? { dirs: components } : components
  122. };
  123. nuxt.hook("build:before", async (builder) => {
  124. const nuxtIgnorePatterns = builder.ignore.ignore ? builder.ignore.ignore._rules.map((rule) => rule.pattern) : [];
  125. await nuxt.callHook("components:dirs", options.dirs);
  126. const resolvePath = (dir) => nuxt.resolver.resolvePath(dir);
  127. try {
  128. const globalDir = getDir(resolvePath("~/components/global"));
  129. if (!options.dirs.find((dir) => resolvePath(dir) === globalDir)) {
  130. options.dirs.push({
  131. path: globalDir
  132. });
  133. }
  134. } catch (err) {
  135. nuxt.options.watch.push(path__default['default'].resolve(nuxt.options.srcDir, "components", "global"));
  136. }
  137. const componentDirs = options.dirs.filter(isPureObjectOrString).map((dir) => {
  138. const dirOptions = typeof dir === "object" ? dir : { path: dir };
  139. let dirPath = dirOptions.path;
  140. try {
  141. dirPath = getDir(nuxt.resolver.resolvePath(dirOptions.path));
  142. } catch (err) {
  143. }
  144. const transpile = typeof dirOptions.transpile === "boolean" ? dirOptions.transpile : "auto";
  145. dirOptions.level = Number(dirOptions.level || 0);
  146. const enabled = fs__default['default'].existsSync(dirPath);
  147. if (!enabled && dirOptions.path !== "~/components") {
  148. console.warn("Components directory not found: `" + dirPath + "`");
  149. }
  150. const extensions = dirOptions.extensions || builder.supportedExtensions;
  151. return {
  152. ...dirOptions,
  153. enabled,
  154. path: dirPath,
  155. extensions,
  156. pattern: dirOptions.pattern || `**/*.{${extensions.join(",")},}`,
  157. isAsync: dirOptions.isAsync,
  158. ignore: [
  159. "**/*.stories.{js,ts,jsx,tsx}",
  160. "**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}",
  161. "**/*.d.ts",
  162. ...nuxtIgnorePatterns,
  163. ...dirOptions.ignore || []
  164. ],
  165. transpile: transpile === "auto" ? dirPath.includes("node_modules") : transpile
  166. };
  167. }).filter((d) => d.enabled);
  168. nuxt.options.build.transpile.push(...componentDirs.filter((dir) => dir.transpile).map((dir) => dir.path));
  169. let components2 = await scanComponents(componentDirs, nuxt.options.srcDir);
  170. await nuxt.callHook("components:extend", components2);
  171. if (options.loader) {
  172. consola__default['default'].info("Using components loader to optimize imports");
  173. this.extendBuild((config) => {
  174. var _a2;
  175. const vueRule = (_a2 = config.module) == null ? void 0 : _a2.rules.find((rule) => {
  176. var _a3;
  177. return (_a3 = rule.test) == null ? void 0 : _a3.toString().includes(".vue");
  178. });
  179. if (!vueRule) {
  180. throw new Error("Cannot find vue loader");
  181. }
  182. if (!vueRule.use) {
  183. vueRule.use = [{
  184. loader: vueRule.loader.toString(),
  185. options: vueRule.options
  186. }];
  187. delete vueRule.loader;
  188. delete vueRule.options;
  189. }
  190. if (!Array.isArray(vueRule.use)) {
  191. vueRule.use = [vueRule.use];
  192. }
  193. vueRule.use.unshift({
  194. loader: require.resolve("./loader"),
  195. options: {
  196. getComponents: () => components2
  197. }
  198. });
  199. });
  200. nuxt.hook("webpack:config", (configs) => {
  201. for (const config of configs.filter((c) => ["client", "modern", "server"].includes(c.name))) {
  202. config.entry.app.unshift(path__default['default'].resolve(__dirname, "../lib/installComponents.js"));
  203. }
  204. });
  205. }
  206. if (nuxt.options.dev && componentDirs.some((dir) => dir.watch !== false)) {
  207. const watcher = chokidar__default['default'].watch(componentDirs.filter((dir) => dir.watch !== false).map((dir) => dir.path), nuxt.options.watchers.chokidar);
  208. watcher.on("all", async (eventName) => {
  209. if (!["add", "unlink"].includes(eventName)) {
  210. return;
  211. }
  212. components2 = await scanComponents(componentDirs, nuxt.options.srcDir);
  213. await nuxt.callHook("components:extend", components2);
  214. await builder.generateRoutesAndFiles();
  215. });
  216. nuxt.hook("close", () => {
  217. watcher.close();
  218. });
  219. }
  220. const getComponents = () => components2;
  221. const templates = [
  222. "components/index.js",
  223. "components/plugin.js",
  224. "components/readme_md",
  225. "vetur/tags.json"
  226. ];
  227. for (const t of templates) {
  228. this[t.includes("plugin") ? "addPlugin" : "addTemplate"]({
  229. src: path__default['default'].resolve(__dirname, "../templates", t),
  230. fileName: t.replace("_", "."),
  231. options: { getComponents }
  232. });
  233. }
  234. const componentsListFile = path__default['default'].resolve(nuxt.options.buildDir, "components/readme.md");
  235. consola__default['default'].info("Discovered Components:", path__default['default'].relative(process.cwd(), componentsListFile));
  236. });
  237. };
  238. componentsModule.meta = { name: "@nuxt/components" };
  239. module.exports = componentsModule;