github.com/dominikszabo/hugo-ds-clean@v0.47.1/resource/tocss/scss/tocss.go (about) 1 // Copyright 2018 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 // +build extended 15 16 package scss 17 18 import ( 19 "fmt" 20 "io" 21 "path" 22 "path/filepath" 23 "strings" 24 25 "github.com/bep/go-tocss/scss" 26 "github.com/bep/go-tocss/scss/libsass" 27 "github.com/bep/go-tocss/tocss" 28 "github.com/gohugoio/hugo/helpers" 29 "github.com/gohugoio/hugo/hugofs" 30 "github.com/gohugoio/hugo/media" 31 "github.com/gohugoio/hugo/resource" 32 ) 33 34 // Used in tests. This feature requires Hugo to be built with the extended tag. 35 func Supports() bool { 36 return true 37 } 38 39 func (t *toCSSTransformation) Transform(ctx *resource.ResourceTransformationCtx) error { 40 ctx.OutMediaType = media.CSSType 41 42 var outName string 43 if t.options.from.TargetPath != "" { 44 ctx.OutPath = t.options.from.TargetPath 45 } else { 46 ctx.ReplaceOutPathExtension(".css") 47 } 48 49 outName = path.Base(ctx.OutPath) 50 51 options := t.options 52 baseDir := path.Dir(ctx.SourcePath) 53 options.to.IncludePaths = t.c.sfs.RealDirs(baseDir) 54 55 // Append any workDir relative include paths 56 for _, ip := range options.from.IncludePaths { 57 options.to.IncludePaths = append(options.to.IncludePaths, t.c.workFs.RealDirs(filepath.Clean(ip))...) 58 } 59 60 // To allow for overrides of SCSS files anywhere in the project/theme hierarchy, we need 61 // to help libsass revolve the filename by looking in the composite filesystem first. 62 // We add the entry directories for both project and themes to the include paths list, but 63 // that only work for overrides on the top level. 64 options.to.ImportResolver = func(url string, prev string) (newUrl string, body string, resolved bool) { 65 // We get URL paths from LibSASS, but we need file paths. 66 url = filepath.FromSlash(url) 67 prev = filepath.FromSlash(prev) 68 69 var basePath string 70 urlDir := filepath.Dir(url) 71 var prevDir string 72 if prev == "stdin" { 73 prevDir = baseDir 74 } else { 75 prevDir = t.c.sfs.MakePathRelative(filepath.Dir(prev)) 76 if prevDir == "" { 77 // Not a member of this filesystem. Let LibSASS handle it. 78 return "", "", false 79 } 80 } 81 82 basePath = filepath.Join(prevDir, urlDir) 83 name := filepath.Base(url) 84 85 // Libsass throws an error in cases where you have several possible candidates. 86 // We make this simpler and pick the first match. 87 var namePatterns []string 88 if strings.Contains(name, ".") { 89 namePatterns = []string{"_%s", "%s"} 90 } else if strings.HasPrefix(name, "_") { 91 namePatterns = []string{"_%s.scss", "_%s.sass"} 92 } else { 93 namePatterns = []string{"_%s.scss", "%s.scss", "_%s.sass", "%s.sass"} 94 } 95 96 name = strings.TrimPrefix(name, "_") 97 98 for _, namePattern := range namePatterns { 99 filenameToCheck := filepath.Join(basePath, fmt.Sprintf(namePattern, name)) 100 fi, err := t.c.sfs.Fs.Stat(filenameToCheck) 101 if err == nil { 102 if fir, ok := fi.(hugofs.RealFilenameInfo); ok { 103 return fir.RealFilename(), "", true 104 } 105 } 106 } 107 108 // Not found, let LibSASS handle it 109 return "", "", false 110 } 111 112 if ctx.InMediaType.SubType == media.SASSType.SubType { 113 options.to.SassSyntax = true 114 } 115 116 if options.from.EnableSourceMap { 117 118 options.to.SourceMapFilename = outName + ".map" 119 options.to.SourceMapRoot = t.c.rs.WorkingDir 120 121 // Setting this to the relative input filename will get the source map 122 // more correct for the main entry path (main.scss typically), but 123 // it will mess up the import mappings. As a workaround, we do a replacement 124 // in the source map itself (see below). 125 //options.InputPath = inputPath 126 options.to.OutputPath = outName 127 options.to.SourceMapContents = true 128 options.to.OmitSourceMapURL = false 129 options.to.EnableEmbeddedSourceMap = false 130 } 131 132 res, err := t.c.toCSS(options.to, ctx.To, ctx.From) 133 if err != nil { 134 return err 135 } 136 137 if options.from.EnableSourceMap && res.SourceMapContent != "" { 138 sourcePath := t.c.sfs.RealFilename(ctx.SourcePath) 139 140 if strings.HasPrefix(sourcePath, t.c.rs.WorkingDir) { 141 sourcePath = strings.TrimPrefix(sourcePath, t.c.rs.WorkingDir+helpers.FilePathSeparator) 142 } 143 144 // This needs to be Unix-style slashes, even on Windows. 145 // See https://github.com/gohugoio/hugo/issues/4968 146 sourcePath = filepath.ToSlash(sourcePath) 147 148 // This is a workaround for what looks like a bug in Libsass. But 149 // getting this resolution correct in tools like Chrome Workspaces 150 // is important enough to go this extra mile. 151 mapContent := strings.Replace(res.SourceMapContent, `stdin",`, fmt.Sprintf("%s\",", sourcePath), 1) 152 153 return ctx.PublishSourceMap(mapContent) 154 } 155 return nil 156 } 157 158 func (c *Client) toCSS(options scss.Options, dst io.Writer, src io.Reader) (tocss.Result, error) { 159 var res tocss.Result 160 161 transpiler, err := libsass.New(options) 162 if err != nil { 163 return res, err 164 } 165 166 res, err = transpiler.Execute(dst, src) 167 if err != nil { 168 return res, fmt.Errorf("SCSS processing failed: %s", err) 169 } 170 171 return res, nil 172 }