plugin.js 16 KB

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