Gatsby: [gatsby-plugin-netlify-cms] Enabling preview styles

Created on 21 Jul 2018  Â·  16Comments  Â·  Source: gatsbyjs/gatsby

Summary

I'm not sure how to get Netlify CMS to display custom preview styles.

Relevant information

I am using Netlify CMS with custom preview components that use CSS modules. It's unclear how to get the styles to be generated and loaded. Particularly, I don't know what I should be putting in stylesPath in the config. All of the styles are imported in the components, so there is no entry point as such. I tried setting the path to point to one of the base stylesheets, but styles.css is not generated. There is a styles.js file created by mini-css-extract-plugin, but no matching CSS.

import CMS from "netlify-cms";
import "netlify-cms/dist/cms.css";

// import preview templates for the CMS here

import ProjectPagePreview from "./preview-templates/ProjectPagePreview";

// Styles for the CMS
CMS.registerPreviewStyle("styles.css");

// Register preview templates

CMS.registerPreviewTemplate("projects", ProjectPagePreview);

Environment (if relevant)

File contents (if changed)

gatsby-config.js:

const siteConfig = require("./site-config");

module.exports = {
    pathPrefix: siteConfig.pathPrefix,
    siteMetadata: siteConfig.siteMetadata,
    mapping: {
        "ProjectsJson.client": "ClientsJson"
    },
    plugins: [
        `gatsby-plugin-typescript`,
        `gatsby-plugin-react-helmet`,
        {
            resolve: `gatsby-source-filesystem`,
            options: {
                path: `${__dirname}/static/assets`,
                name: "uploads"
            }
        },
        {
            resolve: `gatsby-source-filesystem`,
            options: {
                path: `${__dirname}/src/pages`,
                name: "pages"
            }
        },
        // This plugin identifies file nodes that are images and
        // transforms these to create new “ImageSharp” nodes.
        // With them you can resize images and
        // generate responsive image thumbnails.
        `gatsby-transformer-sharp`,
        // transform JSON file nodes
        `gatsby-transformer-json`,
        // This plugin exposes helper functions for processing
        // images with the NPM package “sharp”. It's used by
        // several plugins.
        `gatsby-plugin-sharp`,
        `gatsby-plugin-sass`,
        // Manifest for AppCache and PWA compatibility
        {
            resolve: `gatsby-plugin-manifest`,
            options: siteConfig.manifest
        },
        {
            resolve: `gatsby-plugin-netlify-cms`,
            options: {
                // One convention is to place your Netlify CMS customization code in a
                // `src/cms` directory.
                modulePath: `${__dirname}/src/cms/cms.ts`,
                stylesPath: `${__dirname}/src/scss/base-theme.scss`
            }
        }
    ]
};

package.json:

