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

Add style transforms #7

Merged
merged 2 commits into from
Mar 18, 2021
Merged

Add style transforms #7

merged 2 commits into from
Mar 18, 2021

Conversation

drwpow
Copy link
Member

@drwpow drwpow commented Mar 17, 2021

Grabs styles from a <style> tag in an .hmx component.

⚠️ This is just the style generation; it still needs a way to inject back into the <head>

Example:

<!-- Menu.hmx -->
<style type="text/scss">
  .snow-toc {
    transition: padding 0.2s ease-out, opacity 0.2s ease-in-out;

    // -----------
    //  Components
    // -----------

    &-contents {
      margin: 0;
      padding: 0;
      line-height: 1.8;
      list-style: none;
    }

  // …
</style>

<Component>
  <nav class="snow-toc">
    <ol class="snow-toc-contents">
      <li class="snow-toc-section">
        <span class="snow-toc-section

Produces:

{
  css: '.snow-toc__subnav a,.snow-toc-link__9S2awFPL{position:relative;display:block;color:#fff;text-decoration:none;border:none;transition:color .3s}@media(min-width: 800px){.snow-toc__subnav a,.snow-toc-link__9S2awFPL{color:#717171}}.snow-toc__subnav a::before,.snow-toc-link__9S2awFPL::before{position:absolute;top:-2px;left:-19px;font-weight:400;font-size:26px;line-height:1;opacity:0;transition:left .14s ease-out;content:"▸"}.snow-toc__subnav a:hover,.snow-toc-link__9S2awFPL:hover{text-decoration:underline}.snow-toc__subnav a.active,.active.snow-toc-link__9S2awFPL{color:#0c8cec;text-decoration:underline}.snow-toc__subnav a.active::before,.active.snow-toc-link__9S2awFPL::before{left:-17px;opacity:1}.snow-toc__9S2awFPL{transition:padding .2s ease-out,opacity .2s ease-in-out}.snow-toc-contents__9S2awFPL{margin:0;padding:0;line-height:1.8;list-style:none}.snow-toc-section__9S2awFPL+.snow-toc-section__9S2awFPL{margin-top:1.5rem}.snow-toc-section-header__9S2awFPL{margin-top:0;margin-bottom:8px;color:rgba(255,255,255,.6);font-weight:600;font-size:20px;font-family:"Overpass",-apple-system,"BlinkMacSystemFont","Helvetica Neue","Segoe UI","Oxygen","Ubuntu","Cantarell","Open Sans",sans-serif;line-height:1.2em}@media(min-width: 800px){.snow-toc-section-header__9S2awFPL{color:#393939}}.snow-toc-section-items__9S2awFPL{margin:0;padding:0;list-style:none}.snow-toc__subnav{position:static;z-index:1;padding-top:2rem}.snow-toc__subnav .snow-toc-section-header__9S2awFPL{color:#393939}.snow-toc__subnav hr{display:block;height:1px;margin:1rem 0;background-color:#c1c1c1;border:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.snow-toc__subnav ol{margin:0;padding:0;list-style:none}.snow-toc__subnav li{line-height:1.8}.snow-toc__subnav a{color:#717171}',
  cssModules: Map(8) {
    'snow-toc-link' => 'snow-toc-link__9S2awFPL',
    'snow-toc' => 'snow-toc__9S2awFPL',
    'snow-toc-contents' => 'snow-toc-contents__9S2awFPL',
    'snow-toc-section' => 'snow-toc-section__9S2awFPL',
    'snow-toc-section-header' => 'snow-toc-section-header__9S2awFPL',
    'snow-toc-section-items' => 'snow-toc-section-items__9S2awFPL'
  }
}

(API TBD)

CSS

A string of the styles for this component. Needs to be injected into <head>… somehow

CSS Modules

A Map of which class names need to be injected back into the HTML/JSX. For example:

- <a class="snow-toc-link">…
+ <a class="snow-toc-link__9S2awFPL">…

@drwpow drwpow requested a review from matthewp March 17, 2021 23:03
const contents = await readFile(filePath, "utf-8");

if (!filePath.includes("/pages/") && !filePath.includes("/layouts/")) {
const result = await compileComponent(contents, filePath, { resolve });
const result = await compileComponent(contents, { compileOptions: { resolve }, filename: filePath, projectRoot });
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this function’s signature to (string, options) to simplify adding more options to the object (I personally like combining options into objects when possible)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the only downside is that "options" implies optional, and at least in the case of filename it's important it gets passed through everywhere so that we can produce good error messages. I think filename is just as important as the source text itself! But as long as we keep the field required it can be in an object.

.replace(/"$/, '')
.split(' ')
.map((c) => c.trim())
.forEach((c) => classNames.add(c));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we grab all CSS classes for a given component. This probably isn’t scoped properly yet for each component yet, but I can revisit that later.

walk(ast.css, {
async enter(node) {
if (node.type !== 'Style') return;
const styles = await transformStyle(node, { classNames, fileLoc, fileID }); // TODO: styles needs to go in <head>
Copy link
Member Author

@drwpow drwpow Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the object that gets tossed currently (needs a way to inject back into the <head>)

@drwpow drwpow force-pushed the styles branch 2 times, most recently from 7dda210 to eb783f7 Compare March 17, 2021 23:16

let css = '';
switch (styleType) {
case 'text/css': {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demands a <style type="[type]"> tag. The idea is to support the following types:

  • <style type="text/css"> (default; type isn’t required here)
  • <style type="text/scss">
  • <style type="text/sass">
  • <style type="text/postcss">

@@ -179,12 +181,12 @@ function compileScriptSafe(raw: string, loader: 'jsx' | 'tsx'): string {
return code;
}

async function convertHmxToJsx(template: string, filename: string, compileOptions: CompileOptions) {
async function convertHmxToJsx(template: string, { compileOptions, filename, fileID }: { compileOptions: CompileOptions; filename: string; fileID: string }) {
Copy link
Member Author

@drwpow drwpow Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the fileID is needed because that generates CSS Module IDs. It’s the project-relative version of filename. Read: it should be deterministic.

If we used the full filename to generate CSS Module IDs, then the IDs wouldn’t be deterministic between users. My code at /Users/drew/Code/pikapkg/hosting-experiments/… would generate different IDs than if I built a site at C:\Users\drew\hosting-experiments\…. Ditto for people on Macs. Ditto for CI.

Instead, the fileID should be consistent across users working in the same project. Any computer working on this project should generate the same consistent IDs on build. Building locally should yield the same code as building in CI.

Copy link
Member

@natemoo-re natemoo-re Mar 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we just generate a hash from the declarations inside of a given .className, then? This is getting wild but since we control the entire document we could even globally dedupe declarations and generate atomic class names (like Fela) and convert your CSS modules class name to a concatenation of those atomic class names?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I’ll look into this more. We can definitely add more optimizations after-the-fact like that. But with people using utility classes and such I want to be very careful about not accidentally hashifying a className that shouldn’t be hashified

script,
items,
walk(ast.css, {
async enter(node) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the walker wait for Promises here? It might but usually these kinds of things don't, I think. When we refactor this into more distinct parse, optimize, and codegen steps I think we should have some sort of API that the style optimizer (and others) can block on a "finalization" step. That is assuming the walker is sync.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API for an optimize plugin might be something like this (very much pseudocode):

function optimizeStyles() {
   let styleEl;
   let stylePromise;

  return {
    visitors: {
      Head: {
        exit(node) {
           // Inject style at the end.
           styleEl = someInjectFunction('style');
        }
      },
  
      Style: {
        enter(node) {
           const = node.content.styles;
           stylePromise = transformStyle(code ...)
        }
      }
    },

    async finalize() {
      const styles = await stylePromise;
      styleEl.text = styles;
    }
  }
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the walker wait for Promises here?

I don’t think it does, you’re right. I’ll update this to something like your suggestion below

@drwpow drwpow merged commit 5661b28 into main Mar 18, 2021
@drwpow drwpow deleted the styles branch March 18, 2021 17:26
jasikpark pushed a commit to jasikpark/astro that referenced this pull request Jun 15, 2021
…tructuring

Docs fix conflicts and structuring
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

Successfully merging this pull request may close these issues.

None yet

3 participants