utils.js 9.6 KB

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