11ty / eleventy

A simpler site generator. Transforms a directory of templates (of varying types) into HTML.
https://www.11ty.dev/
MIT License
16.92k stars 491 forks source link

__dirname in JS front matter should be current directory #581

Closed jakearchibald closed 10 months ago

jakearchibald commented 5 years ago
---js
{
  whatever: __dirname
}
---

I would expect whatever to be the directory of the current template, but it's …node_modules/gray-matter/lib.

jevets commented 5 years ago

I'd expect the same...

I doubt the same happens if using a separate .11tydata.js file instead of JS front-matter.

Ryuno-Ki commented 5 years ago

The docs say

The directory name of the current module. This is the same as the path.dirname() of the __filename.

Could you use process.cwd() instead?

jakearchibald commented 5 years ago

Could you use process.cwd() instead?

No that's a different thing.

jakearchibald commented 5 years ago

I doubt the same happens if using a separate .11tydata.js file instead of JS front-matter.

In .11tydata.js files, __filename and __dirname are relatively to the .11tydata.js file (as expected). It also works as expected in ejs files.

Ryuno-Ki commented 5 years ago

Hmm … The only reference for gray-matter is https://github.com/11ty/eleventy/blob/f2de24821b47dfbeef1fff9716d4f442bb93060a/src/TemplateContent.js#L72

So …

The directory name of the current module.

Would be the gray-matter lib at that time (because it is parsing the frontmatter at that point).

@jakearchibald What do you want to do with the __dirname? Could https://github.com/11ty/11ty.io/issues/33 be related here?

Maybe the eleventy-base-blog could give you some starters as well.

jevets commented 5 years ago

@jakearchibald Thinking more about it, I wonder if you'd be better off explicitly defining front-matter (or probably more likely template/directory data), just in case you end up changing your structure in the future. Seems a little cumbersome at first, but you may end up with separate directories that share parts of your layout css that you might prefer to reuse vs ending up having to duplicate something from your "layout chain" or code up exceptions to your lookups related to your current-template. (Just thinking out loud here)

(I'm assuming your other issue is related, but please feel free to correct me and/or provide more context.)

zachleat commented 5 years ago

Gonna have to think on this one a bit.

This is what __filename (and relatedly __dirname) are resolving to here: https://github.com/jonschlinkert/gray-matter/blob/master/lib/engines.js#L39

Looks like the JS front matter parsing does an eval in our dependency (gray-matter).

Somewhat relatedly, I did add the option to do custom front matter engines in the next version (see https://www.11ty.io/docs/data-frontmatter/#example%3A-using-toml-for-front-matter-parsing for a TOML example)

zachleat commented 5 years ago

Related docs has a nice example: https://nodejs.org/api/modules.html#modules_filename

Given two modules: a and b, where b is a dependency of a and there is a directory structure of:

/Users/mjr/app/a.js /Users/mjr/app/node_modules/b/b.js

References to __filename within b.js will return /Users/mjr/app/node_modules/b/b.js while references to __filename within a.js will return /Users/mjr/app/a.js.

zachleat commented 5 years ago

@jakearchibald when you say:

It also works as expected in ejs files.

Can you provide a sample of a working ejs file test? In my local tests it’s still resolving to that gray matter node_modules path.

jakearchibald commented 5 years ago

Sorry, I meant in ejs templates, not in grey matter. So the test would be <%= __dirname %>

Snapstromegon commented 1 year ago

I think a possible solution for this is to pass the currently rendered path into matter from here by overwriting the original JS engine in gray-matter via the options.engine.javascript argument to something like this:

{
  parse: function parse(str, options, wrap) {
    /* eslint no-eval: 0 */
    try {
      if (wrap !== false) {
        const injections = {
          __filename: this.inputPath,
          __dirname: this.inputDir
        };
        str = `(function() {
          ${
            [...Object.entries(injections)]
              .map(([key, value]) => `let ${key} = ${value};`)
              .join('\n')
          }
          return ${str.trim()};
        }());`;
      }
      return eval(str) || {};
    } catch (err) {
      if (wrap !== false && /(unexpected|identifier)/i.test(err.message)) {
        return parse(str, options, false);
      }
      throw new SyntaxError(err);
    }
  },
  stringify: function() {
    throw new Error('stringifying JavaScript is not supported');
  }
}

The code above is not the nicest way to write it, but hopefully gets my point across. I think this is the only place where you can actually do this, because outside of this you don't have access to the currently rendering path and the matter parser. This also means that this doesn't work of a user decides to override the config.frontMatterParsingOptions.

The only alternative right now that I see is monkeypatching some 11ty attribute into the gray-matter options, which a custom matter engine can extract again. That would at least allow for users to use their own parsing engine and enable further injections in the future.

zachleat commented 10 months ago

As things are moving towards ESM long term, I don’t think we’ll probably tackle this directly.

https://nodejs.org/dist/latest-v20.x/docs/api/esm.html#no-__filename-or-__dirname

I would have expected our new node front matter format to have access to __filename though (as it isn’t fully ESM), but in testing it doesn’t look like Node’s vm module has __filename either. That’s okay, it’ll save us some headache later when the ESM version of vm is stable.

HOWEVER, in working on #2819 there is one some small change to your comment @Snapstromegon:

All of that said, I’ll make a change to the node front matter format to make page.inputPath available (per Eleventy conventions). Noting also (taking a 10000 foot view), that page.inputPath is the cross-template syntax Eleventy convention to access the file name inside of a template.

https://www.11ty.dev/docs/data-eleventy-supplied/#page-variable

zachleat commented 10 months ago

Just to be clear, in Eleventy 3.0 you’ll be able to (nunjucks/liquid template):

---js
let whatever = page.inputPath;
---
{{ whatever }}