github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/resources/resource_transformers/js/build.go (about) 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 js 15 16 import ( 17 "fmt" 18 "io" 19 "os" 20 "path" 21 "path/filepath" 22 "regexp" 23 "strings" 24 25 "errors" 26 27 "github.com/spf13/afero" 28 29 "github.com/gohugoio/hugo/hugofs" 30 "github.com/gohugoio/hugo/media" 31 32 "github.com/gohugoio/hugo/common/herrors" 33 "github.com/gohugoio/hugo/common/text" 34 35 "github.com/gohugoio/hugo/hugolib/filesystems" 36 "github.com/gohugoio/hugo/resources/internal" 37 38 "github.com/evanw/esbuild/pkg/api" 39 "github.com/gohugoio/hugo/resources" 40 "github.com/gohugoio/hugo/resources/resource" 41 ) 42 43 // Client context for ESBuild. 44 type Client struct { 45 rs *resources.Spec 46 sfs *filesystems.SourceFilesystem 47 } 48 49 // New creates a new client context. 50 func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) *Client { 51 return &Client{ 52 rs: rs, 53 sfs: fs, 54 } 55 } 56 57 type buildTransformation struct { 58 optsm map[string]any 59 c *Client 60 } 61 62 func (t *buildTransformation) Key() internal.ResourceTransformationKey { 63 return internal.NewResourceTransformationKey("jsbuild", t.optsm) 64 } 65 66 func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx) error { 67 ctx.OutMediaType = media.Builtin.JavascriptType 68 69 opts, err := decodeOptions(t.optsm) 70 if err != nil { 71 return err 72 } 73 74 if opts.TargetPath != "" { 75 ctx.OutPath = opts.TargetPath 76 } else { 77 ctx.ReplaceOutPathExtension(".js") 78 } 79 80 src, err := io.ReadAll(ctx.From) 81 if err != nil { 82 return err 83 } 84 85 opts.sourceDir = filepath.FromSlash(path.Dir(ctx.SourcePath)) 86 opts.resolveDir = t.c.rs.Cfg.BaseConfig().WorkingDir // where node_modules gets resolved 87 opts.contents = string(src) 88 opts.mediaType = ctx.InMediaType 89 opts.tsConfig = t.c.rs.ResolveJSConfigFile("tsconfig.json") 90 91 buildOptions, err := toBuildOptions(opts) 92 if err != nil { 93 return err 94 } 95 96 buildOptions.Plugins, err = createBuildPlugins(t.c, opts) 97 if err != nil { 98 return err 99 } 100 101 if buildOptions.Sourcemap == api.SourceMapExternal && buildOptions.Outdir == "" { 102 buildOptions.Outdir, err = os.MkdirTemp(os.TempDir(), "compileOutput") 103 if err != nil { 104 return err 105 } 106 defer os.Remove(buildOptions.Outdir) 107 } 108 109 if opts.Inject != nil { 110 // Resolve the absolute filenames. 111 for i, ext := range opts.Inject { 112 impPath := filepath.FromSlash(ext) 113 if filepath.IsAbs(impPath) { 114 return fmt.Errorf("inject: absolute paths not supported, must be relative to /assets") 115 } 116 117 m := resolveComponentInAssets(t.c.rs.Assets.Fs, impPath) 118 119 if m == nil { 120 return fmt.Errorf("inject: file %q not found", ext) 121 } 122 123 opts.Inject[i] = m.Filename 124 125 } 126 127 buildOptions.Inject = opts.Inject 128 129 } 130 131 result := api.Build(buildOptions) 132 133 if len(result.Errors) > 0 { 134 135 createErr := func(msg api.Message) error { 136 loc := msg.Location 137 if loc == nil { 138 return errors.New(msg.Text) 139 } 140 path := loc.File 141 if path == stdinImporter { 142 path = ctx.SourcePath 143 } 144 145 errorMessage := msg.Text 146 errorMessage = strings.ReplaceAll(errorMessage, nsImportHugo+":", "") 147 148 var ( 149 f afero.File 150 err error 151 ) 152 153 if strings.HasPrefix(path, nsImportHugo) { 154 path = strings.TrimPrefix(path, nsImportHugo+":") 155 f, err = hugofs.Os.Open(path) 156 } else { 157 var fi os.FileInfo 158 fi, err = t.c.sfs.Fs.Stat(path) 159 if err == nil { 160 m := fi.(hugofs.FileMetaInfo).Meta() 161 path = m.Filename 162 f, err = m.Open() 163 } 164 165 } 166 167 if err == nil { 168 fe := herrors. 169 NewFileErrorFromName(errors.New(errorMessage), path). 170 UpdatePosition(text.Position{Offset: -1, LineNumber: loc.Line, ColumnNumber: loc.Column}). 171 UpdateContent(f, nil) 172 173 f.Close() 174 return fe 175 } 176 177 return fmt.Errorf("%s", errorMessage) 178 } 179 180 var errors []error 181 182 for _, msg := range result.Errors { 183 errors = append(errors, createErr(msg)) 184 } 185 186 // Return 1, log the rest. 187 for i, err := range errors { 188 if i > 0 { 189 t.c.rs.Logger.Errorf("js.Build failed: %s", err) 190 } 191 } 192 193 return errors[0] 194 } 195 196 if buildOptions.Sourcemap == api.SourceMapExternal { 197 content := string(result.OutputFiles[1].Contents) 198 symPath := path.Base(ctx.OutPath) + ".map" 199 re := regexp.MustCompile(`//# sourceMappingURL=.*\n?`) 200 content = re.ReplaceAllString(content, "//# sourceMappingURL="+symPath+"\n") 201 202 if err = ctx.PublishSourceMap(string(result.OutputFiles[0].Contents)); err != nil { 203 return err 204 } 205 _, err := ctx.To.Write([]byte(content)) 206 if err != nil { 207 return err 208 } 209 } else { 210 _, err := ctx.To.Write(result.OutputFiles[0].Contents) 211 if err != nil { 212 return err 213 } 214 } 215 return nil 216 } 217 218 // Process process esbuild transform 219 func (c *Client) Process(res resources.ResourceTransformer, opts map[string]any) (resource.Resource, error) { 220 return res.Transform( 221 &buildTransformation{c: c, optsm: opts}, 222 ) 223 }