plugin.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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. let checkBroadPattern = (0, _content.createBroadPatternCheck)(this.contentPatterns.all);
  170. for (let file of files){
  171. checkBroadPattern(file);
  172. content.push({
  173. content: _fs.default.readFileSync(_path.default.resolve(file), "utf8"),
  174. extension: _path.default.extname(file).slice(1)
  175. });
  176. }
  177. // Resolve raw content in the tailwind config
  178. let rawContent = this.config.content.files.filter((file)=>{
  179. return file !== null && typeof file === "object";
  180. });
  181. for (let { raw: htmlContent , extension ="html" } of rawContent){
  182. content.push({
  183. content: htmlContent,
  184. extension
  185. });
  186. }
  187. return content;
  188. },
  189. getContext ({ createContext , cliConfigPath , root , result , content }) {
  190. _sharedState.env.DEBUG && console.time("Searching for config");
  191. var _findAtConfigPath1;
  192. let configPath = (_findAtConfigPath1 = (0, _findAtConfigPath.findAtConfigPath)(root, result)) !== null && _findAtConfigPath1 !== void 0 ? _findAtConfigPath1 : cliConfigPath;
  193. _sharedState.env.DEBUG && console.timeEnd("Searching for config");
  194. if (this.context) {
  195. this.context.changedContent = this.changedContent.splice(0);
  196. return this.context;
  197. }
  198. _sharedState.env.DEBUG && console.time("Loading config");
  199. let config = this.loadConfig(configPath, content);
  200. _sharedState.env.DEBUG && console.timeEnd("Loading config");
  201. _sharedState.env.DEBUG && console.time("Creating context");
  202. this.context = createContext(config, []);
  203. Object.assign(this.context, {
  204. userConfigPath: configPath
  205. });
  206. _sharedState.env.DEBUG && console.timeEnd("Creating context");
  207. _sharedState.env.DEBUG && console.time("Resolving content paths");
  208. this.refreshContentPaths();
  209. _sharedState.env.DEBUG && console.timeEnd("Resolving content paths");
  210. if (this.watcher) {
  211. _sharedState.env.DEBUG && console.time("Watch new files");
  212. this.watcher.refreshWatchedFiles();
  213. _sharedState.env.DEBUG && console.timeEnd("Watch new files");
  214. }
  215. for (let file of this.readContentPaths()){
  216. this.context.changedContent.push(file);
  217. }
  218. return this.context;
  219. }
  220. };
  221. async function createProcessor(args, cliConfigPath) {
  222. var _args_content;
  223. let postcss = (0, _deps.loadPostcss)();
  224. let input = args["--input"];
  225. let output = args["--output"];
  226. let includePostCss = args["--postcss"];
  227. let customPostCssPath = typeof args["--postcss"] === "string" ? args["--postcss"] : undefined;
  228. let [beforePlugins, afterPlugins, postcssOptions] = includePostCss ? await loadPostCssPlugins(customPostCssPath) : loadBuiltinPostcssPlugins();
  229. if (args["--purge"]) {
  230. _log.default.warn("purge-flag-deprecated", [
  231. "The `--purge` flag has been deprecated.",
  232. "Please use `--content` instead."
  233. ]);
  234. if (!args["--content"]) {
  235. args["--content"] = args["--purge"];
  236. }
  237. }
  238. var _args_content_split;
  239. 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 : [];
  240. let tailwindPlugin = ()=>{
  241. return {
  242. postcssPlugin: "tailwindcss",
  243. async Once (root, { result }) {
  244. _sharedState.env.DEBUG && console.time("Compiling CSS");
  245. await (0, _processTailwindFeatures.default)(({ createContext })=>{
  246. console.error();
  247. console.error("Rebuilding...");
  248. return ()=>{
  249. return state.getContext({
  250. createContext,
  251. cliConfigPath,
  252. root,
  253. result,
  254. content
  255. });
  256. };
  257. })(root, result);
  258. _sharedState.env.DEBUG && console.timeEnd("Compiling CSS");
  259. }
  260. };
  261. };
  262. tailwindPlugin.postcss = true;
  263. let plugins = [
  264. ...beforePlugins,
  265. tailwindPlugin,
  266. !args["--minify"] && _utils.formatNodes,
  267. ...afterPlugins,
  268. !args["--no-autoprefixer"] && (0, _deps.loadAutoprefixer)(),
  269. args["--minify"] && (0, _deps.loadCssNano)()
  270. ].filter(Boolean);
  271. /** @type {import('postcss').Processor} */ // @ts-ignore
  272. let processor = postcss(plugins);
  273. async function readInput() {
  274. // Piping in data, let's drain the stdin
  275. if (input === "-") {
  276. return (0, _utils.drainStdin)();
  277. }
  278. // Input file has been provided
  279. if (input) {
  280. return _fs.default.promises.readFile(_path.default.resolve(input), "utf8");
  281. }
  282. // No input file provided, fallback to default at-rules
  283. return "@tailwind base; @tailwind components; @tailwind utilities";
  284. }
  285. async function build() {
  286. let start = process.hrtime.bigint();
  287. return readInput().then((css)=>processor.process(css, {
  288. ...postcssOptions,
  289. from: input,
  290. to: output
  291. })).then((result)=>{
  292. if (!state.watcher) {
  293. return result;
  294. }
  295. _sharedState.env.DEBUG && console.time("Recording PostCSS dependencies");
  296. for (let message of result.messages){
  297. if (message.type === "dependency") {
  298. state.contextDependencies.add(message.file);
  299. }
  300. }
  301. _sharedState.env.DEBUG && console.timeEnd("Recording PostCSS dependencies");
  302. // TODO: This needs to be in a different spot
  303. _sharedState.env.DEBUG && console.time("Watch new files");
  304. state.watcher.refreshWatchedFiles();
  305. _sharedState.env.DEBUG && console.timeEnd("Watch new files");
  306. return result;
  307. }).then((result)=>{
  308. if (!output) {
  309. process.stdout.write(result.css);
  310. return;
  311. }
  312. return Promise.all([
  313. (0, _utils.outputFile)(result.opts.to, result.css),
  314. result.map && (0, _utils.outputFile)(result.opts.to + ".map", result.map.toString())
  315. ]);
  316. }).then(()=>{
  317. let end = process.hrtime.bigint();
  318. console.error();
  319. console.error("Done in", (end - start) / BigInt(1e6) + "ms.");
  320. }).then(()=>{}, (err)=>{
  321. // TODO: If an initial build fails we can't easily pick up any PostCSS dependencies
  322. // that were collected before the error occurred
  323. // The result is not stored on the error so we have to store it externally
  324. // and pull the messages off of it here somehow
  325. // This results in a less than ideal DX because the watcher will not pick up
  326. // changes to imported CSS if one of them caused an error during the initial build
  327. // If you fix it and then save the main CSS file so there's no error
  328. // The watcher will start watching the imported CSS files and will be
  329. // resilient to future errors.
  330. if (state.watcher) {
  331. console.error(err);
  332. } else {
  333. return Promise.reject(err);
  334. }
  335. });
  336. }
  337. /**
  338. * @param {{file: string, content(): Promise<string>, extension: string}[]} changes
  339. */ async function parseChanges(changes) {
  340. return Promise.all(changes.map(async (change)=>({
  341. content: await change.content(),
  342. extension: change.extension
  343. })));
  344. }
  345. if (input !== undefined && input !== "-") {
  346. state.contextDependencies.add(_path.default.resolve(input));
  347. }
  348. return {
  349. build,
  350. watch: async ()=>{
  351. state.watcher = (0, _watching.createWatcher)(args, {
  352. state,
  353. /**
  354. * @param {{file: string, content(): Promise<string>, extension: string}[]} changes
  355. */ async rebuild (changes) {
  356. let needsNewContext = changes.some((change)=>{
  357. var _state_configBag;
  358. return ((_state_configBag = state.configBag) === null || _state_configBag === void 0 ? void 0 : _state_configBag.dependencies.has(change.file)) || state.contextDependencies.has(change.file);
  359. });
  360. if (needsNewContext) {
  361. state.context = null;
  362. } else {
  363. for (let change of (await parseChanges(changes))){
  364. state.changedContent.push(change);
  365. }
  366. }
  367. return build();
  368. }
  369. });
  370. await build();
  371. }
  372. };
  373. }