utils.js 11 KB

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