笔记 - 模块化CSS

TOC

之前写了一篇笔记 - 模块化JS,记录了模块化使用JS代码,这里记录一下对模块化css的认识,以及在Hugo中模块化css的注意事项。

起因

我之前的做法是网站各个页面都只存在一个css文件,也就说把main.css和页面自定义的page_customize.css之类的文件合并在一起,并且main.css包括了所有基础css内容,哪里有需求了就去改,造成这个文件本身很冗杂,找个元素不是很容易,另外体积也比较大。

组成一个整体的话:加载次数只有一次,但是各个页面的内容不同,每次都需要加载,而且存在冗余重复。

分开显示(main.css+page_customize.css+…): 加载次数会变多,但是相同的部分可以缓存,变相减少加载次数,并且各个部分可以分开,有利于复用。

我现在越来越觉得复用很重要,不然我每次新建一个站我就要重新写一遍,太麻烦了。因为每次都是在同一个main.css里完成所有的内容,并且都是使用自定义的属性(class)来定位元素,所以没办法做到复用。

思考

我完全可以这样做:

css
  | _reset.css
  | _typography.css
  | _var.css
  | _basic.css
  | # more other basic css file
  | _customize.css
  | main.css
  | page_customize.css
  • _reset.css内容较为固定,就是重置浏览器默认的一些设置;

    /*
    Modern Reset
    See https://hankchizljaw.com/wrote/a-modern-css-reset/
    
    */
    
    /* Box sizing rules */
    *,
    *::before,
    *::after {
        box-sizing: border-box;
    }
    
    /* Remove default margin */
    * {
        margin: 0;
    }
    
    ul,
    ol {
        list-style: none;
        padding: 0;
    }
    
    /* Set core root defaults */
    html {
        scroll-behavior: smooth;
    }
    
    /* body {
        -webkit-font-smoothing: antialiased;
    } */
    
    /* A elements that don't have a class get default styles */
    a:not([class]) {
        text-decoration-skip-ink: auto;
    }
    
    a {
        text-decoration: none;
    }
    
    /* Make images easier to work with */
    img,
    picture,
    video,
    canvas,
    svg {
        display: block;
        max-width: 100%;
    }
    
    /* Inherit fonts for inputs and buttons */
    input,
    button,
    textarea,
    select {
        font: inherit;
    }
    
    
    /* Remove all animations, transitions and smooth scroll for people that prefer not to see them */
    @media (prefers-reduced-motion: reduce) {
        html {
            scroll-behavior: auto;
        }
    
        *,
        *::before,
        *::after {
            animation-duration: 0.01ms !important;
            animation-iteration-count: 1 !important;
            scroll-behavior: auto !important;
            transition-duration: 0.01ms !important;
        }
    }
    
    
    @media (prefers-reduced-motion: no-preference) {
        html {
            interpolate-size: allow-keywords;
        }
    }
    
    
    p,
    h1,
    h2,
    h3,
    h4,
    h5,
    h6 {
        overflow-wrap: break-word;
        hyphens: auto;
    }
    
    p {
        text-wrap: pretty;
    }
    
    h1,
    h2,
    h3,
    h4,
    h5,
    h6 {
        text-wrap: balance;
    }
    
  • _typography.css 用来存放字体相关的信息,现在网站使用的字体是我从MDN 上下载的同样的字体;

      @font-face {
      font-display: swap;
      font-family: "Inter";
      font-stretch: 75% 100%;
      font-style: oblique 0deg 20deg;
      font-weight: 1 999;
      src:
        url("/asset/fonts/Inter.var.c2fe3cb2b7c746f7966a.woff2") format("woff2 supports variations"),
        url("/asset/fonts/Inter.var.c2fe3cb2b7c746f7966a.woff2") format("woff2-variations");
    }
    
  • _var.css 存放变量;

  • _basic.css 通用设置,我就用来存放通用的元素选择器相关的css内容,正因为使用元素选择器,所以可以确定通用;

    比如标题,文章内部的样式;

    
    @import "_var.css";
    
    /* Set core body defaults */
    body {
        font-family: var(--font-body);
        font-size: var(--base-font-size);
        line-height: var(--font-content-line-height);
        text-rendering: optimizeSpeed;
        color: var(--text-primary);
        background-color: var(--background-primary);
    }
    
    /* Fix Inter font bug: 'em' not slanted in Safari. See issue: https://github.com/mdn/yari/issues/7203 */
    em {
        font-variation-settings: "slnt" -10;
    }
    
    h1,
    h2,
    h3,
    h4,
    h5,
    h6 {
        font-family: var(--font-heading);
        line-height: var(--heading-line-height);
        letter-spacing: var(--heading-letter-spacing);
    }
    
    h1 {
        font: var(--type-heading-h1);
    }
    h2 {
        font: var(--type-heading-h2);
    }
    h3 {
        font: var(--type-heading-h3);
    }
    h4 {
        font: var(--type-heading-h4);
    }
    h5 {
        font: var(--type-heading-h5);
    }   
    
    
    a {
        color: var(--text-link);
    }
    
    ul ul,
    ul ol,
    ol ul,
    ol ol {
        padding-left: 0.75rem; 
    }
    
    article {
    
        h2, h3, h4, h5, h6 {
            margin: 1rem 0;
        }
    
        p {
            margin: 0.5rem 0;
        }
    
        ol {
            list-style: decimal;
    
            ::marker {
                color: var(--text-link);
                font-weight: bold;
            }
        }
    
        ul {
    
            ::marker {
                content: " - ";
                color: var(--text-link);
                font-weight: bold;
            }
        }
    
        pre {
            padding: 1rem;
            margin: 1rem auto;
            max-height: 40vh;
            overflow: auto;
            border-radius: 0.5rem;
            font-family: var(--font-code);
            font-size: 1rem;
            background-color: var(--code-background-block);
        }
    
    
        code:not(pre code),
        kbd,
        samp {
            padding: 0.25em;
            border-radius: 0.25rem;
            font-size: 1rem;
            word-break: break-all;
            color: var(--text-primary-red);
            background-color: var(--code-background-block);
        }
    
        hr {
            margin: 1rem 0;
        }
    
        del {
            text-decoration-color: var(--text-primary-red);
            text-decoration-thickness: 1px;
            text-decoration-style: double;
        }   
        mark {
            background-color: var(--highlight-bg);
            color: var(--text-primary);
            font-weight: var(--font-body-strong-weight);
        }
    
        dl {
            margin: 1rem 0;
    
            dt {
                font-weight: bold;
                margin: 0.5rem 0;
            }
    
            dd {
                padding-inline-start: 0.75rem;
            }
    
            dd~dd,
            dt~dt {
                margin: 0.5rem 0;
            }
        }
    
        blockquote {
            margin: 1rem 0;
            padding: 0 1rem;
            border-inline-start: 0.2rem solid var(--text-primary-red);
        }
    
        table {
            margin: 1rem auto;
            border-collapse: collapse;
            width: 100%;
            max-width: 100%;
            overflow: auto;
            border-radius: 0.5rem;
            background-color: var(--background-secondary);
    
            thead {
                background-color: var(--background-information);
                border-top: 2px solid var(--text-link);
                border-bottom: 2px solid var(--text-link);
                font-weight: bold;
            }
    
            tbody {
    
                tr {
                    border-bottom: 1px solid var(--text-primary);
                }
    
            }
    
            th,
            td {
                padding: 0.5rem;
                text-align: left;
            }
        }
    
        details {
            margin: 0.5rem 0;
            background-color: var(--background-information);
            border-radius: 0.3rem;
            padding: 0.5rem;
    
            summary {
                color: red;
                font-weight: bold;
            }
        }
    }
    
    
  • _customize.css 用来存放网站自定义的一些设置;

  • main.css 整合其他定义好的内容,也可以用来存放网站自定义的一些设置;

    @import "_reset.css";
    @import "_basic.css";
    @import "_customize.css";
    
    /* other css content */
    
  • page_customize.css各个页面的自定义css;

