Nav apraksta

plugin.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. // @ts-check
  2. "use strict";
  3. Object.defineProperty(exports, "__esModule", {
  4. value: true
  5. });
  6. Object.defineProperty(exports, "createProcessor", {
  7. enumerable: true,
  8. get: function() {
  9. return createProcessor;
  10. }
  11. });
  12. const _path = /*#__PURE__*/ _interop_require_default(require("path"));
  13. const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
  14. const _postcssloadconfig = /*#__PURE__*/ _interop_require_default(require("postcss-load-config"));
  15. const _lilconfig = require("lilconfig");
  16. const _plugins = /*#__PURE__*/ _interop_require_default(require("postcss-load-config/src/plugins" // Little bit scary, looking at private/internal API
  17. ));
  18. const _options = /*#__PURE__*/ _interop_require_default(require("postcss-load-config/src/options" // Little bit scary, looking at private/internal API
  19. ));
  20. const _processTailwindFeatures = /*#__PURE__*/ _interop_require_default(require("../../processTailwindFeatures"));
  21. const _deps = require("./deps");
  22. const _utils = require("./utils");
  23. const _sharedState = require("../../lib/sharedState");
  24. const _resolveConfig = /*#__PURE__*/ _interop_require_default(require("../../../resolveConfig.js"));
  25. const _content = require("../../lib/content.js");
  26. const _watching = require("./watching.js");
  27. const _fastglob = /*#__PURE__*/ _interop_require_default(require("fast-glob"));
  28. const _findAtConfigPath = require("../../lib/findAtConfigPath.js");
  29. const _log = /*#__PURE__*/ _interop_require_default(require("../../util/log"));
  30. const _loadconfig = require("../../lib/load-config");
  31. const _getModuleDependencies = /*#__PURE__*/ _interop_require_default(require("../../lib/getModuleDependencies"));
  32. function _interop_require_default(obj) {
  33. return obj && obj.__esModule ? obj : {
  34. default: obj
  35. };
  36. }
  37. /**
  38. *
  39. * @param {string} [customPostCssPath ]
  40. * @returns
  41. */ async function loadPostCssPlugins(customPostCssPath) {
  42. let config = customPostCssPath ? await (async ()=>{
  43. let file = _path.default.resolve(customPostCssPath);
  44. // Implementation, see: https://unpkg.com/browse/postcss-load-config@3.1.0/src/index.js
  45. // @ts-ignore
  46. let { config ={} } = await (0, _lilconfig.lilconfig)("postcss").load(file);
  47. if (typeof config === "function") {
  48. config = config();
  49. } else {
  50. config = Object.assign({}, config);
  51. }
  52. if (!config.plugins) {
  53. config.plugins = [];
  54. }
  55. return {
  56. file,
  57. plugins: (0, _plugins.default)(config, file),
  58. options: (0, _options.default)(config, file)
  59. };
  60. })() : await (0, _postcssloadconfig.default)();
  61. let configPlugins = config.plugins;
  62. let configPluginTailwindIdx = configPlugins.findIndex((plugin)=>{
  63. if (typeof plugin === "function" && plugin.name === "tailwindcss") {
  64. return true;
  65. }
  66. if (typeof plugin === "object" && plugin !== null && plugin.postcssPlugin === "tailwindcss") {
  67. return true;
  68. }
  69. return false;
  70. });
  71. let beforePlugins = configPluginTailwindIdx === -1 ? [] : configPlugins.slice(0, configPluginTailwindIdx);
  72. let afterPlugins = configPluginTailwindIdx === -1 ? configPlugins : configPlugins.slice(configPluginTailwindIdx + 1);
  73. return [
  74. beforePlugins,
  75. afterPlugins,
  76. config.options
  77. ];
  78. }
  79. function loadBuiltinPostcssPlugins() {
  80. let postcss = (0, _deps.loadPostcss)();
  81. let IMPORT_COMMENT = "__TAILWIND_RESTORE_IMPORT__: ";
  82. return [
  83. [
  84. (root)=>{
  85. root.walkAtRules("import", (rule)=>{
  86. if (rule.params.slice(1).startsWith("tailwindcss/")) {
  87. rule.after(postcss.comment({
  88. text: IMPORT_COMMENT + rule.params
  89. }));
  90. rule.remove();
  91. }
  92. });
  93. },
  94. (0, _deps.loadPostcssImport)(),
  95. (root)=>{
  96. root.walkComments((rule)=>{
  97. if (rule.text.startsWith(IMPORT_COMMENT)) {
  98. rule.after(postcss.atRule({
  99. name: "import",
  100. params: rule.text.replace(IMPORT_COMMENT, "")
  101. }));
  102. rule.remove();
  103. }
  104. });
  105. }
  106. ],
  107. [],
  108. {}
  109. ];
  110. }
  111. let state = {
  112. /** @type {any} */ context: null,
  113. /** @type {ReturnType<typeof createWatcher> | null} */ watcher: null,
  114. /** @type {{content: string, extension: string}[]} */ changedContent: [],
  115. /** @type {ReturnType<typeof load> | null} */ configBag: null,
  116. contextDependencies: new Set(),
  117. /** @type {import('../../lib/content.js').ContentPath[]} */ contentPaths: [],
  118. refreshContentPaths () {
  119. var _this_context;
  120. this.contentPaths = (0, _content.parseCandidateFiles)(this.context, (_this_context = this.context) === null || _this_context === void 0 ? void 0 : _this_context.tailwindConfig);
  121. },
  122. get config () {
  123. return this.context.tailwindConfig;
  124. },
  125. get contentPatterns () {
  126. return {
  127. all: this.contentPaths.map((contentPath)=>contentPath.pattern),
  128. dynamic: this.contentPaths.filter((contentPath)=>contentPath.glob !== undefined).map((contentPath)=>contentPath.pattern)
  129. };
  130. },
  131. loadConfig (configPath, content) {
  132. if (this.watcher && configPath) {
  133. this.refreshConfigDependencies();
  134. }
  135. let config = (0, _loadconfig.loadConfig)(configPath);
  136. let dependencies = (0, _getModuleDependencies.default)(configPath);
  137. this.configBag = {
  138. config,
  139. dependencies,
  140. dispose () {
  141. for (let file of dependencies){
  142. delete require.cache[require.resolve(file)];
  143. }
  144. }
  145. };
  146. // @ts-ignore
  147. this.configBag.config = (0, _resolveConfig.default)(this.configBag.config, {
  148. content: {
  149. files: []
  150. }
  151. });
  152. // Override content files if `--content` has been passed explicitly
  153. if ((content === null || content === void 0 ? void 0 : content.length) > 0) {
  154. this.configBag.config.content.files = content;
  155. }
  156. return this.configBag.config;
  157. },
  158. refreshConfigDependencies () {
  159. var _this_configBag;
  160. _sharedState.env.DEBUG && console.time("Module dependencies");
  161. (_this_configBag = this.configBag) === null || _this_configBag === void 0 ? void 0 : _this_configBag.dispose();
  162. _sharedState.env.DEBUG && console.timeEnd("Module dependencies");
  163. },
  164. readContentPaths () {
  165. let content = [];
  166. // Resolve globs from the content config
  167. // TODO: When we make the postcss plugin async-capable this can become async
  168. let files = _fastglob.default.sync(this.contentPatterns.all);
  169. for (let file of files){
  170. if (false) {
  171. content.push({
  172. file,
  173. extension: _path.default.extname(file).slice(1)
  174. });
  175. } else {
  176. content.push({
  177. content: _fs.default.readFileSync(_path.default.resolve(file), "utf8"),
  178. extension: _path.default.extname(file).slice(1)
  179. });
  180. }
  181. }
  182. // Resolve raw content in the tailwind config
  183. let rawContent = this.config.content.files.filter((file)=>{
  184. return file !== null && typeof file === "object";
  185. });
  186. for (let { raw: htmlContent , extension ="html" } of rawContent){
  187. content.push({
  188. content: htmlContent,
  189. extension
  190. });
  191. }
  192. return content;
  193. },
  194. getContext ({ createContext , cliConfigPath , root , result , content }) {
  195. if (this.context) {
  196. this.context.changedContent = this.changedContent.splice(0);
  197. return this.context;
  198. }
  199. _sharedState.env.DEBUG && console.time("Searching for config");
  200. var _findAtConfigPath1;
  201. let configPath = (_findAtConfigPath1 = (0, _findAtConfigPath.findAtConfigPath)(root, result)) !== null && _findAtConfigPath1 !== void 0 ? _findAtConfigPath1 : cliConfigPath;
  202. _sharedState.env.DEBUG && console.timeEnd("Searching for config");
  203. _sharedState.env.DEBUG && console.time("Loading config");
  204. let config = this.loadConfig(configPath, content);
  205. _sharedState.env.DEBUG && console.timeEnd("Loading config");
  206. _sharedState.env.DEBUG && console.time("Creating context");
  207. this.context = createContext(config, []);
  208. Object.assign(this.context, {
  209. userConfigPath: configPath
  210. });
  211. _sharedState.env.DEBUG && console.timeEnd("Creating context");
  212. _sharedState.env.DEBUG && console.time("Resolving content paths");
  213. this.refreshContentPaths();
  214. _sharedState.env.DEBUG && console.timeEnd("Resolving content paths");
  215. if (this.watcher) {
  216. _sharedState.env.DEBUG && console.time("Watch new files");
  217. this.watcher.refreshWatchedFiles();
  218. _sharedState.env.DEBUG && console.timeEnd("Watch new files");
  219. }
  220. for (let file of this.readContentPaths()){
  221. this.context.changedContent.push(file);
  222. }
  223. return this.context;
  224. }
  225. };
  226. async function createProcessor(args, cliConfigPath) {
  227. var _args_content;
  228. let postcss = (0, _deps.loadPostcss)();
  229. let input = args["--input"];
  230. let output = args["--output"];
  231. let includePostCss = args["--postcss"];
  232. let customPostCssPath = typeof args["--postcss"] === "string" ? args["--postcss"] : undefined;
  233. let [beforePlugins, afterPlugins, postcssOptions] = includePostCss ? await loadPostCssPlugins(customPostCssPath) : loadBuiltinPostcssPlugins();
  234. if (args["--purge"]) {
  235. _log.default.warn("purge-flag-deprecated", [
  236. "The `--purge` flag has been deprecated.",
  237. "Please use `--content` instead."
  238. ]);
  239. if (!args["--content"]) {
  240. args["--content"] = args["--purge"];
  241. }
  242. }
  243. var _args_content_split;
  244. let content = (_args_content_split = (_args_content = args["--content"]) === null || _args_content === void 0 ? void 0 : _args_content.split(/(?<!{[^}]+),/)) !== null && _args_content_split !== void 0 ? _args_content_split : [];
  245. let tailwindPlugin = ()=>{
  246. return {
  247. postcssPlugin: "tailwindcss",
  248. Once (root, { result }) {
  249. _sharedState.env.DEBUG && console.time("Compiling CSS");
  250. (0, _processTailwindFeatures.default)(({ createContext })=>{
  251. console.error();
  252. console.error("Rebuilding...");
  253. return ()=>{
  254. return state.getContext({
  255. createContext,
  256. cliConfigPath,
  257. root,
  258. result,
  259. content
  260. });
  261. };
  262. })(root, result);
  263. _sharedState.env.DEBUG && console.timeEnd("Compiling CSS");
  264. }
  265. };
  266. };
  267. tailwindPlugin.postcss = true;
  268. let plugins = [
  269. ...beforePlugins,
  270. tailwindPlugin,
  271. !args["--minify"] && _utils.formatNodes,
  272. ...afterPlugins,
  273. !args["--no-autoprefixer"] && (0, _deps.loadAutoprefixer)(),
  274. args["--minify"] && (0, _deps.loadCssNano)()
  275. ].filter(Boolean);
  276. /** @type {import('postcss').Processor} */ // @ts-ignore
  277. let processor = postcss(plugins);
  278. async function readInput() {
  279. // Piping in data, let's drain the stdin
  280. if (input === "-") {
  281. return (0, _utils.drainStdin)();
  282. }
  283. // Input file has been provided
  284. if (input) {
  285. return _fs.default.promises.readFile(_path.default.resolve(input), "utf8");
  286. }
  287. // No input file provided, fallback to default atrules
  288. return "@tailwind base; @tailwind components; @tailwind utilities";
  289. }
  290. async function build() {
  291. let start = process.hrtime.bigint();
  292. return readInput().then((css)=>processor.process(css, {
  293. ...postcssOptions,
  294. from: input,
  295. to: output
  296. })).then((result)=>{
  297. if (!state.watcher) {
  298. return result;
  299. }
  300. _sharedState.env.DEBUG && console.time("Recording PostCSS dependencies");
  301. for (let message of result.messages){
  302. if (message.type === "dependency") {
  303. state.contextDependencies.add(message.file);
  304. }
  305. }
  306. _sharedState.env.DEBUG && console.timeEnd("Recording PostCSS dependencies");
  307. // TODO: This needs to be in a different spot
  308. _sharedState.env.DEBUG && console.time("Watch new files");
  309. state.watcher.refreshWatchedFiles();
  310. _sharedState.env.DEBUG && console.timeEnd("Watch new files");
  311. return result;
  312. }).then((result)=>{
  313. if (!output) {
  314. process.stdout.write(result.css);
  315. return;
  316. }
  317. return Promise.all([
  318. (0, _utils.outputFile)(result.opts.to, result.css),
  319. result.map && (0, _utils.outputFile)(result.opts.to + ".map", result.map.toString())
  320. ]);
  321. }).then(()=>{
  322. let end = process.hrtime.bigint();
  323. console.error();
  324. console.error("Done in", (end - start) / BigInt(1e6) + "ms.");
  325. }).then(()=>{}, (err)=>{
  326. // TODO: If an initial build fails we can't easily pick up any PostCSS dependencies
  327. // that were collected before the error occurred
  328. // The result is not stored on the error so we have to store it externally
  329. // and pull the messages off of it here somehow
  330. // This results in a less than ideal DX because the watcher will not pick up
  331. // changes to imported CSS if one of them caused an error during the initial build
  332. // If you fix it and then save the main CSS file so there's no error
  333. // The watcher will start watching the imported CSS files and will be
  334. // resilient to future errors.
  335. if (state.watcher) {
  336. console.error(err);
  337. } else {
  338. return Promise.reject(err);
  339. }
  340. });
  341. }
  342. /**
  343. * @param {{file: string, content(): Promise<string>, extension: string}[]} changes
  344. */ async function parseChanges(changes) {
  345. return Promise.all(changes.map(async (change)=>({
  346. content: await change.content(),
  347. extension: change.extension
  348. })));
  349. }
  350. if (input !== undefined && input !== "-") {
  351. state.contextDependencies.add(_path.default.resolve(input));
  352. }
  353. return {
  354. build,
  355. watch: async ()=>{
  356. state.watcher = (0, _watching.createWatcher)(args, {
  357. state,
  358. /**
  359. * @param {{file: string, content(): Promise<string>, extension: string}[]} changes
  360. */ async rebuild (changes) {
  361. let needsNewContext = changes.some((change)=>{
  362. var _state_configBag;
  363. return ((_state_configBag = state.configBag) === null || _state_configBag === void 0 ? void 0 : _state_configBag.dependencies.has(change.file)) || state.contextDependencies.has(change.file);
  364. });
  365. if (needsNewContext) {
  366. state.context = null;
  367. } else {
  368. for (let change of (await parseChanges(changes))){
  369. state.changedContent.push(change);
  370. }
  371. }
  372. return build();
  373. }
  374. });
  375. await build();
  376. }
  377. };
  378. }