utils.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.getImports = getImports;
  6. exports.renderImports = renderImports;
  7. exports.sourceHasUseStrict = sourceHasUseStrict;
  8. var _path = _interopRequireDefault(require("path"));
  9. var _stripComments = _interopRequireDefault(require("strip-comments"));
  10. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  11. const matchRelativePath = /^\.\.?[/\\]/;
  12. function isAbsolutePath(str) {
  13. return _path.default.posix.isAbsolute(str) || _path.default.win32.isAbsolute(str);
  14. }
  15. function isRelativePath(str) {
  16. return matchRelativePath.test(str);
  17. } // TODO simplify for the next major release
  18. function stringifyRequest(loaderContext, request) {
  19. if (typeof loaderContext.utils !== "undefined" && typeof loaderContext.utils.contextify === "function") {
  20. return JSON.stringify(loaderContext.utils.contextify(loaderContext.context, request));
  21. }
  22. const splitted = request.split("!");
  23. const {
  24. context
  25. } = loaderContext;
  26. return JSON.stringify(splitted.map(part => {
  27. // First, separate singlePath from query, because the query might contain paths again
  28. const splittedPart = part.match(/^(.*?)(\?.*)/);
  29. const query = splittedPart ? splittedPart[2] : "";
  30. let singlePath = splittedPart ? splittedPart[1] : part;
  31. if (isAbsolutePath(singlePath) && context) {
  32. singlePath = _path.default.relative(context, singlePath);
  33. if (isAbsolutePath(singlePath)) {
  34. // If singlePath still matches an absolute path, singlePath was on a different drive than context.
  35. // In this case, we leave the path platform-specific without replacing any separators.
  36. // @see https://github.com/webpack/loader-utils/pull/14
  37. return singlePath + query;
  38. }
  39. if (isRelativePath(singlePath) === false) {
  40. // Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules).
  41. singlePath = `./${singlePath}`;
  42. }
  43. }
  44. return singlePath.replace(/\\/g, "/") + query;
  45. }).join("!"));
  46. }
  47. function forError(item) {
  48. return typeof item === "string" ? item : `\n${JSON.stringify(item, null, " ")}\n`;
  49. }
  50. function sourceHasUseStrict(source) {
  51. const str = (0, _stripComments.default)(source).trim();
  52. return str.startsWith("'use strict'") || str.startsWith('"use strict"');
  53. }
  54. function splitCommand(command) {
  55. const result = command.split("|").map(item => item.split(" ")).reduce((acc, val) => acc.concat(val), []);
  56. for (const item of result) {
  57. if (!item) {
  58. throw new Error(`Invalid command "${item}" in "${command}" for imports. There must be only one separator: " ", or "|"`);
  59. }
  60. }
  61. return result;
  62. }
  63. function resolveImports(type, item) {
  64. const defaultSyntax = type === "module" ? "default" : "single";
  65. let result;
  66. if (typeof item === "string") {
  67. const noWhitespaceItem = item.trim();
  68. if (noWhitespaceItem.length === 0) {
  69. throw new Error(`Invalid "${item}" value for import`);
  70. }
  71. const splittedItem = splitCommand(noWhitespaceItem);
  72. if (splittedItem.length > 4) {
  73. throw new Error(`Invalid "${item}" value for import`);
  74. }
  75. if (splittedItem.length === 1) {
  76. result = {
  77. syntax: defaultSyntax,
  78. moduleName: splittedItem[0],
  79. name: splittedItem[0],
  80. // eslint-disable-next-line no-undefined
  81. alias: undefined
  82. };
  83. } else {
  84. result = {
  85. syntax: splittedItem[0],
  86. moduleName: splittedItem[1],
  87. name: splittedItem[2],
  88. // eslint-disable-next-line no-undefined
  89. alias: splittedItem[3] ? splittedItem[3] : undefined
  90. };
  91. }
  92. } else {
  93. result = {
  94. syntax: defaultSyntax,
  95. ...item
  96. };
  97. }
  98. if (result.syntax === defaultSyntax && typeof result.name === "undefined") {
  99. result.name = result.moduleName;
  100. }
  101. if (["default", "side-effects", "single", "pure"].includes(result.syntax) && typeof result.alias !== "undefined") {
  102. throw new Error(`The "${result.syntax}" syntax does not support "${result.alias}" alias in "${forError(item)}" value`);
  103. }
  104. if (["side-effects", "pure"].includes(result.syntax) && typeof result.name !== "undefined") {
  105. throw new Error(`The "${result.syntax}" syntax does not support "${result.name}" name in "${forError(item)}" value`);
  106. }
  107. if (["default", "namespace", "named", "side-effects"].includes(result.syntax) && type === "commonjs") {
  108. throw new Error(`The "${type}" type does not support the "${result.syntax}" syntax in "${forError(item)}" value`);
  109. }
  110. if (["single", "multiple", "pure"].includes(result.syntax) && type === "module") {
  111. throw new Error(`The "${type}" format does not support the "${result.syntax}" syntax in "${forError(item)}" value`);
  112. }
  113. if (["namespace", "named", "multiple"].includes(result.syntax) && typeof result.name === "undefined") {
  114. throw new Error(`The "${result.syntax}" syntax needs the "name" option in "${forError(item)}" value`);
  115. }
  116. return result;
  117. }
  118. function getIdentifiers(array) {
  119. return array.reduce((accumulator, item) => {
  120. if (typeof item.alias !== "undefined") {
  121. accumulator.push({
  122. type: "alias",
  123. value: item.alias
  124. });
  125. return accumulator;
  126. }
  127. if (typeof item.name !== "undefined") {
  128. accumulator.push({
  129. type: "name",
  130. value: item.name
  131. });
  132. }
  133. return accumulator;
  134. }, []);
  135. }
  136. function duplicateBy(array, key) {
  137. return array.filter((a, aIndex) => array.filter((b, bIndex) => b[key] === a[key] && aIndex !== bIndex).length > 0);
  138. }
  139. function getImports(type, imports) {
  140. let result;
  141. const importItems = typeof imports === "string" && imports.includes(",") ? imports.split(",") : imports;
  142. if (typeof importItems === "string") {
  143. result = [resolveImports(type, importItems)];
  144. } else {
  145. result = [].concat(importItems).map(item => resolveImports(type, item));
  146. }
  147. const identifiers = getIdentifiers(result);
  148. const duplicates = duplicateBy(identifiers, "value");
  149. if (duplicates.length > 0) {
  150. throw new Error(`Duplicate ${duplicates.map(identifier => `"${identifier.value}" (as "${identifier.type}")`).join(", ")} identifiers found in "\n${JSON.stringify(importItems, null, " ")}\n" value`);
  151. }
  152. const sortedResults = Object.create(null);
  153. for (const item of result) {
  154. const {
  155. moduleName
  156. } = item;
  157. if (!sortedResults[moduleName]) {
  158. sortedResults[moduleName] = [];
  159. }
  160. const {
  161. syntax,
  162. name,
  163. alias
  164. } = item;
  165. sortedResults[moduleName].push({
  166. syntax,
  167. name,
  168. alias
  169. });
  170. }
  171. return sortedResults;
  172. }
  173. function renderImports(loaderContext, type, moduleName, imports) {
  174. let code = "";
  175. if (type === "commonjs") {
  176. const pure = imports.filter(({
  177. syntax
  178. }) => syntax === "pure"); // Pure
  179. if (pure.length > 0) {
  180. pure.forEach((_, i) => {
  181. const needNewline = i < pure.length - 1 ? "\n" : "";
  182. code += `require(${stringifyRequest(loaderContext, moduleName)});${needNewline}`;
  183. });
  184. }
  185. const singleImports = imports.filter(({
  186. syntax
  187. }) => syntax === "single"); // Single
  188. if (singleImports.length > 0) {
  189. code += pure.length > 0 ? "\n" : "";
  190. singleImports.forEach((singleImport, i) => {
  191. const {
  192. name
  193. } = singleImport;
  194. const needNewline = i < singleImports.length - 1 ? "\n" : "";
  195. code += `var ${name} = require(${stringifyRequest(loaderContext, moduleName)});${needNewline}`;
  196. });
  197. }
  198. const multipleImports = imports.filter(({
  199. syntax
  200. }) => syntax === "multiple"); // Multiple
  201. if (multipleImports.length > 0) {
  202. code += pure.length > 0 || singleImports.length > 0 ? "\n" : "";
  203. code += `var { `;
  204. multipleImports.forEach((multipleImport, i) => {
  205. const needComma = i > 0 ? ", " : "";
  206. const {
  207. name,
  208. alias
  209. } = multipleImport;
  210. const separator = ": ";
  211. code += alias ? `${needComma}${name}${separator}${alias}` : `${needComma}${name}`;
  212. });
  213. code += ` } = require(${stringifyRequest(loaderContext, moduleName)});`;
  214. }
  215. return code;
  216. }
  217. const sideEffectsImports = imports.filter(({
  218. syntax
  219. }) => syntax === "side-effects"); // Side-effects
  220. if (sideEffectsImports.length > 0) {
  221. sideEffectsImports.forEach((_, i) => {
  222. const needNewline = i < sideEffectsImports.length - 1 ? "\n" : "";
  223. code += `import ${stringifyRequest(loaderContext, moduleName)};${needNewline}`;
  224. });
  225. return code;
  226. }
  227. const defaultImports = imports.filter(({
  228. syntax
  229. }) => syntax === "default");
  230. const namedImports = imports.filter(({
  231. syntax
  232. }) => syntax === "named");
  233. const namespaceImports = imports.filter(({
  234. syntax
  235. }) => syntax === "namespace"); // Default
  236. if (defaultImports.length > 0) {
  237. defaultImports.forEach((defaultImport, i) => {
  238. const {
  239. name
  240. } = defaultImport;
  241. const needNewline = i < defaultImports.length - 1 ? "\n" : "";
  242. code += `import ${name} from ${stringifyRequest(loaderContext, moduleName)};${needNewline}`;
  243. });
  244. } // Named
  245. if (namedImports.length > 0) {
  246. code += defaultImports.length > 0 ? "\n" : "";
  247. code += "import { ";
  248. namedImports.forEach((namedImport, i) => {
  249. const needComma = i > 0 ? ", " : "";
  250. const {
  251. name,
  252. alias
  253. } = namedImport;
  254. const separator = " as ";
  255. code += alias ? `${needComma}${name}${separator}${alias}` : `${needComma}${name}`;
  256. });
  257. code += ` } from ${stringifyRequest(loaderContext, moduleName)};`;
  258. } // Namespace
  259. if (namespaceImports.length > 0) {
  260. code += defaultImports.length > 0 || namedImports.length > 0 ? "\n" : "";
  261. namespaceImports.forEach((namespaceImport, i) => {
  262. const {
  263. name
  264. } = namespaceImport;
  265. const needNewline = i < namespaceImports.length - 1 ? "\n" : "";
  266. code += `import * as ${name} from ${stringifyRequest(loaderContext, moduleName)};${needNewline}`;
  267. });
  268. }
  269. return code;
  270. }