{
    "name": "aerian-site-rebuild",
    "description": "Aerian Studios.",
    "version": "0.0.1",
    "dependencies": {
        "@babel/preset-env": "^7.0.0-beta.54",
        "@fortawesome/fontawesome-svg-core": "^1.2.0-14",
        "@fortawesome/free-brands-svg-icons": "^5.1.0-11",
        "@fortawesome/free-solid-svg-icons": "^5.1.0-11",
        "@fortawesome/react-fontawesome": "^0.1.0-11",
        "@researchgate/react-intersection-observer": "^0.7.3",
        "classnames": "^2.2.6",
        "deep-map": "^1.5.0",
        "extract-text-webpack-plugin": "^3.0.2",
        "gatsby": "2.0.0-beta.45",
        "gatsby-image": "^2.0.0-beta.6",
        "gatsby-plugin-manifest": "^2.0.2-beta.2",
        "gatsby-plugin-netlify-cms": "^2.0.0-beta.6",
        "gatsby-plugin-offline": "next",
        "gatsby-plugin-react-helmet": "^3.0.0-beta.3",
        "gatsby-plugin-sass": "^2.0.0-beta.5",
        "gatsby-plugin-sharp": "^2.0.0-beta.5",
        "gatsby-plugin-typescript": "^2.0.0-beta.5",
        "gatsby-source-filesystem": "^2.0.1-beta.5",
        "gatsby-transformer-json": "^2.1.1-beta.2",
        "gatsby-transformer-sharp": "^2.1.1-beta.5",
        "leaflet": "^1.3.1",
        "netlify-cms": "^1.9.2",
        "react": "^16.4.1",
        "react-dom": "^16.4.1",
        "react-helmet": "^5.2.0",
        "react-leaflet": "^2.0.0",
        "react-markdown": "^3.3.4"
    },
    "keywords": [
        "gatsby"
    ],
    "license": "MIT",
    "scripts": {
        "build": "gatsby build",
        "start": "gatsby develop",
        "test": "jest",
        "updateSnapshot": "jest --updateSnapshot",
        "storybook": "start-storybook -p 9001 -c .storybook",
        "storybook-build": "build-storybook -c .storybook -o docs",
        "precommit": "lint-staged",
        "prepush": "jest --ci",
        "test-ci": "jest --coverage --coverageReporters=text-lcov --maxWorkers=2 > coverage.lcov && codecov"
    },
    "devDependencies": {
        "@babel/core": "^7.0.0-beta.54",
        "@babel/plugin-proposal-class-properties": "^7.0.0-beta.54",
        "@babel/plugin-proposal-optional-chaining": "^7.0.0-beta.54",
        "@babel/plugin-syntax-dynamic-import": "^7.0.0-beta.51",
        "@babel/preset-react": "^7.0.0-beta.54",
        "@storybook/addon-actions": "^3.4.8",
        "@storybook/addon-info": "^3.4.8",
        "@storybook/react": "^4.0.0-alpha.10",
        "@types/classnames": "^2.2.4",
        "@types/jest": "^23.1.1",
        "@types/leaflet": "^1.2.8",
        "@types/node": "^10.5.2",
        "@types/react-helmet": "^5.0.6",
        "@types/react-leaflet": "^1.1.5",
        "@types/react-test-renderer": "^16.0.1",
        "@types/storybook__addon-actions": "^3.0.3",
        "@types/storybook__addon-info": "^3.2.3",
        "@types/storybook__react": "^3.0.7",
        "autoprefixer": "^8.6.5",
        "babel-core": "^7.0.0-bridge.0",
        "babel-jest": "^23.4.0",
        "codecov": "^3.0.4",
        "css-loader": "^0.28.11",
        "extract-text-webpack-plugin": "^3.0.2",
        "gh-pages": "^1.2.0",
        "husky": "^0.14.3",
        "hygen": "^1.6.2",
        "hygen-react-typescript": "^1.0.2",
        "identity-obj-proxy": "^3.0.0",
        "intersection-observer": "^0.5.0",
        "jest": "^23.1.0",
        "lint-staged": "^7.2.0",
        "node-sass": "^4.9.0",
        "prettier": "^1.13.5",
        "react-docgen-typescript-loader": "^2.1.1",
        "react-docgen-typescript-webpack-plugin": "^1.1.0",
        "react-test-renderer": "^16.4.1",
        "regenerator-runtime": "^0.12.0",
        "ts-jest": "^22.4.6",
        "ts-loader": "^4.4.1",
        "tslint": "^5.10.0",
        "tslint-config-aerian": "^1.0.2",
        "typescript": "^2.9.2",
        "typings-for-css-modules-loader": "^1.7.0"
    },
    "lint-staged": {
        "*.{js,jsx,css,md,scss}": [
            "prettier --write",
            "git add",
            "jest --ci --findRelatedTests"
        ],
        "*.{ts,tsx}": [
            "tslint --fix",
            "git add",
            "jest --ci --findRelatedTests"
        ]
    },
    "repository": {
        "type": "git",
        "url": "git+https://github.com:aerian-studios/aerian-site-rebuild.git"
    },
    "jest": {
        "transform": {
            "^.+\\.tsx?$": "ts-jest",
            "^.+\\.jsx?$": "<rootDir>/jestPreprocess.js"
        },
        "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(tsx?)$",
        "moduleFileExtensions": [
            "ts",
            "tsx",
            "js",
            "jsx",
            "json",
            "node"
        ],
        "moduleNameMapper": {
            "^.+\\.(css|less|scss|png)$": "identity-obj-proxy",
            "^./pages.json$": "<rootDir>/__mocks__/pages.json"
        },
        "transformIgnorePatterns": [
            "node_modules/(?!(gatsby)/)"
        ]
    }
}

