├── LICENSE ├── README.md ├── __info.js ├── __run.js └── stuff.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jacob Babich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🔺 Add PostCSS to Svelte

2 | 3 | [![GitHub issues by-label](https://img.shields.io/github/issues/svelte-add/svelte-add/confirmed%20bug?color=%23DC2626)](https://github.com/svelte-add/svelte-add/issues?q=is%3Aopen+is%3Aissue+label%3A%22confirmed+bug%22) 4 | [![GitHub issues by-label](https://img.shields.io/github/issues/svelte-add/svelte-add/support%20question?color=%23FACC15)](https://github.com/svelte-add/svelte-add/issues?q=is%3Aopen+is%3Aissue+label%3A%22support+question%22) 5 | 6 | This is an adder for `svelte-add`; you should [read its `README`](https://github.com/svelte-add/svelte-add#readme) before continuing here. 7 | 8 | ## ➕ Adding PostCSS 9 | 10 | This adder's codename is `postcss`, and can be used like so: 11 | 12 | ```sh 13 | npx svelte-add@latest postcss 14 | ``` 15 | 16 | ### 🏞 Supported environments 17 | 18 | This adder supports SvelteKit and Vite-powered Svelte apps (all the environments `svelte-add` currently supports). 19 | 20 | ### ⚙️ Options 21 | 22 | - `autoprefixer` (default `true`): whether or not to install and set up [Autoprefixer](https://github.com/postcss/autoprefixer). 23 | 24 | ```sh 25 | npx svelte-add@latest postcss --postcss-autoprefixer 26 | ``` 27 | 28 | ## 🛠 Using PostCSS 29 | 30 | After the adder runs, 31 | 32 | - You can write PostCSS syntax in the `style lang="postcss"` blocks in Svelte files. 33 | 34 | - You can write PostCSS syntax in the `src/app.pcss` file. 35 | 36 | This is your global stylesheet because it will be active on every page of your site. 37 | 38 | - You can install more [PostCSS plugins](https://github.com/postcss/postcss/blob/main/docs/plugins.md) and configure them in the `postcss.config.cjs` file. 39 | -------------------------------------------------------------------------------- /__info.js: -------------------------------------------------------------------------------- 1 | import { extension } from "./stuff.js"; 2 | 3 | export const name = "PostCSS"; 4 | 5 | export const emoji = "🔺"; 6 | 7 | export const usageMarkdown = ['You can write PostCSS syntax in the `style lang="postcss"` blocks in Svelte files.', "You can write PostCSS syntax in the `src/app.pcss` file.\n\n This is your global stylesheet because it will be active on every page of your site.", "You can install more [PostCSS plugins](https://github.com/postcss/postcss/blob/main/docs/plugins.md) and configure them in the `postcss.config.cjs` file."]; 8 | 9 | /** @type {import("../..").Gatekeep} */ 10 | export const gatekeep = async () => { 11 | return { able: true }; 12 | }; 13 | 14 | /** @typedef {{ autoprefixer: boolean }} Options */ 15 | 16 | /** @type {import("../..").AdderOptions} */ 17 | export const options = { 18 | autoprefixer: { 19 | context: "https://github.com/postcss/autoprefixer", 20 | default: true, 21 | descriptionMarkdown: "whether or not to install and set up [Autoprefixer](https://github.com/postcss/autoprefixer).", 22 | question: "Do you want to use Autoprefixer?", 23 | }, 24 | }; 25 | 26 | /** @type {import("../..").Heuristic[]} */ 27 | export const heuristics = [ 28 | { 29 | description: "`postcss` is installed", 30 | async detector({ folderInfo }) { 31 | return "postcss" in folderInfo.allDependencies; 32 | }, 33 | }, 34 | { 35 | description: "`postcss-load-config` is installed", 36 | async detector({ folderInfo }) { 37 | return "postcss-load-config" in folderInfo.allDependencies; 38 | }, 39 | }, 40 | { 41 | description: "`vitePreprocess` reads PostCSS config implicitly in `svelte.config.js`", 42 | async detector({ readFile }) { 43 | const js = await readFile({ path: "/svelte.config.js" }); 44 | const cjs = await readFile({ path: "/svelte.config.cjs" }); 45 | 46 | /** @param {string} text */ 47 | const preprocessIsProbablySetup = (text) => { 48 | if (!text.includes("vitePreprocess")) return false; 49 | 50 | return true; 51 | }; 52 | 53 | if (js.exists) return preprocessIsProbablySetup(js.text); 54 | else if (cjs.exists) return preprocessIsProbablySetup(cjs.text); 55 | 56 | return false; 57 | }, 58 | }, 59 | { 60 | description: "`postcss.config.cjs` exists and `postcss.config.js` does not exist", 61 | async detector({ readFile }) { 62 | const cjs = await readFile({ path: "/postcss.config.cjs" }); 63 | const js = await readFile({ path: "/postcss.config.js" }); 64 | 65 | return cjs.exists && !js.exists; 66 | }, 67 | }, 68 | { 69 | description: `\`src/app.${extension}\` exists`, 70 | async detector({ readFile }) { 71 | const postcss = await readFile({ path: `/src/app.${extension}` }); 72 | 73 | return postcss.exists; 74 | }, 75 | }, 76 | { 77 | description: `The main file (\`src/routes/+layout.svelte\` for SvelteKit, \`src/main.js\` or \`src/main.ts\` for Vite) imports \`src/app.${extension}\``, 78 | async detector({ folderInfo, readFile }) { 79 | if (folderInfo.kit) { 80 | const { text } = await readFile({ path: "/src/routes/+layout.svelte" }); 81 | 82 | return text.includes(`../app.${extension}`); 83 | } 84 | 85 | const ts = await readFile({ path: "/src/main.ts" }); 86 | if (ts.exists) return ts.text.includes(`./app.${extension}`); 87 | 88 | const js = await readFile({ path: "/src/main.js" }); 89 | return js.text.includes(`./app.${extension}`); 90 | }, 91 | }, 92 | ]; 93 | -------------------------------------------------------------------------------- /__run.js: -------------------------------------------------------------------------------- 1 | import { setupStyleLanguage } from "../../adder-tools.js"; 2 | import { addImport, findImport, setDefaultDefaultExport, setDefault } from "../../ast-tools.js"; 3 | import { extension, postcssConfigCjsPath, stylesHint } from "./stuff.js"; 4 | 5 | /** 6 | * @param {import("../../ast-io.js").RecastAST} postcssConfigAst 7 | * @param {boolean} autoprefixer 8 | * @returns {import("../../ast-io.js").RecastAST} 9 | */ 10 | const updatePostcssConfig = (postcssConfigAst, autoprefixer) => { 11 | const configObject = setDefaultDefaultExport({ 12 | cjs: true, 13 | defaultValue: { 14 | type: "ObjectExpression", 15 | properties: [], 16 | }, 17 | typeScriptEstree: postcssConfigAst, 18 | }); 19 | 20 | if (configObject.type !== "ObjectExpression") throw new Error("PostCSS config must be an object"); 21 | 22 | const pluginsList = setDefault({ 23 | default: /** @type {import("estree").ArrayExpression} */ ({ 24 | type: "ArrayExpression", 25 | elements: [], 26 | }), 27 | object: configObject, 28 | property: "plugins", 29 | }); 30 | 31 | if (pluginsList.type === "ArrayExpression") { 32 | if (autoprefixer) { 33 | let autoprefixerImportedAs = findImport({ cjs: true, package: "autoprefixer", typeScriptEstree: postcssConfigAst }).require; 34 | // Add an Autoprefixer import if it's not there 35 | if (!autoprefixerImportedAs) { 36 | autoprefixerImportedAs = "autoprefixer"; 37 | addImport({ require: autoprefixerImportedAs, cjs: true, package: "autoprefixer", typeScriptEstree: postcssConfigAst }); 38 | } 39 | pluginsList.elements.push({ 40 | type: "Identifier", 41 | name: autoprefixerImportedAs, 42 | }); 43 | } 44 | } else if (pluginsList.type === "ObjectExpression") { 45 | setDefault({ 46 | default: { 47 | type: "ObjectExpression", 48 | properties: [], 49 | }, 50 | object: pluginsList, 51 | property: "autoprefixer", 52 | }); 53 | } else { 54 | throw new Error("`plugins` in PostCSS config must be an array or object"); 55 | } 56 | 57 | return postcssConfigAst; 58 | }; 59 | 60 | /** @type {import("../..").AdderRun} */ 61 | export const run = async ({ folderInfo, install, options, updateCss, updateJavaScript, updateSvelte }) => { 62 | await setupStyleLanguage({ 63 | extension, 64 | folderInfo, 65 | mutateSveltePreprocessArgs() {}, 66 | stylesHint, 67 | updateCss, 68 | updateJavaScript, 69 | updateSvelte, 70 | }); 71 | 72 | await updateJavaScript({ 73 | path: postcssConfigCjsPath, 74 | async script({ typeScriptEstree }) { 75 | return { 76 | typeScriptEstree: updatePostcssConfig(typeScriptEstree, options.autoprefixer), 77 | }; 78 | }, 79 | }); 80 | 81 | await install({ package: "postcss" }); 82 | await install({ package: "postcss-load-config" }); 83 | 84 | if (!folderInfo.kit) await install({ package: "@sveltejs/vite-plugin-svelte" }); 85 | 86 | if (options.autoprefixer) await install({ package: "autoprefixer" }); 87 | }; 88 | -------------------------------------------------------------------------------- /stuff.js: -------------------------------------------------------------------------------- 1 | export const extension = "pcss"; 2 | export const postcssConfigCjsPath = "/postcss.config.cjs"; 3 | export const stylesHint = "Write your global styles here, in PostCSS syntax"; 4 | --------------------------------------------------------------------------------