plugin.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. Object.defineProperty(exports, "createProcessor", {
  6. enumerable: true,
  7. get: ()=>createProcessor
  8. });
  9. const _path = /*#__PURE__*/ _interopRequireDefault(require("path"));
  10. const _fs = /*#__PURE__*/ _interopRequireDefault(require("fs"));
  11. const _postcssLoadConfig = /*#__PURE__*/ _interopRequireDefault(require("postcss-load-config"));
  12. const _lilconfig = require("lilconfig");
  13. const _plugins = /*#__PURE__*/ _interopRequireDefault(require("postcss-load-config/src/plugins" // Little bit scary, looking at private/internal API
  14. ));
  15. const _options = /*#__PURE__*/ _interopRequireDefault(require("postcss-load-config/src/options" // Little bit scary, looking at private/internal API
  16. ));
  17. const _processTailwindFeatures = /*#__PURE__*/ _interopRequireDefault(require("../../../processTailwindFeatures"));
  18. const _deps = require("./deps");
  19. const _utils = require("./utils");
  20. const _sharedState = require("../../../lib/sharedState");
  21. const _resolveConfig = /*#__PURE__*/ _interopRequireDefault(require("../../../../resolveConfig"));
  22. const _content = require("../../../lib/content");
  23. const _watching = require("./watching");
  24. const _fastGlob = /*#__PURE__*/ _interopRequireDefault(require("fast-glob"));
  25. const _findAtConfigPath = require("../../../lib/findAtConfigPath");
  26. const _log = /*#__PURE__*/ _interopRequireDefault(require("../../../util/log"));
  27. const _loadConfig = require("../../../lib/load-config");
  28. const _getModuleDependencies = /*#__PURE__*/ _interopRequireDefault(require("../../../lib/getModuleDependencies"));
  29. function _interopRequireDefault(obj) {
  30. return obj && obj.__esModule ? obj : {
  31. default: obj
  32. };
  33. }
  34. /**
  35. *
  36. * @param {string} [customPostCssPath ]
  37. * @returns
  38. */ async function loadPostCssPlugins(customPostCssPath) {
  39. let config = customPostCssPath ? await (async ()=>{
  40. let file = _path.default.resolve(customPostCssPath);
  41. // Implementation, see: https://unpkg.com/browse/postcss-load-config@3.1.0/src/index.js
  42. // @ts-ignore
  43. let { config ={} } = await (0, _lilconfig.lilconfig)("postcss").load(file);
  44. if (typeof config === "function") {
  45. config = config();
  46. } else {
  47. config = Object.assign({}, config);
  48. }
  49. if (!config.plugins) {
  50. config.plugins = [];
  51. }
  52. return {
  53. file,
  54. plugins: (0, _plugins.default)(config, file),
  55. options: (0, _options.default)(config, file)
  56. };
  57. })() : await (0, _postcssLoadConfig.default)();
  58. let configPlugins = config.plugins;
  59. let configPluginTailwindIdx = configPlugins.findIndex((plugin)=>{
  60. if (typeof plugin === "function" && plugin.name === "tailwindcss") {
  61. return true;
  62. }
  63. if (typeof plugin === "object" && plugin !== null && plugin.postcssPlugin === "tailwindcss") {
  64. return true;
  65. }
  66. return false;
  67. });
  68. let beforePlugins = configPluginTailwindIdx === -1 ? [] : configPlugins.slice(0, configPluginTailwindIdx);
  69. let afterPlugins = configPluginTailwindIdx === -1 ? configPlugins : configPlugins.slice(configPluginTailwindIdx + 1);
  70. return [
  71. beforePlugins,
  72. afterPlugins,
  73. config.options
  74. ];
  75. }
  76. function loadBuiltinPostcssPlugins() {
  77. let postcss = (0, _deps.loadPostcss)();
  78. let IMPORT_COMMENT = "__TAILWIND_RESTORE_IMPORT__: ";
  79. return [
  80. [
  81. (root)=>{
  82. root.walkAtRules("import", (rule)=>{
  83. if (rule.params.slice(1).startsWith("tailwindcss/")) {
  84. rule.after(postcss.comment({
  85. text: IMPORT_COMMENT + rule.params
  86. }));
  87. rule.remove();
  88. }
  89. });
  90. },
  91. (0, _deps.loadPostcssImport)(),
  92. (root)=>{
  93. root.walkComments((rule)=>{
  94. if (rule.text.startsWith(IMPORT_COMMENT)) {
  95. rule.after(postcss.atRule({
  96. name: "import",
  97. params: rule.text.replace(IMPORT_COMMENT, "")
  98. }));
  99. rule.remove();
  100. }
  101. });
  102. }
  103. ],
  104. [],
  105. {}
  106. ];
  107. }
  108. let state = {
  109. /** @type {any} */ context: null,
  110. /** @type {ReturnType<typeof createWatcher> | null} */ watcher: null,
  111. /** @type {{content: string, extension: string}[]} */ changedContent: [],
  112. /** @type {{config: Config, dependencies: Set<string>, dispose: Function } | null} */ configBag: null,
  113. contextDependencies: new Set(),
  114. /** @type {import('../../lib/content.js').ContentPath[]} */ contentPaths: [],
  115. refreshContentPaths () {
  116. var _this_context;
  117. this.contentPaths = (0, _content.parseCandidateFiles)(this.context, (_this_context = this.context) === null || _this_context === void 0 ? void 0 : _this_context.tailwindConfig);
  118. },
  119. get config () {
  120. return this.context.tailwindConfig;
  121. },
  122. get contentPatterns () {
  123. return {
  124. all: this.contentPaths.map((contentPath)=>contentPath.pattern),
  125. dynamic: this.contentPaths.filter((contentPath)=>contentPath.glob !== undefined).map((contentPath)=>contentPath.pattern)
  126. };
  127. },
  128. loadConfig (configPath, content) {
  129. if (this.watcher && configPath) {
  130. this.refreshConfigDependencies();
  131. }
  132. let config = (0, _loadConfig.loadConfig)(configPath);
  133. let dependencies = (0, _getModuleDependencies.default)(configPath);
  134. this.configBag = {
  135. config,
  136. dependencies,
  137. dispose () {
  138. for (let file of dependencies){
  139. delete require.cache[require.resolve(file)];
  140. }
  141. }
  142. };
  143. // @ts-ignore
  144. this.configBag.config = (0, _resolveConfig.default)(this.configBag.config, {
  145. content: {
  146. files: []
  147. }
  148. });
  149. // Override content files if `--content` has been passed explicitly
  150. if ((content === null || content === void 0 ? void 0 : content.length) > 0) {
  151. this.configBag.config.content.files = content;
  152. }
  153. return this.configBag.config;
  154. },
  155. refreshConfigDependencies (configPath) {
  156. var _this_configBag;
  157. _sharedState.env.DEBUG && console.time("Module dependencies");
  158. (_this_configBag = this.configBag) === null || _this_configBag === void 0 ? void 0 : _this_configBag.dispose();
  159. _sharedState.env.DEBUG && console.timeEnd("Module dependencies");
  160. },
  161. readContentPaths () {
  162. let content = [];
  163. // Resolve globs from the content config
  164. // TODO: When we make the postcss plugin async-capable this can become async
  165. let files = _fastGlob.default.sync(this.contentPatterns.all);
  166. for (let file of files){
  167. if (_sharedState.env.OXIDE) {
  168. content.push({
  169. file,
  170. extension: _path.default.extname(file).slice(1)
  171. });
  172. } else {
  173. content.push({
  174. content: _fs.default.readFileSync(_path.default.resolve(file), "utf8"),
  175. extension: _path.default.extname(file).slice(1)
  176. });
  177. }
  178. }
  179. // Resolve raw content in the tailwind config
  180. let rawContent = this.config.content.files.filter((file)=>{
  181. return file !== null && typeof file === "object";
  182. });
  183. for (let { raw: htmlContent , extension ="html" } of rawContent){
  184. content.push({
  185. content: htmlContent,
  186. extension
  187. });
  188. }
  189. return content;
  190. },
  191. getContext ({ createContext , cliConfigPath , root , result , content }) {
  192. if (this.context) {
  193. this.context.changedContent = this.changedContent.splice(0);
  194. return this.context;
  195. }
  196. _sharedState.env.DEBUG && console.time("Searching for config");
  197. var _findAtConfigPath1;
  198. let configPath = (_findAtConfigPath1 = (0, _findAtConfigPath.findAtConfigPath)(root, result)) !== null && _findAtConfigPath1 !== void 0 ? _findAtConfigPath1 : cliConfigPath;
  199. _sharedState.env.DEBUG && console.timeEnd("Searching for config");
  200. _sharedState.env.DEBUG && console.time("Loading config");
  201. let config = this.loadConfig(configPath, content);
  202. _sharedState.env.DEBUG && console.timeEnd("Loading config");
  203. _sharedState.env.DEBUG && console.time("Creating context");
  204. this.context = createContext(config, []);
  205. Object.assign(this.context, {
  206. userConfigPath: configPath
  207. });
  208. _sharedState.env.DEBUG && console.timeEnd("Creating context");
  209. _sharedState.env.DEBUG && console.time("Resolving content paths");
  210. this.refreshContentPaths();
  211. _sharedState.env.DEBUG && console.timeEnd("Resolving content paths");
  212. if (this.watcher) {
  213. _sharedState.env.DEBUG && console.time("Watch new files");
  214. this.watcher.refreshWatchedFiles();
  215. _sharedState.env.DEBUG && console.timeEnd("Watch new files");
  216. }
  217. for (let file of this.readContentPaths()){
  218. this.context.changedContent.push(file);
  219. }
  220. return this.context;
  221. }
  222. };
  223. async function createProcessor(args, cliConfigPath) {
  224. var _args_content;
  225. let postcss = (0, _deps.loadPostcss)();
  226. let input = args["--input"];
  227. let output = args["--output"];
  228. let includePostCss = args["--postcss"];
  229. let customPostCssPath = typeof args["--postcss"] === "string" ? args["--postcss"] : undefined;
  230. let [beforePlugins, afterPlugins, postcssOptions] = includePostCss ? await loadPostCssPlugins(customPostCssPath) : loadBuiltinPostcssPlugins();
  231. if (args["--purge"]) {
  232. _log.default.warn("purge-flag-deprecated", [
  233. "The `--purge` flag has been deprecated.",
  234. "Please use `--content` instead."
  235. ]);
  236. if (!args["--content"]) {
  237. args["--content"] = args["--purge"];
  238. }
  239. }
  240. var _args_content_split;
  241. 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 : [];
  242. let tailwindPlugin = ()=>{
  243. return {
  244. postcssPlugin: "tailwindcss",
  245. Once (root, { result }) {
  246. _sharedState.env.DEBUG && console.time("Compiling CSS");
  247. (0, _processTailwindFeatures.default)(({ createContext })=>{
  248. console.error();
  249. console.error("Rebuilding...");
  250. return ()=>{
  251. return state.getContext({
  252. createContext,
  253. cliConfigPath,
  254. root,
  255. result,
  256. content
  257. });
  258. };
  259. })(root, result);
  260. _sharedState.env.DEBUG && console.timeEnd("Compiling CSS");
  261. }
  262. };
  263. };
  264. tailwindPlugin.postcss = true;
  265. let plugins = [
  266. ...beforePlugins,
  267. tailwindPlugin,
  268. !args["--minify"] && _utils.formatNodes,
  269. ...afterPlugins
  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 atrules
  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)=>(0, _deps.lightningcss)(!!args["--minify"], result)).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. }