gatsby-node.js:

const path = require("path");
const { createFilePath } = require("gatsby-source-filesystem");
const deepMap = require("deep-map");
// Implement the Gatsby API “createPages”. This is
// called after the Gatsby bootstrap is finished so you have
// access to any information necessary to programmatically
// create pages.
exports.createPages = ({ actions, graphql }) => {
    const { createPage } = actions;

    /**
     * Work out the necessary to generate disntinct pages
     * @param {object} edge - The data for the distinct page
     */
    const generateDistinctPage = data => {
        const template = data.path && data.path.replace("/", "");
        const { id, sections, staff } = data;

        // for the time being we can just assume that there are different teplates for each of the pages, but we can add logic here to reuse page templates
        createPage({
            path: template,
            component: path.resolve(`src/templates/${String(template)}.tsx`),
            // additional data can be passed via context
            context: {
                id
            }
        });
    };

    /**
     *
     * @param {string} id - JSON id, generally a path to the file
     * @param {string} template - the string name of the template without the `.tsx`
     * @param {string} slug - generally the unique name of the JSON file (without path or file type)
     */
    const generatePage = (id, template, slug) => {
        createPage({
            path: slug,
            component: path.resolve(`src/templates/${String(template)}.tsx`),
            // additional data can be passed via context
            context: {
                id
            }
        });
    };

    /**
     * Run queries to get all the types of pages for which we need to make static pages
     *
     * `allPagesJson` needs a bit more information if we want to control how they are
     * processed in the future
     */
    return graphql(
        `
            {
                allProjectsJson(limit: 1000) {
                    edges {
                        node {
                            id
                            slug
                        }
                    }
                }
                allPagesJson(limit: 1000) {
                    edges {
                        node {
                            id
                            path
                            staff {
                                name
                            }
                            sections {
                                title
                            }
                        }
                    }
                }
            }
        `
    ).then(result => {
        if (result.errors) {
            result.errors.forEach(e => console.error(e.toString()));
            return Promise.reject(result.errors);
        }

        // Gatsby uses Redux to manage its internal state.
        // Plugins and sites can use functions like "createPage"
        // to interact with Gatsby.
        result.data.allProjectsJson.edges.forEach(edge => {
            const id = edge.node.id;
            const template = "project";
            const slug = `our-work/project/${edge.node.slug}`;

            generatePage(id, template, slug);
        });

        result.data.allPagesJson.edges.forEach(edge => {
            generateDistinctPage(edge.node);
        });
        return Promise.resolve();
    });
};

const excluded = new Set(["internal", "children", "parent", "id"]);

exports.onCreateNode = ({ node, getNode, getNodes }) => {
    if (node.internal.owner === "gatsby-transformer-json") {
        const parent = getNode(node.parent);
        const makeRelative = value => {
            if (typeof value === "string") {
                const pathToFile = path.join(__dirname, "static", value);
                const foundFileNode = getNodes().find(
                    n => n.absolutePath === pathToFile
                );

                if (foundFileNode) {
                    const p = path.relative(
                        parent.dir,
                        foundFileNode.absolutePath
                    );
                    if (p) {
                        return p;
                    }
                }
            }
            return value;
        };
        Object.keys(node).forEach(key => {
            if (excluded.has(key)) {
                return;
            }

            if (typeof node[key] === "string") {
                node[key] = makeRelative(node[key]);
            }
            deepMap(node[key], makeRelative, {
                inPlace: true
            });
        });
    }
};

gatsby-browser.js: N/A
gatsby-ssr.js: N/A

