transform.go (4451B)
1 // Copyright 2020 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 32 "github.com/spf13/afero" 33 34 "github.com/gohugoio/hugo/hugofs" 35 36 "github.com/bep/godartsass" 37 ) 38 39 const ( 40 dartSassEmbeddedBinaryName = "dart-sass-embedded" 41 ) 42 43 // Supports returns whether dart-sass-embedded is found in $PATH. 44 func Supports() bool { 45 if htesting.SupportsAll() { 46 return true 47 } 48 return hexec.InPath(dartSassEmbeddedBinaryName) 49 } 50 51 type transform struct { 52 optsm map[string]any 53 c *Client 54 } 55 56 func (t *transform) Key() internal.ResourceTransformationKey { 57 return internal.NewResourceTransformationKey(transformationName, t.optsm) 58 } 59 60 func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error { 61 ctx.OutMediaType = media.CSSType 62 63 opts, err := decodeOptions(t.optsm) 64 if err != nil { 65 return err 66 } 67 68 if opts.TargetPath != "" { 69 ctx.OutPath = opts.TargetPath 70 } else { 71 ctx.ReplaceOutPathExtension(".css") 72 } 73 74 baseDir := path.Dir(ctx.SourcePath) 75 filename := dartSassStdinPrefix 76 77 if ctx.SourcePath != "" { 78 filename += t.c.sfs.RealFilename(ctx.SourcePath) 79 } 80 81 args := godartsass.Args{ 82 URL: filename, 83 IncludePaths: t.c.sfs.RealDirs(baseDir), 84 ImportResolver: importResolver{ 85 baseDir: baseDir, 86 c: t.c, 87 }, 88 OutputStyle: godartsass.ParseOutputStyle(opts.OutputStyle), 89 EnableSourceMap: opts.EnableSourceMap, 90 } 91 92 // Append any workDir relative include paths 93 for _, ip := range opts.IncludePaths { 94 info, err := t.c.workFs.Stat(filepath.Clean(ip)) 95 if err == nil { 96 filename := info.(hugofs.FileMetaInfo).Meta().Filename 97 args.IncludePaths = append(args.IncludePaths, filename) 98 } 99 } 100 101 if ctx.InMediaType.SubType == media.SASSType.SubType { 102 args.SourceSyntax = godartsass.SourceSyntaxSASS 103 } 104 105 res, err := t.c.toCSS(args, ctx.From) 106 if err != nil { 107 return err 108 } 109 110 out := res.CSS 111 112 _, err = io.WriteString(ctx.To, out) 113 if err != nil { 114 return err 115 } 116 117 if opts.EnableSourceMap && res.SourceMap != "" { 118 if err := ctx.PublishSourceMap(res.SourceMap); err != nil { 119 return err 120 } 121 _, err = fmt.Fprintf(ctx.To, "\n\n/*# sourceMappingURL=%s */", path.Base(ctx.OutPath)+".map") 122 } 123 124 return err 125 } 126 127 type importResolver struct { 128 baseDir string 129 c *Client 130 } 131 132 func (t importResolver) CanonicalizeURL(url string) (string, error) { 133 filePath, isURL := paths.UrlToFilename(url) 134 var prevDir string 135 var pathDir string 136 if isURL { 137 var found bool 138 prevDir, found = t.c.sfs.MakePathRelative(filepath.Dir(filePath)) 139 140 if !found { 141 // Not a member of this filesystem, let Dart Sass handle it. 142 return "", nil 143 } 144 } else { 145 prevDir = t.baseDir 146 pathDir = path.Dir(url) 147 } 148 149 basePath := filepath.Join(prevDir, pathDir) 150 name := filepath.Base(filePath) 151 152 // Pick the first match. 153 var namePatterns []string 154 if strings.Contains(name, ".") { 155 namePatterns = []string{"_%s", "%s"} 156 } else if strings.HasPrefix(name, "_") { 157 namePatterns = []string{"_%s.scss", "_%s.sass"} 158 } else { 159 namePatterns = []string{"_%s.scss", "%s.scss", "_%s.sass", "%s.sass"} 160 } 161 162 name = strings.TrimPrefix(name, "_") 163 164 for _, namePattern := range namePatterns { 165 filenameToCheck := filepath.Join(basePath, fmt.Sprintf(namePattern, name)) 166 fi, err := t.c.sfs.Fs.Stat(filenameToCheck) 167 if err == nil { 168 if fim, ok := fi.(hugofs.FileMetaInfo); ok { 169 return "file://" + filepath.ToSlash(fim.Meta().Filename), nil 170 } 171 } 172 } 173 174 // Not found, let Dart Dass handle it 175 return "", nil 176 } 177 178 func (t importResolver) Load(url string) (string, error) { 179 filename, _ := paths.UrlToFilename(url) 180 b, err := afero.ReadFile(hugofs.Os, filename) 181 return string(b), err 182 }