utils.js 15 KB

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