help wanted question or discussion

All 16 comments

Do the styles work in production?

Yes, it's just in the CMS preview. The preview even generates the correct HTML, with the correct CSS module classnames. It just isn't building the styles bundle.

There was another issue like this, see:

https://github.com/AustinGreen/gatsby-starter-netlify-cms/issues/19

For the record I'm having similar issues getting styling to work in Gatsby v2, but linking in case its relevant to you. In my case, I can't get even the class names to actually apply to the stuff in the preview so it all just looks like plain text. You're one step ahead of me.

The comment on that thread talks about it generating styles.css. My issue is that that file isn't being generated.
Edit: to be clear, mine is just showing unstyled too. It just has the correct class names in the HTML, but no stylesheet.

I don't think this is the issue but another thing I'm looking at here is that you're calling CMS.registerPreviewStyle("styles.css"); in your cms.js then setting it to something different in gatsby-config.js The config option also calls CMS.registerPreviewStyles() so I imagine that would cause something to be overridden.

I tried it with various combinations. From what I understand, the setting in gatsby-config.js specifies the input file, while CMS.registerPreviewStyles() refers to the output css, which is hard-coded as styles.css in the plugin.

No, I don't think so.

Take a look here at cms.js:

// eslint-disable-next-line no-undef
if (NETLIFY_CMS_PREVIEW_STYLES_SET) {
  CMS.registerPreviewStyle(`styles.css`)
}

And here in gatsby-node.js

plugins.define({
      NETLIFY_CMS_PREVIEW_STYLES_SET: !!stylesPath,
    }),

The plugin is directly calling the CMS.registerPreviewStyles() method on whatever option is set.

Per the documentation here, you can pass a path or array of paths to the option.

Changing my config to provide it with the path to the css file that is used by my site rendered _some_ of the styles in the preview pane. My case is probably a bit simpler though because all the styles are contained within actual css files.

The bit in gatsby-node.js just sets NETLIFY_CMS_PREVIEW_STYLES_SET to true if stylesPath is set to anything. Then cms.js calls CMS.registerPreviewStyle("styles.css") if that is set to true.

Yeah you're right. I'll post back if I find more!

@ascorbic try making the Netlify CMS plugin last (except for any plugins that also require being last, like gatsby-plugin-netlify. Specifically you want the Netlify CMS plugin to come after any plugins that process CSS. Can you give that a shot and comment back?

Ah, disregard, just noticed your config in your original post.

@ascorbic I took the liberty of forking your repo and checking things out locally - styles seem to be working. You'll want to remove the registerPreviewStyle in your cms.ts file, it's duplicating what the Netlify CMS plugin already does (not sure if it's hurting things, it shouldn't be).

Run the develop task and check out http://localhost:8000/admin/styles.css - you'll see the output from base-theme.scss per your stylesPath. I'd recommend creating a styles.ts module (name can be anything) in your cms directory, importing all necessary site styles there, and using that module in your stylesPath.

Hope that makes sense lol.

Closing for now, please comment if you have further issues.

@erquhart Thanks. Unfortunately that doesn't work. The issue is that I'm using CSS modules. Importing base-theme.scss only gives the base styles (fonts etc), not the component and page styles. Those are loaded in each component, rather than from an entry point. I can't load them manually from a styles.ts file, as the classNames won't match.

Can you test out #6871 and see if it works for you? With that, any imported styles in your modulePath module or any of the modules it imports down the chain will all be output to cms.css, which is automatically registered to the preview pane. (So no more stylesPath.)

Ah, that looks like a much better approach. I have a branch with Netlify CMS 2, so I'll try it with that and let you know. If you could merge netlify/netlify-cms#1543 it would be even better ;)

I can confirm that #6871 fixes this.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

andykais picture andykais  Â·  3Comments

magicly picture magicly  Â·  3Comments

benstr picture benstr  Â·  3Comments

3CordGuy picture 3CordGuy  Â·  3Comments

hobochild picture hobochild  Â·  3Comments