├── 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 | [](https://github.com/svelte-add/svelte-add/issues?q=is%3Aopen+is%3Aissue+label%3A%22confirmed+bug%22)
4 | [](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 |
--------------------------------------------------------------------------------