Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

💡 RFC: Support a default layout #397

Closed
jonbell-lot23 opened this issue Jun 11, 2021 · 24 comments
Closed

💡 RFC: Support a default layout #397

jonbell-lot23 opened this issue Jun 11, 2021 · 24 comments

Comments

@jonbell-lot23
Copy link

jonbell-lot23 commented Jun 11, 2021

Currently each markdown page needs a layout set, like this:

---
layout: ../layouts/Main.astro
---

But I'd like to be able to have everything in a directory use a default layout if one isn't already set. That way I can drag 1000 markdown files into a directory and have everything have a layout automatically.

@duncanhealy
Copy link
Contributor

as mentioned on discord can use sed trickery to do this
sed -i '1s/^/\-\-\-\nlayout\: ..\/layouts\/Main.astro\n\-\-\-\n/g' *.md
assumes no frontmatter in the md files already

@FredKSchott
Copy link
Member

I think this makes sense. Not sure the best way to solve though.

If we could get one or two other similar use-cases together, I could see a use-case where a src/pages/some-folder/_meta.js or src/pages/some-folder/_config.js file could make sense.

@ddemaree
Copy link

If we could get one or two other similar use-cases together, I could see a use-case where a src/pages/some-folder/_meta.js or src/pages/some-folder/_config.js file could make sense.

I was just asking about this on the Discord channel — Eleventy addresses this by letting you define folder-level data (as foldername.11tydata.js) which is merged into the doc's frontmatter data. A _config or _meta file to set a default layout would be nice, but it would be double nice if that file or something like _data could set data defaults, which could include layout.

@FredKSchott
Copy link
Member

I think we'll wait until we've officially settled on the Markdown file format. There's talk of moving Markdown frontmatter syntax to JS (instead of YAML) to match .astro + add support for Components-in-markdown.

This convo is currently being kicked off so we should be able to unblock this soon.

@jonbell-lot23
Copy link
Author

Thanks for the update!

@FredKSchott FredKSchott changed the title Support a default layout 💡 RFC: Support a default layout Jun 30, 2021
@akellbl4 akellbl4 mentioned this issue Jul 1, 2021
2 tasks
@natemoo-re
Copy link
Member

Action item from RFC Call: This sounds good, let's work on a potential design so we can discuss this in more detail.

@okikio
Copy link
Member

okikio commented Jul 12, 2021

I'd like to use this together with typedoc & typedoc-plugin-markdown, would this be possible with this proposal?

@jkdoshi
Copy link

jkdoshi commented Aug 1, 2021

I think we'll wait until we've officially settled on the Markdown file format. There's talk of moving Markdown frontmatter syntax to JS (instead of YAML) to match .astro + add support for Components-in-markdown.

This convo is currently being kicked off so we should be able to unblock this soon.

I hope you're just planning to add JS frontmatter as an option, and not talking about changing the default from YAML to JS.

That would be a serious problem (switching default frontmatter sytax to JS). By convention, people have come to expect YAML as the frontmatter syntax (by default) in MD files. There's plenty of MD content that already uses YAML frontmatter. If you change it, it would be harder to migrate existing content into Astro. It might hamper Astro's adoption.

Already I'm waiting for this issue to get fixed (default layout) before I can migrate my site from 11ty to Astro.

In general, the "data" features of 11ty are very useful, and I'd love to see them in Astro. But all those things can be implemented within the layout.astro file once I can supply a default layout.

@philipp-tailor
Copy link

In #1267, I proposed the following options to achieve a default template:

The example file structure to discuss the logic:

src/
  pages/
    docs/
      intro.md
      installation.md
    changelog/
      v1dot0.md
      v1dot1.md
      v2dot0.md

I proposed to allow a file name & placement based default template logic. So let's say, the following layouts would exist:

  layouts/
    docs.astro
    changelog/
      [version].astro

Then, the following heuristic could be used to process markdown files.

