utils.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.exec = exec;
  6. exports.findPackageJSONDir = findPackageJSONDir;
  7. exports.getPostcssImplementation = getPostcssImplementation;
  8. exports.getPostcssOptions = getPostcssOptions;
  9. exports.loadConfig = loadConfig;
  10. exports.normalizeSourceMap = normalizeSourceMap;
  11. exports.normalizeSourceMapAfterPostcss = normalizeSourceMapAfterPostcss;
  12. exports.reportError = reportError;
  13. var _path = _interopRequireDefault(require("path"));
  14. var _url = _interopRequireDefault(require("url"));
  15. var _module = _interopRequireDefault(require("module"));
  16. var _full = require("klona/full");
  17. var _cosmiconfig = require("cosmiconfig");
  18. var _Error = _interopRequireDefault(require("./Error"));
  19. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  20. const parentModule = module;
  21. const stat = (inputFileSystem, filePath) => new Promise((resolve, reject) => {
  22. inputFileSystem.stat(filePath, (err, stats) => {
  23. if (err) {
  24. reject(err);
  25. }
  26. resolve(stats);
  27. });
  28. });
  29. function exec(code, loaderContext) {
  30. const {
  31. resource,
  32. context
  33. } = loaderContext;
  34. const module = new _module.default(resource, parentModule);
  35. // eslint-disable-next-line no-underscore-dangle
  36. module.paths = _module.default._nodeModulePaths(context);
  37. module.filename = resource;
  38. // eslint-disable-next-line no-underscore-dangle
  39. module._compile(code, resource);
  40. return module.exports;
  41. }
  42. let tsLoader;
  43. async function loadConfig(loaderContext, config, postcssOptions) {
  44. const searchPath = typeof config === "string" ? _path.default.resolve(config) : _path.default.dirname(loaderContext.resourcePath);
  45. let stats;
  46. try {
  47. stats = await stat(loaderContext.fs, searchPath);
  48. } catch (errorIgnore) {
  49. throw new Error(`No PostCSS config found in: ${searchPath}`);
  50. }
  51. let isTsNodeInstalled = false;
  52. try {
  53. // eslint-disable-next-line import/no-extraneous-dependencies, global-require
  54. require("ts-node");
  55. isTsNodeInstalled = true;
  56. } catch (_) {
  57. // Nothing
  58. }
  59. const moduleName = "postcss";
  60. const searchPlaces = isTsNodeInstalled ? ["package.json", `.${moduleName}rc`, `.${moduleName}rc.json`, `.${moduleName}rc.yaml`, `.${moduleName}rc.yml`, `.${moduleName}rc.js`, `.${moduleName}rc.mjs`, `.${moduleName}rc.cjs`, `.${moduleName}rc.ts`, `.${moduleName}rc.mts`, `.${moduleName}rc.cts`, `.config/${moduleName}rc`, `.config/${moduleName}rc.json`, `.config/${moduleName}rc.yaml`, `.config/${moduleName}rc.yml`, `.config/${moduleName}rc.js`, `.config/${moduleName}rc.mjs`, `.config/${moduleName}rc.cjs`, `.config/${moduleName}rc.ts`, `.config/${moduleName}rc.mts`, `.config/${moduleName}rc.cts`, `${moduleName}.config.js`, `${moduleName}.config.mjs`, `${moduleName}.config.cjs`, `${moduleName}.config.ts`, `${moduleName}.config.mts`, `${moduleName}.config.cts`] : ["package.json", `.${moduleName}rc`, `.${moduleName}rc.json`, `.${moduleName}rc.yaml`, `.${moduleName}rc.yml`, `.${moduleName}rc.js`, `.${moduleName}rc.mjs`, `.${moduleName}rc.cjs`, `.config/${moduleName}rc`, `.config/${moduleName}rc.json`, `.config/${moduleName}rc.yaml`, `.config/${moduleName}rc.yml`, `.config/${moduleName}rc.js`, `.config/${moduleName}rc.mjs`, `.config/${moduleName}rc.cjs`, `${moduleName}.config.js`, `${moduleName}.config.mjs`, `${moduleName}.config.cjs`];
  61. const loaders = {
  62. ".js": async (...args) => {
  63. let result;
  64. try {
  65. result = _cosmiconfig.defaultLoaders[".js"](...args);
  66. } catch (error) {
  67. let importESM;
  68. try {
  69. // eslint-disable-next-line no-new-func
  70. importESM = new Function("id", "return import(id);");
  71. } catch (e) {
  72. importESM = null;
  73. }
  74. if (error.code === "ERR_REQUIRE_ESM" && _url.default.pathToFileURL && importESM) {
  75. const urlForConfig = _url.default.pathToFileURL(args[0]);
  76. result = await importESM(urlForConfig);
  77. } else {
  78. throw error;
  79. }
  80. }
  81. return result;
  82. },
  83. ".mjs": async (...args) => {
  84. let result;
  85. let importESM;
  86. try {
  87. // eslint-disable-next-line no-new-func
  88. importESM = new Function("id", "return import(id);");
  89. } catch (e) {
  90. importESM = null;
  91. }
  92. if (_url.default.pathToFileURL && importESM) {
  93. const urlForConfig = _url.default.pathToFileURL(args[0]);
  94. result = await importESM(urlForConfig);
  95. } else {
  96. throw new Error("ESM is not supported");
  97. }
  98. return result;
  99. }
  100. };
  101. if (isTsNodeInstalled) {
  102. if (!tsLoader) {
  103. // eslint-disable-next-line global-require
  104. const {
  105. TypeScriptLoader
  106. } = require("cosmiconfig-typescript-loader");
  107. tsLoader = TypeScriptLoader();
  108. }
  109. loaders[".cts"] = tsLoader;
  110. loaders[".mts"] = tsLoader;
  111. loaders[".ts"] = tsLoader;
  112. }
  113. const explorer = (0, _cosmiconfig.cosmiconfig)(moduleName, {
  114. searchPlaces,
  115. loaders
  116. });
  117. let result;
  118. try {
  119. if (stats.isFile()) {
  120. result = await explorer.load(searchPath);
  121. } else {
  122. result = await explorer.search(searchPath);
  123. }
  124. } catch (error) {
  125. throw error;
  126. }
  127. if (!result) {
  128. return {};
  129. }
  130. loaderContext.addBuildDependency(result.filepath);
  131. loaderContext.addDependency(result.filepath);
  132. if (result.isEmpty) {
  133. return result;
  134. }
  135. if (typeof result.config === "function") {
  136. const api = {
  137. mode: loaderContext.mode,
  138. file: loaderContext.resourcePath,
  139. // For complex use
  140. webpackLoaderContext: loaderContext,
  141. // Partial compatibility with `postcss-cli`
  142. env: loaderContext.mode,
  143. options: postcssOptions || {}
  144. };
  145. result.config = result.config(api);
  146. }
  147. result = (0, _full.klona)(result);
  148. return result;
  149. }
  150. function loadPlugin(plugin, options, file) {
  151. try {
  152. // eslint-disable-next-line global-require, import/no-dynamic-require
  153. let loadedPlugin = require(plugin);
  154. if (loadedPlugin.default) {
  155. loadedPlugin = loadedPlugin.default;
  156. }
  157. if (!options || Object.keys(options).length === 0) {
  158. return loadedPlugin;
  159. }
  160. return loadedPlugin(options);
  161. } catch (error) {
  162. throw new Error(`Loading PostCSS "${plugin}" plugin failed: ${error.message}\n\n(@${file})`);
  163. }
  164. }
  165. function pluginFactory() {
  166. const listOfPlugins = new Map();
  167. return plugins => {
  168. if (typeof plugins === "undefined") {
  169. return listOfPlugins;
  170. }
  171. if (Array.isArray(plugins)) {
  172. for (const plugin of plugins) {
  173. if (Array.isArray(plugin)) {
  174. const [name, options] = plugin;
  175. listOfPlugins.set(name, options);
  176. } else if (plugin && typeof plugin === "function") {
  177. listOfPlugins.set(plugin);
  178. } else if (plugin && Object.keys(plugin).length === 1 && (typeof plugin[Object.keys(plugin)[0]] === "object" || typeof plugin[Object.keys(plugin)[0]] === "boolean") && plugin[Object.keys(plugin)[0]] !== null) {
  179. const [name] = Object.keys(plugin);
  180. const options = plugin[name];
  181. if (options === false) {
  182. listOfPlugins.delete(name);
  183. } else {
  184. listOfPlugins.set(name, options);
  185. }
  186. } else if (plugin) {
  187. listOfPlugins.set(plugin);
  188. }
  189. }
  190. } else {
  191. const objectPlugins = Object.entries(plugins);
  192. for (const [name, options] of objectPlugins) {
  193. if (options === false) {
  194. listOfPlugins.delete(name);
  195. } else {
  196. listOfPlugins.set(name, options);
  197. }
  198. }
  199. }
  200. return listOfPlugins;
  201. };
  202. }
  203. async function tryRequireThenImport(module) {
  204. let exports;
  205. try {
  206. // eslint-disable-next-line import/no-dynamic-require, global-require
  207. exports = require(module);
  208. return exports;
  209. } catch (requireError) {
  210. let importESM;
  211. try {
  212. // eslint-disable-next-line no-new-func
  213. importESM = new Function("id", "return import(id);");
  214. } catch (e) {
  215. importESM = null;
  216. }
  217. if (requireError.code === "ERR_REQUIRE_ESM" && importESM) {
  218. exports = await importESM(module);
  219. return exports.default;
  220. }
  221. throw requireError;
  222. }
  223. }
  224. async function getPostcssOptions(loaderContext, loadedConfig = {}, postcssOptions = {}) {
  225. const file = loaderContext.resourcePath;
  226. let normalizedPostcssOptions = postcssOptions;
  227. if (typeof normalizedPostcssOptions === "function") {
  228. normalizedPostcssOptions = normalizedPostcssOptions(loaderContext);
  229. }
  230. let plugins = [];
  231. try {
  232. const factory = pluginFactory();
  233. if (loadedConfig.config && loadedConfig.config.plugins) {
  234. factory(loadedConfig.config.plugins);
  235. }
  236. factory(normalizedPostcssOptions.plugins);
  237. plugins = [...factory()].map(item => {
  238. const [plugin, options] = item;
  239. if (typeof plugin === "string") {
  240. return loadPlugin(plugin, options, file);
  241. }
  242. return plugin;
  243. });
  244. } catch (error) {
  245. loaderContext.emitError(error);
  246. }
  247. const processOptionsFromConfig = loadedConfig.config || {};
  248. if (processOptionsFromConfig.from) {
  249. processOptionsFromConfig.from = _path.default.resolve(_path.default.dirname(loadedConfig.filepath), processOptionsFromConfig.from);
  250. }
  251. if (processOptionsFromConfig.to) {
  252. processOptionsFromConfig.to = _path.default.resolve(_path.default.dirname(loadedConfig.filepath), processOptionsFromConfig.to);
  253. }
  254. // No need them for processOptions
  255. delete processOptionsFromConfig.plugins;
  256. const processOptionsFromOptions = (0, _full.klona)(normalizedPostcssOptions);
  257. if (processOptionsFromOptions.from) {
  258. processOptionsFromOptions.from = _path.default.resolve(loaderContext.rootContext, processOptionsFromOptions.from);
  259. }
  260. if (processOptionsFromOptions.to) {
  261. processOptionsFromOptions.to = _path.default.resolve(loaderContext.rootContext, processOptionsFromOptions.to);
  262. }
  263. // No need them for processOptions
  264. delete processOptionsFromOptions.config;
  265. delete processOptionsFromOptions.plugins;
  266. const processOptions = {
  267. from: file,
  268. to: file,
  269. map: false,
  270. ...processOptionsFromConfig,
  271. ...processOptionsFromOptions
  272. };
  273. if (typeof processOptions.parser === "string") {
  274. try {
  275. processOptions.parser = await tryRequireThenImport(processOptions.parser);
  276. } catch (error) {
  277. loaderContext.emitError(new Error(`Loading PostCSS "${processOptions.parser}" parser failed: ${error.message}\n\n(@${file})`));
  278. }
  279. }
  280. if (typeof processOptions.stringifier === "string") {
  281. try {
  282. processOptions.stringifier = await tryRequireThenImport(processOptions.stringifier);
  283. } catch (error) {
  284. loaderContext.emitError(new Error(`Loading PostCSS "${processOptions.stringifier}" stringifier failed: ${error.message}\n\n(@${file})`));
  285. }
  286. }
  287. if (typeof processOptions.syntax === "string") {
  288. try {
  289. processOptions.syntax = await tryRequireThenImport(processOptions.syntax);
  290. } catch (error) {
  291. loaderContext.emitError(new Error(`Loading PostCSS "${processOptions.syntax}" syntax failed: ${error.message}\n\n(@${file})`));
  292. }
  293. }
  294. if (processOptions.map === true) {
  295. // https://github.com/postcss/postcss/blob/master/docs/source-maps.md
  296. processOptions.map = {
  297. inline: true
  298. };
  299. }
  300. return {
  301. plugins,
  302. processOptions
  303. };
  304. }
  305. const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
  306. const ABSOLUTE_SCHEME = /^[a-z0-9+\-.]+:/i;
  307. function getURLType(source) {
  308. if (source[0] === "/") {
  309. if (source[1] === "/") {
  310. return "scheme-relative";
  311. }
  312. return "path-absolute";
  313. }
  314. if (IS_NATIVE_WIN32_PATH.test(source)) {
  315. return "path-absolute";
  316. }
  317. return ABSOLUTE_SCHEME.test(source) ? "absolute" : "path-relative";
  318. }
  319. function normalizeSourceMap(map, resourceContext) {
  320. let newMap = map;
  321. // Some loader emit source map as string
  322. // Strip any JSON XSSI avoidance prefix from the string (as documented in the source maps specification), and then parse the string as JSON.
  323. if (typeof newMap === "string") {
  324. newMap = JSON.parse(newMap);
  325. }
  326. delete newMap.file;
  327. const {
  328. sourceRoot
  329. } = newMap;
  330. delete newMap.sourceRoot;
  331. if (newMap.sources) {
  332. newMap.sources = newMap.sources.map(source => {
  333. const sourceType = getURLType(source);
  334. // Do no touch `scheme-relative` and `absolute` URLs
  335. if (sourceType === "path-relative" || sourceType === "path-absolute") {
  336. const absoluteSource = sourceType === "path-relative" && sourceRoot ? _path.default.resolve(sourceRoot, _path.default.normalize(source)) : _path.default.normalize(source);
  337. return _path.default.relative(resourceContext, absoluteSource);
  338. }
  339. return source;
  340. });
  341. }
  342. return newMap;
  343. }
  344. function normalizeSourceMapAfterPostcss(map, resourceContext) {
  345. const newMap = map;
  346. // result.map.file is an optional property that provides the output filename.
  347. // Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
  348. // eslint-disable-next-line no-param-reassign
  349. delete newMap.file;
  350. // eslint-disable-next-line no-param-reassign
  351. newMap.sourceRoot = "";
  352. // eslint-disable-next-line no-param-reassign
  353. newMap.sources = newMap.sources.map(source => {
  354. if (source.indexOf("<") === 0) {
  355. return source;
  356. }
  357. const sourceType = getURLType(source);
  358. // Do no touch `scheme-relative`, `path-absolute` and `absolute` types
  359. if (sourceType === "path-relative") {
  360. return _path.default.resolve(resourceContext, source);
  361. }
  362. return source;
  363. });
  364. return newMap;
  365. }
  366. function findPackageJSONDir(cwd, statSync) {
  367. let dir = cwd;
  368. for (;;) {
  369. try {
  370. if (statSync(_path.default.join(dir, "package.json")).isFile()) {
  371. break;
  372. }
  373. } catch (error) {
  374. // Nothing
  375. }
  376. const parent = _path.default.dirname(dir);
  377. if (dir === parent) {
  378. dir = null;
  379. break;
  380. }
  381. dir = parent;
  382. }
  383. return dir;
  384. }
  385. function getPostcssImplementation(loaderContext, implementation) {
  386. let resolvedImplementation = implementation;
  387. if (!implementation || typeof implementation === "string") {
  388. const postcssImplPkg = implementation || "postcss";
  389. try {
  390. // eslint-disable-next-line import/no-dynamic-require, global-require
  391. resolvedImplementation = require(postcssImplPkg);
  392. } catch (error) {
  393. loaderContext.emitError(error);
  394. // eslint-disable-next-line consistent-return
  395. return;
  396. }
  397. }
  398. // eslint-disable-next-line consistent-return
  399. return resolvedImplementation;
  400. }
  401. function reportError(loaderContext, callback, error) {
  402. if (error.file) {
  403. loaderContext.addDependency(error.file);
  404. }
  405. if (error.name === "CssSyntaxError") {
  406. callback(new _Error.default(error));
  407. } else {
  408. callback(error);
  409. }
  410. }