这样如果网页有自定义的需求,就去改page_customize.css这种页面文件,如果是一个新的网站,也只需要改_customize.css 或者 main.css,其他几个文件可以复制过去。

Hugo中模块化css

hugo官网 有相应的步骤介绍。

  1. homebrew安装npmNode.js : 需要npm这个工具(Hugo写的是下载Node.js, 我也没管,因为我看后边的命令只会用到npm)

    brew install npm
    

    后续:我还是去查了一下,看到最后一条,所以你还是直接安装Node.js吧(brew install node):

    NPM(Node Package Manager)是一个 JavaScript 包管理工具,也是 Node.js 的默认包管理器。

    NPM 允许开发者轻松地下载、安装、共享、管理项目的依赖库和工具。

    NPM 是 Node.js 自带的包管理工具,因此,通常你只需安装 Node.js,NPM 就会自动安装在系统中。

  2. 在你的Hugo网站的根目录下使用npm安装插件

    npm install postcss postcss-cli postcss-import autoprefixer --save-dev
    

    说明:

    • postcsspostcss-cli : 这两个是hugo用来处理css的工具,PostCSS 核心引擎(像汽车的发动机);

    • postcss-import : 处理 CSS 中的 @import 语句(实现文件合并);

    • autoprefixer : 自动添加浏览器前缀(如 -webkit-, -moz-);

    • --save-dev : 将这些包保存为 开发依赖 (devDependencies);

      • 与普通依赖的区别:
        dependenciesdevDependencies
        用途项目运行时需要的依赖仅开发/构建阶段需要的工具
        示例React, VuePostCSS, ESLint
        部署影响会打包到生产环境不会包含在生产代码中
    • 完整的流程:

      1. postcss-import 先处理所有 @import 语句(文件合并)

      2. autoprefixer 后处理合并后的 CSS(添加前缀)

      3. 最后通过 Hugo 的 resources.PostCSS 管道输出

  3. 检查你的Hugo项目根目录,应该有三个文件:

    • package.json(记录依赖)

    • package-lock.json(锁定版本)

    • node_modules/(存放实际安装的包)

  4. 在项目根目录创建 postcss.config.js 文件:

module.exports = {
  plugins: [
      require('postcss-import')({
          path: ['assets/css'] // 指定 CSS 模块的基准路径
      }),
      require('autoprefixer') // 可选:添加浏览器前缀
  ]
}
  1. 模板处理,main.css包括全站基础css,所以是通用的,那么就应该单独存一个文件,这样有利于浏览器缓存,页面自定义的css完全可以合并成一个文件(不过下边处理的代码还是按照多个css文件处理,这样更稳妥),这样一个页面就只有main.csspage_customize.css 两个文件了,数量不多,并且可以区分开,而且可以自己import内容。
{{- /* 加载全局 CSS */}}
{{- with resources.Get "css/main.css" | postCSS }}
{{- if eq hugo.Environment "development" }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{- else }}
{{- with . | minify | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{- end }}
{{- end }}
{{- end }}

{{- /* 加载页面自定义 CSS */}}
{{- if .Params.css }}
{{- range .Params.css }}
{{- with resources.Get . | postCSS }}
{{- if eq hugo.Environment "development" }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{- else }}
{{- with . | minify | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

注意事项

  • 更新问题:

    我最开始是把网站自定义的部分放在_customize.css这个文件,然后在main.css的内容只有:

    @import "_reset.css";
    @import "_basic.css";
    @import "_customize.css";
    

    这样做是可以的,但是如果你做了某个变动,比如编辑了_customize.css这个文件,本地的hugo不会自动更新的,你必须重新启动hugo server才行,这对本地测试来说极其不方便,所以我才改成把一些网站自定义的内容放到main.css中,这样编辑后的结果可以立刻得到反馈。我问过deepseek,给出的回复是缓存问题,并且给的解决方案无效,所以这里就先这样处理。请注意这里我遇到的问题是在本地进行测试的,在线的我还没试过,等我上线以后如果遇到还有这个问题我再在这里说明。