**In src/pages/docs/*.md:

  1. Does the markdown file contain a frontmatter layout property? --> Use the referenced layout.
  2. Is there a layout file with the same relative path as the markdown path (docs.astro)? --> Use the layout with the matching relative path.

The heuristic could have an additional layer of flexibility, as demonstrated in src/pages/changelog/*.md:
The first two steps of the heuristic are the same:

  1. Does the markdown file contain a frontmatter layout property? --> Use the referenced layout.
  2. Is there a layout file with the same relative path as the markdown path (changelog.astro)? --> Use the layout with the matching relative path.
  3. Is there a layout (sub) folder with the same relative path as the markdown path (changelog/)? Layout files in there could then have the same name as the markdown file, in which case they'd be used. Or, as demonstrated in the example file structure, the layout file name could allow dynamic parameters, similar to file based routing. In the demonstrated case, the [version].astro layout would match all src/pages/changelog/*.md files, and the layout file would receive a parameter with the markdown file name, based on which it could yield different outputs.

Alternatives considered

Allow to place a layout.astro in src/pages/* (sub)directories. This might be more explicit and easier to understand, but weakens the current folder structure separation.
Other proposals are to provide configuration files. I decided not to propose that because I personally prefer convention over configuration.

Risks, downsides, and/or tradeoffs

Higher amount of framework logic a user has to consider when naming / setting up layouts. Less explicit mapping of pages and layouts. But higher flexibility and reduced verbosity for simple use-cases.

@jasikpark
Copy link
Contributor

@okikio
Copy link
Member

okikio commented Oct 5, 2021

@jasikpark we should hold up until Astro components in markdown are supported.

@FredKSchott
Copy link
Member

Hey everyone! Our current RFC process is beginning to break down at this size, with over 50 open RFCs currently in the "discussing" stage. A growing community is a great problem to have, but our ability to give you RFC feedback has suffered as a result. In an effort to improve our RFC process, we are making some changes to better organize things.

From now on, all RFCs will live in a standalone repo: https://github.com/withastro/rfcs

This allows us to do three things: 1) Use threaded discussions for high-level ideas and improvements, without necessarily requiring an implementation for every idea. 2) Improve the quality of our RFC template and the speed/quality of all feedback. 3) Support inline comments and explicit approvals on RFCs, via a new Pull Request review process.

We hope that this new process leads to better RFC weekly calls and faster feedback on your RFCs from maintainers. More detail can be found in the new RFC repo README.


We can't automatically convert this issue to an RFC in the new repo because new RFC template is more detailed that this one. But, you can still continue this discussion in the new repo by creating a new Discussion in the RFC repo and copy-and-pasting this post (and any relevant follow-up comments) into it. Discussions are available for high-level ideas and suggestions without the requirement of a full implementation proposal.

Then, when you are ready to propose (or re-propose) an implementation for feedback and approval, you can create a new RFC using the new RFC template. More detail about how to do this can be found in the new RFC repo README.

Thanks for your patience as we attempt to improve things for both authors and reviewers. If you have any questions, don't hesitate to reach out on Discord. https://astro.build/chat

@phocks
Copy link
Contributor

phocks commented Jul 24, 2022

This strategy kind of solves it for me using aliases. Granted it's not a "default" layout, but it saves having to set a relative path for all my markdown files. I can just make all of the layout strings the same. https://docs.astro.build/en/guides/aliases/

in tsconfig.json

"paths": {
      "@components/*": ["src/components/*"],
      "@layouts/*": ["src/layouts/*"],
      "@lib/*": ["src/lib/*"]
    }

Then in my .md files

---
layout: "@layouts/Page.astro"

@delucis
Copy link
Member

delucis commented Jul 24, 2022

#3411 should provide this functionality once we have a chance to update and merge it!

@domchristie
Copy link

domchristie commented Sep 4, 2022

My workaround uses a combination of @phocks's alias idea, and a remark plugin:

1. Create an alias to your layouts
In tsconfig.json or jsconfig.json in the compilerOptions property:

    "baseUrl": ".",
    "paths": {
      "@layouts/*": ["./src/layouts/*"],
    }

(Ensure that compilerOptions.baseUrl is also set.)

2. Inject frontmatter.layout
The easiest way to play around with this is to create a remark plugin in astro.config.mjs. The simplest version would be:

function defaultLayoutPlugin () {
  return function (tree, file) {
    file.data.astro.frontmatter.layout = '@layouts/Layout.astro'
  }
}

export default defineConfig({
  markdown: {
    remarkPlugins: [defaultLayoutPlugin],
    extendDefaultPlugins: true
  },
});

I've created a Rails-like convention where it looks for a layout with the same name as the file's directory e.g. for ./src/pages/posts/hello-world.md, it will look for a layout named ./src/layouts/Posts.astro and set it as the layout if it exists.

import { existsSync } from 'fs'

function defaultLayoutPlugin () {
  const layouts = new Set

  return function (tree, file) {
    const fileName = file.history[0]
    const directory = fileName.split('/').slice(-2)[0]
    const layout = (
      directory.charAt(0).toUpperCase() + directory.slice(1) + '.astro'
    )
    if (layouts.has(layout) || existsSync(`./src/layouts/${layout}`)) {
      layouts.add(layout)
      file.data.astro.frontmatter.layout = `@layouts/${layout}`
    }
  }
}

Frontmatter overrides in the file also appear to work.

I hope that helps!

@mrienstra
Copy link
Contributor

Slightly more complex version of @domchristie's "Rails-like" defaultLayoutPlugin (above):

astro.config.mjs:

import { existsSync } from 'fs';
import { join } from 'path';

const pagesPath = 'src/pages';
const layoutPath = 'src/layouts';
const layoutAlias = '@layouts';
const defaultLayout = 'MainLayout';

const pascalCache = {};
function toPascalCase(str) {
  pascalCache[str] =
    pascalCache[str] ||
    (
      /^[\p{L}\d]+$/iu.test(str) &&
      str.charAt(0).toUpperCase() + str.slice(1)
    ) ||
    str.replace(
      /([\p{L}\d])([\p{L}\d]*)/giu,
      (g0, g1, g2) => g1.toUpperCase() + g2.toLowerCase()
    ).replace(/[^\p{L}\d]/giu, '');

  return pascalCache[str];
}

const knownLayouts = new Set;
const knownNotLayouts = new Set;

function defaultLayoutPlugin() {
  return function (tree, file) {
    const filePathFull = file.history[0].replace(/\/[^\/]+$/, '');
    const pagesPathFull = join(file.cwd, pagesPath);
    const nestedDirs = filePathFull.slice(pagesPathFull.length + 1);
    const directories = nestedDirs ? nestedDirs.split('/').reverse() : [];
    let layout = defaultLayout;
    directories.some(directory => {
      const layoutName = toPascalCase(directory);
      if (
        knownLayouts.has(layoutName) ||
        (
          !knownNotLayouts.has(layoutName) &&
          existsSync(join(layoutPath, layoutName + '.astro'))
        )
      ) {
        knownLayouts.add(layoutName);
        layout = layoutName;
        return true;
      } else {
        knownNotLayouts.add(layoutName);
      }
    });
    file.data.astro.frontmatter.layout = `${layoutAlias}/${layout}.astro`;
  }
}

Notable changes:

  • I had to move knownLayouts outside of defaultLayoutPlugin in order for it to persist (while running pnpm dev).
  • Added knownNotLayouts, to further cut down on redundant fs.existsSync calls.
  • Checks multiple nested directories, e.g. one/two/three.html would first check for a Two layout, then for an One layout if Two didn't match a layout.
  • When mapping a directory to a layout, the directory name is converted to PascalCase, instead of just have its first letter capitalized. Before: some-dir --> Some-dir. After: some-dir --> SomeDir.
  • Includes a default fallback layout.

@mrienstra
Copy link
Contributor

export default defineConfig({
  markdown: {
    remarkPlugins: [defaultLayoutPlugin]
  },
});

⚠️ Note that you need to include extendDefaultPlugins: true (as per 1 & 2) if you don't want to drop the default GitHub-flavored Markdown and Smartypants plugins.

export default defineConfig({
  markdown: {
    remarkPlugins: [defaultLayoutPlugin],
    extendDefaultPlugins: true
  },
});

@sean-au
Copy link

sean-au commented Dec 27, 2022

Does anyone know where this is now at? The docs don't mention anything about this. Has progress halted?

@EccentricVamp
Copy link

EccentricVamp commented Dec 28, 2022

Does anyone know where this is now at? The docs don't mention anything about this. Has progress halted?

There hasn't been any progress. The only pull request implementing this RFC was closed and there hasn't been anything since.

@braden-w
Copy link

FYI, if anyone is still trying to solve this, I solved this by running the following in the terminal (from the root directory) to insert the string "layout: ../../layouts/ArticleLayout.astro" on the second line of every file in src/pages/articles/*:

for file in src/pages/articles/*; do (head -n 1 $file && echo "layout: ../../layouts/ArticleLayout.astro" && tail -n +2 $file) > temp && mv temp $file; done

You can modify "layout: ../../layouts/ArticleLayout.astro" and src/pages/articles/* as desired. Note that this overrated files, so you will probably want to check your Git after you run the script. You can put it into your package.json, if you would like!

Explanation

  1. The script uses a for loop to iterate through all files in the folder src/pages/articles.
  2. For each file, the script uses head and tail to insert the string "layout: ../../layouts/ArticleLayout.astro" as the second line.
  3. The modified file is then written to a temporary file, and the original file is overwritten with the temporary file using mv.

@kevinleedrum
Copy link

Just a heads up that this is possible in v2 using a custom remark plugin.

const DEFAULT_LAYOUT = '../layouts/ArticleLayout.astro';

function setDefaultLayout() {
  return function (_, file) {
    const { frontmatter } = file.data.astro;
    if (!frontmatter.layout) frontmatter.layout = DEFAULT_LAYOUT;
  };
}
// astro.config.mjs

export default defineConfig({
  // ...
  markdown: {
    remarkPlugins: [setDefaultLayout],
  },
});

You could even make it dynamic based on other frontmatter properties or properties on the file I suppose.

@braden-w
Copy link

Heads up, this is also no longer necessary if you use content collections. You can edit the [slug].astro file to be the desired layout and insert <Content /> where desired, where <Content /> is the frontmatter body, as mentioned here for static and here for SSR.

@EccentricVamp
Copy link

EccentricVamp commented Jan 28, 2023

I'm glad that recent updates have made it easier to work around the lack of default layouts, but I still haven't seen anything that officially addresses the original RFC: Support a default layout

@mrienstra
Copy link
Contributor

If anyone wishes to revive this conversation, here are instructions on starting a proposal (RFC precursor):
https://github.com/withastro/roadmap#stage-1-proposal

Interested parties should also take a look at the defaultLayout proposal, which includes a frontmatterDefaults proposal.

Might be wise to request feedback on Discord early on, to try to feel out which approach is likely to gain traction with Astro's maintainer team.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests