github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/resources/resource_transformers/tocss/dartsass/transform.go (about) 1 // Copyright 2022 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package dartsass 15 16 import ( 17 "fmt" 18 "io" 19 "path" 20 "path/filepath" 21 "strings" 22 23 "github.com/gohugoio/hugo/common/hexec" 24 "github.com/gohugoio/hugo/common/paths" 25 "github.com/gohugoio/hugo/htesting" 26 "github.com/gohugoio/hugo/media" 27 28 "github.com/gohugoio/hugo/resources" 29 30 "github.com/gohugoio/hugo/resources/internal" 31 "github.com/gohugoio/hugo/resources/resource_transformers/tocss/internal/sass" 32 33 "github.com/spf13/afero" 34 35 "github.com/gohugoio/hugo/hugofs" 36 37 "github.com/bep/godartsass" 38 ) 39 40 const ( 41 dartSassEmbeddedBinaryName = "dart-sass-embedded" 42 ) 43 44 // Supports returns whether dart-sass-embedded is found in $PATH. 45 func Supports() bool { 46 if htesting.SupportsAll() { 47 return true 48 } 49 return hexec.InPath(dartSassEmbeddedBinaryName) 50 } 51 52 type transform struct { 53 optsm map[string]any 54 c *Client 55 } 56 57 func (t *transform) Key() internal.ResourceTransformationKey { 58 return internal.NewResourceTransformationKey(transformationName, t.optsm) 59 } 60 61 func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error { 62 ctx.OutMediaType = media.CSSType 63 64 opts, err := decodeOptions(t.optsm) 65 if err != nil { 66 return err 67 } 68 69 if opts.TargetPath != "" { 70 ctx.OutPath = opts.TargetPath 71 } else { 72 ctx.ReplaceOutPathExtension(".css") 73 } 74 75 baseDir := path.Dir(ctx.SourcePath) 76 filename := dartSassStdinPrefix 77 78 if ctx.SourcePath != "" { 79 filename += t.c.sfs.RealFilename(ctx.SourcePath) 80 } 81 82 args := godartsass.Args{ 83 URL: filename, 84 IncludePaths: t.c.sfs.RealDirs(baseDir), 85 ImportResolver: importResolver{ 86 baseDir: baseDir, 87 c: t.c, 88 89 varsStylesheet: sass.CreateVarsStyleSheet(opts.Vars), 90 }, 91 OutputStyle: godartsass.ParseOutputStyle(opts.OutputStyle), 92 EnableSourceMap: opts.EnableSourceMap, 93 SourceMapIncludeSources: opts.SourceMapIncludeSources, 94 } 95 96 // Append any workDir relative include paths 97 for _, ip := range opts.IncludePaths { 98 info, err := t.c.workFs.Stat(filepath.Clean(ip)) 99 if err == nil { 100 filename := info.(hugofs.FileMetaInfo).Meta().Filename 101 args.IncludePaths = append(args.IncludePaths, filename) 102 } 103 } 104 105 if ctx.InMediaType.SubType == media.SASSType.SubType { 106 args.SourceSyntax = godartsass.SourceSyntaxSASS 107 } 108 109 res, err := t.c.toCSS(args, ctx.From) 110 if err != nil { 111 return err 112 } 113 114 out := res.CSS 115 116 _, err = io.WriteString(ctx.To, out) 117 if err != nil { 118 return err 119 } 120 121 if opts.EnableSourceMap && res.SourceMap != "" { 122 if err := ctx.PublishSourceMap(res.SourceMap); err != nil { 123 return err 124 } 125 _, err = fmt.Fprintf(ctx.To, "\n\n/*# sourceMappingURL=%s */", path.Base(ctx.OutPath)+".map") 126 } 127 128 return err 129 } 130 131 type importResolver struct { 132 baseDir string 133 c *Client 134 135 varsStylesheet string 136 } 137 138 func (t importResolver) CanonicalizeURL(url string) (string, error) { 139 if url == sass.HugoVarsNamespace { 140 return url, nil 141 } 142 filePath, isURL := paths.UrlToFilename(url) 143 var prevDir string 144 var pathDir string 145 if isURL { 146 var found bool 147 prevDir, found = t.c.sfs.MakePathRelative(filepath.Dir(filePath)) 148 149 if !found { 150 // Not a member of this filesystem, let Dart Sass handle it. 151 return "", nil 152 } 153 } else { 154 prevDir = t.baseDir 155 pathDir = path.Dir(url) 156 } 157 158 basePath := filepath.Join(prevDir, pathDir) 159 name := filepath.Base(filePath) 160 161 // Pick the first match. 162 var namePatterns []string 163 if strings.Contains(name, ".") { 164 namePatterns = []string{"_%s", "%s"} 165 } else if strings.HasPrefix(name, "_") { 166 namePatterns = []string{"_%s.scss", "_%s.sass", "_%s.css"} 167 } else { 168 namePatterns = []string{"_%s.scss", "%s.scss", "_%s.sass", "%s.sass", "_%s.css", "%s.css"} 169 } 170 171 name = strings.TrimPrefix(name, "_") 172 173 for _, namePattern := range namePatterns { 174 filenameToCheck := filepath.Join(basePath, fmt.Sprintf(namePattern, name)) 175 fi, err := t.c.sfs.Fs.Stat(filenameToCheck) 176 if err == nil { 177 if fim, ok := fi.(hugofs.FileMetaInfo); ok { 178 return "file://" + filepath.ToSlash(fim.Meta().Filename), nil 179 } 180 } 181 } 182 183 // Not found, let Dart Dass handle it 184 return "", nil 185 } 186 187 func (t importResolver) Load(url string) (string, error) { 188 if url == sass.HugoVarsNamespace { 189 return t.varsStylesheet, nil 190 } 191 filename, _ := paths.UrlToFilename(url) 192 b, err := afero.ReadFile(hugofs.Os, filename) 193 return string(b), err 194 }